Skip to content

Commit

Permalink
chore: proxy configs
Browse files Browse the repository at this point in the history
  • Loading branch information
yanksyoon committed Mar 8, 2024
1 parent 46b3b75 commit fbc1e8e
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 44 deletions.
32 changes: 24 additions & 8 deletions scripts/build-openstack-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ RUNNER_TAR_URL="$1"
HTTP_PROXY="$2"
HTTPS_PROXY="$3"
NO_PROXY="$4"
DOCKER_PROXY_SERVICE_CONF="$5"
DOCKER_PROXY_CONF="$6"

# cleanup any existing mounts
cleanup() {
Expand Down Expand Up @@ -94,21 +96,35 @@ df -h # print disk free space
DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get update -yq
DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get upgrade -yq
DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get install docker.io npm python3-pip shellcheck jq wget unzip gh -yq
ln -s /usr/bin/python3 /usr/bin/python
# Uninstall unattended-upgrades, to avoid lock errors when unattended-upgrades is active in the runner
DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get purge unattended-upgrades -yq
if [[ -n "$HTTP_PROXY" ]]; then
/snap/bin/lxc exec builder -- /usr/bin/npm config set proxy "$HTTP_PROXY"
fi
if [[ -n "$HTTPS_PROXY" ]]; then
/snap/bin/lxc exec builder -- /usr/bin/npm config set https-proxy "$HTTPS_PROXY"
fi
/usr/sbin/useradd -m ubuntu
/usr/bin/npm install --global yarn
/usr/sbin/groupadd microk8s
/usr/sbin/usermod -aG microk8s ubuntu
/usr/sbin/usermod -aG docker ubuntu
/usr/bin/chmod 777 /usr/local/bin
# Proxy configs
if [[ -n "$HTTP_PROXY" ]]; then
/usr/bin/npm config set proxy "$HTTP_PROXY"
fi
if [[ -n "$HTTPS_PROXY" ]]; then
/usr/bin/npm config set https-proxy "$HTTPS_PROXY"
fi
if [[ -n "$DOCKER_PROXY_SERVICE_CONF" ]]; then
mkdir -p /etc/systemd/system/docker.service.d
echo "$DOCKER_PROXY_SERVICE_CONF" > /etc/systemd/system/docker.service.d/http-proxy.conf
fi
if [[ -n "$DOCKER_PROXY_CONF" ]]; then
mkdir -p /root/.docker
echo "$DOCKER_PROXY_CONF" > /root/.docker/config.json
mkdir -p /home/ubuntu/.docker
echo "$DOCKER_PROXY_CONF" > /home/ubuntu/.docker/config.json
fi
# Reduce image size
/usr/bin/npm cache clean --force
Expand All @@ -121,8 +137,8 @@ DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get clean
/usr/bin/wget https://github.com/mikefarah/yq/releases/latest/download/extract-checksum.sh -O extract-checksum.sh
/usr/bin/bash extract-checksum.sh SHA-256 "yq_linux_$BIN_ARCH" | /usr/bin/awk '{print \$2,\$1}' | /usr/bin/sha256sum -c | /usr/bin/grep OK
rm checksums checksums_hashes_order extract-checksum.sh
chmod 755 yq_linux_$BIN_ARCH
mv yq_linux_$BIN_ARCH /usr/bin/yq
/usr/bin/chmod 755 yq_linux_$BIN_ARCH
/usr/bin/mv yq_linux_$BIN_ARCH /usr/bin/yq
# Download runner bin and verify checksum
mkdir -p /home/ubuntu/actions-runner && cd /home/ubuntu/actions-runner
Expand Down
13 changes: 8 additions & 5 deletions src-docs/openstack_manager.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Module for handling interactions with OpenStack.

---

<a href="../src/openstack_manager.py#L63"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/openstack_manager.py#L64"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `list_projects`

Expand All @@ -35,7 +35,7 @@ It currently returns objects directly from the sdk, which may not be ideal (mapp

---

<a href="../src/openstack_manager.py#L134"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/openstack_manager.py#L156"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `build_image`

Expand Down Expand Up @@ -74,7 +74,7 @@ Build and upload an image to OpenStack.

---

<a href="../src/openstack_manager.py#L181"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/openstack_manager.py#L203"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `create_instance_config`

Expand All @@ -101,14 +101,17 @@ Create an instance config from charm data.

---

<a href="../src/openstack_manager.py#L211"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/openstack_manager.py#L233"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `create_instance`

```python
create_instance(
cloud_config: dict[str, dict],
instance_config: InstanceConfig
instance_config: InstanceConfig,
proxies: Optional[ProxyConfig] = None,
dockerhub_mirror: Optional[str] = None,
ssh_debug_connections: list[SSHDebugConnection] | None = None
) → Server
```

Expand Down
3 changes: 3 additions & 0 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,9 @@ def _on_install(self, _event: InstallEvent) -> None:
instance = openstack_manager.create_instance(
cloud_config=state.charm_config.openstack_clouds_yaml,
instance_config=instance_config,
proxies=state.proxy_config,
dockerhub_mirror=state.charm_config.dockerhub_mirror,
ssh_debug_connections=state.ssh_debug_connections
)
logger.info("OpenStack instance: %s", instance)
# Test out openstack integration and then go
Expand Down
34 changes: 33 additions & 1 deletion src/openstack_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# See LICENSE file for licensing details.

"""Module for handling interactions with OpenStack."""
import json
import logging
import secrets
from dataclasses import dataclass
Expand All @@ -20,7 +21,7 @@
from openstack.exceptions import OpenStackCloudException
from openstack.identity.v3.project import Project

from charm_state import Arch, ProxyConfig
from charm_state import Arch, ProxyConfig, SSHDebugConnection
from errors import OpenStackUnauthorizedError, RunnerBinaryError
from github_client import GithubClient
from github_type import RunnerApplication
Expand Down Expand Up @@ -100,13 +101,34 @@ def _build_image_command(runner_info: RunnerApplication, proxies: ProxyConfig) -
https_proxy = proxies.https or ""
no_proxy = proxies.no_proxy or ""

environment = jinja2.Environment(loader=jinja2.FileSystemLoader("templates"), autoescape=True)
docker_proxy_service_conf_content = environment.get_template("systemd-docker-proxy.j2").render(
proxies=proxies
)
docker_proxy = {
"proxies": {
"default": {
key: value
for key, value in (
("httpProxy", proxies.http),
("httpsProxy", proxies.https),
("noProxy", proxies.no_proxy),
)
if value
}
}
}
docker_client_proxy_content = json.dumps(docker_proxy)

cmd = [
"/usr/bin/bash",
BUILD_OPENSTACK_IMAGE_SCRIPT_FILENAME,
runner_info["download_url"],
http_proxy,
https_proxy,
no_proxy,
docker_proxy_service_conf_content,
docker_client_proxy_content,
]

return cmd
Expand Down Expand Up @@ -211,6 +233,9 @@ class InstanceLaunchError(Exception):
def create_instance(
cloud_config: dict[str, dict],
instance_config: InstanceConfig,
proxies: Optional[ProxyConfig] = None,
dockerhub_mirror: Optional[str] = None,
ssh_debug_connections: list[SSHDebugConnection] | None = None,
) -> openstack.compute.v2.server.Server:
"""Create an OpenStack instance.
Expand All @@ -225,11 +250,18 @@ def create_instance(
The created server.
"""
environment = jinja2.Environment(loader=jinja2.FileSystemLoader("templates"), autoescape=True)
env_contents = environment.get_template("env.j2").render(
proxies=proxies,
pre_job_script="",
dockerhub_mirror=dockerhub_mirror,
ssh_debug_info=(secrets.choice(ssh_debug_connections) if ssh_debug_connections else None),
)
cloud_userdata = environment.get_template("openstack-userdata.sh.j2").render(
github_url=f"https://github.com/{instance_config.github_path.path()}",
token=instance_config.registration_token,
instance_labels=",".join(instance_config.labels),
instance_name=instance_config.name,
env_contents=env_contents,
)

try:
Expand Down
3 changes: 3 additions & 0 deletions templates/openstack-userdata.sh.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

set -e

# Write .env contents
su - ubuntu -c 'cd ~/actions-runner && echo "{{ env_contents }}" > .env'

# Create the runner and start the configuration experience
su - ubuntu -c "cd ~/actions-runner && ./config.sh \
--url {{ github_url }} \
Expand Down
87 changes: 57 additions & 30 deletions tests/integration/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
import typing
from asyncio import sleep
from datetime import datetime, timezone
from typing import Any, Awaitable, Callable, Union
from functools import partial
from typing import Any, Awaitable, Callable, ParamSpec, TypeVar, Union

import github
import juju.version
Expand Down Expand Up @@ -357,15 +358,15 @@ async def deploy_github_runner_charm(
storage["runner"] = {"pool": "rootfs", "size": 11}

default_config = {
"path": path,
"token": token,
"virtual-machines": 0,
"denylist": "10.10.0.0/16",
"test-mode": "insecure",
"reconcile-interval": reconcile_interval,
"runner-storage": runner_storage,
}
"path": path,
"token": token,
"virtual-machines": 0,
"denylist": "10.10.0.0/16",
"test-mode": "insecure",
"reconcile-interval": reconcile_interval,
"runner-storage": runner_storage,
}

if config:
default_config.update(config)

Expand Down Expand Up @@ -465,25 +466,35 @@ async def _assert_workflow_run_conclusion(
)


async def _wait_for_workflow_to_complete(
unit: Unit, workflow: Workflow, conclusion: str, start_time: datetime
):
"""Wait for the workflow to complete.
def _get_latest_run(
workflow: Workflow, start_time: datetime, branch: Branch | None = None
) -> WorkflowRun | None:
"""Get the latest run after start_time.
Args:
unit: The unit which contains the runner.
workflow: The workflow to wait for.
conclusion: The workflow conclusion to wait for.
start_time: The start time of the workflow.
workflow: The workflow to get the latest run for.
start_time: The minium start time of the run.
Returns:
The latest workflow run if the workflow has started. None otherwise.
"""
runner_name = await get_runner_name(unit)
await _wait_until_runner_is_used_up(runner_name, unit)
# Wait for the workflow log to contain the conclusion
await sleep(120)
try:
return workflow.get_runs(
branch=branch, created=f">={start_time.isoformat(timespec='seconds')}"
)[0]
except IndexError:
return None

await _assert_workflow_run_conclusion(
runner_name=runner_name, conclusion=conclusion, workflow=workflow, start_time=start_time
)

def _is_workflow_run_complete(run: WorkflowRun) -> bool:
"""Wait for the workflow status to turn to complete.
Args:
run: The workflow run to check status for.
"""
if run.update():
return run.status == "completed"
return False


async def dispatch_workflow(
Expand All @@ -492,6 +503,7 @@ async def dispatch_workflow(
github_repository: Repository,
conclusion: str,
workflow_id_or_name: str,
dispatch_input: dict | None = None,
):
"""Dispatch a workflow on a branch for the runner to run.
Expand All @@ -513,18 +525,33 @@ async def dispatch_workflow(
workflow = github_repository.get_workflow(id_or_file_name=workflow_id_or_name)

# The `create_dispatch` returns True on success.
assert workflow.create_dispatch(branch, {"runner": app.name})
await _wait_for_workflow_to_complete(
unit=app.units[0], workflow=workflow, conclusion=conclusion, start_time=start_time
assert workflow.create_dispatch(
branch, dispatch_input or {"runner": app.name}
), "Failed to create workflow"

# There is a very small chance of selecting a run not created by the dispatch above.
run = await wait_for(
partial(_get_latest_run, workflow=workflow, start_time=start_time, branch=branch)
)
await wait_for(partial(_is_workflow_run_complete, run=run), timeout=60 * 30, check_interval=60)

# The run object is updated by _is_workflow_run_complete function above.
assert (
run.conclusion == conclusion
), f"Unexpected run conclusion, expected: {conclusion}, got: {run.conclusion}"

return workflow


P = ParamSpec("P")
R = TypeVar("R")


async def wait_for(
func: Callable[[], Union[Awaitable, Any]],
func: Callable[P, R],
timeout: int = 300,
check_interval: int = 10,
) -> Any:
) -> R:
"""Wait for function execution to become truthy.
Args:
Expand Down
1 change: 1 addition & 0 deletions tests/integration/test_openstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ async def test_openstack_integration(
github_repository=github_repository,
conclusion="success",
workflow_id_or_name=DISPATCH_E2E_TEST_RUN_WORKFLOW_FILENAME,
dispatch_input={"runner-tag": app_openstack_runner.name},
)
# 1. the workflow run completes successfully.
workflow_run: WorkflowRun = workflow.get_runs()[0]
Expand Down

0 comments on commit fbc1e8e

Please sign in to comment.