← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:charm-assets into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:charm-assets into launchpad:master.

Commit message:
charm: Add a launchpad-assets charm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/442679

This extracts the parts of our current frontend configuration that need access to a Launchpad payload.  Once this is deployed, we can adjust the frontend configuration to point to it using `ProxyPass`.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:charm-assets into launchpad:master.
diff --git a/charm/Makefile b/charm/Makefile
index 6c4f214..d1e4611 100644
--- a/charm/Makefile
+++ b/charm/Makefile
@@ -16,6 +16,7 @@ ASSET = ../build/$(BUILD_LABEL)/$(TARBALL)
 CHARMS := \
 	launchpad-admin \
 	launchpad-appserver \
+	launchpad-assets \
 	launchpad-librarian
 
 all: ## alias to build
diff --git a/charm/launchpad-assets/README.md b/charm/launchpad-assets/README.md
new file mode 100644
index 0000000..34e4fa6
--- /dev/null
+++ b/charm/launchpad-assets/README.md
@@ -0,0 +1,4 @@
+# Launchpad assets
+
+This charm publishes assets (CSS, JavaScript, and API documentation) so that
+they can be proxied by frontends.
diff --git a/charm/launchpad-assets/charmcraft.yaml b/charm/launchpad-assets/charmcraft.yaml
new file mode 100644
index 0000000..c3b7cf8
--- /dev/null
+++ b/charm/launchpad-assets/charmcraft.yaml
@@ -0,0 +1,72 @@
+type: charm
+bases:
+  - build-on:
+    - name: ubuntu
+      channel: "20.04"
+      architectures: [amd64]
+    run-on:
+    - name: ubuntu
+      channel: "20.04"
+      architectures: [amd64]
+parts:
+  charm-wheels:
+    source: https://git.launchpad.net/~ubuntuone-hackers/ols-charm-deps/+git/wheels
+    source-commit: "59b32ae07f98051385c96d6d8e7e02ca4f197fe5"
+    source-submodules: []
+    source-type: git
+    plugin: dump
+    organize:
+      "*": charm-wheels/
+    prime:
+      - "-charm-wheels"
+  ols-layers:
+    source: https://git.launchpad.net/ols-charm-deps
+    source-commit: "56d219f60a293a6c73759b8439ef5fdb11e19d1f"
+    source-submodules: []
+    source-type: git
+    plugin: dump
+    organize:
+      "*": layers/
+    stage:
+      - layers
+    prime:
+      - "-layers"
+  launchpad-layers:
+    after:
+      - ols-layers
+    source: https://git.launchpad.net/launchpad-layers
+    source-commit: "42a4b4c4f62936b1d050c775e84f7364dfb5efc0"
+    source-submodules: []
+    source-type: git
+    plugin: dump
+    organize:
+      launchpad-payload: layers/layer/launchpad-payload
+    stage:
+      - layers
+    prime:
+      - "-layers"
+  interface-apache-website:
+    source: https://github.com/juju-solutions/interface-apache-website
+    source-commit: "2f736ebcc90d19ac142a2d898a2ec7e1aafaa13f"
+    source-submodules: []
+    source-type: git
+    plugin: dump
+    organize:
+      "*": layers/interface/apache-website/
+    stage:
+      - layers
+    prime:
+      - "-layers"
+  launchpad-assets:
+    after:
+      - charm-wheels
+      - launchpad-layers
+      - interface-apache-website
+    source: .
+    plugin: reactive
+    build-snaps: [charm/2.x/stable]
+    build-environment:
+      - CHARM_LAYERS_DIR: $CRAFT_STAGE/layers/layer
+      - CHARM_INTERFACES_DIR: $CRAFT_STAGE/layers/interface
+      - PIP_NO_INDEX: "true"
+      - PIP_FIND_LINKS: $CRAFT_STAGE/charm-wheels
diff --git a/charm/launchpad-assets/config.yaml b/charm/launchpad-assets/config.yaml
new file mode 100644
index 0000000..bab57b5
--- /dev/null
+++ b/charm/launchpad-assets/config.yaml
@@ -0,0 +1,9 @@
+options:
+  domain:
+    type: string
+    description: Domain name for this instance.
+    default: "launchpad.test"
+  port_assets:
+    type: int
+    description: Port for the assets website.
+    default: 8009
diff --git a/charm/launchpad-assets/layer.yaml b/charm/launchpad-assets/layer.yaml
new file mode 100644
index 0000000..4410c41
--- /dev/null
+++ b/charm/launchpad-assets/layer.yaml
@@ -0,0 +1,11 @@
+includes:
+  - layer:launchpad-payload
+  - interface:apache-website
+repo: https://git.launchpad.net/launchpad
+options:
+  apt:
+    packages:
+      - nodejs
+      - python3-convoy
+  launchpad-payload:
+    build_target: build
diff --git a/charm/launchpad-assets/metadata.yaml b/charm/launchpad-assets/metadata.yaml
new file mode 100644
index 0000000..745ccde
--- /dev/null
+++ b/charm/launchpad-assets/metadata.yaml
@@ -0,0 +1,20 @@
+name: launchpad-assets
+display-name: launchpad-assets
+summary: Launchpad assets
+maintainer: Colin Watson <cjwatson@xxxxxxxxxxxxx>
+description: |
+  Launchpad is an open source suite of tools that help people and teams
+  to work together on software projects.
+
+  This charm publishes Launchpad assets (CSS, JavaScript, and API
+  documentation) so that they can be proxied by frontends.
+tags:
+  # https://juju.is/docs/charm-metadata#heading--charm-store-fields
+  - network
+series:
+  - focal
+subordinate: true
+requires:
+  apache-website:
+    interface: apache-website
+    scope: container
diff --git a/charm/launchpad-assets/reactive/launchpad-assets.py b/charm/launchpad-assets/reactive/launchpad-assets.py
new file mode 100644
index 0000000..dce1243
--- /dev/null
+++ b/charm/launchpad-assets/reactive/launchpad-assets.py
@@ -0,0 +1,105 @@
+# Copyright 2023 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+import os.path
+import subprocess
+from pathlib import Path
+
+from charmhelpers.core import hookenv, host, templating
+from charms.reactive import (
+    clear_flag,
+    endpoint_from_flag,
+    set_flag,
+    when,
+    when_all,
+    when_not,
+    when_not_all,
+)
+from ols import base
+
+
+@host.restart_on_change(
+    {
+        "/lib/systemd/system/convoy.service": ["convoy.service"],
+        "/lib/systemd/system/convoy.socket": ["convoy.socket"],
+    }
+)
+def configure_convoy(config):
+    hookenv.log("Writing convoy configuration.")
+
+    # Update convoy symlinks.
+    build_label = config["build_label"]
+    convoy_path = Path(base.base_dir()) / "convoy"
+    convoy_path.mkdir(parents=True, exist_ok=True)
+    link_path = convoy_path / f"rev{build_label}"
+    if not link_path.is_symlink():
+        link_path.symlink_to(Path(base.code_dir()) / "build" / "js")
+    for link_path in convoy_path.iterdir():
+        if link_path.name.startswith("rev") and link_path.is_symlink():
+            if not link_path.exists():
+                link_path.unlink()
+
+    templating.render(
+        "convoy.service.j2", "/lib/systemd/system/convoy.service", config
+    )
+    templating.render(
+        "convoy.socket.j2", "/lib/systemd/system/convoy.socket", config
+    )
+    subprocess.run(["systemctl", "daemon-reload"])
+
+
+def get_service_config():
+    config = hookenv.config()
+    config.update(
+        {
+            "base_dir": base.base_dir(),
+            "code_dir": base.code_dir(),
+            "logs_dir": base.logs_dir(),
+            "payloads_dir": base.payloads_dir(),
+        }
+    )
+    return config
+
+
+def config_file_path(name):
+    return os.path.join(base.code_dir(), "production-configs", name)
+
+
+@when("ols.configured")
+@when_not("service.configured")
+def configure():
+    config = get_service_config()
+    hookenv.log("Writing launchpad-assets/launchpad-lazr.conf.")
+    templating.render(
+        "launchpad-assets-lazr.conf",
+        config_file_path("launchpad-assets/launchpad-lazr.conf"),
+        config,
+        owner="root",
+        group=base.user(),
+        perms=0o444,
+    )
+    configure_convoy(config)
+    set_flag("service.configured")
+
+
+@when_all("service.configured", "apache-website.available")
+@when_not("service.apache-configured")
+def send_apache_website():
+    apache = endpoint_from_flag("apache-website.available")
+    config = get_service_config()
+    apache.send_domain(f"assets.{config['domain']}")
+    apache.send_site_config(templating.render("vhost.conf.j2", None, config))
+    # interface-apache incorrectly sets `modules`, not `site_modules`.  Work
+    # around this.
+    apache.set_remote(site_modules="headers proxy proxy_http")
+    apache.send_ports([config["port_assets"]])
+    apache.send_enabled()
+    hookenv.status_set("active", "Ready")
+    set_flag("service.apache-configured")
+
+
+@when("service.apache-configured")
+@when_not_all("service.configured", "apache-website.available")
+def apache_deconfigured():
+    hookenv.status_set("blocked", "Website not yet configured")
+    clear_flag("service.apache-configured")
diff --git a/charm/launchpad-assets/templates/convoy.service.j2 b/charm/launchpad-assets/templates/convoy.service.j2
new file mode 100644
index 0000000..70cb525
--- /dev/null
+++ b/charm/launchpad-assets/templates/convoy.service.j2
@@ -0,0 +1,21 @@
+[Unit]
+Description=Launchpad CSS/JavaScript combo loader
+Requires=convoy.socket
+After=network.target
+
+[Service]
+Type=notify
+User=launchpad
+Group=launchpad
+Restart=on-failure
+Environment=CONVOY_ROOT={{ base_dir }}/convoy
+ExecStart={{ code_dir }}/bin/gunicorn --bind unix:/run/convoy.socket --log-file {{ logs_dir }}/convoy.log --log-level debug --workers=4 convoy.wsgi
+ExecReload=/bin/kill -USR1 $MAINPID
+PrivateTmp=true
+PrivateDevices=true
+ProtectSystem=true
+NoNewPrivileges=true
+
+[Install]
+WantedBy=multi-user.target
+
diff --git a/charm/launchpad-assets/templates/convoy.socket.j2 b/charm/launchpad-assets/templates/convoy.socket.j2
new file mode 100644
index 0000000..e4d032d
--- /dev/null
+++ b/charm/launchpad-assets/templates/convoy.socket.j2
@@ -0,0 +1,9 @@
+[Unit]
+Description=Launchpad CSS/JavaScript combo loader
+
+[Socket]
+ListenStream=/run/convoy.socket
+
+[Install]
+WantedBy=sockets.target
+
diff --git a/charm/launchpad-assets/templates/launchpad-assets-lazr.conf b/charm/launchpad-assets/templates/launchpad-assets-lazr.conf
new file mode 100644
index 0000000..24637f3
--- /dev/null
+++ b/charm/launchpad-assets/templates/launchpad-assets-lazr.conf
@@ -0,0 +1,14 @@
+# Public configuration data.  The contents of this file may be freely shared
+# with developers if needed for debugging.
+
+# A schema's sections, keys, and values are automatically inherited, except
+# for '.optional' sections.  Update this config to override key values.
+# Values are strings, except for numbers that look like ints.  The tokens
+# true, false, and none are treated as True, False, and None.
+
+[meta]
+extends: ../lib/lp/services/config/schema-lazr.conf
+
+[vhost.api]
+hostname: api.{{ domain }}
+
diff --git a/charm/launchpad-assets/templates/logrotate.conf.j2 b/charm/launchpad-assets/templates/logrotate.conf.j2
new file mode 100644
index 0000000..6f98e3e
--- /dev/null
+++ b/charm/launchpad-assets/templates/logrotate.conf.j2
@@ -0,0 +1,15 @@
+{{ logs_dir }}/convoy.log
+{
+    rotate 21
+    daily
+    dateext
+    delaycompress
+    compress
+    notifempty
+    missingok
+    create 0644 syslog adm
+    postrotate
+        systemctl reload convoy.service
+    endscript
+}
+
diff --git a/charm/launchpad-assets/templates/vhost.conf.j2 b/charm/launchpad-assets/templates/vhost.conf.j2
new file mode 100644
index 0000000..65bc07f
--- /dev/null
+++ b/charm/launchpad-assets/templates/vhost.conf.j2
@@ -0,0 +1,37 @@
+<VirtualHost *:{{ port_assets }}>
+    ServerName assets.{{ domain }}
+
+    ErrorLog /var/log/apache2/assets.{{ domain }}-error.log
+    CustomLog /var/log/apache2/assets.{{ domain }}-access.log combined
+
+    <Location "/+apidoc/">
+        Header set Cache-Control "public,max-age=5184000"
+        Require all granted
+    </Location>
+    Alias "/+apidoc/" "{{ code_dir }}/lib/canonical/launchpad/apidoc/"
+
+    <Location "/+combo/">
+        Header set Cache-Control "public,max-age=5184000"
+        Require all granted
+        ProxyPass "unix:/run/convoy.socket|http://localhost/";
+    </Location>
+
+    <LocationMatch "^/\+icing/rev(?<commit>[0-9a-f]+)/">
+        Header set Cache-Control "public,max-age=5184000"
+        Require all granted
+    </LocationMatch>
+    AliasMatch "^/\+icing/rev([0-9a-f]+)/(.*)" "{{ payloads_dir }}/$1/lib/canonical/launchpad/icing/$2"
+    <Location "/+icing/">
+        Header set Cache-Control "public,max-age=5184000"
+        Require all granted
+    </Location>
+    Alias "/+icing/" "{{ code_dir }}/lib/canonical/launchpad/icing/"
+
+    <Location "/@@/">
+        Options MultiViews
+        Header set Cache-Control "public,max-age=5184000"
+        Require all granted
+    </Location>
+    Alias "/@@/" "{{ code_dir }}/lib/canonical/launchpad/images/"
+</VirtualHost>
+