Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@

## [1.0.0](https://github.com/agilecustoms/release/compare/v0.17.1...v1.0.0) (2025-08-24)

### ⚠ BREAKING CHANGES

* first beta

### Features

* add java-version input - pass it into setup-maven-codeartifact action ([e707ff4](https://github.com/agilecustoms/release/commit/e707ff4254776e8a6694e1021713b3122a7d2a5c))
Expand Down
120 changes: 59 additions & 61 deletions README.md

Large diffs are not rendered by default.

42 changes: 23 additions & 19 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
name: 'Consistent Release'
author: 'Alexey Chekulaev'
description: 'Release software artifacts in AWS (S3, ECR, CodeArtifact) and NPM with consistent versioning'
description: >
Automate consistent, secure releases of software artifacts to AWS (S3, ECR, CodeArtifact) and npm.
Supports semantic versioning, changelog generation, floating tags, prereleases, and dev-releases.
Publish binaries, Docker images, IaC, and raw files with idempotent, repeatable workflows — ideal
for microservices and multi-artifact projects.
branding:
icon: package
color: green
Expand All @@ -12,13 +16,13 @@ inputs:
description: 'AWS region'
required: false
aws-role:
description: 'IAM role to assume to publish, ex. `/ci/publisher`'
description: 'IAM role to assume to publish, e.g., `ci/publisher`'
required: false
aws-codeartifact-domain:
description: 'CodeArtifact domain name, ex. `mycompany`'
description: 'CodeArtifact domain name, e.g., `mycompany`'
required: false
aws-codeartifact-repository:
description: 'CodeArtifact repository name, ex. `maven`'
description: 'CodeArtifact repository name, e.g., `maven`'
required: false
aws-codeartifact-maven:
description: 'If true, then publish maven artifacts to AWS CodeArtifact'
Expand All @@ -30,7 +34,7 @@ inputs:
description: 'Required if uploading to S3 (i.e. s3/ directory exists)'
required: false
aws-s3-dir:
description: 'Allows to specify S3 bucket directory to upload artifacts to. By default just place in `bucket/{repo-name}/{version}/*`'
description: 'Allows you to specify S3 bucket directory to upload artifacts to. By default, just place in `bucket/{repo-name}/{version}/*`'
required: false
changelog-file:
description: 'Changelog file path. Pass empty string to disable changelog generation'
Expand All @@ -41,23 +45,23 @@ inputs:
required: false
default: '# Changelog'
dev-branch-prefix:
description: 'Allows to enforce branch prefix for dev-releases, this help to write auto-disposal rules. Empty string disables enforcement'
description: 'Allows you to enforce branch prefix for dev-releases; this helps to write auto-disposal rules. Empty string disables enforcement'
required: false
default: 'feature/'
dev-release:
description: 'Allows to create temporary named release, mainly for dev testing. Implementation is different for all supported artifact types'
description: 'Allows you to create a temporary named release, mainly for dev testing. Implementation is different for all supported artifact types'
required: false
default: 'false'
floating-tags:
description: 'When next version to be released is 1.2.4, then also release 1, 1.2 and latest. Not desired for public terraform modules'
description: 'When the next version to be released is 1.2.4, then also release 1, 1.2, and latest. Not desired for public terraform modules'
required: false
default: 'true'
java-version:
description: 'Java version to use with aws-codeartifact-maven'
required: false
default: '21'
node-version:
description: 'Node.js version to publish npm packages, default is 22 because it is highest pre-cached in Ubuntu 24'
description: 'Node.js version to publish npm packages, default is 22 because it is highest pre-cached in Ubuntu 24 (latest at time of writing)'
required: false
default: '22'
npm-extra-deps:
Expand All @@ -68,7 +72,7 @@ inputs:
required: false
default: 'public'
pre-publish-script:
description: 'sh script that allows to update version in custom file(s), not only files governed by build tool (pom.xml, package.json, etc)'
description: 'Shell script that allows you to update version in custom file(s), not only files governed by build tool (pom.xml, package.json, etc)'
required: false
release-branches:
description: 'semantic-release "branches" configuration'
Expand All @@ -84,17 +88,17 @@ inputs:
description: 'semantic-release plugins configuration'
required: false
summary:
description: 'If true, then print summary to the GitHub Actions summary page'
description: 'Text to print in step summary. Can use `${version}` placeholder. Default is `### Released ${version}`. Set empty string to omit summary generation'
required: false
default: '### Released ${version}'
tag-format:
description: 'By-default tag (version) has format `v1.0.0`. Use `${version}` to remove `v` prefix'
description: 'By default, the tag (version) has format `v1.0.0`. Use `${version}` to remove `v` prefix'
required: false
version:
description: 'Explicit version to use instead of auto-generation'
required: false
version-bump:
description: 'Allows to bump a version w/o semantic commits'
description: 'Allows you to bump a version without semantic commits'
required: false

runs:
Expand All @@ -117,7 +121,7 @@ runs:
if: inputs.release-gh == 'true' && inputs.dev-release != 'true' && !env.GH_TOKEN
shell: bash
run: |
echo "Error: env: GH_TOKEN is required to create GH release, you can pass $ {{ github.token }} or PAT" >&2; exit 1
echo "Error: env variable GH_TOKEN is required to create GH release, you can pass $ {{ github.token }} or PAT" >&2; exit 1

- name: Validate npm visibility
if : env.NPM_TOKEN && ! (inputs.npm-visibility == 'public' || inputs.npm-visibility == 'private')
Expand Down Expand Up @@ -161,8 +165,8 @@ runs:
fi

if [ "${{ inputs.aws-codeartifact-maven }}" = "true" ]; then
if [ -z "${{ inputs.aws-codeartifact-domain }}" ] || [ -z "${{ inputs.aws-codeartifact-repository }}" ]; then
echo "Error: aws-codeartifact-domain and aws-codeartifact-repository must be provided when aws-codeartifact-maven is true" >&2
if [ -z "${{ inputs.aws-codeartifact-domain }}" ]; then
echo "Error: aws-codeartifact-domain must be provided when aws-codeartifact-maven is true" >&2
exit 1
fi
fi
Expand All @@ -174,9 +178,9 @@ runs:

## Phase 2 - Release generation

# by default (job permissions: contents: write) .git/config already pre-configured with a ${{ github.token }}
# by default (job permissions: contents: write) .git/config already pre-configured with a ${{ github.token }} see misc/.git-config for example
# alternatively user may want to use an explicit PAT, then need to configure git to use it.
# Besides 'git push', release_gen _probes_ git push, that's why it is in front of release_gen
# Besides 'git push', the release_gen _probes_ git push, that's why it is in front of release_gen
- name: Git authorization
if: env.GH_TOKEN
shell: bash
Expand Down Expand Up @@ -248,7 +252,7 @@ runs:
aws-region: ${{ inputs.aws-region }}
aws-role: ${{ inputs.aws-role }}
aws-codeartifact-domain: ${{ inputs.aws-codeartifact-domain }}
aws-codeartifact-repository: ${{ inputs.aws-codeartifact-repository }}
aws-codeartifact-repository: ${{ inputs.aws-codeartifact-repository || 'maven' }}
aws-login: false # we already logged in AWS in a previous step
java-version: ${{ inputs.java-version }}

Expand Down
4 changes: 2 additions & 2 deletions docs/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ In 2023, I (Alexey Chekulaev) started to work on a project that consists of mult
First, I did not find a good GH action to upload files in S3, so I did one myself.
Then I felt a lack of GH action to publish Maven packages in AWS CodeArtifact, so I had to develop two more actions:
one to publish and one to resolve existing packages.
Spring 2025 I started my second project, the number of services grew and so grew a volume of similar code in release pipelines.
In spring 2025 I started my second project, the number of services grew and so grew a volume of similar code in release pipelines.
Then I combined all of them into a single action `agilecustoms/gha-release`.
But then (summer 2025) I decided to make it public and extracted stuff not specific to AgileCustoms into a separate action `agilecustoms/release`

## Current state

Currently (July 2025) this action is being used in two private AgileCustoms projects with over 20+ repositories,
which allows me (author) to have good coverage on different release scenarios
which allows me as the author to have good coverage of different release scenarios

Did I think about a plugin system? Yes, I did. Having every type of artifact as a plugin would be great.
The problem is that right now this GH action is a combination of other GH actions, my custom GH actions (composite and Node.js ones) and some shell scripts.
Expand Down
35 changes: 18 additions & 17 deletions docs/artifact-types/aws-ecr.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ _Note: all examples use shared patterns: two workflows: Build and Release — co
- [AWS Lambda running Spring Boot application in Docker](#aws-lambda-running-spring-boot-application-in-docker)
- [Workflow reuse](#workflow-reuse)
- [Application code and IaC](#application-code-and-iac)
- [dev-release](#dev-release)
- [explicit version](#explicit-version)
- [setup-maven-codeartifact](#setup-maven-codeartifact)
- [dev-release](#dev-release)

## AWS Lambda running Spring Boot application in Docker

Expand Down Expand Up @@ -101,22 +101,6 @@ module "env_cleanup" {

Note file `infrastructure/vars.tf` has variable `aVersion` which is used in `infrastructure/lambda.tf` to set ECR image URL

### dev-release

ECR supports [dev-releases](../features/dev-release.md): Docker image gets published with tag equal to branch name
(branch name `feature/login` becomes ECR tag `feature-login`).
`agilecustoms/release` action enforces branch name prefix. Itt can be configured with `dev-branch-prefix` input (default is `feature/`).
ECR allows you to configure lifecycle rules by tag prefix, this allows to remove dev-releases automatically in few days!

There is one important gotcha: you can't re-run dev-release for immutable ECR repo:
- immutable ECR repo prevents pushing image for existing tag
- tag can be deleted, but IAM doesn't allow to configure permissions selectively for tag prefix,
so if you allow deleting tags — it is a risk that developer can remove some non-dev tag
- if you make ECR repo mutable — it is a risk that developer can override non-dev tag

So if you need to re-run dev-release shortly (before old tag expired) the workaround is just to create a new branch
(simply increment a number in branch name)

### explicit version

This example showcases how to use [explicit version](../features/version-generation.md#explicit-version).
Expand Down Expand Up @@ -146,3 +130,20 @@ see [build.yml](../examples/env-cleanup/.github/workflows/build.yml) workflow fi
```

It also uses OIDC to assume IAM role and access CodeArtifact in read-only mode with role `ci/builder`

## dev-release

ECR supports [dev-release](../features/dev-release.md): Docker image gets published with tag equal to branch name
(branch name `feature/login` becomes ECR tag `feature-login`).
`agilecustoms/release` action enforces branch name prefix (configured via `dev-branch-prefix`, default is `feature/`).
On the other hand, the ECR supports lifecycle rules by tag prefix.
So if you apply both — you can be sure that all self-service artifacts are removed automatically in a few days!

There is one important gotcha: you can't re-run dev-release for immutable ECR repo:
- immutable ECR repo prevents pushing image for an existing tag
- tag can be deleted, but IAM doesn't allow to configure permissions selectively for tag prefix,
so if you allow deleting tags — it is a risk that a developer can remove some non-dev tag
- if you make ECR repo mutable — it is a risk that a developer can override a non-dev tag

So if you need to re-run dev-release shortly (before old tag expired), the workaround is just to create a new branch:
`feature/login` -> `feature/login-2` and run dev-release from it
98 changes: 87 additions & 11 deletions docs/artifact-types/aws-s3.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
_Note: all examples use shared patterns: two workflows: Build and Release — covered in [Best practices](../best-practices.md);
"release" GitHub environment and AWS IAM authorization — covered in [Authorization and security](../authorization.md)_

AWS S3 is very powerful, and specifically it works very well for software distribution.
AWS S3 is very powerful, and specifically, it works very well for software distribution.
S3 allows flexible read and write permissions by prefix and by tags. And it has rules for expiration so you can auto remove temporary artifacts

For publishing in S3 the `agilecustoms/release` uses a simple convention:
Expand All @@ -16,9 +16,8 @@ Additionally, you can specify `aws-s3-dir`, then files will be uploaded to `{aws

In this section we'll cover some examples of releasing software artifacts in S3:
- [Python lambda function](#python-lambda-function)
- [Application code and IaC](#application-code-and-iac)
- [Go lambda function](#go-lambda-function)
- [Static website](#static-website)
- [Go lambda function](#go-lambda-function)
- [Java CLI application](#java-cli-application)
- [dev-release](#dev-release)

Expand Down Expand Up @@ -55,7 +54,7 @@ jobs:
- name: Download artifacts
uses: actions/download-artifact@v4

# this will download `app.zip` in `s3` directory (see Build workflow)
# this will download `app.zip` and place it in `s3` directory (see Build workflow)
# next step recognize that `s3` directory exists and upload all files from it to S3

- name: Release
Expand All @@ -78,7 +77,7 @@ When developer merges a PR, the Release workflow is triggered:
4. upload `s3/app.zip` to `agilecustoms-dist/env-api/{version}/app.zip`
5. push git commit and tags to the remote repository

### Application code and IaC
### IaC

This is an example of a microservice that consists of an application (Python) code and IaC (Terraform in `infrastructure` directory).
Upon release the `agilecustoms/release` generates a new version, and it is used as git tag and S3 prefix.
Expand All @@ -93,11 +92,84 @@ module "env_api" {

Note file `infrastructure/vars.tf` has variable `aVersion` which is used in `infrastructure/lambda.tf` as part of `s3_key`

## Go lambda function
## Static website

TBD
Example: [tt-web](../examples/tt-web) — it is from AgileCustoms repository with all code removed, only workflows left

## Static website
```
<repo root>
├── .github/
├── dist/ <-- created in Build workflow
│ ├── assets/
│ └── index.html
├── infrastructure/ <-- terraform code
├── src/ <-- TypeScript code
└── package.json
```

In this example a standard npm is used to manage dependencies and `vite` to build static files.
Note: there is no 'package' phase like in Python. That's because AWS offers static website hosting directly from S3 bucket,
and it also offers "copy files" API available in Terraform as `aws_s3_object_copy` resource.
So the distribution format (how release files are stored) should match how S3 serves static files.

Also, this example showcases use of corporate action `mycompany/gha-release` which is a thin wrapper around `agilecustoms/release`.
In this action you provide all defaults, so your release workflow gets even simpler, see [details](../best-practices.md#company-specific-gha-release-wrapper)

```yaml
jobs:
Build:
uses: ./.github/workflows/build.yml
# ...

Release:
needs: Build
# ...
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Download artifacts
uses: actions/download-artifact@v4

# this will download static assets and place them in `s3` directory (see Build workflow)
# next step recognize that `s3` directory exists and upload all files from it to S3

- name: Release
uses: mycompany/gha-release@main
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
```

When developer merges a PR, the Release workflow is triggered:
1. Release workflow calls Build workflow
2. Build workflow uses `vite` to compile TypeScript in JavaScript and place final bundled code in `dist` directory, then Build workflow uploads `dist` directory as an artifact named `s3`
3. Release workflow downloads the artifact, so you get `s3/{dist content}`
4. Release workflow calls `mycompany/release-gha` action
5. `mycompany/release-gha` calls `agilecustoms/release` passing lots of defaults: `aws-account`, `aws-region`, `aws-s3-bucket` and others
6. `aguilecustoms/release` action then:
1. generate next version based on commit messages
2. authorize in AWS with role `ci/publisher`, see [Authorization and security](../authorization.md)
3. update version in `package.json`
4. upload `s3/*` to `mycompany-dist/tt-web/{version}/*`
5. push git commit and tags to the remote repository

### IaC

This is an example of a microservice that consists of an application (TypeScript) code and IaC (Terraform in `infrastructure` directory).
Upon release the `agilecustoms/release` generates a new version, and it is used as git tag and S3 prefix.
So your code and infrastructure are in sync! Now you can deploy infra and code like this:

```hcl
module "tt_web" {
source = "git::https://github.com/agilecustoms/tt-web.git//infrastructure?ref=1.2.3"
aVersion = "1.2.3"
}
```

Note file `infrastructure/main.tf` has variable `aVersion` which is datasource `aws_s3_objects` to access (download) files
from dist S3 bucket and then upload them in static website using resource `aws_s3_object_copy`

## Go lambda function

TBD

Expand All @@ -107,7 +179,11 @@ TBD

## dev-release

TBD
S3 supports [dev-release](../features/dev-release.md). Branch name `feature/login` becomes a version `feature-login`,
and files uploaded at `{aws-s3-bucket}/{current-repo-name}/feature-login/`

Publish files in `{aws-s3-bucket}/{current-repo-name}/{current-branch-name}/` directory.
Each S3 file will be tagged with `Release=false`, so you can set up lifecycle rule to delete such files after 30 days!
`agilecustoms/release` action adds tag `Release` to each S3 object. In normal mode `Release=true`, in dev-release mode `Release=false`.
It is important for security and cleanup:
- you can configure S3 lifecycle rule to auto-remove objects with tag `Release=false` after 30 days
- IAM role (e.g. `ci/publisher-dev`) used in dev-release workflow can distinguish between normal release and dev-release:
it allows to override `Release=false` objects and deny to override `Release=true`
16 changes: 16 additions & 0 deletions docs/artifact-types/git.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ This is the simplest scenario for `agilecustoms/release` as there is no need to
_Note: all examples use shared patterns: two workflows: Build and Release; parameter `npm-extra-deps` —
covered in [Best practices](../best-practices.md); "release" GitHub environment — covered in [Authorization and security](../authorization.md)_

- [Terraform module](#terraform-module)
- [Composite GitHub Action](#composite-github-action)
- [Node.js GitHub Action](#nodejs-github-action)
- [dev-release](#dev-release)

## Terraform module

Example: [terraform-aws-ci-publisher](https://github.com/agilecustoms/terraform-aws-ci-builder)
Expand Down Expand Up @@ -86,3 +91,14 @@ When developer merges a PR, the Release workflow is triggered:
3. Release workflow downloads the artifact and calls `agilecustoms/release` action, then action:
1. commit `dist/index.js`
2. push commit and tags to the remote repository

## dev-release

Git formally supports [dev-release](../features/dev-release.md). It just means you can create a feature branch
and run `agilecustoms/release` action in `dev-release` mode on this branch.
Git acts like glue for all other artifacts: branch `feature/login` becomes `feature-login` version.
IaC files such as Terraform are accessible via `?ref=feature/login`

Input `dev-branch-prefix` (default is `feature/`) enforces that only branches with this prefix
can run `agilecustoms/release` in dev-release mode.
This helps with [security](../features/dev-release.md#security) and auto cleanup
Loading
Loading