mirror of
https://github.com/mudler/LocalAI.git
synced 2026-10-28 09:08:54 -04:00
The plan job of the add-on package workflow runs the platform matrix for every add-on. On a publishing event it fails closed when an add-on has no enabled platform. The Tables platform file listed three platforms, all with enabled: false, so every master push failed the plan and nothing was published or signed. Enable the same four platforms as the other first-party add-ons (linux/amd64, linux/arm64, darwin/arm64, darwin/amd64), each with its native runner and the executable-start check. The fail-closed check in the workflow is unchanged. The tests assumed Tables was disabled. The matrix spec now requires at least one enabled platform in the checked-in Tables file. The platform spec covers Tables like the other add-ons. The shell suite builds a disabled copy of the Tables file for the two fail-closed cases. Assisted-by: Claude:claude-sonnet-5-5 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
174 lines
11 KiB
Bash
Executable File
174 lines
11 KiB
Bash
Executable File
#!/bin/sh
|
|
# SPDX-License-Identifier: MIT
|
|
# Packaging checks of the first-party add-on OCI package. It prints one JSON
|
|
# line per case; scripts/addons/verify.sh compares the lines with the exact
|
|
# inventory of the "packaging" suite and rejects a skipped or missing case.
|
|
#
|
|
# 1. Go specs of pkg/addons/packaging: layout rules, rejected packages,
|
|
# event and channel policy, merge completeness, publication and alias
|
|
# promotion against an in-process registry, catalog template, and the
|
|
# workflow policy with one failing mutant per rule.
|
|
# 2. The installer contract: the package built by package.sh is installed
|
|
# by the real installer from a real local OCI distribution and its
|
|
# executable is started by the real supervisor.
|
|
# 3. Shell entry points: package.sh, channel.sh, the workflow policy
|
|
# command and static checks that nothing here pushes, tags or signs.
|
|
#
|
|
# Nothing here pushes, tags, signs or logs in: registries are loopback
|
|
# distributions inside the test process and signatures come from a fixture
|
|
# authority.
|
|
set -eu
|
|
root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
|
|
cd "$root"
|
|
cache="$root/.cache/addons"
|
|
mkdir -p "$cache/tmp" "$cache/logs"
|
|
export TMPDIR="$cache/tmp"
|
|
export PATH="$cache/bin:$PATH"
|
|
export GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off GOFLAGS=-mod=readonly
|
|
export GOPATH="$cache/go" GOMODCACHE="$cache/go/pkg/mod" GOCACHE="$cache/go-build"
|
|
work=$(mktemp -d "$cache/tmp/packaging.XXXXXX")
|
|
trap 'chmod -R u+rwx "$work" 2>/dev/null; rm -rf "$work"' EXIT HUP INT TERM
|
|
|
|
# The UI is a build output of this checkout; build it so the package holds
|
|
# the current files.
|
|
node addons/tables/ui/build.mjs >"$cache/logs/packaging-ui-build.log" 2>&1 || { cat "$cache/logs/packaging-ui-build.log" >&2; exit 1; }
|
|
|
|
go_cases() {
|
|
python3 -I - "$1" <<'PY'
|
|
import json, sys
|
|
for report in json.load(open(sys.argv[1])):
|
|
for spec in report['SpecReports']:
|
|
if spec['LeafNodeType'] != 'It' or spec['State'] == 'skipped' and not spec.get('Failure'):
|
|
continue
|
|
status = {'passed': 'passed'}.get(spec['State'], 'failed')
|
|
print(json.dumps({'case': 'go: ' + spec['LeafNodeText'], 'status': status}))
|
|
PY
|
|
}
|
|
run_go() {
|
|
name=$1
|
|
shift
|
|
report="$cache/logs/packaging-$name.json"
|
|
rm -f "$report"
|
|
go test -count=1 -p 2 -tags auth "$@" -ginkgo.json-report="$report" >"$cache/logs/packaging-$name.log" 2>&1 || { tail -60 "$cache/logs/packaging-$name.log" >&2; exit 1; }
|
|
go_cases "$report"
|
|
}
|
|
run_go specs ./pkg/addons/packaging
|
|
run_go installer ./tests/addons/packaging
|
|
|
|
# Shell entry points.
|
|
tool="$work/addonpkg"
|
|
go build -o "$tool" ./scripts/addons/cmd/addonpkg
|
|
export ADDONPKG="$tool"
|
|
case_ok() { printf '{"case":"%s","status":"passed"}\n' "$1"; }
|
|
expect_fail() { # name expected-text command...
|
|
name=$1; expected=$2; shift 2
|
|
if "$@" >"$work/out" 2>&1; then echo "FAIL $name: unexpectedly succeeded" >&2; cat "$work/out" >&2; exit 1; fi
|
|
grep -F -- "$expected" "$work/out" >/dev/null || { echo "FAIL $name: wrong failure (expected $expected)" >&2; cat "$work/out" >&2; exit 1; }
|
|
case_ok "$name"
|
|
}
|
|
sha=0123456789abcdef0123456789abcdef01234567
|
|
host="$(go env GOOS)/$(go env GOARCH)"
|
|
printf '#!/bin/sh\nexit 2\n' >"$work/stub"
|
|
chmod 755 "$work/stub"
|
|
|
|
# package.sh: a local check layout for the host builds and verifies.
|
|
sh scripts/addons/package.sh --layout "$work/leg" --version 1.2.3 --platform "$host" --executable "$work/stub" --local-check --revision "$sha" >"$work/out" 2>&1 || { cat "$work/out" >&2; exit 1; }
|
|
grep -F 'publishable=false' "$work/out" >/dev/null && grep -F 'verified=tables 1.2.3' "$work/out" >/dev/null
|
|
test -f "$work/leg/receipt.json" && test -f "$work/leg/index.json" && test -f "$work/leg/oci-layout"
|
|
case_ok 'package.sh builds and verifies a local check layout'
|
|
sed 's/enabled: true/enabled: false/' addons/tables/package/platforms.yaml >"$work/disabled.yaml"
|
|
expect_fail 'package.sh refuses a platform the matrix has not enabled' 'not enabled' sh scripts/addons/package.sh --config "$work/disabled.yaml" --layout "$work/leg2" --version 1.2.3 --platform "$host" --executable "$work/stub"
|
|
expect_fail 'package.sh refuses an existing non-empty layout' 'not empty' sh scripts/addons/package.sh --layout "$work/leg" --version 1.2.3 --platform "$host" --executable "$work/stub" --local-check
|
|
expect_fail 'package.sh requires its arguments' 'usage:' sh scripts/addons/package.sh --version 1.2.3
|
|
expect_fail 'package.sh refuses an invalid version' 'semantic version' sh scripts/addons/package.sh --layout "$work/leg3" --version v1 --platform "$host" --executable "$work/stub" --local-check
|
|
# package.sh reads the add-on from its platform file. The voice add-on is built
|
|
# for the host as a local check, and its layout verifies.
|
|
sh scripts/addons/package.sh --config addons/voice/package/platforms.yaml --layout "$work/voice" --version 1.2.3 --platform "$host" --local-check --revision "$sha" >"$work/out" 2>&1 || { cat "$work/out" >&2; exit 1; }
|
|
grep -F 'verified=voice 1.2.3' "$work/out" >/dev/null
|
|
test -f "$work/voice/receipt.json"
|
|
case_ok 'package.sh builds and verifies the voice add-on for the host'
|
|
# A platform other than the host is refused for every add-on, whatever the config.
|
|
if [ "$host" = linux/amd64 ]; then foreign=linux/arm64; else foreign=linux/amd64; fi
|
|
for id in tables voice face fine-tuning quantization studio talk; do
|
|
expect_fail "package.sh refuses a foreign platform without an executable: $id" 'is not this host' sh scripts/addons/package.sh --config "addons/$id/package/platforms.yaml" --layout "$work/foreign-$id" --version 1.2.3 --platform "$foreign" --local-check
|
|
done
|
|
case_ok 'package.sh refuses a foreign platform without an executable for every add-on'
|
|
# The build line: cgo for Tables, none for the pure Go add-ons. A stub go
|
|
# records the environment and the package it was asked to build.
|
|
realgo=$(command -v go)
|
|
mkdir "$work/stubbin"
|
|
cat >"$work/stubbin/go" <<STUB
|
|
#!/bin/sh
|
|
if [ "\$1" = build ]; then
|
|
out=
|
|
for a in "\$@"; do
|
|
if [ "\$prev" = -o ]; then out=\$a; fi
|
|
prev=\$a
|
|
last=\$a
|
|
done
|
|
printf '%s CGO_ENABLED=%s\n' "\$last" "\${CGO_ENABLED-unset}" >>"$work/gobuild.log"
|
|
printf '#!/bin/sh\nexit 2\n' >"\$out"
|
|
chmod 755 "\$out"
|
|
exit 0
|
|
fi
|
|
exec "$realgo" "\$@"
|
|
STUB
|
|
chmod 755 "$work/stubbin/go"
|
|
: >"$work/gobuild.log"
|
|
for id in tables voice face fine-tuning quantization studio talk; do
|
|
PATH="$work/stubbin:$PATH" sh scripts/addons/package.sh --config "addons/$id/package/platforms.yaml" --layout "$work/stub-$id" --version 1.2.3 --platform "$host" --local-check >"$work/out" 2>&1 || { cat "$work/out" >&2; exit 1; }
|
|
done
|
|
grep -x './addons/tables/cmd/tables CGO_ENABLED=1' "$work/gobuild.log" >/dev/null
|
|
for id in voice face fine-tuning quantization studio talk; do
|
|
grep -x "./addons/$id/cmd/$id CGO_ENABLED=0" "$work/gobuild.log" >/dev/null || { echo "FAIL: $id is not built with CGO_ENABLED=0" >&2; cat "$work/gobuild.log" >&2; exit 1; }
|
|
done
|
|
case_ok 'package.sh builds Tables with cgo and the pure Go add-ons without'
|
|
expect_fail 'matrix fails closed while no platform is enabled' 'no enabled platform' "$tool" matrix --config "$work/disabled.yaml"
|
|
expect_fail 'matrix fails closed without a file' 'platform matrix' "$tool" matrix --config "$work/absent.yaml"
|
|
|
|
# channel.sh.
|
|
channel() { sh scripts/addons/channel.sh "$@"; }
|
|
out=$(channel --event pull_request --ref refs/pull/7/merge --sha "$sha")
|
|
printf '%s\n' "$out" | grep -x 'publish=false' >/dev/null && printf '%s\n' "$out" | grep -x 'aliases=' >/dev/null
|
|
case_ok 'channel.sh: a pull request never publishes'
|
|
out=$(channel --event push --ref refs/heads/master --sha "$sha")
|
|
printf '%s\n' "$out" | grep -x 'aliases=development' >/dev/null && printf '%s\n' "$out" | grep -x 'immutable=sha-0123456789ab' >/dev/null
|
|
case_ok 'channel.sh: master publishes development'
|
|
out=$(channel --event push --ref refs/heads/latest --sha "$sha")
|
|
printf '%s\n' "$out" | grep -x 'publish=false' >/dev/null && printf '%s\n' "$out" | grep -x 'aliases=' >/dev/null && printf '%s\n' "$out" | grep -x 'channel=' >/dev/null
|
|
case_ok 'channel.sh: a branch push never publishes, even a branch named latest'
|
|
out=$(channel --event push --ref refs/tags/v1.2.3 --sha "$sha")
|
|
printf '%s\n' "$out" | grep -x 'aliases=' >/dev/null && printf '%s\n' "$out" | grep -x 'immutable=1.2.3' >/dev/null
|
|
case_ok 'channel.sh: a tag push prepares the version and moves no alias'
|
|
out=$(channel --event release --ref refs/tags/v1.2.3 --sha "$sha")
|
|
printf '%s\n' "$out" | grep -x 'aliases=latest' >/dev/null
|
|
case_ok 'channel.sh: a published stable release promotes latest'
|
|
out=$(channel --event release --ref refs/tags/v1.2.3 --sha "$sha" --prerelease)
|
|
printf '%s\n' "$out" | grep -x 'aliases=' >/dev/null
|
|
case_ok 'channel.sh: a pre-release never promotes latest'
|
|
expect_fail 'channel.sh: a draft release fails' 'draft' channel --event release --ref refs/tags/v1.2.3 --sha "$sha" --draft
|
|
expect_fail 'channel.sh: an unknown event fails' 'not allowed to publish' channel --event workflow_dispatch --ref refs/heads/master --sha "$sha"
|
|
expect_fail 'channel.sh: a tag that is not a version fails' 'release tag' channel --event push --ref refs/tags/latest --sha "$sha"
|
|
|
|
# Workflow policy.
|
|
"$tool" policy --workflow .github/workflows/addons-package.yml >"$work/out" 2>&1 || { cat "$work/out" >&2; exit 1; }
|
|
case_ok 'workflow policy accepts the checked-in workflow'
|
|
sed 's/^ release:$/ pull_request_target:\n types: [opened]\n release:/' .github/workflows/addons-package.yml >"$work/mutant.yml"
|
|
expect_fail 'workflow policy rejects pull_request_target' 'P_PR_TARGET' "$tool" policy --workflow "$work/mutant.yml"
|
|
sed 's/^ needs: \[plan, validate, package\]$/ needs: [plan, package]/' .github/workflows/addons-package.yml >"$work/mutant.yml"
|
|
expect_fail 'workflow policy rejects a publish job that does not wait for every leg' 'P_NEEDS' "$tool" policy --workflow "$work/mutant.yml"
|
|
sed 's/^ name: validate package rules$/ name: validate package rules\n continue-on-error: true/' .github/workflows/addons-package.yml >"$work/mutant.yml"
|
|
expect_fail 'workflow policy rejects a validate job that cannot stop the release' 'P_VALIDATE_GATE' "$tool" policy --workflow "$work/mutant.yml"
|
|
|
|
# Nothing in the packaging scripts pushes, tags, signs or logs in.
|
|
if grep -n -E 'docker push|docker login|git tag|git push|oras (push|copy)|cosign (sign|attest)|crane push|gh release' scripts/addons/package.sh scripts/addons/channel.sh scripts/addons/package_test.sh | grep -v '^[^:]*:[0-9]*:#' | grep -v 'grep -n -E' | grep -v 'static checks'; then
|
|
echo 'FAIL: a packaging script pushes, tags or signs' >&2
|
|
exit 1
|
|
fi
|
|
case_ok 'scripts never push, tag, sign or log in'
|
|
if grep -n -i -E 'quay\.io|go-skynet|backend_merge|backend_build|backend\.yml|ci-cache|docker\.io' .github/workflows/addons-package.yml scripts/addons/package.sh scripts/addons/channel.sh addons/*/package/platforms.yaml gallery/addons/tables.yaml.tmpl; then
|
|
echo 'FAIL: a packaging file references the backend pipeline' >&2
|
|
exit 1
|
|
fi
|
|
case_ok 'packaging files never reference backend registries or workflows'
|