Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 30 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,18 +285,18 @@ The following inputs can be used as `step.with` keys:
> org.opencontainers.image.vendor=MyCompany
> ```

| Name | Type | Description |
|-------------------|--------|------------------------------------------------------------------------------|
| `context` | String | Where to get context data. Allowed options are: `workflow` (default), `git`. |
| `images` | List | List of Docker images to use as base name for tags |
| `tags` | List | List of [tags](#tags-input) as key-value pair attributes |
| `flavor` | List | [Flavor](#flavor-input) to apply |
| `labels` | List | List of custom labels |
| `annotations` | List | List of custom annotations |
| `sep-tags` | String | Separator to use for tags output (default `\n`) |
| `sep-labels` | String | Separator to use for labels output (default `\n`) |
| `sep-annotations` | String | Separator to use for annotations output (default `\n`) |
| `bake-target` | String | Bake target name (default `docker-metadata-action`) |
| Name | Type | Description |
|-------------------|--------|--------------------------------------------------------------------------------------------|
| `context` | String | Where to get context data. Allowed options are: `workflow` (default), `git`, `git:<path>`. |
| `images` | List | List of Docker images to use as base name for tags |
| `tags` | List | List of [tags](#tags-input) as key-value pair attributes |
| `flavor` | List | [Flavor](#flavor-input) to apply |
| `labels` | List | List of custom labels |
| `annotations` | List | List of custom annotations |
| `sep-tags` | String | Separator to use for tags output (default `\n`) |
| `sep-labels` | String | Separator to use for labels output (default `\n`) |
| `sep-annotations` | String | Separator to use for annotations output (default `\n`) |
| `bake-target` | String | Bake target name (default `docker-metadata-action`) |

### outputs

Expand Down Expand Up @@ -357,6 +357,24 @@ context: git
* `workflow`: Get context metadata from the workflow (GitHub context). See https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
* `git`: Get context metadata from the workflow and overrides some of them with current Git context, such as `ref` and `sha`.

Use `context: git:<path>` when the repository is checked out to a custom path.
The path can be absolute or relative to the current working directory
(normally `$GITHUB_WORKSPACE`):

```yaml
- uses: actions/checkout@v7
with:
path: source
- uses: docker/metadata-action@v6
with:
context: git:source
images: name/app
tags: type=sha
```

The selected checkout supplies the Git ref, SHA, and commit date. Other repository
metadata still comes from the workflow repository.

## `images` input

`images` defines a list of Docker images to use as base name for [`tags`](#tags-input):
Expand Down
72 changes: 68 additions & 4 deletions __tests__/context.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import {beforeEach, describe, expect, test, it, vi} from 'vitest';
import {afterEach, beforeEach, describe, expect, test, it, vi} from 'vitest';
import {execFileSync} from 'child_process';
import fs from 'fs';
import path from 'path';

import {Git} from '@docker/actions-toolkit/lib/git.js';
import {Toolkit} from '@docker/actions-toolkit/lib/toolkit.js';

import * as context from '../src/context.js';
import {Meta} from '../src/meta.js';
import repoFixture from './fixtures/repo.json' with {type: 'json'};

const toolkit = new Toolkit({githubToken: 'fake-github-token'});

Expand All @@ -16,6 +22,11 @@ describe('getInputs', () => {
}, {});
});

it('reads a Git context with a path', () => {
setInput('context', 'git:nested checkout');
expect(context.getInputs().context).toEqual('git:nested checkout');
});

// prettier-ignore
const cases: [number, Map<string, string>, context.Inputs][] = [
[
Expand Down Expand Up @@ -151,13 +162,26 @@ describe('getInputs', () => {
});

describe('getContext', () => {
it('workflow', async () => {
afterEach(() => {
vi.restoreAllMocks();
});

it('workflow does not read Git context', async () => {
const gitContext = vi.spyOn(Git, 'context');
const gitCommitDate = vi.spyOn(Git, 'commitDate');
const ctx = await context.getContext(context.ContextSource.workflow, toolkit);
expect(ctx.ref).toEqual('refs/heads/dev');
expect(ctx.sha).toEqual('5f3331d7f7044c18ca9f12c77d961c4d7cf3276a');
expect(ctx.commitDate).toEqual(new Date('2024-11-13T13:42:28.000Z'));
expect(gitContext).not.toHaveBeenCalled();
expect(gitCommitDate).not.toHaveBeenCalled();
});
it('git', async () => {
it.each<[string, string | undefined]>([
['git', undefined],
['git:', ''],
['git:source', 'source'],
['git:C:\\nested checkout', 'C:\\nested checkout']
])('reads %s', async (source, workdir) => {
vi.spyOn(Git, 'context').mockImplementation((): Promise<context.Context> => {
return Promise.resolve({
ref: 'refs/heads/git-test',
Expand All @@ -167,11 +191,51 @@ describe('getContext', () => {
vi.spyOn(Git, 'commitDate').mockImplementation(async (): Promise<Date> => {
return new Date('2023-01-01T13:42:28.000Z');
});
const ctx = await context.getContext(context.ContextSource.git, toolkit);
const ctx = await context.getContext(source, toolkit);
expect(Git.context).toHaveBeenCalledWith(workdir);
expect(Git.commitDate).toHaveBeenCalledWith('git-test-sha', workdir);
expect(ctx.ref).toEqual('refs/heads/git-test');
expect(ctx.sha).toEqual('git-test-sha');
expect(ctx.commitDate).toEqual(new Date('2023-01-01T13:42:28.000Z'));
});

it.each(['workflow:source', 'gitfoo:source', 'invalid'])('rejects invalid context %s', async source => {
await expect(context.getContext(source, toolkit)).rejects.toThrow(`Invalid context source: ${source}`);
});

it.each(['relative', 'absolute'])('reads a selected checkout using a %s path', async pathType => {
const checkoutDir = fs.mkdtempSync(path.join(process.cwd(), 'git context-'));
const workdir = pathType === 'relative' ? path.relative(process.cwd(), checkoutDir) : checkoutDir;
const commitDate = '2024-01-02T03:04:05Z';
const git = (args: string[]) =>
execFileSync('git', args, {
cwd: checkoutDir,
encoding: 'utf8',
stdio: 'pipe',
env: {...process.env, GIT_AUTHOR_DATE: commitDate, GIT_COMMITTER_DATE: commitDate}
}).trim();

try {
git(['init', '--initial-branch=selected-checkout']);
git(['-c', 'user.name=Test', '-c', 'user.email=test@example.com', '-c', 'commit.gpgsign=false', 'commit', '--allow-empty', '-m', 'initial']);
const sha = git(['rev-parse', 'HEAD']);
const ctx = await context.getContext(`git:${workdir}`, toolkit);
expect(ctx.ref).toEqual('refs/heads/selected-checkout');
expect(ctx.sha).toEqual(sha);
expect(ctx.commitDate).toEqual(new Date(commitDate));

git(['checkout', '--detach', 'HEAD']);
git(['branch', '-D', 'selected-checkout']);
const detachedContext = await context.getContext(`git:${workdir}`, toolkit);
expect(detachedContext.ref).toEqual('');
expect(detachedContext.sha).toEqual(sha);
expect(detachedContext.commitDate).toEqual(new Date(commitDate));
const meta = new Meta({...context.getInputs(), images: ['name/app'], tags: ['type=sha,format=long'], flavor: []}, detachedContext, repoFixture);
expect(meta.getTags()).toEqual([`name/app:sha-${sha}`]);
} finally {
fs.rmSync(checkoutDir, {recursive: true, force: true});
}
});
});

// See: https://github.com/actions/toolkit/blob/master/packages/core/src/core.ts#L67
Expand Down
12 changes: 12 additions & 0 deletions __tests__/fixtures/repo.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,14 @@
"homepage": "https://github.com",
"language": null,
"forks_count": 9,
"forks": 9,
"stargazers_count": 80,
"watchers_count": 80,
"watchers": 80,
"size": 108,
"default_branch": "master",
"open_issues_count": 0,
"open_issues": 0,
"is_template": true,
"topics": [
"octocat",
Expand All @@ -85,6 +88,7 @@
"api"
],
"has_issues": true,
"has_discussions": false,
"has_projects": true,
"has_wiki": true,
"has_pages": false,
Expand Down Expand Up @@ -211,11 +215,15 @@
"homepage": "https://github.com",
"language": null,
"forks_count": 9,
"forks": 9,
"license": null,
"stargazers_count": 80,
"watchers_count": 80,
"watchers": 80,
"size": 108,
"default_branch": "master",
"open_issues_count": 0,
"open_issues": 0,
"is_template": true,
"topics": [
"octocat",
Expand Down Expand Up @@ -322,11 +330,15 @@
"homepage": "https://github.com",
"language": null,
"forks_count": 9,
"forks": 9,
"license": null,
"stargazers_count": 80,
"watchers_count": 80,
"watchers": 80,
"size": 108,
"default_branch": "master",
"open_issues_count": 0,
"open_issues": 0,
"is_template": true,
"topics": [
"octocat",
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ branding:

inputs:
context:
description: 'Where to get context data. Allowed options are "workflow" (default), "git".'
description: 'Where to get context data. Allowed options are "workflow" (default), "git", "git:<path>".'
default: "workflow"
required: true
images:
Expand Down
150 changes: 75 additions & 75 deletions dist/index.cjs

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/index.cjs.map

Large diffs are not rendered by default.

15 changes: 9 additions & 6 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface Context extends GithubContext {
}

export interface Inputs {
context: ContextSource;
context: string;
images: string[];
tags: string[];
flavor: string[];
Expand All @@ -27,7 +27,7 @@ export interface Inputs {

export function getInputs(): Inputs {
return {
context: (core.getInput('context') || ContextSource.workflow) as ContextSource,
context: core.getInput('context') || ContextSource.workflow,
images: Util.getInputList('images', {ignoreComma: true, comment: '#', commentNoInfix: true}),
tags: Util.getInputList('tags', {ignoreComma: true, comment: '#', commentNoInfix: true}),
flavor: Util.getInputList('flavor', {ignoreComma: true, comment: '#', commentNoInfix: true}),
Expand All @@ -46,7 +46,10 @@ export enum ContextSource {
git = 'git'
}

export async function getContext(source: ContextSource, toolkit: Toolkit): Promise<Context> {
export async function getContext(source: string, toolkit: Toolkit): Promise<Context> {
if (source.startsWith(`${ContextSource.git}:`)) {
return await getContextFromGit(source.slice(ContextSource.git.length + 1));
}
switch (source) {
case ContextSource.workflow:
return await getContextFromWorkflow(toolkit);
Expand Down Expand Up @@ -82,11 +85,11 @@ async function getContextFromWorkflow(toolkit: Toolkit): Promise<Context> {
} as Context;
}

async function getContextFromGit(): Promise<Context> {
const ctx = await Git.context();
async function getContextFromGit(workdir?: string): Promise<Context> {
const ctx = await Git.context(workdir);

return {
commitDate: await Git.commitDate(ctx.sha),
commitDate: await Git.commitDate(ctx.sha, workdir),
...ctx
} as Context;
}
Expand Down