diff --git a/.github/actions/with-post-steps/action.yaml b/.github/actions/with-post-steps/action.yaml index 2bc7cf1..dfd1bd7 100644 --- a/.github/actions/with-post-steps/action.yaml +++ b/.github/actions/with-post-steps/action.yaml @@ -10,6 +10,9 @@ inputs: post: description: "Post command/script." required: true + json: + description: "Inputs to pass through to the main command." + required: true key: description: "Name of the state variable used to detect the post step." required: false diff --git a/.github/actions/with-post-steps/main.js b/.github/actions/with-post-steps/main.js index 31d4988..654ede9 100644 --- a/.github/actions/with-post-steps/main.js +++ b/.github/actions/with-post-steps/main.js @@ -4,7 +4,7 @@ const { appendFileSync } = require("fs"); const { EOL } = require("os"); function run(cmd) { - const subprocess = spawn(cmd, { stdio: "inherit", shell: true }); + const subprocess = spawn(cmd, { stdio: "inherit", shell: true, env: process.env }); subprocess.on("exit", (exitCode) => { process.exitCode = exitCode; }); diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index e437bc8..4f37a2e 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -1,9 +1,9 @@ name: Test Operator Setup on: push: - branches: [main] + branches: [main, concierge] pull_request: - branches: [main] + branches: [main, concierge] jobs: Inclusive-naming-check: @@ -34,6 +34,7 @@ jobs: runs-on: ubuntu-latest needs: [charmcraft] strategy: + fail-fast: false matrix: python: - '3.8' @@ -67,17 +68,20 @@ jobs: needs: [charmcraft] runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: + lxd: + - '5.21' + - '6.1' + - 'latest' os: - ubuntu-20.04 - ubuntu-22.04 - ubuntu-24.04 juju: - '2.9/stable' - - '3.1/stable' - - '3.3/stable' - - '3.4/stable' - - '3.5/edge' + - '3.5/stable' + - '3/stable' steps: - name: Check out code uses: actions/checkout@v4 @@ -89,7 +93,7 @@ jobs: - uses: ./ with: provider: lxd - channel: latest/stable + channel: ${{ matrix.lxd }}/stable juju-channel: ${{ matrix.juju }} charmcraft-channel: ${{ needs.charmcraft.outputs.channel }} @@ -110,6 +114,7 @@ jobs: needs: [charmcraft] runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: - ubuntu-20.04 @@ -141,6 +146,7 @@ jobs: needs: [charmcraft] runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: - ubuntu-20.04 @@ -226,6 +232,7 @@ jobs: needs: [charmcraft] runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: - ubuntu-20.04 @@ -259,5 +266,6 @@ jobs: run: echo "name=$CONTROLLER_NAME" >> $GITHUB_OUTPUT - name: Test we can switch to the controllers run: | + set -eu juju switch ${{ steps.k8s-controller.outputs.name }} juju switch ${{ steps.lxd-controller.outputs.name }} diff --git a/action.yaml b/action.yaml index 16548ff..371c4a5 100644 --- a/action.yaml +++ b/action.yaml @@ -2,6 +2,12 @@ name: "Setup Operator environment" description: "Setup a operator environment" author: "Adam Stokes" inputs: + concierge-channel: + description: | + Channel of concierge to install + https://snapcraft.io/concierge + required: false + default: "latest/stable" provider: description: | Which Juju provider to use. Can be "lxd", "microk8s" or "microstack", @@ -79,6 +85,7 @@ runs: - name: Cache uses: ./.github/actions/with-post-steps with: + json: ${{ toJson(inputs) }} main: "src/bootstrap/main.sh" post: "src/cleanup/main.sh" branding: diff --git a/src/bootstrap/main.sh b/src/bootstrap/main.sh index 3dd0f96..ed67d1b 100755 --- a/src/bootstrap/main.sh +++ b/src/bootstrap/main.sh @@ -1,6 +1,153 @@ #!/bin/bash +set -eu -# The following will eventually just be snap install concierge -sudo snap install go --classic -go install github.com/jnsgruk/concierge@latest +function _get_input() { + local __resultvar=$1 + local key=$2 + local default_value=${3:-''} + local value=$(echo "$INPUT_JSON" | grep -oP '"'"$key"'"\s*:\s*"\K[^"]+') + value=${value:-$default_value} + eval $__resultvar="'$value'" +} +# waitSnapdSeed: wait for snapd to be seeded. +# Optional argument: timeout in seconds, defaults to 60. +function wait_snapd_seed() ( + echo "::group::Waiting for snapd seed..." + waitSecs="${1:-60}" + timeout "${waitSecs}" sudo snap wait system seed.loaded + echo "::endgroup::" +) + +function prepare_lxd(){ + echo "::group::Preparing lxd..." + local lxd_channel="" + _get_input lxd_channel "lxd-channel" + + local current_lxd_channel="$(snap list lxd | grep lxd | awk '{ print $4 }' || true)" + if [ "${current_lxd_channel}" != "${lxd_channel}" ]; then + # rather than purge, stop lxd if it's running + echo "Stopping lxd before refreshing to $lxd_channel with concierge..." + sudo snap stop lxd || true + else + echo "lxd is already at the desired channel: $lxd_channel" + fi + echo "::endgroup::" +} + +function install_concierge() { + # The following will eventually just be snap install concierge + local concierge_channel="" + _get_input concierge_version "concierge-channel" "latest/stable" + + echo "::group::Installing concierge ${concierge_channel}..." + sudo snap install concierge --channel="${concierge_channel}" --classic + echo "::endgroup::" +} + +function install_tox_if_needed() { + local version="" + _get_input version "tox-version" + echo "::group::Installing tox..." + + if command -v tox &> /dev/null; then + echo "Tox is already installed" + tox --version + elif command -v pip &> /dev/null; then + echo "Installing tox with pip..." + TOX_VERSION_ARG=$([ -n "$version" ] && echo "==$version" || echo "") + pip install tox$TOX_VERSION_ARG + echo "::endgroup::" + else + echo "Installing tox with apt..." + sudo apt-get update + sudo apt-get install python3-tox + fi + echo "::endgroup::" +} + +function plan_concierge() { + local provider="" + local channel="" + local lxd_channel="" + local charm_channel="" + local charmcraft_channel="" + local juju_channel="" + local jq_channel="" + local juju_bundle_channel="" + local juju_crashdump_channel="" + local microk8s_addons="" + + _get_input provider "provider" + _get_input channel "channel" + _get_input lxd_channel "lxd-channel" + _get_input charm_channel "charm-channel" + _get_input charmcraft_channel "charmcraft-channel" + _get_input juju_channel "juju-channel" + _get_input jq_channel "jq-channel" "latest/stable" + _get_input juju_bundle_channel "juju-bundle-channel" "latest/stable" + _get_input juju_crashdump_channel "juju-crashdump-channel" "latest/stable" + _get_input microk8s_addons "microk8s-addons" + if [ ${provider} == "lxd" ]; then lxd_channel=${channel:-$lxd_channel}; fi + + cat < concierge.yaml +juju: + channel: ${juju_channel} + model-defaults: + test-mode: true + automatically-retry-hooks: false + logging-config: "=DEBUG" + +providers: + lxd: + enable: true + bootstrap: $( [ "${provider}" == "lxd" ] && echo "true" || echo "false" ) + channel: ${lxd_channel} +EOF + if [ ${provider} == "microk8s" ]; then + # Convert space-separated list to JSON array + microk8s_addons_json=$(echo "$microk8s_addons" | awk '{printf "["; for(i=1;i<=NF;i++) printf "\"%s\"%s", $i, (i> concierge.yaml + microk8s: + enable: true + bootstrap: true + channel: ${channel} + addons: ${microk8s_addons_json} +EOF + fi + cat <> concierge.yaml +host: + snaps: + - charm/${charm_channel} + - charmcraft/${charmcraft_channel} + - jq/${jq_channel} + - juju-bundle/${juju_bundle_channel} + - juju-crashdump/${juju_crashdump_channel} + - kubectl +EOF + + echo "::group::Concierge (concierge.yaml):" + cat concierge.yaml + echo "::endgroup::" +} + +function prepare_concierge() { + echo "::group::Running concierge..." + sudo -E concierge prepare --trace -v + echo "Concierge run complete." + + local CONTROLLER_NAME=$(juju controllers --format json | jq -r '.["current-controller"]') + echo "CONTROLLER_NAME=${CONTROLLER_NAME}" | tee -a "${GITHUB_ENV}" "${GITHUB_STATE}" + echo "::endgroup::" +} + +function run() { + wait_snapd_seed + prepare_lxd + install_concierge + install_tox_if_needed + plan_concierge + prepare_concierge +} + +run \ No newline at end of file diff --git a/src/cleanup/main.sh b/src/cleanup/main.sh index a9bf588..8728c37 100755 --- a/src/cleanup/main.sh +++ b/src/cleanup/main.sh @@ -1 +1,66 @@ #!/bin/bash +set -eu + +function _get_input() { + local __resultvar=$1 + local key=$2 + local default_value=${3:-''} + local value=$(echo "$INPUT_JSON" | grep -oP '"'"$key"'"\s*:\s*"\K[^"]+') + value=${value:-$default_value} + eval $__resultvar="'$value'" +} + +function destroy_controller() { + local controller=$1 + local juju_channel=""; + _get_input juju_channel "juju-channel" + + echo "Removing controller ${controller}..." + if [[ "${juju_channel}" == 2.9* ]]; then + juju destroy-controller -y ${controller} --destroy-all-models --destroy-storage + else + juju destroy-controller ${controller} --no-prompt --destroy-all-models --destroy-storage + fi +} + +function find_and_upload_juju_crashdump() { + local crashdump_files=$(find . -name "juju-crashdump-*.tar.xz") + + if [ -z "${crashdump_files}" ]; then + echo "No juju-crashdump files found." + else + for file in ${crashdump_files}; do + echo "Found crashdump file: ${file}" + upload_artifact $file + done + fi +} + +function upload_artifact() { + local file=$1 + local artifact_name=$(basename $file) + + echo "Uploading artifact not yet implemented: ${artifact_name}" + echo "::set-output name=artifact::${artifact_name}" + echo "::set-output name=artifact_path::${file}" +} + +function run() { + local controller=${STATE_CONTROLLER_NAME:-""} + local provider="" + _get_input provider "provider" + + echo "Cleaning up ${controller} on ${provider}..." + + if [ -n "${controller}" ]; then + if [ "${provider}" != "microk8s" ] && [ "${provider}" != "lxd" ]; then + destroy_controller ${controller} + fi + + echo "::group::uploading juju-crashdump" + find_and_upload_juju_crashdump + echo "::endgroup::" + fi +} + +run \ No newline at end of file diff --git a/tests/test_addons.py b/tests/test_addons.py index 28fdd69..852afa1 100644 --- a/tests/test_addons.py +++ b/tests/test_addons.py @@ -24,7 +24,7 @@ async def test_addons(addons: str): result = run( ["sudo", "microk8s", "status", "--format", "yaml"], capture_output=True ) + assert result.returncode == 0, f"microk8s status failed with {result}" status = yaml.safe_load(result.stdout) - assert status is not None, f"microk8s status = {result}" - status = status["addons"] - verify_enabled(status, addons) + assert "addons" in status, f"addons not in microk8s status: {status}" + verify_enabled(status["addons"], addons)