up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import crypto from 'node:crypto';
|
||||
import yaml from 'yaml';
|
||||
|
||||
const ROOT = path.resolve('src/Api/StellaOps.Api.OpenApi');
|
||||
const BASELINE = path.join(ROOT, 'baselines', 'stella-baseline.yaml');
|
||||
const CURRENT = path.join(ROOT, 'stella.yaml');
|
||||
const OUTPUT = path.join(ROOT, 'CHANGELOG.md');
|
||||
const RELEASE_OUT = path.resolve('src/Sdk/StellaOps.Sdk.Release/out/api-changelog');
|
||||
|
||||
function panic(message) {
|
||||
console.error(`[api:changelog] ${message}`);
|
||||
@@ -76,6 +78,25 @@ function renderMarkdown(diff) {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function ensureReleaseDir() {
|
||||
fs.mkdirSync(RELEASE_OUT, { recursive: true });
|
||||
}
|
||||
|
||||
function sha256(content) {
|
||||
return crypto.createHash('sha256').update(content).digest('hex');
|
||||
}
|
||||
|
||||
function signDigest(digest) {
|
||||
const key = process.env.API_CHANGELOG_SIGNING_KEY;
|
||||
if (!key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hmac = crypto.createHmac('sha256', Buffer.from(key, 'utf8'));
|
||||
hmac.update(digest);
|
||||
return hmac.digest('hex');
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (!fs.existsSync(BASELINE)) {
|
||||
console.log('[api:changelog] baseline missing; skipping');
|
||||
@@ -85,6 +106,24 @@ function main() {
|
||||
const markdown = renderMarkdown(diff);
|
||||
fs.writeFileSync(OUTPUT, markdown, 'utf8');
|
||||
console.log(`[api:changelog] wrote changelog to ${OUTPUT}`);
|
||||
|
||||
ensureReleaseDir();
|
||||
const releaseChangelog = path.join(RELEASE_OUT, 'CHANGELOG.md');
|
||||
fs.writeFileSync(releaseChangelog, markdown, 'utf8');
|
||||
|
||||
const digest = sha256(markdown);
|
||||
const digestFile = path.join(RELEASE_OUT, 'CHANGELOG.sha256');
|
||||
fs.writeFileSync(digestFile, `${digest} CHANGELOG.md\n`, 'utf8');
|
||||
|
||||
const signature = signDigest(digest);
|
||||
if (signature) {
|
||||
fs.writeFileSync(path.join(RELEASE_OUT, 'CHANGELOG.sig'), signature, 'utf8');
|
||||
console.log('[api:changelog] wrote signature for release artifact');
|
||||
} else {
|
||||
console.log('[api:changelog] signature skipped (API_CHANGELOG_SIGNING_KEY not set)');
|
||||
}
|
||||
|
||||
console.log(`[api:changelog] copied changelog + digest to ${RELEASE_OUT}`);
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
63
scripts/attest/build-attestation-bundle.sh
Normal file
63
scripts/attest/build-attestation-bundle.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# DEVOPS-ATTEST-74-002: package attestation outputs into an offline bundle with checksums.
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: $0 <attest-dir> [bundle-out]" >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
ATTEST_DIR=$1
|
||||
BUNDLE_OUT=${2:-"out/attest-bundles"}
|
||||
|
||||
if [[ ! -d "$ATTEST_DIR" ]]; then
|
||||
echo "[attest-bundle] attestation directory not found: $ATTEST_DIR" >&2
|
||||
exit 66
|
||||
fi
|
||||
|
||||
mkdir -p "$BUNDLE_OUT"
|
||||
|
||||
TS=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
BUNDLE_NAME="attestation-bundle-${TS}"
|
||||
WORK_DIR="${BUNDLE_OUT}/${BUNDLE_NAME}"
|
||||
mkdir -p "$WORK_DIR"
|
||||
|
||||
copy_if_exists() {
|
||||
local pattern="$1"
|
||||
shopt -s nullglob
|
||||
local files=("$ATTEST_DIR"/$pattern)
|
||||
if (( ${#files[@]} > 0 )); then
|
||||
cp "${files[@]}" "$WORK_DIR/"
|
||||
fi
|
||||
shopt -u nullglob
|
||||
}
|
||||
|
||||
# Collect common attestation artefacts
|
||||
copy_if_exists "*.dsse.json"
|
||||
copy_if_exists "*.in-toto.jsonl"
|
||||
copy_if_exists "*.sarif"
|
||||
copy_if_exists "*.intoto.json"
|
||||
copy_if_exists "*.rekor.txt"
|
||||
copy_if_exists "*.sig"
|
||||
copy_if_exists "*.crt"
|
||||
copy_if_exists "*.pem"
|
||||
copy_if_exists "*.json"
|
||||
|
||||
# Manifest
|
||||
cat > "${WORK_DIR}/manifest.json" <<EOF
|
||||
{
|
||||
"created_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
||||
"source_dir": "${ATTEST_DIR}",
|
||||
"files": $(ls -1 "${WORK_DIR}" | jq -R . | jq -s .)
|
||||
}
|
||||
EOF
|
||||
|
||||
# Checksums
|
||||
(
|
||||
cd "$WORK_DIR"
|
||||
sha256sum * > SHA256SUMS
|
||||
)
|
||||
|
||||
tar -C "$BUNDLE_OUT" -czf "${WORK_DIR}.tgz" "${BUNDLE_NAME}"
|
||||
echo "[attest-bundle] bundle created at ${WORK_DIR}.tgz"
|
||||
43
scripts/buildx/build-airgap-bundle.sh
Normal file
43
scripts/buildx/build-airgap-bundle.sh
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# DEVOPS-CONTAINERS-46-001: build air-gap bundle from existing buildx OCI archive
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: $0 <image-tag> [bundle-dir]" >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
IMAGE_TAG=$1
|
||||
BUNDLE_DIR=${2:-"out/bundles/$(echo "$IMAGE_TAG" | tr '/:' '__')"}
|
||||
SRC_DIR="out/buildx/$(echo "$IMAGE_TAG" | tr '/:' '__')"
|
||||
OCI_ARCHIVE="${SRC_DIR}/image.oci"
|
||||
|
||||
if [[ ! -f "$OCI_ARCHIVE" ]]; then
|
||||
echo "[airgap] OCI archive not found at $OCI_ARCHIVE. Run build-multiarch first." >&2
|
||||
exit 66
|
||||
fi
|
||||
|
||||
mkdir -p "$BUNDLE_DIR"
|
||||
|
||||
SBOM_FILE=""
|
||||
if [[ -f "${SRC_DIR}/sbom.syft.json" ]]; then
|
||||
SBOM_FILE="${SRC_DIR}/sbom.syft.json"
|
||||
fi
|
||||
|
||||
cat > "${BUNDLE_DIR}/bundle-manifest.json" <<EOF
|
||||
{
|
||||
"image": "${IMAGE_TAG}",
|
||||
"oci_archive": "image.oci",
|
||||
"sbom": "$( [[ -n "$SBOM_FILE" ]] && echo sbom.syft.json || echo null )",
|
||||
"created_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
}
|
||||
EOF
|
||||
|
||||
cp "$OCI_ARCHIVE" "${BUNDLE_DIR}/image.oci"
|
||||
[[ -n "$SBOM_FILE" ]] && cp "$SBOM_FILE" "${BUNDLE_DIR}/sbom.syft.json"
|
||||
[[ -f "${SRC_DIR}/image.sha256" ]] && cp "${SRC_DIR}/image.sha256" "${BUNDLE_DIR}/image.sha256"
|
||||
[[ -f "${SRC_DIR}/image.sig" ]] && cp "${SRC_DIR}/image.sig" "${BUNDLE_DIR}/image.sig"
|
||||
|
||||
tar -C "$BUNDLE_DIR" -czf "${BUNDLE_DIR}.tgz" .
|
||||
echo "[airgap] bundle created at ${BUNDLE_DIR}.tgz"
|
||||
93
scripts/buildx/build-multiarch.sh
Normal file
93
scripts/buildx/build-multiarch.sh
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Multi-arch buildx helper for DEVOPS-CONTAINERS-44-001
|
||||
# Requirements: docker CLI with buildx, optional syft (for SBOM) and cosign (for signing).
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 <image-tag> <context-dir> [--platform linux/amd64,linux/arm64] [--push] [--sbom syft|none] [--sign <cosign-key>]" >&2
|
||||
exit 64
|
||||
}
|
||||
|
||||
if [[ $# -lt 2 ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
IMAGE_TAG=$1; shift
|
||||
CONTEXT_DIR=$1; shift
|
||||
|
||||
PLATFORMS="linux/amd64,linux/arm64"
|
||||
PUSH=false
|
||||
SBOM_TOOL="syft"
|
||||
COSIGN_KEY=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--platform) PLATFORMS="$2"; shift 2;;
|
||||
--push) PUSH=true; shift;;
|
||||
--sbom) SBOM_TOOL="$2"; shift 2;;
|
||||
--sign) COSIGN_KEY="$2"; shift 2;;
|
||||
*) echo "Unknown option: $1" >&2; usage;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "[buildx] docker CLI not found" >&2
|
||||
exit 69
|
||||
fi
|
||||
|
||||
OUT_ROOT="out/buildx/$(echo "$IMAGE_TAG" | tr '/:' '__')"
|
||||
mkdir -p "$OUT_ROOT"
|
||||
|
||||
BUILDER_NAME="stellaops-multiarch"
|
||||
if ! docker buildx inspect "$BUILDER_NAME" >/dev/null 2>&1; then
|
||||
docker buildx create --name "$BUILDER_NAME" --driver docker-container --use >/dev/null
|
||||
else
|
||||
docker buildx use "$BUILDER_NAME" >/dev/null
|
||||
fi
|
||||
|
||||
BUILD_OPTS=(
|
||||
--platform "$PLATFORMS"
|
||||
-t "$IMAGE_TAG"
|
||||
--provenance=false
|
||||
--sbom=false
|
||||
--output "type=oci,dest=${OUT_ROOT}/image.oci"
|
||||
)
|
||||
|
||||
if $PUSH; then
|
||||
BUILD_OPTS+=("--push")
|
||||
fi
|
||||
|
||||
echo "[buildx] building $IMAGE_TAG for $PLATFORMS"
|
||||
docker buildx build "${BUILD_OPTS[@]}" "$CONTEXT_DIR"
|
||||
|
||||
echo "[buildx] computing digest"
|
||||
IMAGE_DIGEST=$(sha256sum "${OUT_ROOT}/image.oci" | awk '{print $1}')
|
||||
echo "$IMAGE_DIGEST image.oci" > "${OUT_ROOT}/image.sha256"
|
||||
|
||||
if [[ "$SBOM_TOOL" == "syft" ]] && command -v syft >/dev/null 2>&1; then
|
||||
echo "[buildx] generating SBOM via syft"
|
||||
syft "oci-archive:${OUT_ROOT}/image.oci" -o json > "${OUT_ROOT}/sbom.syft.json"
|
||||
else
|
||||
echo "[buildx] skipping SBOM (tool=$SBOM_TOOL, syft available? $(command -v syft >/dev/null && echo yes || echo no))"
|
||||
fi
|
||||
|
||||
if [[ -n "$COSIGN_KEY" ]] && command -v cosign >/dev/null 2>&1; then
|
||||
echo "[buildx] signing digest with cosign key"
|
||||
COSIGN_EXPERIMENTAL=1 cosign sign-blob --key "$COSIGN_KEY" --output-signature "${OUT_ROOT}/image.sig" --output-certificate "${OUT_ROOT}/image.cert" "${OUT_ROOT}/image.oci"
|
||||
else
|
||||
echo "[buildx] signature skipped (no key provided or cosign missing)"
|
||||
fi
|
||||
|
||||
cat > "${OUT_ROOT}/build-metadata.json" <<EOF
|
||||
{
|
||||
"image": "${IMAGE_TAG}",
|
||||
"platforms": "${PLATFORMS}",
|
||||
"pushed": ${PUSH},
|
||||
"digest_sha256": "${IMAGE_DIGEST}",
|
||||
"generated_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
||||
"sbom": "$( [[ -f ${OUT_ROOT}/sbom.syft.json ]] && echo sbom.syft.json || echo null )"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "[buildx] artifacts written to ${OUT_ROOT}"
|
||||
82
scripts/cli/build-cli.sh
Normal file
82
scripts/cli/build-cli.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# DEVOPS-CLI-41-001: Build multi-platform CLI binaries with SBOM and checksums.
|
||||
|
||||
RIDS="${RIDS:-linux-x64,win-x64,osx-arm64}"
|
||||
CONFIG="${CONFIG:-Release}"
|
||||
PROJECT="src/Cli/StellaOps.Cli/StellaOps.Cli.csproj"
|
||||
OUT_ROOT="out/cli"
|
||||
SBOM_TOOL="${SBOM_TOOL:-syft}" # syft|none
|
||||
SIGN="${SIGN:-false}"
|
||||
COSIGN_KEY="${COSIGN_KEY:-}"
|
||||
|
||||
IFS=',' read -ra TARGETS <<< "$RIDS"
|
||||
|
||||
mkdir -p "$OUT_ROOT"
|
||||
|
||||
if ! command -v dotnet >/dev/null 2>&1; then
|
||||
echo "[cli-build] dotnet CLI not found" >&2
|
||||
exit 69
|
||||
fi
|
||||
|
||||
generate_sbom() {
|
||||
local dir="$1"
|
||||
local sbom="$2"
|
||||
if [[ "$SBOM_TOOL" == "syft" ]] && command -v syft >/dev/null 2>&1; then
|
||||
syft "dir:${dir}" -o json > "$sbom"
|
||||
fi
|
||||
}
|
||||
|
||||
sign_file() {
|
||||
local file="$1"
|
||||
if [[ "$SIGN" == "true" && -n "$COSIGN_KEY" && -x "$(command -v cosign || true)" ]]; then
|
||||
COSIGN_EXPERIMENTAL=1 cosign sign-blob --key "$COSIGN_KEY" --output-signature "${file}.sig" "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
for rid in "${TARGETS[@]}"; do
|
||||
echo "[cli-build] publishing for $rid"
|
||||
out_dir="${OUT_ROOT}/${rid}"
|
||||
publish_dir="${out_dir}/publish"
|
||||
mkdir -p "$publish_dir"
|
||||
|
||||
dotnet publish "$PROJECT" -c "$CONFIG" -r "$rid" \
|
||||
-o "$publish_dir" \
|
||||
--self-contained true \
|
||||
-p:PublishSingleFile=true \
|
||||
-p:PublishTrimmed=false \
|
||||
-p:DebugType=None \
|
||||
>/dev/null
|
||||
|
||||
# Package
|
||||
archive_ext="tar.gz"
|
||||
archive_cmd=(tar -C "$publish_dir" -czf)
|
||||
if [[ "$rid" == win-* ]]; then
|
||||
archive_ext="zip"
|
||||
archive_cmd=(zip -jr)
|
||||
fi
|
||||
|
||||
archive_name="stella-cli-${rid}.${archive_ext}"
|
||||
archive_path="${out_dir}/${archive_name}"
|
||||
"${archive_cmd[@]}" "$archive_path" "$publish_dir"
|
||||
|
||||
sha256sum "$archive_path" > "${archive_path}.sha256"
|
||||
sign_file "$archive_path"
|
||||
|
||||
# SBOM
|
||||
generate_sbom "$publish_dir" "${archive_path}.sbom.json"
|
||||
done
|
||||
|
||||
# Build manifest
|
||||
manifest="${OUT_ROOT}/manifest.json"
|
||||
cat > "$manifest" <<EOF
|
||||
{
|
||||
"generated_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
||||
"config": "$CONFIG",
|
||||
"rids": [$(printf '"%s",' "${TARGETS[@]}" | sed 's/,$//')],
|
||||
"artifacts_root": "$OUT_ROOT"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "[cli-build] artifacts in $OUT_ROOT"
|
||||
Reference in New Issue
Block a user