Skip to content

Commit

Permalink
Merge pull request #1360 from swisstopo/feature/viewer-1342-ci-cd
Browse files Browse the repository at this point in the history
Feature-1342: add actions
  • Loading branch information
TIL-EBP authored Nov 13, 2024
2 parents 044ec78 + 71b84a3 commit 2c205a6
Show file tree
Hide file tree
Showing 20 changed files with 1,205 additions and 244 deletions.
25 changes: 25 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Application
APP_PORT=3000

# Cognito
COGNITO_AWS_REGION=eu-west-1
COGNITO_CLIENT_ID=10h1tga4i933buv25lelalmtrn
COGNITO_POOL_ID=eu-west-1_dbfEb2FuH

# S3
S3_AWS_REGION=eu-west-1
AWS_ACCESS_KEY_ID=minio
AWS_SECRET_ACCESS_KEY=minio123
S3_BUCKET=ngmpub-userdata-local
PROJECTS_S3_BUCKET=ngmpub-project-files-local
S3_ENDPOINT=http://minio:9000

# Database
PGUSER=www-data
PGPASSWORD=www-data
PGHOST=db
PGPORT=5432
PGDATABASE=swissgeol-local

# sqlx
DATABASE_URL=postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:${PGPORT}/${PGDATABASE}
71 changes: 71 additions & 0 deletions .github/actions/create-image/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: "Create Docker Image"
description: "Builds a docker image and tags it"
inputs:
IMAGE_NAME:
description: "The image name"
required: true
VERSION:
description: "The version of the image"
required: true
TAG:
description: "The tag of the image, in addition to the version"
required: true
OTHER_TAGS:
description: "Any additional tags, passed directly to docker/metadata-action"
DOCKERFILE:
description: "The path to the Dockerfile"
required: true
GITHUB_TOKEN:
description: "The github token"
required: true
CONTEXT:
description: "The build context"
default: "./"
required: false

runs:
using: "composite"
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set environment variables
shell: bash
run: |
echo COMMITED_AT=$(git show -s --format=%cI ${{ github.sha }}) >> $GITHUB_ENV
echo REVISION=$(git rev-parse --short HEAD) >> $GITHUB_ENV
- name: Collect docker image metadata
id: meta-data
uses: docker/metadata-action@v5
with:
images: ${{ inputs.IMAGE_NAME }}
labels: |
org.opencontainers.image.created=${{ env.COMMITED_AT }}
org.opencontainers.image.version=v${{ inputs.VERSION }}
org.opencontainers.image.maintainer=EBP Schweiz AG
flavor: |
latest=${{ inputs.TAG == 'latest' }}
tags: |
type=raw,value=${{ inputs.TAG }}
type=raw,value=${{ inputs.VERSION }}
${{ inputs.OTHER_TAGS }}
- name: Log in to the GitHub container registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ inputs.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ${{ inputs.CONTEXT }}
file: ${{ inputs.DOCKERFILE }}
push: true
tags: ${{ steps.meta-data.outputs.tags }}
labels: ${{ steps.meta-data.outputs.labels }}
no-cache: true
build-args: |
APP_VERSION=${{ inputs.VERSION }}
34 changes: 34 additions & 0 deletions .github/actions/tag-commit/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: "Tag Commit"
description: "Creates or updates a commit tag"
inputs:
TAG_NAME:
description: "The tag's name"
required: true
SHA:
description: "The SHA of the commit to be tagged"
required: true

