diff --git a/.github/workflows/reusable_trigger_prerelease.yaml b/.github/workflows/reusable_trigger_prerelease.yaml index 1facbe7..ad8d294 100644 --- a/.github/workflows/reusable_trigger_prerelease.yaml +++ b/.github/workflows/reusable_trigger_prerelease.yaml @@ -90,7 +90,7 @@ jobs: fetch-depth: 0 ref: ${{ github.event.repository.default_branch }} token: "${{ secrets.bot_token }}" - - uses: newrelic/release-toolkit/contrib/ohi-release-notes@v1 + - uses: ./actions/ohi-release-notes id: release-data with: excluded-dirs: "${{ inputs.rt-excluded-dirs }}" diff --git a/.github/workflows/self_test_actions.yaml b/.github/workflows/self_test_actions.yaml new file mode 100644 index 0000000..a8deedf --- /dev/null +++ b/.github/workflows/self_test_actions.yaml @@ -0,0 +1,256 @@ +name: Self-test +on: + pull_request: + push: + branches: + - main +jobs: + contrib-ohi-release-notes: + name: validate ohi-release-notes (contrib action) + runs-on: ubuntu-latest + env: + MOCK_REPO_ACTION: ./mock_repo_action + MOCK_REPO_SCRIPT: ./mock_repo_script + ACTION_OUTPUTS: ./action_outputs + steps: + - uses: actions/checkout@v4 + - name: Configure test repo + shell: bash + run: | + # MOCK_REPO_SCRIPT is not needed as it is copied from ACTION one. + mkdir -pv "${MOCK_REPO_ACTION}" "${ACTION_OUTPUTS}" + cd "${MOCK_REPO_ACTION}" + + git config --global user.email "you@example.com" + git config --global user.name "Your Name" + + git init + cat > CHANGELOG.md < CHANGELOG.md < expected-changelog.md < expected-partial.md < CHANGELOG.md + echo -ne "${{ steps.ohi-release-notes.outputs.changelog-partial }}" > CHANGELOG.partial.md + + - name: Asserts + run: | + exit_status=0 + + echo "Action's CHANGELOG.md should be equal" + echo RESULT: + cat "${{ env.MOCK_REPO_ACTION }}/CHANGELOG.md" + echo EXPECTED: + cat expected-changelog.md + echo TEST: + if cmp --silent expected-changelog.md ${{ env.MOCK_REPO_ACTION }}/CHANGELOG.md; then + echo PASS + else + echo FAIL - diff between expected and the output: + diff expected-changelog.md ${{ env.MOCK_REPO_ACTION }}/CHANGELOG.md + ((exit_status++)) + fi + echo ================================================================= + echo "script's CHANGELOG.md should be equal" + echo RESULT: + cat "${{ env.MOCK_REPO_SCRIPT }}/CHANGELOG.md" + echo EXPECTED: + cat expected-changelog.md + echo TEST: + if cmp --silent expected-changelog.md ${{ env.MOCK_REPO_SCRIPT }}/CHANGELOG.md; then + echo PASS + else + echo FAIL - diff between expected and the output: + diff expected-changelog.md ${{ env.MOCK_REPO_SCRIPT }}/CHANGELOG.md + ((exit_status++)) + fi + echo ================================================================= + echo "script's CHANGELOG.md should be equal" + echo RESULT: + cat "${{ env.MOCK_REPO_SCRIPT }}/CHANGELOG.md" + echo EXPECTED: + cat expected-changelog.md + echo TEST: + if cmp --silent expected-changelog.md ${{ env.MOCK_REPO_SCRIPT }}/CHANGELOG.md; then + echo PASS + else + echo FAIL - diff between expected and the output: + diff expected-changelog.md ${{ env.ACTION_OUTPUTS }}/CHANGELOG.md + ((exit_status++)) + fi + echo ================================================================= + echo "Action's CHANGELOG.partial.md should be equal" + echo RESULT: + cat "${{ env.MOCK_REPO_ACTION }}/CHANGELOG.partial.md" + echo EXPECTED: + cat expected-partial.md + echo TEST: + if cmp --silent expected-partial.md ${{ env.MOCK_REPO_ACTION }}/CHANGELOG.partial.md; then + echo PASS + else + echo FAIL - diff between expected and the output: + diff expected-partial.md ${{ env.MOCK_REPO_ACTION }}/CHANGELOG.partial.md + ((exit_status++)) + fi + echo ================================================================= + echo "script's CHANGELOG.partial.md should be equal" + echo RESULT: + cat "${{ env.MOCK_REPO_SCRIPT }}/CHANGELOG.partial.md" + echo EXPECTED: + cat expected-partial.md + echo TEST: + if cmp --silent expected-partial.md ${{ env.MOCK_REPO_SCRIPT }}/CHANGELOG.partial.md; then + echo PASS + else + echo FAIL - diff between expected and the output: + diff expected-partial.md ${{ env.MOCK_REPO_SCRIPT }}/CHANGELOG.partial.md + ((exit_status++)) + fi + echo ================================================================= + echo "script's CHANGELOG.partial.md should be equal" + echo RESULT: + cat "${{ env.MOCK_REPO_SCRIPT }}/CHANGELOG.partial.md" + echo EXPECTED: + cat expected-partial.md + echo TEST: + if cmp --silent expected-partial.md ${{ env.MOCK_REPO_SCRIPT }}/CHANGELOG.partial.md; then + echo PASS + else + echo FAIL - diff between expected and the output: + diff expected-partial.md ${{ env.ACTION_OUTPUTS }}/CHANGELOG.partial.md + ((exit_status++)) + fi + echo ================================================================= + echo "action output 'next-version' return the expected value" + echo EXPECTED: + echo "v2.0.0" + echo RESULT: + echo "${{ steps.ohi-release-notes.outputs.next-version }}" + echo TEST: + if [ "${{ steps.ohi-release-notes.outputs.next-version }}" = "v2.0.0" ] ; then + echo PASS + else + echo FAIL - action output 'next-version' does not return the expected value + ((exit_status++)) + fi + echo ================================================================= + echo "action output 'release-title' return the expected value" + echo EXPECTED: + echo "v2.0.0 - $(date +%Y-%m-%d)" + echo RESULT: + echo "${{ steps.ohi-release-notes.outputs.release-title }}" + echo TEST: + if [ "${{ steps.ohi-release-notes.outputs.release-title }}" = "v2.0.0 - $(date +%Y-%m-%d)" ] ; then + echo PASS + else + echo FAIL - action output 'release-title' does not return the expected value + ((exit_status++)) + fi + + exit $exit_status diff --git a/actions/ohi-release-notes/README.md b/actions/ohi-release-notes/README.md new file mode 100644 index 0000000..28116f9 --- /dev/null +++ b/actions/ohi-release-notes/README.md @@ -0,0 +1,48 @@ +# 🛠️ `ohi-release-notes` + +This is a wrapper of all the steps needed to update the changelog and render a snippet to be ready to be used as a release message. This contribution also includes a script that allow to replicate what this action does locally. + +## Example Usage + +```yaml +- uses: newrelic/coreint-automation/actions/ohi-release-notes@v3 + id: release +- name: Commit updated changelog + run: | + git add CHANGELOG.md + git commit -m "Update changelog with changes from ${{ steps.release.outputs.next-version }}" + git push -u origin main + gh release create ${{ steps.release.outputs.release-title }} --target $(git rev-parse HEAD) --notes-file CHANGELOG.partial.md +``` + +## Parameters + +All parameters are optional: + * `excluded-dirs` exclude commits whose changes only impact files in specified dirs relative to repository root. Defaults to ".github". + * `excluded-files` Exclude commits whose changes only impact files in specified files relative to repository root. Defaults to "". + * `included-dirs` Only scan commits scoping at least one file in any of the following comma-separated directories + * `included-files` Only scan commits scoping at least one file in the following comma-separated list + * `fail-if-held` fails if the held toggle is active + * `dictionary` sets the link dependency dictionary file path. Defaults to ".github/rt-dictionary.yml". + * `excluded-dependencies-manifest` sets the excluded dependencies manifest. Defaults to ".github/excluded-dependencies.yml". + +## Outputs + + * `next-version` contains the calculated version for this. + * `release-title` is the title of the release that includes `next-version` and the date it was done. + * `release-changelog` contains the complete changelog of this release. Alias of the file `CHANGELOG.md` + * `release-changelog-partial` contains the changelog for only this release. Alias of the file `CHANGELOG.partial.md` + +This action also leaves the files `CHANGELOG.md` and `CHANGELOG.partial.md` at the working directory so they are also ready to be committed. + +## Use script locally +There is a `run.sh` script that should do the same as this action: Leaves the files `CHANGELOG.md` and `CHANGELOG.partial.md` at the working directory and prints the title of the release with the next version and the date. + +You can run it by bashpipeing this script: +```shell +curl "https://raw.githubusercontent.com/newrelic/release-toolkit/v1/contrib/ohi-release-notes/run.sh" | bash +``` + +## Contributing + +Standard policy and procedure across the New Relic GitHub organization. diff --git a/actions/ohi-release-notes/action.yml b/actions/ohi-release-notes/action.yml new file mode 100644 index 0000000..3c87d2d --- /dev/null +++ b/actions/ohi-release-notes/action.yml @@ -0,0 +1,151 @@ +name: OHI release notes +description: Wrapper for release toolkit that runs commands needed to release an OHI +inputs: + git-root: + description: Path to the root of the git repository to source bot commits from. + default: "." + excluded-dirs: + description: Exclude commits whose changes only impact files in specified dirs relative to repository root. Defaults to ".github". + default: '.github' + excluded-files: + description: Exclude commits whose changes only impact files in specified files relative to repository root. Defaults to "". + default: '' + included-dirs: + description: Only scan commits scoping at least one file in any of the following comma-separated directories + required: false + default: "" + included-files: + description: Only scan commits scoping at least one file in the following comma-separated list + required: false + default: "" + fail-if-held: + description: Fail if the held toggle is active. Defaults to `true`. + default: 'true' + link-dependencies-dictionary: + description: Sets the link dependency dictionary. `.github/rt-dictionary.yml` is used by default, if it does not exist a common dictionary is used. + default: '.github/rt-dictionary.yml' + excluded-dependencies-manifest: + description: Excluded dependencies manifest. Dependency commits containing any of the strings listed, will be excluded. Defaults to `.github/excluded-dependencies.yml`. + default: '.github/excluded-dependencies.yml' +outputs: + is-empty: + description: "Outputs if changelog is empty" + value: ${{ steps.empty.outputs.is-empty }} + is-held: + description: "Outputs if changelog is held" + value: ${{ steps.held.outputs.is-held }} + skip-release: + description: "Outputs if a release should be skipped" + value: ${{ steps.check-release.outputs.skip }} + next-version: + description: "Version of this release" + value: ${{ steps.version.outputs.next-version }} + release-title: + description: "Title of this release" + value: ${{ steps.release.outputs.title }} + release-changelog: + description: "Complete changelog of this release" + value: ${{ steps.release.outputs.changelog }} + release-changelog-partial: + description: "Changelog for only this release" + value: ${{ steps.release.outputs.changelog-partial }} +runs: + using: composite + steps: + - name: Validate that the markdown is correct + uses: newrelic/release-toolkit/validate-markdown@v1 + with: + markdown: ${{ inputs.git-root }}/CHANGELOG.md + # excluded-dependencies is a file contained by this action and is located inside $GITHUB_ACTION_PATH, but this + # folder is not mounted in the container that runs the generate-yaml action, so we copied the file to + # ${GITHUB_WORKSPACE} which is mounted in the container. + - name: Copy dependencies file to temp folder mounted by the generate-yaml action + shell: bash + run: | + if ! [ -f "${GITHUB_WORKSPACE}/${{ inputs.excluded-dependencies-manifest }}" ]; then + cp ${{ github.action_path }}/excluded-dependencies.yml "${GITHUB_WORKSPACE}/${{ inputs.excluded-dependencies-manifest }}" + fi + - name: Generate YAML + uses: newrelic/release-toolkit/generate-yaml@v1 + with: + excluded-dirs: ${{ inputs.excluded-dirs }} + excluded-files: ${{ inputs.excluded-files }} + included-dirs: ${{ inputs.included-dirs }} + included-files: ${{ inputs.included-files }} + excluded-dependencies-manifest: ${{ inputs.excluded-dependencies-manifest }} + git-root: ${{ inputs.git-root }} + markdown: ${{ inputs.git-root }}/CHANGELOG.md + yaml: ${{ inputs.git-root }}/changelog.yaml + exit-code: "0" + - name: Check if the release is empty + id: empty + uses: newrelic/release-toolkit/is-empty@v1 + with: + yaml: ${{ inputs.git-root }}/changelog.yaml + - name: Check if the release is held + id: held + uses: newrelic/release-toolkit/is-held@v1 + with: + yaml: ${{ inputs.git-root }}/changelog.yaml + - name: Check if the release should be skipped + id: check-release + shell: bash + run: | + echo "::set-output name=skip::${{ steps.empty.outputs.is-empty == 'true' || steps.held.outputs.is-held == 'true' }}" + # rt-dictionary is a file contained by this action and is located inside $GITHUB_ACTION_PATH, but this + # folder is not mounted in the container that runs the link-dependencies action, so we copied the file to + # ${GITHUB_WORKSPACE} which is mounted in the container. + - name: Copy dictionary to temp folder mounted by the link-dependencies action + shell: bash + run: | + if ! [ -f "${GITHUB_WORKSPACE}/${{ inputs.link-dependencies-dictionary }}" ]; then + cp ${{ github.action_path }}/rt-dictionary.yml "${GITHUB_WORKSPACE}/${{ inputs.link-dependencies-dictionary }}" + fi + - name: Link dependencies + if: ${{ steps.check-release.outputs.skip != 'true' }} + uses: newrelic/release-toolkit/link-dependencies@v1 + with: + # see comment from last step regarding this file. + dictionary: ${{ inputs.link-dependencies-dictionary }} + yaml: ${{ inputs.git-root }}/changelog.yaml + - name: Calculate next version + if: ${{ steps.check-release.outputs.skip != 'true' }} + id: version + uses: newrelic/release-toolkit/next-version@v1 + with: + git-root: ${{ inputs.git-root }} + yaml: ${{ inputs.git-root }}/changelog.yaml + - name: Update the markdown + if: ${{ steps.check-release.outputs.skip != 'true' }} + uses: newrelic/release-toolkit/update-markdown@v1 + with: + markdown: ${{ inputs.git-root }}/CHANGELOG.md + yaml: ${{ inputs.git-root }}/changelog.yaml + version: ${{ steps.version.outputs.next-version }} + - name: Render the changelog snippet + if: ${{ steps.check-release.outputs.skip != 'true' }} + uses: newrelic/release-toolkit/render@v1 + with: + markdown: ${{ inputs.git-root }}/CHANGELOG.partial.md + yaml: ${{ inputs.git-root }}/changelog.yaml + version: ${{ steps.version.outputs.next-version }} + - name: Create outputs + if: ${{ steps.check-release.outputs.skip != 'true' }} + shell: bash + id: release + run: | + echo "title=$(grep -E "^## " ${{ inputs.git-root }}/CHANGELOG.partial.md | sed 's|^## ||')" >> $GITHUB_OUTPUT + + echo "changelog-partial<> $GITHUB_OUTPUT + cat ${{ inputs.git-root }}/CHANGELOG.partial.md >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "changelog<> $GITHUB_OUTPUT + cat ${{ inputs.git-root }}/CHANGELOG.md >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + - name: Clean the workspace + if: ${{ steps.check-release.outputs.skip != 'true' }} + shell: bash + run: | + rm ${{ inputs.git-root }}/CHANGELOG.md.bak + rm ${{ inputs.git-root }}/changelog.yaml diff --git a/actions/ohi-release-notes/excluded-dependencies.yml b/actions/ohi-release-notes/excluded-dependencies.yml new file mode 100644 index 0000000..58ac0b2 --- /dev/null +++ b/actions/ohi-release-notes/excluded-dependencies.yml @@ -0,0 +1,4 @@ +# This contains a list of development dependencies that should be skipped from the changelog. +dependencies: + - github.com/stretchr/testify + - github.com/testcontainers/testcontainers-go diff --git a/actions/ohi-release-notes/rt-dictionary.yml b/actions/ohi-release-notes/rt-dictionary.yml new file mode 100644 index 0000000..6f38278 --- /dev/null +++ b/actions/ohi-release-notes/rt-dictionary.yml @@ -0,0 +1,32 @@ +# This file is a dictionary used by the [link-dependencies](https://github.com/newrelic/release-toolkit/tree/main/link-dependencies) action. +# Notice that the implementation uses dep.To.ToString that removes the leading v if present. +dictionary: + newrelic/infrastructure-bundle: "https://github.com/newrelic/infrastructure-bundle/releases/tag/v{{.To}}" + newrelic/infrastructure: "https://github.com/newrelic/infrastructure-agent/releases/tag/{{.To}}" + newrelic/nri-apache: "https://github.com/newrelic/nri-apache/releases/tag/v{{.To}}" + newrelic/nri-cassandra: "https://github.com/newrelic/nri-cassandra/releases/tag/v{{.To}}" + newrelic/nri-consul: "https://github.com/newrelic/nri-consul/releases/tag/v{{.To}}" + newrelic/nri-couchbase: "https://github.com/newrelic/nri-couchbase/releases/tag/v{{.To}}" + newrelic/nri-discovery-kubernetes: "https://github.com/newrelic/nri-discovery-kubernetes/releases/tag/v{{.To}}" + newrelic/nri-docker: "https://github.com/newrelic/nri-docker/releases/tag/v{{.To}}" + newrelic/nri-ecs: "https://github.com/newrelic/nri-ecs/releases/tag/v{{.To}}" + newrelic/nri-elasticsearch: "https://github.com/newrelic/nri-elasticsearch/releases/tag/v{{.To}}" + newrelic/nri-f5: "https://github.com/newrelic/nri-f5/releases/tag/v{{.To}}" + newrelic/nri-haproxy: "https://github.com/newrelic/nri-haproxy/releases/tag/v{{.To}}" + newrelic/nri-jmx: "https://github.com/newrelic/nri-jmx/releases/tag/v{{.To}}" + newrelic/nri-kafka: "https://github.com/newrelic/nri-kafka/releases/tag/v{{.To}}" + newrelic/nri-memcached: "https://github.com/newrelic/nri-memcached/releases/tag/v{{.To}}" + newrelic/nri-mongodb: "https://github.com/newrelic/nri-mongodb/releases/tag/v{{.To}}" + newrelic/nri-mssql: "https://github.com/newrelic/nri-mssql/releases/tag/v{{.To}}" + newrelic/nri-mysql: "https://github.com/newrelic/nri-mysql/releases/tag/v{{.To}}" + newrelic/nri-nagios: "https://github.com/newrelic/nri-nagios/releases/tag/v{{.To}}" + newrelic/nri-nginx: "https://github.com/newrelic/nri-nginx/releases/tag/v{{.To}}" + newrelic/nri-oracledb: "https://github.com/newrelic/nri-oracledb/releases/tag/v{{.To}}" + newrelic/nri-postgresql: "https://github.com/newrelic/nri-postgresql/releases/tag/v{{.To}}" + newrelic/nri-prometheus: "https://github.com/newrelic/nri-prometheus/releases/tag/v{{.To}}" + newrelic/nri-rabbitmq: "https://github.com/newrelic/nri-rabbitmq/releases/tag/v{{.To}}" + newrelic/nri-redis: "https://github.com/newrelic/nri-redis/releases/tag/v{{.To}}" + newrelic/nri-varnish: "https://github.com/newrelic/nri-varnish/releases/tag/v{{.To}}" + newrelic/nri-winservices: "https://github.com/newrelic/nri-winservices/releases/tag/v{{.To}}" + newrelic/nrjmx: "https://github.com/newrelic/nrjmx/releases/tag/v{{.To}}" + newrelic/prometheus-exporter-supervisor: "https://github.com/newrelic/prometheus-exporter-supervisor/releases/tag/v{{.To}}" diff --git a/actions/ohi-release-notes/run.sh b/actions/ohi-release-notes/run.sh new file mode 100755 index 0000000..927234a --- /dev/null +++ b/actions/ohi-release-notes/run.sh @@ -0,0 +1,149 @@ +#!/bin/bash + +set -euo pipefail + +RT_PKG="github.com/newrelic/release-toolkit@latest" +DICTIONARY_URL="https://raw.githubusercontent.com/newrelic/release-toolkit/v1/contrib/ohi-release-notes/rt-dictionary.yml" +EXCLUDED_DEPENDENCIES_MANIFEST_URL="https://raw.githubusercontent.com/newrelic/release-toolkit/v1/contrib/ohi-release-notes/excluded-dependencies.yml" +ARGS="$*" + + +# creating a temporary folder where to build the rt binary and cleaning after exiting. +TEMP_DIR=$(mktemp -dt release-toolkit-XXX) +function cleanup() { + rm -rf $TEMP_DIR || true + rm "${GIT_ROOT}/CHANGELOG.md.bak" || true + rm "${GIT_ROOT}/changelog.yaml" || true +} +trap cleanup EXIT + + +# usage and help command. It is also an error exit in case flags are not correct. +function help() { + set +x # Disable verbosity. If it is enabled at this point, it is not needed anymore. + ERRNO=0 + if ! [ -z "${1:-}" ]; then + echo "ERROR:" + echo " $1" + echo "" + ERRNO=1 + fi + + cat < /dev/null + ${RT_BIN} is-held "${IS_HELD_FAIL}" > /dev/null + if [ -f "$DICTIONARY" ]; then + ${RT_BIN} link-dependencies --dictionary "$DICTIONARY" + else + ${RT_BIN} link-dependencies + fi + NEXT_VERSION="$(${RT_BIN} next-version)" + ${RT_BIN} update-markdown --version "$NEXT_VERSION" + ${RT_BIN} render-changelog --version "$NEXT_VERSION" + + echo "Release title should be: $(grep -E "^## " CHANGELOG.partial.md | sed 's|^## ||')" +)