Skip to content

Release

Release #44

Workflow file for this run

name: Release
on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: false
env:
COMMIT_NAME: Monkeynator
COMMIT_EMAIL: [email protected]
jobs:
semver:
name: Semantic Version
runs-on: ubuntu-22.04
outputs:
last: ${{ steps.dry-run.outputs.last_release_version }}
published: ${{ steps.dry-run.outputs.new_release_published }}
channel: ${{ steps.dry-run.outputs.new_release_channel }}
version: ${{ steps.dry-run.outputs.new_release_version }}
major: ${{ steps.dry-run.outputs.new_release_major_version }}
minor: ${{ steps.dry-run.outputs.new_release_minor_version }}
patch: ${{ steps.dry-run.outputs.new_release_patch_version }}
notes: ${{ steps.dry-run.outputs.new_release_notes }}
steps:
- name: Checkout Repository
uses: actions/checkout@v3
with:
persist-credentials: false
- name: Run semantic-release (dry-run)
id: dry-run
uses: cycjimmy/[email protected]
with:
dry_run: true
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
- name: Inspect semantic-release (dry-run) outcome
shell: python
env:
PYTHONPATH: ${{ github.workspace }}/.github
SR_LAST: ${{ steps.dry-run.outputs.last_release_version }}
SR_PUBLISHED: ${{ steps.dry-run.outputs.new_release_published }}
SR_CHANNEL: ${{ steps.dry-run.outputs.new_release_channel }}
SR_VERSION: ${{ steps.dry-run.outputs.new_release_version }}
SR_MAJOR: ${{ steps.dry-run.outputs.new_release_major_version }}
SR_MINOR: ${{ steps.dry-run.outputs.new_release_minor_version }}
SR_PATCH: ${{ steps.dry-run.outputs.new_release_patch_version }}
SR_NOTES: ${{ steps.dry-run.outputs.new_release_notes }}
run: |
from lib import *
import os
header('semantic-release job outputs')
info('last = {}'.format(os.environ['SR_LAST']))
info('published = {}'.format(os.environ['SR_PUBLISHED']))
info('channel = {}'.format(os.environ['SR_CHANNEL']))
info('version = {}'.format(os.environ['SR_VERSION']))
info('major = {}'.format(os.environ['SR_MAJOR']))
info('minor = {}'.format(os.environ['SR_MINOR']))
info('patch = {}'.format(os.environ['SR_PATCH']))
info('notes ⏎\n{}'.format(os.environ['SR_NOTES']))
header('sanity checks')
action('should be published')
assert_equality((
(os.environ['SR_PUBLISHED'], 'true'),
))
tests:
name: Tests
runs-on: ubuntu-22.04
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Set up Golang
uses: actions/setup-go@v4
with:
go-version-file: go.mod
check-latest: true
- name: Set up chart-testing
uses: helm/[email protected]
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
args: --timeout 3m --verbose --issues-exit-code=0
- name: Run unit tests
run: |
set -euo pipefail
make test
go tool cover -html=cover.out -o coverage.html
- name: Run hadolint
id: hadolint
uses: hadolint/[email protected]
- name: Run chart-testing (lint)
run: |
set -euo pipefail
ct lint \
--charts helm/kube-image-keeper \
--chart-repos bitnami=https://charts.bitnami.com/bitnami \
--validate-maintainers=false --check-version-increment=false
build:
name: Build test image
needs:
- semver
- tests
runs-on: ubuntu-22.04
env:
VERSION: ${{ github.run_id }}
GHCR_REGISTRY: "ghcr.io"
QUAY_IMAGE: "quay.io/enix/kube-image-keeper"
steps:
- name: Checkout Repository
uses: actions/checkout@v3
with:
path: repository
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ env.COMMIT_NAME }}
password: ${{ secrets.RELEASE_GITHUB_TOKEN }}
- name: Generate image metadata
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.GHCR_REGISTRY }}/${{ github.repository }}
${{ github.repository }}
${{ env.QUAY_IMAGE }}
- name: Build container images
uses: docker/build-push-action@v4
with:
context: repository
platforms: linux/amd64,linux/arm64
build-args: |
"VERSION=${{ env.VERSION }}"
"REVISION=${{ github.sha }}"
push: true
labels: ${{ steps.meta.outputs.labels }}
tags: |
${{ env.GHCR_REGISTRY }}/${{ github.repository }}:${{ env.VERSION }}
- name: Build alpine container images
uses: docker/build-push-action@v4
with:
context: repository
platforms: linux/amd64,linux/arm64
build-args: |
"VERSION=${{ env.VERSION }}"
"REVISION=${{ github.sha }}"
target: alpine
push: true
labels: ${{ steps.meta.outputs.labels }}
tags: |
${{ env.GHCR_REGISTRY }}/${{ github.repository }}:${{ env.VERSION }}-alpine
e2e:
name: Tests End-to-End on K8s
needs:
- build
runs-on: ubuntu-22.04
env:
VERSION: ${{ github.run_id }}
GHCR_IMAGE_NAME: "ghcr.io/enix/kube-image-keeper"
GHCR_REGISTRY: "ghcr.io"
strategy:
max-parallel: 6
matrix:
k8sversion: ["v1.21.14", "v1.22.17", "v1.23.17", "v1.24.15", "v1.25.11", "v1.26.6", "v1.27.3"]
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Setup KinD
uses: engineerd/[email protected]
with:
image: kindest/node:${{ matrix.k8sversion }}
version: v0.17.0
- name: Run cert-manager installation
run: |
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml
kubectl wait pods -n cert-manager -l app.kubernetes.io/instance=cert-manager --for condition=Ready --timeout=30s
- name: Set up chart-testing
uses: helm/[email protected]
- name: Set up helm
uses: azure/setup-helm@v3
with:
version: '3.9.0'
- name: Run chart-testing (lint)
run: |
set -euo pipefail
ct lint \
--charts helm/kube-image-keeper \
--chart-repos bitnami=https://charts.bitnami.com/bitnami \
--validate-maintainers=false --check-version-increment=false
# Need wait for the next release with flag --skip-clean-up
# - name: Run chart-testing (install)
# run: |
# set -euo pipefail
# ct install \
# --charts helm/kube-image-keeper \
# --helm-extra-set-args "--set controllers.image.tag=latest --set proxy.image.tag=latest"
- name: Run helm (install)
run : |
set -euo pipefail
kubectl create namespace kuik-system
kubectl create secret docker-registry ghcr-secret -n kuik-system --docker-server=https://ghcr.io \
--docker-username=monkeynator --docker-password=${{ secrets.RELEASE_GITHUB_TOKEN }} \
--docker-email=${{ env.COMMIT_EMAIL }}
helm upgrade --install kube-image-keeper -n kuik-system --create-namespace ./helm/kube-image-keeper \
--set controllers.image.tag=${{ env.VERSION }} --set proxy.image.tag=${{ env.VERSION }} \
--set controllers.image.repository=${{ env.GHCR_IMAGE_NAME }} --set proxy.image.repository=${{ env.GHCR_IMAGE_NAME }} \
--set controllers.imagePullSecrets[0].name=ghcr-secret --set proxy.image.imagePullSecrets[0].name=ghcr-secret --debug
kubectl wait pods -n kuik-system -l app.kubernetes.io/instance=kube-image-keeper --for condition=Ready --timeout=30s
- name: Run end-to-end tests
run: |
set -euo pipefail
kubectl create deploy nginx --image=nginx:stable-alpine --replicas=2
kubectl wait deployment nginx --for condition=Available=True --timeout=30s
echo "kubectl get cachedimages"
kubectl get cachedimages
if [ $(kubectl get cachedimages -o json | jq ".items[0].status.isCached") ];
then
if [ $(kubectl get cachedimages -o json | jq ".items[0].status.usedBy.count") -eq 2 ];
then
echo "Found cached image used by 2 pods"
else
echo "Error: pods count should be equal 2"
exit 1
fi
else
echo "Error: image cached status is false"
exit 1
fi
release:
name: Release
needs:
- e2e
- semver
runs-on: ubuntu-22.04
outputs:
last: ${{ steps.release.outputs.last_release_version }}
published: ${{ steps.release.outputs.new_release_published }}
channel: ${{ steps.release.outputs.new_release_channel }}
version: ${{ steps.release.outputs.new_release_version }}
major: ${{ steps.release.outputs.new_release_major_version }}
minor: ${{ steps.release.outputs.new_release_minor_version }}
patch: ${{ steps.release.outputs.new_release_patch_version }}
notes: ${{ steps.release.outputs.new_release_notes }}
prerelease: ${{ steps.inspect.outputs.prerelease }}
steps:
- name: Checkout Repository
uses: actions/checkout@v3
with:
path: repository
persist-credentials: false
- name: Run semantic-release
id: release
uses: cycjimmy/semantic-release-action@v3
with:
working_directory: repository
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
GIT_COMMITTER_NAME: ${{ env.COMMIT_NAME }}
GIT_COMMITTER_EMAIL: ${{ env.COMMIT_EMAIL }}
GIT_AUTHOR_NAME: ${{ env.COMMIT_NAME }}
GIT_AUTHOR_EMAIL: ${{ env.COMMIT_EMAIL }}
- name: Inspect semantic-release outcome
id: inspect
shell: python
env:
PYTHONPATH: ${{ github.workspace }}/repository/.github
SRDRY_CHANNEL: ${{ needs.semver.outputs.channel }}
SRDRY_VERSION: ${{ needs.semver.outputs.version }}
SR_LAST: ${{ steps.release.outputs.last_release_version }}
SR_PUBLISHED: ${{ steps.release.outputs.new_release_published }}
SR_CHANNEL: ${{ steps.release.outputs.new_release_channel }}
SR_VERSION: ${{ steps.release.outputs.new_release_version }}
SR_MAJOR: ${{ steps.release.outputs.new_release_major_version }}
SR_MINOR: ${{ steps.release.outputs.new_release_minor_version }}
SR_PATCH: ${{ steps.release.outputs.new_release_patch_version }}
SR_NOTES: ${{ steps.release.outputs.new_release_notes }}
run: |
from lib import *
import os
header('semantic-release job outputs')
info('last = {}'.format(os.environ['SR_LAST']))
info('published = {}'.format(os.environ['SR_PUBLISHED']))
info('channel = {}'.format(os.environ['SR_CHANNEL']))
info('version = {}'.format(os.environ['SR_VERSION']))
info('major = {}'.format(os.environ['SR_MAJOR']))
info('minor = {}'.format(os.environ['SR_MINOR']))
info('patch = {}'.format(os.environ['SR_PATCH']))
info('notes ⏎\n{}'.format(os.environ['SR_NOTES']))
header('sanity checks')
action('should be published')
assert_equality((
(os.environ['SR_PUBLISHED'], 'true'),
))
action('consistency with the dry-run')
assert_equality((
(os.environ['SR_CHANNEL'], os.environ['SRDRY_CHANNEL']),
(os.environ['SR_VERSION'], os.environ['SRDRY_VERSION']),
))
header('set the prerelease status')
is_prerelease = '-' in os.environ['SR_VERSION']
info('pre-release = {}'.format(is_prerelease))
output = '{}'.format(str(is_prerelease).lower())
with open(os.environ['GITHUB_OUTPUT'], 'a') as outfile:
print (f'prerelease={output}', file=outfile)
containers:
name: Containers
needs:
- release
runs-on: ubuntu-22.04
env:
HELM_DOCS_VERSION: "1.11.0"
CR_VERSION: "1.4.1"
VERSION: ${{ needs.release.outputs.version }}
PRERELEASE: ${{ needs.release.outputs.prerelease }}
QUAY_REPOSITORY: "quay.io/enix/kube-image-keeper"
steps:
- name: Configure git
run: |
set -euo pipefail
git config --global user.name '${{ env.COMMIT_NAME }}'
git config --global user.email '${{ env.COMMIT_EMAIL }}'
- name: Checkout Repository
uses: actions/checkout@v3
with:
path: repository
- name: Set up helm
uses: azure/setup-helm@v3
with:
version: '3.9.0'
- name: Helm repository deps
run: |
helm repo add bitnami https://charts.bitnami.com/bitnami
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Cache for chart-releaser
id: cache-cr
uses: actions/cache@v3
with:
path: bin/cr
key: ${{ runner.os }}-cr-${{ env.CR_VERSION }}
- name: Set up chart-releaser
if: steps.cache-cr.outputs.cache-hit != 'true'
run: |
set -euo pipefail
[ -d bin ] || mkdir bin
URL='https://github.com/helm/chart-releaser/releases/download/v${{ env.CR_VERSION }}/chart-releaser_${{ env.CR_VERSION }}_linux_amd64.tar.gz'
curl -sSL "${URL}" | tar xz -C bin cr
- name: Cache for helm-docs
id: cache-helm-docs
uses: actions/cache@v3
with:
path: bin/helm-docs
key: ${{ runner.os }}-helm-docs-${{ env.HELM_DOCS_VERSION }}
- name: Set up helm-docs
if: steps.cache-helm-docs.outputs.cache-hit != 'true'
run: |
set -euo pipefail
[ -d bin ] || mkdir bin
URL='https://github.com/norwoodj/helm-docs/releases/download/v${{ env.HELM_DOCS_VERSION }}/helm-docs_${{ env.HELM_DOCS_VERSION }}_Linux_x86_64.tar.gz'
curl -sSL "${URL}" | tar xz -C bin helm-docs
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Quay.io
uses: docker/login-action@v2
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_ROBOT_TOKEN }}
- name: Generate image metadata
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ github.repository }}
${{ env.QUAY_REPOSITORY }}
- name: Build container images
uses: docker/build-push-action@v4
with:
context: repository
platforms: linux/amd64,linux/arm64
build-args: |
"VERSION=${{ env.VERSION }}"
"REVISION=${{ github.sha }}"
push: false
labels: ${{ steps.meta.outputs.labels }}
tags: |
${{ github.repository }}:${{ env.VERSION }}
${{ env.QUAY_REPOSITORY }}:${{ env.VERSION }}
- name: Push container images tag (Latest)
uses: docker/build-push-action@v4
if: ${{ env.PRERELEASE != 'true' }}
with:
context: repository
platforms: linux/amd64,linux/arm64
build-args: |
"VERSION=${{ env.VERSION }}"
"REVISION=${{ github.sha }}"
push: true
labels: ${{ steps.meta.outputs.labels }}
tags: |
${{ github.repository }}:latest
${{ env.QUAY_REPOSITORY }}:latest
- name: Push container images tag (Release)
uses: docker/build-push-action@v4
with:
context: repository
platforms: linux/amd64,linux/arm64
build-args: |
"VERSION=${{ env.VERSION }}"
"REVISION=${{ github.sha }}"
push: true
labels: ${{ steps.meta.outputs.labels }}
tags: |
${{ github.repository }}:${{ env.VERSION }}
${{ env.QUAY_REPOSITORY }}:${{ env.VERSION }}
- name: Push alpine container images tag (Release)
uses: docker/build-push-action@v4
with:
context: repository
platforms: linux/amd64,linux/arm64
build-args: |
"VERSION=${{ env.VERSION }}"
"REVISION=${{ github.sha }}"
target: alpine
push: true
labels: ${{ steps.meta.outputs.labels }}
tags: |
${{ github.repository }}:${{ env.VERSION }}-alpine
${{ env.QUAY_REPOSITORY }}:${{ env.VERSION }}-alpine
- name: Convert Github changelog for Artifacthub
shell: python
env:
GITHUB_CHANGELOG: ${{ needs.release.outputs.notes }}
run: |
import os, yaml, re
# Based on:
# - https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-angular/writer-opts.js
# - https://github.com/artifacthub/hub/blob/master/web/src/layout/package/changelog/Content.tsx
header_to_kind = {
'Features': { 'kind': 'added', 'prefix': '' },
'Bug Fixes': { 'kind': 'fixed', 'prefix': '' },
'Reverts': { 'kind': 'removed', 'prefix': 'revert' },
'Performance Improvements': { 'kind': 'changed', 'prefix': 'perf' },
'BREAKING CHANGES': { 'kind': 'changed', 'prefix': 'BREAKING' },
# sections bellow won't show up in conventional-changelog unless having 'BREAKING' notes
'Documentation': { 'kind': 'changed', 'prefix': 'docs' },
'Styles': { 'kind': 'changed', 'prefix': 'style' },
'Code Refactoring': { 'kind': 'changed', 'prefix': 'refactor' },
'Tests': { 'kind': 'changed', 'prefix': 'test' },
'Build System': { 'kind': 'changed', 'prefix': 'build' },
'Continuous Integration': { 'kind': 'changed', 'prefix': 'ci' },
}
extract_log = re.compile(
r'\* '
r'(?:\*\*(?P<scope>.+):\*\* )?'
r'(?P<description>.*?)'
r'(?: \(\[[0-9a-f]+\]\((?P<commit>[^)]*)\)\)'
r'(?:, closes (?P<issues>.*))?'
r')?')
extract_issues = re.compile(
r' ?(?:(?:#[0-9+])|(?:\[#(?P<id>[0-9]+)\]\((?P<url>[^)]*)\)))+')
entries = []
mapping = None
for line in os.environ['GITHUB_CHANGELOG'].splitlines():
if line.startswith('### '):
header = line[4:].strip()
mapping = header_to_kind.get(header, None)
continue
if mapping and line.startswith('*'):
match = extract_log.fullmatch(line)
if match is None:
raise ValueError('failed to extract log line: {}'.format(line))
scope = match.group('scope')
if scope == '*':
scope = None
kind = mapping.get('kind')
description = match.group('description')
desc_prefix = mapping.get('prefix')
if desc_prefix:
if scope:
description = '{}({}): {}'.format(desc_prefix, scope, description)
else:
description = '{}: {}'.format(desc_prefix, description)
else:
if scope == 'security':
kind = 'security'
elif scope:
description = '{}: {}'.format(scope, description)
links = []
commit_url = match.group('commit')
if commit_url:
links.append({
'name': 'GitHub commit',
'url': commit_url
})
issues = match.group('issues')
if issues:
for issue in extract_issues.finditer(issues):
links.append({
'name': 'GitHub issue #{}'.format(issue.group('id')),
'url': issue.group('url')
})
entry = {
'kind': kind,
'description': description
}
if len(links):
entry['links'] = links
entries.append(entry)
if len(entries):
output = yaml.dump(entries)
else:
output = ''
print(output)
with open(os.environ['GITHUB_ENV'], 'a') as outfile:
outfile.write('ARTIFACTHUB_CHANGELOG<<EOF\n')
outfile.write(output)
outfile.write('EOF\n')
# TODO
# - OCI releases?
# - signing?
- name: Run chart-releaser
shell: python
env:
WORKSPACE: ${{ github.workspace }}
PYTHONPATH: ${{ github.workspace }}/repository/.github
HELM_DOCS_PATH: ${{ github.workspace }}/bin/helm-docs
CR_PATH: ${{ github.workspace }}/bin/cr
CR_TOKEN: ${{ secrets.CHARTSREPO_GITHUB_TOKEN }}
CHART_NAME: kube-image-keeper
VERSION: ${{ needs.release.outputs.version }}
PRERELEASE: ${{ needs.release.outputs.prerelease }}
ARTIFACTHUB_CHANGELOG: ${{ env.ARTIFACTHUB_CHANGELOG }}
run: |
from lib import *
import os, yaml, shutil
chart_path = os.path.join(os.environ['WORKSPACE'], 'repository', 'helm', 'kube-image-keeper')
os.chdir(chart_path)
header('generate chart readme')
run(os.environ['HELM_DOCS_PATH'], '--dry-run')
run(os.environ['HELM_DOCS_PATH'])
header('prepare chart manifest')
version = os.environ['VERSION']
assert_length_above((
(version, 0),
))
is_prerelease = os.environ['PRERELEASE']
is_security_update = 'false' # FIXME
assert_in((
(is_prerelease, ('true', 'false')),
(is_security_update, ('true', 'false')),
))
info('version = {}'.format(version))
info('prerelease = {}'.format(is_prerelease))
info('security fix = {}'.format(is_security_update))
manifest_file = 'Chart.yaml'
manifest = yaml.safe_load(open(manifest_file, 'r'))
manifest.update({
'version': version,
'appVersion': version,
})
manifest['annotations'].update({
'artifacthub.io/prerelease': is_prerelease,
'artifacthub.io/containsSecurityUpdates': is_security_update,
})
changelog = os.environ['ARTIFACTHUB_CHANGELOG']
if len(changelog):
manifest['annotations'].update({
'artifacthub.io/changes': changelog,
})
open(manifest_file, 'w').write(yaml.dump(manifest))
header('inspect files to be released')
for yaml_file in (manifest_file,):
action('YAML: {}'.format(yaml_file))
print(yaml.safe_load(open(yaml_file, 'r')))
header('release the chart')
action('clone helm charts repository')
charts_repo = os.path.join(os.environ['WORKSPACE'], 'enix-charts')
run('git', 'clone', 'https://github.com/enix/helm-charts', charts_repo)
action('copy chart files')
repo_chart_path = os.path.join(charts_repo, 'charts', os.environ['CHART_NAME'])
shutil.copytree(chart_path, repo_chart_path, symlinks=True, dirs_exist_ok=False)
action('create the chart package')
run(os.environ['CR_PATH'], 'package', repo_chart_path, cwd=charts_repo)
action('upload the chart')
run(os.environ['CR_PATH'], 'upload', '--skip-existing', '--owner', 'enix', '--git-repo', 'helm-charts', cwd=charts_repo)
action('update repository index')
run(os.environ['CR_PATH'], 'index', '--push', '-i', 'index.yaml', '--owner', 'enix', '--git-repo', 'helm-charts', cwd=charts_repo)
cleanup:
name: Cleanup
needs:
# - release
- containers
runs-on: ubuntu-22.04
steps:
- name: Delete assets artifact
uses: geekyeggo/delete-artifact@v2
with:
name: binaries