runs:
using: "composite"
steps:
- name: Create/update tag
uses: actions/github-script@v7
env:
TAG: ${{ inputs.TAG_NAME }}
SHA: ${{ inputs.SHA }}
with:
script: |
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/tags/${process.env.TAG}`,
sha: process.env.SHA
}).catch(err => {
if (err.status !== 422) throw err;
github.rest.git.updateRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `tags/${process.env.TAG}`,
sha: process.env.SHA
});
})
107 changes: 107 additions & 0 deletions .github/scripts/find-version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
const findNextVersion = (tags, branch) => {
const version = findMostRecentVersion(tags);
if (branch.startsWith("feature/")) {
// It's a minor feature.

// If the previous version was a full release or a patch dev release,
// we are a completely new minor dev release.
// Otherwise, the previous version was itself a minor dev release,
// and we can reuse its number.
if (version.preRelease == null || version.patch !== 0) {
version.minor += 1;
version.patch = 0;
}
} else {
// It's a patch.

// If the previous version was a full release,
// we are a completely new patch dev release.
// Otherwise, we can simply reuse the previous version's number.
if (version.preRelease == null) {
version.patch += 1;
}
}

version.preRelease ??= 0;
version.preRelease += 1;
return version;
};

const findMostRecentVersion = (tags) => {
const versions = findAllVersions(tags);
if (versions.length === 0) {
throw new Error("unable to find a valid version on current edge tag");
}
return versions[0];
};

const findOutdatedVersions = (tags, recentTag) => {
const recentVersion = parseVersion(recentTag);
if (recentVersion == null) {
throw new Error(`recent tag '${recentTag}' is not a version number`);
}
const versions = findAllVersions(tags);
return versions.filter(
(version) =>
// Select all pre-releases that appear before the most recent one.
version.preRelease != null && compareVersions(recentVersion, version) > 0
);
};

const findAllVersions = (tags) => {
return tags
.map(parseVersion)
.filter((it) => it != null)
.sort((a, b) => compareVersions(a, b) * -1);
};

const SEMANTIC_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-dev\d+)?$/;
const parseVersion = (tag) => {
if (!SEMANTIC_VERSION_PATTERN.test(tag)) {
return null;
}
const [major, minor, patch, preRelease] = tag.split(/[.\-]/);
return {
major: parseInt(major),
minor: parseInt(minor),
patch: parseInt(patch),
preRelease: preRelease && parseInt(preRelease.substring(3)),
};
};

const compareVersions = (a, b) => {
if (a.major !== b.major) {
return a.major - b.major;
}
if (a.minor !== b.minor) {
return a.minor - b.minor;
}
if (a.patch !== b.patch) {
return a.patch - b.patch;
}
if (a.preRelease !== b.preRelease) {
if (a.preRelease == null) {
return 1;
}
if (b.preRelease == null) {
return -1;
}
return a.preRelease - b.preRelease;
}
return 0;
};

const makeVersionTag = ({ major, minor, patch, preRelease }) => {
const tag = `${major}.${minor}.${patch}`;
if (preRelease == null) {
return tag;
}
return `${tag}-dev${preRelease}`;
};

module.exports = {
findNextVersion,
findMostRecentVersion,
findOutdatedVersions,
makeVersionTag,
};
51 changes: 51 additions & 0 deletions .github/scripts/remove-packages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const removePackageVersions = async (imageUrl, imageVersions) => {
const { Octokit } = await import("@octokit/rest");

const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
});

const [_imageHost, imageOwner, imageName] = imageUrl.split("/");
const imageIds = await loadOutdatedVersionIds(octokit, imageOwner, imageName, imageVersions);
for (const imageId of imageIds) {
await octokit.rest.packages.deletePackageVersionForOrg({
package_type: "container",
package_name: imageName,
org: imageOwner,
package_version_id: imageId,
});
}
};

const loadOutdatedVersionIds = async (octokit, imageOwner, imageName, versions) => {
let page = 0;
versions = new Set(versions);

const ids = new Set();
while (true) {
const response = await octokit.rest.packages.getAllPackageVersionsForPackageOwnedByOrg({
package_type: "container",
package_name: imageName,
org: imageOwner,
page,
});
if (response.data.length === 0) {
break;
}
for (const entry of response.data) {
// Match any of the requested version's ids,
// as well as any ids that do not have a tag anymore, i.e. are fully unused.
const { tags } = entry.metadata.container;
const matchedTags = tags.filter((tag) => versions.delete(tag));
if (tags.length === 0 || matchedTags.length !== 0) {
ids.add(entry.id);
}
}
page += 1;
}
return ids;
};

module.exports = {
removePackageVersions,
};
22 changes: 22 additions & 0 deletions .github/scripts/wait_for_service.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash

MAX_RETRY_COUNT=10
SERVICE_NAME="$1"

if [ ! -n "$SERVICE_NAME" ]; then
echo "Usage: wait_for_container.sh <service_name>"
exit 1
fi

for i in $(seq 1 $MAX_RETRY_COUNT); do
if [ "$(docker inspect -f '{{.State.Health.Status}}' "swissgeol-viewer-app-$SERVICE_NAME-1")" == "healthy" ]; then
echo "Service $SERVICE_NAME is healthy!"
exit 0
else
echo "Waiting for $SERVICE_NAME to be healthy... (attempt $i of $MAX_RETRY_COUNT)"
sleep 10
fi
done

echo "Service $SERVICE_NAME did not become healthy in time."
exit 1
Loading

0 comments on commit 2c205a6

Please sign in to comment.