global enhancements

- create context object and remove github one
- more tests and improve mocks

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax 2023-01-30 00:08:45 +01:00
parent b03f6a405c
commit c857b8425c
No known key found for this signature in database
GPG Key ID: 3248E46B6BB8C7F7
19 changed files with 536 additions and 459 deletions

View File

@ -0,0 +1,207 @@
import {jest} from '@jest/globals';
export const context = {
repo: {
owner: 'docker',
repo: 'actions-toolkit'
},
ref: 'refs/heads/master',
runId: 123,
payload: {
after: '860c1904a1ce19322e91ac35af1ab07466440c37',
base_ref: null,
before: '5f3331d7f7044c18ca9f12c77d961c4d7cf3276a',
commits: [
{
author: {
email: 'crazy-max@users.noreply.github.com',
name: 'CrazyMax',
username: 'crazy-max'
},
committer: {
email: 'crazy-max@users.noreply.github.com',
name: 'CrazyMax',
username: 'crazy-max'
},
distinct: true,
id: '860c1904a1ce19322e91ac35af1ab07466440c37',
message: 'hello dev',
timestamp: '2022-04-19T11:27:24+02:00',
tree_id: 'd2c60af597e863787d2d27f569e30495b0b92820',
url: 'https://github.com/docker/test-docker-action/commit/860c1904a1ce19322e91ac35af1ab07466440c37'
}
],
compare: 'https://github.com/docker/test-docker-action/compare/5f3331d7f704...860c1904a1ce',
created: false,
deleted: false,
forced: false,
head_commit: {
author: {
email: 'crazy-max@users.noreply.github.com',
name: 'CrazyMax',
username: 'crazy-max'
},
committer: {
email: 'crazy-max@users.noreply.github.com',
name: 'CrazyMax',
username: 'crazy-max'
},
distinct: true,
id: '860c1904a1ce19322e91ac35af1ab07466440c37',
message: 'hello dev',
timestamp: '2022-04-19T11:27:24+02:00',
tree_id: 'd2c60af597e863787d2d27f569e30495b0b92820',
url: 'https://github.com/docker/test-docker-action/commit/860c1904a1ce19322e91ac35af1ab07466440c37'
},
organization: {
avatar_url: 'https://avatars.githubusercontent.com/u/5429470?v=4',
description: 'Docker helps developers bring their ideas to life by conquering the complexity of app development.',
events_url: 'https://api.github.com/orgs/docker/events',
hooks_url: 'https://api.github.com/orgs/docker/hooks',
id: 5429470,
issues_url: 'https://api.github.com/orgs/docker/issues',
login: 'docker',
members_url: 'https://api.github.com/orgs/docker/members{/member}',
node_id: 'MDEyOk9yZ2FuaXphdGlvbjU0Mjk0NzA=',
public_members_url: 'https://api.github.com/orgs/docker/public_members{/member}',
repos_url: 'https://api.github.com/orgs/docker/repos',
url: 'https://api.github.com/orgs/docker'
},
pusher: {
email: 'github@crazymax.dev',
name: 'crazy-max'
},
ref: 'refs/heads/dev',
repository: {
allow_forking: true,
archive_url: 'https://api.github.com/repos/docker/test-docker-action/{archive_format}{/ref}',
archived: false,
assignees_url: 'https://api.github.com/repos/docker/test-docker-action/assignees{/user}',
blobs_url: 'https://api.github.com/repos/docker/test-docker-action/git/blobs{/sha}',
branches_url: 'https://api.github.com/repos/docker/test-docker-action/branches{/branch}',
clone_url: 'https://github.com/docker/test-docker-action.git',
collaborators_url: 'https://api.github.com/repos/docker/test-docker-action/collaborators{/collaborator}',
comments_url: 'https://api.github.com/repos/docker/test-docker-action/comments{/number}',
commits_url: 'https://api.github.com/repos/docker/test-docker-action/commits{/sha}',
compare_url: 'https://api.github.com/repos/docker/test-docker-action/compare/{base}...{head}',
contents_url: 'https://api.github.com/repos/docker/test-docker-action/contents/{+path}',
contributors_url: 'https://api.github.com/repos/docker/test-docker-action/contributors',
created_at: 1596792180,
default_branch: 'master',
deployments_url: 'https://api.github.com/repos/docker/test-docker-action/deployments',
description: 'Test "Docker" Actions',
disabled: false,
downloads_url: 'https://api.github.com/repos/docker/test-docker-action/downloads',
events_url: 'https://api.github.com/repos/docker/test-docker-action/events',
fork: false,
forks: 1,
forks_count: 1,
forks_url: 'https://api.github.com/repos/docker/test-docker-action/forks',
full_name: 'docker/test-docker-action',
git_commits_url: 'https://api.github.com/repos/docker/test-docker-action/git/commits{/sha}',
git_refs_url: 'https://api.github.com/repos/docker/test-docker-action/git/refs{/sha}',
git_tags_url: 'https://api.github.com/repos/docker/test-docker-action/git/tags{/sha}',
git_url: 'git://github.com/docker/test-docker-action.git',
has_downloads: true,
has_issues: true,
has_pages: false,
has_projects: true,
has_wiki: true,
homepage: '',
hooks_url: 'https://api.github.com/repos/docker/test-docker-action/hooks',
html_url: 'https://github.com/docker/test-docker-action',
id: 285789493,
is_template: false,
issue_comment_url: 'https://api.github.com/repos/docker/test-docker-action/issues/comments{/number}',
issue_events_url: 'https://api.github.com/repos/docker/test-docker-action/issues/events{/number}',
issues_url: 'https://api.github.com/repos/docker/test-docker-action/issues{/number}',
keys_url: 'https://api.github.com/repos/docker/test-docker-action/keys{/key_id}',
labels_url: 'https://api.github.com/repos/docker/test-docker-action/labels{/name}',
language: 'JavaScript',
languages_url: 'https://api.github.com/repos/docker/test-docker-action/languages',
license: {
key: 'mit',
name: 'MIT License',
node_id: 'MDc6TGljZW5zZTEz',
spdx_id: 'MIT',
url: 'https://api.github.com/licenses/mit'
},
master_branch: 'master',
merges_url: 'https://api.github.com/repos/docker/test-docker-action/merges',
milestones_url: 'https://api.github.com/repos/docker/test-docker-action/milestones{/number}',
mirror_url: null,
name: 'test-docker-action',
node_id: 'MDEwOlJlcG9zaXRvcnkyODU3ODk0OTM=',
notifications_url: 'https://api.github.com/repos/docker/test-docker-action/notifications{?since,all,participating}',
open_issues: 6,
open_issues_count: 6,
organization: 'docker',
owner: {
avatar_url: 'https://avatars.githubusercontent.com/u/5429470?v=4',
email: 'info@docker.com',
events_url: 'https://api.github.com/users/docker/events{/privacy}',
followers_url: 'https://api.github.com/users/docker/followers',
following_url: 'https://api.github.com/users/docker/following{/other_user}',
gists_url: 'https://api.github.com/users/docker/gists{/gist_id}',
gravatar_id: '',
html_url: 'https://github.com/docker',
id: 5429470,
login: 'docker',
name: 'docker',
node_id: 'MDEyOk9yZ2FuaXphdGlvbjU0Mjk0NzA=',
organizations_url: 'https://api.github.com/users/docker/orgs',
received_events_url: 'https://api.github.com/users/docker/received_events',
repos_url: 'https://api.github.com/users/docker/repos',
site_admin: false,
starred_url: 'https://api.github.com/users/docker/starred{/owner}{/repo}',
subscriptions_url: 'https://api.github.com/users/docker/subscriptions',
type: 'Organization',
url: 'https://api.github.com/users/docker'
},
private: true,
pulls_url: 'https://api.github.com/repos/docker/test-docker-action/pulls{/number}',
pushed_at: 1650360446,
releases_url: 'https://api.github.com/repos/docker/test-docker-action/releases{/id}',
size: 796,
ssh_url: 'git@github.com:docker/test-docker-action.git',
stargazers: 0,
stargazers_count: 0,
stargazers_url: 'https://api.github.com/repos/docker/test-docker-action/stargazers',
statuses_url: 'https://api.github.com/repos/docker/test-docker-action/statuses/{sha}',
subscribers_url: 'https://api.github.com/repos/docker/test-docker-action/subscribers',
subscription_url: 'https://api.github.com/repos/docker/test-docker-action/subscription',
svn_url: 'https://github.com/docker/test-docker-action',
tags_url: 'https://api.github.com/repos/docker/test-docker-action/tags',
teams_url: 'https://api.github.com/repos/docker/test-docker-action/teams',
topics: [],
trees_url: 'https://api.github.com/repos/docker/test-docker-action/git/trees{/sha}',
updated_at: '2022-04-19T09:05:09Z',
url: 'https://github.com/docker/test-docker-action',
visibility: 'private',
watchers: 0,
watchers_count: 0
},
sender: {
avatar_url: 'https://avatars.githubusercontent.com/u/1951866?v=4',
events_url: 'https://api.github.com/users/crazy-max/events{/privacy}',
followers_url: 'https://api.github.com/users/crazy-max/followers',
following_url: 'https://api.github.com/users/crazy-max/following{/other_user}',
gists_url: 'https://api.github.com/users/crazy-max/gists{/gist_id}',
gravatar_id: '',
html_url: 'https://github.com/crazy-max',
id: 1951866,
login: 'crazy-max',
node_id: 'MDQ6VXNlcjE5NTE4NjY=',
organizations_url: 'https://api.github.com/users/crazy-max/orgs',
received_events_url: 'https://api.github.com/users/crazy-max/received_events',
repos_url: 'https://api.github.com/users/crazy-max/repos',
site_admin: false,
starred_url: 'https://api.github.com/users/crazy-max/starred{/owner}{/repo}',
subscriptions_url: 'https://api.github.com/users/crazy-max/subscriptions',
type: 'User',
url: 'https://api.github.com/users/crazy-max'
}
}
};
export const getOctokit = jest.fn();

