Skip to content

Commit

Permalink
ci(release-please): add monorepo config (#412)
Browse files Browse the repository at this point in the history
This will allow us to release each of our actions and reusable workflows
individually, which will allow us to evolve them without worrying about
breaking users. Doing it separately will avoid users being asked to
upgrade unnecessarily.

Closes: #187
  • Loading branch information
iainlane authored Sep 27, 2024
1 parent 1994696 commit 023c802
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 23 deletions.
104 changes: 86 additions & 18 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,90 @@ jobs:
- uses: googleapis/release-please-action@7987652d64b4581673a76e33ad5e98e3dd56832f # v4.1.3
id: release
with:
release-type: simple
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
# this step is needed since we are publishing github actions releases.
# when patching a version e.g. v2.0.0 to v2.0.1,
# we need to make sure that v2 is updated as well as v2.0
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
target-branch: ${{ github.ref_name }}

# We release full semver versions major.minor.patch. We need to make sure
# that v2 is updated as well as v2.0, so that people can pin to the major
# or major.minor versions if they want.
#
# `steps.release.outputs` will contain `<type>/<name>--release_created:
# true` for each component that was released.
# We then need to look at `<type>/<name>--tag_name to extract the tag
# name, which will be <name>-<semver>. From that, we can work out the
# major and minor tags and update them to point at the value of
# `<actions>/<name>--sha`.
- name: tag major and minor versions
if: ${{ steps.release.outputs.release_created }}
run: |
git config user.name github-actions[bot]
git config user.email github-actions[bot]@users.noreply.github.com
git remote add gh-token "https://${{ github.token }}@github.com/google-github-actions/release-please-action.git"
git tag -d v${{ steps.release.outputs.major }} || true
git tag -d v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true
git push origin :v${{ steps.release.outputs.major }} || true
git push origin :v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true
git tag -a v${{ steps.release.outputs.major }} -m "Release v${{ steps.release.outputs.major }}"
git tag -a v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} -m "Release v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}"
git push origin v${{ steps.release.outputs.major }}
git push origin v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}
if: steps.release.outputs.releases_created == 'true'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
RELEASES: ${{ toJSON(steps.release.outputs) }}
with:
script: |
const createOrUpdateTag = async (tag, sha) => {
const ref = `refs/tags/${tag}`;
let existingRef = null;
try {
const { data } = await github.rest.git.getRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref
});
existingRef = data;
} catch (e) {
if (e.status !== 404) {
throw e;
}
}
if (existingRef) {
console.log(`Updating tag ${tag} from ${existingRef.object.sha} to ${sha}`);
await github.rest.git.updateRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref,
sha
});
} else {
console.log(`Creating tag ${tag} at ${sha}`);
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref,
sha
});
}
}
const rsplit = (str, sep, maxsplit) => {
var split = str.split(sep);
return maxsplit ?
[split.slice(0, -maxsplit).join(sep)].concat(split.slice(-maxsplit)) :
split;
}
const releases = JSON.parse(process.env.RELEASES);
// Filter `releases` to get the `*--release_created` outputs where
// the value is `"true"`. Then strip off that suffix to get an array
// of components that were released.
const components = Object.entries(releases)
.filter(([key, value]) => key.endsWith('--release_created') && value === 'true')
.map(([key]) => key.replace(/--release_created$/, ''));
console.log(`Components released: ${components.join(', ')}`);
for (const component of components) {
const tag = releases[`${component}--tag_name`];
const sha = releases[`${component}--sha`];
console.log(`Updating major and minor tags for ${component} to ${sha}`);
const [name, semver] = rsplit(tag, '-', 1);
const [major, minor] = semver.split('.');
await createOrUpdateTag(`${name}-${major}`, sha);
await createOrUpdateTag(`${name}-${major}.${minor}`, sha);
}
1 change: 1 addition & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,22 @@ will ensure actions in this repo are always used at the same commit. To do this:
some-input: some-value
```
### Releasing a version of shared-workflows
### Releasing a version of a component in shared-workflows
When working with `shared-workflows`, it's essential to avoid breaking backwards compatibility. To ensure this, we must provide releasable actions for engineers to review incoming changes. This also helps automated update tools like `dependabot` and `renovate` to work effectively.

Upon push to main, a new PR with updates in the CHANGELOG.md will be generated. The author needs to review and approve the PR, then merge. When merged, a new tag with a new release will be shown in the repository's GitHub page.
Upon push to main, a draft PR with updates in the CHANGELOG.md will be updated or created. This can be undrafted and merged at any time to create the next tagged release. Since we're a monorepo, one PR will be created for each action/reusable workflow that has been updated. They can be released individually and tags will be of the form `<name>-<semver version>`.

In order for the release action to work properly, which means to generate a CHANGELOG for the current release, the pull request titles need to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/). This means that the PR should start with a `type` followed by a colon, and then a `subject` - all in lowercase.

For example:

- `feat: add new release action`
Minor version bumps are indicated by new features: `feat: add support for eating lollipops`. Major version bumps are indicated by an `!` after the type in the commit message/description, for example: `feat!: rename foo input to bar`.

Also, the PR description needs to be filled and should never be empty.

Failing to follow any of the aforementioned necessary steps, will lead to CI failing on your pull request.

More about how the upstream action works can be found [here](https://github.com/googleapis/release-please-action).

### Add new components to Release Please config file

In order for components to be released, they must be in the [release-please-config.json](./release-please-config.json) file. Always ensure new components are added to this file.
104 changes: 104 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"changelog-sections": [
{
"section": "🎉 Features",
"type": "feat"
},
{
"section": "🐛 Bug Fixes",
"type": "fix"
},
{
"section": "⚡ Performance Improvements",
"type": "perf"
},
{
"section": "🔗 Dependencies",
"type": "deps"
},
{
"section": "📝 Documentation",
"type": "docs"
},
{
"section": "🏗️ Build System",
"type": "build"
},
{
"section": "🤖 Continuous Integration",
"type": "ci"
},
{
"section": "🔧 Miscellaneous Chores",
"type": "chore"
},
{
"section": "⏪ Reverts",
"type": "revert"
},
{
"section": "✅ Tests",
"type": "test"
},
{
"section": "💄 Style",
"type": "style"
},
{
"section": "♻️ Code Refactoring",
"type": "refactor"
}
],
"draft-pull-request": true,
"include-v-in-tag": true,
"packages": {
"actions/argo-lint": {
"package-name": "argo-lint"
},
"actions/aws-auth": {
"package-name": "aws-auth"
},
"actions/build-push-to-dockerhub": {
"package-name": "build-push-to-dockerhub"
},
"actions/dockerhub-login": {
"package-name": "dockerhub-login"
},
"actions/find-pr-for-commit": {
"package-name": "find-pr-for-commit"
},
"actions/generate-openapi-clients": {
"package-name": "generate-openapi-clients"
},
"actions/get-vault-secrets": {
"package-name": "get-vault-secrets"
},
"actions/lint-pr-title": {
"package-name": "lint-pr-title"
},
"actions/login-to-gar": {
"package-name": "login-to-gar"
},
"actions/login-to-gcs": {
"package-name": "login-to-gcs"
},
"actions/push-to-gar-docker": {
"package-name": "push-to-gar-docker"
},
"actions/push-to-gcs": {
"package-name": "push-to-gcs"
},
"actions/send-slack-message": {
"package-name": "send-slack-message"
},
"actions/setup-argo": {
"package-name": "setup-argo"
},
"actions/setup-conftest": {
"package-name": "setup-conftest"
}
},
"release-type": "simple",
"separate-pull-requests": true
}

0 comments on commit 023c802

Please sign in to comment.