Skip to content

Assemble a fabric from input objects, and migrate them by asking Ansible #71

Assemble a fabric from input objects, and migrate them by asking Ansible

Assemble a fabric from input objects, and migrate them by asking Ansible #71

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: offline suite
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
with:
# The AVD examples and their golden structured configs -- what the
# suite diffs against -- live in the submodule, so without it every
# fidelity test collects nothing.
submodules: true
fetch-depth: 1
# Full version, not @v9: setup-uv publishes floating major aliases only up
# to v7, so @v9 does not resolve. Renovate tracks the exact tag instead.
- uses: astral-sh/setup-uv@v9.0.0
with:
enable-cache: true
# --locked fails if uv.lock drifts from pyproject.toml, so a dependency
# bump can't land without its lock.
- run: uv sync --locked
- run: uv run pytest -q
# `xpkg build` is not a lint: it parses every YAML under --package-root and
# rejects anything that is not a valid manifest of a kind the Configuration
# scheme knows. So a green build here is a real statement about both XRDs,
# both Compositions and all six examples.
#
# No --ignore: the dev-only Function manifests live in `dev/`, outside this
# package root, precisely so this build needs no exclusions. Putting a
# `Function` back under apis/ fails with "no kind Function is registered".
configuration:
name: configuration package
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Install crossplane CLI
run: |
set -euo pipefail
# renovate: datasource=github-releases depName=crossplane/crossplane
curl -sfL "https://cli.crossplane.io/install.sh" | XP_VERSION=v2.4.0 sh
sudo mv ./crossplane /usr/local/bin/crossplane
- name: Build the Configuration package
run: |
set -euo pipefail
crossplane xpkg build \
--package-root=apis \
--examples-root=examples/fabric \
-o configuration-avd-ci.xpkg
# The Function package's own build, minus the runtime image -- embedding
# needs a built tarball, and nothing here depends on it to answer the
# question this step asks: does `package/` plus the examples still parse?
#
# Added after the v0.1.4 release failed on exactly this. `--examples-root`
# defaults to ./examples, so this build collected a helm values file that
# had just landed under examples/ and rejected it -- a failure reachable
# only from the release, i.e. after the tag. The configuration build above
# could not have caught it: it names a narrower examples root, so the two
# builds disagreed about what "the examples" are.
- name: Build the Function package
run: |
set -euo pipefail
crossplane xpkg build \
--package-root=package \
--examples-root=examples/fabric \
-o function-avd-ci.xpkg
# The offline suite runs on the runner's interpreter, so it says nothing about
# the image the xpkg actually embeds. Without this job a Dockerfile, uv or
# Python bump is only exercised by the release build -- i.e. after the tag,
# when the cheap moment to react has passed.
image:
name: runtime image
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
with:
# No submodules: .dockerignore keeps avd/ out of the image anyway.
fetch-depth: 1
- uses: docker/setup-buildx-action@v4
# amd64 only. The release also builds arm64, but that leg is emulated and
# slow, and a Dockerfile broken enough to matter breaks on both.
- name: Build the runtime image
uses: docker/build-push-action@v7
with:
context: .
load: true
tags: function-avd-runtime:ci
# xpkg wants a plain image; kind-up.sh passes --provenance=false too.
provenance: false
# Its own cache scope: a PR build must not evict the release's, which
# is the one that has to carry an emulated arm64 build.
cache-from: type=gha,scope=ci-amd64
cache-to: type=gha,mode=max,scope=ci-amd64
# A build that succeeds is the weaker claim. An image can install cleanly
# and still fail to import pyavd or to serve, and neither shows up until
# something pulls the published package.
- name: Smoke-test the image
run: |
set -euo pipefail
docker run --rm -i --entrypoint /app/.venv/bin/python function-avd-runtime:ci - <<'PY'
import sys, pyavd
from function import fn
print("python", sys.version.split()[0], "/ pyavd", pyavd.__version__)
assert hasattr(fn, "FunctionRunner"), "function package did not import"
# Exercise the schema machinery, not just the import: a bad interpreter
# or a half-installed dependency surfaces here, not at import time.
pyavd.validate_inputs({"hostname": "smoke", "fabric_name": "smoke"})
print("pyavd validate_inputs OK")
PY
# And that the entrypoint actually serves, which is all Crossplane
# ever asks of this image.
docker run -d --name smoke function-avd-runtime:ci --insecure >/dev/null
for _ in $(seq 30); do
if docker exec smoke /app/.venv/bin/python \
-c "import socket; socket.create_connection(('127.0.0.1', 9443), 1).close()" 2>/dev/null; then
served=1; break
fi
sleep 1
done
docker logs smoke
docker rm -f smoke >/dev/null
if [ "${served:-0}" != 1 ]; then
echo "::error::the entrypoint never accepted a connection on 9443"
exit 1
fi
echo "entrypoint served on 9443"
e2e:
name: e2e on kind
# Builds the function image and installs Crossplane, so it is far too heavy
# to gate a PR: run it by hand from the Actions tab when the function,
# the compositions, or the XRDs change.
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
with:
submodules: true
fetch-depth: 1
- uses: astral-sh/setup-uv@v9.0.0
with:
enable-cache: true
- run: uv sync --locked
- name: Install kind + crossplane CLI
run: |
set -euo pipefail
# Matches the version the e2e scenario was proven against locally.
# renovate: datasource=github-releases depName=kubernetes-sigs/kind
curl -sSLo ./kind https://kind.sigs.k8s.io/dl/v0.32.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
# XP_VERSION pins the CLI: install.sh otherwise takes whatever is
# current, so this job would drift onto a different CLI with no commit.
# renovate: datasource=github-releases depName=crossplane/crossplane
curl -sfL "https://cli.crossplane.io/install.sh" | XP_VERSION=v2.4.0 sh
sudo mv ./crossplane /usr/local/bin/crossplane
# kind-up.sh needs helm; the runner image ships it, but don't rely on that.
command -v helm >/dev/null || \
curl -sSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
kind --version
# --client only: plain `crossplane version` talks to the cluster, which
# does not exist yet, and would fail this step under `set -e`.
crossplane version --client
- name: Bring up the cluster
run: scripts/kind-up.sh
- name: Apply a fabric
run: |
kubectl --context kind-avd apply -f examples/fabric/single-dc-l3ls.yaml
kubectl --context kind-avd -n default wait --for=condition=Ready \
fabric/single-dc-l3ls --timeout=300s
# -s: let the [phase] timings through. pytest swallows stdout on a pass,
# which is exactly the run where you want to see how much headroom is left.
- run: uv run pytest -m e2e -v -s
- name: Dump state on failure
if: failure()
run: |
kubectl --context kind-avd -n default get fabric,device,cm -o wide || true
kubectl --context kind-avd -n default describe device || true
kubectl --context kind-avd -n crossplane-system logs -l app=crossplane --tail=200 || true