diff --git a/.github/workflows/_conda-forge-package-release.yml b/.github/workflows/_conda-forge-package-release.yml new file mode 100644 index 0000000000..347489124e --- /dev/null +++ b/.github/workflows/_conda-forge-package-release.yml @@ -0,0 +1,99 @@ +name: _Conda Forge Package Release + +on: + workflow_call: + inputs: + package: + description: 'Package being released (e.g. sagemaker-train)' + required: true + type: string + feedstock: + description: 'Feedstock repo (e.g. conda-forge/sagemaker-train-feedstock)' + required: true + type: string + pr_search: + description: 'PR title search string (e.g. sagemaker-train v1.6.0)' + required: true + type: string + version: + description: 'Version of this package being released (e.g. 1.6.0)' + required: true + type: string + dep_package: + description: 'Conda dependency to wait for before retrying CI (e.g. sagemaker-core)' + required: true + type: string + dep_version: + description: 'Version of the dependency to wait for (e.g. 2.6.0)' + required: true + type: string + poll_interval: + required: true + type: string + max_attempts: + required: true + type: string + secrets: + token: + required: true + +jobs: + release: + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ secrets.token }} + steps: + - name: Wait for dependency (${{ inputs.dep_package }}==${{ inputs.dep_version }}) on conda-forge + run: | + PACKAGE="${{ inputs.dep_package }}" + VERSION="${{ inputs.dep_version }}" + echo "Waiting for ${PACKAGE}==${VERSION} on conda-forge..." + for i in $(seq 1 ${{ inputs.max_attempts }}); do + RESULT=$(conda search -c conda-forge --override-channels \ + "${PACKAGE}==${VERSION}" --json 2>/dev/null \ + | python3 -c "import sys,json; d=json.load(sys.stdin); print('found' if d.get('$PACKAGE') else 'not_found')" \ + 2>/dev/null || echo "not_found") + echo "Attempt $i: ${RESULT}" + [ "$RESULT" = "found" ] && echo "${PACKAGE}==${VERSION} is available." && exit 0 + sleep ${{ inputs.poll_interval }} + done + echo "Timed out waiting for ${PACKAGE}==${VERSION}." && exit 1 + + - name: Merge and wait for ${{ inputs.package }} feedstock PR + run: | + REPO="${{ inputs.feedstock }}" + SEARCH="${{ inputs.pr_search }}" + for i in $(seq 1 ${{ inputs.max_attempts }}); do + STATE=$(gh pr list --repo "$REPO" --state all \ + --search "$SEARCH" --json state -q '.[0].state // "NOT_FOUND"') + echo "Attempt $i: ${STATE}" + [ "$STATE" = "MERGED" ] && exit 0 + PR=$(gh pr list --repo "$REPO" --state open \ + --search "$SEARCH" --json number -q '.[0].number') + if [ -n "$PR" ]; then + CI_STATUS=$(gh pr view "$PR" --repo "$REPO" \ + --json statusCheckRollup -q ' + .statusCheckRollup | map( + if .__typename == "CheckRun" then .conclusion + elif .__typename == "StatusContext" then .state + else null end + ) | if length == 0 then "pending" + elif any(. == null or . == "" or . == "IN_PROGRESS" or . == "QUEUED" or . == "WAITING" or . == "PENDING") then "pending" + elif all(. == "SUCCESS") then "success" + else "failure" end') + echo "CI status: ${CI_STATUS}" + if [ "$CI_STATUS" = "success" ]; then + echo "CI passed, merging PR #${PR}..." + gh pr merge "$PR" --repo "$REPO" --merge 2>/dev/null || true + elif [ "$CI_STATUS" = "failure" ]; then + echo "CI failed, retriggering..." + BRANCH=$(gh pr view "$PR" --repo "$REPO" --json headRefName -q .headRefName) + RUN_ID=$(gh run list --repo "$REPO" --branch "$BRANCH" \ + --json databaseId,conclusion -q \ + '[.[] | select(.conclusion=="failure")][0].databaseId') + [ -n "$RUN_ID" ] && gh run rerun "$RUN_ID" --repo "$REPO" --failed || true + fi + fi + sleep ${{ inputs.poll_interval }} + done + echo "Timed out waiting for ${{ inputs.package }} PR to merge." && exit 1 diff --git a/.github/workflows/conda-forge-release-chain.yml b/.github/workflows/conda-forge-release-chain.yml new file mode 100644 index 0000000000..8c49014b74 --- /dev/null +++ b/.github/workflows/conda-forge-release-chain.yml @@ -0,0 +1,122 @@ +name: Conda Forge Release Chain + +on: + workflow_dispatch: + inputs: + poll_interval_seconds: + description: 'Seconds between polls (default: 300)' + required: false + default: '300' + timeout_attempts: + description: 'Max poll attempts per package before failing (default: 20 = 100min at 300s)' + required: false + default: '20' + +jobs: + read-versions: + runs-on: ubuntu-latest + outputs: + core: ${{ steps.v.outputs.core }} + train: ${{ steps.v.outputs.train }} + serve: ${{ steps.v.outputs.serve }} + mlops: ${{ steps.v.outputs.mlops }} + pysdk: ${{ steps.v.outputs.pysdk }} + meta: ${{ steps.v.outputs.meta }} + steps: + - uses: actions/checkout@v4 + - name: Read versions + id: v + run: | + echo "core=$(cat sagemaker-core/VERSION)" >> $GITHUB_OUTPUT + echo "train=$(cat sagemaker-train/VERSION)" >> $GITHUB_OUTPUT + echo "serve=$(cat sagemaker-serve/VERSION)" >> $GITHUB_OUTPUT + echo "mlops=$(cat sagemaker-mlops/VERSION)" >> $GITHUB_OUTPUT + echo "pysdk=$(cat VERSION)" >> $GITHUB_OUTPUT + echo "meta=$(cat VERSION)" >> $GITHUB_OUTPUT + echo "Versions:" + echo " sagemaker-core: $(cat sagemaker-core/VERSION)" + echo " sagemaker-train: $(cat sagemaker-train/VERSION)" + echo " sagemaker-serve: $(cat sagemaker-serve/VERSION)" + echo " sagemaker-mlops: $(cat sagemaker-mlops/VERSION)" + echo " sagemaker-python-sdk: $(cat VERSION)" + echo " sagemaker (meta): $(cat VERSION)" + + # sagemaker-train waits for sagemaker-core + release-sagemaker-train: + needs: read-versions + uses: ./.github/workflows/_conda-forge-package-release.yml + with: + package: sagemaker-train + feedstock: conda-forge/sagemaker-train-feedstock + pr_search: "sagemaker-train v${{ needs.read-versions.outputs.train }}" + version: ${{ needs.read-versions.outputs.train }} + dep_package: sagemaker-core + dep_version: ${{ needs.read-versions.outputs.core }} + poll_interval: ${{ github.event.inputs.poll_interval_seconds }} + max_attempts: ${{ github.event.inputs.timeout_attempts }} + secrets: + token: ${{ secrets.CONDA_FORGE_RELEASE }} + + # sagemaker-serve waits for sagemaker-train + release-sagemaker-serve: + needs: [read-versions, release-sagemaker-train] + uses: ./.github/workflows/_conda-forge-package-release.yml + with: + package: sagemaker-serve + feedstock: conda-forge/sagemaker-serve-feedstock + pr_search: "sagemaker-serve v${{ needs.read-versions.outputs.serve }}" + version: ${{ needs.read-versions.outputs.serve }} + dep_package: sagemaker-train + dep_version: ${{ needs.read-versions.outputs.train }} + poll_interval: ${{ github.event.inputs.poll_interval_seconds }} + max_attempts: ${{ github.event.inputs.timeout_attempts }} + secrets: + token: ${{ secrets.CONDA_FORGE_RELEASE }} + + # sagemaker-mlops waits for sagemaker-serve + release-sagemaker-mlops: + needs: [read-versions, release-sagemaker-serve] + uses: ./.github/workflows/_conda-forge-package-release.yml + with: + package: sagemaker-mlops + feedstock: conda-forge/sagemaker-mlops-feedstock + pr_search: "sagemaker-mlops v${{ needs.read-versions.outputs.mlops }}" + version: ${{ needs.read-versions.outputs.mlops }} + dep_package: sagemaker-serve + dep_version: ${{ needs.read-versions.outputs.serve }} + poll_interval: ${{ github.event.inputs.poll_interval_seconds }} + max_attempts: ${{ github.event.inputs.timeout_attempts }} + secrets: + token: ${{ secrets.CONDA_FORGE_RELEASE }} + + # sagemaker-python-sdk waits for sagemaker-mlops + release-sagemaker-python-sdk: + needs: [read-versions, release-sagemaker-mlops] + uses: ./.github/workflows/_conda-forge-package-release.yml + with: + package: sagemaker-python-sdk + feedstock: conda-forge/sagemaker-python-sdk-feedstock + pr_search: "[bot-automerge] sagemaker-python-sdk v${{ needs.read-versions.outputs.pysdk }}" + version: ${{ needs.read-versions.outputs.pysdk }} + dep_package: sagemaker-mlops + dep_version: ${{ needs.read-versions.outputs.mlops }} + poll_interval: ${{ github.event.inputs.poll_interval_seconds }} + max_attempts: ${{ github.event.inputs.timeout_attempts }} + secrets: + token: ${{ secrets.CONDA_FORGE_RELEASE }} + + # sagemaker (meta) waits for sagemaker-python-sdk + release-sagemaker: + needs: [read-versions, release-sagemaker-python-sdk] + uses: ./.github/workflows/_conda-forge-package-release.yml + with: + package: sagemaker + feedstock: conda-forge/sagemaker-feedstock + pr_search: "[bot-automerge] sagemaker v${{ needs.read-versions.outputs.meta }}" + version: ${{ needs.read-versions.outputs.meta }} + dep_package: sagemaker-python-sdk + dep_version: ${{ needs.read-versions.outputs.pysdk }} + poll_interval: ${{ github.event.inputs.poll_interval_seconds }} + max_attempts: ${{ github.event.inputs.timeout_attempts }} + secrets: + token: ${{ secrets.CONDA_FORGE_RELEASE }}