← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/lp-archive:charm into lp-archive:main

 

Colin Watson has proposed merging ~cjwatson/lp-archive:charm into lp-archive:main with ~cjwatson/lp-archive:service-config-env 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-archive/+git/lp-archive/+merge/437090

This is based on the layers provided by lp:ols-charm-deps.

This somewhat relies on getting artifact tarballs into PS5's object storage service, which is currently blocked on https://portal.admin.canonical.com/C155938, but it's possible to test it locally without that by building the artifact yourself.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/lp-archive:charm into lp-archive:main.
diff --git a/charm/.gitignore b/charm/.gitignore
new file mode 100644
index 0000000..4e727f8
--- /dev/null
+++ b/charm/.gitignore
@@ -0,0 +1,3 @@
+*.charm
+dist
+tmp
diff --git a/charm/Makefile b/charm/Makefile
new file mode 100644
index 0000000..20dec9c
--- /dev/null
+++ b/charm/Makefile
@@ -0,0 +1,96 @@
+# The charmcraft tool is shipped as a snap, so make sure it's on $PATH.
+export PATH := $(PATH):/snap/bin
+
+APP_NAME := lp-archive
+
+BUILDDIR := $(CURDIR)/dist
+TMPDIR := $(CURDIR)/tmp
+CHARM_LAYERS_DIR := $(TMPDIR)/deps/ols-layers/layer
+CHARM_INTERFACES_DIR := $(TMPDIR)/deps/ols-layers/interface
+
+CHARM_SERIES ?= 22.04
+ARCH := $(shell dpkg --print-architecture)
+charm_file = $(1)_ubuntu-$(CHARM_SERIES)-$(ARCH).charm
+
+BUILD_LABEL = $(shell git rev-parse HEAD)
+TARBALL = $(APP_NAME).tar.gz
+ASSET = ../build/$(BUILD_LABEL)/$(TARBALL)
+
+CHARMS := lp-archive
+
+all: ## alias to build
+all: build
+
+help: ## display this help message
+	@echo "Please use \`make <target>' where <target> is one of"
+	@grep '^[a-zA-Z]' $(MAKEFILE_LIST) | sort | awk -F ':.*?## ' 'NF==2 {printf "\033[36m  %-25s\033[0m %s\n", $$1, $$2}'
+
+$(BUILDDIR) $(TMPDIR):
+	@mkdir -p $@
+
+# We have to clone our dependencies by hand rather than letting charmcraft
+# do it, since some of them are in private repositories and charmcraft
+# doesn't have suitable credentials.
+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: ## build all the charms
+build: $(foreach charm,$(CHARMS),build-$(charm))
+
+build-lp-archive: ## build the lp-archive charm
+build-lp-archive: dist/$(call charm_file,lp-archive)
+
+dist/%_ubuntu-$(CHARM_SERIES)-$(ARCH).charm: $(CHARM_DEPS) | $(BUILDDIR)
+	echo "Building $*..."
+	rm -rf $*/tmp
+	cp -a tmp $*/tmp
+	cd $* && charmcraft pack
+	cp -a $*/$(call charm_file,$*) dist/
+	rm -rf $*/tmp
+
+clean: ## clean the build environment
+clean: $(foreach charm,$(CHARMS),clean-$(charm))
+	@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)
+
+clean-%:
+	@echo "Cleaning $*..."
+	@cd $* && charmcraft clean
+	@rm -f dist/$(call charm_file,$*)
+
+bundle.yaml: ## create the bundle.yaml file from the bundle.yaml.in template
+bundle.yaml: bundle.yaml.in
+	sed \
+	    -e 's/%BUILD_LABEL%/$(BUILD_LABEL)/g' \
+	    bundle.yaml.in >bundle.yaml
+
+deploy: ## deploy the built charm
+deploy: build bundle.yaml
+	@echo "Deploying $(APP_NAME)..."
+	@juju deploy ./bundle.yaml
+
+payload: ## build an lp-archive tarball
+payload: $(ASSET)
+$(ASSET):
+	@echo "Building asset for $(BUILD_LABEL)..."
+	@$(MAKE) -C .. build-tarball
+
+
+setup-jenkaas: ## 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 payload setup-jenkaas
diff --git a/charm/dependencies.txt b/charm/dependencies.txt
new file mode 100644
index 0000000..a0a0973
--- /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=e5787897
+charm-wheels		git+ssh://git.launchpad.net/~ubuntuone-hackers/ols-charm-deps/+git/wheels;revno=fbf2c56c
diff --git a/charm/lp-archive/charmcraft.yaml b/charm/lp-archive/charmcraft.yaml
new file mode 100644
index 0000000..76b6fae
--- /dev/null
+++ b/charm/lp-archive/charmcraft.yaml
@@ -0,0 +1,20 @@
+type: charm
+bases:
+  - build-on:
+    - name: ubuntu
+      channel: "22.04"
+    run-on:
+    - name: ubuntu
+      channel: "22.04"
+parts:
+  lp-archive:
+    source: .
+    plugin: reactive
+    build-snaps: [charm/2.x/stable]
+    build-environment:
+      - CHARM_LAYERS_DIR: tmp/deps/ols-layers/layer
+      - CHARM_INTERFACES_DIR: tmp/deps/ols-layers/interface
+      - PIP_NO_INDEX: "true"
+      - PIP_FIND_LINKS: tmp/deps/charm-wheels
+    stage:
+      - -tmp
diff --git a/charm/lp-archive/config.yaml b/charm/lp-archive/config.yaml
new file mode 100644
index 0000000..e9a0ca6
--- /dev/null
+++ b/charm/lp-archive/config.yaml
@@ -0,0 +1,17 @@
+options:
+  archive_endpoint:
+    type: string
+    default: http://xmlrpc-private.launchpad.test:8087/archive
+    description: URL of private Launchpad XML-RPC archive endpoint.
+  layouts:
+    type: string
+    default: ""
+    description: >
+      YAML-encoded list of layouts offered by this deployment.  Each layout is
+      a dictionary containing "host" and "purpose" keys, where "purpose" is
+      "primary" or "ppa".  For example: [{"host": "snapshot.ubuntu.test:8000",
+      "purpose": "primary"}].
+  log_hosts_allow:
+    type: string
+    default: ""
+    description: Hosts that should be allowed to rsync logs.
diff --git a/charm/lp-archive/layer.yaml b/charm/lp-archive/layer.yaml
new file mode 100644
index 0000000..11d23f2
--- /dev/null
+++ b/charm/lp-archive/layer.yaml
@@ -0,0 +1,18 @@
+includes:
+  - layer:ols-wsgi
+  - interface:memcache
+options:
+  apt:
+    packages:
+      - libmemcached11
+  ols:
+    service_name: lp-archive
+    config_filename: config.toml
+    user: launchpad
+    tarball_payload: true
+    symlink_switch_payload: true
+  ols-http:
+    port: 8000
+  ols-wsgi:
+    app: lp_archive
+repo: https://git.launchpad.net/lp-archive
diff --git a/charm/lp-archive/metadata.yaml b/charm/lp-archive/metadata.yaml
new file mode 100644
index 0000000..0774d2f
--- /dev/null
+++ b/charm/lp-archive/metadata.yaml
@@ -0,0 +1,19 @@
+name: lp-archive
+display-name: lp-archive
+summary: Launchpad archive frontend service
+maintainer: Colin Watson <cjwatson@xxxxxxxxxxxxx>
+description: >
+  A frontend service to serve Launchpad archives (such as the primary Ubuntu
+  archive or PPAs) over HTTP without the need for local storage of the
+  archive files.
+series:
+  - jammy
+subordinate: false
+requires:
+  memcache:
+    interface: memcache
+resources:
+  lp-archive:
+    type: file
+    filename: lp-archive.tar.gz
+    description: lp-archive code
diff --git a/charm/lp-archive/reactive/lp-archive.py b/charm/lp-archive/reactive/lp-archive.py
new file mode 100644
index 0000000..28c8efe
--- /dev/null
+++ b/charm/lp-archive/reactive/lp-archive.py
@@ -0,0 +1,66 @@
+# Copyright 2023 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+import yaml
+from charmhelpers.core import hookenv, host, templating
+from charms.reactive import hook, remove_state, set_state, when, when_not
+from ols import base, http
+
+
+def configure_rsync():
+    config = hookenv.config()
+    if config["log_hosts_allow"]:
+        rsync_config = dict(config)
+        rsync_config["base_dir"] = base.base_dir()
+        templating.render(
+            "lp-archive-rsync.j2",
+            "/etc/rsync-juju.d/010-lp-archive.conf",
+            rsync_config,
+            perms=0o644,
+        )
+        if not host.service_restart("rsync"):
+            raise RuntimeError("Failed to restart rsync")
+
+
+@when("ols.configured", "memcache.available")
+@when_not("service.configured")
+def configure(memcache):
+    config = dict(hookenv.config())
+    config["memcache_servers"] = sorted(
+        f"{host}:{port}" for host, port in memcache.memcache_hosts_ports()
+    )
+    config["layouts"] = yaml.safe_load(config["layouts"])
+
+    hookenv.log("Writing service config.")
+    templating.render(
+        "config.toml.j2",
+        base.service_config_path(),
+        config,
+        owner="root",
+        group=base.user(),
+        perms=0o440,
+    )
+
+    configure_rsync()
+
+    set_state("service.configured")
+
+
+@when("service.configured", "ols.wsgi.configured")
+def check_is_running():
+    if http.is_listening(url_path="/_status/check"):
+        hookenv.status_set("active", "Ready")
+    else:
+        hookenv.status_set("blocked", "Service not running, check logs")
+
+
+@when("config.changed.build_label")
+def build_label_changed():
+    remove_state("ols.service.installed")
+    remove_state("ols.configured")
+    remove_state("service.configured")
+
+
+@hook("{requires:memcache}-relation-changed")
+def memcache_relation_changed(memcache):
+    remove_state("service.configured")
diff --git a/charm/lp-archive/templates/config.toml.j2 b/charm/lp-archive/templates/config.toml.j2
new file mode 100644
index 0000000..5512086
--- /dev/null
+++ b/charm/lp-archive/templates/config.toml.j2
@@ -0,0 +1,12 @@
+ARCHIVE_ENDPOINT = "{{ archive_endpoint }}"
+CACHE_MEMCACHED_SERVERS = [{% for server in memcache_servers %}"{{ server }}"{% if not loop.last %}, {% endif %}{% endfor %}]
+CACHE_TYPE = "MemcachedCache"
+{%- if layouts %}
+{%- for layout in layouts %}
+
+[[LAYOUTS]]
+host = "{{ layout["host"] }}"
+purpose = "{{ layout["purpose"] }}"
+{%- endfor %}
+{%- endif %}
+
diff --git a/charm/lp-archive/templates/lp-archive-rsync.j2 b/charm/lp-archive/templates/lp-archive-rsync.j2
new file mode 100644
index 0000000..52c519d
--- /dev/null
+++ b/charm/lp-archive/templates/lp-archive-rsync.j2
@@ -0,0 +1,8 @@
+
+[lp-archive-logs]
+  path = {{ base_dir }}/logs
+  comment = LP Archive Logs
+  list = false
+  read only = true
+  hosts allow = {{ log_hosts_allow }}
+
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/tox.ini b/tox.ini
index c054870..eac8482 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,7 +13,7 @@ skip_missing_interpreters =
 description =
     run test suite
 commands =
-    pytest {posargs}
+    pytest {posargs:tests}
 deps =
     -r requirements.txt
     .[test]
@@ -62,6 +62,6 @@ deps =
     .[test]
 commands =
     coverage erase
-    coverage run -m pytest
+    coverage run -m pytest tests
     coverage html
     coverage report -m  --fail-under=100

Follow ups