The following was almost entirely written by @fabio-rigato
This documents describes an approach to deploying using SemVer release tags to Google Cloud/GKE using Github Actions.
In particular, after reading this document you should be able to:
- Understand how Conventional Commits is integrated
- Understand how Semantic Release is integrated
- Dive into the GitHub Actions (workflows)
- 1 - PR Build & Scan
- 2 - Create Pre-release & Deploy to Dev
- 3 - Create Release
- 4 - Deploy to Integration
- 5 - Deploy to Production
The Conventional Commits specification is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history; which makes it easier to write automated tools on top of. This convention dovetails with SemVer, by describing the features, fixes, and breaking changes made in commit messages.
The commit message should be structured as follows:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
The commit contains the following structural elements, to communicate intent to the consumers of your library:
- fix: a commit of the type fix patches a bug in your codebase (this correlates with PATCH in Semantic Versioning).
- feat: a commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in Semantic Versioning).
- BREAKING CHANGE: a commit that has a footer BREAKING CHANGE:, or appends a ! after the type/scope, introduces a breaking API change (correlating with MAJOR in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type.
- Types other than
fix
: andfeat
: are allowed, for example @commitlint/config-conventional (based on the the Angular convention) recommendsbuild:
,chore:
,ci:
,docs:
,style:
,refactor:
,perf:
,test:
, and others. - footers other than
BREAKING CHANGE: <description>
may be provided and follow a convention similar to git trailer format.
Additional types are not mandated by the Conventional Commits specification, and have no implicit effect in Semantic Versioning (unless they include a BREAKING CHANGE). A scope may be provided to a commit’s type, to provide additional contextual information and is contained within parenthesis, e.g., feat(parser): add ability to parse arrays
.
feat: allow provided config object to extend other configs
BREAKING CHANGE: `extends` key in config file is now used for extending other config files
feat!: send an email to the customer when a product is shipped
feat(api)!: send an email to the customer when a product is shipped
chore!: drop support for Node 6
BREAKING CHANGE: use JavaScript features not available in Node 6.
docs: correct spelling of CHANGELOG
feat(lang): add Polish language
fix: prevent racing of requests
Introduce a request id and a reference to latest request. Dismiss
incoming responses other than from latest request.
Remove timeouts which were used to mitigate the racing issue but are
obsolete now.
Reviewed-by: Z
Refs: #123
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'rotateCommitLabel': true}} }%%
gitGraph
commit id: "wrong commit message"
branch feat/feature
checkout feat/feature
commit id: "feat(feature-1): 'Example of Conventional commit'"
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
- Commits MUST be prefixed with a type, which consists of a noun,
feat
,fix
, etc., followed by the OPTIONAL scope, OPTIONAL!
, and REQUIRED terminal colon and space. - The type
feat
MUST be used when a commit adds a new feature to your application or library. - The type
fix
MUST be used when a commit represents a bug fix for your application. - A scope MAY be provided after a type. A scope MUST consist of a noun describing a section of the codebase surrounded by parenthesis, e.g.,
fix(parser):
- A description MUST immediately follow the colon and space after the type/scope prefix. The description is a short summary of the code changes, e.g., fix: array parsing issue when multiple spaces were contained in string.
- A longer commit body MAY be provided after the short description, providing additional contextual information about the code changes. The body MUST begin one blank line after the description.
- A commit body is free-form and MAY consist of any number of newline separated paragraphs.
- One or more footers MAY be provided one blank line after the body. Each footer MUST consist of a word token, followed by either a
:<space>
or<space>#
separator, followed by a string value (this is inspired by the git trailer convention). - A footer’s token MUST use
-
in place of whitespace characters, e.g.,Acked-by
(this helps differentiate the footer section from a multi-paragraph body). An exception is made forBREAKING CHANGE
, which MAY also be used as a token. - A footer’s value MAY contain spaces and newlines, and parsing MUST terminate when the next valid footer token/separator pair is observed.
- Breaking changes MUST be indicated in the type/scope prefix of a commit, or as an entry in the footer.
- If included as a footer, a breaking change MUST consist of the uppercase text BREAKING CHANGE, followed by a colon, space, and description, e.g., BREAKING CHANGE: environment variables now take precedence over config files.
- If included in the type/scope prefix, breaking changes MUST be indicated by a
!
immediately before the:
. If!
is used,BREAKING CHANGE
: MAY be omitted from the footer section, and the commit description SHALL be used to describe the breaking change. - Types other than
feat
andfix
MAY be used in your commit messages, e.g., docs: updated ref docs. - The units of information that make up Conventional Commits MUST NOT be treated as case sensitive by implementors, with the exception of BREAKING CHANGE which MUST be uppercase.
- BREAKING-CHANGE MUST be synonymous with BREAKING CHANGE, when used as a token in a footer.
- Automatically generating CHANGELOGs.
- Automatically determining a semantic version bump (based on the types of commits landed).
- Communicating the nature of changes to teammates, the public, and other stakeholders.
- Triggering build and publish processes.
- Making it easier for people to contribute to your projects, by allowing them to explore a more structured commit history.
semantic-release automates the whole package release workflow including: determining the next version number, generating the release notes, and publishing the package.
This removes the immediate connection between human emotions and version numbers, strictly following the Semantic Versioning specification and communicating the impact of changes to consumers.
semantic-release uses the commit messages to determine the consumer impact of changes in the codebase. Following formalized conventions for commit messages, semantic-release automatically determines the next semantic version number, generates a changelog and publishes the release.
By default, semantic-release uses Angular Commit Message Conventions.
The table below shows which commit message gets you which release type when semantic-release
runs:
Commit message | Release type |
---|---|
fix(pencil): stop graphite breaking when too much pressure applied |
|
feat(pencil): add 'graphiteWidth' option |
|
perf(pencil): remove graphiteWidth option``BREAKING CHANGE: The graphiteWidth option has been removed.``The default graphite width of 10mm is always used for performance reasons. |
BREAKING CHANGE: token must be in the footer of the commit) |
In this chapter the 3 main GitHub workflow are described. In particular, the 3 workflows will be triggered with in 3 different events.
Workflow Name | Trigger |
---|---|
pull-request-build | PR created |
prerelease-build-and-deploy | PR closed |
new-release | Manual Action |
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'rotateCommitLabel': true}} }%%
gitGraph
commit
commit
branch feat/feature-1
checkout feat/feature-1
commit id: "feat(1)"
commit id: "feat(2)"
commit id: "feat(3)"
checkout main
merge feat/feature-1 tag: "v1.0.0-prerelease.1"
branch release/v1.0.0
checkout release/v1.0.0
commit id: "New Release"
checkout main
merge release/v1.0.0 tag: "v1.0.0"
branch bugfix/fix-1
checkout bugfix/fix-1
commit id: "fix(bug-1)"
commit id: "fix(bug-2)"
checkout main
merge bugfix/fix-1 tag: "v1.0.1-prerelease.1"
branch release/v1.0.1
checkout release/v1.0.1
commit id: "New Release 2"
checkout main
merge release/v1.0.1 tag: "v1.0.1"
branch feat/feature-2
checkout feat/feature-2
commit id: "feat(4)"
commit id: "feat(5)"
commit id: "feat(6)"
checkout main
merge feat/feature-2 tag: "v1.2.0-prerelease.1"
branch release/v1.2.0
checkout release/v1.2.0
commit id: "New Release 3"
checkout main
merge release/v1.2.0 tag: "v1.2.0"
The Git graph above, shows how the branching strategy works. In particular, the default branch is the main
one and features branches (feat/feat-#
) and bugfix branches (bugfix/fix-#
) are forked directly from main
.
Each time a feat/feat-#
or bugfix/fix-#
branch has to be merged to main
, a new PR (Pull Request) has to be created. At this point of time the first workflow pull-request-build
will be triggered. Once the PR will be closed, the second workflow prerelease-build-and-deploy
will be executed and a new pre-release
will be automatically deployed to develop
cluster.
Another branch type that requires to be mentioned, is the release branch (release/vX.Y.Z
). In particular, each time a the manual workflow new-release
will be triggered, a new release will be created and ready to be deployed to a cluster.
In addition, release branches give the ability to schedule releases without disrupting development velocity.
The 1 - PR Build & Scan
is triggered each time a PR is created or new commits are pushed to a open PR, this workflow is a composition of 2 jobs:
commit-lint
build
check
The aim of commit-lint
job is to ensure that Conventional Commit Standard is adopted on each commit messages.
The build
job is mainly responsible to build the Docker images, and generate the manifest file for Kubernetes clusters. In order to recognize the image in the Artifact Registry, and eventually use that image, a Docker tag is generated with the PR number, for example pr-1
.
PLEASE NOTE:
Each time new commits will be pushed to an open PR, the previous image in Artifact Registry (AR) will be replaced with the new one, the main reason is to keep the AR as clean as possible because of the high cost.
The check
job is responsible to scan the image and the manifest file previously generated from virus and vulnerability using Snyk.
In particular, the following graph describes in details each step in the jobs.
graph TD
subgraph 1 - PR Build & Scan
subgraph Conventional Commit Linter
L1[Checkout]-->L2[Conventional Commit Linter]
end
subgraph Build
L2-->B1
B1[Set environment variables]-->B2[Checkout]
B2-->B3[Authenticate to GCP through workload identity pool]
B3-->B4[Install gcloud CLI]
B4-->B5[Configure docker auth]
B5-->B6[Delete previous pr-image from Google Artifacts Registry if exists]
B6-->B7[Build Docker Image]
B7-->B8[Publish Image to Artifact Registry]
B8-->B9[Install helm]
B9-->B10[Pull helm chart]
B10-->B11[Generate manifest file]
B11-->B12["Replace placeholders to job.yaml (if exists)"]
B12-->B13[Set up GKE credentials]
B13-->B14[Dry run deployment]
end
subgraph Check
B14-->C1
C1[Pull Docker to Scan]-->C2[Run Snyk to check Docker image for vulnerabilities]
C2-->C3[Print Snyk image scan result]
C3-->C4[Run Snyk to check yaml files]
C4-->C5[Print Snyk yaml files result]
end
end
The 2 - Create Pre-release & Deploy to Dev
workflow is triggered automatically each time a PR is closed (accepted).
The main purpose of this workflow is to generate a release candidate (or pre-release
) and deploy it automatically to the develop
cluster in order to allow developers to test the new version of the code in place.
The pre-release
version is automatically generated and managed by semantic-release
where, based on the previous version (if any, otherwise the default starting version v1.0.0
will be used as protocol standard) and the conventional commits messages.
The 2 - Create Pre-release & Deploy to Dev
workflow has 4 different jobs:
commit-lint
prerelease
build
check
dev_deploy
The aim of commit-lint
job is to ensure that Conventional Commit Standard is adopted on each commit messages.
The prerelease
job is responsible to generate a new prerelease version and push the TAG
to the main
branch. An example of prerelease tag is v1.0.0-prerelease.1
.
The build
job is mainly responsible to build the Docker images, and generate the manifest file for Kubernetes clusters. The Docker image will have the prerelease tag as a Docker tag.
The check
job is responsible to scan the image and the manifest file previously generated from virus and vulnerability using Snyk.
The dev_deploy
job will deploy to develop
cluster the new images and the Kubernetes manifest.
The following graph describes in details each step in the jobs:
graph TD
subgraph 2 - Create Pre-release & Deploy to Dev
subgraph Conventional Commit Linter
L1[Checkout]-->L2[Conventional Commit Linter]
end
subgraph Prerelease
L2-->P1
P1[Checkout]-->P2[Semantic Release]
P2-->P3[Print info about new pre-release published]
end
subgraph Build
P3-->B1
B1[Set environment variables]-->B2[Checkout]
B2-->B3[Authenticate to GCP through workload identity pool]
B3-->B4[Install gcloud CLI]
B4-->B5[Configure docker auth]
B5-->B6[Build Docker Image]
B6-->B7[Publish Image to Artifact Registry]
B7-->B8[Install helm]
B8-->B9[Pull helm chart]
B9-->B10[Generate manifest file]
B10-->B11["Replace placeholders to job.yaml (if exists)"]
B11-->B12[Set up GKE credentials]
B12-->B13[Dry run deployment]
end
subgraph Check
B13-->C1
C1[Pull Docker to Scan]-->C2[Run Snyk to check Docker image for vulnerabilities]
C2-->C3[Print Snyk image scan result]
C3-->C4[Run Snyk to check yaml files]
C4-->C5[Print Snyk yaml files result]
end
subgraph Dev Deploy
C5-->D1
D1[Set environment variables]-->D2[Fetch manifest]
D2-->D3[Authenticate to GCP through workload identity pool]
D3-->D4[Set up GKE credentials]
D4-->D5[K8s Delete and apply deploy/job.yaml if exists]
D5-->D6[Deploy to develop kubernetes]
end
end
The 3 - Create Release
workflow is triggered from manual action ony
. The aim of this workflow is to promote a current pre-release to a new release. During the execution of this workflow a new release branch will also be created containing the source code of the release. In particular, the release branch, will have the following syntax: release/vX.Y.X
.
The 3 - Create Release
workflow is a composition of 3 jobs:
policy-check
release
build
The policy-check
job guarantee that only the right people with the right permissions can trigger the manual action. In particular, this job makes sure that the user (actor) is part of the core-prod-release
GitHub team. Otherwise the workflow will terminate with a permission error message.
The release
job is responsible to promote the current pre-release to a release. For Example, if the current pre-release is v1.0.1-prerelease.1
the new release will be v1.0.1
. Another important task executed by the release job is to create a new release branch containing the release source code. Following the same release example as before, the new branch will be named release/v1.0.1
. As a last step, a new GitHub release v1.0.1
will be created.
The build
job is responsible to build the image with the v1.0.1
tag and push it to prod
Artifact Registry.
In particular, the following graph is describing in details each steps for each job.
graph TD
subgraph 3 - Create Release
subgraph Policy Check
PC1[Checkout] -->PC2[Get User member]
PC2 -->PC3[Check if member has right permissions]
end
subgraph Release
PC3-->R1
R1[Checkout]-->R2[Semantic Release]
R2-->R3[Print info about new pre-release published]
R3-->R4[Check out code]
R4-->R5[Create release branch]
R5-->R6[Initialize mandatory git config]
R6-->R7[Push new branch]
R7-->R8[Create GitHub Release]
end
subgraph Build
R8-->B1
B1[Set environment variables]-->B2[Checkout]
B2-->B3[Authenticate to GCP through workload identity pool]
B3-->B4[Install gcloud CLI]
B4-->B5[Configure docker auth]
B5-->B6[Build Docker Image]
B6-->B7[Publish Image to Artifact Registry]
end
end
The 4 - Deploy to Integration
workflow, is also triggered from a manual action only
from one of the feature/vX.Y.Z
features branches. The aim of this workflow is to deploy to integration cluster a particular release.
Since the feature/vX.Y.Z
features branches are created with the 3 - Create Release
workflow, we are sure that the Docker images were pushed to prod
Artifact Registry.
This workflow is a composition of 3 jobs:
policy-check
generate-manifest
deploy-to-int
The policy-check
job guarantee that only the right people with the right permissions can trigger the manual action. In particular, this job makes sure that the user (actor) is part of the core-prod-release
GitHub team. Otherwise the workflow will terminate with a permission error message.
The generate-manifest
will generate through Helm template the manifest file for the deployment process.
The deploy-to-int
process, will apply the YAML manifest file generated in the generate-manifest
step to the integration
cluster.
In particular, the following graph describes in details each step of the pipeline:
graph TD
subgraph 4 - Deploy to Integration
subgraph Policy Check
PC1[Checkout] -->PC2[Get User member]
PC2 -->PC3[Check if member has right permissions]
end
subgraph Generate Manifest
PC3-->G1
G1[Set environment variables]-->G2[Extract version X.Y.Z from branch name]
G2-->G3[Checkout]
G3-->G4[Authenticate to GCP through workload identity pool]
G4-->G5[Install gcloud CLI]
G5-->G6[Configure docker auth]
G6-->G7[Install helm]
G7-->G8[Generate manifest file]
G8-->G9[Set up GKE credentials]
G9-->G10[Dry run deployment]
end
subgraph Deploy to Integration
G10-->D1
D1[Set environment variables]-->D2[Fetch manifest]
D2-->D3[Authenticate to GCP through workload identity pool]
D3-->D4[Set up GKE credentials]
D4-->D5[K8s Delete and apply deploy/job.yaml if exists]
D5-->D6[Deploy to kubernetes]
end
end
Same concept as for the integration deployment, the 5 - Deploy to Production
workflow, is also triggered from a manual action only
from one of the feature/vX.Y.Z
features branches. The aim of this workflow is to deploy to production cluster a particular release.
Since the feature/vX.Y.Z
features branches are created with the 3 - Create Release
workflow, we are sure that the Docker images were pushed to prod
Artifact Registry.
This workflow is a composition of 3 jobs:
policy-check
generate-manifest
deploy-to-prd
The policy-check
job guarantee that only the right people with the right permissions can trigger the manual action. In particular, this job makes sure that the user (actor) is part of the core-prod-release
GitHub team. Otherwise the workflow will terminate with a permission error message.
The generate-manifest
will generate through Helm template the manifest file for the deployment process.
The deploy-to-prd
process, will apply the YAML manifest file generated in the generate-manifest
step to the production
cluster.
In particular, the following graph describes in details each step of the pipeline:
graph TD
subgraph 4 - Deploy to Production
subgraph Policy Check
PC1[Checkout] -->PC2[Get User member]
PC2 -->PC3[Check if member has right permissions]
end
subgraph Generate Manifest
PC3-->G1
G1[Set environment variables]-->G2[Extract version X.Y.Z from branch name]
G2-->G3[Checkout]
G3-->G4[Authenticate to GCP through workload identity pool]
G4-->G5[Install gcloud CLI]
G5-->G6[Configure docker auth]
G6-->G7[Install helm]
G7-->G8[Generate manifest file]
G8-->G9[Set up GKE credentials]
G9-->G10[Dry run deployment]
end
subgraph Deploy to Production
G10-->D1
D1[Set environment variables]-->D2[Fetch manifest]
D2-->D3[Authenticate to GCP through workload identity pool]
D3-->D4[Set up GKE credentials]
D4-->D5[K8s Delete and apply deploy/job.yaml if exists]
D5-->D6[Deploy to kubernetes]
end
end