← Back to team overview

launchpad-reviewers team mailing list archive

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

 

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

Commit message:
charm: Add launchpad-loggerhead

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/453470
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:charm-loggerhead into launchpad:master.
diff --git a/charm/launchpad-loggerhead/README.md b/charm/launchpad-loggerhead/README.md
new file mode 100644
index 0000000..92efbe0
--- /dev/null
+++ b/charm/launchpad-loggerhead/README.md
@@ -0,0 +1,7 @@
+# Launchpad Bazaar/Breezy code browsing server
+
+This charm runs a code browsing server for Bazaar/Breezy branches.
+
+You will need the following relations:
+
+    juju relate launchpad-loggerhead rabbitmq-server
diff --git a/charm/launchpad-loggerhead/charmcraft.yaml b/charm/launchpad-loggerhead/charmcraft.yaml
new file mode 100644
index 0000000..71dcf7f
--- /dev/null
+++ b/charm/launchpad-loggerhead/charmcraft.yaml
@@ -0,0 +1,75 @@
+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: "42c89d9c66dbe137139b047fd54aed49b66d1a5e"
+    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: "9c59a9804f1f40e2a74be7dac9bf18a655a7864f"
+    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: "58edb3e5a88794c3baa2274a94e21d3a298a6c79"
+    source-submodules: []
+    source-type: git
+    plugin: dump
+    organize:
+      launchpad-base: layers/layer/launchpad-base
+      launchpad-payload: layers/layer/launchpad-payload
+    stage:
+      - layers
+    prime:
+      - "-layers"
+  layer-coordinator:
+    source: https://git.launchpad.net/layer-coordinator
+    source-commit: "fa27fc93e0b08000963e83a6bfe49812d890dfcf"
+    source-submodules: []
+    source-type: git
+    plugin: dump
+    organize:
+      "*": layers/layer/coordinator/
+    stage:
+      - layers
+    prime:
+      - "-layers"
+  charm:
+    after:
+      - charm-wheels
+      - launchpad-layers
+      - layer-coordinator
+    source: .
+    plugin: reactive
+    build-snaps: [charm]
+    build-packages: [libpq-dev, python3-dev]
+    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
+    reactive-charm-build-arguments: [--binary-wheels-from-source]
diff --git a/charm/launchpad-loggerhead/config.yaml b/charm/launchpad-loggerhead/config.yaml
new file mode 100644
index 0000000..3145bff
--- /dev/null
+++ b/charm/launchpad-loggerhead/config.yaml
@@ -0,0 +1,43 @@
+options:
+  haproxy_server_options:
+    type: string
+    description: Options to add to HAProxy "server" lines.
+    default: check inter 10000 rise 2 fall 2 maxconn 15
+  haproxy_service_options:
+    type: string
+    description: HAProxy options for codebrowse services.
+    default: |
+      - mode http
+      - option httplog
+      - option httpchk GET /robots.txt HTTP/1.0
+      - option forwardfor
+      - balance leastconn
+  internal_branch_by_id_root:
+    type: string
+    description: |
+      The URL prefix for where branches are served by URLs based on the
+      branch ID.
+    default:
+  nagios_check_branch:
+    type: string
+    description: If set, add Nagios checks for this branch.
+    default: ""
+  port_loggerhead:
+    type: int
+    description: >
+      Port to expose to the public (indirectly; we expect Apache on the
+      Bazaar codehosting system to ProxyPass to this port).  This serves
+      both public and private branches, but requests for private branches
+      must be authenticated.
+    default: 10007
+  port_loggerhead_api:
+    type: int
+    description: >
+      Private port for read-only API requests.  This must not be exposed to
+      the public; other parts of Launchpad with access to this port must
+      ensure that the appropriate security checks are performed.
+    default: 10017
+  session_secret:
+    type: string
+    description: A base64-encoded secret key used to sign session cookies.
+    default: ""
diff --git a/charm/launchpad-loggerhead/layer.yaml b/charm/launchpad-loggerhead/layer.yaml
new file mode 100644
index 0000000..5743ccb
--- /dev/null
+++ b/charm/launchpad-loggerhead/layer.yaml
@@ -0,0 +1,5 @@
+includes:
+  - layer:launchpad-base
+  - layer:coordinator
+  - interface:http
+repo: https://git.launchpad.net/launchpad
diff --git a/charm/launchpad-loggerhead/metadata.yaml b/charm/launchpad-loggerhead/metadata.yaml
new file mode 100644
index 0000000..186e666
--- /dev/null
+++ b/charm/launchpad-loggerhead/metadata.yaml
@@ -0,0 +1,18 @@
+name: launchpad-loggerhead
+display-name: launchpad-loggerhead
+summary: Launchpad Bazaar/Breezy code browsing server
+maintainer: Launchpad Developers <launchpad-dev@xxxxxxxxxxxxxxxxxxx>
+description: |
+  Launchpad is an open source suite of tools that help people and teams
+  to work together on software projects.
+
+  This charm runs a code browsing server for Bazaar/Breezy branches.
+tags:
+  # https://juju.is/docs/charm-metadata#heading--charm-store-fields
+  - network
+series:
+  - focal
+subordinate: false
+provides:
+  loadbalancer:
+    interface: http
diff --git a/charm/launchpad-loggerhead/reactive/launchpad-loggerhead.py b/charm/launchpad-loggerhead/reactive/launchpad-loggerhead.py
new file mode 100644
index 0000000..5d9347c
--- /dev/null
+++ b/charm/launchpad-loggerhead/reactive/launchpad-loggerhead.py
@@ -0,0 +1,225 @@
+# Copyright 2023 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+import base64
+import os.path
+import subprocess
+
+import yaml
+from charmhelpers.core import hookenv, host, templating
+from charms.coordinator import acquire
+from charms.launchpad.base import (
+    get_service_config,
+    lazr_config_files,
+    secrets_dir,
+)
+from charms.launchpad.payload import (
+    config_file_path,
+    configure_cron,
+    configure_lazr,
+)
+from charms.reactive import (
+    clear_flag,
+    endpoint_from_flag,
+    helpers,
+    set_flag,
+    when,
+    when_none,
+    when_not,
+    when_not_all,
+)
+from ols import base
+
+
+def reload_or_restart(service):
+    subprocess.run(["systemctl", "reload-or-restart", service], check=True)
+
+
+@host.restart_on_change(
+    {
+        "/lib/systemd/system/launchpad-loggerhead.service": [
+            "launchpad-loggerhead.service"
+        ],
+    },
+)
+def configure_systemd(config):
+    hookenv.log("Writing systemd service.")
+    config = dict(config)
+    templating.render(
+        "launchpad-loggerhead.service.j2",
+        "/lib/systemd/system/launchpad-loggerhead.service",
+        config,
+    )
+    subprocess.run(["systemctl", "daemon-reload"], check=True)
+    host.add_user_to_group("syslog", base.user())
+
+
+def configure_logrotate(config):
+    hookenv.log("Writing logrotate configuration.")
+    templating.render(
+        "logrotate.conf.j2",
+        "/etc/logrotate.d/loggerhead",
+        config,
+        perms=0o644,
+    )
+
+
+def session_secret_path():
+    return os.path.join(secrets_dir(), "cookies.hmac")
+
+
+def configure_session_secret(config):
+    session_secret = base64.b64decode(config["session_secret"].encode())
+    host.write_file(
+        session_secret_path(), session_secret, group=base.user(), perms=0o440
+    )
+
+
+def config_files():
+    files = []
+    files.extend(lazr_config_files())
+    files.append(config_file_path("launchpad-loggerhead/launchpad-lazr.conf"))
+    files.append(session_secret_path())
+    return files
+
+
+@when(
+    "config.set.domain_bzr",
+    "config.set.session_secret",
+    "launchpad.base.configured",
+)
+@when_none("coordinator.requested.restart", "service.configured")
+def configure():
+    config = get_service_config()
+    config["cache_dir"] = os.path.join(base.base_dir(), "cache")
+    host.mkdir(
+        config["cache_dir"], owner=base.user(), group=base.user(), perms=0o700
+    )
+    configure_lazr(
+        config,
+        "launchpad-loggerhead-lazr.conf",
+        "launchpad-loggerhead/launchpad-lazr.conf",
+    )
+    configure_systemd(config)
+    configure_logrotate(config)
+    configure_cron(config, "crontab.j2")
+    configure_session_secret(config)
+
+    if helpers.any_file_changed(
+        [
+            base.version_info_path(),
+            "/lib/systemd/system/launchpad-loggerhead.service",
+        ]
+        + config_files()
+    ):
+        hookenv.log("Config files changed; waiting for restart lock")
+        acquire("restart")
+    else:
+        hookenv.log("Not restarting, since no config files were changed")
+        set_flag("service.configured")
+
+
+@when("coordinator.granted.restart")
+def restart():
+    hookenv.log("Restarting application server")
+    host.service_restart("launchpad-loggerhead.service")
+    set_flag("service.configured")
+
+
+@when("service.configured")
+def check_is_running():
+    hookenv.status_set("active", "Ready")
+
+
+@when("service.configured")
+@when_not_all(
+    "config.set.domain_bzr",
+    "config.set.session_secret",
+    "launchpad.base.configured",
+)
+def deconfigure():
+    clear_flag("service.configured")
+
+
+@when("nrpe-external-master.available", "service.configured")
+@when_not("launchpad.loggerhead.nrpe-external-master.published")
+def nrpe_available():
+    nrpe = endpoint_from_flag("nrpe-external-master.available")
+    config = hookenv.config()
+    if config["nagios_check_branch"]:
+        nrpe.add_check(
+            [
+                "/usr/lib/nagios/plugins/check_http",
+                "-H",
+                "localhost",
+                "-p",
+                str(config["port_loggerhead"]),
+                "-u",
+                f"{config['nagios_check_branch']}/files",
+            ],
+            name="check_launchpad_loggerhead",
+            description="Launchpad loggerhead",
+            context=config["nagios_context"],
+        )
+    set_flag("launchpad.loggerhead.nrpe-external-master.published")
+
+
+@when("launchpad.loggerhead.nrpe-external-master.published")
+@when_not("nrpe-external-master.available")
+def nrpe_unavailable():
+    clear_flag("launchpad.loggerhead.nrpe-external-master.published")
+
+
+@when("loadbalancer.available", "service.configured")
+@when_not("launchpad.loadbalancer.configured")
+def configure_loadbalancer():
+    config = hookenv.config()
+
+    try:
+        service_options = yaml.safe_load(config["haproxy_service_options"])
+    except yaml.YAMLError:
+        hookenv.log("Could not parse haproxy_service_options YAML")
+        hookenv.status_set(
+            "blocked", "Bad haproxy_service_options YAML configuration"
+        )
+        return
+    server_options = config["haproxy_server_options"]
+
+    unit_name = hookenv.local_unit().replace("/", "-")
+    unit_ip = hookenv.unit_private_ip()
+    services = [
+        {
+            "service_name": "launchpad-loggerhead",
+            "service_port": config["port_loggerhead"],
+            "service_host": "0.0.0.0",
+            "service_options": list(service_options),
+            "servers": [
+                [
+                    f"public_{unit_name}",
+                    unit_ip,
+                    config["port_loggerhead"],
+                    server_options,
+                ]
+            ],
+        },
+        {
+            "service_name": "launchpad-loggerhead-api",
+            "service_port": config["port_loggerhead_api"],
+            "service_host": "0.0.0.0",
+            "service_options": list(service_options),
+            "servers": [
+                [
+                    f"public_{unit_name}",
+                    unit_ip,
+                    config["port_loggerhead_api"],
+                    server_options,
+                ]
+            ],
+        },
+    ]
+    services_yaml = yaml.dump(services)
+
+    for rel in hookenv.relations_of_type("loadbalancer"):
+        hookenv.relation_set(rel["__relid__"], services=services_yaml)
+
+    set_flag("launchpad.loadbalancer.configured")
diff --git a/charm/launchpad-loggerhead/templates/crontab.j2 b/charm/launchpad-loggerhead/templates/crontab.j2
new file mode 100644
index 0000000..0a532ba
--- /dev/null
+++ b/charm/launchpad-loggerhead/templates/crontab.j2
@@ -0,0 +1,10 @@
+TZ=UTC
+MAILTO={{ cron_mailto }}
+
+# Clean up cache directory.
+25 0 * * * find {{ cache_dir }} -maxdepth 1 -type d -mtime +240 -execdir rm -rf {} +
+
+# Catch up with publishing OOPSes that were temporarily spooled to disk due
+# to RabbitMQ being unavailable.
+*/15 * * * * {{ code_dir }}/bin/datedir2amqp --exchange oopses --host {{ rabbitmq_host }} --username {{ rabbitmq_username }} --password {{ rabbitmq_password }} --vhost {{ rabbitmq_vhost }} --repo {{ oopses_dir }} --key ""
+
diff --git a/charm/launchpad-loggerhead/templates/launchpad-loggerhead-lazr.conf b/charm/launchpad-loggerhead/templates/launchpad-loggerhead-lazr.conf
new file mode 100644
index 0000000..d1212e9
--- /dev/null
+++ b/charm/launchpad-loggerhead/templates/launchpad-loggerhead-lazr.conf
@@ -0,0 +1,24 @@
+# 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.
+
+{% from "macros.j2" import opt -%}
+
+[meta]
+extends: ../launchpad-base-lazr.conf
+
+[codebrowse]
+cachepath: {{ cache_dir }}
+launchpad_root: https://code.{{ domain }}/
+log_folder: {{ logs_dir }}
+port: {{ port_loggerhead }}
+private_port: {{ port_loggerhead_api }}
+secret_path: {{ secrets_dir }}/cookies.hmac
+
+[codehosting]
+{{- opt("internal_branch_by_id_root", internal_branch_by_id_root) }}
+
diff --git a/charm/launchpad-loggerhead/templates/launchpad-loggerhead.service.j2 b/charm/launchpad-loggerhead/templates/launchpad-loggerhead.service.j2
new file mode 100644
index 0000000..4a98153
--- /dev/null
+++ b/charm/launchpad-loggerhead/templates/launchpad-loggerhead.service.j2
@@ -0,0 +1,22 @@
+[Unit]
+Description=Launchpad Bazaar/Breezy code browsing server
+After=network.target
+ConditionPathExists=!{{ code_dir }}/maintenance.txt
+
+[Service]
+Type=notify
+User=launchpad
+Group=launchpad
+WorkingDirectory={{ code_dir }}
+Environment=BRZ_PLUGIN_PATH=brzplugins
+Environment=LPCONFIG=launchpad-loggerhead
+SyslogIdentifier=loggerhead
+ExecStart={{ code_dir }}/scripts/start-loggerhead.py
+ExecReload=/bin/kill -HUP $MAINPID
+KillMode=mixed
+Restart=on-failure
+PrivateTmp=true
+
+[Install]
+WantedBy=multi-user.target
+
diff --git a/charm/launchpad-loggerhead/templates/logrotate.conf.j2 b/charm/launchpad-loggerhead/templates/logrotate.conf.j2
new file mode 100644
index 0000000..5bde352
--- /dev/null
+++ b/charm/launchpad-loggerhead/templates/logrotate.conf.j2
@@ -0,0 +1,15 @@
+{{ logs_dir }}/access.log {{ logs_dir }}/debug.log
+{
+    rotate 21
+    daily
+    dateext
+    delaycompress
+    compress
+    notifempty
+    missingok
+    create 0644 {{ user }} {{ user }}
+    postrotate
+        systemctl restart launchpad-loggerhead.service
+    endscript
+}
+