name: Publish Images on: workflow_dispatch: inputs: target_arch: description: Architecture to publish type: choice options: - all - amd64 - arm64 default: all target_image: description: Image to publish type: choice options: - all - api - executor - notifier - agent - web default: all push: branches: - main - master tags: - "v*" env: REGISTRY_HOST: ${{ vars.CLUSTER_GITEA_HOST }} REGISTRY_NAMESPACE: ${{ vars.CONTAINER_REGISTRY_NAMESPACE }} REGISTRY_PLAIN_HTTP: ${{ vars.CONTAINER_REGISTRY_INSECURE }} REPOSITORY_NAME: attune ARTIFACT_REPOSITORY: attune/build-artifacts GNU_GLIBC_VERSION: "2.28" CARGO_TERM_COLOR: always CARGO_INCREMENTAL: 0 CARGO_NET_RETRY: 10 RUSTUP_MAX_RETRIES: 10 RUST_MIN_STACK: 67108864 SQLX_OFFLINE: true RUNNER_TOOL_CACHE: /toolcache jobs: metadata: name: Resolve Publish Metadata runs-on: build-amd64 outputs: registry: ${{ steps.meta.outputs.registry }} namespace: ${{ steps.meta.outputs.namespace }} registry_plain_http: ${{ steps.meta.outputs.registry_plain_http }} image_tag: ${{ steps.meta.outputs.image_tag }} image_tags: ${{ steps.meta.outputs.image_tags }} artifact_ref_base: ${{ steps.meta.outputs.artifact_ref_base }} steps: - name: Resolve tags and registry paths id: meta shell: bash run: | set -euo pipefail registry="${REGISTRY_HOST}" namespace="${REGISTRY_NAMESPACE}" registry_plain_http_raw="${REGISTRY_PLAIN_HTTP:-}" registry_host_only="${registry%%:*}" registry_plain_http_default="false" if [ -z "$registry" ]; then echo "CLUSTER_GITEA_HOST app variable is required" exit 1 fi if [ -z "$namespace" ]; then namespace="${{ github.repository_owner }}" fi if printf '%s' "$registry_host_only" | grep -Eq '(^|[.])svc[.]cluster[.]local$'; then registry_plain_http_default="true" fi if [ -n "$registry_plain_http_raw" ]; then case "$(printf '%s' "$registry_plain_http_raw" | tr '[:upper:]' '[:lower:]')" in 1|true|yes|on) registry_plain_http="true" ;; 0|false|no|off) registry_plain_http="false" ;; *) echo "CONTAINER_REGISTRY_INSECURE must be a boolean when set" exit 1 ;; esac else registry_plain_http="$registry_plain_http_default" fi short_sha="$(printf '%s' "${{ github.sha }}" | cut -c1-12)" ref_type="${{ github.ref_type }}" ref_name="${{ github.ref_name }}" if [ "$ref_type" = "tag" ] && printf '%s' "$ref_name" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+([-.].*)?$'; then version="${ref_name#v}" image_tags="${version},latest,sha-${short_sha}" else version="sha-${short_sha}" image_tags="edge,sha-${short_sha}" fi artifact_ref_base="${registry}/${namespace}/${ARTIFACT_REPOSITORY}" { echo "registry=$registry" echo "namespace=$namespace" echo "registry_plain_http=$registry_plain_http" echo "image_tag=$version" echo "image_tags=$image_tags" echo "artifact_ref_base=$artifact_ref_base" } >> "$GITHUB_OUTPUT" build-rust-bundles: name: Build Rust Bundles (${{ matrix.arch }}) runs-on: ${{ matrix.runner_label }} needs: metadata if: | github.event_name != 'workflow_dispatch' || inputs.target_arch == 'all' || inputs.target_arch == matrix.arch strategy: fail-fast: false matrix: include: - arch: amd64 runner_label: build-amd64 service_rust_target: x86_64-unknown-linux-gnu service_target: x86_64-unknown-linux-gnu.2.28 musl_target: x86_64-unknown-linux-musl - arch: arm64 runner_label: build-amd64 service_rust_target: aarch64-unknown-linux-gnu service_target: aarch64-unknown-linux-gnu.2.28 musl_target: aarch64-unknown-linux-musl steps: - name: Checkout uses: actions/checkout@v4 - name: Cache Rust toolchain uses: actions/cache@v4 with: path: | ~/.rustup/toolchains ~/.rustup/update-hashes key: rustup-publish-${{ runner.os }}-${{ matrix.arch }}-stable-v1 restore-keys: | rustup-${{ runner.os }}-${{ matrix.arch }}-stable-v1 rustup-${{ runner.os }}-stable-v1 rustup- - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: targets: | ${{ matrix.service_rust_target }} ${{ matrix.musl_target }} - name: Cache Cargo registry + index uses: actions/cache@v4 with: path: | ~/.cargo/registry/index ~/.cargo/registry/cache ~/.cargo/git/db key: cargo-registry-publish-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: | cargo-registry-publish-${{ matrix.arch }}- cargo-registry- - name: Cache Cargo build artifacts uses: actions/cache@v4 with: path: target key: cargo-publish-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/*.rs', '**/Cargo.toml') }} restore-keys: | cargo-publish-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }}- cargo-publish-${{ matrix.arch }}- - name: Install native build dependencies shell: bash run: | set -euo pipefail apt-get update apt-get install -y pkg-config libssl-dev file binutils python3 python3-pip - name: Install Zig shell: bash run: | set -euo pipefail pip3 install --break-system-packages --no-cache-dir ziglang - name: Install cargo-zigbuild shell: bash run: | set -euo pipefail if ! command -v cargo-zigbuild >/dev/null 2>&1; then cargo install --locked cargo-zigbuild fi - name: Build release binaries shell: bash run: | set -euo pipefail cargo zigbuild --release \ --target "${{ matrix.service_target }}" \ --bin attune-api \ --bin attune-executor \ --bin attune-notifier - name: Verify minimum glibc requirement shell: bash run: | set -euo pipefail output_dir="target/${{ matrix.service_rust_target }}/release" get_min_glibc() { local file_path="$1" readelf -W --version-info --dyn-syms "$file_path" \ | grep 'Name: GLIBC_' \ | sed -E 's/.*GLIBC_([0-9.]+).*/\1/' \ | sort -t . -k1,1n -k2,2n \ | tail -n 1 } version_gt() { [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -n 1)" = "$1" ] && [ "$1" != "$2" ] } for binary in attune-api attune-executor attune-notifier; do min_glibc="$(get_min_glibc "${output_dir}/${binary}")" if [ -z "${min_glibc}" ]; then echo "Failed to determine glibc requirement for ${binary}" exit 1 fi echo "${binary} requires glibc ${min_glibc}" if version_gt "${min_glibc}" "${GNU_GLIBC_VERSION}"; then echo "Expected ${binary} to require glibc <= ${GNU_GLIBC_VERSION}, got ${min_glibc}" exit 1 fi done - name: Build static agent binaries shell: bash run: | set -euo pipefail cargo zigbuild --release \ --target "${{ matrix.musl_target }}" \ --bin attune-agent \ --bin attune-sensor-agent - name: Assemble binary bundle shell: bash run: | set -euo pipefail bundle_root="dist/bundle/${{ matrix.arch }}" service_output_dir="target/${{ matrix.service_rust_target }}/release" mkdir -p "$bundle_root/bin" "$bundle_root/agent" cp "${service_output_dir}/attune-api" "$bundle_root/bin/" cp "${service_output_dir}/attune-executor" "$bundle_root/bin/" cp "${service_output_dir}/attune-notifier" "$bundle_root/bin/" cp target/${{ matrix.musl_target }}/release/attune-agent "$bundle_root/agent/" cp target/${{ matrix.musl_target }}/release/attune-sensor-agent "$bundle_root/agent/" cat > "$bundle_root/metadata.json" < "$HOME/.docker/config.json" < "$HOME/.docker/config.json" < "$HOME/.docker/config.json" </dev/null 2>&1 || true docker manifest create "${create_args[@]}" "$manifest_ref" "$amd64_ref" "$arm64_ref" docker manifest annotate "$manifest_ref" "$amd64_ref" --os linux --arch amd64 docker manifest annotate "$manifest_ref" "$arm64_ref" --os linux --arch arm64 docker manifest push "${push_args[@]}" "$manifest_ref" done