From a850b007d3da240d67f13134a122134101760562 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Fri, 21 Nov 2025 03:00:15 +0000 Subject: [PATCH 01/53] refactor: streamline OpenStack options and remove deprecated token_alt fixture --- tests/conftest.py | 72 +++++++++-------------------------- tests/integration/conftest.py | 40 +++++++------------ 2 files changed, 31 insertions(+), 81 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 9968b1d664..fed9d64b89 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ """Fixtures for github runner charm.""" +import os from pytest import Parser @@ -83,95 +84,56 @@ def pytest_addoption(parser: Parser): ) # Private endpoint options AMD64 parser.addoption( - "--openstack-network-name-amd64", + "--openstack-network-name", action="store", help="The Openstack network to create testing instances under.", ) parser.addoption( - "--openstack-flavor-name-amd64", + "--openstack-flavor-name", action="store", help="The Openstack flavor to create testing instances with.", ) parser.addoption( - "--openstack-auth-url-amd64", + "--openstack-auth-url", action="store", help="The URL to Openstack authentication service, i.e. keystone.", + default=os.environ.get("OS_AUTH_URL"), ) parser.addoption( - "--openstack-password-amd64", + "--openstack-password", action="store", help="The password to authenticate to Openstack service.", + default=os.environ.get("OS_PASSWORD"), ) parser.addoption( - "--openstack-project-domain-name-amd64", + "--openstack-project-domain-name", action="store", help="The Openstack project domain name to use.", + default=os.environ.get("OS_PROJECT_DOMAIN_NAME"), ) parser.addoption( - "--openstack-project-name-amd64", + "--openstack-project-name", action="store", help="The Openstack project name to use.", + default=os.environ.get("OS_PROJECT_NAME"), ) parser.addoption( - "--openstack-user-domain-name-amd64", + "--openstack-user-domain-name", action="store", help="The Openstack user domain name to use.", + default=os.environ.get("OS_USER_DOMAIN_NAME"), ) parser.addoption( - "--openstack-username-amd64", + "--openstack-username", action="store", help="The Openstack user to authenticate as.", + default=os.environ.get("OS_USERNAME"), ) parser.addoption( - "--openstack-region-name-amd64", - action="store", - help="The Openstack region to authenticate to.", - ) - # Private endpoint options ARM64 - parser.addoption( - "--openstack-network-name-arm64", - action="store", - help="The Openstack network to create testing instances under.", - ) - parser.addoption( - "--openstack-flavor-name-arm64", - action="store", - help="The Openstack flavor to create testing instances with.", - ) - parser.addoption( - "--openstack-auth-url-arm64", - action="store", - help="The URL to Openstack authentication service, i.e. keystone.", - ) - parser.addoption( - "--openstack-password-arm64", - action="store", - help="The password to authenticate to Openstack service.", - ) - parser.addoption( - "--openstack-project-domain-name-arm64", - action="store", - help="The Openstack project domain name to use.", - ) - parser.addoption( - "--openstack-project-name-arm64", - action="store", - help="The Openstack project name to use.", - ) - parser.addoption( - "--openstack-user-domain-name-arm64", - action="store", - help="The Openstack user domain name to use.", - ) - parser.addoption( - "--openstack-username-arm64", - action="store", - help="The Openstack user to authenticate as.", - ) - parser.addoption( - "--openstack-region-name-arm64", + "--openstack-region-name", action="store", help="The Openstack region to authenticate to.", + default=os.environ.get("OS_REGION_NAME"), ) # Interface testing args parser.addoption( diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index bf9f9a01a7..e48c85fec3 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -132,18 +132,6 @@ def token(pytestconfig: pytest.Config) -> str: return random_token -@pytest.fixture(scope="module") -def token_alt(pytestconfig: pytest.Config, token: str) -> str: - """Configured token_alt setting.""" - token_alt = pytestconfig.getoption("--token-alt") or os.environ.get("INTEGRATION_TOKEN_ALT") - assert token_alt, ( - "Please specify the --token-alt command line option with GitHub Personal " - "Access Token value." - ) - assert token_alt != token, "Please specify a different token for --token-alt" - return token_alt - - @pytest.fixture(scope="module") def http_proxy(pytestconfig: pytest.Config) -> str: """Configured http_proxy setting.""" @@ -189,17 +177,17 @@ def openstack_no_proxy_fixture(pytestconfig: pytest.Config) -> str: @pytest.fixture(scope="module", name="private_endpoint_config") def private_endpoint_config_fixture(pytestconfig: pytest.Config) -> PrivateEndpointConfigs | None: """The private endpoint configuration values.""" - auth_url = pytestconfig.getoption("--openstack-auth-url-amd64") - password = pytestconfig.getoption("--openstack-password-amd64") - password = password or os.environ.get("INTEGRATION_OPENSTACK_PASSWORD_AMD64") - assert ( - password - ), "Please specify the --openstack-password-amd64 option or INTEGRATION_OPENSTACK_PASSWORD_AMD64 environment variable" - project_domain_name = pytestconfig.getoption("--openstack-project-domain-name-amd64") - project_name = pytestconfig.getoption("--openstack-project-name-amd64") - user_domain_name = pytestconfig.getoption("--openstack-user-domain-name-amd64") - user_name = pytestconfig.getoption("--openstack-username-amd64") - region_name = pytestconfig.getoption("--openstack-region-name-amd64") + auth_url = pytestconfig.getoption("--openstack-auth-url") + password = pytestconfig.getoption("--openstack-password") + assert password, ( + "Please specify the --openstack-password option or " + "INTEGRATION_OPENSTACK_PASSWORD or OS_PASSWORD environment variable" + ) + project_domain_name = pytestconfig.getoption("--openstack-project-domain-name") + project_name = pytestconfig.getoption("--openstack-project-name") + user_domain_name = pytestconfig.getoption("--openstack-user-domain-name") + user_name = pytestconfig.getoption("--openstack-username") + region_name = pytestconfig.getoption("--openstack-region-name") if any( not val for val in ( @@ -264,15 +252,15 @@ def clouds_yaml_contents_fixture( @pytest.fixture(scope="module", name="network_name") def network_name_fixture(pytestconfig: pytest.Config) -> str: """Network to use to spawn test instances under.""" - network_name = pytestconfig.getoption("--openstack-network-name-amd64") - assert network_name, "Please specify the --openstack-network-name-amd64 command line option" + network_name = pytestconfig.getoption("--openstack-network-name") + assert network_name, "Please specify the --openstack-network-name command line option" return network_name @pytest.fixture(scope="module", name="flavor_name") def flavor_name_fixture(pytestconfig: pytest.Config) -> str: """Flavor to create testing instances with.""" - flavor_name = pytestconfig.getoption("--openstack-flavor-name-amd64") + flavor_name = pytestconfig.getoption("--openstack-flavor-name") assert flavor_name, "Please specify the --openstack-flavor-name command line option" return flavor_name From 3ef26d0baa87850f6573fd77874fb270af252847 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Fri, 21 Nov 2025 03:02:09 +0000 Subject: [PATCH 02/53] chore: update integration test environment variables for OpenStack --- tox.ini | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a09c975544..c691f5f7bc 100644 --- a/tox.ini +++ b/tox.ini @@ -114,9 +114,15 @@ commands = description = Run integration tests pass_env = PYTEST_ADDOPTS - INTEGRATION_OPENSTACK_PASSWORD_AMD64 INTEGRATION_TOKEN INTEGRATION_TOKEN_ALT + OS_AUTH_URL + OS_PASSWORD + OS_PROJECT_DOMAIN_NAME + OS_PROJECT_NAME + OS_REGION_NAME + OS_USER_DOMAIN_NAME + OS_USERNAME deps = juju3.1: juju==3.1.* juju3.6: juju==3.6.* From 2ffb626bd9b65f2acaf0a4c9a80792b7688f1995 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Fri, 21 Nov 2025 03:23:24 +0000 Subject: [PATCH 03/53] chore: apply pflake8 rules to all tests --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e5121fb41b..4721d14afd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ select = ["E", "W", "F", "C", "N", "R", "D", "H"] ignore = ["W503", "E501", "D107", "E203"] # D100, D101, D102, D103, D104: Ignore docstring style issues in tests # temporary disable E402 for the fix in charm.py for lp:2058335 -per-file-ignores = ["src/charm.py:E402", "tests/*:D100,D101,D102,D103,D104,D205,D212"] +per-file-ignores = ["src/charm.py:E402", "**/tests/*:D100,D101,D102,D103,D104,D205,D212"] docstring-convention = "google" # Check for properly formatted copyright header in each file copyright-check = "True" From 0e7133d96575ec15d2ca76ebcb38298a122b64e9 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Fri, 21 Nov 2025 03:25:26 +0000 Subject: [PATCH 04/53] chore: remove reference to unused env var --- tests/integration/conftest.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index e48c85fec3..859e8bdf16 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -179,10 +179,9 @@ def private_endpoint_config_fixture(pytestconfig: pytest.Config) -> PrivateEndpo """The private endpoint configuration values.""" auth_url = pytestconfig.getoption("--openstack-auth-url") password = pytestconfig.getoption("--openstack-password") - assert password, ( - "Please specify the --openstack-password option or " - "INTEGRATION_OPENSTACK_PASSWORD or OS_PASSWORD environment variable" - ) + assert ( + password + ), "Please specify the --openstack-password option or OS_PASSWORD environment variable" project_domain_name = pytestconfig.getoption("--openstack-project-domain-name") project_name = pytestconfig.getoption("--openstack-project-name") user_domain_name = pytestconfig.getoption("--openstack-user-domain-name") From 305f8818edebc941019c703a3430051ddf3c69ec Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Fri, 21 Nov 2025 04:51:48 +0000 Subject: [PATCH 05/53] test: map INTEGRATION_TOKEN env var to --token default value --- tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/conftest.py b/tests/conftest.py index fed9d64b89..4cd9339c31 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,6 +26,7 @@ def pytest_addoption(parser: Parser): "An optionally comma separated GitHub Personal Access Token(s). " "Add more than one to help reduce rate limiting." ), + default=os.environ.get("INTEGRATION_TOKEN"), ) parser.addoption( "--charm-file", action="store", help="The prebuilt github-runner-operator charm file." From 27a4ded5cfb9d47330f33ecfd5a97f5f649b8a6c Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Fri, 21 Nov 2025 04:52:03 +0000 Subject: [PATCH 06/53] test: remove unused --token-alt --- tests/conftest.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4cd9339c31..d42cbf290d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -31,9 +31,6 @@ def pytest_addoption(parser: Parser): parser.addoption( "--charm-file", action="store", help="The prebuilt github-runner-operator charm file." ) - parser.addoption( - "--token-alt", action="store", help="An alternative token to test the change of a token." - ) parser.addoption( "--http-proxy", action="store", From c3a900f1a73ebd7e6fc4068ecf9c9b9d94fec7c4 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Fri, 21 Nov 2025 04:52:23 +0000 Subject: [PATCH 07/53] test: move option parsing to upper module --- 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 859e8bdf16..46ba0093df 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -125,7 +125,7 @@ def path(pytestconfig: pytest.Config) -> str: @pytest.fixture(scope="module") def token(pytestconfig: pytest.Config) -> str: """Configured token setting.""" - token = pytestconfig.getoption("--token") or os.environ.get("INTEGRATION_TOKEN") + token = pytestconfig.getoption("--token") assert token, "Please specify the --token command line option" tokens = {token.strip() for token in token.split(",")} random_token = random.choice(list(tokens)) From 9199b6499ccc40fe4130c980bad509ffcca000d8 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Fri, 21 Nov 2025 06:42:04 +0000 Subject: [PATCH 08/53] chore: update integration test arguments and enhance CI setup guidelines in CONTRIBUTING.md --- .github/workflows/integration_test.yaml | 48 +++++++++++++++++++++++-- CONTRIBUTING.md | 9 +++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 5454c62245..741622ec54 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -21,9 +21,30 @@ jobs: provider: lxd test-tox-env: integration-juju3.6 modules: '["test_charm_metrics_failure", "test_charm_metrics_success", "test_charm_fork_repo", "test_charm_fork_path_change", "test_charm_no_runner", "test_charm_runner", "test_debug_ssh", "test_charm_upgrade", "test_reactive"]' - extra-arguments: '-m openstack --log-format="%(asctime)s %(levelname)s %(message)s"' + # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ + # mapping. See CONTRIBUTING.md for more details. + extra-arguments: | + -m openstack \ + --log-format="%(asctime)s %(levelname)s %(message)s" \ + --path="${{ secrets.INTEGRATION_TEST_PATH }}" \ + --openstack-auth-url="${{ secrets.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ + --openstack-project-domain-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ + --openstack-project-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ + --openstack-user-domain-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ + --openstack-username="${{ secrets.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ + --openstack-region-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ + --openstack-network-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ + --openstack-flavor-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ + --openstack-test-image="${{ secrets.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ + --openstack-test-flavor="${{ secrets.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ + --https-proxy "${{ secrets.INTEGRATION_TEST_HTTPS_PROXY }}" \ + --http-proxy "${{ secrets.INTEGRATION_TEST_HTTP_PROXY }}" \ + --no-proxy "${{ secrets.INTEGRATION_TEST_NO_PROXY }}" \ + --openstack-https-proxy "${{ secrets.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ + --openstack-http-proxy "${{ secrets.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ + --openstack-no-proxy "${{ secrets.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" self-hosted-runner: true - self-hosted-runner-label: pfe-ci + self-hosted-runner-label: pfe-ci openstack-integration-tests-cross-controller-private-endpoint: name: Cross controller integration test using private-endpoint uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main @@ -34,7 +55,28 @@ jobs: provider: lxd test-tox-env: integration-juju3.6 modules: '["test_prometheus_metrics"]' - extra-arguments: '-m openstack --log-format="%(asctime)s %(levelname)s %(message)s"' + # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ + # mapping. See CONTRIBUTING.md for more details. + extra-arguments: | + -m openstack \ + --log-format="%(asctime)s %(levelname)s %(message)s" \ + --path="${{ secrets.INTEGRATION_TEST_PATH }}" \ + --openstack-auth-url="${{ secrets.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ + --openstack-project-domain-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ + --openstack-project-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ + --openstack-user-domain-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ + --openstack-username="${{ secrets.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ + --openstack-region-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ + --openstack-network-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ + --openstack-flavor-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ + --openstack-test-image="${{ secrets.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ + --openstack-test-flavor="${{ secrets.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ + --https-proxy "${{ secrets.INTEGRATION_TEST_HTTPS_PROXY }}" \ + --http-proxy "${{ secrets.INTEGRATION_TEST_HTTP_PROXY }}" \ + --no-proxy "${{ secrets.INTEGRATION_TEST_NO_PROXY }}" \ + --openstack-https-proxy "${{ secrets.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ + --openstack-http-proxy "${{ secrets.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ + --openstack-no-proxy "${{ secrets.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" self-hosted-runner: true self-hosted-runner-label: pfe-ci allure-report: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a5457a6489..9b2626540a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,15 @@ that can be used for linting and formatting code when you're preparing contribut > NOTE: Integration tests are currently intended to run on the CI and may not work locally without further argument. +#### CI testing setup guidelines + +To prefer explicit setting arguments passing, use `extra-arguments` of the +[following reusesable workflow](https://github.com/canonical/operator-workflows/blob/main/.github/workflows/integration_test_run.yaml) +to pass in non-sensitive values. +For sensitive values (`--token`, `--openstack-password`), map them via `INTEGRATION_TEST_SECRET_ENV_NAME_` +[environment variable settings](https://github.com/canonical/github-runner-operator/settings/variables/actions). +This is to prevent GitHub from leaking secrets when passing them over the CLI calls (unresolved). + ### Building the charm Build the charm in this git repository using: From 6e8d7d7fcdcee9ff17e2e90086805640ce881e04 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Fri, 21 Nov 2025 06:53:26 +0000 Subject: [PATCH 09/53] chore: fix vale complaints --- .vale.ini | 2 +- CONTRIBUTING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.vale.ini b/.vale.ini index fe3518b077..ca41729ca0 100644 --- a/.vale.ini +++ b/.vale.ini @@ -5,7 +5,7 @@ StylesPath = .vale/styles Packages = https://github.com/canonical/platform-engineering-vale-package/releases/download/latest/pfe-vale.zip -Vocab = PFE, local +Vocab = PFE, local, CI [*] BasedOnStyles = PFE diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9b2626540a..579bde7d67 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,7 @@ that can be used for linting and formatting code when you're preparing contribut #### CI testing setup guidelines To prefer explicit setting arguments passing, use `extra-arguments` of the -[following reusesable workflow](https://github.com/canonical/operator-workflows/blob/main/.github/workflows/integration_test_run.yaml) +[following reusable workflow](https://github.com/canonical/operator-workflows/blob/main/.github/workflows/integration_test_run.yaml) to pass in non-sensitive values. For sensitive values (`--token`, `--openstack-password`), map them via `INTEGRATION_TEST_SECRET_ENV_NAME_` [environment variable settings](https://github.com/canonical/github-runner-operator/settings/variables/actions). From e32cbc0e57ae496bb8c05026b25dfd3068ba5a68 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Fri, 21 Nov 2025 08:25:41 +0000 Subject: [PATCH 10/53] chore: minor lint & doc fixes --- .vale.ini | 2 +- CONTRIBUTING.md | 5 ++++- pyproject.toml | 2 +- tests/integration/conftest.py | 1 - 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.vale.ini b/.vale.ini index ca41729ca0..fe3518b077 100644 --- a/.vale.ini +++ b/.vale.ini @@ -5,7 +5,7 @@ StylesPath = .vale/styles Packages = https://github.com/canonical/platform-engineering-vale-package/releases/download/latest/pfe-vale.zip -Vocab = PFE, local, CI +Vocab = PFE, local [*] BasedOnStyles = PFE diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 579bde7d67..51a4f6b7d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,13 +42,16 @@ that can be used for linting and formatting code when you're preparing contribut > NOTE: Integration tests are currently intended to run on the CI and may not work locally without further argument. + #### CI testing setup guidelines + To prefer explicit setting arguments passing, use `extra-arguments` of the [following reusable workflow](https://github.com/canonical/operator-workflows/blob/main/.github/workflows/integration_test_run.yaml) to pass in non-sensitive values. For sensitive values (`--token`, `--openstack-password`), map them via `INTEGRATION_TEST_SECRET_ENV_NAME_` -[environment variable settings](https://github.com/canonical/github-runner-operator/settings/variables/actions). +environment variable settings under [repository](https://github.com/canonical/github-runner-operator) +> settings > variables > actions. This is to prevent GitHub from leaking secrets when passing them over the CLI calls (unresolved). ### Building the charm diff --git a/pyproject.toml b/pyproject.toml index 4721d14afd..e5121fb41b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ select = ["E", "W", "F", "C", "N", "R", "D", "H"] ignore = ["W503", "E501", "D107", "E203"] # D100, D101, D102, D103, D104: Ignore docstring style issues in tests # temporary disable E402 for the fix in charm.py for lp:2058335 -per-file-ignores = ["src/charm.py:E402", "**/tests/*:D100,D101,D102,D103,D104,D205,D212"] +per-file-ignores = ["src/charm.py:E402", "tests/*:D100,D101,D102,D103,D104,D205,D212"] docstring-convention = "google" # Check for properly formatted copyright header in each file copyright-check = "True" diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 46ba0093df..9523d38455 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -3,7 +3,6 @@ """Fixtures for github runner charm integration tests.""" import logging -import os import random import secrets import string From 4cbfb031e06ebe90fdeab06452d8d594bf232f52 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Fri, 21 Nov 2025 09:36:25 +0000 Subject: [PATCH 11/53] ci: parse repository settings non-sensitive secrets --- .github/workflows/integration_test.yaml | 92 ++++++++++++++++--------- 1 file changed, 58 insertions(+), 34 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 741622ec54..1835794b01 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -12,8 +12,31 @@ concurrency: cancel-in-progress: true jobs: + parse-integration-test-vars: + runs-on: ubuntu-22.04 + outputs: + path: ${{ secrets.INTEGRATION_TEST_PATH }} + openstack-auth-url: ${{ secrets.INTEGRATION_TEST_OPENSTACK_AUTH_URL }} + openstack-project-domain-name: ${{ secrets.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }} + openstack-project-name: ${{ secrets.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }} + openstack-user-domain-name: ${{ secrets.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }} + openstack-username: ${{ secrets.INTEGRATION_TEST_OPENSTACK_USERNAME }} + openstack-region-name: ${{ secrets.INTEGRATION_TEST_OPENSTACK_REGION_NAME }} + openstack-network-name: ${{ secrets.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }} + openstack-flavor-name: ${{ secrets.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }} + openstack-test-image: ${{ secrets.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }} + openstack-test-flavor: ${{ secrets.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }} + https-proxy: ${{ secrets.INTEGRATION_TEST_HTTPS_PROXY }} + http-proxy: ${{ secrets.INTEGRATION_TEST_HTTP_PROXY }} + no-proxy: ${{ secrets.INTEGRATION_TEST_NO_PROXY }} + openstack-https-proxy: ${{ secrets.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }} + openstack-http-proxy: ${{ secrets.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }} + openstack-no-proxy: ${{ secrets.INTEGRATION_TEST_OPENSTACK_NO_PROXY }} + steps: + - run: echo "Exposing env vars" openstack-integration-tests-private-endpoint: name: Integration test using private-endpoint + needs: parse-integration-test-vars uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main secrets: inherit with: @@ -26,27 +49,28 @@ jobs: extra-arguments: | -m openstack \ --log-format="%(asctime)s %(levelname)s %(message)s" \ - --path="${{ secrets.INTEGRATION_TEST_PATH }}" \ - --openstack-auth-url="${{ secrets.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ - --openstack-project-domain-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ - --openstack-project-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ - --openstack-user-domain-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ - --openstack-username="${{ secrets.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ - --openstack-region-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ - --openstack-network-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ - --openstack-flavor-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ - --openstack-test-image="${{ secrets.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ - --openstack-test-flavor="${{ secrets.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ - --https-proxy "${{ secrets.INTEGRATION_TEST_HTTPS_PROXY }}" \ - --http-proxy "${{ secrets.INTEGRATION_TEST_HTTP_PROXY }}" \ - --no-proxy "${{ secrets.INTEGRATION_TEST_NO_PROXY }}" \ - --openstack-https-proxy "${{ secrets.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ - --openstack-http-proxy "${{ secrets.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ - --openstack-no-proxy "${{ secrets.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" + --path="${{ needs.parse-integration-test-vars.outputs.path }}" \ + --openstack-auth-url="${{ needs.parse-integration-test-vars.outputs.openstack-auth-url }}" \ + --openstack-project-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-domain-name }}" \ + --openstack-project-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-name }}" \ + --openstack-user-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-user-domain-name }}" \ + --openstack-username="${{ needs.parse-integration-test-vars.outputs.openstack-username }}" \ + --openstack-region-name="${{ needs.parse-integration-test-vars.outputs.openstack-region-name }}" \ + --openstack-network-name="${{ needs.parse-integration-test-vars.outputs.openstack-network-name }}" \ + --openstack-flavor-name="${{ needs.parse-integration-test-vars.outputs.openstack-flavor-name }}" \ + --openstack-test-image="${{ needs.parse-integration-test-vars.outputs.openstack-test-image }}" \ + --openstack-test-flavor="${{ needs.parse-integration-test-vars.outputs.openstack-test-flavor }}" \ + --https-proxy "${{ needs.parse-integration-test-vars.outputs.https-proxy }}" \ + --http-proxy "${{ needs.parse-integration-test-vars.outputs.http-proxy }}" \ + --no-proxy "${{ needs.parse-integration-test-vars.outputs.no-proxy }}" \ + --openstack-https-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-https-proxy }}" \ + --openstack-http-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-http-proxy }}" \ + --openstack-no-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-no-proxy }}" self-hosted-runner: true self-hosted-runner-label: pfe-ci openstack-integration-tests-cross-controller-private-endpoint: name: Cross controller integration test using private-endpoint + needs: parse-integration-test-vars uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main secrets: inherit with: @@ -60,23 +84,23 @@ jobs: extra-arguments: | -m openstack \ --log-format="%(asctime)s %(levelname)s %(message)s" \ - --path="${{ secrets.INTEGRATION_TEST_PATH }}" \ - --openstack-auth-url="${{ secrets.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ - --openstack-project-domain-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ - --openstack-project-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ - --openstack-user-domain-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ - --openstack-username="${{ secrets.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ - --openstack-region-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ - --openstack-network-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ - --openstack-flavor-name="${{ secrets.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ - --openstack-test-image="${{ secrets.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ - --openstack-test-flavor="${{ secrets.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ - --https-proxy "${{ secrets.INTEGRATION_TEST_HTTPS_PROXY }}" \ - --http-proxy "${{ secrets.INTEGRATION_TEST_HTTP_PROXY }}" \ - --no-proxy "${{ secrets.INTEGRATION_TEST_NO_PROXY }}" \ - --openstack-https-proxy "${{ secrets.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ - --openstack-http-proxy "${{ secrets.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ - --openstack-no-proxy "${{ secrets.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" + --path="${{ needs.parse-integration-test-vars.outputs.path }}" \ + --openstack-auth-url="${{ needs.parse-integration-test-vars.outputs.openstack-auth-url }}" \ + --openstack-project-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-domain-name }}" \ + --openstack-project-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-name }}" \ + --openstack-user-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-user-domain-name }}" \ + --openstack-username="${{ needs.parse-integration-test-vars.outputs.openstack-username }}" \ + --openstack-region-name="${{ needs.parse-integration-test-vars.outputs.openstack-region-name }}" \ + --openstack-network-name="${{ needs.parse-integration-test-vars.outputs.openstack-network-name }}" \ + --openstack-flavor-name="${{ needs.parse-integration-test-vars.outputs.openstack-flavor-name }}" \ + --openstack-test-image="${{ needs.parse-integration-test-vars.outputs.openstack-test-image }}" \ + --openstack-test-flavor="${{ needs.parse-integration-test-vars.outputs.openstack-test-flavor }}" \ + --https-proxy "${{ needs.parse-integration-test-vars.outputs.https-proxy }}" \ + --http-proxy "${{ needs.parse-integration-test-vars.outputs.http-proxy }}" \ + --no-proxy "${{ needs.parse-integration-test-vars.outputs.no-proxy }}" \ + --openstack-https-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-https-proxy }}" \ + --openstack-http-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-http-proxy }}" \ + --openstack-no-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-no-proxy }}" self-hosted-runner: true self-hosted-runner-label: pfe-ci allure-report: From f7bd4498b91cc6ca5a8e17c1308a2b549e089475 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Sun, 23 Nov 2025 12:07:27 +0000 Subject: [PATCH 12/53] ci: trigger w/ new secrets From e2506384a5efb36f673b63a7736e69bf085d3c9d Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 01:49:22 +0000 Subject: [PATCH 13/53] ci: test secret variable conversion --- .github/workflows/integration_test.yaml | 157 +++++++++++++----------- 1 file changed, 83 insertions(+), 74 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 1835794b01..48fbb4f1ce 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -34,78 +34,87 @@ jobs: openstack-no-proxy: ${{ secrets.INTEGRATION_TEST_OPENSTACK_NO_PROXY }} steps: - run: echo "Exposing env vars" - openstack-integration-tests-private-endpoint: - name: Integration test using private-endpoint + test-secret-outputs: + name: Test secret outputs needs: parse-integration-test-vars - uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main - secrets: inherit - with: - juju-channel: 3.6/stable - provider: lxd - test-tox-env: integration-juju3.6 - modules: '["test_charm_metrics_failure", "test_charm_metrics_success", "test_charm_fork_repo", "test_charm_fork_path_change", "test_charm_no_runner", "test_charm_runner", "test_debug_ssh", "test_charm_upgrade", "test_reactive"]' - # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ - # mapping. See CONTRIBUTING.md for more details. - extra-arguments: | - -m openstack \ - --log-format="%(asctime)s %(levelname)s %(message)s" \ - --path="${{ needs.parse-integration-test-vars.outputs.path }}" \ - --openstack-auth-url="${{ needs.parse-integration-test-vars.outputs.openstack-auth-url }}" \ - --openstack-project-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-domain-name }}" \ - --openstack-project-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-name }}" \ - --openstack-user-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-user-domain-name }}" \ - --openstack-username="${{ needs.parse-integration-test-vars.outputs.openstack-username }}" \ - --openstack-region-name="${{ needs.parse-integration-test-vars.outputs.openstack-region-name }}" \ - --openstack-network-name="${{ needs.parse-integration-test-vars.outputs.openstack-network-name }}" \ - --openstack-flavor-name="${{ needs.parse-integration-test-vars.outputs.openstack-flavor-name }}" \ - --openstack-test-image="${{ needs.parse-integration-test-vars.outputs.openstack-test-image }}" \ - --openstack-test-flavor="${{ needs.parse-integration-test-vars.outputs.openstack-test-flavor }}" \ - --https-proxy "${{ needs.parse-integration-test-vars.outputs.https-proxy }}" \ - --http-proxy "${{ needs.parse-integration-test-vars.outputs.http-proxy }}" \ - --no-proxy "${{ needs.parse-integration-test-vars.outputs.no-proxy }}" \ - --openstack-https-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-https-proxy }}" \ - --openstack-http-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-http-proxy }}" \ - --openstack-no-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-no-proxy }}" - self-hosted-runner: true - self-hosted-runner-label: pfe-ci - openstack-integration-tests-cross-controller-private-endpoint: - name: Cross controller integration test using private-endpoint - needs: parse-integration-test-vars - uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main - secrets: inherit - with: - juju-channel: 3.6/stable - pre-run-script: tests/integration/setup-integration-tests.sh - provider: lxd - test-tox-env: integration-juju3.6 - modules: '["test_prometheus_metrics"]' - # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ - # mapping. See CONTRIBUTING.md for more details. - extra-arguments: | - -m openstack \ - --log-format="%(asctime)s %(levelname)s %(message)s" \ - --path="${{ needs.parse-integration-test-vars.outputs.path }}" \ - --openstack-auth-url="${{ needs.parse-integration-test-vars.outputs.openstack-auth-url }}" \ - --openstack-project-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-domain-name }}" \ - --openstack-project-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-name }}" \ - --openstack-user-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-user-domain-name }}" \ - --openstack-username="${{ needs.parse-integration-test-vars.outputs.openstack-username }}" \ - --openstack-region-name="${{ needs.parse-integration-test-vars.outputs.openstack-region-name }}" \ - --openstack-network-name="${{ needs.parse-integration-test-vars.outputs.openstack-network-name }}" \ - --openstack-flavor-name="${{ needs.parse-integration-test-vars.outputs.openstack-flavor-name }}" \ - --openstack-test-image="${{ needs.parse-integration-test-vars.outputs.openstack-test-image }}" \ - --openstack-test-flavor="${{ needs.parse-integration-test-vars.outputs.openstack-test-flavor }}" \ - --https-proxy "${{ needs.parse-integration-test-vars.outputs.https-proxy }}" \ - --http-proxy "${{ needs.parse-integration-test-vars.outputs.http-proxy }}" \ - --no-proxy "${{ needs.parse-integration-test-vars.outputs.no-proxy }}" \ - --openstack-https-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-https-proxy }}" \ - --openstack-http-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-http-proxy }}" \ - --openstack-no-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-no-proxy }}" - self-hosted-runner: true - self-hosted-runner-label: pfe-ci - allure-report: - if: ${{ (success() || failure()) && github.event_name == 'schedule' }} - needs: - - openstack-integration-tests-private-endpoint - - openstack-integration-tests-cross-controller-private-endpoint - uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main + runs-on: ubuntu-latest + steps: + - name: Display parsed secret outputs + run: | + echo "Path: ${{ needs.parse-integration-test-vars.outputs.path }}" + echo "OpenStack Auth URL: ${{ needs.parse-integration-test-vars.outputs.openstack-auth-url }}" + # openstack-integration-tests-private-endpoint: + # name: Integration test using private-endpoint + # needs: parse-integration-test-vars + # uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main + # secrets: inherit + # with: + # juju-channel: 3.6/stable + # provider: lxd + # test-tox-env: integration-juju3.6 + # modules: '["test_charm_metrics_failure", "test_charm_metrics_success", "test_charm_fork_repo", "test_charm_fork_path_change", "test_charm_no_runner", "test_charm_runner", "test_debug_ssh", "test_charm_upgrade", "test_reactive"]' + # # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ + # # mapping. See CONTRIBUTING.md for more details. + # extra-arguments: | + # -m openstack \ + # --log-format="%(asctime)s %(levelname)s %(message)s" \ + # --path="${{ needs.parse-integration-test-vars.outputs.path }}" \ + # --openstack-auth-url="${{ needs.parse-integration-test-vars.outputs.openstack-auth-url }}" \ + # --openstack-project-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-domain-name }}" \ + # --openstack-project-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-name }}" \ + # --openstack-user-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-user-domain-name }}" \ + # --openstack-username="${{ needs.parse-integration-test-vars.outputs.openstack-username }}" \ + # --openstack-region-name="${{ needs.parse-integration-test-vars.outputs.openstack-region-name }}" \ + # --openstack-network-name="${{ needs.parse-integration-test-vars.outputs.openstack-network-name }}" \ + # --openstack-flavor-name="${{ needs.parse-integration-test-vars.outputs.openstack-flavor-name }}" \ + # --openstack-test-image="${{ needs.parse-integration-test-vars.outputs.openstack-test-image }}" \ + # --openstack-test-flavor="${{ needs.parse-integration-test-vars.outputs.openstack-test-flavor }}" \ + # --https-proxy "${{ needs.parse-integration-test-vars.outputs.https-proxy }}" \ + # --http-proxy "${{ needs.parse-integration-test-vars.outputs.http-proxy }}" \ + # --no-proxy "${{ needs.parse-integration-test-vars.outputs.no-proxy }}" \ + # --openstack-https-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-https-proxy }}" \ + # --openstack-http-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-http-proxy }}" \ + # --openstack-no-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-no-proxy }}" + # self-hosted-runner: true + # self-hosted-runner-label: pfe-ci + # openstack-integration-tests-cross-controller-private-endpoint: + # name: Cross controller integration test using private-endpoint + # needs: parse-integration-test-vars + # uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main + # secrets: inherit + # with: + # juju-channel: 3.6/stable + # pre-run-script: tests/integration/setup-integration-tests.sh + # provider: lxd + # test-tox-env: integration-juju3.6 + # modules: '["test_prometheus_metrics"]' + # # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ + # # mapping. See CONTRIBUTING.md for more details. + # extra-arguments: | + # -m openstack \ + # --log-format="%(asctime)s %(levelname)s %(message)s" \ + # --path="${{ needs.parse-integration-test-vars.outputs.path }}" \ + # --openstack-auth-url="${{ needs.parse-integration-test-vars.outputs.openstack-auth-url }}" \ + # --openstack-project-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-domain-name }}" \ + # --openstack-project-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-name }}" \ + # --openstack-user-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-user-domain-name }}" \ + # --openstack-username="${{ needs.parse-integration-test-vars.outputs.openstack-username }}" \ + # --openstack-region-name="${{ needs.parse-integration-test-vars.outputs.openstack-region-name }}" \ + # --openstack-network-name="${{ needs.parse-integration-test-vars.outputs.openstack-network-name }}" \ + # --openstack-flavor-name="${{ needs.parse-integration-test-vars.outputs.openstack-flavor-name }}" \ + # --openstack-test-image="${{ needs.parse-integration-test-vars.outputs.openstack-test-image }}" \ + # --openstack-test-flavor="${{ needs.parse-integration-test-vars.outputs.openstack-test-flavor }}" \ + # --https-proxy "${{ needs.parse-integration-test-vars.outputs.https-proxy }}" \ + # --http-proxy "${{ needs.parse-integration-test-vars.outputs.http-proxy }}" \ + # --no-proxy "${{ needs.parse-integration-test-vars.outputs.no-proxy }}" \ + # --openstack-https-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-https-proxy }}" \ + # --openstack-http-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-http-proxy }}" \ + # --openstack-no-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-no-proxy }}" + # self-hosted-runner: true + # self-hosted-runner-label: pfe-ci + # allure-report: + # if: ${{ (success() || failure()) && github.event_name == 'schedule' }} + # needs: + # - openstack-integration-tests-private-endpoint + # - openstack-integration-tests-cross-controller-private-endpoint + # uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main From 214dde267b82303a84d467e0e2e551b75035cc9e Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 02:06:10 +0000 Subject: [PATCH 14/53] ci: replace sercrets to vars --- .github/workflows/integration_test.yaml | 179 ++++++++++-------------- 1 file changed, 73 insertions(+), 106 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 48fbb4f1ce..4dfed6d2d7 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -12,109 +12,76 @@ concurrency: cancel-in-progress: true jobs: - parse-integration-test-vars: - runs-on: ubuntu-22.04 - outputs: - path: ${{ secrets.INTEGRATION_TEST_PATH }} - openstack-auth-url: ${{ secrets.INTEGRATION_TEST_OPENSTACK_AUTH_URL }} - openstack-project-domain-name: ${{ secrets.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }} - openstack-project-name: ${{ secrets.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }} - openstack-user-domain-name: ${{ secrets.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }} - openstack-username: ${{ secrets.INTEGRATION_TEST_OPENSTACK_USERNAME }} - openstack-region-name: ${{ secrets.INTEGRATION_TEST_OPENSTACK_REGION_NAME }} - openstack-network-name: ${{ secrets.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }} - openstack-flavor-name: ${{ secrets.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }} - openstack-test-image: ${{ secrets.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }} - openstack-test-flavor: ${{ secrets.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }} - https-proxy: ${{ secrets.INTEGRATION_TEST_HTTPS_PROXY }} - http-proxy: ${{ secrets.INTEGRATION_TEST_HTTP_PROXY }} - no-proxy: ${{ secrets.INTEGRATION_TEST_NO_PROXY }} - openstack-https-proxy: ${{ secrets.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }} - openstack-http-proxy: ${{ secrets.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }} - openstack-no-proxy: ${{ secrets.INTEGRATION_TEST_OPENSTACK_NO_PROXY }} - steps: - - run: echo "Exposing env vars" - test-secret-outputs: - name: Test secret outputs - needs: parse-integration-test-vars - runs-on: ubuntu-latest - steps: - - name: Display parsed secret outputs - run: | - echo "Path: ${{ needs.parse-integration-test-vars.outputs.path }}" - echo "OpenStack Auth URL: ${{ needs.parse-integration-test-vars.outputs.openstack-auth-url }}" - # openstack-integration-tests-private-endpoint: - # name: Integration test using private-endpoint - # needs: parse-integration-test-vars - # uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main - # secrets: inherit - # with: - # juju-channel: 3.6/stable - # provider: lxd - # test-tox-env: integration-juju3.6 - # modules: '["test_charm_metrics_failure", "test_charm_metrics_success", "test_charm_fork_repo", "test_charm_fork_path_change", "test_charm_no_runner", "test_charm_runner", "test_debug_ssh", "test_charm_upgrade", "test_reactive"]' - # # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ - # # mapping. See CONTRIBUTING.md for more details. - # extra-arguments: | - # -m openstack \ - # --log-format="%(asctime)s %(levelname)s %(message)s" \ - # --path="${{ needs.parse-integration-test-vars.outputs.path }}" \ - # --openstack-auth-url="${{ needs.parse-integration-test-vars.outputs.openstack-auth-url }}" \ - # --openstack-project-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-domain-name }}" \ - # --openstack-project-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-name }}" \ - # --openstack-user-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-user-domain-name }}" \ - # --openstack-username="${{ needs.parse-integration-test-vars.outputs.openstack-username }}" \ - # --openstack-region-name="${{ needs.parse-integration-test-vars.outputs.openstack-region-name }}" \ - # --openstack-network-name="${{ needs.parse-integration-test-vars.outputs.openstack-network-name }}" \ - # --openstack-flavor-name="${{ needs.parse-integration-test-vars.outputs.openstack-flavor-name }}" \ - # --openstack-test-image="${{ needs.parse-integration-test-vars.outputs.openstack-test-image }}" \ - # --openstack-test-flavor="${{ needs.parse-integration-test-vars.outputs.openstack-test-flavor }}" \ - # --https-proxy "${{ needs.parse-integration-test-vars.outputs.https-proxy }}" \ - # --http-proxy "${{ needs.parse-integration-test-vars.outputs.http-proxy }}" \ - # --no-proxy "${{ needs.parse-integration-test-vars.outputs.no-proxy }}" \ - # --openstack-https-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-https-proxy }}" \ - # --openstack-http-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-http-proxy }}" \ - # --openstack-no-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-no-proxy }}" - # self-hosted-runner: true - # self-hosted-runner-label: pfe-ci - # openstack-integration-tests-cross-controller-private-endpoint: - # name: Cross controller integration test using private-endpoint - # needs: parse-integration-test-vars - # uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main - # secrets: inherit - # with: - # juju-channel: 3.6/stable - # pre-run-script: tests/integration/setup-integration-tests.sh - # provider: lxd - # test-tox-env: integration-juju3.6 - # modules: '["test_prometheus_metrics"]' - # # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ - # # mapping. See CONTRIBUTING.md for more details. - # extra-arguments: | - # -m openstack \ - # --log-format="%(asctime)s %(levelname)s %(message)s" \ - # --path="${{ needs.parse-integration-test-vars.outputs.path }}" \ - # --openstack-auth-url="${{ needs.parse-integration-test-vars.outputs.openstack-auth-url }}" \ - # --openstack-project-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-domain-name }}" \ - # --openstack-project-name="${{ needs.parse-integration-test-vars.outputs.openstack-project-name }}" \ - # --openstack-user-domain-name="${{ needs.parse-integration-test-vars.outputs.openstack-user-domain-name }}" \ - # --openstack-username="${{ needs.parse-integration-test-vars.outputs.openstack-username }}" \ - # --openstack-region-name="${{ needs.parse-integration-test-vars.outputs.openstack-region-name }}" \ - # --openstack-network-name="${{ needs.parse-integration-test-vars.outputs.openstack-network-name }}" \ - # --openstack-flavor-name="${{ needs.parse-integration-test-vars.outputs.openstack-flavor-name }}" \ - # --openstack-test-image="${{ needs.parse-integration-test-vars.outputs.openstack-test-image }}" \ - # --openstack-test-flavor="${{ needs.parse-integration-test-vars.outputs.openstack-test-flavor }}" \ - # --https-proxy "${{ needs.parse-integration-test-vars.outputs.https-proxy }}" \ - # --http-proxy "${{ needs.parse-integration-test-vars.outputs.http-proxy }}" \ - # --no-proxy "${{ needs.parse-integration-test-vars.outputs.no-proxy }}" \ - # --openstack-https-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-https-proxy }}" \ - # --openstack-http-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-http-proxy }}" \ - # --openstack-no-proxy "${{ needs.parse-integration-test-vars.outputs.openstack-no-proxy }}" - # self-hosted-runner: true - # self-hosted-runner-label: pfe-ci - # allure-report: - # if: ${{ (success() || failure()) && github.event_name == 'schedule' }} - # needs: - # - openstack-integration-tests-private-endpoint - # - openstack-integration-tests-cross-controller-private-endpoint - # uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main + openstack-integration-tests-private-endpoint: + name: Integration test using private-endpoint + uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main + secrets: inherit + with: + juju-channel: 3.6/stable + provider: lxd + test-tox-env: integration-juju3.6 + modules: '["test_charm_metrics_failure", "test_charm_metrics_success", "test_charm_fork_repo", "test_charm_fork_path_change", "test_charm_no_runner", "test_charm_runner", "test_debug_ssh", "test_charm_upgrade", "test_reactive"]' + # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ + # mapping. See CONTRIBUTING.md for more details. + extra-arguments: | + -m openstack \ + --log-format="%(asctime)s %(levelname)s %(message)s" \ + --path="${{ vars.INTEGRATION_TEST_PATH }}" \ + --openstack-auth-url="${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ + --openstack-project-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ + --openstack-project-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ + --openstack-user-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ + --openstack-username="${{ vars.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ + --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ + --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ + --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ + --openstack-test-image="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ + --openstack-test-flavor="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ + --https-proxy "${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ + --http-proxy "${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ + --no-proxy "${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ + --openstack-https-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ + --openstack-http-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ + --openstack-no-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" + self-hosted-runner: true + self-hosted-runner-label: pfe-ci + openstack-integration-tests-cross-controller-private-endpoint: + name: Cross controller integration test using private-endpoint + uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main + secrets: inherit + with: + juju-channel: 3.6/stable + pre-run-script: tests/integration/setup-integration-tests.sh + provider: lxd + test-tox-env: integration-juju3.6 + modules: '["test_prometheus_metrics"]' + # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ + # mapping. See CONTRIBUTING.md for more details. + extra-arguments: | + -m openstack \ + --log-format="%(asctime)s %(levelname)s %(message)s" \ + --path="${{ vars.INTEGRATION_TEST_PATH }}" \ + --openstack-auth-url="${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ + --openstack-project-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ + --openstack-project-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ + --openstack-user-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ + --openstack-username="${{ vars.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ + --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ + --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ + --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ + --openstack-test-image="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ + --openstack-test-flavor="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ + --https-proxy "${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ + --http-proxy "${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ + --no-proxy "${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ + --openstack-https-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ + --openstack-http-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ + --openstack-no-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" + self-hosted-runner: true + self-hosted-runner-label: pfe-ci + allure-report: + if: ${{ (success() || failure()) && github.event_name == 'schedule' }} + needs: + - openstack-integration-tests-private-endpoint + - openstack-integration-tests-cross-controller-private-endpoint + uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main From 04e19f5f168d72a31757be530f78beaaaff358b3 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 02:23:55 +0000 Subject: [PATCH 15/53] ci: test vars settings --- .github/workflows/integration_test.yaml | 155 +++++++++++++----------- 1 file changed, 82 insertions(+), 73 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 4dfed6d2d7..6e52def347 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -12,76 +12,85 @@ concurrency: cancel-in-progress: true jobs: - openstack-integration-tests-private-endpoint: - name: Integration test using private-endpoint - uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main - secrets: inherit - with: - juju-channel: 3.6/stable - provider: lxd - test-tox-env: integration-juju3.6 - modules: '["test_charm_metrics_failure", "test_charm_metrics_success", "test_charm_fork_repo", "test_charm_fork_path_change", "test_charm_no_runner", "test_charm_runner", "test_debug_ssh", "test_charm_upgrade", "test_reactive"]' - # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ - # mapping. See CONTRIBUTING.md for more details. - extra-arguments: | - -m openstack \ - --log-format="%(asctime)s %(levelname)s %(message)s" \ - --path="${{ vars.INTEGRATION_TEST_PATH }}" \ - --openstack-auth-url="${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ - --openstack-project-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ - --openstack-project-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ - --openstack-user-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ - --openstack-username="${{ vars.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ - --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ - --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ - --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ - --openstack-test-image="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ - --openstack-test-flavor="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ - --https-proxy "${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ - --http-proxy "${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ - --no-proxy "${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ - --openstack-https-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ - --openstack-http-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ - --openstack-no-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" - self-hosted-runner: true - self-hosted-runner-label: pfe-ci - openstack-integration-tests-cross-controller-private-endpoint: - name: Cross controller integration test using private-endpoint - uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main - secrets: inherit - with: - juju-channel: 3.6/stable - pre-run-script: tests/integration/setup-integration-tests.sh - provider: lxd - test-tox-env: integration-juju3.6 - modules: '["test_prometheus_metrics"]' - # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ - # mapping. See CONTRIBUTING.md for more details. - extra-arguments: | - -m openstack \ - --log-format="%(asctime)s %(levelname)s %(message)s" \ - --path="${{ vars.INTEGRATION_TEST_PATH }}" \ - --openstack-auth-url="${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ - --openstack-project-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ - --openstack-project-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ - --openstack-user-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ - --openstack-username="${{ vars.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ - --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ - --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ - --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ - --openstack-test-image="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ - --openstack-test-flavor="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ - --https-proxy "${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ - --http-proxy "${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ - --no-proxy "${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ - --openstack-https-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ - --openstack-http-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ - --openstack-no-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" - self-hosted-runner: true - self-hosted-runner-label: pfe-ci - allure-report: - if: ${{ (success() || failure()) && github.event_name == 'schedule' }} - needs: - - openstack-integration-tests-private-endpoint - - openstack-integration-tests-cross-controller-private-endpoint - uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main + print-repo-vars: + name: Print repository variables + runs-on: ubuntu-latest + steps: + - name: Print all repository variables + run: | + echo "Repository Variables:" + echo "INTEGRATION_TEST_PATH: ${{ vars.INTEGRATION_TEST_PATH }}" + echo "INTEGRATION_TEST_OPENSTACK_AUTH_URL: ${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" + # openstack-integration-tests-private-endpoint: + # name: Integration test using private-endpoint + # uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main + # secrets: inherit + # with: + # juju-channel: 3.6/stable + # provider: lxd + # test-tox-env: integration-juju3.6 + # modules: '["test_charm_metrics_failure", "test_charm_metrics_success", "test_charm_fork_repo", "test_charm_fork_path_change", "test_charm_no_runner", "test_charm_runner", "test_debug_ssh", "test_charm_upgrade", "test_reactive"]' + # # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ + # # mapping. See CONTRIBUTING.md for more details. + # extra-arguments: | + # -m openstack \ + # --log-format="%(asctime)s %(levelname)s %(message)s" \ + # --path="${{ vars.INTEGRATION_TEST_PATH }}" \ + # --openstack-auth-url="${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ + # --openstack-project-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ + # --openstack-project-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ + # --openstack-user-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ + # --openstack-username="${{ vars.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ + # --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ + # --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ + # --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ + # --openstack-test-image="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ + # --openstack-test-flavor="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ + # --https-proxy "${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ + # --http-proxy "${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ + # --no-proxy "${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ + # --openstack-https-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ + # --openstack-http-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ + # --openstack-no-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" + # self-hosted-runner: true + # self-hosted-runner-label: pfe-ci + # openstack-integration-tests-cross-controller-private-endpoint: + # name: Cross controller integration test using private-endpoint + # uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main + # secrets: inherit + # with: + # juju-channel: 3.6/stable + # pre-run-script: tests/integration/setup-integration-tests.sh + # provider: lxd + # test-tox-env: integration-juju3.6 + # modules: '["test_prometheus_metrics"]' + # # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ + # # mapping. See CONTRIBUTING.md for more details. + # extra-arguments: | + # -m openstack \ + # --log-format="%(asctime)s %(levelname)s %(message)s" \ + # --path="${{ vars.INTEGRATION_TEST_PATH }}" \ + # --openstack-auth-url="${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ + # --openstack-project-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ + # --openstack-project-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ + # --openstack-user-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ + # --openstack-username="${{ vars.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ + # --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ + # --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ + # --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ + # --openstack-test-image="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ + # --openstack-test-flavor="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ + # --https-proxy "${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ + # --http-proxy "${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ + # --no-proxy "${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ + # --openstack-https-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ + # --openstack-http-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ + # --openstack-no-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" + # self-hosted-runner: true + # self-hosted-runner-label: pfe-ci + # allure-report: + # if: ${{ (success() || failure()) && github.event_name == 'schedule' }} + # needs: + # - openstack-integration-tests-private-endpoint + # - openstack-integration-tests-cross-controller-private-endpoint + # uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main From a820122400a36609ac0cb4515c2a09fe3fe988f0 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 02:26:25 +0000 Subject: [PATCH 16/53] ci: revert test vars settings --- .github/workflows/integration_test.yaml | 155 +++++++++++------------- 1 file changed, 73 insertions(+), 82 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 6e52def347..77630c9656 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -12,85 +12,76 @@ concurrency: cancel-in-progress: true jobs: - print-repo-vars: - name: Print repository variables - runs-on: ubuntu-latest - steps: - - name: Print all repository variables - run: | - echo "Repository Variables:" - echo "INTEGRATION_TEST_PATH: ${{ vars.INTEGRATION_TEST_PATH }}" - echo "INTEGRATION_TEST_OPENSTACK_AUTH_URL: ${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" - # openstack-integration-tests-private-endpoint: - # name: Integration test using private-endpoint - # uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main - # secrets: inherit - # with: - # juju-channel: 3.6/stable - # provider: lxd - # test-tox-env: integration-juju3.6 - # modules: '["test_charm_metrics_failure", "test_charm_metrics_success", "test_charm_fork_repo", "test_charm_fork_path_change", "test_charm_no_runner", "test_charm_runner", "test_debug_ssh", "test_charm_upgrade", "test_reactive"]' - # # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ - # # mapping. See CONTRIBUTING.md for more details. - # extra-arguments: | - # -m openstack \ - # --log-format="%(asctime)s %(levelname)s %(message)s" \ - # --path="${{ vars.INTEGRATION_TEST_PATH }}" \ - # --openstack-auth-url="${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ - # --openstack-project-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ - # --openstack-project-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ - # --openstack-user-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ - # --openstack-username="${{ vars.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ - # --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ - # --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ - # --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ - # --openstack-test-image="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ - # --openstack-test-flavor="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ - # --https-proxy "${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ - # --http-proxy "${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ - # --no-proxy "${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ - # --openstack-https-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ - # --openstack-http-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ - # --openstack-no-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" - # self-hosted-runner: true - # self-hosted-runner-label: pfe-ci - # openstack-integration-tests-cross-controller-private-endpoint: - # name: Cross controller integration test using private-endpoint - # uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main - # secrets: inherit - # with: - # juju-channel: 3.6/stable - # pre-run-script: tests/integration/setup-integration-tests.sh - # provider: lxd - # test-tox-env: integration-juju3.6 - # modules: '["test_prometheus_metrics"]' - # # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ - # # mapping. See CONTRIBUTING.md for more details. - # extra-arguments: | - # -m openstack \ - # --log-format="%(asctime)s %(levelname)s %(message)s" \ - # --path="${{ vars.INTEGRATION_TEST_PATH }}" \ - # --openstack-auth-url="${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ - # --openstack-project-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ - # --openstack-project-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ - # --openstack-user-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ - # --openstack-username="${{ vars.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ - # --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ - # --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ - # --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ - # --openstack-test-image="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ - # --openstack-test-flavor="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ - # --https-proxy "${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ - # --http-proxy "${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ - # --no-proxy "${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ - # --openstack-https-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ - # --openstack-http-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ - # --openstack-no-proxy "${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" - # self-hosted-runner: true - # self-hosted-runner-label: pfe-ci - # allure-report: - # if: ${{ (success() || failure()) && github.event_name == 'schedule' }} - # needs: - # - openstack-integration-tests-private-endpoint - # - openstack-integration-tests-cross-controller-private-endpoint - # uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main + openstack-integration-tests-private-endpoint: + name: Integration test using private-endpoint + uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main + secrets: inherit + with: + juju-channel: 3.6/stable + provider: lxd + test-tox-env: integration-juju3.6 + modules: '["test_charm_metrics_failure", "test_charm_metrics_success", "test_charm_fork_repo", "test_charm_fork_path_change", "test_charm_no_runner", "test_charm_runner", "test_debug_ssh", "test_charm_upgrade", "test_reactive"]' + # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ + # mapping. See CONTRIBUTING.md for more details. + extra-arguments: | + -m=openstack \ + --log-format="%(asctime)s %(levelname)s %(message)s" \ + --path="${{ vars.INTEGRATION_TEST_PATH }}" \ + --openstack-auth-url="${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ + --openstack-project-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ + --openstack-project-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ + --openstack-user-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ + --openstack-username="${{ vars.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ + --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ + --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ + --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ + --openstack-test-image="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ + --openstack-test-flavor="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ + --https-proxy="${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ + --http-proxy="${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ + --no-proxy="${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ + --openstack-https-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ + --openstack-http-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ + --openstack-no-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" + self-hosted-runner: true + self-hosted-runner-label: pfe-ci + openstack-integration-tests-cross-controller-private-endpoint: + name: Cross controller integration test using private-endpoint + uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main + secrets: inherit + with: + juju-channel: 3.6/stable + pre-run-script: tests/integration/setup-integration-tests.sh + provider: lxd + test-tox-env: integration-juju3.6 + modules: '["test_prometheus_metrics"]' + # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ + # mapping. See CONTRIBUTING.md for more details. + extra-arguments: | + -m=openstack \ + --log-format="%(asctime)s %(levelname)s %(message)s" \ + --path="${{ vars.INTEGRATION_TEST_PATH }}" \ + --openstack-auth-url="${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ + --openstack-project-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ + --openstack-project-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ + --openstack-user-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ + --openstack-username="${{ vars.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ + --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ + --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ + --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ + --openstack-test-image="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ + --openstack-test-flavor="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ + --https-proxy="${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ + --http-proxy="${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ + --no-proxy="${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ + --openstack-https-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ + --openstack-http-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ + --openstack-no-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" + self-hosted-runner: true + self-hosted-runner-label: pfe-ci + allure-report: + if: ${{ (success() || failure()) && github.event_name == 'schedule' }} + needs: + - openstack-integration-tests-private-endpoint + - openstack-integration-tests-cross-controller-private-endpoint + uses: canonical/operator-workflows/.github/workflows/allure_report.yaml@main From bef1db1bb3ddb1b7efc5a9f97c34597f52e77113 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 02:53:12 +0000 Subject: [PATCH 17/53] ci: don't use test image/flavor --- .github/workflows/integration_test.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 77630c9656..27fecd67c7 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -35,8 +35,6 @@ jobs: --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ - --openstack-test-image="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ - --openstack-test-flavor="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ --https-proxy="${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ --http-proxy="${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ --no-proxy="${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ @@ -69,8 +67,6 @@ jobs: --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ - --openstack-test-image="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_IMAGE }}" \ - --openstack-test-flavor="${{ vars.INTEGRATION_TEST_OPENSTACK_TEST_FLAVOR }}" \ --https-proxy="${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ --http-proxy="${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ --no-proxy="${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ From d5829eded08c35c49305f898fe063de218171aa0 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 03:09:17 +0000 Subject: [PATCH 18/53] ci: trigger w/ new secrets From aa477b2fe9a3815ef3f1c80827215e9e85613a7e Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 03:31:13 +0000 Subject: [PATCH 19/53] ci: trigger w/ new secrets From d99f30b1bf2ab9f86b82630773d99746ec6a93ab Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 03:39:43 +0000 Subject: [PATCH 20/53] docs: clarify sensitive values mapping in CONTRIBUTING.md --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 51a4f6b7d7..eef030a238 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,8 @@ that can be used for linting and formatting code when you're preparing contribut To prefer explicit setting arguments passing, use `extra-arguments` of the [following reusable workflow](https://github.com/canonical/operator-workflows/blob/main/.github/workflows/integration_test_run.yaml) to pass in non-sensitive values. -For sensitive values (`--token`, `--openstack-password`), map them via `INTEGRATION_TEST_SECRET_ENV_NAME_` +For sensitive values (`INTEGRATION_TOKEN`: `--token`, `OS_PASSWORD`: `--openstack-password`), map +them via `INTEGRATION_TEST_SECRET_ENV_NAME_` environment variable settings under [repository](https://github.com/canonical/github-runner-operator) > settings > variables > actions. This is to prevent GitHub from leaking secrets when passing them over the CLI calls (unresolved). From 5d1f6ff56fb3f8bd6739ff91b22b82fadf22c493 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 04:35:56 +0000 Subject: [PATCH 21/53] chore: update to newer platforms syntax for charmcraft yaml --- charmcraft.yaml | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/charmcraft.yaml b/charmcraft.yaml index 72266be193..06b419a01a 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -2,28 +2,22 @@ # See LICENSE file for licensing details. type: charm + +platforms: + ubuntu@22.04:amd64: + ubuntu@24.04:amd64: + parts: charm: charm-python-packages: - - setuptools>=75.8.0 # for jinja2 + - setuptools>=75.8.0 # for jinja2 build-packages: - - git # for installing git source of pylxd - - libffi-dev # for cffi - - libssl-dev # for cryptography + - git # for installing git source of pylxd + - libffi-dev # for cffi + - libssl-dev # for cryptography - pkg-config # for cryptography build-snaps: - rustup # for cryptography override-build: | rustup default stable craftctl default -bases: - - build-on: - - name: "ubuntu" - channel: "22.04" - architectures: - - amd64 - run-on: - - name: "ubuntu" - channel: "22.04" - architectures: - - amd64 From e94667f3c08ef69d9fe7b15ec858c76951cbe589 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 04:37:05 +0000 Subject: [PATCH 22/53] chore: use single OS_ prefixed params for tests --- tests/conftest.py | 6 ----- tests/integration/conftest.py | 47 +++++++++++++---------------------- 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d42cbf290d..bb80263a12 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,12 +46,6 @@ def pytest_addoption(parser: Parser): action="store", help="No proxy configuration value for juju model proxy configuration.", ) - parser.addoption( - "--openstack-clouds-yaml", - action="store", - help="The OpenStack clouds yaml file for the charm to use.", - default="", - ) parser.addoption( "--use-existing-app-suffix", action="store", diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 9523d38455..2e99498e12 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -6,6 +6,7 @@ import random import secrets import string +import subprocess from pathlib import Path from time import sleep from typing import Any, AsyncGenerator, AsyncIterator, Generator, Iterator, Optional, cast @@ -94,10 +95,16 @@ def image_builder_app_name(random_app_name_suffix: str) -> str: return f"github-runner-image-builder-{random_app_name_suffix}" -@pytest.fixture(scope="module", name="openstack_clouds_yaml") -def openstack_clouds_yaml_fixture(pytestconfig: pytest.Config) -> str | None: - """The openstack clouds yaml config.""" - return pytestconfig.getoption("--openstack-clouds-yaml") +@pytest.fixture(name="codename", scope="module") +def codename_fixture(): + """Series codename for deploying any-charm.""" + return subprocess.check_output(["lsb_release", "-cs"]).strip().decode("utf-8") + + +@pytest.fixture(name="series", scope="module") +def series_fixture(): + """Series version for deploying any-charm.""" + return subprocess.check_output(["lsb_release", "-rs"]).strip().decode("utf-8") @pytest.fixture(scope="module") @@ -174,7 +181,7 @@ def openstack_no_proxy_fixture(pytestconfig: pytest.Config) -> str: @pytest.fixture(scope="module", name="private_endpoint_config") -def private_endpoint_config_fixture(pytestconfig: pytest.Config) -> PrivateEndpointConfigs | None: +def private_endpoint_config_fixture(pytestconfig: pytest.Config) -> PrivateEndpointConfigs: """The private endpoint configuration values.""" auth_url = pytestconfig.getoption("--openstack-auth-url") password = pytestconfig.getoption("--openstack-password") @@ -186,8 +193,8 @@ def private_endpoint_config_fixture(pytestconfig: pytest.Config) -> PrivateEndpo user_domain_name = pytestconfig.getoption("--openstack-user-domain-name") user_name = pytestconfig.getoption("--openstack-username") region_name = pytestconfig.getoption("--openstack-region-name") - if any( - not val + assert all( + val for val in ( auth_url, password, @@ -197,8 +204,7 @@ def private_endpoint_config_fixture(pytestconfig: pytest.Config) -> PrivateEndpo user_name, region_name, ) - ): - return None + ), "Specify all OpenStack private endpoint options." return { "auth_url": auth_url, "password": str(password), @@ -210,13 +216,9 @@ def private_endpoint_config_fixture(pytestconfig: pytest.Config) -> PrivateEndpo } -@pytest.fixture(scope="module", name="private_endpoint_clouds_yaml") -def private_endpoint_clouds_yaml_fixture( - private_endpoint_config: PrivateEndpointConfigs | None, -) -> Optional[str]: +@pytest.fixture(scope="module", name="clouds_yaml_contents") +def clouds_yaml_contents_fixture(private_endpoint_config: PrivateEndpointConfigs) -> str: """The openstack private endpoint clouds yaml.""" - if not private_endpoint_config: - return None return string.Template( Path("tests/integration/data/clouds.yaml.tmpl").read_text(encoding="utf-8") ).substitute( @@ -232,21 +234,6 @@ def private_endpoint_clouds_yaml_fixture( ) -@pytest.fixture(scope="module", name="clouds_yaml_contents") -def clouds_yaml_contents_fixture( - openstack_clouds_yaml: Optional[str], private_endpoint_clouds_yaml: Optional[str] -): - """The Openstack clouds yaml or private endpoint cloud yaml contents.""" - clouds_yaml_contents = openstack_clouds_yaml or private_endpoint_clouds_yaml - assert clouds_yaml_contents, ( - "Please specify --openstack-clouds-yaml or all of private endpoint arguments " - "(--openstack-auth-url, --openstack-password, --openstack-project-domain-name, " - "--openstack-project-name, --openstack-user-domain-name, --openstack-user-name, " - "--openstack-region-name)" - ) - return clouds_yaml_contents - - @pytest.fixture(scope="module", name="network_name") def network_name_fixture(pytestconfig: pytest.Config) -> str: """Network to use to spawn test instances under.""" From dea5e7149f4dc5cd6d195d6ff86404580cf32153 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 04:39:18 +0000 Subject: [PATCH 23/53] chore: remove unused vars --- tests/integration/conftest.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 2e99498e12..27b68365b0 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -381,9 +381,6 @@ async def image_builder_fixture( existing_app_suffix: Optional[str], image_builder_app_name: str, image_builder_config: dict, - flavor_name: str, - network_name: str, - openstack_model_proxy: None, openstack_connection, ): """The image builder application for OpenStack runners.""" From 668d30bb96b6f64aa299401dafa9ede979a7052b Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 04:45:49 +0000 Subject: [PATCH 24/53] chore: revert series deploy fixtures --- tests/integration/conftest.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 27b68365b0..08ee019fd3 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -95,18 +95,6 @@ def image_builder_app_name(random_app_name_suffix: str) -> str: return f"github-runner-image-builder-{random_app_name_suffix}" -@pytest.fixture(name="codename", scope="module") -def codename_fixture(): - """Series codename for deploying any-charm.""" - return subprocess.check_output(["lsb_release", "-cs"]).strip().decode("utf-8") - - -@pytest.fixture(name="series", scope="module") -def series_fixture(): - """Series version for deploying any-charm.""" - return subprocess.check_output(["lsb_release", "-rs"]).strip().decode("utf-8") - - @pytest.fixture(scope="module") def charm_file(pytestconfig: pytest.Config, openstack_clouds_yaml: Optional[str]) -> str: """Path to the built charm.""" From d09fc04feef45a8d367dc774d8efee9bd0fc09a8 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 04:46:22 +0000 Subject: [PATCH 25/53] chore: revert series deploy fixtures --- tests/integration/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 08ee019fd3..d064cf851c 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -6,7 +6,6 @@ import random import secrets import string -import subprocess from pathlib import Path from time import sleep from typing import Any, AsyncGenerator, AsyncIterator, Generator, Iterator, Optional, cast From dbfcecfdd5db6ac15d35d7b205168545d49e50dd Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 05:39:32 +0000 Subject: [PATCH 26/53] chore: remove ref to openstack clouds yaml fixture --- 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 d064cf851c..2c96a2f5b9 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -95,7 +95,7 @@ def image_builder_app_name(random_app_name_suffix: str) -> str: @pytest.fixture(scope="module") -def charm_file(pytestconfig: pytest.Config, openstack_clouds_yaml: Optional[str]) -> str: +def charm_file(pytestconfig: pytest.Config) -> str: """Path to the built charm.""" charm = pytestconfig.getoption("--charm-file") assert charm, "Please specify the --charm-file command line option" From e3509b2235da8097a652faea4ec3abd2d9194995 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 06:49:48 +0000 Subject: [PATCH 27/53] chore: use VM virt type to mitigate snap bug --- tests/integration/conftest.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 2c96a2f5b9..6c9661932a 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -378,6 +378,7 @@ async def image_builder_fixture( application_name=application_name, channel="latest/edge", config=image_builder_config, + constraints={"root-disk": 20 * 1024, "mem": 2 * 1024, "virt-type": "virtual-machine"}, ) else: app = model.applications[image_builder_app_name] @@ -423,10 +424,7 @@ async def app_openstack_runner_fixture( https_proxy=openstack_https_proxy, no_proxy=openstack_no_proxy, reconcile_interval=DEFAULT_RECONCILE_INTERVAL, - constraints={ - "root-disk": 50 * 1024, - "mem": 2 * 1024, - }, + constraints={"root-disk": 50 * 1024, "mem": 2 * 1024, "virt-type": "virtual-machine"}, config={ OPENSTACK_CLOUDS_YAML_CONFIG_NAME: clouds_yaml_contents, OPENSTACK_NETWORK_CONFIG_NAME: network_name, From edc5bd135b67225c0f2d961029f182ba8c351043 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 07:22:13 +0000 Subject: [PATCH 28/53] debug --- .github/workflows/integration_test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 27fecd67c7..c39d07d9f4 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -43,6 +43,8 @@ jobs: --openstack-no-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" self-hosted-runner: true self-hosted-runner-label: pfe-ci + tmate-debug: true + tmate-timeout: 90 openstack-integration-tests-cross-controller-private-endpoint: name: Cross controller integration test using private-endpoint uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main From 784277560c28b13ed5a82bbc7f588c8206ef5ae9 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 08:26:04 +0000 Subject: [PATCH 29/53] test: use matching series --- tests/integration/conftest.py | 26 +++++++++++++++++++++++--- tests/integration/helpers/common.py | 3 ++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 6c9661932a..5ef20a0e56 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -6,6 +6,7 @@ import random import secrets import string +import subprocess from pathlib import Path from time import sleep from typing import Any, AsyncGenerator, AsyncIterator, Generator, Iterator, Optional, cast @@ -94,14 +95,27 @@ def image_builder_app_name(random_app_name_suffix: str) -> str: return f"github-runner-image-builder-{random_app_name_suffix}" +@pytest.fixture(name="series", scope="module") +def series_fixture(): + """Series version for deploying any-charm.""" + return ( + # Ignore B603 since this is a trusted subprocess call + subprocess.check_output(["/usr/bin/lsb_release", "-rs"]) # nosec: B603 + .strip() + .decode("utf-8") + ) + + @pytest.fixture(scope="module") -def charm_file(pytestconfig: pytest.Config) -> str: +def charm_file(pytestconfig: pytest.Config, series: str) -> Path: """Path to the built charm.""" charm = pytestconfig.getoption("--charm-file") assert charm, "Please specify the --charm-file command line option" - charm_path_str = f"./{charm}" - return charm_path_str + charm_dir = Path(f"./{charm}").parent + charm_matching_series = list(charm_dir.rglob(f"*{series}*.charm")) + assert charm_matching_series, f"No build found for series {series}" + return charm_matching_series[0] @pytest.fixture(scope="module") @@ -397,6 +411,7 @@ async def image_builder_fixture( async def app_openstack_runner_fixture( model: Model, charm_file: str, + series: str, app_name: str, path: str, token: str, @@ -417,6 +432,7 @@ async def app_openstack_runner_fixture( application = await deploy_github_runner_charm( model=model, charm_file=charm_file, + series=series, app_name=app_name, path=path, token=token, @@ -488,6 +504,7 @@ async def app_no_wait_tmate_fixture( async def app_runner( model: Model, charm_file: str, + series: str, app_name: str, path: str, token: str, @@ -500,6 +517,7 @@ async def app_runner( application = await deploy_github_runner_charm( model=model, charm_file=charm_file, + series=series, app_name=f"{app_name}-test", path=path, token=token, @@ -515,6 +533,7 @@ async def app_runner( async def app_no_wait_fixture( model: Model, charm_file: str, + series: str, app_name: str, path: str, token: str, @@ -526,6 +545,7 @@ async def app_no_wait_fixture( app: Application = await deploy_github_runner_charm( model=model, charm_file=charm_file, + series=series, app_name=app_name, path=path, token=token, diff --git a/tests/integration/helpers/common.py b/tests/integration/helpers/common.py index 0146cbb015..5f118c561d 100644 --- a/tests/integration/helpers/common.py +++ b/tests/integration/helpers/common.py @@ -158,6 +158,7 @@ async def deploy_github_runner_charm( https_proxy: str, no_proxy: str, reconcile_interval: int, + series: str = "22.04", constraints: dict | None = None, config: dict | None = None, deploy_kwargs: dict | None = None, @@ -207,7 +208,7 @@ async def deploy_github_runner_charm( application = await model.deploy( charm_file, application_name=app_name, - base="ubuntu@22.04", + base=f"ubuntu@{series}", config=default_config, constraints=constraints or DEFAULT_RUNNER_CONSTRAINTS, **(deploy_kwargs or {}), From d930d7719382e1d718d6158847f9d9ae044e6f45 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 08:47:10 +0000 Subject: [PATCH 30/53] test: update path to str --- tests/integration/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 5ef20a0e56..888339fbd4 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -107,7 +107,7 @@ def series_fixture(): @pytest.fixture(scope="module") -def charm_file(pytestconfig: pytest.Config, series: str) -> Path: +def charm_file(pytestconfig: pytest.Config, series: str) -> str: """Path to the built charm.""" charm = pytestconfig.getoption("--charm-file") assert charm, "Please specify the --charm-file command line option" @@ -115,7 +115,7 @@ def charm_file(pytestconfig: pytest.Config, series: str) -> Path: charm_dir = Path(f"./{charm}").parent charm_matching_series = list(charm_dir.rglob(f"*{series}*.charm")) assert charm_matching_series, f"No build found for series {series}" - return charm_matching_series[0] + return f"./{charm_matching_series[0]}" @pytest.fixture(scope="module") From eb538b1a99220ca9c0901d22775ad61e30d56efe Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 09:48:05 +0000 Subject: [PATCH 31/53] test: debug series --- tests/integration/conftest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 888339fbd4..92e6d8118f 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -98,12 +98,14 @@ def image_builder_app_name(random_app_name_suffix: str) -> str: @pytest.fixture(name="series", scope="module") def series_fixture(): """Series version for deploying any-charm.""" - return ( + series = ( # Ignore B603 since this is a trusted subprocess call subprocess.check_output(["/usr/bin/lsb_release", "-rs"]) # nosec: B603 .strip() .decode("utf-8") ) + logging.info("Series: %s", series) + return series @pytest.fixture(scope="module") From bd0dbe952766243885142212c317c7aeecaf2099 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 11:01:30 +0000 Subject: [PATCH 32/53] chore: latest charmcraft syntax --- charmcraft.yaml | 41 +++++++++++++++++++++++++++++++++++++++++ metadata.yaml | 41 ----------------------------------------- 2 files changed, 41 insertions(+), 41 deletions(-) delete mode 100644 metadata.yaml diff --git a/charmcraft.yaml b/charmcraft.yaml index 06b419a01a..b397d66afe 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -1,5 +1,27 @@ # Copyright 2025 Canonical Ltd. # See LICENSE file for licensing details. +name: github-runner + +links: + contact: https://launchpad.net/~canonical-is-devops + documentation: https://discourse.charmhub.io/t/github-runner-documentation-overview/13347 + issues: + - https://github.com/canonical/github-runner-operator/issues + source: + - https://github.com/canonical/github-runner-operator + website: + - https://charmhub.io/github-runner + +summary: Creates a group of self-hosted github runners. +description: | + A [Juju](https://juju.is/) [charm](https://juju.is/docs/olm/charmed-operators) managing + [self-hosted runners for GitHub Actions](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners). + + Each unit of this charm will start a configurable number of virtual machines to host + self-hosted runners. Each runner performs only one job, after which it unregisters from GitHub to + ensure that each job runs in a clean environment. The charm will periodically check the number of + runners and spawn or destroy them as necessary to maintain the configured number of runners. Both + the reconciliation interval and the number of runners to maintain are configurable. type: charm @@ -21,3 +43,22 @@ parts: override-build: | rustup default stable craftctl default + +provides: + cos-agent: + interface: cos_agent + +requires: + debug-ssh: + interface: debug-ssh + image: + interface: github_runner_image_v0 + mongodb: + interface: mongodb_client + limit: 1 + +storage: + runner: + description: Storage for the root disk of LXD instances hosting the runner application. + type: filesystem + location: /storage/juju diff --git a/metadata.yaml b/metadata.yaml deleted file mode 100644 index c16c8ca5a2..0000000000 --- a/metadata.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2025 Canonical Ltd. -# See LICENSE file for licensing details. - -name: github-runner -display-name: GitHub runner -maintainers: - - https://launchpad.net/~canonical-is-devops -docs: https://discourse.charmhub.io/t/github-runner-documentation-overview/13347 -issues: https://github.com/canonical/github-runner-operator/issues -source: https://github.com/canonical/github-runner-operator -summary: Creates a group of self-hosted github runners. -description: | - A [Juju](https://juju.is/) [charm](https://juju.is/docs/olm/charmed-operators) managing - [self-hosted runners for GitHub Actions](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners). - - Each unit of this charm will start a configurable number of virtual machines to host - self-hosted runners. Each runner performs only one job, after which it unregisters from GitHub to - ensure that each job runs in a clean environment. The charm will periodically check the number of - runners and spawn or destroy them as necessary to maintain the configured number of runners. Both - the reconciliation interval and the number of runners to maintain are configurable. -series: - - jammy - -provides: - cos-agent: - interface: cos_agent - -requires: - debug-ssh: - interface: debug-ssh - image: - interface: github_runner_image_v0 - mongodb: - interface: mongodb_client - limit: 1 - -storage: - runner: - description: Storage for the root disk of LXD instances hosting the runner application. - type: filesystem - location: /storage/juju From c38c9834ccdcb8e77c95c7ff312c57fc3ac0bfe3 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 11:02:25 +0000 Subject: [PATCH 33/53] chore: latest charmcraft syntax --- actions.yaml | 7 -- charmcraft.yaml | 187 ++++++++++++++++++++++++++++++++++++++++++++++++ config.yaml | 180 ---------------------------------------------- 3 files changed, 187 insertions(+), 187 deletions(-) delete mode 100644 actions.yaml diff --git a/actions.yaml b/actions.yaml deleted file mode 100644 index a8b1be89e7..0000000000 --- a/actions.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright 2025 Canonical Ltd. -# See LICENSE file for licensing details. - -check-runners: - description: Get info on active and registered runners -flush-runners: - description: Clear out all runners and start a new set diff --git a/charmcraft.yaml b/charmcraft.yaml index b397d66afe..2d3825c5f2 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -62,3 +62,190 @@ storage: description: Storage for the root disk of LXD instances hosting the runner application. type: filesystem location: /storage/juju + +actions: + check-runners: + description: Get info on active and registered runners + flush-runners: + description: Clear out all runners and start a new set + +config: + dockerhub-mirror: + type: string + default: "" + description: >- + The URL to the private docker registry configured as the dockerhub mirror to be used by the + runners. If set a message will be displayed prior to job execution on self-hosted runner + informing users to use the provided registry. You must use https:// as the protocol. + openstack-clouds-yaml: + type: string + default: "" + description: >- + The clouds.yaml yaml necessary for OpenStack integration. + The format for the clouds.yaml is described in the docs: + (https://docs.openstack.org/python-openstackclient/pike/configuration/index.html#clouds-yaml). + openstack-flavor: + type: string + default: "" + description: | + (Deprecated, use flavor-label-list instead) + The openstack flavor to use to spawn virtual machines. This controls the hardware resource + of the virtual machines. See docs for details: + (https://docs.openstack.org/nova/rocky/user/flavors.html). + openstack-network: + type: string + default: "" + description: | + The openstack network for virtual machines hosting the runners. The network must allow this + application to be able to access the virtual machine though the IP assigned by the openstack + network. See docs for details: + (https://docs.openstack.org/neutron/latest/admin/intro-os-networking.html). + experimental-use-aproxy: + type: boolean + default: false + description: >- + (Experimental, may be removed) When set to true, aproxy (https://github.com/canonical/aproxy) + will be installed within the runners. It will forward TCP traffic matching the 'aproxy-exclude-addresses' + and 'aproxy-redirect-ports' settings to a proxy server configured by the Juju model config 'juju-http-proxy' + (or, if this is not set, 'juju-https-proxy' will be used). + This is useful when the charm is deployed in a network that requires a proxy to access the + internet. + Note that you should carefully choose values for the 'aproxy-exclude-addresses' and + 'aproxy-redirect-ports' so that the network traffic from the runner to the HTTP proxy is not + captured by aproxy. The simplest way to achieve this is to add the IP address of the HTTP proxy + to 'aproxy-exclude-addresses' or exclude the HTTP proxy port from 'aproxy-redirect-ports'. + aproxy-exclude-addresses: + type: string + default: "10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16" + description: >- + A comma-separated list of IP addresses that should be excluded from redirection to aproxy. + 127.0.0.0/8 are always excluded so you can omit if from the configuration. + aproxy-redirect-ports: + type: string + default: "80, 443" + description: >- + A comma-separated list of ports or port ranges that should be redirected to aproxy. + group: + type: string + default: "default" + description: >- + The organization runner group to register the self-hosted runner under. This has no effect on + runners under a repository. + labels: + type: string + default: "" + description: >- + Additional comma separated labels to attach to self-hosted runners. By default, the labels + "self-hosted", architecture (i.e. "x64", "arm64"), os (i.e. "linux"), os-flavor (i.e. + "jammy") are set. Any labels provided via this configuration will be appended to the default + values. + path: + type: string + default: "" + description: >- + The organization or the repository to register the self-hosted runners. For organization the + path would be the name of the organization. For repository the path should be in the + "{owner}/{repo}" format. + reconcile-interval: + type: int + default: 5 + description: >- + Minutes between each reconciliation of the current runners state and their targeted state. + On reconciliation, the charm polls the state of runners and see if actions are needed. The + value should be kept low, unless Github API rate limiting is encountered. + repo-policy-compliance-token: + type: string + description: >- + The token to authenticate with the repository-policy-compliance service in order to + generate one-time-tokens. This option requires the repo-policy-compliance-url to be set. + If not set, the repository-policy-compliance service will not be used. + repo-policy-compliance-url: + type: string + description: >- + The URL to the repository-policy-compliance service. This option requires the + repo-policy-compliance-token to be set. If not set, the repository-policy-compliance service + will not be used. This option is only supported when using OpenStack Cloud. + test-mode: + type: string + description: >- + When set to 'insecure', the charm test mode is activated, which may deactivate some security + hardening measures. + token: + type: string + default: "" + description: >- + The GitHub Personal Access Token for registering the self-hosted runners. The token requires + 'repo' scope for repository runners and 'repo' + 'admin:org' scope for organization runners. + For fine grained token scopes, see + https://charmhub.io/github-runner/docs/how-to-change-token. + virtual-machines: + type: int + default: 0 + description: >- + (Deprecated, use base-virtual-machines or max-total-virtual-machines instead) + The number of virtual machine runners. This charm will spawn or destroy virtual machines + runners to match this setting. Setting virtual-machines to non-zero and any of + base-virtual-machines or max-total-virtual-machines will block the charm. + base-virtual-machines: + type: int + default: 0 + description: >- + The number of non-reactive virtual machine runners spawned by the charm per each + combination of flavor and base. Currently, if the charm is integrated with MongoDB + this config option will be ignored. + max-total-virtual-machines: + type: int + default: 0 + description: >- + If the github-runner charm is integrated with MongoDB, the reactive mode will not spawn + new virtual machines if there are max-total-virtual-machines of more virtual machines + managed by the charm. + flavor-label-combinations: + type: string + default: "" + description: >- + List of "flavor:label" entries separated by ",". The flavor will correspond to a OpenStack + flavor and the label will be a label for the GitHub runner. Only one flavor-label combination + is supported at the moment. A valid configuration looks like "m1.small:small". + manager-ssh-proxy-command: + type: string + default: "" + description: >- + Optional ProxyCommand used by the ssh connection which is used to access the virtual + machine runners from the runner manager. It is equivalent to ProxyCommand in ssh-config, + but without a shell. For example, a value could be something like: + `bash -c "openssl s_client -quiet -connect 192.168.20.2:2222 -servername %h 2> /dev/null"` + pre-job-script: + type: string + description: >- + Optional script (needs shebang) to execute in the pre-job phase of a spawned runner VM. + This can e.g. be useful for specific infrastructure related configurations + (e.g. usage of certain proxies or custom routes). + Note that the user executing the script is the ubuntu user (which has sudo rights). + Example script: + #!/usr/bin/env bash + cat > ~/.ssh/config <- + The HTTP proxy value to use for the runners. If not specified, the model configuration option + juju-http-proxy will be used instead. + use-runner-proxy-for-tmate: + type: boolean + default: false + description: >- + When set to true, the ssh connection from the runner to the tmate-ssh-server will be done + using the proxy for the runner. + runner-manager-log-level: + type: string + default: "INFO" + description: >- + The log level for the runner manager application. The value can be CRITICAL, FATAL, ERROR, + WARNING, INFO, or DEBUG. diff --git a/config.yaml b/config.yaml index 113005dddf..e8dffb3ee3 100644 --- a/config.yaml +++ b/config.yaml @@ -1,183 +1,3 @@ # Copyright 2025 Canonical Ltd. # See LICENSE file for licensing details. -options: - dockerhub-mirror: - type: string - default: "" - description: >- - The URL to the private docker registry configured as the dockerhub mirror to be used by the - runners. If set a message will be displayed prior to job execution on self-hosted runner - informing users to use the provided registry. You must use https:// as the protocol. - openstack-clouds-yaml: - type: string - default: "" - description: >- - The clouds.yaml yaml necessary for OpenStack integration. - The format for the clouds.yaml is described in the docs: - (https://docs.openstack.org/python-openstackclient/pike/configuration/index.html#clouds-yaml). - openstack-flavor: - type: string - default: "" - description: | - (Deprecated, use flavor-label-list instead) - The openstack flavor to use to spawn virtual machines. This controls the hardware resource - of the virtual machines. See docs for details: - (https://docs.openstack.org/nova/rocky/user/flavors.html). - openstack-network: - type: string - default: "" - description: | - The openstack network for virtual machines hosting the runners. The network must allow this - application to be able to access the virtual machine though the IP assigned by the openstack - network. See docs for details: - (https://docs.openstack.org/neutron/latest/admin/intro-os-networking.html). - experimental-use-aproxy: - type: boolean - default: false - description: >- - (Experimental, may be removed) When set to true, aproxy (https://github.com/canonical/aproxy) - will be installed within the runners. It will forward TCP traffic matching the 'aproxy-exclude-addresses' - and 'aproxy-redirect-ports' settings to a proxy server configured by the Juju model config 'juju-http-proxy' - (or, if this is not set, 'juju-https-proxy' will be used). - This is useful when the charm is deployed in a network that requires a proxy to access the - internet. - Note that you should carefully choose values for the 'aproxy-exclude-addresses' and - 'aproxy-redirect-ports' so that the network traffic from the runner to the HTTP proxy is not - captured by aproxy. The simplest way to achieve this is to add the IP address of the HTTP proxy - to 'aproxy-exclude-addresses' or exclude the HTTP proxy port from 'aproxy-redirect-ports'. - aproxy-exclude-addresses: - type: string - default: "10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16" - description: >- - A comma-separated list of IP addresses that should be excluded from redirection to aproxy. - 127.0.0.0/8 are always excluded so you can omit if from the configuration. - aproxy-redirect-ports: - type: string - default: "80, 443" - description: >- - A comma-separated list of ports or port ranges that should be redirected to aproxy. - group: - type: string - default: "default" - description: >- - The organization runner group to register the self-hosted runner under. This has no effect on - runners under a repository. - labels: - type: string - default: "" - description: >- - Additional comma separated labels to attach to self-hosted runners. By default, the labels - "self-hosted", architecture (i.e. "x64", "arm64"), os (i.e. "linux"), os-flavor (i.e. - "jammy") are set. Any labels provided via this configuration will be appended to the default - values. - path: - type: string - default: "" - description: >- - The organization or the repository to register the self-hosted runners. For organization the - path would be the name of the organization. For repository the path should be in the - "{owner}/{repo}" format. - reconcile-interval: - type: int - default: 5 - description: >- - Minutes between each reconciliation of the current runners state and their targeted state. - On reconciliation, the charm polls the state of runners and see if actions are needed. The - value should be kept low, unless Github API rate limiting is encountered. - repo-policy-compliance-token: - type: string - description: >- - The token to authenticate with the repository-policy-compliance service in order to - generate one-time-tokens. This option requires the repo-policy-compliance-url to be set. - If not set, the repository-policy-compliance service will not be used. - repo-policy-compliance-url: - type: string - description: >- - The URL to the repository-policy-compliance service. This option requires the - repo-policy-compliance-token to be set. If not set, the repository-policy-compliance service - will not be used. This option is only supported when using OpenStack Cloud. - test-mode: - type: string - description: >- - When set to 'insecure', the charm test mode is activated, which may deactivate some security - hardening measures. - token: - type: string - default: "" - description: >- - The GitHub Personal Access Token for registering the self-hosted runners. The token requires - 'repo' scope for repository runners and 'repo' + 'admin:org' scope for organization runners. - For fine grained token scopes, see - https://charmhub.io/github-runner/docs/how-to-change-token. - virtual-machines: - type: int - default: 0 - description: >- - (Deprecated, use base-virtual-machines or max-total-virtual-machines instead) - The number of virtual machine runners. This charm will spawn or destroy virtual machines - runners to match this setting. Setting virtual-machines to non-zero and any of - base-virtual-machines or max-total-virtual-machines will block the charm. - base-virtual-machines: - type: int - default: 0 - description: >- - The number of non-reactive virtual machine runners spawned by the charm per each - combination of flavor and base. Currently, if the charm is integrated with MongoDB - this config option will be ignored. - max-total-virtual-machines: - type: int - default: 0 - description: >- - If the github-runner charm is integrated with MongoDB, the reactive mode will not spawn - new virtual machines if there are max-total-virtual-machines of more virtual machines - managed by the charm. - flavor-label-combinations: - type: string - default: "" - description: >- - List of "flavor:label" entries separated by ",". The flavor will correspond to a OpenStack - flavor and the label will be a label for the GitHub runner. Only one flavor-label combination - is supported at the moment. A valid configuration looks like "m1.small:small". - manager-ssh-proxy-command: - type: string - default: "" - description: >- - Optional ProxyCommand used by the ssh connection which is used to access the virtual - machine runners from the runner manager. It is equivalent to ProxyCommand in ssh-config, - but without a shell. For example, a value could be something like: - `bash -c "openssl s_client -quiet -connect 192.168.20.2:2222 -servername %h 2> /dev/null"` - pre-job-script: - type: string - description: >- - Optional script (needs shebang) to execute in the pre-job phase of a spawned runner VM. - This can e.g. be useful for specific infrastructure related configurations - (e.g. usage of certain proxies or custom routes). - Note that the user executing the script is the ubuntu user (which has sudo rights). - Example script: - #!/usr/bin/env bash - cat > ~/.ssh/config <- - The HTTP proxy value to use for the runners. If not specified, the model configuration option - juju-http-proxy will be used instead. - use-runner-proxy-for-tmate: - type: boolean - default: false - description: >- - When set to true, the ssh connection from the runner to the tmate-ssh-server will be done - using the proxy for the runner. - runner-manager-log-level: - type: string - default: "INFO" - description: >- - The log level for the runner manager application. The value can be CRITICAL, FATAL, ERROR, - WARNING, INFO, or DEBUG. From 5f8395eb2cca4f4e17b41bfd5b41f4c36c7be6b1 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 11:02:41 +0000 Subject: [PATCH 34/53] chore: image builder set vm constraints --- tests/integration/conftest.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 92e6d8118f..099ba9e6fa 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -394,7 +394,12 @@ async def image_builder_fixture( application_name=application_name, channel="latest/edge", config=image_builder_config, - constraints={"root-disk": 20 * 1024, "mem": 2 * 1024, "virt-type": "virtual-machine"}, + constraints={ + "root-disk": 20 * 1024, + "mem": 2 * 1024, + "virt-type": "virtual-machine", + "cores": 2, + }, ) else: app = model.applications[image_builder_app_name] From 249cc78c359b8075404d771bceb22095bca37a2d Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 11:03:58 +0000 Subject: [PATCH 35/53] test: only deploy jammy (deprecated ops library charm base detection) --- tests/integration/conftest.py | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 099ba9e6fa..ea642b0625 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -95,29 +95,13 @@ def image_builder_app_name(random_app_name_suffix: str) -> str: return f"github-runner-image-builder-{random_app_name_suffix}" -@pytest.fixture(name="series", scope="module") -def series_fixture(): - """Series version for deploying any-charm.""" - series = ( - # Ignore B603 since this is a trusted subprocess call - subprocess.check_output(["/usr/bin/lsb_release", "-rs"]) # nosec: B603 - .strip() - .decode("utf-8") - ) - logging.info("Series: %s", series) - return series - - @pytest.fixture(scope="module") -def charm_file(pytestconfig: pytest.Config, series: str) -> str: +def charm_file(pytestconfig: pytest.Config) -> str: """Path to the built charm.""" charm = pytestconfig.getoption("--charm-file") assert charm, "Please specify the --charm-file command line option" - charm_dir = Path(f"./{charm}").parent - charm_matching_series = list(charm_dir.rglob(f"*{series}*.charm")) - assert charm_matching_series, f"No build found for series {series}" - return f"./{charm_matching_series[0]}" + return f"./{charm[0]}" @pytest.fixture(scope="module") @@ -418,7 +402,6 @@ async def image_builder_fixture( async def app_openstack_runner_fixture( model: Model, charm_file: str, - series: str, app_name: str, path: str, token: str, @@ -439,7 +422,6 @@ async def app_openstack_runner_fixture( application = await deploy_github_runner_charm( model=model, charm_file=charm_file, - series=series, app_name=app_name, path=path, token=token, @@ -511,7 +493,6 @@ async def app_no_wait_tmate_fixture( async def app_runner( model: Model, charm_file: str, - series: str, app_name: str, path: str, token: str, @@ -524,7 +505,6 @@ async def app_runner( application = await deploy_github_runner_charm( model=model, charm_file=charm_file, - series=series, app_name=f"{app_name}-test", path=path, token=token, @@ -540,7 +520,6 @@ async def app_runner( async def app_no_wait_fixture( model: Model, charm_file: str, - series: str, app_name: str, path: str, token: str, @@ -552,7 +531,6 @@ async def app_no_wait_fixture( app: Application = await deploy_github_runner_charm( model=model, charm_file=charm_file, - series=series, app_name=app_name, path=path, token=token, From 7d92a229569fb3e72631be7ab3e72164ce9af647 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 11:06:35 +0000 Subject: [PATCH 36/53] fix: only support jammy --- charmcraft.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/charmcraft.yaml b/charmcraft.yaml index 2d3825c5f2..bf25859609 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -27,7 +27,9 @@ type: charm platforms: ubuntu@22.04:amd64: - ubuntu@24.04:amd64: + # 2025/11/24 deprecated ops testing lib does not support 24.04, deploying 24.04 base on jammy VM + # raises issues with crypto libs. + # ubuntu@24.04:amd64: parts: charm: From 3ab72db23708ea7a085691b1a46afca304704da6 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 11:09:13 +0000 Subject: [PATCH 37/53] chore: remove obsolete config.yaml file --- config.yaml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 config.yaml diff --git a/config.yaml b/config.yaml deleted file mode 100644 index e8dffb3ee3..0000000000 --- a/config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright 2025 Canonical Ltd. -# See LICENSE file for licensing details. - From e43c2a51e888498960b8170afd17ce6bf42f9ba9 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 11:39:39 +0000 Subject: [PATCH 38/53] test: fix series to jammy --- tests/integration/helpers/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/helpers/common.py b/tests/integration/helpers/common.py index 5f118c561d..167cfa6a75 100644 --- a/tests/integration/helpers/common.py +++ b/tests/integration/helpers/common.py @@ -158,7 +158,6 @@ async def deploy_github_runner_charm( https_proxy: str, no_proxy: str, reconcile_interval: int, - series: str = "22.04", constraints: dict | None = None, config: dict | None = None, deploy_kwargs: dict | None = None, @@ -208,7 +207,8 @@ async def deploy_github_runner_charm( application = await model.deploy( charm_file, application_name=app_name, - base=f"ubuntu@{series}", + base="ubuntu@22.04", + series="jammy", config=default_config, constraints=constraints or DEFAULT_RUNNER_CONSTRAINTS, **(deploy_kwargs or {}), From 3048eab6f855c47cdfa23b47bf93a9019bfe4ef2 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 12:02:44 +0000 Subject: [PATCH 39/53] revert charmcraft yaml related changes (incompatible old ops lib) --- charmcraft.yaml | 244 +++--------------------------------------------- config.yaml | 183 ++++++++++++++++++++++++++++++++++++ metadata.yaml | 40 ++++++++ 3 files changed, 234 insertions(+), 233 deletions(-) create mode 100644 config.yaml create mode 100644 metadata.yaml diff --git a/charmcraft.yaml b/charmcraft.yaml index bf25859609..d438d84b3b 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -1,36 +1,8 @@ # Copyright 2025 Canonical Ltd. # See LICENSE file for licensing details. -name: github-runner - -links: - contact: https://launchpad.net/~canonical-is-devops - documentation: https://discourse.charmhub.io/t/github-runner-documentation-overview/13347 - issues: - - https://github.com/canonical/github-runner-operator/issues - source: - - https://github.com/canonical/github-runner-operator - website: - - https://charmhub.io/github-runner - -summary: Creates a group of self-hosted github runners. -description: | - A [Juju](https://juju.is/) [charm](https://juju.is/docs/olm/charmed-operators) managing - [self-hosted runners for GitHub Actions](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners). - - Each unit of this charm will start a configurable number of virtual machines to host - self-hosted runners. Each runner performs only one job, after which it unregisters from GitHub to - ensure that each job runs in a clean environment. The charm will periodically check the number of - runners and spawn or destroy them as necessary to maintain the configured number of runners. Both - the reconciliation interval and the number of runners to maintain are configurable. type: charm -platforms: - ubuntu@22.04:amd64: - # 2025/11/24 deprecated ops testing lib does not support 24.04, deploying 24.04 base on jammy VM - # raises issues with crypto libs. - # ubuntu@24.04:amd64: - parts: charm: charm-python-packages: @@ -46,208 +18,14 @@ parts: rustup default stable craftctl default -provides: - cos-agent: - interface: cos_agent - -requires: - debug-ssh: - interface: debug-ssh - image: - interface: github_runner_image_v0 - mongodb: - interface: mongodb_client - limit: 1 - -storage: - runner: - description: Storage for the root disk of LXD instances hosting the runner application. - type: filesystem - location: /storage/juju - -actions: - check-runners: - description: Get info on active and registered runners - flush-runners: - description: Clear out all runners and start a new set - -config: - dockerhub-mirror: - type: string - default: "" - description: >- - The URL to the private docker registry configured as the dockerhub mirror to be used by the - runners. If set a message will be displayed prior to job execution on self-hosted runner - informing users to use the provided registry. You must use https:// as the protocol. - openstack-clouds-yaml: - type: string - default: "" - description: >- - The clouds.yaml yaml necessary for OpenStack integration. - The format for the clouds.yaml is described in the docs: - (https://docs.openstack.org/python-openstackclient/pike/configuration/index.html#clouds-yaml). - openstack-flavor: - type: string - default: "" - description: | - (Deprecated, use flavor-label-list instead) - The openstack flavor to use to spawn virtual machines. This controls the hardware resource - of the virtual machines. See docs for details: - (https://docs.openstack.org/nova/rocky/user/flavors.html). - openstack-network: - type: string - default: "" - description: | - The openstack network for virtual machines hosting the runners. The network must allow this - application to be able to access the virtual machine though the IP assigned by the openstack - network. See docs for details: - (https://docs.openstack.org/neutron/latest/admin/intro-os-networking.html). - experimental-use-aproxy: - type: boolean - default: false - description: >- - (Experimental, may be removed) When set to true, aproxy (https://github.com/canonical/aproxy) - will be installed within the runners. It will forward TCP traffic matching the 'aproxy-exclude-addresses' - and 'aproxy-redirect-ports' settings to a proxy server configured by the Juju model config 'juju-http-proxy' - (or, if this is not set, 'juju-https-proxy' will be used). - This is useful when the charm is deployed in a network that requires a proxy to access the - internet. - Note that you should carefully choose values for the 'aproxy-exclude-addresses' and - 'aproxy-redirect-ports' so that the network traffic from the runner to the HTTP proxy is not - captured by aproxy. The simplest way to achieve this is to add the IP address of the HTTP proxy - to 'aproxy-exclude-addresses' or exclude the HTTP proxy port from 'aproxy-redirect-ports'. - aproxy-exclude-addresses: - type: string - default: "10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16" - description: >- - A comma-separated list of IP addresses that should be excluded from redirection to aproxy. - 127.0.0.0/8 are always excluded so you can omit if from the configuration. - aproxy-redirect-ports: - type: string - default: "80, 443" - description: >- - A comma-separated list of ports or port ranges that should be redirected to aproxy. - group: - type: string - default: "default" - description: >- - The organization runner group to register the self-hosted runner under. This has no effect on - runners under a repository. - labels: - type: string - default: "" - description: >- - Additional comma separated labels to attach to self-hosted runners. By default, the labels - "self-hosted", architecture (i.e. "x64", "arm64"), os (i.e. "linux"), os-flavor (i.e. - "jammy") are set. Any labels provided via this configuration will be appended to the default - values. - path: - type: string - default: "" - description: >- - The organization or the repository to register the self-hosted runners. For organization the - path would be the name of the organization. For repository the path should be in the - "{owner}/{repo}" format. - reconcile-interval: - type: int - default: 5 - description: >- - Minutes between each reconciliation of the current runners state and their targeted state. - On reconciliation, the charm polls the state of runners and see if actions are needed. The - value should be kept low, unless Github API rate limiting is encountered. - repo-policy-compliance-token: - type: string - description: >- - The token to authenticate with the repository-policy-compliance service in order to - generate one-time-tokens. This option requires the repo-policy-compliance-url to be set. - If not set, the repository-policy-compliance service will not be used. - repo-policy-compliance-url: - type: string - description: >- - The URL to the repository-policy-compliance service. This option requires the - repo-policy-compliance-token to be set. If not set, the repository-policy-compliance service - will not be used. This option is only supported when using OpenStack Cloud. - test-mode: - type: string - description: >- - When set to 'insecure', the charm test mode is activated, which may deactivate some security - hardening measures. - token: - type: string - default: "" - description: >- - The GitHub Personal Access Token for registering the self-hosted runners. The token requires - 'repo' scope for repository runners and 'repo' + 'admin:org' scope for organization runners. - For fine grained token scopes, see - https://charmhub.io/github-runner/docs/how-to-change-token. - virtual-machines: - type: int - default: 0 - description: >- - (Deprecated, use base-virtual-machines or max-total-virtual-machines instead) - The number of virtual machine runners. This charm will spawn or destroy virtual machines - runners to match this setting. Setting virtual-machines to non-zero and any of - base-virtual-machines or max-total-virtual-machines will block the charm. - base-virtual-machines: - type: int - default: 0 - description: >- - The number of non-reactive virtual machine runners spawned by the charm per each - combination of flavor and base. Currently, if the charm is integrated with MongoDB - this config option will be ignored. - max-total-virtual-machines: - type: int - default: 0 - description: >- - If the github-runner charm is integrated with MongoDB, the reactive mode will not spawn - new virtual machines if there are max-total-virtual-machines of more virtual machines - managed by the charm. - flavor-label-combinations: - type: string - default: "" - description: >- - List of "flavor:label" entries separated by ",". The flavor will correspond to a OpenStack - flavor and the label will be a label for the GitHub runner. Only one flavor-label combination - is supported at the moment. A valid configuration looks like "m1.small:small". - manager-ssh-proxy-command: - type: string - default: "" - description: >- - Optional ProxyCommand used by the ssh connection which is used to access the virtual - machine runners from the runner manager. It is equivalent to ProxyCommand in ssh-config, - but without a shell. For example, a value could be something like: - `bash -c "openssl s_client -quiet -connect 192.168.20.2:2222 -servername %h 2> /dev/null"` - pre-job-script: - type: string - description: >- - Optional script (needs shebang) to execute in the pre-job phase of a spawned runner VM. - This can e.g. be useful for specific infrastructure related configurations - (e.g. usage of certain proxies or custom routes). - Note that the user executing the script is the ubuntu user (which has sudo rights). - Example script: - #!/usr/bin/env bash - cat > ~/.ssh/config <- - The HTTP proxy value to use for the runners. If not specified, the model configuration option - juju-http-proxy will be used instead. - use-runner-proxy-for-tmate: - type: boolean - default: false - description: >- - When set to true, the ssh connection from the runner to the tmate-ssh-server will be done - using the proxy for the runner. - runner-manager-log-level: - type: string - default: "INFO" - description: >- - The log level for the runner manager application. The value can be CRITICAL, FATAL, ERROR, - WARNING, INFO, or DEBUG. +bases: + - build-on: + - name: "ubuntu" + channel: "22.04" + architectures: + - amd64 + run-on: + - name: "ubuntu" + channel: "22.04" + architectures: + - amd64 diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000000..113005dddf --- /dev/null +++ b/config.yaml @@ -0,0 +1,183 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +options: + dockerhub-mirror: + type: string + default: "" + description: >- + The URL to the private docker registry configured as the dockerhub mirror to be used by the + runners. If set a message will be displayed prior to job execution on self-hosted runner + informing users to use the provided registry. You must use https:// as the protocol. + openstack-clouds-yaml: + type: string + default: "" + description: >- + The clouds.yaml yaml necessary for OpenStack integration. + The format for the clouds.yaml is described in the docs: + (https://docs.openstack.org/python-openstackclient/pike/configuration/index.html#clouds-yaml). + openstack-flavor: + type: string + default: "" + description: | + (Deprecated, use flavor-label-list instead) + The openstack flavor to use to spawn virtual machines. This controls the hardware resource + of the virtual machines. See docs for details: + (https://docs.openstack.org/nova/rocky/user/flavors.html). + openstack-network: + type: string + default: "" + description: | + The openstack network for virtual machines hosting the runners. The network must allow this + application to be able to access the virtual machine though the IP assigned by the openstack + network. See docs for details: + (https://docs.openstack.org/neutron/latest/admin/intro-os-networking.html). + experimental-use-aproxy: + type: boolean + default: false + description: >- + (Experimental, may be removed) When set to true, aproxy (https://github.com/canonical/aproxy) + will be installed within the runners. It will forward TCP traffic matching the 'aproxy-exclude-addresses' + and 'aproxy-redirect-ports' settings to a proxy server configured by the Juju model config 'juju-http-proxy' + (or, if this is not set, 'juju-https-proxy' will be used). + This is useful when the charm is deployed in a network that requires a proxy to access the + internet. + Note that you should carefully choose values for the 'aproxy-exclude-addresses' and + 'aproxy-redirect-ports' so that the network traffic from the runner to the HTTP proxy is not + captured by aproxy. The simplest way to achieve this is to add the IP address of the HTTP proxy + to 'aproxy-exclude-addresses' or exclude the HTTP proxy port from 'aproxy-redirect-ports'. + aproxy-exclude-addresses: + type: string + default: "10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16" + description: >- + A comma-separated list of IP addresses that should be excluded from redirection to aproxy. + 127.0.0.0/8 are always excluded so you can omit if from the configuration. + aproxy-redirect-ports: + type: string + default: "80, 443" + description: >- + A comma-separated list of ports or port ranges that should be redirected to aproxy. + group: + type: string + default: "default" + description: >- + The organization runner group to register the self-hosted runner under. This has no effect on + runners under a repository. + labels: + type: string + default: "" + description: >- + Additional comma separated labels to attach to self-hosted runners. By default, the labels + "self-hosted", architecture (i.e. "x64", "arm64"), os (i.e. "linux"), os-flavor (i.e. + "jammy") are set. Any labels provided via this configuration will be appended to the default + values. + path: + type: string + default: "" + description: >- + The organization or the repository to register the self-hosted runners. For organization the + path would be the name of the organization. For repository the path should be in the + "{owner}/{repo}" format. + reconcile-interval: + type: int + default: 5 + description: >- + Minutes between each reconciliation of the current runners state and their targeted state. + On reconciliation, the charm polls the state of runners and see if actions are needed. The + value should be kept low, unless Github API rate limiting is encountered. + repo-policy-compliance-token: + type: string + description: >- + The token to authenticate with the repository-policy-compliance service in order to + generate one-time-tokens. This option requires the repo-policy-compliance-url to be set. + If not set, the repository-policy-compliance service will not be used. + repo-policy-compliance-url: + type: string + description: >- + The URL to the repository-policy-compliance service. This option requires the + repo-policy-compliance-token to be set. If not set, the repository-policy-compliance service + will not be used. This option is only supported when using OpenStack Cloud. + test-mode: + type: string + description: >- + When set to 'insecure', the charm test mode is activated, which may deactivate some security + hardening measures. + token: + type: string + default: "" + description: >- + The GitHub Personal Access Token for registering the self-hosted runners. The token requires + 'repo' scope for repository runners and 'repo' + 'admin:org' scope for organization runners. + For fine grained token scopes, see + https://charmhub.io/github-runner/docs/how-to-change-token. + virtual-machines: + type: int + default: 0 + description: >- + (Deprecated, use base-virtual-machines or max-total-virtual-machines instead) + The number of virtual machine runners. This charm will spawn or destroy virtual machines + runners to match this setting. Setting virtual-machines to non-zero and any of + base-virtual-machines or max-total-virtual-machines will block the charm. + base-virtual-machines: + type: int + default: 0 + description: >- + The number of non-reactive virtual machine runners spawned by the charm per each + combination of flavor and base. Currently, if the charm is integrated with MongoDB + this config option will be ignored. + max-total-virtual-machines: + type: int + default: 0 + description: >- + If the github-runner charm is integrated with MongoDB, the reactive mode will not spawn + new virtual machines if there are max-total-virtual-machines of more virtual machines + managed by the charm. + flavor-label-combinations: + type: string + default: "" + description: >- + List of "flavor:label" entries separated by ",". The flavor will correspond to a OpenStack + flavor and the label will be a label for the GitHub runner. Only one flavor-label combination + is supported at the moment. A valid configuration looks like "m1.small:small". + manager-ssh-proxy-command: + type: string + default: "" + description: >- + Optional ProxyCommand used by the ssh connection which is used to access the virtual + machine runners from the runner manager. It is equivalent to ProxyCommand in ssh-config, + but without a shell. For example, a value could be something like: + `bash -c "openssl s_client -quiet -connect 192.168.20.2:2222 -servername %h 2> /dev/null"` + pre-job-script: + type: string + description: >- + Optional script (needs shebang) to execute in the pre-job phase of a spawned runner VM. + This can e.g. be useful for specific infrastructure related configurations + (e.g. usage of certain proxies or custom routes). + Note that the user executing the script is the ubuntu user (which has sudo rights). + Example script: + #!/usr/bin/env bash + cat > ~/.ssh/config <- + The HTTP proxy value to use for the runners. If not specified, the model configuration option + juju-http-proxy will be used instead. + use-runner-proxy-for-tmate: + type: boolean + default: false + description: >- + When set to true, the ssh connection from the runner to the tmate-ssh-server will be done + using the proxy for the runner. + runner-manager-log-level: + type: string + default: "INFO" + description: >- + The log level for the runner manager application. The value can be CRITICAL, FATAL, ERROR, + WARNING, INFO, or DEBUG. diff --git a/metadata.yaml b/metadata.yaml new file mode 100644 index 0000000000..91767d2864 --- /dev/null +++ b/metadata.yaml @@ -0,0 +1,40 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +name: github-runner +display-name: GitHub runner +maintainers: + - https://launchpad.net/~canonical-is-devops +docs: https://discourse.charmhub.io/t/github-runner-documentation-overview/13347 +issues: https://github.com/canonical/github-runner-operator/issues +source: https://github.com/canonical/github-runner-operator +summary: Creates a group of self-hosted github runners. +description: | + A [Juju](https://juju.is/) [charm](https://juju.is/docs/olm/charmed-operators) managing + [self-hosted runners for GitHub Actions](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners). + Each unit of this charm will start a configurable number of virtual machines to host + self-hosted runners. Each runner performs only one job, after which it unregisters from GitHub to + ensure that each job runs in a clean environment. The charm will periodically check the number of + runners and spawn or destroy them as necessary to maintain the configured number of runners. Both + the reconciliation interval and the number of runners to maintain are configurable. +series: + - jammy + +provides: + cos-agent: + interface: cos_agent + +requires: + debug-ssh: + interface: debug-ssh + image: + interface: github_runner_image_v0 + mongodb: + interface: mongodb_client + limit: 1 + +storage: + runner: + description: Storage for the root disk of LXD instances hosting the runner application. + type: filesystem + location: /storage/juju From 4d9e69aeee0a698e943aeec6cf0362e92beecea1 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 12:07:47 +0000 Subject: [PATCH 40/53] revert charmcraft yaml contents (add back actions.yaml) --- actions.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 actions.yaml diff --git a/actions.yaml b/actions.yaml new file mode 100644 index 0000000000..cb0bb0b5ce --- /dev/null +++ b/actions.yaml @@ -0,0 +1,7 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +check-runners: + description: Get info on active and registered runners +flush-runners: + description: Clear out all runners and start a new set From 9f3fb0787ab7bcab30f6efa6c636f3a0808ec605 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 12:20:54 +0000 Subject: [PATCH 41/53] remove unused subprocess --- tests/integration/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index ea642b0625..884676f4a7 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -6,7 +6,6 @@ import random import secrets import string -import subprocess from pathlib import Path from time import sleep from typing import Any, AsyncGenerator, AsyncIterator, Generator, Iterator, Optional, cast From 13fd5d9495ef4068a445ba119dfb96bf59479ebd Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 12:22:34 +0000 Subject: [PATCH 42/53] charm file path --- 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 884676f4a7..3591415c6c 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -100,7 +100,7 @@ def charm_file(pytestconfig: pytest.Config) -> str: charm = pytestconfig.getoption("--charm-file") assert charm, "Please specify the --charm-file command line option" - return f"./{charm[0]}" + return f"./{charm}" @pytest.fixture(scope="module") From fc8fcead136ec3f088c957e89f9aa9d74f314125 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Mon, 24 Nov 2025 13:08:58 +0000 Subject: [PATCH 43/53] revert debug --- .github/workflows/integration_test.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index c39d07d9f4..27fecd67c7 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -43,8 +43,6 @@ jobs: --openstack-no-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" self-hosted-runner: true self-hosted-runner-label: pfe-ci - tmate-debug: true - tmate-timeout: 90 openstack-integration-tests-cross-controller-private-endpoint: name: Cross controller integration test using private-endpoint uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main From 0b5aac727179a1c7445c4ab977762b52f0311fe6 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Tue, 25 Nov 2025 03:14:13 +0000 Subject: [PATCH 44/53] test: use testing image for deployment --- .github/workflows/integration_test.yaml | 6 ++- tests/conftest.py | 7 +++ tests/integration/conftest.py | 57 +++++++++++++++++++++---- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 27fecd67c7..617e038d69 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -40,7 +40,8 @@ jobs: --no-proxy="${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ --openstack-https-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ --openstack-http-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ - --openstack-no-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" + --openstack-no-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" \ + --test-image-id="${{ vars.INTEGRATION_TEST_IMAGE_ID }}" self-hosted-runner: true self-hosted-runner-label: pfe-ci openstack-integration-tests-cross-controller-private-endpoint: @@ -72,7 +73,8 @@ jobs: --no-proxy="${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ --openstack-https-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ --openstack-http-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ - --openstack-no-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" + --openstack-no-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" \ + --test-image-id="${{ vars.INTEGRATION_TEST_IMAGE_ID }}" self-hosted-runner: true self-hosted-runner-label: pfe-ci allure-report: diff --git a/tests/conftest.py b/tests/conftest.py index bb80263a12..ddae78b641 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -140,3 +140,10 @@ def pytest_addoption(parser: Parser): help="The flavor for testing openstack interfaces. The resource should be enough to boot the test image.", default=None, ) + parser.addoption( + "--test-image-id", + action="store", + help="The image ID to use for testing. If provided, any-charm will be used to mock the " + "image builder for faster testing deployment.", + default=None, + ) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 3591415c6c..3eb5326c07 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -2,10 +2,12 @@ # See LICENSE file for licensing details. """Fixtures for github runner charm integration tests.""" +import json import logging import random import secrets import string +import textwrap from pathlib import Path from time import sleep from typing import Any, AsyncGenerator, AsyncIterator, Generator, Iterator, Optional, cast @@ -252,6 +254,12 @@ def openstack_test_flavor_fixture(pytestconfig: pytest.Config) -> str: return test_flavor +@pytest.fixture(scope="module", name="test_image_id") +def test_image_id_fixture(pytestconfig: pytest.Config) -> Optional[str]: + """The test image ID for mocking image builder.""" + return pytestconfig.getoption("--test-image-id") + + @pytest.fixture(scope="module", name="openstack_connection") def openstack_connection_fixture( clouds_yaml_contents: str, @@ -366,15 +374,49 @@ async def image_builder_fixture( model: Model, existing_app_suffix: Optional[str], image_builder_app_name: str, + test_image_id: Optional[str], image_builder_config: dict, openstack_connection, ): - """The image builder application for OpenStack runners.""" - if not existing_app_suffix: - application_name = image_builder_app_name - app = await model.deploy( + """The image builder application for OpenStack runners. + + If --test-image-id is provided, uses any-charm to mock the image relation. + Otherwise, deploys the real github-runner-image-builder charm. + """ + if existing_app_suffix: + yield model.applications[image_builder_app_name] + return + + if test_image_id: + # Use any-charm to mock the image relation provider + any_charm_src_overwrite = { + "any_charm.py": textwrap.dedent( + f"""\ + from any_charm_base import AnyCharmBase + + class AnyCharm(AnyCharmBase): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.framework.observe(self.on['image'].relation_changed, self._image_relation_changed) + + def _image_relation_changed(self, event): + # Provide mock image relation data + event.relation.data[self.unit]['id'] = '{test_image_id}' + event.relation.data[self.unit]['tags'] = 'jammy, amd64' + """ + ), + } + yield await model.deploy( + "any-charm", + application_name=image_builder_app_name, + channel="latest/beta", + config={"src-overwrite": json.dumps(any_charm_src_overwrite)}, + ) + else: + # Deploy the real github-runner-image-builder + yield await model.deploy( "github-runner-image-builder", - application_name=application_name, + application_name=image_builder_app_name, channel="latest/edge", config=image_builder_config, constraints={ @@ -384,11 +426,8 @@ async def image_builder_fixture( "cores": 2, }, ) - else: - app = model.applications[image_builder_app_name] - yield app - if not existing_app_suffix: + if not existing_app_suffix and not test_image_id: # The github-image-builder does not clean keypairs. Until it does, # we clean them manually here. for key in openstack_connection.list_keypairs(): From 6907b4e783407330c8293c61855513d8c45c1f8d Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Tue, 25 Nov 2025 03:31:14 +0000 Subject: [PATCH 45/53] test: refactor: simplify image builder fixture logic and cleanup --- tests/integration/conftest.py | 61 +++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 3eb5326c07..88334154c2 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -377,42 +377,21 @@ async def image_builder_fixture( test_image_id: Optional[str], image_builder_config: dict, openstack_connection, + request: pytest.FixtureRequest, ): """The image builder application for OpenStack runners. If --test-image-id is provided, uses any-charm to mock the image relation. Otherwise, deploys the real github-runner-image-builder charm. + For test_e2e module, always deploys the real image builder. """ if existing_app_suffix: + logging.info("Using existing image builder %s", image_builder_app_name) yield model.applications[image_builder_app_name] return - if test_image_id: - # Use any-charm to mock the image relation provider - any_charm_src_overwrite = { - "any_charm.py": textwrap.dedent( - f"""\ - from any_charm_base import AnyCharmBase - - class AnyCharm(AnyCharmBase): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.framework.observe(self.on['image'].relation_changed, self._image_relation_changed) - - def _image_relation_changed(self, event): - # Provide mock image relation data - event.relation.data[self.unit]['id'] = '{test_image_id}' - event.relation.data[self.unit]['tags'] = 'jammy, amd64' - """ - ), - } - yield await model.deploy( - "any-charm", - application_name=image_builder_app_name, - channel="latest/beta", - config={"src-overwrite": json.dumps(any_charm_src_overwrite)}, - ) - else: + if not test_image_id or request.node.name == "test_e2e": + logging.info("Deploying image builder %s", image_builder_app_name) # Deploy the real github-runner-image-builder yield await model.deploy( "github-runner-image-builder", @@ -427,14 +406,42 @@ def _image_relation_changed(self, event): }, ) - if not existing_app_suffix and not test_image_id: # The github-image-builder does not clean keypairs. Until it does, # we clean them manually here. + logging.info("Cleaning up image builder resources...") for key in openstack_connection.list_keypairs(): key_name: str = key.name if key_name.startswith(image_builder_app_name): openstack_connection.delete_keypair(key_name) + return + + # Use any-charm to mock the image relation provider + any_charm_src_overwrite = { + "any_charm.py": textwrap.dedent( + f"""\ + from any_charm_base import AnyCharmBase + + class AnyCharm(AnyCharmBase): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.framework.observe(self.on['image'].relation_changed, self._image_relation_changed) + + def _image_relation_changed(self, event): + # Provide mock image relation data + event.relation.data[self.unit]['id'] = '{test_image_id}' + event.relation.data[self.unit]['tags'] = 'jammy, amd64' + """ + ), + } + logging.info("Deploying fake image builder via any-charm for image ID %s", test_image_id) + yield await model.deploy( + "any-charm", + application_name=image_builder_app_name, + channel="latest/beta", + config={"src-overwrite": json.dumps(any_charm_src_overwrite)}, + ) + @pytest_asyncio.fixture(scope="module", name="app_openstack_runner") async def app_openstack_runner_fixture( From 642f6d70a4f0adc2db69bf650bf3567616434c94 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Wed, 26 Nov 2025 02:40:00 +0000 Subject: [PATCH 46/53] test: update image builder fixture to use provide-github-runner-image-v0 relation and latest/edge channel --- tests/integration/conftest.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 88334154c2..5dc9da73f8 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -421,12 +421,13 @@ async def image_builder_fixture( "any_charm.py": textwrap.dedent( f"""\ from any_charm_base import AnyCharmBase - + class AnyCharm(AnyCharmBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.framework.observe(self.on['image'].relation_changed, self._image_relation_changed) - + self.framework.observe(self.on['provide-github-runner-image-v0'].\ +relation_changed, self._image_relation_changed) + def _image_relation_changed(self, event): # Provide mock image relation data event.relation.data[self.unit]['id'] = '{test_image_id}' @@ -438,7 +439,7 @@ def _image_relation_changed(self, event): yield await model.deploy( "any-charm", application_name=image_builder_app_name, - channel="latest/beta", + channel="latest/edge", config={"src-overwrite": json.dumps(any_charm_src_overwrite)}, ) From 4b48699d5cf448eed0d6e22c89d3945bcc453d31 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Wed, 26 Nov 2025 04:08:34 +0000 Subject: [PATCH 47/53] test: fix image builder w/ any-charm & pin revision to beta --- tests/integration/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 5dc9da73f8..efe8793150 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -439,7 +439,7 @@ def _image_relation_changed(self, event): yield await model.deploy( "any-charm", application_name=image_builder_app_name, - channel="latest/edge", + channel="latest/beta", config={"src-overwrite": json.dumps(any_charm_src_overwrite)}, ) @@ -486,7 +486,7 @@ async def app_openstack_runner_fixture( }, wait_idle=False, ) - await model.integrate(f"{image_builder.name}:image", f"{application.name}:image") + await model.integrate(image_builder.name, f"{application.name}:image") await model.wait_for_idle( apps=[application.name, image_builder.name], status=ACTIVE, From df8ae40cd45d81598850d0bcbbacf1fa424139ae Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Wed, 26 Nov 2025 06:10:55 +0000 Subject: [PATCH 48/53] test: revert e2e pre-built image use --- tests/integration/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index efe8793150..0b3e40ad29 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -383,14 +383,13 @@ async def image_builder_fixture( If --test-image-id is provided, uses any-charm to mock the image relation. Otherwise, deploys the real github-runner-image-builder charm. - For test_e2e module, always deploys the real image builder. """ if existing_app_suffix: logging.info("Using existing image builder %s", image_builder_app_name) yield model.applications[image_builder_app_name] return - if not test_image_id or request.node.name == "test_e2e": + if not test_image_id: logging.info("Deploying image builder %s", image_builder_app_name) # Deploy the real github-runner-image-builder yield await model.deploy( From f8a2b0b665c36c4a2210c25fdfd6566b4b916a0e Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Wed, 26 Nov 2025 06:11:16 +0000 Subject: [PATCH 49/53] test: infer image relation via interface --- tests/integration/test_charm_upgrade.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_charm_upgrade.py b/tests/integration/test_charm_upgrade.py index 8da401074d..21eb56c821 100644 --- a/tests/integration/test_charm_upgrade.py +++ b/tests/integration/test_charm_upgrade.py @@ -91,7 +91,7 @@ async def test_charm_upgrade( }, wait_idle=False, ) - await model.integrate(f"{image_builder.name}:image", f"{application.name}:image") + await model.integrate(f"{image_builder.name}", f"{application.name}:image") await model.wait_for_idle( apps=[application.name, image_builder.name], raise_on_error=False, From d9c268e92cdb174786bc71cf14bb222b9c241caa Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Wed, 26 Nov 2025 07:13:01 +0000 Subject: [PATCH 50/53] test: document fork behavior --- tests/integration/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 0b3e40ad29..c84b3aa24f 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -638,6 +638,8 @@ def forked_github_repository( github_repository: Repository, ) -> Iterator[Repository]: """Create a fork for a GitHub repository.""" + # After fork creation, the repository workflow run must be enabled manually. Otherwise, a 404 + # on the workflow get API will be returned. forked_repository = github_repository.create_fork(name=f"test-{github_repository.name}") # Wait for repo to be ready From a2fdc377dfb34217c6ccfe281ce5cb84ac599a6d Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Wed, 26 Nov 2025 12:00:39 +0000 Subject: [PATCH 51/53] test: deploy all apps on virtual machine due to snapd bug --- tests/integration/conftest.py | 13 ++++++++++++- tests/integration/helpers/common.py | 4 +++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index c84b3aa24f..b98ea52c58 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -400,6 +400,8 @@ async def image_builder_fixture( constraints={ "root-disk": 20 * 1024, "mem": 2 * 1024, + # 2025-11-26: Set deployment type to virtual-machine due to bug with snapd. See: + # https://github.com/canonical/snapd/pull/16131 "virt-type": "virtual-machine", "cores": 2, }, @@ -595,6 +597,9 @@ async def tmate_ssh_server_app_fixture(model: Model) -> AsyncIterator[Applicatio tmate_app: Application = await model.deploy( "tmate-ssh-server", channel="edge", + # 2025-11-26: Set deployment type to virtual-machine due to bug with snapd. See: + # https://github.com/canonical/snapd/pull/16131 + constraints={"virt-type": "virtual-machine"}, ) return tmate_app @@ -747,7 +752,13 @@ async def app_for_metric_fixture( async def mongodb_fixture(model: Model, existing_app_suffix: str | None) -> Application: """Deploy MongoDB.""" if not existing_app_suffix: - mongodb = await model.deploy(MONGODB_APP_NAME, channel="6/edge") + mongodb = await model.deploy( + MONGODB_APP_NAME, + channel="6/edge", + # 2025-11-26: Set deployment type to virtual-machine due to bug with snapd. See: + # https://github.com/canonical/snapd/pull/16131 + constraints={"virt-type": "virtual-machine"}, + ) else: mongodb = model.applications["mongodb"] return mongodb diff --git a/tests/integration/helpers/common.py b/tests/integration/helpers/common.py index 167cfa6a75..3c9051a733 100644 --- a/tests/integration/helpers/common.py +++ b/tests/integration/helpers/common.py @@ -46,7 +46,9 @@ DISPATCH_E2E_TEST_RUN_OPENSTACK_WORKFLOW_FILENAME = "e2e_test_run_openstack.yaml" MONGODB_APP_NAME = "mongodb" -DEFAULT_RUNNER_CONSTRAINTS = {"root-disk": 15} +# 2025-11-26: Set deployment type to virtual-machine due to bug with snapd. See: +# https://github.com/canonical/snapd/pull/16131 +DEFAULT_RUNNER_CONSTRAINTS = {"root-disk": 15, "virt-type": "virtual-machine"} logger = logging.getLogger(__name__) From b22485b5ada1bdc5c822c39f08f2b21ae9b3d48b Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Wed, 26 Nov 2025 12:03:41 +0000 Subject: [PATCH 52/53] test: supply explicit args to e2e tests --- .github/workflows/e2e_test.yaml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e_test.yaml b/.github/workflows/e2e_test.yaml index 3cf5b688bb..8e85be457b 100644 --- a/.github/workflows/e2e_test.yaml +++ b/.github/workflows/e2e_test.yaml @@ -19,7 +19,26 @@ jobs: provider: lxd test-tox-env: integration-juju3.6 modules: '["test_e2e"]' - extra-arguments: "-m openstack" + # INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_ + # mapping. See CONTRIBUTING.md for more details. + extra-arguments: | + -m=openstack \ + --log-format="%(asctime)s %(levelname)s %(message)s" \ + --path="${{ vars.INTEGRATION_TEST_PATH }}" \ + --openstack-auth-url="${{ vars.INTEGRATION_TEST_OPENSTACK_AUTH_URL }}" \ + --openstack-project-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_DOMAIN_NAME }}" \ + --openstack-project-name="${{ vars.INTEGRATION_TEST_OPENSTACK_PROJECT_NAME }}" \ + --openstack-user-domain-name="${{ vars.INTEGRATION_TEST_OPENSTACK_USER_DOMAIN_NAME }}" \ + --openstack-username="${{ vars.INTEGRATION_TEST_OPENSTACK_USERNAME }}" \ + --openstack-region-name="${{ vars.INTEGRATION_TEST_OPENSTACK_REGION_NAME }}" \ + --openstack-network-name="${{ vars.INTEGRATION_TEST_OPENSTACK_NETWORK_NAME }}" \ + --openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \ + --https-proxy="${{ vars.INTEGRATION_TEST_HTTPS_PROXY }}" \ + --http-proxy="${{ vars.INTEGRATION_TEST_HTTP_PROXY }}" \ + --no-proxy="${{ vars.INTEGRATION_TEST_NO_PROXY }}" \ + --openstack-https-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_HTTPS_PROXY }}" \ + --openstack-http-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \ + --openstack-no-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" self-hosted-runner: true self-hosted-runner-label: pfe-ci From f6543663a470b90c04173032e36d64c3ed222920 Mon Sep 17 00:00:00 2001 From: charlie4284 Date: Wed, 26 Nov 2025 13:55:57 +0000 Subject: [PATCH 53/53] test: apply more root disk to default runner config --- tests/integration/helpers/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/helpers/common.py b/tests/integration/helpers/common.py index 3c9051a733..45042e316f 100644 --- a/tests/integration/helpers/common.py +++ b/tests/integration/helpers/common.py @@ -48,7 +48,7 @@ MONGODB_APP_NAME = "mongodb" # 2025-11-26: Set deployment type to virtual-machine due to bug with snapd. See: # https://github.com/canonical/snapd/pull/16131 -DEFAULT_RUNNER_CONSTRAINTS = {"root-disk": 15, "virt-type": "virtual-machine"} +DEFAULT_RUNNER_CONSTRAINTS = {"root-disk": 20 * 1024, "virt-type": "virtual-machine"} logger = logging.getLogger(__name__)