diff --git a/.github/workflows/canary.yaml b/.github/workflows/canary.yaml index 83ddadb9..f371fe4f 100644 --- a/.github/workflows/canary.yaml +++ b/.github/workflows/canary.yaml @@ -6,7 +6,230 @@ on: - created jobs: - trigger-deploy: - name: Canary Deploy - uses: jupiterone/github-internal/.github/workflows/monorepo-canary-release.yaml@v1 - secrets: inherit + # Gate job to check authorization before running the main workflow + check-authorization: + name: Check Authorization + runs-on: ubuntu-latest + if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/canary-release') }} + outputs: + is-authorized: ${{ steps.check.outputs.authorized }} + steps: + - name: Check if user is authorized + id: check + uses: actions/github-script@v7 + with: + script: | + const authorAssociation = context.payload.comment.author_association; + const authorizedRoles = ['OWNER', 'MEMBER', 'COLLABORATOR']; + const isAuthorized = authorizedRoles.includes(authorAssociation); + + console.log(`Author: ${context.payload.comment.user.login}`); + console.log(`Association: ${authorAssociation}`); + console.log(`Authorized: ${isAuthorized}`); + + core.setOutput('authorized', isAuthorized ? 'true' : 'false'); + + if (!isAuthorized) { + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: '-1', + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `⛔ **Unauthorized**: Only organization members can trigger canary releases.\n\n` + + `User \`${context.payload.comment.user.login}\` has association: \`${authorAssociation}\`` + }); + } + + canary-release: + name: Canary Release + runs-on: ubuntu-latest + needs: check-authorization + if: ${{ needs.check-authorization.outputs.is-authorized == 'true' }} + # Environment with protection rules - configure in GitHub Settings > Environments + environment: canary-publish + permissions: + contents: write + pull-requests: write + id-token: write + steps: + - name: Add reaction to comment + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'eyes', + }); + + - name: Post starting comment + id: start-comment + uses: actions/github-script@v7 + with: + script: | + const comment = await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `🚀 Canary release workflow has been triggered.\n\nYou can follow the progress [here](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}).` + }); + return comment.data.id; + result-encoding: string + + - name: Checkout PR + uses: actions/checkout@v4 + with: + ref: refs/pull/${{ github.event.issue.number }}/head + fetch-depth: 0 + token: ${{ secrets.AUTO_GITHUB_PAT_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - name: Configure npm for JupiterOne packages + run: | + echo "@jupiterone:registry=https://npm.pkg.github.com" >> .npmrc + echo "//npm.pkg.github.com/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" >> .npmrc + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" >> .npmrc + + - name: Install dependencies + run: npm ci --include=optional + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + + - name: Configure git + run: | + git config --global user.email "automation@jupiterone.com" + git config --global user.name "Automation" + + - name: Get changed packages + id: changed + run: | + # Get list of packages changed since main + CHANGED=$(npx lerna changed --json 2>/dev/null || echo "[]") + echo "Changed packages: $CHANGED" + echo "packages=$CHANGED" >> $GITHUB_OUTPUT + + # Check if there are any changed packages + if [ "$CHANGED" = "[]" ]; then + echo "has_changes=false" >> $GITHUB_OUTPUT + else + echo "has_changes=true" >> $GITHUB_OUTPUT + fi + + - name: Build packages + if: steps.changed.outputs.has_changes == 'true' + run: npm run build:dist + + - name: Publish canary versions + id: publish + if: steps.changed.outputs.has_changes == 'true' + run: | + PRERELEASE_ID="canary-${{ github.event.issue.number }}-${{ github.run_attempt }}" + + # Version bump with canary prerelease + npx lerna version prerelease --preid "$PRERELEASE_ID" --no-git-tag-version --no-push --yes + + # Publish with canary tag + npx lerna publish from-package --dist-tag canary --yes 2>&1 | tee publish-output.txt + + # Extract published versions + PUBLISHED=$(grep -E "Successfully published" publish-output.txt || echo "") + echo "published<> $GITHUB_OUTPUT + echo "$PUBLISHED" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + GH_TOKEN: ${{ secrets.AUTO_GITHUB_PAT_TOKEN }} + + - name: Get published versions + id: versions + if: steps.changed.outputs.has_changes == 'true' + run: | + # Get the canary versions from package.json files + VERSIONS="" + for pkg in $(echo '${{ steps.changed.outputs.packages }}' | jq -r '.[].name'); do + PKG_PATH=$(echo '${{ steps.changed.outputs.packages }}' | jq -r ".[] | select(.name == \"$pkg\") | .location") + VERSION=$(jq -r '.version' "$PKG_PATH/package.json") + VERSIONS="$VERSIONS\n- \`$pkg@$VERSION\`" + done + + echo "versions<> $GITHUB_OUTPUT + echo -e "$VERSIONS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Comment with published versions + if: steps.changed.outputs.has_changes == 'true' + uses: actions/github-script@v7 + with: + script: | + const versions = `${{ steps.versions.outputs.versions }}`; + const packages = JSON.parse('${{ steps.changed.outputs.packages }}'); + + let installCommands = packages.map(pkg => { + const pkgPath = pkg.location.startsWith(process.env.GITHUB_WORKSPACE) + ? pkg.location + : `${process.env.GITHUB_WORKSPACE}/${pkg.location}`; + const pkgJson = require(`${pkgPath}/package.json`); + return `npm install ${pkg.name}@${pkgJson.version}`; + }).join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `✅ **Canary release published successfully!**\n\n**Published packages:**\n${versions}\n\n**Install with:**\n\`\`\`bash\n${installCommands}\n\`\`\`` + }); + + - name: Comment if no changes + if: steps.changed.outputs.has_changes == 'false' + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `⚠️ **No packages to publish.**\n\nNo changes detected compared to main branch.` + }); + + - name: Add success reaction + if: success() + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'rocket', + }); + + - name: Add failure reaction + if: failure() + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'confused', + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `❌ **Canary release failed.**\n\nCheck the [workflow run](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}) for details.` + });