-
Notifications
You must be signed in to change notification settings - Fork 0
214 lines (191 loc) · 8.49 KB
/
Copy pathci.yml
File metadata and controls
214 lines (191 loc) · 8.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
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