diff --git a/.coreci.yml b/.coreci.yml index 887dec6..2030c3d 100644 --- a/.coreci.yml +++ b/.coreci.yml @@ -23,25 +23,25 @@ pipelines: description: Validate Go toolchain, formatting, and security scans steps: - name: go-version - image: golang:1.25 + image: golang:1.25.12 commands: - go version - gofmt -l . - go vet ./... - name: verify-reqs - image: golang:1.25 + image: golang:1.25.12 commands: - make verify-reqs - name: gosec - image: golang:1.25 + image: golang:1.25.12 commands: - go install github.com/securego/gosec/v2/cmd/gosec@v2.18.2 - gosec -fmt text -quiet ./... - name: govulncheck - image: golang:1.25 + image: golang:1.25.12 env: # REQ-027: offline mode. GOFLAGS=-mod=mod ensures module mode; # GOVULNCHECK_DB (when present) overrides the bundled DB. @@ -51,7 +51,7 @@ pipelines: - govulncheck -mode binary ./... - name: gitleaks - image: golang:1.25 + image: golang:1.25.12 commands: - apk add --no-cache curl - sh -c "$(curl -fsSL https://github.com/gitleaks/gitleaks/releases/latest/download/install.sh)" @@ -61,7 +61,7 @@ pipelines: description: Build the orca binary with version injection steps: - name: build - image: golang:1.25 + image: golang:1.25.12 env: VERSION: ${CI_COMMIT_TAG:-dev} GIT_COMMIT: ${CI_COMMIT_SHA} @@ -80,7 +80,7 @@ pipelines: description: Run all tests with race detection and coverage (REQ-031) steps: - name: test - image: golang:1.25 + image: golang:1.25.12 commands: - go test -race -coverprofile=coverage.out ./... - go tool cover -func=coverage.out | tail -1 @@ -91,7 +91,7 @@ pipelines: ref: "refs/tags/v*" steps: - name: build-artifact - image: golang:1.25 + image: golang:1.25.12 env: VERSION: ${CI_COMMIT_TAG} GIT_COMMIT: ${CI_COMMIT_SHA} @@ -105,20 +105,48 @@ pipelines: go build -trimpath -ldflags="${LDFLAGS}" -o bin/orca ./cmd/orca - make changelog - tar -czf orca-${VERSION}-linux-amd64.tar.gz -C bin orca - - ls -lh orca-${VERSION}-linux-amd64.tar.gz + - sha256sum orca-${VERSION}-linux-amd64.tar.gz > SHA256SUMS + - ls -lh orca-${VERSION}-linux-amd64.tar.gz SHA256SUMS + - cat SHA256SUMS - name: gitea-release - image: golang:1.25 + image: golang:1.25.12 env: GITEA_TOKEN: ${GITEA_TOKEN} VERSION: ${CI_COMMIT_TAG} commands: - - apk add --no-cache curl tar + - apk add --no-cache curl tar python3 - sh -c "$(curl -fsSL https://gitea.com/gitea/tea/releases/latest/download/install.sh)" - tea releases create ${VERSION} --repo coreci/orca --title "Orca ${VERSION}" --note-file CHANGELOG.md --asset orca-${VERSION}-linux-amd64.tar.gz + --asset SHA256SUMS + - | + # Verify assets are actually attached (REQ-097, gate C-21). + # tea releases create has been observed to exit 0 without + # attaching the asset in some versions. Verify via the API. + ASSET_COUNT=$(curl -fsSL \ + "https://git.cloudinit.dev/api/v1/repos/coreci/orca/releases/tags/${VERSION}" \ + | python3 -c "import json,sys; r=json.load(sys.stdin); print(len(r.get('assets',[])))") + echo "Release ${VERSION} has ${ASSET_COUNT} assets" + if [ "${ASSET_COUNT}" -lt 2 ]; then + echo "ERROR: Expected at least 2 assets (tarball + SHA256SUMS), got ${ASSET_COUNT}" + echo "Attempting to attach assets manually..." + TARBALL_URL=$(curl -fsSL \ + "https://git.cloudinit.dev/api/v1/repos/coreci/orca/releases/tags/${VERSION}" \ + | python3 -c "import json,sys; r=json.load(sys.stdin); print(r.get('id',''))") + if [ -n "${TARBALL_URL}" ]; then + curl -fsSL -X "POST" \ + "https://git.cloudinit.dev/api/v1/repos/coreci/orca/releases/${TARBALL_URL}/assets?name=orca-${VERSION}-linux-amd64.tar.gz" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -F "attachment=@orca-${VERSION}-linux-amd64.tar.gz" + curl -fsSL -X "POST" \ + "https://git.cloudinit.dev/api/v1/repos/coreci/orca/releases/${TARBALL_URL}/assets?name=SHA256SUMS" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -F "attachment=@SHA256SUMS" + fi + fi - name: container-publish description: Build and publish OCI image to Gitea container registry (REQ-046) image: docker:24-cli diff --git a/Dockerfile b/Dockerfile index 5a0119f..55184fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ ARG BUILD_TIME=unknown # --- Stage 1: build ------------------------------------------------------- -FROM golang:1.25 AS builder +FROM golang:1.25.12 AS builder ARG VERSION ARG GIT_COMMIT diff --git a/SHA256SUMS b/SHA256SUMS new file mode 100644 index 0000000..f2f38be --- /dev/null +++ b/SHA256SUMS @@ -0,0 +1 @@ +9fa21a416801ef88e52d8f07b5406752f56a3f07ca8d758e445ba5f3a2fce055 orca-v0.12.18-linux-amd64.tar.gz diff --git a/scripts/install.sh b/scripts/install.sh index 3f90295..26e2817 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -111,34 +111,57 @@ TARBALL="orca-${VERSION}-${OS}-${ARCH}.tar.gz" find_asset_url() { # $1 = tag. Prints the browser_download_url for the matching tarball, or empty. - # The `|| true` prevents set -e + pipefail from exiting the script when - # grep finds no match (exit 1) — an empty result is a valid outcome. + # Match by asset NAME (not URL path) — Gitea attachment URLs are opaque + # UUIDs that don't contain the tarball name. local tag="$1" - curl -fsSL "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/tags/${tag}" \ - | sed -n 's/.*"browser_download_url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \ - | grep "/${TARBALL}\$" \ - | head -1 || true + local json + json="$(curl -fsSL "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/tags/${tag}" 2>/dev/null)" || return 1 + if command -v python3 >/dev/null 2>&1; then + echo "$json" | python3 -c " +import json,sys +r=json.load(sys.stdin) +for a in r.get('assets',[]): + if a.get('name')=='$TARBALL': + print(a.get('browser_download_url','')) + break +" 2>/dev/null || true + else + echo "$json" | sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"$TARBALL".*"browser_download_url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1 || true + fi +} + +find_asset_in_releases() { + # Walk recent releases, find the newest with a matching asset name. + # Outputs two lines: URL and VERSION (caller captures both). + local json + json="$(curl -fsSL "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases?limit=50" 2>/dev/null)" || return 1 + if command -v python3 >/dev/null 2>&1; then + echo "$json" | python3 -c " +import json,sys,re +rels=json.load(sys.stdin) +for r in rels: + tag=r.get('tag_name','') + for a in r.get('assets',[]): + name=a.get('name','') + m=re.match(r'orca-(v[0-9.]+)-' + '${OS}' + '-' + '${ARCH}' + r'\.tar\.gz$', name) + if m: + print(a.get('browser_download_url','')) + print(m.group(1)) + sys.exit(0) +" + fi } info "locating asset ${TARBALL} in release ${VERSION}..." -ASSET_URL="$(find_asset_url "$VERSION")" +ASSET_URL="$(find_asset_url "$VERSION" 2>/dev/null)" || true if [ -z "$ASSET_URL" ]; then info "WARNING: release ${VERSION} has no ${TARBALL} asset. Walking back through recent releases..." - # The /releases list endpoint returns assets inline (browser_download_url - # appears within each release's assets array). Extract all download URLs - # from the list response and find the first (newest) one matching our - # OS+arch tarball pattern (any version). This avoids per-release API calls. - ASSET_URL="$(curl -fsSL "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases?limit=50" \ - | grep -oE '"browser_download_url"[[:space:]]*:[[:space:]]*"[^"]*"' \ - | sed -n 's/.*"browser_download_url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \ - | grep -E "/orca-[^/]*-${OS}-${ARCH}\.tar\.gz$" \ - | head -1 || true)" + FALLBACK_OUT="$(find_asset_in_releases || true)" + ASSET_URL="$(echo "$FALLBACK_OUT" | head -1)" if [ -n "$ASSET_URL" ]; then - # Extract the version from the URL (e.g. .../download/v0.4.5/orca-...) - FALLBACK_VERSION="$(echo "$ASSET_URL" | sed -n 's|.*/download/\([^/]*\)/.*|\1|p')" - info "WARNING: latest release ${VERSION} has no binary asset; falling back to ${FALLBACK_VERSION} which has orca-${FALLBACK_VERSION}-${OS}-${ARCH}.tar.gz." - VERSION="$FALLBACK_VERSION" + VERSION="$(echo "$FALLBACK_OUT" | tail -1)" + info "WARNING: falling back to ${VERSION} which has orca-${VERSION}-${OS}-${ARCH}.tar.gz." else err "could not find any release with a ${OS}-${ARCH} tarball in the last 50 releases. Check that a release exists with a linux-${ARCH} binary." fi diff --git a/scripts/release.sh b/scripts/release.sh index 59ee27b..86ad1af 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -101,6 +101,9 @@ info "built: bin/orca (linux-amd64)" TARBALL="orca-${VERSION}-linux-amd64.tar.gz" tar -czf "$TARBALL" -C bin orca +sha256sum "$TARBALL" > SHA256SUMS +info "checksum: SHA256SUMS" +cat SHA256SUMS info "packaged: $TARBALL ($(du -h "$TARBALL" | cut -f1))" # --- release notes from ---ci--- blocks ----------------------------------