Skip to content

Commit 9d7542e

Browse files
authored
test: introduce dgoss tests & github actions (#3)
* test: introduce dgoss tests & github actions * ci: merge publish workflows & introduce prepare action * fix: workflow file errors * ci: fix action path * ci: add image names * ci: setup buildx before building * ci: export image to docker for dgoss * ci: checkout before running prepare * ci: disable multi arch and version tests for now * ci: testing publish action * ci: some debug outputs * ci: fix typo * ci: fix tag-as-latest options * ci: trying to get it working... * ci: simplify github action * ci: force build temporarily * ci: re-enable previous steps & deps * ci: introduce codecov-action * ci: speed up pipeline by skipping unrelated platforms
1 parent 0a45b70 commit 9d7542e

13 files changed

Lines changed: 357 additions & 129 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Docker image tagger
2+
description: Action that generates docker tags based on github events
3+
4+
inputs:
5+
git-ref:
6+
required: true
7+
description: 'GitHub ref to base tags off of. (default: $github.ref)'
8+
default: ${{ github.ref }}
9+
image-names:
10+
required: true
11+
description: 'New line delimited list of repositories'
12+
13+
outputs:
14+
image-tags:
15+
description: 'New line separated list of tags... can be directly used as "tags" for docker/build-push-action@v2'
16+
17+
runs:
18+
using: node12
19+
main: index.js
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const parseRef = ref => {
2+
const [, type, ...rest] = ref.split('/');
3+
return [type, rest.join('/')];
4+
};
5+
6+
const isRefType = (parsedRef, type) => parsedRef[0] === type;
7+
const isHead = parsedRef => isRefType(parsedRef, 'heads');
8+
const isBranch = (parsedRef, branch) =>
9+
isHead(parsedRef) && parsedRef[1] === branch;
10+
const isTag = parsedRef => isRefType(parsedRef, 'tags');
11+
12+
module.exports = {
13+
parseRef,
14+
isRefType,
15+
isHead,
16+
isBranch,
17+
isTag,
18+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const { isBranch, isTag, isHead } = require('./git-ref');
2+
3+
const isMaster = parsedRef => isBranch(parsedRef, 'master');
4+
const isVersionTag = parsedRef => isTag(parsedRef) && parsedRef[1].startsWith('v');
5+
6+
const REF_TYPE = {
7+
VERSION_TAG: 'version-tag',
8+
MASTER_BRANCH: 'master-branch',
9+
NON_MASTER_BRANCH: 'non-master-branch',
10+
};
11+
12+
const getRefType = parsedRef => {
13+
switch (true) {
14+
case isVersionTag(parsedRef):
15+
return REF_TYPE.VERSION_TAG;
16+
case isMaster(parsedRef):
17+
return REF_TYPE.MASTER_BRANCH;
18+
case isHead(parsedRef):
19+
return REF_TYPE.NON_MASTER_BRANCH;
20+
default:
21+
return;
22+
}
23+
};
24+
25+
module.exports = {
26+
REF_TYPE,
27+
isMaster,
28+
isVersionTag,
29+
getRefType,
30+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const { getTagMatrix } = require('./tags');
2+
const { getConfig, setOutput, writeInfo, fail } = require('./io');
3+
4+
const { gitRef, imageNames } = getConfig();
5+
6+
const tags = getTagMatrix(gitRef, imageNames, 'git-');
7+
if (!tags.length) {
8+
fail(
9+
`Strategy (${JSON.stringify(
10+
tags,
11+
)}) produced no tags for git-ref "${gitRef}"`,
12+
);
13+
}
14+
15+
writeInfo(JSON.stringify({ outputs: { 'image-tags': tags } }, null, 2));
16+
setOutput('image-tags', tags.join('\n'));
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { getInput, getInputLines } = require('./input');
2+
const {
3+
writeDebug,
4+
writeInfo,
5+
writeWarning,
6+
writeError,
7+
setOutput,
8+
fail,
9+
} = require('./output');
10+
11+
const getConfig = () => ({
12+
gitRef: getInput('git-ref'),
13+
imageNames: getInputLines('image-names'),
14+
});
15+
16+
module.exports = {
17+
getInput,
18+
getInputLines,
19+
getConfig,
20+
writeDebug,
21+
writeInfo,
22+
writeWarning,
23+
writeError,
24+
setOutput,
25+
fail,
26+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const { fail } = require('./output');
2+
3+
const getRawInput = name =>
4+
process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`];
5+
6+
const getInput = (name, defaultValue = '') => getRawInput(name) || defaultValue;
7+
8+
const getInputLines = (name, separator = '\n') => {
9+
const input = getInput(name).trim();
10+
return input ? input.split(separator).map(line => line.trim()) : [];
11+
};
12+
13+
const validateEnum = (name, value, options = []) =>
14+
options.includes(value)
15+
? value
16+
: fail(`${name} can't be ${value}. Only ${options.join(', ')} allowed`);
17+
18+
const getInputEnum = (name, options = [], defaultValue = '\n') =>
19+
validateEnum(name, getInput(name, defaultValue), options);
20+
21+
module.exports = {
22+
getInput,
23+
getInputEnum,
24+
getInputLines,
25+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const { EOL } = require('os');
2+
3+
const sanitize = input => {
4+
if (input === null || input === undefined) {
5+
return '';
6+
} else if (typeof input === 'string' || input instanceof String) {
7+
return input;
8+
}
9+
return JSON.stringify(input);
10+
};
11+
12+
const escapeData = s =>
13+
sanitize(s)
14+
.replace(/%/g, '%25')
15+
.replace(/\r/g, '%0D')
16+
.replace(/\n/g, '%0A');
17+
18+
const escapeProperty = s =>
19+
sanitize(s)
20+
.replace(/%/g, '%25')
21+
.replace(/\r/g, '%0D')
22+
.replace(/\n/g, '%0A')
23+
.replace(/:/g, '%3A')
24+
.replace(/,/g, '%2C');
25+
26+
const formatCommand = (name, value, props = {}) =>
27+
`::${name} ${formatProperties(props)}::${escapeData(value)}`;
28+
29+
const formatProperties = props =>
30+
Object.entries(props)
31+
.map(([key, prop]) => `${key}=${escapeProperty(prop)}`)
32+
.join(',');
33+
34+
const writeLine = line => process.stdout.write(`${line}${EOL}`);
35+
36+
const setOutput = (name, data) =>
37+
writeLine(formatCommand('set-output', data, { name }));
38+
39+
const writeDebug = message => writeLine(formatCommand('debug', message));
40+
41+
const writeInfo = message => writeLine(message);
42+
43+
const ensureString = reason =>
44+
reason instanceof Error ? reason.toString() : reason;
45+
46+
const writeWarning = message =>
47+
writeLine(formatCommand('warning', ensureString(message)));
48+
49+
const writeError = message =>
50+
writeLine(formatCommand('error', ensureString(message)));
51+
52+
const fail = reason => {
53+
writeError(reason);
54+
process.exit(1);
55+
};
56+
57+
module.exports = {
58+
setOutput,
59+
writeDebug,
60+
writeInfo,
61+
writeWarning,
62+
writeError,
63+
fail,
64+
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const { parseRef } = require('./git-ref');
2+
const { REF_TYPE, getRefType } = require('./helpers');
3+
4+
const refTypeTag = (refType, parsedRef, branchPrefix) => {
5+
switch (refType) {
6+
case REF_TYPE.VERSION_TAG:
7+
return [parsedRef[1]];
8+
case REF_TYPE.MASTER_BRANCH:
9+
return ['master'];
10+
case REF_TYPE.NON_MASTER_BRANCH:
11+
return [`${branchPrefix}${parsedRef[1]}`];
12+
}
13+
};
14+
15+
const latestTag = refType =>
16+
refType === REF_TYPE.VERSION_TAG ? ['latest'] : [];
17+
18+
const getTags = (parsedRef, branchPrefix) => {
19+
const refType = getRefType(parsedRef);
20+
return [
21+
...refTypeTag(refType, parsedRef, branchPrefix),
22+
...latestTag(refType),
23+
];
24+
};
25+
26+
const combine = (imageNames, tags) =>
27+
tags
28+
.flatMap(tag => imageNames.map(image => [image, tag]))
29+
.map(([image, tag]) => `${image}:${tag}`);
30+
31+
const getTagMatrix = (ref, imageNames, branchPrefix) => {
32+
const parsedRef = parseRef(ref);
33+
return combine(imageNames, getTags(parsedRef, branchPrefix));
34+
};
35+
36+
module.exports = {
37+
getTagMatrix,
38+
};

.github/workflows/cicd.yml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
name: CICD
2+
on:
3+
push:
4+
branches: '*'
5+
tags: 'v*'
6+
paths:
7+
- '.github/workflows/cicd.yml'
8+
9+
jobs:
10+
test:
11+
runs-on: ${{ matrix.os }}
12+
strategy:
13+
matrix:
14+
node: ['14.x', '12.x', '10.x']
15+
os: [ubuntu-latest]
16+
steps:
17+
- run: git config --global core.autocrlf false
18+
- uses: actions/checkout@v2
19+
- name: Use Node v${{ matrix.node }}
20+
uses: actions/setup-node@v2
21+
with:
22+
check-latest: true
23+
node-version: ${{ matrix.node }}
24+
- uses: bahmutov/npm-install@v1
25+
- run: yarn lint
26+
- run: yarn test --ci --coverage --maxWorkers=2
27+
- run: yarn build
28+
- uses: codecov/codecov-action@v1
29+
if: ${{ success() }}
30+
continue-on-error: true
31+
32+
test-dgoss:
33+
runs-on: ubuntu-latest
34+
steps:
35+
- uses: docker/setup-buildx-action@v1
36+
- name: Cache Docker layers
37+
uses: actions/cache@v2
38+
with:
39+
path: /tmp/.buildx-cache
40+
key: ${{ runner.os }}-buildx-${{ github.sha }}
41+
restore-keys: |
42+
${{ runner.os }}-buildx-
43+
- uses: actions/checkout@v2
44+
- uses: docker/build-push-action@v2
45+
with:
46+
context: .
47+
file: ./Dockerfile
48+
push: false
49+
load: true
50+
cache-from: type=local,src=/tmp/.buildx-cache
51+
cache-to: type=local,dest=/tmp/.buildx-cache
52+
tags: tesseract-server:goss
53+
- uses: e1himself/goss-installation-action@v1.0.3
54+
- run: dgoss run tesseract-server:goss
55+
56+
publish:
57+
if: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master' }}
58+
needs: [test, test-dgoss]
59+
runs-on: ubuntu-latest
60+
steps:
61+
- uses: actions/checkout@v2
62+
- id: dockerTagger
63+
uses: ./.github/actions/publish/dockerTagger
64+
with:
65+
image-names: |
66+
ghcr.io/hertzg/tesseract-server
67+
hertzg/tesseract-server
68+
69+
- uses: docker/setup-qemu-action@v1
70+
- uses: docker/setup-buildx-action@v1
71+
- uses: docker/login-action@v1
72+
with:
73+
username: ${{ secrets.DOCKERHUB_USERNAME }}
74+
password: ${{ secrets.DOCKERHUB_TOKEN }}
75+
- uses: docker/login-action@v1
76+
with:
77+
registry: ghcr.io
78+
username: ${{ github.repository_owner }}
79+
password: ${{ secrets.GHCR_PAT }}
80+
- name: Cache Docker layers
81+
uses: actions/cache@v2
82+
with:
83+
path: /tmp/.buildx-cache
84+
key: ${{ runner.os }}-buildx-${{ github.sha }}
85+
restore-keys: |
86+
${{ runner.os }}-buildx-
87+
- name: Build & Push
88+
uses: docker/build-push-action@v2
89+
with:
90+
context: .
91+
file: ./Dockerfile
92+
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
93+
push: false
94+
cache-from: type=local,src=/tmp/.buildx-cache
95+
cache-to: type=local,dest=/tmp/.buildx-cache
96+
tags: ${{ steps.dockerTagger.outputs.image-tags }}

0 commit comments

Comments
 (0)