[chore] Implement chloggen fragment automation for releases (#2787) #4
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Changelog Fragments | |
| on: | |
| pull_request: | |
| types: | |
| - opened | |
| - synchronize | |
| - reopened | |
| - labeled | |
| - unlabeled | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| models: read | |
| jobs: | |
| generate-fragment: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ github.event.pull_request.head.repo.full_name }} | |
| ref: ${{ github.head_ref }} | |
| token: ${{ github.token }} | |
| fetch-depth: 0 | |
| - name: Install yq | |
| run: | | |
| go install github.com/mikefarah/yq/v4@v4.44.3 | |
| - name: Check skip conditions | |
| id: skip | |
| env: | |
| PR_TITLE: ${{ github.event.pull_request.title }} | |
| PR_LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }} | |
| PR_USER_LOGIN: ${{ github.event.pull_request.user.login }} | |
| run: | | |
| skip=false | |
| if [[ "$PR_USER_LOGIN" == "dependabot[bot]" || "$PR_USER_LOGIN" == "renovate[bot]" ]]; then | |
| skip=true | |
| fi | |
| if [[ "$PR_TITLE" == \[chore\]* ]]; then | |
| skip=true | |
| fi | |
| if jq -e 'index("Skip Changelog")' <<<"$PR_LABELS" >/dev/null; then | |
| skip=true | |
| fi | |
| echo "skip=$skip" >> "$GITHUB_OUTPUT" | |
| - name: Check existing fragment | |
| id: fragment | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| fragment=".chloggen/pr-${PR_NUMBER}.yaml" | |
| if [[ -f "$fragment" ]]; then | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Fetch base branch | |
| if: ${{ steps.skip.outputs.skip != 'true' && steps.fragment.outputs.exists != 'true' && github.event.pull_request.head.repo.full_name == github.repository }} | |
| run: | | |
| git fetch origin "${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }}" --depth=1 | |
| - name: Generate fragment | |
| if: ${{ steps.skip.outputs.skip != 'true' && steps.fragment.outputs.exists != 'true' && github.event.pull_request.head.repo.full_name == github.repository }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| PR_TITLE: ${{ github.event.pull_request.title }} | |
| PR_BODY: ${{ github.event.pull_request.body }} | |
| BASE_REF: ${{ github.event.pull_request.base.ref }} | |
| run: | | |
| changed_files="$(git diff --name-only "origin/${BASE_REF}"...HEAD)" | |
| request_payload="$(jq -n \ | |
| --arg title "$PR_TITLE" \ | |
| --arg body "$PR_BODY" \ | |
| --arg changed_files "$changed_files" \ | |
| '{ | |
| model: "gpt-4o-mini", | |
| response_format: {type: "json_object"}, | |
| messages: [ | |
| { | |
| role: "system", | |
| content: "Generate a single JSON object for a changelog fragment. Return only valid JSON with these keys: change_type, component, note, issues, subtext, change_logs. change_type must be one of breaking, deprecation, new_component, enhancement, bug_fix. component must be blank if the PR does not clearly map to one component. note must be a brief end-user facing summary. issues must be an array of issue numbers without #. subtext is optional and may be null or omitted. change_logs must be [\"user\"]. Do not add markdown or extra keys." | |
| }, | |
| { | |
| role: "user", | |
| content: "PR title: \($title)\n\nPR body:\n\($body)\n\nChanged files:\n\($changed_files)" | |
| } | |
| ] | |
| }')" | |
| response="$(curl --fail-with-body --silent --show-error https://models.github.ai/inference/chat/completions \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Content-Type: application/json" \ | |
| -H "Authorization: Bearer ${GITHUB_TOKEN}" \ | |
| -d "$request_payload")" | |
| jq -r '.choices[0].message.content | fromjson' <<<"$response" | "$(go env GOPATH)/bin/yq" -P - > ".chloggen/pr-${PR_NUMBER}.yaml" | |
| - name: Validate AI Output | |
| if: ${{ steps.skip.outputs.skip != 'true' && steps.fragment.outputs.exists != 'true' && github.event.pull_request.head.repo.full_name == github.repository }} | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| fragment=".chloggen/pr-${PR_NUMBER}.yaml" | |
| if ! "$(go env GOPATH)/bin/yq" -e 'has("change_type") and has("component") and has("note")' "$fragment" >/dev/null; then | |
| echo "missing required keys: change_type, component, note" | |
| rm -f "$fragment" | |
| exit 1 | |
| fi | |
| - name: Commit | |
| if: ${{ steps.skip.outputs.skip != 'true' && steps.fragment.outputs.exists != 'true' && github.event.pull_request.head.repo.full_name == github.repository }} | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| PR_BRANCH: ${{ github.head_ref }} | |
| run: | | |
| git config user.name opentelemetrybot | |
| git config user.email 107717825+opentelemetrybot@users.noreply.github.com | |
| git add ".chloggen/pr-${PR_NUMBER}.yaml" | |
| git commit -m "Add changelog fragment for PR #${PR_NUMBER}" | |
| git push origin HEAD:"${PR_BRANCH}" | |
| validate-fragment: | |
| needs: generate-fragment | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ github.event.pull_request.head.repo.full_name }} | |
| ref: ${{ github.head_ref }} | |
| token: ${{ github.token }} | |
| fetch-depth: 0 | |
| - name: Check skip conditions | |
| id: skip | |
| env: | |
| PR_TITLE: ${{ github.event.pull_request.title }} | |
| PR_LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }} | |
| PR_USER_LOGIN: ${{ github.event.pull_request.user.login }} | |
| run: | | |
| skip=false | |
| if [[ "$PR_USER_LOGIN" == "dependabot[bot]" || "$PR_USER_LOGIN" == "renovate[bot]" ]]; then | |
| skip=true | |
| fi | |
| if [[ "$PR_TITLE" == \[chore\]* ]]; then | |
| skip=true | |
| fi | |
| if jq -e 'index("Skip Changelog")' <<<"$PR_LABELS" >/dev/null; then | |
| skip=true | |
| fi | |
| echo "skip=$skip" >> "$GITHUB_OUTPUT" | |
| - name: Exit if skipped | |
| if: ${{ steps.skip.outputs.skip == 'true' }} | |
| run: exit 0 | |
| - name: Validate fragment | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| fragment=".chloggen/pr-${PR_NUMBER}.yaml" | |
| if [[ ! -f "$fragment" ]]; then | |
| echo "❌ Missing changelog fragment: $fragment" | |
| echo "If you are contributing from a fork, you must create this file manually." | |
| echo "Copy the contents of .chloggen/TEMPLATE.yaml into a new file named $fragment, fill it out, and commit it to your branch." | |
| exit 1 | |
| fi | |
| go install go.opentelemetry.io/build-tools/chloggen@v0.15.0 | |
| "$(go env GOPATH)/bin/chloggen" validate --config .chloggen/config.yaml |