From 83a7117b8a02aa16d5a364f186449292810adbd8 Mon Sep 17 00:00:00 2001 From: Mohammad Jaber <49678859+mo-jaber@users.noreply.github.com> Date: Fri, 26 Jun 2026 04:08:50 +0400 Subject: [PATCH] ci(wab): add root-level AWS Marketplace release workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Publishes infra/wab to the AWS Marketplace listing (prod-67ziqtkrihz34) on infra/v* tags, with the version name driven by infra/wab/package.json — the same source of truth infra-release.yaml uses for GHCR. Builds the image, pushes it to the Marketplace ECR, and registers the version via the Catalog API (AddDeliveryOptions); idempotent on re-runs. The old Marketplace workflow stopped firing when WAB moved into the monorepo because GitHub does not run workflows under infra/wab/.github/workflows/. - OIDC auth (role-to-assume vars.WAB_MP_ROLE_ARN), SHA-pinned actions, concurrency guard. Version name is always derived from package.json; the git tag is only the trigger. Titles are v-prefixed to match the live listing. - Remove dead nested workflows: build-and-push.yml (Marketplace) and image.yaml (Docker Hub, dropped — GHCR is the live image channel). - Document the release convention in CLAUDE.md. --- .github/workflows/wab-marketplace-release.yml | 171 ++++++++++++++++++ CLAUDE.md | 20 ++ .../wab/.github/workflows/build-and-push.yml | 51 ------ infra/wab/.github/workflows/image.yaml | 66 ------- 4 files changed, 191 insertions(+), 117 deletions(-) create mode 100644 .github/workflows/wab-marketplace-release.yml create mode 100644 CLAUDE.md delete mode 100644 infra/wab/.github/workflows/build-and-push.yml delete mode 100644 infra/wab/.github/workflows/image.yaml diff --git a/.github/workflows/wab-marketplace-release.yml b/.github/workflows/wab-marketplace-release.yml new file mode 100644 index 000000000..059b153f4 --- /dev/null +++ b/.github/workflows/wab-marketplace-release.yml @@ -0,0 +1,171 @@ +name: WAB → AWS Marketplace + +# Publishes infra/wab to the AWS Marketplace listing (ContainerProduct prod-67ziqtkrihz34). +# +# VERSION SOURCE OF TRUTH: infra/wab/package.json `version`. The published Marketplace version +# name is ALWAYS derived from package.json — the `infra/v*` git tag is ONLY the trigger (it is +# date-based, e.g. infra/v2026-June-26, and is never parsed for a version). These two MUST stay +# decoupled. `version_override` is a manual escape hatch (e.g. the one-time 1.0.8 -> 1.4.7 +# catch-up), not the normal path. +# +# Naming mirrors the existing listing exactly: +# package.json 1.4.7 +# ECR image tag bsv-blockchain/wab:1.4.7 (bare, like the existing wab:1.0.7) +# Marketplace VersionTitle "v1.4.7" (v-prefixed, like the existing v1.0.7 / v1.0.8) +# +# IMPORTANT: this file MUST live in the repo ROOT .github/workflows/. GitHub never runs workflow +# files placed in subdirectories such as infra/wab/.github/workflows/ — that is exactly why the old +# build-and-push.yml stopped firing when WAB moved into the monorepo. + +on: + push: + tags: + - 'infra/v*' # same trigger as infra-release.yaml — the tag is only the trigger + workflow_dispatch: + inputs: + version_override: + description: 'Manual escape hatch: publish this version instead of reading package.json' + required: false + default: '' + +concurrency: + group: wab-marketplace-release + cancel-in-progress: false # never cancel a half-registered Catalog change set + +env: + AWS_REGION: us-east-1 + MP_ECR_ACCOUNT: '709825985650' # AWS Marketplace-managed ECR account + MP_ECR_REPO: bsv-blockchain/wab # existing Marketplace repo for this product + PRODUCT_ID: prod-67ziqtkrihz34 # WAB ContainerProduct + WAB_DIR: infra/wab + +jobs: + publish: + name: Publish WAB to Marketplace + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # OIDC: assume the seller-account publisher role (no static keys) + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Resolve version from package.json + id: ver + run: | + # The version name is ALWAYS package.json; the git tag is only the trigger (keep decoupled). + if [ -n "${{ github.event.inputs.version_override }}" ]; then + VERSION="${{ github.event.inputs.version_override }}" + else + VERSION="$(node -p "require('./${WAB_DIR}/package.json').version")" + fi + [ -n "$VERSION" ] || { echo "No version found"; exit 1; } + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "title=v$VERSION" >> "$GITHUB_OUTPUT" + echo "Resolved WAB version: $VERSION (Marketplace title: v$VERSION)" + + - name: Configure AWS credentials (OIDC) + uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0 + with: + role-to-assume: ${{ vars.WAB_MP_ROLE_ARN }} + aws-region: ${{ env.AWS_REGION }} + + - name: Skip if this version is already published + id: exists + run: | + TITLE="${{ steps.ver.outputs.title }}" + # Use DetailsDocument (clean JSON). The legacy `Details` field returns non-strict JSON + # (raw newlines in release notes) which breaks `jq fromjson` and would make this check + # silently match nothing. + TITLES="$(aws marketplace-catalog describe-entity \ + --catalog AWSMarketplace \ + --entity-id "${PRODUCT_ID}" \ + --query DetailsDocument --output json 2>/dev/null \ + | jq -r '.Versions[]?.VersionTitle' 2>/dev/null || true)" + if echo "$TITLES" | grep -qx "$TITLE"; then + echo "already=true" >> "$GITHUB_OUTPUT" + echo "Version $TITLE already exists on the listing — nothing to do." + else + echo "already=false" >> "$GITHUB_OUTPUT" + echo "Version $TITLE not present — will build, push, and register." + fi + + - name: Log in to AWS Marketplace ECR + if: steps.exists.outputs.already == 'false' + run: | + aws ecr get-login-password --region "${AWS_REGION}" \ + | docker login --username AWS --password-stdin \ + "${MP_ECR_ACCOUNT}.dkr.ecr.${AWS_REGION}.amazonaws.com" + + - name: Build and push image to Marketplace ECR + if: steps.exists.outputs.already == 'false' + run: | + VERSION="${{ steps.ver.outputs.version }}" + IMAGE="${MP_ECR_ACCOUNT}.dkr.ecr.${AWS_REGION}.amazonaws.com/${MP_ECR_REPO}:${VERSION}" + # context = infra/wab (self-contained Dockerfile; same recipe infra-release.yaml uses). + docker build --platform linux/amd64 -t "$IMAGE" "${WAB_DIR}" + docker push "$IMAGE" + echo "Pushed $IMAGE" + + - name: Register new version on the listing (AddDeliveryOptions) + if: steps.exists.outputs.already == 'false' + id: changeset + run: | + VERSION="${{ steps.ver.outputs.version }}" + TITLE="${{ steps.ver.outputs.title }}" + IMAGE="${MP_ECR_ACCOUNT}.dkr.ecr.${AWS_REGION}.amazonaws.com/${MP_ECR_REPO}:${VERSION}" + + # DeliveryOptionTitle and CompatibleServices match the live listing (verified via + # describe-entity). The Catalog API expects the "Anywhere" services as hyphenated + # tokens: ECS-Anywhere / EKS-Anywhere. Uses DetailsDocument (not legacy Details). + CHANGE_SET=$(jq -nc --arg id "$PRODUCT_ID" --arg t "$TITLE" --arg img "$IMAGE" ' + [{ + ChangeType: "AddDeliveryOptions", + Entity: { Identifier: $id, Type: "ContainerProduct@1.0" }, + DetailsDocument: { + Version: { + VersionTitle: $t, + ReleaseNotes: ("WAB \($t). See https://github.com/bsv-blockchain/ts-stack/tree/main/infra/wab") + }, + DeliveryOptions: [{ + DeliveryOptionTitle: "WAB Container - ECS/EKS Deployment", + Details: { + EcrDeliveryOptionDetails: { + ContainerImages: [ $img ], + CompatibleServices: [ "ECS", "EKS", "ECS-Anywhere", "EKS-Anywhere" ], + Description: "Wallet Authentication Backend", + UsageInstructions: "See https://github.com/bsv-blockchain/ts-stack/tree/main/infra/wab" + } + } + }] + } + }]') + + CS_ID=$(aws marketplace-catalog start-change-set \ + --catalog AWSMarketplace \ + --change-set "$CHANGE_SET" \ + --query 'ChangeSetId' --output text) + echo "change_set_id=$CS_ID" >> "$GITHUB_OUTPUT" + echo "Started change set $CS_ID for $TITLE" + + - name: Wait for change set to settle (scan can take minutes–hours) + if: steps.exists.outputs.already == 'false' + run: | + CS_ID="${{ steps.changeset.outputs.change_set_id }}" + for i in $(seq 1 60); do + STATUS=$(aws marketplace-catalog describe-change-set \ + --catalog AWSMarketplace --change-set-id "$CS_ID" \ + --query 'Status' --output text) + echo "[$i] status=$STATUS" + case "$STATUS" in + SUCCEEDED) echo "Version published."; exit 0 ;; + FAILED|CANCELLED) + echo "Change set $STATUS. Error details:" + aws marketplace-catalog describe-change-set --catalog AWSMarketplace \ + --change-set-id "$CS_ID" --query 'ChangeSet[].ErrorDetailList' + exit 1 ;; + esac + sleep 60 + done + echo "Still PREPARING/APPLYING after timeout; check the Requests log in the portal." + echo "Change set: $CS_ID" diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..9571d4693 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,20 @@ +# ts-stack — repo guidance + +## Releasing infra/wab to AWS Marketplace + +The Marketplace version is driven by `infra/wab/package.json` `version` — the same source of +truth `infra-release.yaml` uses for GHCR. To release: bump that version, then push an `infra/v*` tag. + +- Workflow: `.github/workflows/wab-marketplace-release.yml` (must be at repo root — GitHub does not + run workflows under `infra/wab/.github/workflows/`). +- The published version name is **always** derived from `infra/wab/package.json`; the `infra/v*` git + tag (date-based) is **only the trigger** and is never parsed for a version. Marketplace version + titles are `v`-prefixed (`v1.4.7`) to match the existing listing; the ECR image tag is bare + (`wab:1.4.7`). +- Marketplace product ID: `prod-67ziqtkrihz34` (ContainerProduct@1.0). +- Image goes to the Marketplace ECR `709825985650.dkr.ecr.us-east-1.amazonaws.com/bsv-blockchain/wab:`, + NOT GHCR or Docker Hub. +- Publishing = push image to Marketplace ECR THEN register the version via Catalog API + `StartChangeSet` / `AddDeliveryOptions`. Pushing the image alone does nothing buyers can see. +- Version names on the listing are immutable and must be unique; never reuse one. +- New versions are scanned (minutes–hours) before going live. diff --git a/infra/wab/.github/workflows/build-and-push.yml b/infra/wab/.github/workflows/build-and-push.yml deleted file mode 100644 index 3cc240c55..000000000 --- a/infra/wab/.github/workflows/build-and-push.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Build and Push to AWS Marketplace ECR - -on: - workflow_dispatch: - inputs: - version: - description: 'Version tag (e.g., 1.0.7)' - required: true - default: '1.0.7' - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: us-east-1 - - - name: Login to Amazon ECR - run: | - aws ecr get-login-password --region us-east-1 | \ - docker login --username AWS --password-stdin \ - 709825985650.dkr.ecr.us-east-1.amazonaws.com - - - name: Build Docker image - run: | - docker build --platform linux/amd64 -t wab:${{ github.event.inputs.version }} . - - - name: Tag image - run: | - docker tag wab:${{ github.event.inputs.version }} \ - 709825985650.dkr.ecr.us-east-1.amazonaws.com/bsv-blockchain/wab:${{ github.event.inputs.version }} - - - name: Push to ECR - run: | - docker push 709825985650.dkr.ecr.us-east-1.amazonaws.com/bsv-blockchain/wab:${{ github.event.inputs.version }} - - - name: Verify push - run: | - aws ecr describe-images \ - --registry-id 709825985650 \ - --repository-name bsv-blockchain/wab \ - --region us-east-1 \ - --image-ids imageTag=${{ github.event.inputs.version }} diff --git a/infra/wab/.github/workflows/image.yaml b/infra/wab/.github/workflows/image.yaml deleted file mode 100644 index de9de93d4..000000000 --- a/infra/wab/.github/workflows/image.yaml +++ /dev/null @@ -1,66 +0,0 @@ -name: Build and push OCI image to Docker Hub - -on: - push: - tags: - - 'v*' - -jobs: - check-current-branch: - runs-on: ubuntu-latest - outputs: - branch: ${{ steps.check_step.outputs.branch }} - steps: - - name: Checkout - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 - with: - fetch-depth: 0 - - - name: Get current branch - id: check_step - # 1. Get the list of branches ref where this tag exists - # 2. Remove 'origin/' from that result - # 3. Put that string in output - run: | - raw=$(git branch -r --contains ${{ github.ref }}) - branch="$(echo ${raw//origin\//} | tr -d '\n')" - echo "{name}=branch" >> $GITHUB_OUTPUT - echo "Branches where this tag exists : $branch." - - image: - runs-on: ubuntu-latest - needs: check-current-branch - if: contains(${{ needs.check.outputs.branch }}, 'main')` - steps: - - name: Check out the repo - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Get build args - id: build_args - run: | - echo "APP_COMMIT=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - echo "APP_VERSION=$(git describe --tags --always --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*' 2> /dev/null | sed 's/^.//')" >> "$GITHUB_OUTPUT" - - - name: Log in to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) - id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 - with: - images: bsvb/wab - - - name: Build and push image - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 - with: - context: . - file: ./Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - APP_COMMIT=${{ steps.build_args.outputs.APP_COMMIT }} - APP_VERSION=${{ steps.build_args.outputs.APP_VERSION }}