View File

@ -1,15 +1,37 @@
import {jest, describe, expect, it, test, beforeEach} from '@jest/globals';
import {afterEach, beforeEach, describe, expect, it, jest, test} from '@jest/globals';
import * as fs from 'fs';
import * as path from 'path';
import {Builder} from '../src/builder';
import {Builder, BuilderInfo} from '../src/builder';
import {Context} from '../src/context';
beforeEach(() => {
jest.clearAllMocks();
});
jest.spyOn(Builder.prototype, 'inspect').mockImplementation(async (): Promise<BuilderInfo> => {
return {
name: 'builder2',
driver: 'docker-container',
lastActivity: new Date('2023-01-16 09:45:23 +0000 UTC'),
nodes: [
{
buildkitVersion: 'v0.11.0',
buildkitdFlags: '--debug --allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host',
driverOpts: ['BUILDKIT_STEP_LOG_MAX_SIZE=10485760', 'BUILDKIT_STEP_LOG_MAX_SPEED=10485760', 'JAEGER_TRACE=localhost:6831', 'image=moby/buildkit:latest', 'network=host'],
endpoint: 'unix:///var/run/docker.sock',
name: 'builder20',
platforms: 'linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6',
status: 'running'
}
]
};
});
describe('inspect', () => {
it('valid', async () => {
const builder = new Builder();
const builder = new Builder({
context: new Context()
});
const builderInfo = await builder.inspect('');
expect(builderInfo).not.toBeUndefined();
expect(builderInfo.name).not.toEqual('');

View File

@ -1,23 +1,23 @@
import {afterEach, beforeEach, describe, expect, it, jest, test} from '@jest/globals';
import {describe, expect, it, jest, test, beforeEach, afterEach} from '@jest/globals';
import * as fs from 'fs';
import * as path from 'path';
import * as semver from 'semver';
import rimraf from 'rimraf';
import * as semver from 'semver';
import {BuildKit} from '../src/buildkit';
import {Builder, BuilderInfo} from '../src/builder';
import {Context} from '../src/context';
const tmpDir = path.join('/tmp/.docker-actions-toolkit-jest').split(path.sep).join(path.posix.sep);
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep);
jest.spyOn(BuildKit.prototype as any, 'tmpDir').mockImplementation((): string => {
jest.spyOn(Context.prototype as any, 'tmpDir').mockImplementation((): string => {
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true});
}
return tmpDir;
});
jest.spyOn(BuildKit.prototype as any, 'tmpName').mockImplementation((): string => {
jest.spyOn(Context.prototype as any, 'tmpName').mockImplementation((): string => {
return tmpName;
});
@ -50,7 +50,9 @@ jest.spyOn(Builder.prototype, 'inspect').mockImplementation(async (): Promise<Bu
describe('getVersion', () => {
it('valid', async () => {
const buildkit = new BuildKit();
const buildkit = new BuildKit({
context: new Context()
});
const version = await buildkit.getVersion('builder2');
expect(semver.valid(version)).not.toBeNull();
});
@ -61,7 +63,9 @@ describe('satisfies', () => {
['builder2', '>=0.10.0', true],
['builder2', '>0.11.0', false]
])('given %p', async (builderName, range, expected) => {
const buildkit = new BuildKit();
const buildkit = new BuildKit({
context: new Context()
});
expect(await buildkit.versionSatisfies(builderName, range)).toBe(expected);
});
});
@ -81,7 +85,9 @@ describe('generateConfig', () => {
]
])('given %p config', async (val, file, exValue, error: Error) => {
try {
const buildkit = new BuildKit();
const buildkit = new BuildKit({
context: new Context()
});
let config: string;
if (file) {
config = buildkit.generateConfigFile(val);

View File

@ -1,11 +1,12 @@
import {afterEach, beforeEach, describe, expect, it, jest, test} from '@jest/globals';
import {describe, expect, it, jest, test, beforeEach, afterEach} from '@jest/globals';
import * as fs from 'fs';
import * as path from 'path';
import rimraf from 'rimraf';
import * as semver from 'semver';
import * as exec from '@actions/exec';
import rimraf from 'rimraf';
import {Buildx} from '../src/buildx';
import {Context} from '../src/context';
const tmpDir = path.join('/tmp/.docker-actions-toolkit-jest').split(path.sep).join(path.posix.sep);
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep);
@ -14,12 +15,15 @@ const metadata = `{
"containerimage.digest": "sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c"
}`;
jest.spyOn(Buildx.prototype as any, 'tmpDir').mockImplementation((): string => {
jest.spyOn(Context.prototype as any, 'tmpDir').mockImplementation((): string => {
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true});
}
return tmpDir;
});
jest.spyOn(Context.prototype as any, 'tmpName').mockImplementation((): string => {
return tmpName;
});
beforeEach(() => {
jest.clearAllMocks();
@ -29,13 +33,11 @@ afterEach(() => {
rimraf.sync(tmpDir);
});
jest.spyOn(Buildx.prototype as any, 'tmpName').mockImplementation((): string => {
return tmpName;
});
describe('getBuildImageID', () => {
it('matches', async () => {
const buildx = new Buildx();
const buildx = new Buildx({
context: new Context()
});
const imageID = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9';
const imageIDFile = buildx.getBuildImageIDFilePath();
await fs.writeFileSync(imageIDFile, imageID);
@ -46,7 +48,9 @@ describe('getBuildImageID', () => {
describe('getBuildMetadata', () => {
it('matches', async () => {
const buildx = new Buildx();
const buildx = new Buildx({
context: new Context()
});
const metadataFile = buildx.getBuildMetadataFilePath();
await fs.writeFileSync(metadataFile, metadata);
const expected = buildx.getBuildMetadata();
@ -56,7 +60,9 @@ describe('getBuildMetadata', () => {
describe('getDigest', () => {
it('matches', async () => {
const buildx = new Buildx();
const buildx = new Buildx({
context: new Context()
});
const metadataFile = buildx.getBuildMetadataFilePath();
await fs.writeFileSync(metadataFile, metadata);
const expected = buildx.getDigest();
@ -116,6 +122,7 @@ describe('isAvailable', () => {
it('docker cli', async () => {
const execSpy = jest.spyOn(exec, 'getExecOutput');
const buildx = new Buildx({
context: new Context(),
standalone: false
});
buildx.isAvailable().catch(() => {
@ -130,6 +137,7 @@ describe('isAvailable', () => {
it('standalone', async () => {
const execSpy = jest.spyOn(exec, 'getExecOutput');
const buildx = new Buildx({
context: new Context(),
standalone: true
});
buildx.isAvailable().catch(() => {
@ -147,6 +155,7 @@ describe('printVersion', () => {
it('docker cli', () => {
const execSpy = jest.spyOn(exec, 'exec');
const buildx = new Buildx({
context: new Context(),
standalone: false
});
buildx.printVersion();
@ -157,6 +166,7 @@ describe('printVersion', () => {
it('standalone', () => {
const execSpy = jest.spyOn(exec, 'exec');
const buildx = new Buildx({
context: new Context(),
standalone: true
});
buildx.printVersion();
@ -168,8 +178,10 @@ describe('printVersion', () => {
describe('getVersion', () => {
it('valid', async () => {
const buildx = new Buildx();
expect(semver.valid(await buildx.version())).not.toBeNull();
const buildx = new Buildx({
context: new Context()
});
expect(semver.valid(await buildx.version)).not.toBeNull();
});
});
@ -190,7 +202,9 @@ describe('versionSatisfies', () => {
['bda4882a65349ca359216b135896bddc1d92461c', '>0.1.0', false],
['f117971', '>0.6.0', true]
])('given %p', async (version, range, expected) => {
const buildx = new Buildx();
const buildx = new Buildx({
context: new Context()
});
expect(await buildx.versionSatisfies(range, version)).toBe(expected);
});
});
@ -207,7 +221,9 @@ describe('generateBuildSecret', () => {
[`notfound=secret`, true, '', '', new Error('secret file secret not found')]
])('given %p key and %p secret', async (kvp: string, file: boolean, exKey: string, exValue: string, error: Error) => {
try {
const buildx = new Buildx();
const buildx = new Buildx({
context: new Context()
});
let secret: string;
if (file) {
secret = buildx.generateBuildSecretFile(kvp);

87
__tests__/context.test.ts Normal file
View File

@ -0,0 +1,87 @@
import fs from 'fs';
import path from 'path';
import rimraf from 'rimraf';
import {describe, expect, jest, it, beforeEach, afterEach} from '@jest/globals';
import {Context, ReposGetResponseData} from '../src/context';
const tmpDir = path.join('/tmp/.docker-actions-toolkit-jest').split(path.sep).join(path.posix.sep);
const tmpName = path.join(tmpDir, '.tmpname-jest').split(path.sep).join(path.posix.sep);
beforeEach(() => {
jest.clearAllMocks();
});
jest.spyOn(Context.prototype as any, 'tmpDir').mockImplementation((): string => {
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true});
}
return tmpDir;
});
jest.spyOn(Context.prototype as any, 'tmpName').mockImplementation((): string => {
return tmpName;
});
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
rimraf.sync(tmpDir);
});
import * as repoFixture from './fixtures/repo.json';
jest.spyOn(Context.prototype as any, 'repoData').mockImplementation((): Promise<ReposGetResponseData> => {
return <Promise<ReposGetResponseData>>(repoFixture as unknown);
});
describe('serverURL', () => {
const originalEnv = process.env;
beforeEach(() => {
jest.resetModules();
process.env = {
...originalEnv,
GITHUB_SERVER_URL: 'https://foo.github.com'
};
});
afterEach(() => {
process.env = originalEnv;
});
it('returns default', async () => {
process.env.GITHUB_SERVER_URL = '';
const context = new Context();
expect(context.serverURL).toEqual('https://github.com');
});
it('returns from env', async () => {
const context = new Context();
expect(context.serverURL).toEqual('https://foo.github.com');
});
});
describe('gitContext', () => {
it('returns refs/heads/master', async () => {
const context = new Context();
expect(context.buildGitContext).toEqual('https://github.com/docker/actions-toolkit.git#refs/heads/master');
});
});
describe('provenanceBuilderID', () => {
it('returns 123', async () => {
const context = new Context();
expect(context.provenanceBuilderID).toEqual('https://github.com/docker/actions-toolkit/actions/runs/123');
});
});
describe('repo', () => {
it('returns GitHub repository', async () => {
const context = new Context();
expect((await context.repoData()).name).toEqual('Hello-World');
});
});
describe('fromPayload', () => {
it('returns repository name from payload', async () => {
const context = new Context();
expect(await context.fromPayload('repository.name')).toEqual('test-docker-action');
});
});

View File

@ -1,195 +0,0 @@
{
"after": "860c1904a1ce19322e91ac35af1ab07466440c37",
"base_ref": null,
"before": "5f3331d7f7044c18ca9f12c77d961c4d7cf3276a",
"commits": [
{
"author": {
"email": "crazy-max@users.noreply.github.com",
"name": "CrazyMax",
"username": "crazy-max"
},
"committer": {
"email": "crazy-max@users.noreply.github.com",
"name": "CrazyMax",
"username": "crazy-max"
},
"distinct": true,
"id": "860c1904a1ce19322e91ac35af1ab07466440c37",
"message": "hello dev",
"timestamp": "2022-04-19T11:27:24+02:00",
"tree_id": "d2c60af597e863787d2d27f569e30495b0b92820",
"url": "https://github.com/docker/test-docker-action/commit/860c1904a1ce19322e91ac35af1ab07466440c37"
}
],
"compare": "https://github.com/docker/test-docker-action/compare/5f3331d7f704...860c1904a1ce",
"created": false,
"deleted": false,
"forced": false,
"head_commit": {
"author": {
"email": "crazy-max@users.noreply.github.com",
"name": "CrazyMax",
"username": "crazy-max"
},
"committer": {
"email": "crazy-max@users.noreply.github.com",
"name": "CrazyMax",
"username": "crazy-max"
},
"distinct": true,
"id": "860c1904a1ce19322e91ac35af1ab07466440c37",
"message": "hello dev",
"timestamp": "2022-04-19T11:27:24+02:00",
"tree_id": "d2c60af597e863787d2d27f569e30495b0b92820",
"url": "https://github.com/docker/test-docker-action/commit/860c1904a1ce19322e91ac35af1ab07466440c37"
},
"organization": {
"avatar_url": "https://avatars.githubusercontent.com/u/5429470?v=4",
"description": "Docker helps developers bring their ideas to life by conquering the complexity of app development.",
"events_url": "https://api.github.com/orgs/docker/events",
"hooks_url": "https://api.github.com/orgs/docker/hooks",
"id": 5429470,
"issues_url": "https://api.github.com/orgs/docker/issues",
"login": "docker",
"members_url": "https://api.github.com/orgs/docker/members{/member}",
"node_id": "MDEyOk9yZ2FuaXphdGlvbjU0Mjk0NzA=",
"public_members_url": "https://api.github.com/orgs/docker/public_members{/member}",
"repos_url": "https://api.github.com/orgs/docker/repos",
"url": "https://api.github.com/orgs/docker"
},
"pusher": {
"email": "github@crazymax.dev",
"name": "crazy-max"
},
"ref": "refs/heads/dev",
"repository": {
"allow_forking": true,
"archive_url": "https://api.github.com/repos/docker/test-docker-action/{archive_format}{/ref}",
"archived": false,
"assignees_url": "https://api.github.com/repos/docker/test-docker-action/assignees{/user}",
"blobs_url": "https://api.github.com/repos/docker/test-docker-action/git/blobs{/sha}",
"branches_url": "https://api.github.com/repos/docker/test-docker-action/branches{/branch}",
"clone_url": "https://github.com/docker/test-docker-action.git",
"collaborators_url": "https://api.github.com/repos/docker/test-docker-action/collaborators{/collaborator}",
"comments_url": "https://api.github.com/repos/docker/test-docker-action/comments{/number}",
"commits_url": "https://api.github.com/repos/docker/test-docker-action/commits{/sha}",
"compare_url": "https://api.github.com/repos/docker/test-docker-action/compare/{base}...{head}",
"contents_url": "https://api.github.com/repos/docker/test-docker-action/contents/{+path}",
"contributors_url": "https://api.github.com/repos/docker/test-docker-action/contributors",
"created_at": 1596792180,
"default_branch": "master",
"deployments_url": "https://api.github.com/repos/docker/test-docker-action/deployments",
"description": "Test \"Docker\" Actions",
"disabled": false,
"downloads_url": "https://api.github.com/repos/docker/test-docker-action/downloads",
"events_url": "https://api.github.com/repos/docker/test-docker-action/events",
"fork": false,
"forks": 1,
"forks_count": 1,
"forks_url": "https://api.github.com/repos/docker/test-docker-action/forks",
"full_name": "docker/test-docker-action",
"git_commits_url": "https://api.github.com/repos/docker/test-docker-action/git/commits{/sha}",
"git_refs_url": "https://api.github.com/repos/docker/test-docker-action/git/refs{/sha}",
"git_tags_url": "https://api.github.com/repos/docker/test-docker-action/git/tags{/sha}",
"git_url": "git://github.com/docker/test-docker-action.git",
"has_downloads": true,
"has_issues": true,
"has_pages": false,
"has_projects": true,
"has_wiki": true,
"homepage": "",
"hooks_url": "https://api.github.com/repos/docker/test-docker-action/hooks",
"html_url": "https://github.com/docker/test-docker-action",
"id": 285789493,
"is_template": false,
"issue_comment_url": "https://api.github.com/repos/docker/test-docker-action/issues/comments{/number}",
"issue_events_url": "https://api.github.com/repos/docker/test-docker-action/issues/events{/number}",
"issues_url": "https://api.github.com/repos/docker/test-docker-action/issues{/number}",
"keys_url": "https://api.github.com/repos/docker/test-docker-action/keys{/key_id}",
"labels_url": "https://api.github.com/repos/docker/test-docker-action/labels{/name}",
"language": "JavaScript",
"languages_url": "https://api.github.com/repos/docker/test-docker-action/languages",
"license": {
"key": "mit",
"name": "MIT License",
"node_id": "MDc6TGljZW5zZTEz",
"spdx_id": "MIT",
"url": "https://api.github.com/licenses/mit"
},
"master_branch": "master",
"merges_url": "https://api.github.com/repos/docker/test-docker-action/merges",
"milestones_url": "https://api.github.com/repos/docker/test-docker-action/milestones{/number}",
"mirror_url": null,
"name": "test-docker-action",
"node_id": "MDEwOlJlcG9zaXRvcnkyODU3ODk0OTM=",
"notifications_url": "https://api.github.com/repos/docker/test-docker-action/notifications{?since,all,participating}",
"open_issues": 6,
"open_issues_count": 6,
"organization": "docker",
"owner": {
"avatar_url": "https://avatars.githubusercontent.com/u/5429470?v=4",
"email": "info@docker.com",
"events_url": "https://api.github.com/users/docker/events{/privacy}",
"followers_url": "https://api.github.com/users/docker/followers",
"following_url": "https://api.github.com/users/docker/following{/other_user}",
"gists_url": "https://api.github.com/users/docker/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/docker",
"id": 5429470,
"login": "docker",
"name": "docker",
"node_id": "MDEyOk9yZ2FuaXphdGlvbjU0Mjk0NzA=",
"organizations_url": "https://api.github.com/users/docker/orgs",
"received_events_url": "https://api.github.com/users/docker/received_events",
"repos_url": "https://api.github.com/users/docker/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/docker/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/docker/subscriptions",
"type": "Organization",
"url": "https://api.github.com/users/docker"
},
"private": true,
"pulls_url": "https://api.github.com/repos/docker/test-docker-action/pulls{/number}",
"pushed_at": 1650360446,
"releases_url": "https://api.github.com/repos/docker/test-docker-action/releases{/id}",
"size": 796,
"ssh_url": "git@github.com:docker/test-docker-action.git",
"stargazers": 0,
"stargazers_count": 0,
"stargazers_url": "https://api.github.com/repos/docker/test-docker-action/stargazers",
"statuses_url": "https://api.github.com/repos/docker/test-docker-action/statuses/{sha}",
"subscribers_url": "https://api.github.com/repos/docker/test-docker-action/subscribers",
"subscription_url": "https://api.github.com/repos/docker/test-docker-action/subscription",
"svn_url": "https://github.com/docker/test-docker-action",
"tags_url": "https://api.github.com/repos/docker/test-docker-action/tags",
"teams_url": "https://api.github.com/repos/docker/test-docker-action/teams",
"topics": [],
"trees_url": "https://api.github.com/repos/docker/test-docker-action/git/trees{/sha}",
"updated_at": "2022-04-19T09:05:09Z",
"url": "https://github.com/docker/test-docker-action",
"visibility": "private",
"watchers": 0,
"watchers_count": 0
},
"sender": {
"avatar_url": "https://avatars.githubusercontent.com/u/1951866?v=4",
"events_url": "https://api.github.com/users/crazy-max/events{/privacy}",
"followers_url": "https://api.github.com/users/crazy-max/followers",
"following_url": "https://api.github.com/users/crazy-max/following{/other_user}",
"gists_url": "https://api.github.com/users/crazy-max/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/crazy-max",
"id": 1951866,
"login": "crazy-max",
"node_id": "MDQ6VXNlcjE5NTE4NjY=",
"organizations_url": "https://api.github.com/users/crazy-max/orgs",
"received_events_url": "https://api.github.com/users/crazy-max/received_events",
"repos_url": "https://api.github.com/users/crazy-max/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/crazy-max/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/crazy-max/subscriptions",
"type": "User",
"url": "https://api.github.com/users/crazy-max"
}
}

View File

@ -1,5 +1,6 @@
import {beforeEach, describe, expect, it, jest} from '@jest/globals';
import * as git from '../src/git';
import {Git} from '../src/git';
beforeEach(() => {
jest.clearAllMocks();
@ -8,7 +9,7 @@ beforeEach(() => {
describe('git', () => {
it('returns git remote ref', async () => {
try {
expect(await git.getRemoteSha('https://github.com/docker/buildx.git', 'refs/pull/648/head')).toEqual('f11797113e5a9b86bd976329c5dbb8a8bfdfadfa');
expect(await Git.getRemoteSha('https://github.com/docker/buildx.git', 'refs/pull/648/head')).toEqual('f11797113e5a9b86bd976329c5dbb8a8bfdfadfa');
} catch (e) {
// eslint-disable-next-line jest/no-conditional-expect
expect(e).toEqual(null);

View File

@ -1,73 +0,0 @@
import {describe, expect, jest, it, beforeEach, afterEach} from '@jest/globals';
import {Context} from '@actions/github/lib/context';
import {GitHub, Payload, ReposGetResponseData} from '../src/github';
beforeEach(() => {
jest.clearAllMocks();
GitHub.getInstance().reset();
});
jest.spyOn(GitHub.prototype, 'context').mockImplementation((): Context => {
return new Context();
});
import * as repoFixture from './fixtures/repo.json';
jest.spyOn(GitHub.prototype, 'repo').mockImplementation((): Promise<ReposGetResponseData> => {
return <Promise<ReposGetResponseData>>(repoFixture as unknown);
});
import * as payloadFixture from './fixtures/github-payload.json';
jest.spyOn(GitHub.prototype as any, 'payload').mockImplementation((): Promise<Payload> => {
return <Promise<Payload>>(payloadFixture as unknown);
});
jest.spyOn(GitHub.prototype as any, 'ref').mockImplementation((): string => {
return 'refs/heads/master';
});
describe('serverURL', () => {
const originalEnv = process.env;
beforeEach(() => {
jest.resetModules();
process.env = {
...originalEnv,
GITHUB_SERVER_URL: 'https://foo.github.com'
};
});
afterEach(() => {
process.env = originalEnv;
});
it('returns default', async () => {
process.env.GITHUB_SERVER_URL = '';
expect(GitHub.getInstance().serverURL()).toEqual('https://github.com');
});
it('returns from env', async () => {
expect(GitHub.getInstance().serverURL()).toEqual('https://foo.github.com');
});
});
describe('gitContext', () => {
it('returns refs/heads/master', async () => {
expect(GitHub.getInstance().gitContext()).toEqual('https://github.com/docker/test-docker-action.git#refs/heads/master');
});
});
describe('provenanceBuilderID', () => {
it('returns 123', async () => {
expect(GitHub.getInstance().provenanceBuilderID()).toEqual('https://github.com/docker/test-docker-action/actions/runs/123');
});
});
describe('repo', () => {
it('returns GitHub repository', async () => {
const repo = await GitHub.getInstance().repo(process.env.GITHUB_TOKEN || '');
expect(repo.name).toEqual('Hello-World');
});
});
describe('fromPayload', () => {
it('returns repository name from payload', async () => {
const repoName = await GitHub.getInstance().fromPayload('repository.name');
expect(repoName).toEqual('test-docker-action');
});
});

View File

@ -1,6 +1,5 @@
process.env = Object.assign({}, process.env, {
GITHUB_REPOSITORY: 'docker/test-docker-action',
GITHUB_RUN_ID: '123',
GITHUB_REPOSITORY: 'docker/actions-toolkit',
RUNNER_TEMP: '/tmp/github_runner',
RUNNER_TOOL_CACHE: '/tmp/github_tool_cache'
}) as {
@ -19,5 +18,7 @@ module.exports = {
moduleNameMapper: {
'^csv-parse/sync': '<rootDir>/node_modules/csv-parse/dist/cjs/sync.cjs'
},
collectCoverageFrom: ['src/**/{!(toolkit.ts),}.ts'],
coveragePathIgnorePatterns: ['lib/', 'node_modules/', '__mocks__/', '__tests__/'],
verbose: true
};

View File

@ -22,8 +22,8 @@
],
"author": "Docker Inc.",
"license": "Apache-2.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"main": "lib/toolkit.js",
"types": "lib/toolkit.d.ts",
"directories": {
"lib": "lib",
"test": "__tests__"

View File

@ -1,6 +1,7 @@
import * as exec from '@actions/exec';
import {Buildx} from './buildx';
import {Context} from './context';
export interface BuilderInfo {
name?: string;
@ -20,14 +21,21 @@ export interface NodeInfo {
}
export interface BuilderOpts {
context: Context;
buildx?: Buildx;
}
export class Builder {
private buildx: Buildx;
private readonly context: Context;
private readonly buildx: Buildx;
constructor(opts?: BuilderOpts) {
this.buildx = opts?.buildx || new Buildx();
constructor(opts: BuilderOpts) {
this.context = opts.context;
this.buildx =
opts?.buildx ||
new Buildx({
context: this.context
});
}
public async inspect(name: string): Promise<BuilderInfo> {

View File

@ -1,37 +1,36 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as semver from 'semver';
import * as tmp from 'tmp';
import {Context} from './context';
import {Buildx} from './buildx';
import {Builder, BuilderInfo} from './builder';
export interface BuildKitOpts {
context: Context;
buildx?: Buildx;
}
export class BuildKit {
private buildx: Buildx;
private readonly context: Context;
private readonly buildx: Buildx;
private containerNamePrefix = 'buildx_buildkit_';
private tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')).split(path.sep).join(path.posix.sep);
constructor(opts?: BuildKitOpts) {
this.buildx = opts?.buildx || new Buildx();
}
public tmpDir() {
return this.tmpdir;
}
public tmpName(options?: tmp.TmpNameOptions): string {
return tmp.tmpNameSync(options);
constructor(opts: BuildKitOpts) {
this.context = opts.context;
this.buildx =
opts?.buildx ||
new Buildx({
context: this.context
});
}
private async getBuilderInfo(name: string): Promise<BuilderInfo> {
const builder = new Builder({buildx: this.buildx});
const builder = new Builder({
context: this.context,
buildx: this.buildx
});
return builder.inspect(name);
}
@ -118,7 +117,7 @@ export class BuildKit {
}
s = fs.readFileSync(s, {encoding: 'utf-8'});
}
const configFile = this.tmpName({tmpdir: this.tmpDir()});
const configFile = this.context.tmpName({tmpdir: this.context.tmpDir()});
fs.writeFileSync(configFile, s);
return configFile;
}

View File

@ -1,39 +1,32 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import * as exec from '@actions/exec';
import {parse} from 'csv-parse/sync';
import * as semver from 'semver';
import * as tmp from 'tmp';
import {Docker} from './docker';
import {Context} from './context';
export interface BuildxOpts {
context: Context;
standalone?: boolean;
}
export class Buildx {
private _standalone: boolean;
private _version: Promise<string>;
private _tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')).split(path.sep).join(path.posix.sep);
private readonly context: Context;
public standalone: boolean;
public version: Promise<string>;
constructor(opts?: BuildxOpts) {
this._standalone = opts?.standalone ?? !Docker.isAvailable();
this._version = this.getVersion();
}
public tmpDir() {
return this._tmpdir;
}
public tmpName(options?: tmp.TmpNameOptions): string {
return tmp.tmpNameSync(options);
constructor(opts: BuildxOpts) {
this.context = opts.context;
this.standalone = opts?.standalone ?? !Docker.isAvailable();
this.version = this.getVersion();
}
public getCommand(args: Array<string>) {
return {
command: this._standalone ? 'buildx' : 'docker',
args: this._standalone ? args : ['buildx', ...args]
command: this.standalone ? 'buildx' : 'docker',
args: this.standalone ? args : ['buildx', ...args]
};
}
@ -71,10 +64,6 @@ export class Buildx {
});
}
public async version(): Promise<string> {
return this._version;
}
public async printVersion() {
const cmd = this.getCommand(['version']);
await exec.exec(cmd.command, cmd.args, {
@ -91,16 +80,16 @@ export class Buildx {
}
public async versionSatisfies(range: string, version?: string): Promise<boolean> {
const ver = version ?? (await this.version());
const ver = version ?? (await this.version);
return semver.satisfies(ver, range) || /^[0-9a-f]{7}$/.exec(ver) !== null;
}
public getBuildImageIDFilePath(): string {
return path.join(this.tmpDir(), 'iidfile').split(path.sep).join(path.posix.sep);
return path.join(this.context.tmpDir(), 'iidfile').split(path.sep).join(path.posix.sep);
}
public getBuildMetadataFilePath(): string {
return path.join(this.tmpDir(), 'metadata-file').split(path.sep).join(path.posix.sep);
return path.join(this.context.tmpDir(), 'metadata-file').split(path.sep).join(path.posix.sep);
}
public getBuildImageID(): string | undefined {
@ -143,7 +132,7 @@ export class Buildx {
return this.generateBuildSecret(kvp, true);
}
private generateBuildSecret(kvp: string, file: boolean): string {
public generateBuildSecret(kvp: string, file: boolean): string {
const delimiterIndex = kvp.indexOf('=');
const key = kvp.substring(0, delimiterIndex);
let value = kvp.substring(delimiterIndex + 1);
@ -156,7 +145,7 @@ export class Buildx {
}
value = fs.readFileSync(value, {encoding: 'utf-8'});
}
const secretFile = this.tmpName({tmpdir: this.tmpDir()});
const secretFile = this.context.tmpName({tmpdir: this.context.tmpDir()});
fs.writeFileSync(secretFile, value);
return `id=${key},src=${secretFile}`;
}

71
src/context.ts Normal file
View File

@ -0,0 +1,71 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import * as tmp from 'tmp';
import jwt_decode, {JwtPayload} from 'jwt-decode';
import {GitHub} from '@actions/github/lib/utils';
import * as github from '@actions/github';
import {components as OctoOpenApiTypes} from '@octokit/openapi-types';
export type ReposGetResponseData = OctoOpenApiTypes['schemas']['repository'];
export interface Jwt extends JwtPayload {
ac?: string;
}
export class Context {
public serverURL: string;
public gitRef: string;
public buildGitContext: string;
public provenanceBuilderID: string;
public octokit: InstanceType<typeof GitHub>;
private readonly _tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')).split(path.sep).join(path.posix.sep);
constructor(githubToken?: string) {
this.gitRef = github.context.ref;
if (github.context.sha && this.gitRef && !this.gitRef.startsWith('refs/')) {
this.gitRef = `refs/heads/${github.context.ref}`;
}
if (github.context.sha && !this.gitRef.startsWith(`refs/pull/`)) {
this.gitRef = github.context.sha;
}
this.serverURL = process.env.GITHUB_SERVER_URL || 'https://github.com';
this.buildGitContext = `${this.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}.git#${this.gitRef}`;
this.provenanceBuilderID = `${this.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}`;
this.octokit = github.getOctokit(`${githubToken}`);
}
public tmpDir(): string {
return this._tmpDir;
}
public tmpName(options?: tmp.TmpNameOptions): string {
return tmp.tmpNameSync(options);
}
public repoData(): Promise<ReposGetResponseData> {
return this.octokit.rest.repos.get({...github.context.repo}).then(response => response.data as ReposGetResponseData);
}
public parseRuntimeToken(): Jwt {
return jwt_decode<Jwt>(process.env['ACTIONS_RUNTIME_TOKEN'] || '');
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public fromPayload(path: string): any {
return this.select(github.context.payload, path);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private select(obj: any, path: string): any {
if (!obj) {
return undefined;
}
const i = path.indexOf('.');
if (i < 0) {
return obj[path];
}
const key = path.slice(0, i);
return this.select(obj[key], path.slice(i + 1));
}
}

View File

@ -1,19 +1,21 @@
import * as exec from '@actions/exec';
export async function getRemoteSha(repo: string, ref: string): Promise<string> {
return await exec
.getExecOutput(`git`, ['ls-remote', repo, ref], {
ignoreReturnCode: true,
silent: true
})
.then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr);
}
const [rsha] = res.stdout.trim().split(/[\s\t]/);
if (rsha.length == 0) {
throw new Error(`Cannot find remote ref for ${repo}#${ref}`);
}
return rsha;
});
export class Git {
public static async getRemoteSha(repo: string, ref: string): Promise<string> {
return await exec
.getExecOutput(`git`, ['ls-remote', repo, ref], {
ignoreReturnCode: true,
silent: true
})
.then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr);
}
const [rsha] = res.stdout.trim().split(/[\s\t]/);
if (rsha.length == 0) {
throw new Error(`Cannot find remote ref for ${repo}#${ref}`);
}
return rsha;
});
}
}

View File

@ -1,90 +0,0 @@
import jwt_decode, {JwtPayload} from 'jwt-decode';
import * as github from '@actions/github';
import {Context} from '@actions/github/lib/context';
import {components as OctoOpenApiTypes} from '@octokit/openapi-types';
import {WebhookPayload} from '@actions/github/lib/interfaces';
export type Payload = WebhookPayload;
export type ReposGetResponseData = OctoOpenApiTypes['schemas']['repository'];
interface Jwt extends JwtPayload {
ac?: string;
}
export class GitHub {
private static instance?: GitHub;
static getInstance = (): GitHub => (GitHub.instance = GitHub.instance ?? new GitHub());
private static _serverURL: string;
private static _gitContext: string;
private static _provenanceBuilderID: string;
private constructor() {
let ref = this.ref();
if (github.context.sha && ref && !ref.startsWith('refs/')) {
ref = `refs/heads/${this.ref()}`;
}
if (github.context.sha && !ref.startsWith(`refs/pull/`)) {
ref = github.context.sha;
}
GitHub._serverURL = process.env.GITHUB_SERVER_URL || 'https://github.com';
GitHub._gitContext = `${GitHub._serverURL}/${github.context.repo.owner}/${github.context.repo.repo}.git#${ref}`;
GitHub._provenanceBuilderID = `${GitHub._serverURL}/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}`;
}
public reset() {
GitHub.instance = undefined;
}
public context(): Context {
return github.context;
}
private ref(): string {
return github.context.ref;
}
public serverURL() {
return GitHub._serverURL;
}
public gitContext() {
return GitHub._gitContext;
}
public provenanceBuilderID() {
return GitHub._provenanceBuilderID;
}
private payload(): Payload {
return github.context.payload;
}
public repo(token: string): Promise<ReposGetResponseData> {
return github
.getOctokit(token)
.rest.repos.get({...github.context.repo})
.then(response => response.data as ReposGetResponseData);
}
public parseRuntimeToken(): Jwt {
return jwt_decode<Jwt>(process.env['ACTIONS_RUNTIME_TOKEN'] || '');
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public fromPayload(path: string): any {
return this.select(this.payload(), path);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private select(obj: any, path: string): any {
if (!obj) {
return undefined;
}
const i = path.indexOf('.');
if (i < 0) {
return obj[path];
}
const key = path.slice(0, i);
return this.select(obj[key], path.slice(i + 1));
}
}

View File

@ -1,6 +0,0 @@
export * as buildkit from './buildkit';
export * as buildx from './buildx';
export * as docker from './docker';
export * as git from './git';
export * as github from './github';
export * as util from './util';

31
src/toolkit.ts Normal file
View File

@ -0,0 +1,31 @@
import {Context} from './context';
import {Buildx} from './buildx';
import {BuildKit} from './buildkit';
export {BuildKit, BuildKitOpts} from './buildkit';
export {Builder, BuilderOpts, BuilderInfo, NodeInfo} from './builder';
export {Buildx, BuildxOpts} from './buildx';
export {Context, ReposGetResponseData, Jwt} from './context';
export {Docker} from './docker';
export {Git} from './git';
export {Util} from './util';
export interface ToolkitOpts {
/**
* GitHub token to use for authentication.
* Uses `process.env.GITHUB_TOKEN` by default.
*/
githubToken?: string;
}
export class Toolkit {
public context: Context;
public buildx: Buildx;
public buildkit: BuildKit;
constructor(opts: ToolkitOpts = {}) {
this.context = new Context(opts.githubToken);
this.buildx = new Buildx({context: this.context});
this.buildkit = new BuildKit({context: this.context, buildx: this.buildx});
}
}

View File

@ -15,9 +15,10 @@
"useUnknownInCatchVariables": false,
},
"exclude": [
"./__mocks__/**/*",
"./__tests__/**/*",
"./lib/**/*",
"node_modules",
"**/*.test.ts",
"jest.config.ts"
]
}