Skip to content

[chore] Implement chloggen fragment automation for releases (#2787) #3

[chore] Implement chloggen fragment automation for releases (#2787)

[chore] Implement chloggen fragment automation for releases (#2787) #3

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: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN || secrets.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: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN || secrets.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"
exit 1
fi
go install go.opentelemetry.io/build-tools/chloggen@v0.15.0
"$(go env GOPATH)/bin/chloggen" validate --config .chloggen/config.yaml