Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[project]
name = "github-runner-image-builder"
version = "0.12.0"
version = "0.12.1"
authors = [
{ name = "Canonical IS DevOps", email = "is-devops-team@canonical.com" },
]
Expand Down
1 change: 0 additions & 1 deletion app/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

"""Fixtures for github runner image builder app."""


from pytest import Parser


Expand Down
1 change: 1 addition & 0 deletions app/tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# See LICENSE file for licensing details.

"""Fixtures for github runner image builder integration tests."""

import logging
import os
import secrets
Expand Down
6 changes: 2 additions & 4 deletions app/tests/integration/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,7 @@ def setup_aproxy(ssh_connection: SSHConnection, proxy: str) -> None:
proxy: The hostname and port in the format of "hostname:port".
"""
ssh_connection.run(f"/usr/bin/sudo snap set aproxy proxy={proxy} listen=:8444")
ssh_connection.run(
"""/usr/bin/sudo nft -f - << EOF
ssh_connection.run("""/usr/bin/sudo nft -f - << EOF
define default-ip = $(ip route get $(ip route show 0.0.0.0/0 | grep -oP 'via \\K\\S+') \
| grep -oP 'src \\K\\S+')
define private-ips = { 10.0.0.0/8, 127.0.0.1/8, 172.16.0.0/12, 192.168.0.0/16 }
Expand All @@ -400,8 +399,7 @@ def setup_aproxy(ssh_connection: SSHConnection, proxy: str) -> None:
}
}
EOF
"""
)
""")
# Wait for aproxy to become active.
for _ in range(6):
time.sleep(5)
Expand Down
5 changes: 3 additions & 2 deletions app/tests/integration/test_openstack_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import functools
import itertools
import logging
import secrets
import typing
import urllib.parse
from datetime import datetime, timezone
Expand Down Expand Up @@ -120,8 +121,8 @@ def image_ids_fixture(
script_config=config.ScriptConfig(
script_url=urllib.parse.urlparse(TESTDATA_TEST_SCRIPT_URL),
script_secrets={
"TEST_SECRET": "SHOULD_EXIST",
"TEST_NON_SECRET": "SHOULD_NOT_EXIST",
"TEST_SECRET": secrets.token_hex(16),
"TEST_NON_SECRET": secrets.token_hex(16),
},
),
),
Expand Down
1 change: 1 addition & 0 deletions app/tests/unit/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# See LICENSE file for licensing details.

"""Unit tests for logging module."""

import logging as logging_module
from pathlib import Path
from unittest.mock import MagicMock
Expand Down
19 changes: 14 additions & 5 deletions app/tests/unit/test_openstack_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,9 @@ def test_run(
if with_external_script
else None
),
script_secrets={"TEST_SECRET_ONE": "HELLO"} if with_external_script else {},
script_secrets=(
({"TEST_SECRET_ONE": secrets.token_hex(16)}) if with_external_script else {}
),
),
)

Expand Down Expand Up @@ -694,7 +696,10 @@ def test__generate_cloud_init_script(
name="test-image",
script_config=openstack_builder.config.ScriptConfig(
script_url=urllib.parse.urlparse("https://test-url.com/script.sh"),
script_secrets={"TEST_SECRET_ONE": "HELLO", "TEST_SECRET_TWO": "WORLD"},
script_secrets={
"TEST_SECRET_ONE": secrets.token_hex(16),
"TEST_SECRET_TWO": secrets.token_hex(16),
},
),
),
proxy="test.proxy.internal:3128",
Expand Down Expand Up @@ -948,9 +953,13 @@ def test__wait_for_cloud_init_complete():
@pytest.mark.parametrize(
"script_secrets",
[
pytest.param({"TEST_SECRET_ONE": "HELLO"}, id="single secret"),
pytest.param({"TEST_SECRET_ONE": secrets.token_hex(16)}, id="single secret"),
pytest.param(
{"TEST_SECRET_ONE": "HELLO", "TEST_SECRET_TWO": "WORLD"}, id="multiple secrets"
{
"TEST_SECRET_ONE": secrets.token_hex(16),
"TEST_SECRET_TWO": secrets.token_hex(16),
},
id="multiple secrets",
),
pytest.param({}, id="no secrets"),
],
Expand Down Expand Up @@ -1011,7 +1020,7 @@ def run_side_effect(*_, **__):
with pytest.raises(ExternalScriptError) as exc:
openstack_builder._execute_external_script(
script_url="https://test-url.com/script.sh",
script_secrets={"TEST_SECRET_ONE": "HELLO"},
script_secrets={"TEST_SECRET_ONE": secrets.token_hex(16)},
ssh_conn=mock_connection,
)
assert "Unexpected exit code" in str(exc)
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@

<!-- vale Canonical.007-Headings-sentence-case = NO -->

## [#185 Remove aproxy installation and add proxy support in workload](https://github.com/canonical/github-runner-image-builder-operator/pull/185) (2026-01-20)
* Remove `aproxy` snap installation in the charm and inject proxy values from the model config into the workload process.

## [#172 feat: apt upgrade](https://github.com/canonical/github-runner-image-builder-operator/pull/172) (2025-11-26)
* Apply apt-update and apt-upgrade to GH runner images by applying them during cloud-init.

Expand Down
49 changes: 45 additions & 4 deletions src/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
OPENSTACK_CLOUDS_YAML_PATH = UBUNTU_HOME / "clouds.yaml"

# Bandit thinks this is a hardcoded secret
IMAGE_BUILDER_SECRET_PREFIX = "IMAGE_BUILDER_SECRET_" # nosec: B105
IMAGE_BUILDER_SECRET_PREFIX = "IMAGE_BUILDER_SECRET_" # nosec: hardcoded_password_string


@dataclasses.dataclass
Expand Down Expand Up @@ -348,10 +348,10 @@ class ExternalServiceConfig:
"""Builder run external service dependencies.

