-
Notifications
You must be signed in to change notification settings - Fork 1
Add S3 upload support for changelogs #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| name: Changelog upload | ||
|
|
||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| config: | ||
| description: 'Path to changelog.yml configuration file' | ||
| type: string | ||
| default: 'docs/changelog.yml' | ||
|
|
||
| concurrency: | ||
| group: changelog-upload-${{ github.event.pull_request.number || github.run_id }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| upload: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| id-token: write | ||
| steps: | ||
| - name: Upload changelog | ||
| uses: elastic/docs-actions/changelog/upload@main | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pinned to Change to: uses: elastic/docs-actions/changelog/upload@v1 |
||
| with: | ||
| config: ${{ inputs.config }} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| .idea/ | ||
| node_modules/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <!-- Generated by https://github.com/reakaleek/gh-action-readme --> | ||
| # <!--name-->Changelog upload<!--/name--> | ||
| <!--description--> | ||
| Uploads the changelog entry committed for a merged PR to the elastic-docs-v3-changelog-bundles S3 bucket under {product}/changelogs/{filename}. Preserves the original filename as set by the repository's changelog configuration. Exits silently when no changelog file is found in the bundle.directory for this PR. | ||
| <!--/description--> | ||
|
|
||
| ## Inputs | ||
| <!--inputs--> | ||
| | Name | Description | Required | Default | | ||
| |------------------|------------------------------------------|----------|-----------------------| | ||
| | `config` | Path to changelog.yml configuration file | `false` | `docs/changelog.yml` | | ||
| | `github-token` | GitHub token with contents:read | `false` | `${{ github.token }}` | | ||
| | `aws-account-id` | The AWS account ID | `false` | `197730964718` | | ||
| <!--/inputs--> | ||
|
|
||
| ## Outputs | ||
| <!--outputs--> | ||
| | Name | Description | | ||
| |------|-------------| | ||
| <!--/outputs--> | ||
|
|
||
| ## Usage | ||
| <!--usage action="your/action" version="v1"--> | ||
| <!--/usage--> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| name: Changelog upload | ||
| description: > | ||
| Uploads the changelog entry committed for a merged PR to the | ||
| elastic-docs-v3-changelog-bundles S3 bucket under {product}/changelogs/{filename}. | ||
| Preserves the original filename as set by the repository's changelog configuration. | ||
| Exits silently when no changelog file is found in the bundle.directory for this PR. | ||
|
|
||
| inputs: | ||
| config: | ||
| description: 'Path to changelog.yml configuration file' | ||
| default: 'docs/changelog.yml' | ||
| github-token: | ||
| description: 'GitHub token with contents:read' | ||
| default: '${{ github.token }}' | ||
| aws-account-id: | ||
| description: 'The AWS account ID' | ||
| default: '197730964718' | ||
|
|
||
| runs: | ||
| using: composite | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v6 | ||
| with: | ||
| ref: ${{ github.event.pull_request.merge_commit_sha }} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Compare: Add a guard step before the checkout: - name: Verify event context
shell: bash
env:
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
run: |
if [ -z "$MERGE_SHA" ]; then
echo "::error::merge_commit_sha is empty — must be triggered from a merged pull_request event"
exit 1
fi |
||
| persist-credentials: false | ||
|
|
||
| - name: Install upload dependencies | ||
| shell: bash | ||
| run: npm ci --prefix ${{ github.action_path }} | ||
|
|
||
| - name: Find changelog files and prepare upload targets | ||
| id: prepare | ||
| uses: actions/github-script@v8 | ||
| env: | ||
| CONFIG_FILE: ${{ inputs.config }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| with: | ||
| github-token: ${{ inputs.github-token }} | ||
| script: | | ||
| const script = require('${{ github.action_path }}/scripts/prepare-upload.js'); | ||
| await script({ github, context, core }); | ||
|
|
||
| - name: Authenticate with AWS | ||
| if: steps.prepare.outputs.has-uploads == 'true' | ||
| uses: elastic/docs-actions/aws/auth@v1 | ||
| with: | ||
| aws_account_id: ${{ inputs.aws-account-id }} | ||
| aws_role_name_prefix: elastic-docs-v3-changelog- | ||
|
|
||
| - name: Upload changelogs to S3 | ||
| if: steps.prepare.outputs.has-uploads == 'true' | ||
| shell: bash | ||
| env: | ||
| UPLOAD_PAIRS: ${{ steps.prepare.outputs.upload-pairs }} | ||
| AWS_RETRY_MODE: standard | ||
| AWS_MAX_ATTEMPTS: 6 | ||
| run: | | ||
| while IFS=' ' read -r FRAGMENT PRODUCT; do | ||
| S3_KEY="${PRODUCT}/changelogs/$(basename "${FRAGMENT}")" | ||
| echo "Uploading ${FRAGMENT} → s3://elastic-docs-v3-changelog-bundles/${S3_KEY}" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there's a risk here since
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, doing it now via 1b90a61 |
||
| aws s3 cp "${FRAGMENT}" "s3://elastic-docs-v3-changelog-bundles/${S3_KEY}" \ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing
aws s3 cp "${FRAGMENT}" "s3://elastic-docs-v3-changelog-bundles/${S3_KEY}" \
--no-follow-symlinks \
--checksum-algorithm SHA256 |
||
| --checksum-algorithm SHA256 | ||
| done <<< "${UPLOAD_PAIRS}" | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "private": true, | ||
| "dependencies": { | ||
| "js-yaml": "^4.1.0" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| // Licensed to Elasticsearch B.V under one or more agreements. | ||
| // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. | ||
| // See the LICENSE file in the project root for more information | ||
|
|
||
| 'use strict'; | ||
|
|
||
| const fs = require('fs'); | ||
| const yaml = require('js-yaml'); | ||
|
|
||
| const PRODUCT_RE = /^[a-zA-Z0-9_-]+$/; | ||
|
|
||
| module.exports = async ({ github, context, core }) => { | ||
| const configFile = process.env.CONFIG_FILE; | ||
| const prNumber = parseInt(process.env.PR_NUMBER, 10); | ||
|
|
||
| const changelogDir = readChangelogDir(configFile); | ||
|
|
||
| const prFiles = await github.paginate(github.rest.pulls.listFiles, { | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| pull_number: prNumber, | ||
| per_page: 100, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quick thought,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, and we used paginate for changelog additions too. Added in 1b90a61 |
||
| }); | ||
|
|
||
| const changelogFiles = prFiles | ||
| .filter(f => | ||
| f.filename.startsWith(changelogDir + '/') && | ||
| f.filename.endsWith('.yaml') && | ||
| (f.status === 'added' || f.status === 'modified') | ||
| ) | ||
| .map(f => f.filename); | ||
|
|
||
| const pairs = []; | ||
| for (const fragmentPath of changelogFiles) { | ||
| let products; | ||
| try { | ||
| products = readProducts(fs.readFileSync(fragmentPath, 'utf8')); | ||
| } catch (e) { | ||
| core.warning(`Could not read fragment ${fragmentPath}: ${e.message}`); | ||
| continue; | ||
| } | ||
| for (const product of products) { | ||
| if (!PRODUCT_RE.test(product)) { | ||
| core.warning( | ||
| `Skipping invalid product name "${product}" in ${fragmentPath} ` + | ||
| '(must match [a-zA-Z0-9_-]+)' | ||
| ); | ||
| continue; | ||
| } | ||
| pairs.push(`${fragmentPath} ${product}`); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a space as the field separator between Consider using a tab ( |
||
| } | ||
| } | ||
|
|
||
| if (pairs.length > 0) { | ||
| core.setOutput('has-uploads', 'true'); | ||
| core.setOutput('upload-pairs', pairs.join('\n')); | ||
| core.info(`Found ${pairs.length} upload target(s):`); | ||
| for (const pair of pairs) core.info(` ${pair}`); | ||
| } else { | ||
| core.setOutput('has-uploads', 'false'); | ||
| core.setOutput('upload-pairs', ''); | ||
| const reason = changelogFiles.length > 0 | ||
| ? 'no products found in changelog files' | ||
| : `no changelog files changed in ${changelogDir}/`; | ||
| core.info(`Nothing to upload (${reason})`); | ||
| } | ||
| }; | ||
|
|
||
| function readChangelogDir(configFile) { | ||
| let content; | ||
| try { | ||
| content = fs.readFileSync(configFile, 'utf8'); | ||
| } catch (_) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to use a YAML parsing library here (like
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done at 1b90a61.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are your thoughts on using https://github.com/vercel/ncc, I think this is a common pattern to bundle the JS, including its npm dependencies. |
||
| return 'docs/changelog'; | ||
| } | ||
| try { | ||
| const config = yaml.load(content); | ||
| return config?.bundle?.directory || 'docs/changelog'; | ||
| } catch (_) { | ||
| return 'docs/changelog'; | ||
| } | ||
| } | ||
|
|
||
| function readProducts(content) { | ||
| const doc = yaml.load(content); | ||
| if (!doc || !Array.isArray(doc.products)) return []; | ||
| return doc.products | ||
| .map(entry => (typeof entry === 'string' ? entry : entry?.product)) | ||
| .filter(Boolean); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing top-level
permissions: {}. Bothchangelog-validate.yml(line 11) andchangelog-submit.yml(line 15) deny all permissions at the workflow scope and then grant only what each job needs. Without this, the workflow inherits the repository's default token permissions.Add before
jobs::