launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #26242
[Merge] ~cjwatson/lp-codeimport:charm into lp-codeimport:master
Colin Watson has proposed merging ~cjwatson/lp-codeimport:charm into lp-codeimport:master with ~cjwatson/lp-codeimport:jenkaas-secrets as a prerequisite.
Commit message:
Add a Juju charm
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/lp-codeimport/+git/lp-codeimport/+merge/397657
This is based on the layers provided by lp:ols-charm-deps.
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/lp-codeimport:charm into lp-codeimport:master.
diff --git a/Makefile b/Makefile
index ae16dc5..aa90c00 100644
--- a/Makefile
+++ b/Makefile
@@ -178,6 +178,7 @@ build-tarball: build_wheels
--exclude .git \
--exclude .gitignore \
--exclude build \
+ --exclude charm \
--exclude env \
--exclude logs \
--exclude pip-cache \
diff --git a/charm/.gitignore b/charm/.gitignore
new file mode 100644
index 0000000..04ae82c
--- /dev/null
+++ b/charm/.gitignore
@@ -0,0 +1,3 @@
+bundle.yaml
+dist
+tmp
diff --git a/charm/Makefile b/charm/Makefile
new file mode 100644
index 0000000..eccdb3c
--- /dev/null
+++ b/charm/Makefile
@@ -0,0 +1,131 @@
+# The charm tool is shipped as a snap, so make sure it's on $PATH.
+export PATH := $(PATH):/snap/bin
+
+APP_NAME := lp-codeimport
+
+BUILDDIR := $(CURDIR)/dist
+TMPDIR := $(CURDIR)/tmp
+export CHARM_LAYERS_DIR := $(TMPDIR)/deps/ols-layers/layer
+export CHARM_INTERFACES_DIR := $(TMPDIR)/deps/ols-layers/interface
+CHARM_WHEELS_DIR := $(TMPDIR)/deps/charm-wheels
+
+BUILD_LABEL = $(shell git rev-parse HEAD)
+TARBALL = $(APP_NAME).tar.gz
+ASSET = ../build/$(BUILD_LABEL)/$(TARBALL)
+
+CHARMS := lp-codeimport
+
+PUBLISH_REPO_PREFIX := lp:~launchpad/lp-codeimport/+git/charm-build-
+PUBLISHDIR := $(BUILDDIR)/publish
+# We may need to force username and email when publishing, because git may
+# not be able to autodetect this in automatic build environments.
+DOMAIN ?= $(shell hostname -f)
+GIT_USERNAME = $(shell git config --get user.name || echo $(USER))
+GIT_EMAIL = $(shell git config --get user.email || echo $(USER)@$(DOMAIN))
+
+all: build lint
+
+$(BUILDDIR) $(TMPDIR) $(PUBLISHDIR):
+ @mkdir -p $@
+
+CHARM_DEPS := $(CHARM_LAYERS_DIR)/.done $(CHARM_INTERFACES_DIR)/.done
+$(CHARM_DEPS): $(CURDIR)/dependencies.txt | $(TMPDIR)
+ @echo "Fetching dependencies..."
+ @mkdir -p $(TMPDIR)/deps
+ @cd $(TMPDIR)/deps && codetree $<
+ @touch $(CHARM_DEPS)
+
+build: $(foreach charm,$(CHARMS),build-$(charm))
+
+build-lp-codeimport: dist/.built-lp-codeimport
+
+dist/.built-%: $(CHARM_DEPS) | $(BUILDDIR)
+ @echo "Building $*..."
+ @cd $* && \
+ PIP_NO_INDEX=true PIP_FIND_LINKS=$(CHARM_WHEELS_DIR) \
+ charm build -o $(BUILDDIR)
+ @touch $@
+
+clean-%:
+ @echo "Cleaning $*..."
+ @rm -rf dist/.built-$* dist/$* $(PUBLISHDIR)/$*
+
+tmp/ssh-key: | $(TMPDIR)
+ ssh-keygen -t rsa -b 2048 -f $@ -N ''
+
+tmp/gpg-key.sec: | $(TMPDIR)
+ mkdir -p -m0700 $(TMPDIR)/gpg
+ cp test-gpg-parameters $(TMPDIR)/gpg/parameters
+ gpg --homedir $(TMPDIR)/gpg \
+ --batch --generate-key $(TMPDIR)/gpg/parameters
+ gpg --homedir $(TMPDIR)/gpg --export-secret-key --armor \
+ >tmp/gpg-key.sec
+ gpg --homedir $(TMPDIR)/gpg --export --armor >tmp/gpg-key.pub
+
+bundle.yaml: bundle.yaml.in tmp/ssh-key tmp/gpg-key.sec
+ sed \
+ -e 's/%BUILD_LABEL%/$(BUILD_LABEL)/g' \
+ -e "s/%PRIVATE_SSH_KEY%/$$(base64 -w 0 <tmp/ssh-key)/g" \
+ -e "s/%PUBLIC_SSH_KEY%/$$(base64 -w 0 <tmp/ssh-key.pub)/g" \
+ -e "s/%PRIVATE_GPG_KEY%/$$(base64 -w 0 <tmp/gpg-key.sec)/g" \
+ -e "s/%PUBLIC_GPG_KEY%/$$(base64 -w 0 <tmp/gpg-key.pub)/g" \
+ bundle.yaml.in >bundle.yaml
+
+deploy: build payload bundle.yaml
+ @echo "Deploying $(APP_NAME)..."
+ @juju deploy ./bundle.yaml
+
+payload: $(ASSET)
+$(ASSET):
+ @echo "Building asset for $(BUILD_LABEL)..."
+ @$(MAKE) -C .. build-tarball
+
+clean:
+ @find . -name \*.pyc -delete
+ @find . -depth -name '__pycache__' -exec rm -rf '{}' \;
+ @rm -f bundle.yaml
+ @rm -f layer/*/codetree-collect-info.yaml
+ @rm -rf $(BUILDDIR) $(TMPDIR)
+
+lint: build
+ @echo "Linting charms..."
+ @set -e; for charm in $(CHARMS); do \
+ charm proof dist/$$charm; \
+ done
+ @echo "Linting python sources..."
+ @flake8 layer $(CHARMS)
+
+publish: build lint | $(PUBLISHDIR)
+ @set -e; for charm in $(CHARMS); do \
+ if [ -d $(PUBLISHDIR)/$$charm ]; then \
+ git -C $(PUBLISHDIR)/$$charm pull; \
+ else \
+ git clone $(PUBLISH_REPO_PREFIX)$$charm \
+ $(PUBLISHDIR)/$$charm; \
+ fi; \
+ rsync -a -m --ignore-times --exclude .git --delete \
+ dist/$$charm/ $(PUBLISHDIR)/$$charm/; \
+ git -C $(PUBLISHDIR)/$$charm add .; \
+ if [ "$$(git -C $(PUBLISHDIR)/$$charm status --porcelain || \
+ echo status failed)" ]; then \
+ git -C $(PUBLISHDIR)/$$charm \
+ -c user.name="$(GIT_USERNAME)" \
+ -c user.email="$(GIT_EMAIL)" \
+ commit -a \
+ -m "Build of $$charm from $(BUILD_LABEL)"; \
+ git -C $(PUBLISHDIR)/$$charm tag build/$(BUILD_LABEL); \
+ fi; \
+ git -C $(PUBLISHDIR)/$$charm push --tags origin master; \
+ done
+
+# Prepare a Jenkins-as-a-service container for charm building.
+setup-jenkaas:
+ sudo systemctl stop snapd.socket
+ sudo systemctl stop snapd
+ echo SNAPPY_STORE_NO_CDN=1 | sudo tee -a /etc/environment >/dev/null
+ echo SNAPPY_TESTING=1 | sudo tee -a /etc/environment >/dev/null
+ sudo systemctl start snapd.socket
+ sudo snap install --classic charm
+
+.PHONY: $(foreach charm,$(CHARMS),build-$(charm))
+.PHONY: all build clean deploy lint payload publish setup-jenkaas
diff --git a/charm/README.md b/charm/README.md
new file mode 100644
index 0000000..56a23d0
--- /dev/null
+++ b/charm/README.md
@@ -0,0 +1,20 @@
+# Overview
+
+This charm provides the Launchpad code import worker.
+
+# Usage
+
+ $ juju add-model lp-codeimport
+ $ make deploy
+
+You'll also need corresponding test deployments of Launchpad and turnip. To
+set up the code import worker to be able to push to your local turnip
+deployment, you'll need to do something like this:
+
+ $ juju config -m lp-codeimport lp-codeimport \
+ git_certificate="$(juju config -m turnip haproxy ssl_cert)"
+
+# Contact Information
+
+Colin Watson <cjwatson@xxxxxxxxxxxxx>
+https://launchpad.net/~cjwatson
diff --git a/charm/bundle.yaml.in b/charm/bundle.yaml.in
new file mode 100644
index 0000000..6ebcbdd
--- /dev/null
+++ b/charm/bundle.yaml.in
@@ -0,0 +1,15 @@
+series: xenial
+description: "lp-codeimport development bundle"
+applications:
+ lp-codeimport:
+ charm: ./dist/lp-codeimport
+ num_units: 1
+ options:
+ build_label: "%BUILD_LABEL%"
+ private_ssh_key: "%PRIVATE_SSH_KEY%"
+ public_ssh_key: "%PUBLIC_SSH_KEY%"
+ bzr_identity: "VCS Imports <vcs-imports@xxxxxxxxxxxxxx>"
+ private_gpg_key: "%PRIVATE_GPG_KEY%"
+ public_gpg_key: "%PUBLIC_GPG_KEY%"
+ resources:
+ lp-codeimport: "../build/%BUILD_LABEL%/lp-codeimport.tar.gz"
diff --git a/charm/dependencies.txt b/charm/dependencies.txt
new file mode 100644
index 0000000..983566b
--- /dev/null
+++ b/charm/dependencies.txt
@@ -0,0 +1,2 @@
+ols-layers git+ssh://git.launchpad.net/~ubuntuone-pqm-team/ols-charm-deps/+git/ols-layers;revno=4b403705
+charm-wheels git+ssh://git.launchpad.net/~ubuntuone-hackers/ols-charm-deps/+git/wheels;revno=c38224af
diff --git a/charm/lp-codeimport/config.yaml b/charm/lp-codeimport/config.yaml
new file mode 100644
index 0000000..4a1489a
--- /dev/null
+++ b/charm/lp-codeimport/config.yaml
@@ -0,0 +1,80 @@
+options:
+ git_hostname:
+ type: string
+ default: git.launchpad.test
+ description: The hostname of the Launchpad Git server.
+ git_certificate:
+ type: string
+ default: ""
+ description: >
+ Base64-encoded TLS certificate for the Launchpad Git server. The
+ default is to use the global CA infrastructure.
+ bazaar_branch_store:
+ type: string
+ default: sftp://hoover@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/srv/importd/www/
+ description: Where the Bazaar imports are stored.
+ foreign_tree_store:
+ type: string
+ default: sftp://hoover@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/srv/importd/sources/
+ description: >
+ Where the tarballs of foreign branches are uploaded for storage.
+ private_ssh_key:
+ type: string
+ default: ""
+ description: >
+ Base64-encoded private SSH key, used to communicate with the Launchpad
+ Bazaar server.
+ public_ssh_key:
+ type: string
+ default: ""
+ description: >
+ Base64-encoded public SSH key, used to communicate with the Launchpad
+ Bazaar server.
+ bzr_identity:
+ type: string
+ default: ""
+ description: Email identity used when making Bazaar commits.
+ private_gpg_key:
+ type: string
+ default: ""
+ description: >
+ Base64-encoded private GPG key, used when signing Bazaar commits.
+ public_gpg_key:
+ type: string
+ default: ""
+ description: >
+ Base64-encoded public GPG key, used when signing Bazaar commits.
+ scheduler_endpoint:
+ type: string
+ default: http://xmlrpc-private.launchpad.test:8087/codeimportscheduler
+ description: Where to find the code import scheduler service.
+ oops_prefix:
+ type: string
+ default: DEVEL
+ description: A prefix for OOPS codes for this instance.
+ rabbitmq_host:
+ type: string
+ default: ""
+ description: The host:port at which RabbitMQ is listening.
+ rabbitmq_user:
+ type: string
+ default: ""
+ description: The RabbitMQ user name.
+ rabbitmq_password:
+ type: string
+ default: ""
+ description: The RabbitMQ password.
+ rabbitmq_virtual_host:
+ type: string
+ default: ""
+ description: The RabbitMQ virtual host name.
+ error_email:
+ type: string
+ default: ""
+ description: An email address where errors are sent.
+ log_hosts_allow:
+ type: string
+ default: ""
+ description: >
+ Hosts that should be allowed to rsync logs. Note that this relies on
+ basenode.
diff --git a/charm/lp-codeimport/icon.svg b/charm/lp-codeimport/icon.svg
new file mode 100644
index 0000000..b7920cc
--- /dev/null
+++ b/charm/lp-codeimport/icon.svg
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="100px"
+ height="100px"
+ viewBox="0 0 100 100"
+ version="1.1"
+ id="svg18"
+ sodipodi:docname="lp-charm-icon.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata22">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>eclispe-che</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1016"
+ id="namedview20"
+ showgrid="false"
+ inkscape:pagecheckerboard="true"
+ inkscape:zoom="1.668772"
+ inkscape:cx="29.156684"
+ inkscape:cy="41.621867"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer2"
+ inkscape:snap-bbox="false"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:snap-object-midpoints="true" />
+ <!-- Generator: Sketch 45.2 (43514) - http://www.bohemiancoding.com/sketch -->
+ <title
+ id="title2">eclispe-che</title>
+ <desc
+ id="desc4">Created with Sketch.</desc>
+ <defs
+ id="defs7">
+ <path
+ d="M50.0004412,4.04252804e-14 C22.3871247,4.04252804e-14 0,22.3848726 0,49.9995588 C0,77.6133626 22.3871247,100 50.0004412,100 C77.6137577,100 100,77.6133626 100,49.9995588 C100,22.3848726 77.6128753,3.55271368e-14 50.0004412,4.04252804e-14 Z"
+ id="path-1" />
+ <style
+ id="style6"
+ type="text/css">
+
+ .fil0 {fill:#F8C300}
+ .fil6 {fill:#3895BD}
+ .fil3 {fill:#3941BF}
+ .fil1 {fill:#8FB635}
+ .fil4 {fill:#A02C35}
+ .fil7 {fill:#BB3A84}
+ .fil5 {fill:#D18C3B}
+ .fil8 {fill:#72706F;fill-rule:nonzero}
+ .fil9 {fill:#F8C300;fill-rule:nonzero}
+ .fil2 {fill:white;fill-rule:nonzero}
+
+ </style>
+ </defs>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="BACKGROUND">
+ <g
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-opacity:1"
+ id="Page-1">
+ <g
+ id="eclispe-che"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1">
+ <g
+ id="path3023-path"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1">
+ <use
+ xlink:href="#path-1"
+ id="use9"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-opacity:1"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ <path
+ d="M 50.000441,0.5 C 22.662621,0.5 0.5,22.661661 0.5,49.999559 0.5,77.337051 22.663098,99.5 50.000441,99.5 77.337613,99.5 99.5,77.337222 99.5,49.999559 99.5,22.661796 77.337514,0.5 50.000441,0.5 Z"
+ id="path11"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="PLACE LOGO HERE">
+ <g
+ style="fill-rule:evenodd"
+ id="g3473"
+ transform="matrix(3.2292607,0,0,3.2292607,-486.24769,-600.53027)">
+ <g
+ id="g3489">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#3895bd;fill-opacity:1;fill-rule:evenodd"
+ d="m 165.22326,188.53265 -9.92868,5.72521 4.76477,2.75658 5.15144,-2.96862 z"
+ id="path3475" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#8fb635;fill-opacity:1;fill-rule:evenodd"
+ d="m 166.90714,188.53265 v 5.51317 l 5.15144,2.96862 4.77724,-2.75658 z"
+ id="path3477" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#f8c300;fill-rule:evenodd"
+ d="m 166.05897,195.50518 -5.15144,2.96863 v 5.96219 l 5.15144,2.95616 5.15143,-2.95616 v -5.96219 z"
+ id="path3479" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#d18c3b;fill-opacity:1;fill-rule:evenodd"
+ d="m 177.67153,195.71723 -4.77724,2.76905 v 5.93725 l 4.77724,2.75658 z"
+ id="path3481" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#3941bf;fill-opacity:1;fill-rule:evenodd"
+ d="m 154.4464,195.7297 v 11.45041 l 4.77724,-2.75658 v -5.94972 z"
+ id="path3483" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#bb3a84;fill-opacity:1;fill-rule:evenodd"
+ d="m 160.05935,205.8829 -4.76477,2.75658 9.91621,5.73768 V 208.864 l -5.13897,-2.9811 z"
+ id="path3485" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#a02c35;fill-opacity:1;fill-rule:evenodd"
+ d="m 172.05858,205.89537 -5.15144,2.96863 v 5.51316 l 9.91621,-5.73768 z"
+ id="path3487" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/charm/lp-codeimport/layer.yaml b/charm/lp-codeimport/layer.yaml
new file mode 100644
index 0000000..c5cd846
--- /dev/null
+++ b/charm/lp-codeimport/layer.yaml
@@ -0,0 +1,25 @@
+includes:
+ - layer:ols
+options:
+ apt:
+ packages:
+ - bzr
+ - cvs
+ - git
+ - libffi-dev
+ - libssl-dev
+ - libsvn-dev
+ - python
+ - python-pkg-resources
+ - python-sqlite
+ - python-tdb
+ - subversion
+ - virtualenv
+ ols:
+ service_name: lp-codeimport
+ config_filename: service.conf
+ user: importd
+ tarball_payload: true
+ symlink_switch_payload: true
+ python_bin: python2.7
+repo: https://git.launchpad.net/lp-codeimport
diff --git a/charm/lp-codeimport/metadata.yaml b/charm/lp-codeimport/metadata.yaml
new file mode 100644
index 0000000..f509db3
--- /dev/null
+++ b/charm/lp-codeimport/metadata.yaml
@@ -0,0 +1,16 @@
+name: lp-codeimport
+display-name: lp-codeimport
+summary: Launchpad code import worker
+maintainer: Colin Watson <cjwatson@xxxxxxxxxxxxx>
+description: A worker for importing from other version control repositories.
+tags:
+ # https://juju.is/docs/charm-metadata#heading--charm-store-fields
+ - network
+series:
+ - xenial
+subordinate: false
+resources:
+ lp-codeimport:
+ type: file
+ filename: lp-codeimport.tar.gz
+ description: lp-codeimport code
diff --git a/charm/lp-codeimport/reactive/lp-codeimport.py b/charm/lp-codeimport/reactive/lp-codeimport.py
new file mode 100644
index 0000000..4e5d00f
--- /dev/null
+++ b/charm/lp-codeimport/reactive/lp-codeimport.py
@@ -0,0 +1,240 @@
+# Copyright 2018-2021 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import base64
+import os.path
+import shutil
+import subprocess
+
+from charmhelpers.core import (
+ hookenv,
+ host,
+ templating,
+ )
+from charms.reactive import (
+ remove_state,
+ set_state,
+ when,
+ when_not,
+ )
+from ols import base
+
+
+# Monkey-patch layer:ols.
+def create_virtualenv(wheels_dir, codedir, python_exe):
+ subprocess.run(
+ ['make', 'compile', 'PYTHON={}'.format(python_exe)],
+ cwd=codedir, check=True)
+
+
+base.create_virtualenv = create_virtualenv
+
+
+def data_dir():
+ return os.path.join(base.base_dir(), 'data')
+
+
+def scripts_dir():
+ return os.path.join(base.base_dir(), 'scripts')
+
+
+def var_dir():
+ return os.path.join(base.base_dir(), 'var')
+
+
+def oopses_dir():
+ return os.path.join(base.base_dir(), 'oopses')
+
+
+def home_dir():
+ return os.path.join('/home', base.user())
+
+
+def ensure_lp_directories():
+ for dirpath in (data_dir(), var_dir(), oopses_dir()):
+ host.mkdir(dirpath, group=base.user(), perms=0o775)
+ host.mkdir(home_dir(), owner=base.user(), group=base.user(), perms=0o755)
+
+
+def base64_decode(value):
+ return base64.b64decode(value.encode('ASCII'))
+
+
+def install_ca_certificates(config):
+ hookenv.log('Writing CA certificates.')
+ git_certificate_path = os.path.join(
+ '/usr/local/share/ca-certificates',
+ '{}.crt'.format(config['git_hostname']))
+ if config['git_certificate']:
+ host.write_file(
+ git_certificate_path, base64_decode(config['git_certificate']),
+ perms=0o644)
+ else:
+ if os.path.exists(git_certificate_path):
+ os.unlink(git_certificate_path)
+ subprocess.run(['update-ca-certificates'], check=True)
+
+
+def install_keys(config):
+ hookenv.log('Writing keys.')
+
+ ssh_dir = os.path.join(home_dir(), '.ssh')
+ ssh_private_path = os.path.join(ssh_dir, 'lp-codeimport')
+ ssh_public_path = os.path.join(ssh_dir, 'lp-codeimport.pub')
+ ssh_config_path = os.path.join(ssh_dir, 'config')
+ if config['private_ssh_key'] and config['public_ssh_key']:
+ if not os.path.exists(ssh_dir):
+ host.mkdir(
+ ssh_dir, owner=base.user(), group=base.user(), perms=0o700)
+ host.write_file(
+ ssh_private_path, base64_decode(config['private_ssh_key']),
+ owner=base.user(), group=base.user(), perms=0o600)
+ host.write_file(
+ ssh_public_path, base64_decode(config['public_ssh_key']),
+ owner=base.user(), group=base.user(), perms=0o644)
+ templating.render(
+ 'ssh_config.j2', ssh_config_path,
+ config, owner=base.user(), group=base.user(), perms=0o644)
+ else:
+ for path in (ssh_private_path, ssh_public_path, ssh_config_path):
+ if os.path.exists(path):
+ os.unlink(path)
+
+ if config['private_gpg_key'] and config['public_gpg_key']:
+ # Unfortunately we can't use check=True, since these will fail if
+ # the key has already been added.
+ subprocess.run(
+ ['sudo', '-H', '-u', base.user(), 'gpg', '--import'],
+ input=base64_decode(config['private_gpg_key']))
+ subprocess.run(
+ ['sudo', '-H', '-u', base.user(), 'gpg', '--import'],
+ input=base64_decode(config['public_gpg_key']))
+
+ bazaar_dir = os.path.join(home_dir(), '.bazaar')
+ if not os.path.exists(bazaar_dir):
+ host.mkdir(
+ bazaar_dir, owner=base.user(), group=base.user(), perms=0o755)
+ templating.render(
+ 'bazaar.conf.j2', os.path.join(bazaar_dir, 'bazaar.conf'),
+ config, owner=base.user(), group=base.user(), perms=0o644)
+
+
+def install_scripts(config):
+ hookenv.log('Writing scripts.')
+ src = os.path.join(hookenv.charm_dir(), 'scripts')
+ dst = scripts_dir()
+ if not os.path.exists(dst):
+ host.mkdir(dst, perms=0o755)
+ for name in ('ps_dump.sh', 'ps_dump_clean.sh'):
+ shutil.copy2(os.path.join(src, name), os.path.join(dst, name))
+ templating.render(
+ 'clean_importd_logs.sh.j2',
+ os.path.join(dst, 'clean_importd_logs.sh'),
+ config, perms=0o755)
+
+
+def configure_logrotate(config):
+ hookenv.log('Writing logrotate configuration.')
+ templating.render(
+ 'logrotate.conf.j2',
+ os.path.join('/etc/logrotate.d', hookenv.application_name()),
+ config, perms=0o644)
+
+
+def configure_rsync(config):
+ hookenv.log('Writing rsync configuration.')
+ rsync_path = '/etc/rsync-juju.d/010-lp-codeimport.conf'
+ if config['log_hosts_allow']:
+ templating.render(
+ 'lp-codeimport-rsync.j2', rsync_path, config, perms=0o644)
+ elif os.path.exists(rsync_path):
+ os.unlink(rsync_path)
+ if not host.service_restart('rsync'):
+ raise RuntimeError('Failed to restart rsync')
+
+
+@when('ols.configured')
+@when_not('service.configured')
+def configure():
+ ensure_lp_directories()
+
+ system_packages = os.path.join(base.code_dir(), 'system-packages.txt')
+ if os.path.exists(system_packages):
+ site_packages = subprocess.run(
+ [os.path.join(base.code_dir(), 'env', 'bin', 'python'), '-c',
+ 'from distutils.sysconfig import get_python_lib; '
+ 'print(get_python_lib())'],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True,
+ universal_newlines=True).stdout.rstrip('\n')
+ link_system_packages = os.path.join(
+ base.code_dir(), 'utilities', 'link-system-packages.py')
+ subprocess.run(
+ [link_system_packages, site_packages, system_packages], check=True)
+
+ config = hookenv.config()
+ config_path = os.path.join(
+ base.code_dir(), 'production-configs', 'charm', 'codeimport-lazr.conf')
+ svc_config = dict(config)
+ svc_config.update({
+ 'base_dir': base.base_dir(),
+ 'code_dir': base.code_dir(),
+ 'logs_dir': base.logs_dir(),
+ 'data_dir': data_dir(),
+ 'scripts_dir': scripts_dir(),
+ 'var_dir': var_dir(),
+ 'oopses_dir': oopses_dir(),
+ 'home_dir': home_dir(),
+ 'user': base.user(),
+ # Chosen to allow distributing dispatch start time over a 30-second
+ # interval.
+ 'dispatch_offset': host.modulo_distribution(modulo=6, wait=5),
+ })
+ if config['private_gpg_key']:
+ gpg_key_colons = subprocess.run(
+ ['gpg', '--quiet', '--with-colons'],
+ input=base64_decode(config['private_gpg_key']),
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ check=True).stdout.decode('UTF-8')
+ svc_config['gpg_keyid'] = [
+ line for line in gpg_key_colons.splitlines()
+ if line.startswith('sec:')][0].split(':')[4]
+
+ hookenv.log('Writing service configuration.')
+ templating.render(
+ 'codeimport-lazr.conf.j2', config_path, svc_config,
+ owner='root', group=base.user(), perms=0o440)
+
+ install_ca_certificates(svc_config)
+ install_keys(svc_config)
+ install_scripts(svc_config)
+ configure_logrotate(svc_config)
+ configure_rsync(svc_config)
+
+ hookenv.log('Writing crontab.')
+ crontab_path = os.path.join(home_dir(), 'crontab')
+ templating.render(
+ 'crontab.j2', crontab_path,
+ svc_config, owner=base.user(), group=base.user(), perms=0o644)
+ subprocess.run(
+ ['sudo', '-H', '-u', 'importd', 'crontab', crontab_path], check=True)
+
+ set_state('service.configured')
+
+
+@when('service.configured')
+def check_is_running():
+ hookenv.status_set('active', 'Ready')
+
+
+@when('config.changed.build_label')
+def build_label_changed():
+ remove_state('ols.service.installed')
+ remove_state('ols.configured')
+ remove_state('service.configured')
+
+
+@when('config.changed')
+def config_changed():
+ remove_state('service.configured')
diff --git a/charm/lp-codeimport/scripts/ps_dump.sh b/charm/lp-codeimport/scripts/ps_dump.sh
new file mode 100755
index 0000000..895f34e
--- /dev/null
+++ b/charm/lp-codeimport/scripts/ps_dump.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+if [ -z "$1" ]; then
+ echo "Usage: $0 <path to store results>"
+ exit 1
+fi
+DIR=$1
+if [ ! -d "$DIR" ]; then
+ if ! mkdir -p "$DIR"; then
+ echo "$0: Unable to create Stats directory: $DIR"
+ exit 1
+ fi
+fi
+
+# delay 0-30 seconds, random. avoid crontab sameness/issues
+DELAY=$(( $(dd if=/dev/urandom count=1 2> /dev/null | cksum | cut -f1 -d" ") % 31 ))
+sleep $DELAY
+
+OUT="$DIR/$(date +%F:%T).ps"
+ps axfww -o user,pid,ppid,ni,pri,cputime,pmem,rss,size,vsize,stat,blocked,nlwp,lstart,etime,cmd > "$OUT"
diff --git a/charm/lp-codeimport/scripts/ps_dump_clean.sh b/charm/lp-codeimport/scripts/ps_dump_clean.sh
new file mode 100755
index 0000000..38d64ab
--- /dev/null
+++ b/charm/lp-codeimport/scripts/ps_dump_clean.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+if [ -z "$1" ]; then
+ echo "Usage: $0 <path to store results>"
+ exit 1
+fi
+DIR=$1
+
+YESTERDAY=$(date +%F --date="yesterday")
+cd "$DIR"
+tar -cj -f "stats.$YESTERDAY.tar.bz2" "$YESTERDAY"*.ps
+rm "$YESTERDAY"*.ps
+find . -maxdepth 1 -type f -mtime +7 -name 'stats.*.tar.bz2' -o -name '*.ps' | xargs --no-run-if-empty rm
diff --git a/charm/lp-codeimport/templates/bazaar.conf.j2 b/charm/lp-codeimport/templates/bazaar.conf.j2
new file mode 100644
index 0000000..d7662fb
--- /dev/null
+++ b/charm/lp-codeimport/templates/bazaar.conf.j2
@@ -0,0 +1,7 @@
+email={{ bzr_identity }}
+check_signatures=require
+{%- if private_gpg_key %}
+create_signatures=always
+gpg_signing_key={{ gpg_keyid }}
+{%- endif %}
+
diff --git a/charm/lp-codeimport/templates/clean_importd_logs.sh.j2 b/charm/lp-codeimport/templates/clean_importd_logs.sh.j2
new file mode 100644
index 0000000..59a4708
--- /dev/null
+++ b/charm/lp-codeimport/templates/clean_importd_logs.sh.j2
@@ -0,0 +1,39 @@
+#! /bin/bash
+#
+# Clean up old importd scripts
+# Archive 1 day modified old+ to .../archive/YYYY-MM-DD/
+# and compress
+
+# Base initialisation
+IFS=$' \t\n'
+unset -f unalias
+\unalias -a
+unset -f command
+PATH="/bin:/usr/bin"
+
+##################
+
+DEST_ROOT='{{ logs_dir }}'
+DEST_ARCH="$DEST_ROOT/archives"
+
+
+# Check for Archive directory, barf loudly if not found!
+if [ ! -d "$DEST_ARCH" ]; then
+ echo "ERROR! Archive path does NOT exist. $DEST_ARCH"
+ exit 1
+fi
+
+DEST="$DEST_ARCH"/$(date +%F)
+
+if ! mkdir -p "$DEST"; then
+ echo "ERROR! cannot create archive directory! $DEST"
+ exit 1
+fi
+
+FINDARGS="-maxdepth 1 -mtime +1 -type f -name code-import-worker-*.log"
+
+find "$DEST_ROOT" $FINDARGS -print0 | xargs -r0 mv --target-directory="$DEST"
+find "$DEST" $FINDARGS -print0 | xargs -r0 bzip2
+
+find "$DEST_ARCH" -maxdepth 1 -type d -mtime +90 -print0 | xargs -r0 rm -r
+
diff --git a/charm/lp-codeimport/templates/codeimport-lazr.conf.j2 b/charm/lp-codeimport/templates/codeimport-lazr.conf.j2
new file mode 100644
index 0000000..47e9a3f
--- /dev/null
+++ b/charm/lp-codeimport/templates/codeimport-lazr.conf.j2
@@ -0,0 +1,38 @@
+[meta]
+extends: ../../lib/lp/services/config/schema-lazr.conf
+
+[codehosting]
+git_browse_root: https://{{ git_hostname }}/
+
+[codeimport]
+bazaar_branch_store: {{ bazaar_branch_store }}
+foreign_tree_store: {{ foreign_tree_store }}
+
+[codeimportdispatcher]
+codeimportscheduler_url: {{ scheduler_endpoint }}
+worker_log_dir: {{ logs_dir }}
+
+[codeimportworker]
+working_directory_root: {{ data_dir }}
+
+[error_reports]
+oops_prefix: {{ oops_prefix }}
+error_dir: {{ oopses_dir }}
+{%- if not rabbitmq_host %}
+error_exchange: none
+{%- endif %}
+
+[rabbitmq]
+{%- if rabbitmq_host %}
+host: {{ rabbitmq_host }}
+{%- endif %}
+{%- if rabbitmq_user %}
+userid: {{ rabbitmq_user }}
+{%- endif %}
+{%- if rabbitmq_password %}
+password: {{ rabbitmq_password }}
+{%- endif %}
+{%- if rabbitmq_virtual_host %}
+virtual_host: {{ rabbitmq_virtual_host }}
+{%- endif %}
+
diff --git a/charm/lp-codeimport/templates/crontab.j2 b/charm/lp-codeimport/templates/crontab.j2
new file mode 100644
index 0000000..c2e10ce
--- /dev/null
+++ b/charm/lp-codeimport/templates/crontab.j2
@@ -0,0 +1,23 @@
+{%- if error_email %}
+MAILTO={{ error_email }}
+{%- endif %}
+LPCONFIG=charm
+LP_PY={{ code_dir }}/bin/py
+
+0 0 * * 1,4 /usr/sbin/logrotate -s ~/.logrotate.state {{ etc_dir }}/logrotate.conf
+
+* * * * * sleep {{ dispatch_offset }} && [ -f {{ base_dir }}/maintenance.txt ] || {{ code_dir }}/cronscripts/code-import-dispatcher.py -v --max-jobs=10 --log-file {{ logs_dir }}/code-import-dispatcher.log >> {{ logs_dir }}/code-import-dispatcher-out.log 2>&1
+* * * * * sleep {{ dispatch_offset + 30 }} && [ -f {{ base_dir }}/maintenance.txt ] || {{ code_dir }}/cronscripts/code-import-dispatcher.py -v --max-jobs=10 --log-file {{ logs_dir }}/code-import-dispatcher.log >> {{ logs_dir }}/code-import-dispatcher-out.log 2>&1
+
+2 1 * * * {{ scripts_dir }}/clean_importd_logs.sh
+
+# Processes watcher and cleaner
+* * * * * {{ scripts_dir }}/ps_dump.sh {{ var_dir }}/ps_stats
+15 0 * * * {{ scripts_dir }}/ps_dump_clean.sh {{ var_dir }}/ps_stats
+
+# OOPS amqp
+*/15 * * * * {{ code_dir }}/bin/datedir2amqp --exchange oopses --host {{ rabbitmq_host }} --username {{ rabbitmq_user }} --password {{ rabbitmq_password }} --vhost {{ rabbitmq_virtual_host }} --repo {{ oopses_dir }} --key ""
+
+# Work around https://bugs.launchpad.net/lp-codeimport/+bug/810288
+5 * * * * find /tmp -ignore_readdir_race -maxdepth 1 -type f \( -name 'tmp*.pack' -o -name 'tmp*.idx' \) -mtime +3 -delete
+
diff --git a/charm/lp-codeimport/templates/logrotate.conf.j2 b/charm/lp-codeimport/templates/logrotate.conf.j2
new file mode 100644
index 0000000..95e1819
--- /dev/null
+++ b/charm/lp-codeimport/templates/logrotate.conf.j2
@@ -0,0 +1,12 @@
+{{ logs_dir }}/code-import-dispatcher*.log {
+ rotate 5
+ daily
+ dateext
+ compress
+ delaycompress
+ compresscmd /bin/bzip2
+ uncompresscmd /bin/bunzip2
+ compressext .bz2
+ compressoptions -9
+}
+
diff --git a/charm/lp-codeimport/templates/lp-codeimport-rsync.j2 b/charm/lp-codeimport/templates/lp-codeimport-rsync.j2
new file mode 100644
index 0000000..0f1ee48
--- /dev/null
+++ b/charm/lp-codeimport/templates/lp-codeimport-rsync.j2
@@ -0,0 +1,8 @@
+
+[lp-codeimport-logs]
+ path = {{ logs_dir }}
+ comment = LP Code Import Logs
+ list = false
+ read only = true
+ hosts allow = {{ log_hosts_allow }}
+
diff --git a/charm/lp-codeimport/templates/ssh_config.j2 b/charm/lp-codeimport/templates/ssh_config.j2
new file mode 100644
index 0000000..926b3f5
--- /dev/null
+++ b/charm/lp-codeimport/templates/ssh_config.j2
@@ -0,0 +1,3 @@
+Host *
+ IdentityFile ~/.ssh/lp-codeimport
+
diff --git a/charm/packages.txt b/charm/packages.txt
new file mode 100644
index 0000000..c33fce5
--- /dev/null
+++ b/charm/packages.txt
@@ -0,0 +1,3 @@
+flake8
+python-codetree
+squashfuse
diff --git a/charm/test-gpg-parameters b/charm/test-gpg-parameters
new file mode 100644
index 0000000..93edead
--- /dev/null
+++ b/charm/test-gpg-parameters
@@ -0,0 +1,8 @@
+%no-protection
+Key-Type: RSA
+Key-Length: 2048
+Key-Usage: sign
+Name-Real: VCS Imports
+Name-Email: vcs-imports@xxxxxxxxxxxxxx
+Expire-Date: 0
+%commit
Follow ups