Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 227 additions & 4 deletions .github/workflows/canary.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same NPM_AUTH_TOKEN is being used for both GitHub Packages and the public npm registry. This could be a security concern if the token has broader permissions than needed. Consider using separate tokens (NPM_AUTH_TOKEN for npm and GITHUB_TOKEN or a dedicated token for GitHub Packages) to follow the principle of least privilege.

Suggested change
echo "//npm.pkg.github.com/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" >> .npmrc
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same NPM_AUTH_TOKEN is being used for both GitHub Packages registry and the public npm registry. These should typically be different tokens - one for GitHub Packages (@JupiterOne scope) and one for npmjs.org. Using the same token could be a security risk if it grants broader permissions than necessary.

Suggested change
echo "//npm.pkg.github.com/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" >> .npmrc
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_PACKAGES_NPM_TOKEN }}" >> .npmrc

Copilot uses AI. Check for mistakes.
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<<EOF" >> $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<<EOF" >> $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.`
});