Files
harmony/fleet/scripts/release.sh

205 lines
8.5 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Build, publish, and deploy independent Harmony Fleet releases.
Usage:
fleet/scripts/release.sh [options]
At least one release version is required:
--control-version VERSION Operator and NATS callout version
--agent-version VERSION Standalone fleet-agent version
Agent options:
--agent-arch ARCH x86_64 (default) or aarch64
--namespace NAMESPACE Namespace containing Device CRs
--device NAME Device to upgrade; repeat for multiple devices
Control-plane deployment options:
--deploy-manifest PATH Private deployment Cargo.toml
--deploy-bin NAME Deployment binary
--context NAME Harmony deployment context
Registry options:
--registry HOST Default: hub.nationtech.io
--repository PATH Default: harmony
Other:
--kube-context NAME kubectl context used for Device patches
--publish-only Build and push without deploying
-h, --help
Environment equivalents:
CONTROL_VERSION, AGENT_VERSION, AGENT_ARCH, FLEET_DEPLOY_MANIFEST,
FLEET_DEPLOY_BIN, FLEET_CONTEXT, FLEET_NAMESPACE, KUBECTL_CONTEXT,
REGISTRY_USER, REGISTRY_TOKEN, OPENBAO_TOKEN, RUST_LOG.
EOF
}
fail() {
printf 'error: %s\n' "$*" >&2
exit 1
}
require_command() {
command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
}
validate_version() {
[[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] ||
fail "version must be SemVer without a leading v or build metadata: $1"
}
control_version="${CONTROL_VERSION:-}"
agent_version="${AGENT_VERSION:-}"
agent_arch="${AGENT_ARCH:-x86_64}"
deploy_manifest="${FLEET_DEPLOY_MANIFEST:-}"
deploy_bin="${FLEET_DEPLOY_BIN:-}"
deploy_context="${FLEET_CONTEXT:-}"
namespace="${FLEET_NAMESPACE:-}"
kube_context="${KUBECTL_CONTEXT:-}"
registry="${FLEET_REGISTRY:-hub.nationtech.io}"
repository="${FLEET_REGISTRY_REPOSITORY:-harmony}"
publish_only=0
devices=()
while (($#)); do
case "$1" in
--control-version) control_version="${2:?missing control version}"; shift 2 ;;
--agent-version) agent_version="${2:?missing agent version}"; shift 2 ;;
--agent-arch) agent_arch="${2:?missing agent architecture}"; shift 2 ;;
--deploy-manifest) deploy_manifest="${2:?missing deployment manifest}"; shift 2 ;;
--deploy-bin) deploy_bin="${2:?missing deployment binary}"; shift 2 ;;
--context) deploy_context="${2:?missing deployment context}"; shift 2 ;;
--namespace) namespace="${2:?missing namespace}"; shift 2 ;;
--device) devices+=("${2:?missing device name}"); shift 2 ;;
--kube-context) kube_context="${2:?missing kubectl context}"; shift 2 ;;
--registry) registry="${2:?missing registry}"; shift 2 ;;
--repository) repository="${2:?missing repository}"; shift 2 ;;
--publish-only) publish_only=1; shift ;;
-h|--help) usage; exit 0 ;;
*) fail "unknown argument: $1" ;;
esac
done
[[ -n "$control_version" || -n "$agent_version" ]] || fail "set a control or agent version"
[[ -z "$control_version" ]] || validate_version "$control_version"
[[ -z "$agent_version" ]] || validate_version "$agent_version"
[[ "$agent_arch" == "x86_64" || "$agent_arch" == "aarch64" ]] ||
fail "agent architecture must be x86_64 or aarch64"
if [[ -n "$control_version" && ("$registry" != "hub.nationtech.io" || "$repository" != "harmony") ]]; then
fail "control-plane releases publish to hub.nationtech.io/harmony"
fi
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/../.." && pwd)"
cd "$repo_root"
require_command cargo
require_command sha256sum
require_command stat
[[ -z "$control_version" ]] || require_command docker
if [[ -n "$control_version" || -n "$agent_version" ]]; then
: "${REGISTRY_USER:?REGISTRY_USER is required for publication}"
: "${REGISTRY_TOKEN:?REGISTRY_TOKEN is required for publication}"
fi
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
agent_binary=""
agent_size=""
agent_sha256=""
agent_tag_ref=""
agent_oci_ref=""
if [[ -n "$agent_version" ]]; then
printf '==> Building fleet agent %s for %s\n' "$agent_version" "$agent_arch"
if [[ "$agent_arch" == "x86_64" ]]; then
[[ "$(uname -m)" == "x86_64" ]] || fail "x86_64 agent build requires an x86_64 host"
HARMONY_FLEET_AGENT_VERSION="$agent_version" \
cargo build --release --locked -p harmony-fleet-agent
agent_binary="$repo_root/target/release/harmony-fleet-agent"
version_output="$("$agent_binary" --version)"
[[ "$version_output" == *" $agent_version" ]] ||
fail "built agent reports the wrong version: $version_output"
else
require_command rustup
rustup target add aarch64-unknown-linux-gnu
HARMONY_FLEET_AGENT_VERSION="$agent_version" \
cargo build --release --locked --target aarch64-unknown-linux-gnu \
-p harmony-fleet-agent
agent_binary="$repo_root/target/aarch64-unknown-linux-gnu/release/harmony-fleet-agent"
fi
[[ -x "$agent_binary" ]] || fail "agent binary not produced: $agent_binary"
agent_size="$(stat -c %s "$agent_binary")"
agent_sha256="$(sha256sum "$agent_binary" | cut -d' ' -f1)"
agent_tag_ref="$registry/$repository/harmony-fleet-agent:$agent_version-$agent_arch"
fi
operator_ref=""
callout_ref=""
if [[ -n "$agent_version" ]]; then
printf '==> Publishing %s\n' "$agent_tag_ref"
agent_release_log="$tmp/agent-release.log"
REGISTRY_USER="$REGISTRY_USER" REGISTRY_TOKEN="$REGISTRY_TOKEN" \
RUST_LOG="${RUST_LOG:-info}" \
cargo run --release -p harmony-fleet-deploy --bin harmony-fleet-release -- \
agent --binary "$agent_binary" --reference "$agent_tag_ref" \
--version "$agent_version" | tee "$agent_release_log"
agent_oci_ref="$(awk -F= '$1 == "agent" { value=$2 } END { print value }' "$agent_release_log")"
[[ "$agent_oci_ref" =~ @sha256:[0-9a-f]{64}$ ]] || fail "agent digest missing from release output"
fi
if [[ -n "$control_version" ]]; then
printf '==> Resolving or publishing Fleet control plane %s\n' "$control_version"
release_log="$tmp/control-release.log"
REGISTRY_USER="$REGISTRY_USER" REGISTRY_TOKEN="$REGISTRY_TOKEN" \
RUST_LOG="${RUST_LOG:-info}" \
cargo run --release -p harmony-fleet-deploy --bin harmony-fleet-release -- \
control --version "$control_version" --push | tee "$release_log"
operator_ref="$(awk -F= '$1 == "operator" { value=$2 } END { print value }' "$release_log")"
callout_ref="$(awk -F= '$1 == "callout" { value=$2 } END { print value }' "$release_log")"
[[ "$operator_ref" =~ @sha256:[0-9a-f]{64}$ ]] || fail "operator digest missing from release output"
[[ "$callout_ref" =~ @sha256:[0-9a-f]{64}$ ]] || fail "callout digest missing from release output"
fi
if ((publish_only == 0)) && [[ -n "$control_version" ]]; then
[[ -n "$deploy_manifest" ]] || fail "--deploy-manifest is required to deploy the control plane"
[[ -n "$deploy_bin" ]] || fail "--deploy-bin is required to deploy the control plane"
[[ -n "$deploy_context" ]] || fail "--context is required to deploy the control plane"
: "${OPENBAO_TOKEN:?OPENBAO_TOKEN is required for deployment}"
printf '==> Deploying Fleet control plane %s\n' "$control_version"
OPENBAO_TOKEN="$OPENBAO_TOKEN" RUST_LOG="${RUST_LOG:-info}" \
cargo run --release --manifest-path "$deploy_manifest" --bin "$deploy_bin" -- \
deploy --context "$deploy_context" --tag "$control_version" \
--image "operator=$operator_ref" --image "callout=$callout_ref"
fi
if ((publish_only == 0)) && [[ -n "$agent_version" && ${#devices[@]} -gt 0 ]]; then
[[ -n "$namespace" ]] || fail "--namespace is required when --device is used"
require_command kubectl
printf 'note: Device setup must have refreshed the bootstrap updater with OCI support.\n'
kubectl_args=()
[[ -z "$kube_context" ]] || kubectl_args+=(--context "$kube_context")
for device in "${devices[@]}"; do
printf '==> Targeting agent %s on %s\n' "$agent_version" "$device"
kubectl "${kubectl_args[@]}" -n "$namespace" patch \
devices.fleet.nationtech.io "$device" --type merge -p \
"{\"spec\":{\"agentUpgrade\":{\"version\":\"$agent_version\",\"architecture\":\"$agent_arch\",\"artifactUrl\":\"$agent_oci_ref\",\"maxBytes\":$agent_size,\"sha256\":\"$agent_sha256\"}}}"
done
fi
printf '\nRelease outputs:\n'
[[ -z "$operator_ref" ]] || printf ' operator=%s\n callout=%s\n' "$operator_ref" "$callout_ref"
if [[ -n "$agent_oci_ref" ]]; then
printf ' agent.version=%s\n' "$agent_version"
printf ' agent.architecture=%s\n' "$agent_arch"
printf ' agent.artifactUrl=%s\n' "$agent_oci_ref"
printf ' agent.maxBytes=%s\n' "$agent_size"
printf ' agent.sha256=%s\n' "$agent_sha256"
fi