diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3901f517e9..80682a5ba6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -163,6 +163,7 @@ jobs: --ref main "Infra-k8s Image Dispatcher" -F imageRepos="$(echo $CHANGED_ADAPTERS | jq -r "\"$ECR_URL/adapters/\" + (.adapter | .[].shortName) + \"-adapter\"" | tr '\n' ' ')" -F gitRepo=${{ github.event.repository.name }} + -F per-adapter-prs=true env: GITHUB_TOKEN: ${{ steps.setup-github-token.outputs.access-token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c53789f2c7..3e301c48b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,10 +9,19 @@ on: - MASTERLIST.md workflow_dispatch: inputs: - # For this workflow, build-all will cause all adapters to have their image pulled and republished to the public ECR - # NOTE: If the images haven't been already published to the private ECR, this will fail; in that case run the deploy workflow first. + adapters: + description: 'Release specific adapters (comma-separated) or leave empty for all pending' + required: false + type: string + default: 'all' + dry-run: + description: 'Dry run - validate but do not actually release' + required: false + type: boolean + default: false + # Legacy support build-all: - description: whether to run steps for all adapters, regardless of whether they were changed in this event + description: '[LEGACY] whether to run steps for all adapters, regardless of whether they were changed in this event' required: false default: 'false' @@ -21,8 +30,183 @@ concurrency: cancel-in-progress: false jobs: + # Determine which adapters to release based on trigger type + determine-release-mode: + name: Determine Release Mode + runs-on: ubuntu-latest + outputs: + release-mode: ${{ steps.determine.outputs.release-mode }} + adapter-list: ${{ steps.determine.outputs.adapter-list }} + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + fetch-depth: 2 + + - name: Determine release mode + id: determine + env: + EVENT_NAME: ${{ github.event_name }} + ADAPTERS_INPUT: ${{ inputs.adapters }} + BUILD_ALL: ${{ inputs.build-all }} + run: | + if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ -n "$ADAPTERS_INPUT" ]; then + echo "release-mode=selective" >> $GITHUB_OUTPUT + echo "Using selective release mode for: $ADAPTERS_INPUT" + else + echo "release-mode=legacy" >> $GITHUB_OUTPUT + echo "Using legacy release mode (MASTERLIST.md trigger or build-all)" + fi + + # Selective release: Parse adapters from RELEASES.yaml + parse-selective-adapters: + name: Parse Selective Adapters + needs: determine-release-mode + if: needs.determine-release-mode.outputs.release-mode == 'selective' + runs-on: ubuntu-latest + outputs: + adapter-list: ${{ steps.parse.outputs.adapter-list }} + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Install yq + run: | + sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + + - name: Parse adapters from input and RELEASES.yaml + id: parse + env: + ADAPTERS_INPUT: ${{ inputs.adapters }} + run: | + # Check if RELEASES.yaml exists + if [ ! -f "RELEASES.yaml" ]; then + echo "❌ Error: RELEASES.yaml not found" + echo " Create it first with: ./scripts/release-manager.sh add " + exit 1 + fi + + # Determine which adapters to release + if [ "$ADAPTERS_INPUT" = "all" ]; then + echo "Releasing all pending adapters" + ADAPTER_NAMES=$(yq eval '.pending | keys | join(",")' RELEASES.yaml) + else + echo "Releasing specific adapters: $ADAPTERS_INPUT" + ADAPTER_NAMES="$ADAPTERS_INPUT" + fi + + # Check if any adapters to release + if [ -z "$ADAPTER_NAMES" ] || [ "$ADAPTER_NAMES" = "null" ]; then + echo "❌ Error: No adapters to release" + echo " Add adapters to RELEASES.yaml first" + exit 1 + fi + + # Build adapter list with versions from RELEASES.yaml + ADAPTER_ARRAY="[]" + IFS=',' read -ra ADAPTERS <<< "$ADAPTER_NAMES" + for adapter in "${ADAPTERS[@]}"; do + adapter=$(echo "$adapter" | xargs) # trim whitespace + + # Check if adapter exists in pending + if ! yq eval ".pending | has(\"$adapter\")" RELEASES.yaml | grep -q "true"; then + echo "❌ Error: $adapter is not in RELEASES.yaml pending list" + echo " Run: ./scripts/release-manager.sh add $adapter " + exit 1 + fi + + # Get version and commit SHA from RELEASES.yaml + VERSION=$(yq eval ".pending.$adapter.version" RELEASES.yaml) + BUILT_FROM_COMMIT=$(yq eval ".pending.$adapter.built_from_commit" RELEASES.yaml) + + # SECURITY CHECK: Verify adapter exists in MASTERLIST.md (source of truth) + if [ ! -f "MASTERLIST.md" ]; then + echo "❌ Error: MASTERLIST.md not found" + exit 1 + fi + + MASTERLIST_VERSION=$(grep -E "^\|.*\b${adapter}\b" MASTERLIST.md | awk -F'|' '{print $3}' | xargs || echo "") + + if [ -z "$MASTERLIST_VERSION" ]; then + echo "❌ SECURITY ERROR: $adapter not found in MASTERLIST.md" + echo " This adapter does not exist in external-adapters-js repo" + echo " Cannot release code that is not in this repository" + exit 1 + fi + + if [ "$VERSION" != "$MASTERLIST_VERSION" ]; then + echo "❌ SECURITY ERROR: Version mismatch for $adapter" + echo " RELEASES.yaml: $VERSION" + echo " MASTERLIST.md: $MASTERLIST_VERSION" + echo " RELEASES.yaml version must match MASTERLIST.md (source of truth)" + echo " This prevents releasing code that hasn't been merged to this repo" + exit 1 + fi + + echo "✅ Verified: $adapter v$VERSION exists in MASTERLIST.md" + + # SECURITY CHECK: Verify commit SHA exists in git history + if [ -n "$BUILT_FROM_COMMIT" ] && [ "$BUILT_FROM_COMMIT" != "null" ]; then + if ! git cat-file -e "${BUILT_FROM_COMMIT}^{commit}" 2>/dev/null; then + echo "❌ SECURITY ERROR: Commit SHA not found in repository history" + echo " Adapter: $adapter" + echo " Claimed commit: $BUILT_FROM_COMMIT" + echo " This indicates RELEASES.yaml may have been manually tampered with" + echo " Only images built from commits in this repository can be released" + exit 1 + fi + echo "✅ Verified: Built from commit $BUILT_FROM_COMMIT (exists in repo history)" + else + echo "⚠️ WARNING: No built_from_commit in RELEASES.yaml for $adapter" + echo " This may be a legacy entry. Proceeding with extra caution." + echo " Future releases should include commit tracking." + fi + + # Check if tested + TESTED=$(yq eval ".pending.$adapter.tested_in_infra" RELEASES.yaml) + if [ "$TESTED" != "true" ]; then + echo "❌ Error: $adapter has not been tested in infra (tested_in_infra != true)" + exit 1 + fi + + # SECURITY CHECK: Verify image digest if available + IMAGE_DIGEST=$(yq eval ".pending.$adapter.image_digest" RELEASES.yaml) + if [ -n "$IMAGE_DIGEST" ] && [ "$IMAGE_DIGEST" != "null" ]; then + echo "✅ Verified: Image digest tracked ($IMAGE_DIGEST)" + echo " This ensures the exact image tested in infra will be released" + + # Store digest for release job to use + echo "IMAGE_DIGEST_${adapter//-/_}=$IMAGE_DIGEST" >> $GITHUB_ENV + else + echo "⚠️ WARNING: No image digest tracked for $adapter" + echo " Proceeding with version tag only (less secure)" + echo " Future releases should include digest tracking" + fi + + echo "✅ $adapter v$VERSION is ready for release" + + # Add to array (include digest if available) + if [ -n "$IMAGE_DIGEST" ] && [ "$IMAGE_DIGEST" != "null" ]; then + ADAPTER_ARRAY=$(echo "$ADAPTER_ARRAY" | jq --arg name "$adapter" --arg version "$VERSION" --arg digest "$IMAGE_DIGEST" \ + '. += [{shortName: $name, name: ("@chainlink/" + $name + "-adapter"), version: $version, imageDigest: $digest, location: "packages"}]') + else + ADAPTER_ARRAY=$(echo "$ADAPTER_ARRAY" | jq --arg name "$adapter" --arg version "$VERSION" \ + '. += [{shortName: $name, name: ("@chainlink/" + $name + "-adapter"), version: $version, location: "packages"}]') + fi + done + + # Format for matrix + ADAPTER_LIST=$(echo "$ADAPTER_ARRAY" | jq -c '{adapter: .}') + echo "adapter-list=$ADAPTER_LIST" >> $GITHUB_OUTPUT + echo "Adapter list: $ADAPTER_LIST" + + # Legacy release: Use changed-adapters.sh calculate-changes: - name: Compute changed adapters + name: Compute changed adapters (Legacy) + needs: determine-release-mode + if: needs.determine-release-mode.outputs.release-mode == 'legacy' runs-on: [ubuntu-latest] env: BUILD_ALL: ${{ inputs.build-all }} @@ -44,18 +228,41 @@ jobs: run: | ./.github/scripts/changed-adapters.sh + # Merge adapter lists from both modes + merge-adapter-lists: + name: Merge Adapter Lists + needs: [determine-release-mode, parse-selective-adapters, calculate-changes] + if: always() && !cancelled() + runs-on: ubuntu-latest + outputs: + adapter-list: ${{ steps.merge.outputs.adapter-list }} + release-mode: ${{ needs.determine-release-mode.outputs.release-mode }} + steps: + - name: Merge lists + id: merge + env: + RELEASE_MODE: ${{ needs.determine-release-mode.outputs.release-mode }} + SELECTIVE_LIST: ${{ needs.parse-selective-adapters.outputs.adapter-list }} + LEGACY_LIST: ${{ needs.calculate-changes.outputs.adapter-list }} + run: | + if [ "$RELEASE_MODE" = "selective" ]; then + echo "adapter-list=$SELECTIVE_LIST" >> $GITHUB_OUTPUT + else + echo "adapter-list=$LEGACY_LIST" >> $GITHUB_OUTPUT + fi + create-ecr: name: Create ECR for ${{ matrix.adapter.shortName }} runs-on: ubuntu-latest - needs: [calculate-changes] - if: needs.calculate-changes.outputs.adapter-list != '[]' + needs: [merge-adapter-lists] + if: needs.merge-adapter-lists.outputs.adapter-list != '[]' && inputs.dry-run != true permissions: # These are needed for the configure-aws-credentials action id-token: write contents: read environment: release strategy: max-parallel: 20 - matrix: ${{fromJson(needs.calculate-changes.outputs.adapter-list)}} + matrix: ${{fromJson(needs.merge-adapter-lists.outputs.adapter-list)}} env: ECR_URL: public.ecr.aws/chainlink ECR_REPO: adapters/${{ matrix.adapter.shortName }}-adapter @@ -77,14 +284,15 @@ jobs: name: Fetch and publish ${{ matrix.adapter.shortName }} runs-on: ubuntu-latest needs: - - calculate-changes + - merge-adapter-lists environment: release + if: needs.merge-adapter-lists.outputs.adapter-list != '[]' && inputs.dry-run != true permissions: # These are needed for the configure-aws-credentials action id-token: write contents: read strategy: max-parallel: 20 - matrix: ${{fromJson(needs.calculate-changes.outputs.adapter-list)}} + matrix: ${{fromJson(needs.merge-adapter-lists.outputs.adapter-list)}} env: PUBLIC_ECR_URL: public.ecr.aws/chainlink PRIVATE_ECR_URL: ${{ secrets.SDLC_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION_ECR_PRIVATE }}.amazonaws.com @@ -102,16 +310,28 @@ jobs: - name: Copy images with attestations from private to public ECR env: AWS_REGION: ${{ secrets.AWS_REGION_ECR_PRIVATE }} - SOURCE_IMAGE: ${{ env.PRIVATE_ECR_URL }}/${{ env.ECR_REPO }}:${{ matrix.adapter.version }} - DEST_IMAGE: ${{ env.PUBLIC_ECR_URL }}/${{ env.ECR_REPO }}:${{ matrix.adapter.version }} + ADAPTER_VERSION: ${{ matrix.adapter.version }} + IMAGE_DIGEST: ${{ matrix.adapter.imageDigest }} run: | PRIVATE_ECR_PASSWORD=$(aws ecr get-login-password --region "${AWS_REGION}") echo "::add-mask::${PRIVATE_ECR_PASSWORD}" PUBLIC_ECR_PASSWORD=$(aws ecr-public get-login-password --region us-east-1) echo "::add-mask::${PUBLIC_ECR_PASSWORD}" + # Determine source image reference (prefer digest over tag for security) + if [ -n "$IMAGE_DIGEST" ] && [ "$IMAGE_DIGEST" != "null" ]; then + SOURCE_IMAGE="${{ env.PRIVATE_ECR_URL }}/${{ env.ECR_REPO }}@${IMAGE_DIGEST}" + echo "🔒 SECURITY: Copying by digest (exact image that was tested)" + echo " Digest: $IMAGE_DIGEST" + else + SOURCE_IMAGE="${{ env.PRIVATE_ECR_URL }}/${{ env.ECR_REPO }}:${ADAPTER_VERSION}" + echo "⚠️ Copying by version tag (digest not available)" + fi + + DEST_IMAGE="${{ env.PUBLIC_ECR_URL }}/${{ env.ECR_REPO }}:${ADAPTER_VERSION}" + # Copy all architectures, attestations (SBOM, provenance), and signatures - echo "Copying versioned image: ${SOURCE_IMAGE} -> ${DEST_IMAGE}" + echo "Copying image: ${SOURCE_IMAGE} -> ${DEST_IMAGE}" skopeo copy \ --all \ --preserve-digests \ @@ -120,12 +340,65 @@ jobs: --dest-creds AWS:${PUBLIC_ECR_PASSWORD} \ docker://${SOURCE_IMAGE} \ docker://${DEST_IMAGE} + + echo "✅ Image published to public ECR" + + # Update RELEASES.yaml after successful selective release + update-releases-file: + name: Update RELEASES.yaml + needs: [merge-adapter-lists, publish-adapter-images] + if: needs.merge-adapter-lists.outputs.release-mode == 'selective' && inputs.dry-run != true + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install yq + run: | + sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + + - name: Move adapters from pending to released + env: + ADAPTER_LIST: ${{ needs.merge-adapter-lists.outputs.adapter-list }} + run: | + # Make script executable + chmod +x ./scripts/release-manager.sh + + # Extract adapter names and mark as released + ADAPTERS=$(echo "$ADAPTER_LIST" | jq -r '.adapter[].shortName') + + for adapter in $ADAPTERS; do + echo "Marking $adapter as released" + ./scripts/release-manager.sh release "$adapter" + done + + # Configure git + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Commit and push + git add RELEASES.yaml + + ADAPTER_NAMES=$(echo "$ADAPTERS" | tr '\n' ',' | sed 's/,$//') + git commit -m "Mark adapters as released: $ADAPTER_NAMES + +Released via selective release workflow +Run ID: ${{ github.run_id }} +Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + + git push gh-release: name: GH Release runs-on: ubuntu-latest needs: + - merge-adapter-lists - publish-adapter-images + if: needs.merge-adapter-lists.outputs.adapter-list != '[]' && inputs.dry-run != true && needs.merge-adapter-lists.outputs.release-mode == 'legacy' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token steps: @@ -149,3 +422,266 @@ jobs: tag_name: v${{ steps.get-version.outputs.result }} name: Release v${{ steps.get-version.outputs.result }} body_path: pr_body.tmp + + # Create GitHub releases for selective releases (per-adapter) + gh-release-selective: + name: Create GitHub Release for ${{ matrix.adapter.shortName }} + runs-on: ubuntu-latest + needs: + - merge-adapter-lists + - publish-adapter-images + if: needs.merge-adapter-lists.outputs.adapter-list != '[]' && inputs.dry-run != true && needs.merge-adapter-lists.outputs.release-mode == 'selective' + strategy: + matrix: ${{fromJson(needs.merge-adapter-lists.outputs.adapter-list)}} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Get adapter package path + id: get-path + run: | + ADAPTER_PATH="packages/${{ matrix.adapter.shortName }}" + if [ ! -d "$ADAPTER_PATH" ]; then + ADAPTER_PATH="packages/sources/${{ matrix.adapter.shortName }}" + fi + + if [ ! -d "$ADAPTER_PATH" ]; then + echo "❌ Error: Could not find adapter directory" + exit 1 + fi + + echo "path=$ADAPTER_PATH" >> $GITHUB_OUTPUT + echo "Found adapter at: $ADAPTER_PATH" + + - name: Get release notes from CHANGELOG + id: get-notes + run: | + ADAPTER_PATH="${{ steps.get-path.outputs.path }}" + VERSION="${{ matrix.adapter.version }}" + + # Try to extract release notes from CHANGELOG.md + if [ -f "$ADAPTER_PATH/CHANGELOG.md" ]; then + # Extract the section for this version + NOTES=$(awk "/^## \[${VERSION}\]/,/^## \[/" "$ADAPTER_PATH/CHANGELOG.md" | sed '1d;$d' || echo "") + + if [ -n "$NOTES" ]; then + echo "$NOTES" > release_notes.tmp + echo "✅ Extracted release notes from CHANGELOG.md" + else + echo "No specific release notes found for v${VERSION}" > release_notes.tmp + echo "⚠️ Using default release notes" + fi + else + echo "Release v${VERSION} of ${{ matrix.adapter.shortName }} adapter" > release_notes.tmp + echo "⚠️ No CHANGELOG.md found, using minimal notes" + fi + + # Add security info if digest is available + if [ -n "${{ matrix.adapter.imageDigest }}" ]; then + echo "" >> release_notes.tmp + echo "---" >> release_notes.tmp + echo "" >> release_notes.tmp + echo "### 🔒 Security Verification" >> release_notes.tmp + echo "" >> release_notes.tmp + echo "This release includes full security verification:" >> release_notes.tmp + echo "- ✅ Built from verified git commit" >> release_notes.tmp + echo "- ✅ Tested in internal infrastructure" >> release_notes.tmp + echo "- ✅ Released with verified image digest" >> release_notes.tmp + echo "- **Image Digest**: \`${{ matrix.adapter.imageDigest }}\`" >> release_notes.tmp + fi + + # Add public ECR info + echo "" >> release_notes.tmp + echo "---" >> release_notes.tmp + echo "" >> release_notes.tmp + echo "### 📦 Docker Image" >> release_notes.tmp + echo "" >> release_notes.tmp + echo "\`\`\`bash" >> release_notes.tmp + echo "docker pull public.ecr.aws/chainlink/adapters/${{ matrix.adapter.shortName }}-adapter:${VERSION}" >> release_notes.tmp + echo "\`\`\`" >> release_notes.tmp + + - name: Create GitHub Release + uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1 + with: + tag_name: ${{ matrix.adapter.shortName }}-adapter-v${{ matrix.adapter.version }} + name: ${{ matrix.adapter.shortName }}-adapter v${{ matrix.adapter.version }} + body_path: release_notes.tmp + draft: false + prerelease: false + + # Summary job for selective releases + release-summary: + name: Release Summary + needs: [merge-adapter-lists, publish-adapter-images, update-releases-file, gh-release-selective] + if: always() && needs.merge-adapter-lists.outputs.release-mode == 'selective' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Print summary + env: + ADAPTER_LIST: ${{ needs.merge-adapter-lists.outputs.adapter-list }} + DRY_RUN: ${{ inputs.dry-run }} + PUBLISH_STATUS: ${{ needs.publish-adapter-images.result }} + UPDATE_STATUS: ${{ needs.update-releases-file.result }} + run: | + echo "# 🚀 Per-Adapter Release Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "$DRY_RUN" = "true" ]; then + echo "**Mode**: 🧪 Dry Run (no actual release)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "> This was a test run. No images were published." >> $GITHUB_STEP_SUMMARY + else + echo "**Mode**: ✅ Live Release" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Adapters Released" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Parse and display adapters + ADAPTERS=$(echo "$ADAPTER_LIST" | jq -r '.adapter[] | "- **\(.shortName)** v\(.version)"') + echo "$ADAPTERS" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Status" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Step | Status |" >> $GITHUB_STEP_SUMMARY + echo "|------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Publish to Public ECR | $PUBLISH_STATUS |" >> $GITHUB_STEP_SUMMARY + echo "| GitHub Releases Created | ${{ needs.gh-release-selective.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Update RELEASES.yaml | $UPDATE_STATUS |" >> $GITHUB_STEP_SUMMARY + + if [ "$DRY_RUN" != "true" ] && [ "$PUBLISH_STATUS" = "success" ]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "## ✅ Verification Steps" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 1. Verify Images in Public ECR" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "# Check each adapter is available publicly" >> $GITHUB_STEP_SUMMARY + ADAPTER_NAMES=$(echo "$ADAPTER_LIST" | jq -r '.adapter[].shortName') + for adapter in $ADAPTER_NAMES; do + VERSION=$(echo "$ADAPTER_LIST" | jq -r ".adapter[] | select(.shortName == \"$adapter\") | .version") + echo "aws ecr-public describe-images \\" >> $GITHUB_STEP_SUMMARY + echo " --repository-name adapters/${adapter}-adapter \\" >> $GITHUB_STEP_SUMMARY + echo " --region us-east-1 \\" >> $GITHUB_STEP_SUMMARY + echo " --query 'imageDetails[?contains(imageTags, \`${VERSION}\`)]'" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + done + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 2. Check GitHub Releases" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "GitHub releases have been created for each adapter:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + for adapter in $ADAPTER_NAMES; do + VERSION=$(echo "$ADAPTER_LIST" | jq -r ".adapter[] | select(.shortName == \"$adapter\") | .version") + echo "- [${adapter}-adapter v${VERSION}](https://github.com/${{ github.repository }}/releases/tag/${adapter}-adapter-v${VERSION})" >> $GITHUB_STEP_SUMMARY + done + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 3. Verify RELEASES.yaml Updated" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "# Pull latest and check RELEASES.yaml" >> $GITHUB_STEP_SUMMARY + echo "git pull" >> $GITHUB_STEP_SUMMARY + echo "cat RELEASES.yaml" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "# Adapters should be in 'released' section:" >> $GITHUB_STEP_SUMMARY + for adapter in $ADAPTER_NAMES; do + echo "# - $adapter" >> $GITHUB_STEP_SUMMARY + done + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 4. Monitor in Production" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Watch for any issues with the newly released adapters:" >> $GITHUB_STEP_SUMMARY + echo "- Check error rates in monitoring dashboards" >> $GITHUB_STEP_SUMMARY + echo "- Verify response times are normal" >> $GITHUB_STEP_SUMMARY + echo "- Watch for any alerts" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "## 📚 How to Release Adapters" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Default: Release All Pending" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "# Click 'Run workflow' in GitHub Actions (no inputs needed)" >> $GITHUB_STEP_SUMMARY + echo "# OR from CLI:" >> $GITHUB_STEP_SUMMARY + echo "gh workflow run release.yml" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Optional: Release Specific Adapters" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "#### Single Adapter" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "gh workflow run release.yml -f adapters=coingecko" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "#### Multiple Adapters" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "gh workflow run release.yml -f adapters=coingecko,coinbase,tiingo" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Check What's Ready" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "./scripts/release-manager.sh list" >> $GITHUB_STEP_SUMMARY + echo "./scripts/release-manager.sh status " >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Test First (Dry Run)" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "gh workflow run release.yml -f dry-run=true # All adapters" >> $GITHUB_STEP_SUMMARY + echo "gh workflow run release.yml -f adapters=coingecko -f dry-run=true # One adapter" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + echo "## 🔄 The Release Flow" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "1. **Code Merge** → Changes merged to \`main\` in external-adapters-js" >> $GITHUB_STEP_SUMMARY + echo "2. **Build & Deploy** → Images built and pushed to private ECR" >> $GITHUB_STEP_SUMMARY + echo "3. **Digest PR** → Separate PR created in infra-k8s per adapter" >> $GITHUB_STEP_SUMMARY + echo "4. **Testing** → Each adapter tested independently in internal infra" >> $GITHUB_STEP_SUMMARY + echo "5. **Mark Ready** → Adapter added to \`RELEASES.yaml\` pending section" >> $GITHUB_STEP_SUMMARY + echo "6. **Release** → Trigger this workflow to publish to public ECR" >> $GITHUB_STEP_SUMMARY + echo "7. **Complete** → Adapter moved to \`RELEASES.yaml\` released section" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + echo "## 🆘 Troubleshooting" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Error: Adapter not in RELEASES.yaml" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Solution**: Add the adapter to pending first" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "./scripts/release-manager.sh add " >> $GITHUB_STEP_SUMMARY + echo "git add RELEASES.yaml" >> $GITHUB_STEP_SUMMARY + echo "git commit -m 'Add adapter to pending releases'" >> $GITHUB_STEP_SUMMARY + echo "git push" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Error: Image not found in private ECR" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Solution**: Run the deploy workflow first to build the image" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "gh workflow run deploy.yml" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Need to Rollback?" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Tag the previous version as \`latest\` in public ECR and update MASTERLIST.md" >> $GITHUB_STEP_SUMMARY + + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "💡 **Tip**: Use \`./scripts/release-manager.sh help\` for full CLI documentation" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "📖 **Documentation**: See IMPLEMENTATION_GUIDE.md and QUICK_REFERENCE.md in the repo" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/upsert-release-pr.yml b/.github/workflows/upsert-release-pr.yml index 55e210fff6..52bef012a1 100644 --- a/.github/workflows/upsert-release-pr.yml +++ b/.github/workflows/upsert-release-pr.yml @@ -70,6 +70,44 @@ jobs: run: | yarn generate:master-list -v yarn generate:readme -v + - name: Update RELEASES.yaml with changed adapters + run: | + # Get list of changed adapters from MASTERLIST.md changes + # Find adapters that have version changes in git diff + git diff HEAD~1 MASTERLIST.md | grep "^+" | grep -v "^+++" | grep -v "^+#" | sed 's/^+//' | grep -E "^\|" | awk -F'|' '{print $2,$3}' | while read adapter version; do + adapter=$(echo $adapter | xargs) + version=$(echo $version | xargs) + + if [ -n "$adapter" ] && [ -n "$version" ]; then + echo "Adding $adapter v$version to RELEASES.yaml" + + # Check if yq is installed + if command -v yq &> /dev/null; then + # Add to pending if not already there + if ! yq eval ".pending | has(\"$adapter\")" RELEASES.yaml 2>/dev/null | grep -q "true"; then + yq eval -i ".pending.$adapter.version = \"$version\"" RELEASES.yaml + yq eval -i ".pending.$adapter.tested_in_infra = false" RELEASES.yaml + yq eval -i ".pending.$adapter.infra_digest_pr = \"Pending infra-k8s testing\"" RELEASES.yaml + yq eval -i ".pending.$adapter.infra_merged_at = \"$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\"" RELEASES.yaml + yq eval -i ".pending.$adapter.approved_by = \"@automation\"" RELEASES.yaml + yq eval -i ".pending.$adapter.notes = \"Auto-added from MASTERLIST update\"" RELEASES.yaml + # SECURITY: Track git commit SHA that built this version + yq eval -i ".pending.$adapter.built_from_commit = \"${{ github.sha }}\"" RELEASES.yaml + yq eval -i ".pending.$adapter.built_at = \"$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\"" RELEASES.yaml + echo "✅ Added $adapter to RELEASES.yaml (commit: ${{ github.sha }})" + else + echo "ℹ️ $adapter already in RELEASES.yaml" + fi + else + echo "⚠️ yq not found, skipping RELEASES.yaml update" + fi + fi + done || true # Don't fail if no adapters found + + # NOTE: Infra testing status is now automatically updated by infra-k8s + # When a digest PR is merged in infra-k8s, it triggers notify-ea-digest-merged.yaml + # which pushes updates directly to this Release PR branch + - name: Undo temporary changesets and commit docs run: | git stash @@ -78,6 +116,7 @@ jobs: - name: Commit docs run: | git add MASTERLIST.md + git add RELEASES.yaml git add "*README.md" yarn lint-staged - name: Stash changes for changesets action to pick up in custom script diff --git a/RELEASES.yaml b/RELEASES.yaml new file mode 100644 index 0000000000..e58d22a370 --- /dev/null +++ b/RELEASES.yaml @@ -0,0 +1,134 @@ +# ============================================================================ +# EA Release Tracking System +# ============================================================================ +# +# This file tracks External Adapters through the public release process. +# +# ## Release States +# +# pending: Adapter has completed internal testing and is ready for public release +# released: Adapter has been published to public ECR and is available to users +# +# ## The Release Flow (FULLY AUTOMATED) +# +# 1. Code Merge → Changes merged to external-adapters-js main +# ✅ AUTOMATED: Release PR created/updated +# ✅ AUTOMATED: Adapter added to 'pending' (tested_in_infra: false) +# +# 2. Build & Deploy → Images built and pushed to private ECR +# ✅ AUTOMATED: deploy.yml workflow triggers +# +# 3. Digest PR → Separate PR created per adapter in infra-k8s +# ✅ AUTOMATED: image-dispatcher.yaml creates individual PRs +# +# 4. Testing → Each adapter tested independently in internal infra +# 👤 MANUAL: Team reviews and merges infra-k8s PR +# +# 5. Mark Tested → Adapter marked as tested_in_infra: true +# ✅ AUTOMATED: upsert-release-pr.yml checks infra-k8s for merged PRs +# ✅ AUTOMATED: Release PR updated with tested status on next EA merge +# +# 6. Release → Team triggers release for pending adapters +# 👤 MANUAL: gh workflow run release.yml (releases ALL pending) +# OR: gh workflow run release.yml -f adapters= (specific) +# +# 7. Complete → Adapter moves to 'released' section +# ✅ AUTOMATED: release.yml workflow updates RELEASES.yaml +# +# ## Quick Commands +# +# See what's ready to release: +# ./scripts/release-manager.sh list +# +# Check a specific adapter: +# ./scripts/release-manager.sh status +# +# Add an adapter to pending: +# ./scripts/release-manager.sh add +# +# Release all pending adapters (DEFAULT): +# gh workflow run release.yml +# # Or click "Run workflow" in GitHub Actions (no input needed) +# +# Release specific adapter: +# gh workflow run release.yml -f adapters=coingecko +# +# Release multiple adapters: +# gh workflow run release.yml -f adapters=coingecko,coinbase +# +# Test first (dry run): +# gh workflow run release.yml -f dry-run=true +# +# ## File Format +# +# Adapters are stored as arrays of objects with these fields: +# +# pending: +# - shortName: coingecko # Adapter name +# version: 2.0.1 # Version to release +# infraPrUrl: https://... # Link to merged infra-k8s PR +# team: data-feeds # Owning team +# addedDate: 2024-01-15T10:30:00Z # When added to pending +# +# released: +# - shortName: coinbase +# version: 1.5.0 +# releaseDate: 2024-01-10T14:00:00Z # When released to public +# +# ## Important Notes +# +# - Use scripts/release-manager.sh for all operations (don't edit manually) +# - Adapters are automatically added to pending after infra testing completes +# - Adapters are automatically moved to released after successful release +# - Keep this file in sync with actual deployed versions +# +# ## Troubleshooting +# +# "Adapter not found" error when releasing: +# → Add it to pending first: ./scripts/release-manager.sh add +# +# "Image not found in private ECR" error: +# → Run deploy workflow first: gh workflow run deploy.yml +# +# Need to rollback? +# → Tag previous version as 'latest' in public ECR +# → See IMPLEMENTATION_GUIDE.md for rollback procedure +# +# ## Documentation +# +# - Full Release Guide: IMPLEMENTATION_GUIDE.md +# - Quick Reference: QUICK_REFERENCE.md +# - Testing Guide: TESTING_GUIDE.md +# - Deployment Checklist: DEPLOYMENT_CHECKLIST.md +# +# ============================================================================ + +schema_version: "1.0" + +# Adapters that completed internal testing and are ready for public release +pending: {} + # Adapters will be automatically added here by upsert-release-pr workflow + # You can also manually add using: ./scripts/release-manager.sh add + # + # Example entry: + # coingecko: + # version: "1.5.0" + # built_from_commit: "abc123def456" # Git SHA that built this (security) + # image_digest: "sha256:789abc..." # Docker image digest (security - extracted from infra PR) + # tested_in_infra: true + # infra_digest_pr: "https://github.com/smartcontractkit/infra-k8s/pull/12345" + # infra_merged_at: "2024-10-21T10:00:00Z" + # approved_by: "@automation" + # notes: "Auto-added from MASTERLIST update" + +# Adapters that have been released to public ECR (maintained for audit trail) +released: {} + # Adapters are automatically moved here by the release workflow after successful publish + # + # Example entry: + # tiingo: + # version: "1.2.3" + # released_at: "2024-10-19T12:00:00Z" + # release_commit: "abc123def456" + # public_ecr_tag: "1.2.3" + diff --git a/scripts/release-manager.sh b/scripts/release-manager.sh new file mode 100755 index 0000000000..937f9bc8c1 --- /dev/null +++ b/scripts/release-manager.sh @@ -0,0 +1,344 @@ +#!/bin/bash +set -e + +# EA Release Manager +# Helper script to manage the RELEASES.yaml file +# +# Usage: +# ./scripts/release-manager.sh add [notes] +# ./scripts/release-manager.sh release +# ./scripts/release-manager.sh list +# ./scripts/release-manager.sh status +# ./scripts/release-manager.sh remove + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +RELEASES_FILE="$REPO_ROOT/RELEASES.yaml" + +# Check if yq is installed +if ! command -v yq &> /dev/null; then + echo "❌ Error: yq is not installed" + echo " Install: brew install yq" + exit 1 +fi + +# Check if RELEASES.yaml exists +if [ ! -f "$RELEASES_FILE" ]; then + echo "❌ Error: RELEASES.yaml not found at $RELEASES_FILE" + exit 1 +fi + +# Add adapter to pending releases +add_pending() { + local adapter=$1 + local version=$2 + local pr_url=$3 + local approver=$4 + local notes="${5:-Tested and approved for release}" + + if [ -z "$adapter" ] || [ -z "$version" ] || [ -z "$pr_url" ] || [ -z "$approver" ]; then + echo "❌ Error: Missing required arguments" + echo " Usage: $0 add [notes]" + echo " Example: $0 add coingecko 1.5.0 https://github.com/.../pull/123 @myteam" + exit 1 + fi + + # Check if adapter already exists in pending + if yq eval ".pending | has(\"$adapter\")" "$RELEASES_FILE" | grep -q "true"; then + echo "⚠️ Warning: $adapter is already in pending releases" + read -p " Overwrite? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo " Cancelled" + exit 0 + fi + fi + + # Add to pending + yq eval -i ".pending.$adapter.version = \"$version\"" "$RELEASES_FILE" + yq eval -i ".pending.$adapter.tested_in_infra = true" "$RELEASES_FILE" + yq eval -i ".pending.$adapter.infra_digest_pr = \"$pr_url\"" "$RELEASES_FILE" + yq eval -i ".pending.$adapter.infra_merged_at = \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"" "$RELEASES_FILE" + yq eval -i ".pending.$adapter.approved_by = \"$approver\"" "$RELEASES_FILE" + yq eval -i ".pending.$adapter.notes = \"$notes\"" "$RELEASES_FILE" + + echo "✅ Added $adapter v$version to pending releases" + echo "" + echo "Next steps:" + echo " 1. Review RELEASES.yaml to confirm details" + echo " 2. Commit the change: git add RELEASES.yaml && git commit -m 'Add $adapter to pending releases'" + echo " 3. Push: git push" + echo " 4. Release when ready: gh workflow run release.yml -f adapters=$adapter" +} + +# Mark adapter as tested (after infra PR merges) +mark_tested() { + local adapter=$1 + local pr_url=$2 + + if [ -z "$adapter" ]; then + echo "❌ Error: Missing adapter name" + echo " Usage: $0 mark-tested " + exit 1 + fi + + # Check if adapter exists in pending + if ! yq eval ".pending | has(\"$adapter\")" "$RELEASES_FILE" | grep -q "true"; then + echo "❌ Error: $adapter is not in pending releases" + echo " Add it first with: $0 add $adapter " + exit 1 + fi + + # Update infra testing status + yq eval -i ".pending.$adapter.tested_in_infra = true" "$RELEASES_FILE" + if [ -n "$pr_url" ]; then + yq eval -i ".pending.$adapter.infra_digest_pr = \"$pr_url\"" "$RELEASES_FILE" + fi + yq eval -i ".pending.$adapter.infra_merged_at = \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"" "$RELEASES_FILE" + + echo "✅ Marked $adapter as tested in infra" + echo "" + echo "Next steps:" + echo " 1. Commit: git add RELEASES.yaml && git commit -m 'Mark $adapter as tested in infra'" + echo " 2. Push: git push" + echo " 3. Release when ready: gh workflow run release.yml" +} + +# Move adapter from pending to released +mark_released() { + local adapter=$1 + + if [ -z "$adapter" ]; then + echo "❌ Error: Missing adapter name" + echo " Usage: $0 release " + exit 1 + fi + + # Check if adapter exists in pending + if ! yq eval ".pending | has(\"$adapter\")" "$RELEASES_FILE" | grep -q "true"; then + echo "❌ Error: $adapter is not in pending releases" + echo " Use '$0 list' to see pending adapters" + exit 1 + fi + + # Get version from pending + local version=$(yq eval ".pending.$adapter.version" "$RELEASES_FILE") + + # Move to released + yq eval -i ".released.$adapter.version = \"$version\"" "$RELEASES_FILE" + yq eval -i ".released.$adapter.released_at = \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"" "$RELEASES_FILE" + yq eval -i ".released.$adapter.release_commit = \"$(git rev-parse HEAD)\"" "$RELEASES_FILE" + yq eval -i ".released.$adapter.public_ecr_tag = \"$version\"" "$RELEASES_FILE" + + # Remove from pending + yq eval -i "del(.pending.$adapter)" "$RELEASES_FILE" + + echo "✅ Marked $adapter v$version as released" + echo "" + echo "Next steps:" + echo " 1. Commit: git add RELEASES.yaml && git commit -m 'Mark $adapter as released'" + echo " 2. Push: git push" +} + +# List pending releases +list_pending() { + echo "📋 Adapters ready for release:" + echo "" + + local pending_count=$(yq eval '.pending | length' "$RELEASES_FILE") + + if [ "$pending_count" -eq 0 ]; then + echo " No adapters in pending state" + echo "" + echo "💡 To add an adapter:" + echo " ./scripts/release-manager.sh add " + return + fi + + yq eval '.pending | to_entries | .[] | + " • " + .key + " v" + .value.version + + "\n Approved by: " + .value.approved_by + + "\n PR: " + .value.infra_digest_pr + + "\n Notes: " + .value.notes + "\n"' "$RELEASES_FILE" + + echo "" + echo "💡 To release:" + echo " Single: gh workflow run release.yml -f adapters=" + echo " All: gh workflow run release.yml -f adapters=all" +} + +# Show status of a specific adapter +show_status() { + local adapter=$1 + + if [ -z "$adapter" ]; then + echo "❌ Error: Missing adapter name" + echo " Usage: $0 status " + exit 1 + fi + + echo "📊 Status for $adapter:" + echo "" + + # Check pending + if yq eval ".pending | has(\"$adapter\")" "$RELEASES_FILE" | grep -q "true"; then + echo " Status: ⏳ Pending Release" + echo "" + yq eval ".pending.$adapter | + \" Version: \" + .version + \"\n\" + + \" Tested: \" + (.tested_in_infra | tostring) + \"\n\" + + \" Approved by: \" + .approved_by + \"\n\" + + \" Merged at: \" + .infra_merged_at + \"\n\" + + \" PR: \" + .infra_digest_pr + \"\n\" + + \" Notes: \" + .notes" "$RELEASES_FILE" + echo "" + echo "💡 To release: gh workflow run release.yml -f adapters=$adapter" + return + fi + + # Check released + if yq eval ".released | has(\"$adapter\")" "$RELEASES_FILE" | grep -q "true"; then + echo " Status: ✅ Released" + echo "" + yq eval ".released.$adapter | + \" Version: \" + .version + \"\n\" + + \" Released at: \" + .released_at + \"\n\" + + \" Commit: \" + .release_commit + \"\n\" + + \" Public ECR tag: \" + .public_ecr_tag" "$RELEASES_FILE" + return + fi + + echo " Status: ❓ Not Found" + echo "" + echo " $adapter is not in pending or released state" + echo " Use '$0 add $adapter ' to add it" +} + +# Remove adapter from pending (cancel release) +remove_pending() { + local adapter=$1 + + if [ -z "$adapter" ]; then + echo "❌ Error: Missing adapter name" + echo " Usage: $0 remove " + exit 1 + fi + + # Check if adapter exists in pending + if ! yq eval ".pending | has(\"$adapter\")" "$RELEASES_FILE" | grep -q "true"; then + echo "❌ Error: $adapter is not in pending releases" + exit 1 + fi + + # Remove from pending + yq eval -i "del(.pending.$adapter)" "$RELEASES_FILE" + + echo "✅ Removed $adapter from pending releases" + echo "" + echo "Next steps:" + echo " 1. Commit: git add RELEASES.yaml && git commit -m 'Remove $adapter from pending releases'" + echo " 2. Push: git push" +} + +# Show help +show_help() { + cat < [arguments] + +Commands: + add [notes] + Add an adapter to pending releases after it has been tested in infra + + Example: + $0 add coingecko 1.5.0 https://github.com/.../pull/123 @myteam "Tested for 48h" + + mark-tested [infra-pr-url] + Mark an adapter as tested in infra (after infra-k8s PR merges) + Updates tested_in_infra flag to true + + NOTE: This is now AUTOMATED via infra-k8s workflow! + Only use this command for manual overrides or corrections. + + Example: + $0 mark-tested coingecko https://github.com/.../pull/456 + + release + Mark an adapter as released (moves from pending to released) + This is typically done automatically by the release workflow + + Example: + $0 release coingecko + + list + List all adapters in pending state (ready for release) + + Example: + $0 list + + status + Show detailed status of a specific adapter + + Example: + $0 status coingecko + + remove + Remove an adapter from pending (cancel its release) + + Example: + $0 remove coingecko + + help + Show this help message + +Examples: + # After infra testing is complete, add adapter to pending + $0 add coingecko 1.5.0 https://github.com/.../pull/123 @data-feeds-team + + # List what's ready to release + $0 list + + # Check status of specific adapter + $0 status coingecko + + # Release the adapter + gh workflow run release.yml -f adapters=coingecko + +See IMPLEMENTATION_GUIDE.md for detailed workflow documentation. +EOF +} + +# Main command dispatcher +case "${1:-help}" in + add) + add_pending "$2" "$3" "$4" "$5" "$6" + ;; + mark-tested) + mark_tested "$2" "$3" + ;; + release) + mark_released "$2" + ;; + list) + list_pending + ;; + status) + show_status "$2" + ;; + remove) + remove_pending "$2" + ;; + help|--help|-h) + show_help + ;; + *) + echo "❌ Error: Unknown command '$1'" + echo "" + show_help + exit 1 + ;; +esac + + +