Attributes:
proxy: The proxy to use to build the image.
proxy: The proxy configuration to use to build the image.
"""

proxy: str | None
proxy: state.ProxyConfig | None


@dataclasses.dataclass
Expand Down Expand Up @@ -515,7 +515,11 @@ def _run(config: RunConfig) -> list[CloudImage]:
script_secrets=config.image.script_config.script_secrets,
),
service_options=_ServiceOptions(
proxy=config.external_service.proxy,
proxy=(
config.external_service.proxy.http or config.external_service.proxy.https
if config.external_service.proxy
else None
),
),
)
logger.info("Run build command: %s", run_command)
Expand All @@ -528,6 +532,7 @@ def _run(config: RunConfig) -> list[CloudImage]:
env={
"HOME": str(UBUNTU_HOME),
**_transform_secrets(secrets=config.image.script_config.script_secrets),
**_get_proxy_env(proxy=config.external_service.proxy),
},
)
# The return value of the CLI is "Image build success:\n<comma-separated-image-ids>"
Expand Down Expand Up @@ -571,6 +576,42 @@ def _transform_secrets(secrets: dict[str, str] | None) -> dict[str, str]:
)


def _get_proxy_env(proxy: state.ProxyConfig | None) -> dict[str, str]:
"""Transform proxy config to standard environment variables.

Args:
proxy: The proxy configuration.

Returns:
Dictionary of proxy environment variables.
"""
if not proxy:
return {}
env_vars = {}
if proxy.http:
env_vars.update(
{
"http_proxy": proxy.http,
"HTTP_PROXY": proxy.http,
}
)
if proxy.https:
env_vars.update(
{
"https_proxy": proxy.https,
"HTTPS_PROXY": proxy.https,
}
)
if proxy.no_proxy:
env_vars.update(
{
"no_proxy": proxy.no_proxy,
"NO_PROXY": proxy.no_proxy,
}
)
return env_vars


@dataclasses.dataclass
class _RunArgs:
"""Builder application run arguments.
Expand Down
13 changes: 4 additions & 9 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# See LICENSE file for licensing details.

"""Entrypoint for GithubRunnerImageBuilder charm."""

import json
import logging

Expand All @@ -21,7 +22,6 @@
import builder
import charm_utils
import image
import proxy
import state

LOG_FILE_DIR = Path("/var/log/github-runner-image-builder")
Expand Down Expand Up @@ -104,7 +104,6 @@ def _on_config_changed(self, _: ops.ConfigChangedEvent) -> None:
if not self._is_any_image_relation_ready(cloud_config=builder_config_state.cloud_config):
return
# The following lines should be covered by integration tests.
proxy.configure_aproxy(proxy=state.ProxyConfig.from_env()) # pragma: no cover
builder.install_clouds_yaml( # pragma: no cover
cloud_config=builder_config_state.cloud_config.openstack_clouds_config
)
Expand All @@ -131,7 +130,6 @@ def _on_image_relation_changed(self, evt: ops.RelationChangedEvent) -> None:
evt.unit.name,
)
return
proxy.configure_aproxy(proxy=state.ProxyConfig.from_env())
builder.install_clouds_yaml(
cloud_config=builder_config_state.cloud_config.openstack_clouds_config
)
Expand Down Expand Up @@ -178,7 +176,6 @@ def _on_run_action(self, event: ops.ActionEvent) -> None:

def _setup_builder(self) -> None:
"""Set up the builder application."""
proxy.setup(proxy=state.ProxyConfig.from_env())
builder_config_state = state.BuilderConfig.from_charm(charm=self)
builder.initialize(
app_init_config=builder.ApplicationInitializationConfig(
Expand All @@ -193,17 +190,15 @@ def _setup_builder(self) -> None:
def _setup_logrotate(self) -> None:
"""Set up the log rotation for image-builder application."""
APP_LOGROTATE_CONFIG_PATH.write_text(
dedent(
f"""\
dedent(f"""\
{str(LOG_FILE_PATH.absolute())} {{
weekly
rotate 3
compress
delaycompress
missingok
}}
"""
),
"""),
encoding="utf-8",
)
try:
Expand Down Expand Up @@ -322,7 +317,7 @@ def _get_static_config(self, builder_config: state.BuilderConfig) -> builder.Sta
runner_version=builder_config.image_config.runner_version,
),
service_config=builder.ExternalServiceConfig(
proxy=(builder_config.proxy.http if builder_config.proxy else None),
proxy=builder_config.proxy,
),
)

Expand Down
4 changes: 0 additions & 4 deletions src/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ class BuilderRunError(BuilderBaseError):
"""Represents an error while running the image builder."""


class ProxyInstallError(BuilderBaseError):
"""Represents an error while installing proxy."""


class GetLatestImageError(BuilderBaseError):
"""Represents an error while fetching the latest image."""

Expand Down
1 change: 0 additions & 1 deletion src/pipx.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

"""Module handling interactions with pipx."""


# Code is abstracting process interactions and is currently tested in integration tests.

import logging
Expand Down
83 changes: 0 additions & 83 deletions src/proxy.py

This file was deleted.

Loading