From 4807d87cb567a122dc22198b315db17eff6f2c27 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 2 Jun 2026 12:53:35 +0000 Subject: [PATCH 01/21] Adapt CI to canonical/charm-ci - Add artifacts.yaml generated by `opcli artifacts init` - Replace spread.yaml with opcli-generated version using the integration-test virtual backend and integration-suites for all four sub-charm test directories - Add concierge.yaml for LXD-based Juju test environment provisioning - Replace integration_test.yaml workflow with canonical/charm-ci reusable workflow (build-artifacts + integration-test) The previous workflow called canonical/operator-workflows; the new one delegates to canonical/charm-ci which uses opcli + spread to discover, build, and test artifacts declared in artifacts.yaml / spread.yaml. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/integration_test.yaml | 76 ++++--------------------- artifacts.yaml | 33 +++++++++++ concierge.yaml | 10 ++++ spread.yaml | 69 +++++++++++----------- 4 files changed, 89 insertions(+), 99 deletions(-) create mode 100644 artifacts.yaml create mode 100644 concierge.yaml diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 3e111ee79..741cc3411 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -1,74 +1,18 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + name: Integration tests on: pull_request: schedule: - - cron: "0 15 * * SAT" + - cron: "0 15 * * SAT" jobs: - integration-tests: - strategy: - matrix: - charm: - - name: haproxy-operator - working-directory: ./haproxy-operator - modules: | - [ - "test_action.py", - "test_actions.py", - "test_charm.py", - "test_config.py", - "test_cos.py", - "test_ha.py", - "test_haproxy_route.py", - "test_http_interface.py", - "test_ingress.py", - "test_ingress_per_unit.py", - "test_haproxy_route_tcp.py", - "test_haproxy_route_grpc.py", - "test_haproxy_route_https_backend.py" - ] - - name: haproxy-spoe-auth-operator - working-directory: ./haproxy-spoe-auth-operator - modules: '["test_charm.py"]' - - name: haproxy-ddos-protection-configurator - working-directory: ./haproxy-ddos-protection-configurator - modules: '["test_charm.py"]' - - name: haproxy-route-policy-operator - working-directory: ./haproxy-route-policy-operator - modules: '["test_charm.py", "test_haproxy_route_policy_relation.py"]' - name: Integration tests for ${{ matrix.charm.name }} - uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main - secrets: inherit - with: - provider: lxd - juju-channel: 3/stable - self-hosted-runner: true - charmcraft-channel: latest/edge - working-directory: ${{ matrix.charm.working-directory }} - modules: ${{ matrix.charm.modules }} - with-uv: true - integration-tests-global: - uses: - canonical/operator-workflows/.github/workflows/integration_test.yaml@main + integration-test: + uses: canonical/charm-ci/.github/workflows/integration-test.yml@main secrets: inherit - with: - use-canonical-k8s: true - provider: 'k8s' - channel: '1.32-classic/stable' - self-hosted-runner: true - juju-channel: 3/stable - modules: | - [ - "test_oauth_spoe.py", - "test_haproxy_ddos.py", - "test_haproxy_route_policy.py" - ] - with-uv: true - pre-run-script: ./tests/integration/setup-integration-tests.sh - allure-report: - if: ${{ !cancelled() && github.event_name == 'schedule' }} - needs: - - integration-tests - - integration-tests-global - uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main + permissions: + contents: read + packages: write + actions: read diff --git a/artifacts.yaml b/artifacts.yaml new file mode 100644 index 000000000..bbfe7053d --- /dev/null +++ b/artifacts.yaml @@ -0,0 +1,33 @@ +version: 1 +rocks: [] +charms: +- name: haproxy-ddos-protection-configurator + charmcraft-yaml: haproxy-ddos-protection-configurator/charmcraft.yaml + resources: {} + platforms: + - arch: amd64 +- name: haproxy + charmcraft-yaml: haproxy-operator/charmcraft.yaml + resources: {} + platforms: + - arch: amd64 +- name: haproxy-route-policy + charmcraft-yaml: haproxy-route-policy-operator/charmcraft.yaml + resources: {} + platforms: + - arch: amd64 +- name: haproxy-spoe-auth + charmcraft-yaml: haproxy-spoe-auth-operator/charmcraft.yaml + resources: {} + platforms: + - arch: amd64 +snaps: +- name: haproxy-route-policy + snapcraft-yaml: haproxy-route-policy/snap/snapcraft.yaml + pack-dir: haproxy-route-policy + platforms: + - arch: amd64 +- name: haproxy-spoe-auth + snapcraft-yaml: haproxy-spoe-auth-snap/snapcraft.yaml + platforms: + - arch: amd64 diff --git a/concierge.yaml b/concierge.yaml new file mode 100644 index 000000000..8634812c5 --- /dev/null +++ b/concierge.yaml @@ -0,0 +1,10 @@ +juju: + channel: 3/stable + model-defaults: + test-mode: "true" + automatically-retry-hooks: "false" + +providers: + lxd: + enable: true + bootstrap: true diff --git a/spread.yaml b/spread.yaml index b15cc34e8..3721cf000 100644 --- a/spread.yaml +++ b/spread.yaml @@ -1,35 +1,38 @@ -# Copyright 2026 Canonical Ltd. -# See LICENSE file for licensing details. - -project: haproxy-operator-tests - +project: haproxy-operator +path: /home/ubuntu/proj +kill-timeout: 60m +warn-timeout: 1m backends: - github-ci: - type: adhoc - - allocate: | - echo "Allocating ad-hoc $SPREAD_SYSTEM" - if [ -z "${GITHUB_RUN_ID:-}" ]; then - FATAL "this back-end only works inside GitHub CI" - exit 1 - fi - echo 'ubuntu ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/99-spread-users - ADDRESS localhost:22 - discard: | - echo "Discarding ad-hoc $SPREAD_SYSTEM" + integration-test: + type: integration-test systems: - # username and password are required because docs-spread.yaml creates a new user (ubuntu:ubuntu) - # Before tests are ran. - - ubuntu-24.04: - username: ubuntu - password: ubuntu - workers: 1 - -suites: - tests/spread/: - summary: Automated spread testing - systems: - - ubuntu-24.04 - -path: /home/spread/proj -kill-timeout: 1h + - ubuntu-24.04 +environment: + CONCIERGE: '$(HOST: echo "${CONCIERGE:-concierge.yaml}")' + OPCLI_GIT_REF: '$(HOST: echo "${OPCLI_GIT_REF:-main}")' +exclude: +- .git +- .tox +- .venv +- .*_cache +integration-suites: + haproxy-operator/tests/integration/: + summary: haproxy-operator integration tests + cwd: ./ + backends: + - integration-test + haproxy-spoe-auth-operator/tests/integration/: + summary: haproxy-spoe-auth-operator integration tests + cwd: ./ + backends: + - integration-test + haproxy-ddos-protection-configurator/tests/integration/: + summary: haproxy-ddos-protection-configurator integration tests + cwd: ./ + backends: + - integration-test + haproxy-route-policy-operator/tests/integration/: + summary: haproxy-route-policy-operator integration tests + cwd: ./ + backends: + - integration-test From 8fb4d7066d175ca4f8bf9208db64cd47f7c1df62 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 2 Jun 2026 13:11:47 +0000 Subject: [PATCH 02/21] fix: use working-dir, add global k8s integration suite - Replace invalid 'cwd' key with 'working-dir' in all per-charm suites - Set working-dir to each charm's sub-directory so pytest runs from the correct root (relative paths in conftest.py resolve correctly) - Add tests/integration/ suite for cross-charm tests requiring canonical-k8s - Create concierge-ck8s.yaml with LXD + canonical-k8s 1.32-classic/stable (k8s not bootstrapped as juju controller; conftest handles that itself) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- concierge-ck8s.yaml | 14 ++++++++++++++ spread.yaml | 15 +++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 concierge-ck8s.yaml diff --git a/concierge-ck8s.yaml b/concierge-ck8s.yaml new file mode 100644 index 000000000..01c750ec3 --- /dev/null +++ b/concierge-ck8s.yaml @@ -0,0 +1,14 @@ +juju: + channel: 3/stable + model-defaults: + test-mode: "true" + automatically-retry-hooks: "false" + +providers: + lxd: + enable: true + bootstrap: true + k8s: + enable: true + bootstrap: false + channel: 1.32-classic/stable diff --git a/spread.yaml b/spread.yaml index 3721cf000..062dab72b 100644 --- a/spread.yaml +++ b/spread.yaml @@ -18,21 +18,28 @@ exclude: integration-suites: haproxy-operator/tests/integration/: summary: haproxy-operator integration tests - cwd: ./ + working-dir: haproxy-operator/ backends: - integration-test haproxy-spoe-auth-operator/tests/integration/: summary: haproxy-spoe-auth-operator integration tests - cwd: ./ + working-dir: haproxy-spoe-auth-operator/ backends: - integration-test haproxy-ddos-protection-configurator/tests/integration/: summary: haproxy-ddos-protection-configurator integration tests - cwd: ./ + working-dir: haproxy-ddos-protection-configurator/ backends: - integration-test haproxy-route-policy-operator/tests/integration/: summary: haproxy-route-policy-operator integration tests - cwd: ./ + working-dir: haproxy-route-policy-operator/ backends: - integration-test + tests/integration/: + summary: global integration tests (cross-charm, requires LXD + canonical-k8s) + working-dir: ./ + backends: + - integration-test + environment: + CONCIERGE: concierge-ck8s.yaml From 005d7070056f55966ea56a9b066af8f29263e354 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 2 Jun 2026 14:22:15 +0000 Subject: [PATCH 03/21] fix: filter charm file by name in ddos-protection-configurator tests opcli passes --charm-file for all built charms; change action to 'append' and filter by charm name to avoid deploying the wrong charm. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- haproxy-ddos-protection-configurator/tests/conftest.py | 2 +- .../tests/integration/conftest.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/haproxy-ddos-protection-configurator/tests/conftest.py b/haproxy-ddos-protection-configurator/tests/conftest.py index 09a84dda8..3369b407e 100644 --- a/haproxy-ddos-protection-configurator/tests/conftest.py +++ b/haproxy-ddos-protection-configurator/tests/conftest.py @@ -10,4 +10,4 @@ def pytest_addoption(parser): Args: parser: Pytest parser. """ - parser.addoption("--charm-file", action="store") + parser.addoption("--charm-file", action="append", default=[]) diff --git a/haproxy-ddos-protection-configurator/tests/integration/conftest.py b/haproxy-ddos-protection-configurator/tests/integration/conftest.py index 20192077f..a17312b26 100644 --- a/haproxy-ddos-protection-configurator/tests/integration/conftest.py +++ b/haproxy-ddos-protection-configurator/tests/integration/conftest.py @@ -16,8 +16,10 @@ @pytest.fixture(scope="session", name="charm") def charm_fixture(pytestconfig: pytest.Config): """Pytest fixture that returns the --charm-file.""" - charm = pytestconfig.getoption("--charm-file") - assert charm, "--charm-file must be set" + charm_name = "haproxy-ddos-protection-configurator" + charm_files = pytestconfig.getoption("--charm-file") + charm = next((f for f in charm_files if f"{charm_name}_" in f), None) + assert charm, f"--charm-file matching {charm_name} must be set" return charm From 0ce1239ae9b518ebdd565f6ab944cfc1fd03c2f6 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 2 Jun 2026 14:23:18 +0000 Subject: [PATCH 04/21] Revert "fix: filter charm file by name in ddos-protection-configurator tests" This reverts commit 005d7070056f55966ea56a9b066af8f29263e354. --- haproxy-ddos-protection-configurator/tests/conftest.py | 2 +- .../tests/integration/conftest.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/haproxy-ddos-protection-configurator/tests/conftest.py b/haproxy-ddos-protection-configurator/tests/conftest.py index 3369b407e..09a84dda8 100644 --- a/haproxy-ddos-protection-configurator/tests/conftest.py +++ b/haproxy-ddos-protection-configurator/tests/conftest.py @@ -10,4 +10,4 @@ def pytest_addoption(parser): Args: parser: Pytest parser. """ - parser.addoption("--charm-file", action="append", default=[]) + parser.addoption("--charm-file", action="store") diff --git a/haproxy-ddos-protection-configurator/tests/integration/conftest.py b/haproxy-ddos-protection-configurator/tests/integration/conftest.py index a17312b26..20192077f 100644 --- a/haproxy-ddos-protection-configurator/tests/integration/conftest.py +++ b/haproxy-ddos-protection-configurator/tests/integration/conftest.py @@ -16,10 +16,8 @@ @pytest.fixture(scope="session", name="charm") def charm_fixture(pytestconfig: pytest.Config): """Pytest fixture that returns the --charm-file.""" - charm_name = "haproxy-ddos-protection-configurator" - charm_files = pytestconfig.getoption("--charm-file") - charm = next((f for f in charm_files if f"{charm_name}_" in f), None) - assert charm, f"--charm-file matching {charm_name} must be set" + charm = pytestconfig.getoption("--charm-file") + assert charm, "--charm-file must be set" return charm From 13b372230bab100e4290a40db9b2fd2af7f49b66 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 2 Jun 2026 14:23:47 +0000 Subject: [PATCH 05/21] fix: use pytest-arguments-template to pass correct charm file for ddos suite opcli passes all charm files by default; use pytest-arguments-template to restrict --charm-file to only haproxy-ddos-protection-configurator. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- spread.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spread.yaml b/spread.yaml index 062dab72b..652ba99b3 100644 --- a/spread.yaml +++ b/spread.yaml @@ -31,6 +31,12 @@ integration-suites: working-dir: haproxy-ddos-protection-configurator/ backends: - integration-test + pytest-arguments-template: | + {% for charm in artifacts.charms if charm.name == "haproxy-ddos-protection-configurator" %} + {% for build in charm.builds if build.arch == arch %} + --charm-file={{ build.path }} + {% endfor %} + {% endfor %} haproxy-route-policy-operator/tests/integration/: summary: haproxy-route-policy-operator integration tests working-dir: haproxy-route-policy-operator/ From 7efcd339e0b54480a6aa22f6211467a45949791c Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 2 Jun 2026 14:25:56 +0000 Subject: [PATCH 06/21] style: use selectattr filter for cleaner Jinja2 in pytest-arguments-template Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- spread.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spread.yaml b/spread.yaml index 652ba99b3..8a19fd8c9 100644 --- a/spread.yaml +++ b/spread.yaml @@ -32,10 +32,9 @@ integration-suites: backends: - integration-test pytest-arguments-template: | - {% for charm in artifacts.charms if charm.name == "haproxy-ddos-protection-configurator" %} - {% for build in charm.builds if build.arch == arch %} - --charm-file={{ build.path }} - {% endfor %} + {% set charm = artifacts.charms | selectattr("name", "equalto", "haproxy-ddos-protection-configurator") | first %} + {% for build in charm.builds | selectattr("arch", "equalto", arch) %} + --charm-file={{ build.path }} {% endfor %} haproxy-route-policy-operator/tests/integration/: summary: haproxy-route-policy-operator integration tests From e263d31bd887db186e8bac7749e74ba5d94d51e5 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 2 Jun 2026 14:49:25 +0000 Subject: [PATCH 07/21] fix: resolve charm-file path relative to working-dir in ddos suite build.path is relative to the project root (e.g. './built-charm-.../charm.charm') but pytest runs from haproxy-ddos-protection-configurator/ due to working-dir, so replace './' with '../' to make the path resolve correctly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- spread.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spread.yaml b/spread.yaml index 8a19fd8c9..25d32a4b2 100644 --- a/spread.yaml +++ b/spread.yaml @@ -34,7 +34,7 @@ integration-suites: pytest-arguments-template: | {% set charm = artifacts.charms | selectattr("name", "equalto", "haproxy-ddos-protection-configurator") | first %} {% for build in charm.builds | selectattr("arch", "equalto", arch) %} - --charm-file={{ build.path }} + --charm-file={{ build.path | replace('./', '../', 1) }} {% endfor %} haproxy-route-policy-operator/tests/integration/: summary: haproxy-route-policy-operator integration tests From c7e5e5502dd4834a8e5814c4aae78059a9ced6a1 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 2 Jun 2026 15:01:11 +0000 Subject: [PATCH 08/21] fix: add pytest-arguments-template to all per-charm integration suites Each suite now passes only its own charm file, avoiding cross-charm contamination and fixing the relative path issue introduced by working-dir. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- spread.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spread.yaml b/spread.yaml index 25d32a4b2..821953b03 100644 --- a/spread.yaml +++ b/spread.yaml @@ -21,11 +21,21 @@ integration-suites: working-dir: haproxy-operator/ backends: - integration-test + pytest-arguments-template: | + {% set charm = artifacts.charms | selectattr("name", "equalto", "haproxy") | first %} + {% for build in charm.builds | selectattr("arch", "equalto", arch) %} + --charm-file={{ build.path | replace('./', '../', 1) }} + {% endfor %} haproxy-spoe-auth-operator/tests/integration/: summary: haproxy-spoe-auth-operator integration tests working-dir: haproxy-spoe-auth-operator/ backends: - integration-test + pytest-arguments-template: | + {% set charm = artifacts.charms | selectattr("name", "equalto", "haproxy-spoe-auth") | first %} + {% for build in charm.builds | selectattr("arch", "equalto", arch) %} + --charm-file={{ build.path | replace('./', '../', 1) }} + {% endfor %} haproxy-ddos-protection-configurator/tests/integration/: summary: haproxy-ddos-protection-configurator integration tests working-dir: haproxy-ddos-protection-configurator/ @@ -41,6 +51,11 @@ integration-suites: working-dir: haproxy-route-policy-operator/ backends: - integration-test + pytest-arguments-template: | + {% set charm = artifacts.charms | selectattr("name", "equalto", "haproxy-route-policy") | first %} + {% for build in charm.builds | selectattr("arch", "equalto", arch) %} + --charm-file={{ build.path | replace('./', '../', 1) }} + {% endfor %} tests/integration/: summary: global integration tests (cross-charm, requires LXD + canonical-k8s) working-dir: ./ From 6a15bd0de141b0ae27ec53d70bd3204a3a989b7b Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 2 Jun 2026 15:13:10 +0000 Subject: [PATCH 09/21] fix: install charmcraft in concierge.yaml for legacy pytest-operator tests Legacy tests under haproxy-operator/tests/integration/legacy/ use pytest-operator which unconditionally checks for charmcraft at startup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- concierge.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/concierge.yaml b/concierge.yaml index 8634812c5..ed99c6d66 100644 --- a/concierge.yaml +++ b/concierge.yaml @@ -8,3 +8,9 @@ providers: lxd: enable: true bootstrap: true + +host: + snaps: + charmcraft: + channel: latest/stable + classic: true From ee68906046999cf97c9a12caa7a312f229028402 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 2 Jun 2026 15:42:27 +0000 Subject: [PATCH 10/21] fix: add k8s cloud setup as spread prepare script for global integration suite Replaces the operator-workflows pre-run-script. Runs as root (spread default) but delegates juju to the ubuntu user where concierge bootstrapped the controller. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- spread.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spread.yaml b/spread.yaml index 821953b03..7dd63add7 100644 --- a/spread.yaml +++ b/spread.yaml @@ -63,3 +63,5 @@ integration-suites: - integration-test environment: CONCIERGE: concierge-ck8s.yaml + prepare: | + k8s config | sudo -u ubuntu juju add-k8s ck8s --client From 6b85ae2e495db259df7507cd431175e75f089522 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Tue, 2 Jun 2026 15:46:51 +0000 Subject: [PATCH 11/21] fix: let conftest own the LXD controller bootstrap in ck8s suite Concierge was bootstrapping concierge-lxd while conftest bootstraps localhost, resulting in two controllers. Set bootstrap: false so concierge only installs LXD+Juju and the conftest manages its own localhost controller. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- concierge-ck8s.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concierge-ck8s.yaml b/concierge-ck8s.yaml index 01c750ec3..65a11a852 100644 --- a/concierge-ck8s.yaml +++ b/concierge-ck8s.yaml @@ -7,7 +7,7 @@ juju: providers: lxd: enable: true - bootstrap: true + bootstrap: false k8s: enable: true bootstrap: false From 6f4afdbc4777bfd4618b9e85b96db7dd86c7655b Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Wed, 3 Jun 2026 07:52:33 +0000 Subject: [PATCH 12/21] ci: relaunch CI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> From 69838670eb4c14574ac6f3315a320e93acd89e8a Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Wed, 3 Jun 2026 08:58:36 +0000 Subject: [PATCH 13/21] fix: enable load-balancer feature in concierge-ck8s.yaml Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- concierge-ck8s.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/concierge-ck8s.yaml b/concierge-ck8s.yaml index 65a11a852..3dd9c1bec 100644 --- a/concierge-ck8s.yaml +++ b/concierge-ck8s.yaml @@ -12,3 +12,8 @@ providers: enable: true bootstrap: false channel: 1.32-classic/stable + features: + load-balancer: + enabled: true + l2-mode: true + cidrs: 10.43.45.0/24 From 3ad48ee99553892919011ae0d0cc42da88b3fba3 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Wed, 3 Jun 2026 09:44:32 +0000 Subject: [PATCH 14/21] fix: re-enable LXD bootstrap in concierge-ck8s.yaml Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- concierge-ck8s.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concierge-ck8s.yaml b/concierge-ck8s.yaml index 3dd9c1bec..eba650f8f 100644 --- a/concierge-ck8s.yaml +++ b/concierge-ck8s.yaml @@ -7,7 +7,7 @@ juju: providers: lxd: enable: true - bootstrap: false + bootstrap: true k8s: enable: true bootstrap: false From 723dbbc1e65aa7d2f2dce0f712544e347d786d82 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Wed, 3 Jun 2026 09:49:35 +0000 Subject: [PATCH 15/21] fix: add license headers to new YAML files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- artifacts.yaml | 3 +++ concierge-ck8s.yaml | 3 +++ concierge.yaml | 3 +++ spread.yaml | 3 +++ 4 files changed, 12 insertions(+) diff --git a/artifacts.yaml b/artifacts.yaml index bbfe7053d..38fa32839 100644 --- a/artifacts.yaml +++ b/artifacts.yaml @@ -1,3 +1,6 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + version: 1 rocks: [] charms: diff --git a/concierge-ck8s.yaml b/concierge-ck8s.yaml index eba650f8f..af5ff4499 100644 --- a/concierge-ck8s.yaml +++ b/concierge-ck8s.yaml @@ -1,3 +1,6 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + juju: channel: 3/stable model-defaults: diff --git a/concierge.yaml b/concierge.yaml index ed99c6d66..4194f1c29 100644 --- a/concierge.yaml +++ b/concierge.yaml @@ -1,3 +1,6 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + juju: channel: 3/stable model-defaults: diff --git a/spread.yaml b/spread.yaml index 7dd63add7..0435e83b3 100644 --- a/spread.yaml +++ b/spread.yaml @@ -1,3 +1,6 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + project: haproxy-operator path: /home/ubuntu/proj kill-timeout: 60m From c51c04c4a7001d8bdb2b18c0b4dbe1915528e25c Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Wed, 3 Jun 2026 09:57:12 +0000 Subject: [PATCH 16/21] fix: use concierge-lxd controller name to reuse concierge-bootstrapped controller Avoids bootstrapping a second LXD controller when concierge already bootstrapped one named concierge-lxd. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/integration/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 91bdf28e5..9ca5920aa 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -32,7 +32,7 @@ def lxd_juju_fixture(request: pytest.FixtureRequest): """Bootstrap a new lxd controller and model and return a Juju fixture for it.""" juju = jubilant.Juju() - lxd_controller_name = "localhost" + lxd_controller_name = "concierge-lxd" lxd_cloud_name = "localhost" juju.wait_timeout = JUJU_WAIT_TIMEOUT try: From 5f9ced00f07bbbfad2a46abc0e3945bdd4e39774 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Wed, 3 Jun 2026 10:47:49 +0000 Subject: [PATCH 17/21] fix: move k8s cloud setup from spread prepare to pytest fixture Replaces the spread prepare script with a session-scoped pytest fixture that runs 'juju add-k8s ck8s --client', keeping setup logic closer to the tests that need it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- spread.yaml | 2 -- tests/integration/conftest.py | 13 ++++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/spread.yaml b/spread.yaml index 0435e83b3..0403ebdda 100644 --- a/spread.yaml +++ b/spread.yaml @@ -66,5 +66,3 @@ integration-suites: - integration-test environment: CONCIERGE: concierge-ck8s.yaml - prepare: | - k8s config | sudo -u ubuntu juju add-k8s ck8s --client diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 9ca5920aa..7fd40480b 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -27,6 +27,17 @@ POSTGRESQL_APPLICATION = "db" +@pytest.fixture(scope="session", name="k8s_cloud_client") +def k8s_cloud_client_fixture(): + """Register the k8s cloud in the local Juju client config.""" + k8s_config = subprocess.run(["k8s", "config"], capture_output=True, check=True) # nosec + subprocess.run( # nosec + ["juju", "add-k8s", "ck8s", "--client"], + input=k8s_config.stdout, + check=True, + ) + + @pytest.fixture(scope="session", name="lxd_juju") def lxd_juju_fixture(request: pytest.FixtureRequest): """Bootstrap a new lxd controller and model and return a Juju fixture for it.""" @@ -69,7 +80,7 @@ def lxd_juju_fixture(request: pytest.FixtureRequest): @pytest.fixture(scope="session", name="k8s_juju") -def k8s_juju_fixture(lxd_juju: jubilant.Juju, request: pytest.FixtureRequest): +def k8s_juju_fixture(lxd_juju: jubilant.Juju, request: pytest.FixtureRequest, k8s_cloud_client): """Bootstrap a new k8s model in the lxd controller and return a Juju fixture for it.""" clouds_json = lxd_juju.cli("clouds", "--format=json", include_model=False) clouds = json.loads(clouds_json) From 793e3def5fb6c84bd7fdb5832535af5c1cd3428c Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Wed, 3 Jun 2026 11:00:07 +0000 Subject: [PATCH 18/21] debug: print k8s config stdout/stderr on failure Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/integration/conftest.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 7fd40480b..494cfa84d 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -30,7 +30,15 @@ @pytest.fixture(scope="session", name="k8s_cloud_client") def k8s_cloud_client_fixture(): """Register the k8s cloud in the local Juju client config.""" - k8s_config = subprocess.run(["k8s", "config"], capture_output=True, check=True) # nosec + k8s_config = subprocess.run( # nosec + ["k8s", "config"], capture_output=True + ) + if k8s_config.returncode != 0: + raise RuntimeError( + f"k8s config failed (exit {k8s_config.returncode}):\n" + f"stdout: {k8s_config.stdout.decode()}\n" + f"stderr: {k8s_config.stderr.decode()}" + ) subprocess.run( # nosec ["juju", "add-k8s", "ck8s", "--client"], input=k8s_config.stdout, From 9fad1816159cd4510885ba14ed5033d7e39ae733 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Wed, 3 Jun 2026 11:11:16 +0000 Subject: [PATCH 19/21] fix: use sudo for k8s config to get kubeconfig as root Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/integration/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 494cfa84d..f92428885 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -31,7 +31,7 @@ def k8s_cloud_client_fixture(): """Register the k8s cloud in the local Juju client config.""" k8s_config = subprocess.run( # nosec - ["k8s", "config"], capture_output=True + ["sudo", "k8s", "config"], capture_output=True ) if k8s_config.returncode != 0: raise RuntimeError( From 9b31234a1135186f82410a8ac7db92d22283c3b2 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Wed, 3 Jun 2026 11:26:01 +0000 Subject: [PATCH 20/21] fix: increase haproxy-spoe-auth wait timeout from 300s to 330s Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/integration/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index f92428885..61f6ee305 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -374,7 +374,7 @@ def deployer(haproxy_spoe_name, hostname): ) ca_cert = ca_cert_result.results["ca-certificate"].encode("utf-8") lxd_juju.wait( - lambda status: not status.apps[haproxy_spoe_name].is_waiting, timeout=5 * 60 + lambda status: not status.apps[haproxy_spoe_name].is_waiting, timeout=330 ) logger.info(lxd_juju.status().apps[haproxy_spoe_name]) inject_ca_certificate(lxd_juju, f"{haproxy_spoe_name}/0", ca_cert) From 2100a97f51fd81df76fa4831920ea3227c6874f4 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Wed, 3 Jun 2026 12:32:18 +0000 Subject: [PATCH 21/21] Add tutorial spread suite using opcli-minimal backend - Add 'docs' opcli-minimal backend to spread.yaml - Add tests/spread/tutorial/ suite running opcli tutorial expand on docs/tutorial/getting-started.md - Add TUTORIAL env var pointing to the tutorial markdown file - Create tests/spread/tutorial/run/task.yaml (replaces the operator-workflows generated task.yaml) - Remove .github/workflows/docs_spread.yaml (replaced by the tutorial suite running in the main integration_test workflow) - Add Monday 09:00 UTC schedule to integration_test.yaml, aligned with weekly haproxy stable release - Remove tests/spread/tutorial/ from .gitignore (now committed) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/docs_spread.yaml | 18 ------------------ .github/workflows/integration_test.yaml | 1 + .gitignore | 1 - spread.yaml | 10 ++++++++++ tests/spread/tutorial/run/task.yaml | 9 +++++++++ 5 files changed, 20 insertions(+), 19 deletions(-) delete mode 100644 .github/workflows/docs_spread.yaml create mode 100644 tests/spread/tutorial/run/task.yaml diff --git a/.github/workflows/docs_spread.yaml b/.github/workflows/docs_spread.yaml deleted file mode 100644 index 323130614..000000000 --- a/.github/workflows/docs_spread.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: Automated spread testing - -on: - workflow_dispatch: - pull_request: - paths: - - 'docs/tutorial/getting-started.md' - schedule: - - cron: 0 9 * * 1 # At 09:00 UTC on Monday, aligned with the weekly haproxy stable release. - -jobs: - docs-checks: - uses: canonical/operator-workflows/.github/workflows/docs_spread.yaml@main - secrets: inherit - with: - input-file: docs/tutorial/getting-started.md - output-dir: tests/spread/tutorial - spread-job: github-ci:ubuntu-24.04:tests/ diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 741cc3411..7f82b09fb 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -7,6 +7,7 @@ on: pull_request: schedule: - cron: "0 15 * * SAT" + - cron: "0 9 * * MON" # aligned with weekly haproxy stable release jobs: integration-test: diff --git a/.gitignore b/.gitignore index 6326091fe..ec6ee4b95 100644 --- a/.gitignore +++ b/.gitignore @@ -32,5 +32,4 @@ terraform/**/*.tfstate* haproxy-route-policy/db.sqlite3 haproxy-route-policy/.python-version -tests/spread/tutorial/ **/.spread-reuse* diff --git a/spread.yaml b/spread.yaml index 0403ebdda..556d6e2db 100644 --- a/spread.yaml +++ b/spread.yaml @@ -10,9 +10,19 @@ backends: type: integration-test systems: - ubuntu-24.04 + docs: + type: opcli-minimal + systems: + - ubuntu-24.04 environment: CONCIERGE: '$(HOST: echo "${CONCIERGE:-concierge.yaml}")' OPCLI_GIT_REF: '$(HOST: echo "${OPCLI_GIT_REF:-main}")' + TUTORIAL: docs/tutorial/getting-started.md +suites: + tests/spread/tutorial/: + summary: Tutorial smoke test (getting-started.md) + systems: + - ubuntu-24.04 exclude: - .git - .tox diff --git a/tests/spread/tutorial/run/task.yaml b/tests/spread/tutorial/run/task.yaml new file mode 100644 index 000000000..8ee0a0aae --- /dev/null +++ b/tests/spread/tutorial/run/task.yaml @@ -0,0 +1,9 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +summary: Run getting-started tutorial + +execute: | + runuser -l ubuntu -s /bin/bash -c \ + 'set -ex; . <(opcli tutorial expand -- "$1")' \ + _ "${SPREAD_PATH}${TUTORIAL}"