Skip to content

Commit 1ce9422

Browse files
authored
Merge pull request #248 from escemi-tech/add-option-to-choose-between-git-or-workflow-context
allows to specify context to fetch git data
2 parents 9ec57ed + 8f8c795 commit 1ce9422

File tree

9 files changed

+177
-73
lines changed

9 files changed

+177
-73
lines changed

README.md

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ ___
2222
* [inputs](#inputs)
2323
* [outputs](#outputs)
2424
* [environment variables](#environment-variables)
25+
* [`context` input](#context-input)
2526
* [`images` input](#images-input)
2627
* [`flavor` input](#flavor-input)
2728
* [`tags` input](#tags-input)
@@ -275,15 +276,16 @@ Following inputs can be used as `step.with` keys
275276
> org.opencontainers.image.vendor=MyCompany
276277
> ```
277278

278-
| Name | Type | Description |
279-
|---------------------|--------|----------------------------------------------------------|
280-
| `images` | List | List of Docker images to use as base name for tags |
281-
| `tags` | List | List of [tags](#tags-input) as key-value pair attributes |
282-
| `flavor` | List | [Flavor](#flavor-input) to apply |
283-
| `labels` | List | List of custom labels |
284-
| `sep-tags` | String | Separator to use for tags output (default `\n`) |
285-
| `sep-labels` | String | Separator to use for labels output (default `\n`) |
286-
| `bake-target` | String | Bake target name (default `docker-metadata-action`) |
279+
| Name | Type | Description |
280+
|---------------------|--------|-------------------------------------------------------------------------------|
281+
| `context` | String | Where to get context data. Allowed options are: `workflow` (default), `git`. |
282+
| `images` | List | List of Docker images to use as base name for tags |
283+
| `tags` | List | List of [tags](#tags-input) as key-value pair attributes |
284+
| `flavor` | List | [Flavor](#flavor-input) to apply |
285+
| `labels` | List | List of custom labels |
286+
| `sep-tags` | String | Separator to use for tags output (default `\n`) |
287+
| `sep-labels` | String | Separator to use for labels output (default `\n`) |
288+
| `bake-target` | String | Bake target name (default `docker-metadata-action`) |
287289

288290
### outputs
289291

@@ -320,6 +322,20 @@ So it can be used with our [Docker Build Push action](https://github.com/docker/
320322
|-------------------------------|------|------------------------------------------------------------------------------------------------------------|
321323
| `DOCKER_METADATA_PR_HEAD_SHA` | Bool | If `true`, set associated head SHA instead of commit SHA that triggered the workflow on pull request event |
322324

325+
## `context` input
326+
327+
`context` defines where to get context metadata:
328+
329+
```yaml
330+
# default
331+
context: workflow
332+
# or
333+
context: git
334+
```
335+
336+
* `workflow`: Get context metadata from the workflow (GitHub context). See https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
337+
* `git`: Get context metadata from the workflow and overrides some of them with current Git context, such as `ref` and `sha`.
338+
323339
## `images` input
324340

325341
`images` defines a list of Docker images to use as base name for [`tags`](#tags-input):

__tests__/context.test.ts

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import {beforeEach, describe, expect, test} from '@jest/globals';
2-
3-
import * as context from '../src/context';
1+
import {Context} from '@actions/github/lib/context';
2+
import {beforeEach, describe, expect, test, it, jest} from '@jest/globals';
3+
import * as dotenv from 'dotenv';
4+
import * as fs from 'fs';
5+
import * as path from 'path';
6+
import {ContextSource, getInputs, Inputs} from '../src/context';
47

58
describe('getInputs', () => {
69
beforeEach(() => {
@@ -20,6 +23,7 @@ describe('getInputs', () => {
2023
['images', 'moby/buildkit\nghcr.io/moby/mbuildkit'],
2124
]),
2225
{
26+
context: ContextSource.workflow,
2327
bakeTarget: 'docker-metadata-action',
2428
flavor: [],
2529
githubToken: '',
@@ -28,7 +32,7 @@ describe('getInputs', () => {
2832
sepLabels: '\n',
2933
sepTags: '\n',
3034
tags: [],
31-
} as context.Inputs
35+
} as Inputs
3236
],
3337
[
3438
1,
@@ -39,6 +43,7 @@ describe('getInputs', () => {
3943
['sep-tags', ','],
4044
]),
4145
{
46+
context: ContextSource.workflow,
4247
bakeTarget: 'metadata',
4348
flavor: [],
4449
githubToken: '',
@@ -47,19 +52,54 @@ describe('getInputs', () => {
4752
sepLabels: ',',
4853
sepTags: ',',
4954
tags: [],
50-
} as context.Inputs
55+
} as Inputs
5156
]
5257
])(
5358
'[%d] given %p as inputs, returns %p',
54-
async (num: number, inputs: Map<string, string>, expected: context.Inputs) => {
59+
async (num: number, inputs: Map<string, string>, expected: Inputs) => {
5560
inputs.forEach((value: string, name: string) => {
5661
setInput(name, value);
5762
});
58-
expect(await context.getInputs()).toEqual(expected);
63+
expect(await getInputs()).toEqual(expected);
5964
}
6065
);
6166
});
6267

68+
describe('getContext', () => {
69+
it('get context with workflow', async () => {
70+
process.env = dotenv.parse(fs.readFileSync(path.join(__dirname, 'fixtures/event_create_branch.env')));
71+
72+
jest.resetModules();
73+
// eslint-disable-next-line @typescript-eslint/no-var-requires
74+
const {getContext} = require('../src/context');
75+
const contextResult = await getContext(ContextSource.workflow);
76+
77+
expect(contextResult.ref).toEqual('refs/heads/dev');
78+
expect(contextResult.sha).toEqual('5f3331d7f7044c18ca9f12c77d961c4d7cf3276a');
79+
});
80+
81+
it('get context with git', async () => {
82+
jest.resetModules();
83+
84+
// eslint-disable-next-line @typescript-eslint/no-var-requires
85+
const git = require('@docker/actions-toolkit/lib/git');
86+
jest.spyOn(git.Git, 'context').mockImplementation((): Promise<Context> => {
87+
return Promise.resolve({
88+
ref: 'refs/heads/git-test',
89+
sha: 'git-test-sha'
90+
} as Context);
91+
});
92+
93+
// eslint-disable-next-line @typescript-eslint/no-var-requires
94+
const {getContext} = require('../src/context');
95+
96+
const contextResult = await getContext(ContextSource.git);
97+
98+
expect(contextResult.ref).toEqual('refs/heads/git-test');
99+
expect(contextResult.sha).toEqual('git-test-sha');
100+
});
101+
});
102+
63103
// See: https://github.com/actions/toolkit/blob/master/packages/core/src/core.ts#L67
64104
function getInputName(name: string): string {
65105
return `INPUT_${name.replace(/ /g, '_').toUpperCase()}`;

__tests__/meta.test.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@ import {beforeEach, describe, expect, jest, test} from '@jest/globals';
22
import * as fs from 'fs';
33
import * as path from 'path';
44
import * as dotenv from 'dotenv';
5-
import {Context} from '@actions/github/lib/context';
65
import {GitHub} from '@docker/actions-toolkit/lib/github';
76
import {Toolkit} from '@docker/actions-toolkit/lib/toolkit';
87
import {GitHubRepo} from '@docker/actions-toolkit/lib/types/github';
9-
10-
import {getInputs, Inputs} from '../src/context';
8+
import {ContextSource, getInputs, Inputs} from '../src/context';
119
import {Meta, Version} from '../src/meta';
1210

1311
import repoFixture from './fixtures/repo.json';
@@ -23,6 +21,17 @@ jest.mock('moment-timezone', () => {
2321
return () => (jest.requireActual('moment-timezone') as typeof import('moment-timezone'))('2020-01-10T00:30:00.000Z');
2422
});
2523

24+
/**
25+
* Get a workflow context based on the current environment variables.
26+
*/
27+
const getFreshWorkflowContext = async () => {
28+
jest.resetModules();
29+
// eslint-disable-next-line @typescript-eslint/no-var-requires
30+
const {getContext} = require('../src/context');
31+
const context = await getContext(ContextSource.workflow);
32+
return context;
33+
};
34+
2635
beforeEach(() => {
2736
jest.clearAllMocks();
2837
Object.keys(process.env).forEach(function (key) {
@@ -49,7 +58,8 @@ const tagsLabelsTest = async (name: string, envFile: string, inputs: Inputs, exV
4958
process.env = dotenv.parse(fs.readFileSync(path.join(__dirname, 'fixtures', envFile)));
5059
const toolkit = new Toolkit();
5160
const repo = await toolkit.github.repoData();
52-
const meta = new Meta({...getInputs(), ...inputs}, new Context(), repo);
61+
const context = await getFreshWorkflowContext();
62+
const meta = new Meta({...getInputs(), ...inputs}, context, repo);
5363

5464
const version = meta.version;
5565
expect(version).toEqual(exVersion);
@@ -2766,7 +2776,8 @@ describe('pr-head-sha', () => {
27662776

27672777
const toolkit = new Toolkit();
27682778
const repo = await toolkit.github.repoData();
2769-
const meta = new Meta({...getInputs(), ...inputs}, new Context(), repo);
2779+
const context = await getFreshWorkflowContext();
2780+
const meta = new Meta({...getInputs(), ...inputs}, context, repo);
27702781

27712782
const version = meta.version;
27722783
expect(version).toEqual(exVersion);
@@ -3708,7 +3719,8 @@ describe('json', () => {
37083719

37093720
const toolkit = new Toolkit();
37103721
const repo = await toolkit.github.repoData();
3711-
const meta = new Meta({...getInputs(), ...inputs}, new Context(), repo);
3722+
const context = await getFreshWorkflowContext();
3723+
const meta = new Meta({...getInputs(), ...inputs}, context, repo);
37123724

37133725
const jsonOutput = meta.getJSON();
37143726
expect(jsonOutput).toEqual(exJSON);
@@ -4014,7 +4026,8 @@ describe('bake', () => {
40144026

40154027
const toolkit = new Toolkit();
40164028
const repo = await toolkit.github.repoData();
4017-
const meta = new Meta({...getInputs(), ...inputs}, new Context(), repo);
4029+
const context = await getFreshWorkflowContext();
4030+
const meta = new Meta({...getInputs(), ...inputs}, context, repo);
40184031

40194032
const bakeFile = meta.getBakeFile();
40204033
expect(JSON.parse(fs.readFileSync(bakeFile, 'utf8'))).toEqual(exBakeDefinition);
@@ -4056,11 +4069,13 @@ describe('sepTags', () => {
40564069
"user/app:dev,user/app:my,user/app:custom,user/app:tags"
40574070
]
40584071
])('given %p with %p event', async (name: string, envFile: string, inputs: Inputs, expTags: string) => {
4072+
40594073
process.env = dotenv.parse(fs.readFileSync(path.join(__dirname, 'fixtures', envFile)));
4060-
4074+
40614075
const toolkit = new Toolkit();
40624076
const repo = await toolkit.github.repoData();
4063-
const meta = new Meta({...getInputs(), ...inputs}, new Context(), repo);
4077+
const context = await getFreshWorkflowContext();
4078+
const meta = new Meta({...getInputs(), ...inputs}, context, repo);
40644079

40654080
expect(meta.getTags().join(inputs.sepTags)).toEqual(expTags);
40664081
});

action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ branding:
77
color: 'blue'
88

99
inputs:
10+
context:
11+
description: 'Where to get context data. Allowed options are "workflow" (default), "git".'
12+
default: "workflow"
13+
required: true
1014
images:
1115
description: 'List of Docker images to use as base name for tags'
1216
required: true

dist/index.js

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/context.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import * as core from '@actions/core';
2+
import {Context} from '@actions/github/lib/context';
23
import {Util} from '@docker/actions-toolkit/lib/util';
4+
import {Git} from '@docker/actions-toolkit/lib/git';
5+
import {GitHub} from '@docker/actions-toolkit/lib/github';
36

47
export interface Inputs {
8+
context: ContextSource;
59
images: string[];
610
tags: string[];
711
flavor: string[];
@@ -14,6 +18,7 @@ export interface Inputs {
1418

1519
export function getInputs(): Inputs {
1620
return {
21+
context: (core.getInput('context') || ContextSource.workflow) as ContextSource,
1722
images: Util.getInputList('images', {ignoreComma: true}),
1823
tags: Util.getInputList('tags', {ignoreComma: true}),
1924
flavor: Util.getInputList('flavor', {ignoreComma: true}),
@@ -24,3 +29,45 @@ export function getInputs(): Inputs {
2429
githubToken: core.getInput('github-token')
2530
};
2631
}
32+
33+
export enum ContextSource {
34+
workflow = 'workflow',
35+
git = 'git'
36+
}
37+
38+
export async function getContext(source: ContextSource): Promise<Context> {
39+
switch (source) {
40+
case ContextSource.workflow:
41+
return getContextFromWorkflow();
42+
case ContextSource.git:
43+
return await getContextFromGit();
44+
default:
45+
throw new Error(`Invalid context source: ${source}`);
46+
}
47+
}
48+
49+
function getContextFromWorkflow(): Context {
50+
const context = GitHub.context;
51+
52+
// Needs to override Git reference with pr ref instead of upstream branch ref
53+
// for pull_request_target event
54+
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
55+
if (/pull_request_target/.test(context.eventName)) {
56+
context.ref = `refs/pull/${context.payload.number}/merge`;
57+
}
58+
59+
// DOCKER_METADATA_PR_HEAD_SHA env var can be used to set associated head
60+
// SHA instead of commit SHA that triggered the workflow on pull request
61+
// event.
62+
if (/true/i.test(process.env.DOCKER_METADATA_PR_HEAD_SHA || '')) {
63+
if ((/pull_request/.test(context.eventName) || /pull_request_target/.test(context.eventName)) && context.payload?.pull_request?.head?.sha != undefined) {
64+
context.sha = context.payload.pull_request.head.sha;
65+
}
66+
}
67+
68+
return context;
69+
}
70+
71+
async function getContextFromGit(): Promise<Context> {
72+
return await Git.context();
73+
}

src/main.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import * as fs from 'fs';
22
import * as core from '@actions/core';
33
import * as actionsToolkit from '@docker/actions-toolkit';
4-
import {Context} from '@actions/github/lib/context';
5-
import {GitHub} from '@docker/actions-toolkit/lib/github';
64
import {Toolkit} from '@docker/actions-toolkit/lib/toolkit';
75

8-
import {getInputs, Inputs} from './context';
6+
import {getContext, getInputs, Inputs} from './context';
97
import {Meta, Version} from './meta';
108

119
function setOutput(name: string, value: string) {
@@ -16,13 +14,13 @@ function setOutput(name: string, value: string) {
1614
actionsToolkit.run(
1715
// main
1816
async () => {
19-
const inputs: Inputs = await getInputs();
17+
const inputs: Inputs = getInputs();
2018
if (inputs.images.length == 0) {
2119
throw new Error(`images input required`);
2220
}
2321

2422
const toolkit = new Toolkit({githubToken: inputs.githubToken});
25-
const context: Context = GitHub.context;
23+
const context = await getContext(inputs.context);
2624
const repo = await toolkit.github.repoData();
2725

2826
await core.group(`Context info`, async () => {

0 commit comments

Comments
 (0)