Compare commits

...

16 Commits

Author SHA1 Message Date
CrazyMax
2e59ae7030 Merge pull request #53 from crazy-max/docker-io
Some checks failed
publish / publish (push) Has been cancelled
docker: check command using actions/io module
2023-02-21 08:46:20 +01:00
CrazyMax
99487d6986 docker: check command using actions/io module
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-21 08:40:27 +01:00
CrazyMax
3d9ec9f02d Merge pull request #52 from crazy-max/index
run function to handle GitHub Action main and post runs
2023-02-21 08:21:59 +01:00
CrazyMax
f2b1224b00 run function to handle GitHub Action main and post runs
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-21 08:18:56 +01:00
CrazyMax
1383a2bcaf Merge pull request #51 from crazy-max/fix-docker-exporter
Some checks failed
publish / publish (push) Has been cancelled
buildx: fix docker exporter check
2023-02-20 10:28:01 +01:00
CrazyMax
1acd6c2fc0 buildx: fix docker exporter check
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-20 10:25:50 +01:00
CrazyMax
d153cfaf3c Merge pull request #50 from crazy-max/github-throw-runtime-token
github: throw if runtime token invalid
2023-02-20 10:17:13 +01:00
CrazyMax
d09114e0c5 Merge pull request #49 from crazy-max/buildx-version
buildx: fix version method
2023-02-20 10:17:01 +01:00
CrazyMax
c3aa7f205d github: throw if runtime token invalid
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-20 10:14:11 +01:00
CrazyMax
62f8c6bef6 buildkit: fix debug logs for versionSatisfies
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-20 10:13:04 +01:00
CrazyMax
a9ce06b57e docker: do not set undefined args for checking availability
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-20 10:13:04 +01:00
CrazyMax
cb6ca3829f buildx: fix version method
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-20 09:59:44 +01:00
CrazyMax
1098847fe7 Merge pull request #48 from crazy-max/exec
Exec class
2023-02-20 09:21:41 +01:00
CrazyMax
35a8193474 Exec class
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-20 09:18:53 +01:00
CrazyMax
2915834633 Merge pull request #47 from crazy-max/context-static
make Context static
2023-02-20 07:27:00 +01:00
CrazyMax
a0e8f0bf18 make Context static
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-02-20 07:24:32 +01:00
26 changed files with 327 additions and 293 deletions

View File

@@ -39,7 +39,7 @@ $ npm install @docker/actions-toolkit
## Usage ## Usage
```js ```js
const { Toolkit } = require('@docker/actions-toolkit') const { Toolkit } = require('@docker/actions-toolkit/lib/toolkit')
const toolkit = new Toolkit() const toolkit = new Toolkit()
``` ```

View File

@@ -18,7 +18,6 @@ import {beforeEach, describe, expect, it, jest, test} from '@jest/globals';
import {BuildKit} from '../../src/buildkit/buildkit'; import {BuildKit} from '../../src/buildkit/buildkit';
import {Builder} from '../../src/buildx/builder'; import {Builder} from '../../src/buildx/builder';
import {Context} from '../../src/context';
import {BuilderInfo} from '../../src/types/builder'; import {BuilderInfo} from '../../src/types/builder';
@@ -47,13 +46,9 @@ jest.spyOn(Builder.prototype, 'inspect').mockImplementation(async (): Promise<Bu
describe('getVersion', () => { describe('getVersion', () => {
it('valid', async () => { it('valid', async () => {
const builder = new Builder({ const builder = new Builder();
context: new Context()
});
const builderInfo = await builder.inspect('builder2'); const builderInfo = await builder.inspect('builder2');
const buildkit = new BuildKit({ const buildkit = new BuildKit();
context: new Context()
});
const version = await buildkit.getVersion(builderInfo.nodes[0]); const version = await buildkit.getVersion(builderInfo.nodes[0]);
expect(version).toBe('v0.11.0'); expect(version).toBe('v0.11.0');
}); });
@@ -64,9 +59,7 @@ describe('satisfies', () => {
['builder2', '>=0.10.0', true], ['builder2', '>=0.10.0', true],
['builder2', '>0.11.0', false] ['builder2', '>0.11.0', false]
])('given %p', async (builderName, range, expected) => { ])('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); expect(await buildkit.versionSatisfies(builderName, range)).toBe(expected);
}); });
}); });

View File

@@ -27,13 +27,14 @@ const fixturesDir = path.join(__dirname, '..', 'fixtures');
const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildkit-config-jest'); const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildkit-config-jest');
const tmpName = path.join(tmpDir, '.tmpname-jest'); const tmpName = path.join(tmpDir, '.tmpname-jest');
jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => { jest.spyOn(Context, 'tmpDir').mockImplementation((): string => {
if (!fs.existsSync(tmpDir)) { if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true}); fs.mkdirSync(tmpDir, {recursive: true});
} }
return tmpDir; return tmpDir;
}); });
jest.spyOn(Context.prototype, 'tmpName').mockImplementation((): string => {
jest.spyOn(Context, 'tmpName').mockImplementation((): string => {
return tmpName; return tmpName;
}); });
@@ -60,9 +61,7 @@ describe('resolve', () => {
] ]
])('given %p config', async (val, file, exValue, error: Error) => { ])('given %p config', async (val, file, exValue, error: Error) => {
try { try {
const buildkit = new BuildKit({ const buildkit = new BuildKit();
context: new Context()
});
let config: string; let config: string;
if (file) { if (file) {
config = buildkit.config.resolveFromFile(val); config = buildkit.config.resolveFromFile(val);

View File

@@ -19,7 +19,6 @@ import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import {Builder} from '../../src/buildx/builder'; import {Builder} from '../../src/buildx/builder';
import {Context} from '../../src/context';
import {BuilderInfo} from '../../src/types/builder'; import {BuilderInfo} from '../../src/types/builder';
@@ -50,9 +49,7 @@ jest.spyOn(Builder.prototype, 'inspect').mockImplementation(async (): Promise<Bu
describe('inspect', () => { describe('inspect', () => {
it('valid', async () => { it('valid', async () => {
const builder = new Builder({ const builder = new Builder();
context: new Context()
});
const builderInfo = await builder.inspect(''); const builderInfo = await builder.inspect('');
expect(builderInfo).not.toBeUndefined(); expect(builderInfo).not.toBeUndefined();
expect(builderInfo.name).not.toEqual(''); expect(builderInfo.name).not.toEqual('');

View File

@@ -19,10 +19,10 @@ import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as rimraf from 'rimraf'; import * as rimraf from 'rimraf';
import * as semver from 'semver'; import * as semver from 'semver';
import * as exec from '@actions/exec';
import {Buildx} from '../../src/buildx/buildx'; import {Buildx} from '../../src/buildx/buildx';
import {Context} from '../../src/context'; import {Context} from '../../src/context';
import {Exec} from '../../src/exec';
import {Cert} from '../../src/types/buildx'; import {Cert} from '../../src/types/buildx';
@@ -30,13 +30,14 @@ import {Cert} from '../../src/types/buildx';
const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-jest'); const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-jest');
const tmpName = path.join(tmpDir, '.tmpname-jest'); const tmpName = path.join(tmpDir, '.tmpname-jest');
jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => { jest.spyOn(Context, 'tmpDir').mockImplementation((): string => {
if (!fs.existsSync(tmpDir)) { if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true}); fs.mkdirSync(tmpDir, {recursive: true});
} }
return tmpDir; return tmpDir;
}); });
jest.spyOn(Context.prototype, 'tmpName').mockImplementation((): string => {
jest.spyOn(Context, 'tmpName').mockImplementation((): string => {
return tmpName; return tmpName;
}); });
@@ -90,9 +91,8 @@ describe('certsDir', () => {
describe('isAvailable', () => { describe('isAvailable', () => {
it('docker cli', async () => { it('docker cli', async () => {
const execSpy = jest.spyOn(exec, 'getExecOutput'); const execSpy = jest.spyOn(Exec, 'getExecOutput');
const buildx = new Buildx({ const buildx = new Buildx({
context: new Context(),
standalone: false standalone: false
}); });
await buildx.isAvailable(); await buildx.isAvailable();
@@ -103,9 +103,8 @@ describe('isAvailable', () => {
}); });
}); });
it('standalone', async () => { it('standalone', async () => {
const execSpy = jest.spyOn(exec, 'getExecOutput'); const execSpy = jest.spyOn(Exec, 'getExecOutput');
const buildx = new Buildx({ const buildx = new Buildx({
context: new Context(),
standalone: true standalone: true
}); });
await buildx.isAvailable(); await buildx.isAvailable();
@@ -119,9 +118,8 @@ describe('isAvailable', () => {
describe('printInspect', () => { describe('printInspect', () => {
it('prints builder2 instance', async () => { it('prints builder2 instance', async () => {
const execSpy = jest.spyOn(exec, 'exec'); const execSpy = jest.spyOn(Exec, 'exec');
const buildx = new Buildx({ const buildx = new Buildx({
context: new Context(),
standalone: true standalone: true
}); });
await buildx.printInspect('builder2').catch(() => { await buildx.printInspect('builder2').catch(() => {
@@ -135,9 +133,8 @@ describe('printInspect', () => {
describe('printVersion', () => { describe('printVersion', () => {
it('docker cli', async () => { it('docker cli', async () => {
const execSpy = jest.spyOn(exec, 'exec'); const execSpy = jest.spyOn(Exec, 'exec');
const buildx = new Buildx({ const buildx = new Buildx({
context: new Context(),
standalone: false standalone: false
}); });
await buildx.printVersion(); await buildx.printVersion();
@@ -146,9 +143,8 @@ describe('printVersion', () => {
}); });
}); });
it('standalone', async () => { it('standalone', async () => {
const execSpy = jest.spyOn(exec, 'exec'); const execSpy = jest.spyOn(Exec, 'exec');
const buildx = new Buildx({ const buildx = new Buildx({
context: new Context(),
standalone: true standalone: true
}); });
await buildx.printVersion(); await buildx.printVersion();
@@ -160,10 +156,8 @@ describe('printVersion', () => {
describe('version', () => { describe('version', () => {
it('valid', async () => { it('valid', async () => {
const buildx = new Buildx({ const buildx = new Buildx();
context: new Context() expect(semver.valid(await buildx.version())).not.toBeUndefined();
});
expect(semver.valid(await buildx.version)).not.toBeUndefined();
}); });
}); });
@@ -184,9 +178,7 @@ describe('versionSatisfies', () => {
['bda4882a65349ca359216b135896bddc1d92461c', '>0.1.0', false], ['bda4882a65349ca359216b135896bddc1d92461c', '>0.1.0', false],
['f117971', '>0.6.0', true] ['f117971', '>0.6.0', true]
])('given %p', async (version, range, expected) => { ])('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); expect(await buildx.versionSatisfies(range, version)).toBe(expected);
}); });
}); });

View File

@@ -32,13 +32,14 @@ const metadata = `{
"containerimage.digest": "sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c" "containerimage.digest": "sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c"
}`; }`;
jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => { jest.spyOn(Context, 'tmpDir').mockImplementation((): string => {
if (!fs.existsSync(tmpDir)) { if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true}); fs.mkdirSync(tmpDir, {recursive: true});
} }
return tmpDir; return tmpDir;
}); });
jest.spyOn(Context.prototype, 'tmpName').mockImplementation((): string => {
jest.spyOn(Context, 'tmpName').mockImplementation((): string => {
return tmpName; return tmpName;
}); });
@@ -52,9 +53,7 @@ afterEach(() => {
describe('resolveBuildImageID', () => { describe('resolveBuildImageID', () => {
it('matches', async () => { it('matches', async () => {
const buildx = new Buildx({ const buildx = new Buildx();
context: new Context()
});
const imageID = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9'; const imageID = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9';
const imageIDFile = buildx.inputs.getBuildImageIDFilePath(); const imageIDFile = buildx.inputs.getBuildImageIDFilePath();
await fs.writeFileSync(imageIDFile, imageID); await fs.writeFileSync(imageIDFile, imageID);
@@ -65,9 +64,7 @@ describe('resolveBuildImageID', () => {
describe('resolveBuildMetadata', () => { describe('resolveBuildMetadata', () => {
it('matches', async () => { it('matches', async () => {
const buildx = new Buildx({ const buildx = new Buildx();
context: new Context()
});
const metadataFile = buildx.inputs.getBuildMetadataFilePath(); const metadataFile = buildx.inputs.getBuildMetadataFilePath();
await fs.writeFileSync(metadataFile, metadata); await fs.writeFileSync(metadataFile, metadata);
const expected = buildx.inputs.resolveBuildMetadata(); const expected = buildx.inputs.resolveBuildMetadata();
@@ -77,9 +74,7 @@ describe('resolveBuildMetadata', () => {
describe('resolveDigest', () => { describe('resolveDigest', () => {
it('matches', async () => { it('matches', async () => {
const buildx = new Buildx({ const buildx = new Buildx();
context: new Context()
});
const metadataFile = buildx.inputs.getBuildMetadataFilePath(); const metadataFile = buildx.inputs.getBuildMetadataFilePath();
await fs.writeFileSync(metadataFile, metadata); await fs.writeFileSync(metadataFile, metadata);
const expected = buildx.inputs.resolveDigest(); const expected = buildx.inputs.resolveDigest();
@@ -129,9 +124,7 @@ describe('getProvenanceInput', () => {
], ],
])('given input %p', async (input: string, expected: string) => { ])('given input %p', async (input: string, expected: string) => {
await setInput('provenance', input); await setInput('provenance', input);
const buildx = new Buildx({ const buildx = new Buildx();
context: new Context()
});
expect(buildx.inputs.getProvenanceInput('provenance')).toEqual(expected); expect(buildx.inputs.getProvenanceInput('provenance')).toEqual(expected);
}); });
}); });
@@ -160,9 +153,7 @@ describe('resolveProvenanceAttrs', () => {
'builder-id=https://github.com/docker/actions-toolkit/actions/runs/123' 'builder-id=https://github.com/docker/actions-toolkit/actions/runs/123'
], ],
])('given %p', async (input: string, expected: string) => { ])('given %p', async (input: string, expected: string) => {
const buildx = new Buildx({ const buildx = new Buildx();
context: new Context()
});
expect(buildx.inputs.resolveProvenanceAttrs(input)).toEqual(expected); expect(buildx.inputs.resolveProvenanceAttrs(input)).toEqual(expected);
}); });
}); });
@@ -179,9 +170,7 @@ describe('resolveBuildSecret', () => {
[`notfound=secret`, true, '', '', new Error('secret file secret not found')] [`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) => { ])('given %p key and %p secret', async (kvp: string, file: boolean, exKey: string, exValue: string, error: Error) => {
try { try {
const buildx = new Buildx({ const buildx = new Buildx();
context: new Context()
});
let secret: string; let secret: string;
if (file) { if (file) {
secret = buildx.inputs.resolveBuildSecretFile(kvp); secret = buildx.inputs.resolveBuildSecretFile(kvp);
@@ -239,6 +228,8 @@ describe('hasDockerExporter', () => {
[['type=docker', 'type=tar,dest=/tmp/image.tar'], true, undefined], [['type=docker', 'type=tar,dest=/tmp/image.tar'], true, undefined],
[['"type=tar","dest=/tmp/image.tar"'], false, undefined], [['"type=tar","dest=/tmp/image.tar"'], false, undefined],
[['" type= local" , dest=./release-out'], false, undefined], [['" type= local" , dest=./release-out'], false, undefined],
[['type=docker'], true, false],
[['type=docker'], true, true],
[['.'], true, true], [['.'], true, true],
])('given %p returns %p', async (exporters: Array<string>, expected: boolean, load: boolean | undefined) => { ])('given %p returns %p', async (exporters: Array<string>, expected: boolean, load: boolean | undefined) => {
expect(Inputs.hasDockerExporter(exporters, load)).toEqual(expected); expect(Inputs.hasDockerExporter(exporters, load)).toEqual(expected);

View File

@@ -25,13 +25,14 @@ import {Context} from '../src/context';
const tmpDir = path.join(process.env.TEMP || '/tmp', 'context-jest'); const tmpDir = path.join(process.env.TEMP || '/tmp', 'context-jest');
const tmpName = path.join(tmpDir, '.tmpname-jest'); const tmpName = path.join(tmpDir, '.tmpname-jest');
jest.spyOn(Context.prototype, 'tmpDir').mockImplementation((): string => { jest.spyOn(Context, 'tmpDir').mockImplementation((): string => {
if (!fs.existsSync(tmpDir)) { if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true}); fs.mkdirSync(tmpDir, {recursive: true});
} }
return tmpDir; return tmpDir;
}); });
jest.spyOn(Context.prototype, 'tmpName').mockImplementation((): string => {
jest.spyOn(Context, 'tmpName').mockImplementation((): string => {
return tmpName; return tmpName;
}); });
@@ -43,16 +44,20 @@ afterEach(() => {
rimraf.sync(tmpDir); rimraf.sync(tmpDir);
}); });
describe('gitRef', () => {
it('returns refs/heads/master', async () => {
expect(Context.gitRef()).toEqual('refs/heads/master');
});
});
describe('gitContext', () => { describe('gitContext', () => {
it('returns refs/heads/master', async () => { it('returns refs/heads/master', async () => {
const context = new Context(); expect(Context.gitContext()).toEqual('https://github.com/docker/actions-toolkit.git#refs/heads/master');
expect(context.buildGitContext).toEqual('https://github.com/docker/actions-toolkit.git#refs/heads/master');
}); });
}); });
describe('provenanceBuilderID', () => { describe('provenanceBuilderID', () => {
it('returns 123', async () => { it('returns 123', async () => {
const context = new Context(); expect(Context.provenanceBuilderID()).toEqual('https://github.com/docker/actions-toolkit/actions/runs/123');
expect(context.provenanceBuilderID).toEqual('https://github.com/docker/actions-toolkit/actions/runs/123');
}); });
}); });

View File

@@ -15,11 +15,12 @@
*/ */
import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals'; import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals';
import * as exec from '@actions/exec';
import path from 'path'; import path from 'path';
import * as io from '@actions/io';
import osm = require('os'); import osm = require('os');
import {Docker} from '../src/docker'; import {Docker} from '../src/docker';
import {Exec} from '../src/exec';
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@@ -49,19 +50,16 @@ describe('configDir', () => {
describe('isAvailable', () => { describe('isAvailable', () => {
it('cli', async () => { it('cli', async () => {
const execSpy = jest.spyOn(exec, 'getExecOutput'); const ioWhichSpy = jest.spyOn(io, 'which');
await Docker.isAvailable(); await Docker.isAvailable();
// eslint-disable-next-line jest/no-standalone-expect expect(ioWhichSpy).toHaveBeenCalledTimes(1);
expect(execSpy).toHaveBeenCalledWith(`docker`, undefined, { expect(ioWhichSpy).toHaveBeenCalledWith('docker', true);
silent: true,
ignoreReturnCode: true
});
}); });
}); });
describe('printVersion', () => { describe('printVersion', () => {
it('call docker version', async () => { it('call docker version', async () => {
const execSpy = jest.spyOn(exec, 'exec'); const execSpy = jest.spyOn(Exec, 'exec');
await Docker.printVersion().catch(() => { await Docker.printVersion().catch(() => {
// noop // noop
}); });
@@ -71,7 +69,7 @@ describe('printVersion', () => {
describe('printInfo', () => { describe('printInfo', () => {
it('call docker info', async () => { it('call docker info', async () => {
const execSpy = jest.spyOn(exec, 'exec'); const execSpy = jest.spyOn(Exec, 'exec');
await Docker.printInfo().catch(() => { await Docker.printInfo().catch(() => {
// noop // noop
}); });

51
__tests__/exec.test.ts Normal file
View File

@@ -0,0 +1,51 @@
/**
* Copyright 2023 actions-toolkit authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {beforeEach, describe, expect, it, jest} from '@jest/globals';
import {Exec} from '../src/exec';
beforeEach(() => {
jest.clearAllMocks();
});
describe('exec', () => {
it('returns docker version', async () => {
const execSpy = jest.spyOn(Exec, 'exec');
await Exec.exec('docker', ['version'], {
ignoreReturnCode: true,
silent: true
});
expect(execSpy).toHaveBeenCalledWith(`docker`, ['version'], {
ignoreReturnCode: true,
silent: true
});
});
});
describe('getExecOutput', () => {
it('returns docker version', async () => {
const execSpy = jest.spyOn(Exec, 'getExecOutput');
await Exec.getExecOutput('docker', ['version'], {
ignoreReturnCode: true,
silent: true
});
expect(execSpy).toHaveBeenCalledWith(`docker`, ['version'], {
ignoreReturnCode: true,
silent: true
});
});
});

View File

@@ -130,18 +130,12 @@ describe('printActionsRuntimeTokenACs', () => {
process.env = originalEnv; process.env = originalEnv;
}); });
it('empty', async () => { it('empty', async () => {
const warnSpy = jest.spyOn(core, 'warning');
process.env.ACTIONS_RUNTIME_TOKEN = ''; process.env.ACTIONS_RUNTIME_TOKEN = '';
await GitHub.printActionsRuntimeTokenACs(); await expect(GitHub.printActionsRuntimeTokenACs()).rejects.toThrowError(new Error('ACTIONS_RUNTIME_TOKEN not set'));
expect(warnSpy).toHaveBeenCalledTimes(1);
expect(warnSpy).toHaveBeenCalledWith(`ACTIONS_RUNTIME_TOKEN not set`);
}); });
it('malformed', async () => { it('malformed', async () => {
const warnSpy = jest.spyOn(core, 'warning');
process.env.ACTIONS_RUNTIME_TOKEN = 'foo'; process.env.ACTIONS_RUNTIME_TOKEN = 'foo';
await GitHub.printActionsRuntimeTokenACs(); await expect(GitHub.printActionsRuntimeTokenACs()).rejects.toThrowError(new Error("Cannot parse GitHub Actions Runtime Token: Invalid token specified: Cannot read properties of undefined (reading 'replace')"));
expect(warnSpy).toHaveBeenCalledTimes(1);
expect(warnSpy).toHaveBeenCalledWith(`Cannot parse Actions Runtime Token: Invalid token specified: Cannot read properties of undefined (reading 'replace')`);
}); });
it('refs/heads/master', async () => { it('refs/heads/master', async () => {
const infoSpy = jest.spyOn(core, 'info'); const infoSpy = jest.spyOn(core, 'info');

View File

@@ -41,7 +41,7 @@ module.exports = {
moduleNameMapper: { moduleNameMapper: {
'^csv-parse/sync': '<rootDir>/node_modules/csv-parse/dist/cjs/sync.cjs' '^csv-parse/sync': '<rootDir>/node_modules/csv-parse/dist/cjs/sync.cjs'
}, },
collectCoverageFrom: ['src/**/{!(toolkit.ts),}.ts'], collectCoverageFrom: ['src/**/{!(index.ts),}.ts'],
coveragePathIgnorePatterns: ['lib/', 'node_modules/', '__mocks__/', '__tests__/'], coveragePathIgnorePatterns: ['lib/', 'node_modules/', '__mocks__/', '__tests__/'],
verbose: true verbose: true
}; };

View File

@@ -28,8 +28,8 @@
"author": "Docker Inc.", "author": "Docker Inc.",
"license": "Apache-2.0", "license": "Apache-2.0",
"packageManager": "yarn@3.3.1", "packageManager": "yarn@3.3.1",
"main": "lib/toolkit.js", "main": "lib/index.js",
"types": "lib/toolkit.d.ts", "types": "lib/index.d.ts",
"directories": { "directories": {
"lib": "lib", "lib": "lib",
"test": "__tests__" "test": "__tests__"
@@ -46,6 +46,7 @@
"@actions/exec": "^1.1.1", "@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1", "@actions/github": "^5.1.1",
"@actions/http-client": "^2.0.1", "@actions/http-client": "^2.0.1",
"@actions/io": "^1.1.2",
"@actions/tool-cache": "^2.0.1", "@actions/tool-cache": "^2.0.1",
"csv-parse": "^5.3.5", "csv-parse": "^5.3.5",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",

View File

@@ -15,35 +15,27 @@
*/ */
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as semver from 'semver'; import * as semver from 'semver';
import {Context} from '../context';
import {Buildx} from '../buildx/buildx'; import {Buildx} from '../buildx/buildx';
import {Builder} from '../buildx/builder'; import {Builder} from '../buildx/builder';
import {Config} from './config'; import {Config} from './config';
import {Exec} from '../exec';
import {BuilderInfo, NodeInfo} from '../types/builder'; import {BuilderInfo, NodeInfo} from '../types/builder';
export interface BuildKitOpts { export interface BuildKitOpts {
context: Context;
buildx?: Buildx; buildx?: Buildx;
} }
export class BuildKit { export class BuildKit {
private readonly context: Context;
private readonly buildx: Buildx; private readonly buildx: Buildx;
public readonly config: Config; public readonly config: Config;
constructor(opts: BuildKitOpts) { constructor(opts?: BuildKitOpts) {
this.context = opts.context; this.config = new Config();
this.config = new Config(this.context); this.buildx = opts?.buildx || new Buildx();
this.buildx =
opts?.buildx ||
new Buildx({
context: this.context
});
} }
public async getVersion(node: NodeInfo): Promise<string | undefined> { public async getVersion(node: NodeInfo): Promise<string | undefined> {
@@ -59,53 +51,46 @@ export class BuildKit {
private async getVersionWithinImage(nodeName: string): Promise<string> { private async getVersionWithinImage(nodeName: string): Promise<string> {
core.debug(`BuildKit.getVersionWithinImage nodeName: ${nodeName}`); core.debug(`BuildKit.getVersionWithinImage nodeName: ${nodeName}`);
return exec return Exec.getExecOutput(`docker`, ['inspect', '--format', '{{.Config.Image}}', `${Buildx.containerNamePrefix}${nodeName}`], {
.getExecOutput(`docker`, ['inspect', '--format', '{{.Config.Image}}', `${Buildx.containerNamePrefix}${nodeName}`], { ignoreReturnCode: true,
ignoreReturnCode: true, silent: true
silent: true }).then(bkitimage => {
}) if (bkitimage.exitCode == 0 && bkitimage.stdout.length > 0) {
.then(bkitimage => { core.debug(`BuildKit.getVersionWithinImage image: ${bkitimage.stdout.trim()}`);
if (bkitimage.exitCode == 0 && bkitimage.stdout.length > 0) { return Exec.getExecOutput(`docker`, ['run', '--rm', bkitimage.stdout.trim(), '--version'], {
core.debug(`BuildKit.getVersionWithinImage image: ${bkitimage.stdout.trim()}`); ignoreReturnCode: true,
return exec silent: true
.getExecOutput(`docker`, ['run', '--rm', bkitimage.stdout.trim(), '--version'], { }).then(bkitversion => {
ignoreReturnCode: true, if (bkitversion.exitCode == 0 && bkitversion.stdout.length > 0) {
silent: true return `${bkitimage.stdout.trim()} => ${bkitversion.stdout.trim()}`;
}) } else if (bkitversion.stderr.length > 0) {
.then(bkitversion => { throw new Error(bkitimage.stderr.trim());
if (bkitversion.exitCode == 0 && bkitversion.stdout.length > 0) { }
return `${bkitimage.stdout.trim()} => ${bkitversion.stdout.trim()}`; return bkitversion.stdout.trim();
} else if (bkitversion.stderr.length > 0) { });
throw new Error(bkitimage.stderr.trim()); } else if (bkitimage.stderr.length > 0) {
} throw new Error(bkitimage.stderr.trim());
return bkitversion.stdout.trim(); }
}); return bkitimage.stdout.trim();
} else if (bkitimage.stderr.length > 0) { });
throw new Error(bkitimage.stderr.trim());
}
return bkitimage.stdout.trim();
});
} }
public async versionSatisfies(builderName: string, range: string, builderInfo?: BuilderInfo): Promise<boolean> { public async versionSatisfies(builderName: string, range: string, builderInfo?: BuilderInfo): Promise<boolean> {
if (!builderInfo) { if (!builderInfo) {
builderInfo = await new Builder({ builderInfo = await new Builder({buildx: this.buildx}).inspect(builderName);
context: this.context,
buildx: this.buildx
}).inspect(builderName);
} }
for (const node of builderInfo.nodes) { for (const node of builderInfo.nodes) {
core.debug(`BuildKit.versionSatisfies ${node}: ${range}`);
let bkversion = node.buildkitVersion; let bkversion = node.buildkitVersion;
core.debug(`BuildKit.versionSatisfies ${bkversion}: ${range}`);
if (!bkversion) { if (!bkversion) {
try { try {
bkversion = await this.getVersionWithinImage(node.name || ''); bkversion = await this.getVersionWithinImage(node.name || '');
} catch (e) { } catch (e) {
core.debug(`BuildKit.versionSatisfies ${node}: can't get version`); core.debug(`BuildKit.versionSatisfies ${node.name}: can't get version`);
return false; return false;
} }
} }
core.debug(`BuildKit.versionSatisfies ${node}: version ${bkversion}`); core.debug(`BuildKit.versionSatisfies ${node.name}: version ${bkversion}`);
// BuildKit version reported by moby is in the format of `v0.11.0-moby` // BuildKit version reported by moby is in the format of `v0.11.0-moby`
if (builderInfo.driver == 'docker' && !bkversion.endsWith('-moby')) { if (builderInfo.driver == 'docker' && !bkversion.endsWith('-moby')) {
return false; return false;

View File

@@ -19,12 +19,6 @@ import fs from 'fs';
import {Context} from '../context'; import {Context} from '../context';
export class Config { export class Config {
private readonly context: Context;
constructor(context: Context) {
this.context = context;
}
public resolveFromString(s: string): string { public resolveFromString(s: string): string {
return this.resolve(s, false); return this.resolve(s, false);
} }
@@ -40,7 +34,7 @@ export class Config {
} }
s = fs.readFileSync(s, {encoding: 'utf-8'}); s = fs.readFileSync(s, {encoding: 'utf-8'});
} }
const configFile = this.context.tmpName({tmpdir: this.context.tmpDir()}); const configFile = Context.tmpName({tmpdir: Context.tmpDir()});
fs.writeFileSync(configFile, s); fs.writeFileSync(configFile, s);
return configFile; return configFile;
} }

View File

@@ -14,44 +14,33 @@
* limitations under the License. * limitations under the License.
*/ */
import * as exec from '@actions/exec';
import {Buildx} from './buildx'; import {Buildx} from './buildx';
import {Context} from '../context'; import {Exec} from '../exec';
import {BuilderInfo, NodeInfo} from '../types/builder'; import {BuilderInfo, NodeInfo} from '../types/builder';
export interface BuilderOpts { export interface BuilderOpts {
context: Context;
buildx?: Buildx; buildx?: Buildx;
} }
export class Builder { export class Builder {
private readonly context: Context;
private readonly buildx: Buildx; private readonly buildx: Buildx;
constructor(opts: BuilderOpts) { constructor(opts?: BuilderOpts) {
this.context = opts.context; this.buildx = opts?.buildx || new Buildx();
this.buildx =
opts?.buildx ||
new Buildx({
context: this.context
});
} }
public async inspect(name: string): Promise<BuilderInfo> { public async inspect(name: string): Promise<BuilderInfo> {
const cmd = await this.buildx.getCommand(['inspect', name]); const cmd = await this.buildx.getCommand(['inspect', name]);
return await exec return await Exec.getExecOutput(cmd.command, cmd.args, {
.getExecOutput(cmd.command, cmd.args, { ignoreReturnCode: true,
ignoreReturnCode: true, silent: true
silent: true }).then(res => {
}) if (res.stderr.length > 0 && res.exitCode != 0) {
.then(res => { throw new Error(res.stderr.trim());
if (res.stderr.length > 0 && res.exitCode != 0) { }
throw new Error(res.stderr.trim()); return Builder.parseInspect(res.stdout);
} });
return Builder.parseInspect(res.stdout);
});
} }
public static parseInspect(data: string): BuilderInfo { public static parseInspect(data: string): BuilderInfo {

View File

@@ -17,33 +17,32 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as semver from 'semver'; import * as semver from 'semver';
import {Docker} from '../docker'; import {Docker} from '../docker';
import {Context} from '../context'; import {Exec} from '../exec';
import {Inputs} from './inputs'; import {Inputs} from './inputs';
import {Cert} from '../types/buildx'; import {Cert} from '../types/buildx';
export interface BuildxOpts { export interface BuildxOpts {
context: Context;
standalone?: boolean; standalone?: boolean;
} }
export class Buildx { export class Buildx {
private _version: string | undefined; private _version: string;
private _versionOnce: boolean;
private readonly _standalone: boolean | undefined; private readonly _standalone: boolean | undefined;
private readonly context: Context;
public readonly inputs: Inputs; public readonly inputs: Inputs;
public static readonly containerNamePrefix = 'buildx_buildkit_'; public static readonly containerNamePrefix = 'buildx_buildkit_';
constructor(opts: BuildxOpts) { constructor(opts?: BuildxOpts) {
this._standalone = opts?.standalone; this._standalone = opts?.standalone;
this.context = opts.context; this._version = '';
this.inputs = new Inputs(this.context); this._versionOnce = false;
this.inputs = new Inputs();
} }
static get configDir(): string { static get configDir(): string {
@@ -71,11 +70,10 @@ export class Buildx {
public async isAvailable(): Promise<boolean> { public async isAvailable(): Promise<boolean> {
const cmd = await this.getCommand([]); const cmd = await this.getCommand([]);
const ok: boolean = await exec const ok: boolean = await Exec.getExecOutput(cmd.command, cmd.args, {
.getExecOutput(cmd.command, cmd.args, { ignoreReturnCode: true,
ignoreReturnCode: true, silent: true
silent: true })
})
.then(res => { .then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) { if (res.stderr.length > 0 && res.exitCode != 0) {
core.debug(`Buildx.isAvailable cmd err: ${res.stderr}`); core.debug(`Buildx.isAvailable cmd err: ${res.stderr}`);
@@ -94,34 +92,32 @@ export class Buildx {
public async printInspect(name: string): Promise<void> { public async printInspect(name: string): Promise<void> {
const cmd = await this.getCommand(['inspect', name]); const cmd = await this.getCommand(['inspect', name]);
await exec.exec(cmd.command, cmd.args, { await Exec.exec(cmd.command, cmd.args, {
failOnStdErr: false failOnStdErr: false
}); });
} }
get version() { public async version(): Promise<string> {
return (async () => { if (this._versionOnce) {
if (!this._version) {
const cmd = await this.getCommand(['version']);
this._version = await exec
.getExecOutput(cmd.command, cmd.args, {
ignoreReturnCode: true,
silent: true
})
.then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr.trim());
}
return Buildx.parseVersion(res.stdout.trim());
});
}
return this._version; return this._version;
})(); }
this._versionOnce = true;
const cmd = await this.getCommand(['version']);
this._version = await Exec.getExecOutput(cmd.command, cmd.args, {
ignoreReturnCode: true,
silent: true
}).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr.trim());
}
return Buildx.parseVersion(res.stdout.trim());
});
return this._version;
} }
public async printVersion() { public async printVersion() {
const cmd = await this.getCommand(['version']); const cmd = await this.getCommand(['version']);
await exec.exec(cmd.command, cmd.args, { await Exec.exec(cmd.command, cmd.args, {
failOnStdErr: false failOnStdErr: false
}); });
} }
@@ -135,7 +131,7 @@ export class Buildx {
} }
public async versionSatisfies(range: string, version?: string): Promise<boolean> { public async versionSatisfies(range: string, version?: string): Promise<boolean> {
const ver = version ?? (await this.version); const ver = version ?? (await this.version());
if (!ver) { if (!ver) {
core.debug(`Buildx.versionSatisfies false: undefined version`); core.debug(`Buildx.versionSatisfies false: undefined version`);
return false; return false;

View File

@@ -22,18 +22,12 @@ import {parse} from 'csv-parse/sync';
import {Context} from '../context'; import {Context} from '../context';
export class Inputs { export class Inputs {
private readonly context: Context;
constructor(context: Context) {
this.context = context;
}
public getBuildImageIDFilePath(): string { public getBuildImageIDFilePath(): string {
return path.join(this.context.tmpDir(), 'iidfile'); return path.join(Context.tmpDir(), 'iidfile');
} }
public getBuildMetadataFilePath(): string { public getBuildMetadataFilePath(): string {
return path.join(this.context.tmpDir(), 'metadata-file'); return path.join(Context.tmpDir(), 'metadata-file');
} }
public resolveBuildImageID(): string | undefined { public resolveBuildImageID(): string | undefined {
@@ -89,7 +83,7 @@ export class Inputs {
} }
value = fs.readFileSync(value, {encoding: 'utf-8'}); value = fs.readFileSync(value, {encoding: 'utf-8'});
} }
const secretFile = this.context.tmpName({tmpdir: this.context.tmpDir()}); const secretFile = Context.tmpName({tmpdir: Context.tmpDir()});
fs.writeFileSync(secretFile, value); fs.writeFileSync(secretFile, value);
return `id=${key},src=${secretFile}`; return `id=${key},src=${secretFile}`;
} }
@@ -100,9 +94,8 @@ export class Inputs {
// if input is not set returns empty string // if input is not set returns empty string
return input; return input;
} }
const builderID = this.context.provenanceBuilderID;
try { try {
return core.getBooleanInput(name) ? `builder-id=${builderID}` : 'false'; return core.getBooleanInput(name) ? `builder-id=${Context.provenanceBuilderID()}` : 'false';
} catch (err) { } catch (err) {
// not a valid boolean, so we assume it's a string // not a valid boolean, so we assume it's a string
return this.resolveProvenanceAttrs(input); return this.resolveProvenanceAttrs(input);
@@ -111,7 +104,7 @@ export class Inputs {
public resolveProvenanceAttrs(input: string): string { public resolveProvenanceAttrs(input: string): string {
if (!input) { if (!input) {
return `builder-id=${this.context.provenanceBuilderID}`; return `builder-id=${Context.provenanceBuilderID()}`;
} }
// parse attributes from input // parse attributes from input
const fields = parse(input, { const fields = parse(input, {
@@ -129,7 +122,7 @@ export class Inputs {
} }
} }
// if not add builder-id attribute // if not add builder-id attribute
return `${input},builder-id=${this.context.provenanceBuilderID}`; return `${input},builder-id=${Context.provenanceBuilderID()}`;
} }
public static hasLocalExporter(exporters: string[]): boolean { public static hasLocalExporter(exporters: string[]): boolean {
@@ -141,7 +134,7 @@ export class Inputs {
} }
public static hasDockerExporter(exporters: string[], load?: boolean): boolean { public static hasDockerExporter(exporters: string[], load?: boolean): boolean {
return load ?? Inputs.hasExporterType('docker', exporters); return load || Inputs.hasExporterType('docker', exporters);
} }
public static hasExporterType(name: string, exporters: string[]): boolean { public static hasExporterType(name: string, exporters: string[]): boolean {

View File

@@ -18,7 +18,6 @@ import fs from 'fs';
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as httpm from '@actions/http-client'; import * as httpm from '@actions/http-client';
import * as tc from '@actions/tool-cache'; import * as tc from '@actions/tool-cache';
import * as semver from 'semver'; import * as semver from 'semver';
@@ -26,23 +25,20 @@ import * as util from 'util';
import {Buildx} from './buildx'; import {Buildx} from './buildx';
import {Context} from '../context'; import {Context} from '../context';
import {Exec} from '../exec';
import {Docker} from '../docker'; import {Docker} from '../docker';
import {Git} from '../git'; import {Git} from '../git';
import {GitHubRelease} from '../types/github'; import {GitHubRelease} from '../types/github';
export interface InstallOpts { export interface InstallOpts {
context?: Context;
standalone?: boolean; standalone?: boolean;
} }
export class Install { export class Install {
private readonly _standalone: boolean | undefined; private readonly _standalone: boolean | undefined;
private readonly context: Context;
constructor(opts?: InstallOpts) { constructor(opts?: InstallOpts) {
this.context = opts?.context || new Context();
this._standalone = opts?.standalone; this._standalone = opts?.standalone;
} }
@@ -84,18 +80,16 @@ export class Install {
let toolPath: string; let toolPath: string;
toolPath = tc.find('buildx', vspec); toolPath = tc.find('buildx', vspec);
if (!toolPath) { if (!toolPath) {
const outputDir = path.join(this.context.tmpDir(), 'build-cache'); const outputDir = path.join(Context.tmpDir(), 'build-cache');
const buildCmd = await this.buildCommand(gitContext, outputDir); const buildCmd = await this.buildCommand(gitContext, outputDir);
toolPath = await exec toolPath = await Exec.getExecOutput(buildCmd.command, buildCmd.args, {
.getExecOutput(buildCmd.command, buildCmd.args, { ignoreReturnCode: true
ignoreReturnCode: true }).then(res => {
}) if (res.stderr.length > 0 && res.exitCode != 0) {
.then(res => { core.warning(res.stderr.trim());
if (res.stderr.length > 0 && res.exitCode != 0) { }
core.warning(res.stderr.trim()); return tc.cacheFile(`${outputDir}/buildx`, os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx', 'buildx', vspec);
} });
return tc.cacheFile(`${outputDir}/buildx`, os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx', 'buildx', vspec);
});
} }
return toolPath; return toolPath;
@@ -103,7 +97,7 @@ export class Install {
public async installStandalone(toolPath: string, dest?: string): Promise<string> { public async installStandalone(toolPath: string, dest?: string): Promise<string> {
core.info('Standalone mode'); core.info('Standalone mode');
dest = dest || this.context.tmpDir(); dest = dest || Context.tmpDir();
const toolBinPath = path.join(toolPath, os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'); const toolBinPath = path.join(toolPath, os.platform() == 'win32' ? 'docker-buildx.exe' : 'docker-buildx');
const binDir = path.join(dest, 'bin'); const binDir = path.join(dest, 'bin');
if (!fs.existsSync(binDir)) { if (!fs.existsSync(binDir)) {
@@ -143,8 +137,8 @@ export class Install {
} }
private async buildCommand(gitContext: string, outputDir: string): Promise<{args: Array<string>; command: string}> { private async buildCommand(gitContext: string, outputDir: string): Promise<{args: Array<string>; command: string}> {
const buildxStandaloneFound = await new Buildx({context: this.context, standalone: true}).isAvailable(); const buildxStandaloneFound = await new Buildx({standalone: true}).isAvailable();
const buildxPluginFound = await new Buildx({context: this.context, standalone: false}).isAvailable(); const buildxPluginFound = await new Buildx({standalone: false}).isAvailable();
let buildStandalone = false; let buildStandalone = false;
if ((await this.isStandalone()) && buildxStandaloneFound) { if ((await this.isStandalone()) && buildxStandaloneFound) {
@@ -164,7 +158,7 @@ export class Install {
} }
//prettier-ignore //prettier-ignore
return await new Buildx({context: this.context, standalone: buildStandalone}).getCommand([ return await new Buildx({standalone: buildStandalone}).getCommand([
'build', 'build',
'--target', 'binaries', '--target', 'binaries',
'--build-arg', 'BUILDKIT_CONTEXT_KEEP_GIT_DIR=1', '--build-arg', 'BUILDKIT_CONTEXT_KEEP_GIT_DIR=1',

View File

@@ -23,29 +23,32 @@ import * as github from '@actions/github';
import {GitHub} from './github'; import {GitHub} from './github';
export class Context { export class Context {
public gitRef: string; private static readonly _tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-'));
public buildGitContext: string;
public provenanceBuilderID: string;
private readonly _tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-actions-toolkit-')); public static tmpDir(): string {
return Context._tmpDir;
constructor() {
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.buildGitContext = `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}.git#${this.gitRef}`;
this.provenanceBuilderID = `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}`;
} }
public tmpDir(): string { public static tmpName(options?: tmp.TmpNameOptions): string {
return this._tmpDir;
}
public tmpName(options?: tmp.TmpNameOptions): string {
return tmp.tmpNameSync(options); return tmp.tmpNameSync(options);
} }
public static gitRef(): string {
let gitRef = github.context.ref;
if (github.context.sha && gitRef && !gitRef.startsWith('refs/')) {
gitRef = `refs/heads/${github.context.ref}`;
}
if (github.context.sha && !gitRef.startsWith(`refs/pull/`)) {
gitRef = github.context.sha;
}
return gitRef;
}
public static gitContext(): string {
return `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}.git#${Context.gitRef()}`;
}
public static provenanceBuilderID(): string {
return `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}`;
}
} }

View File

@@ -17,7 +17,8 @@
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as exec from '@actions/exec'; import * as io from '@actions/io';
import {Exec} from './exec';
export class Docker { export class Docker {
static get configDir(): string { static get configDir(): string {
@@ -25,32 +26,23 @@ export class Docker {
} }
public static async isAvailable(): Promise<boolean> { public static async isAvailable(): Promise<boolean> {
const ok: boolean = await exec return await io
.getExecOutput('docker', undefined, { .which('docker', true)
ignoreReturnCode: true,
silent: true
})
.then(res => { .then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) { core.debug(`Docker.isAvailable ok: ${res}`);
core.debug(`Docker.isAvailable cmd err: ${res.stderr}`); return true;
return false;
}
return res.exitCode == 0;
}) })
.catch(error => { .catch(error => {
core.debug(`Docker.isAvailable error: ${error}`); core.debug(`Docker.isAvailable error: ${error}`);
return false; return false;
}); });
core.debug(`Docker.isAvailable: ${ok}`);
return ok;
} }
public static async printVersion(): Promise<void> { public static async printVersion(): Promise<void> {
await exec.exec('docker', ['version']); await Exec.exec('docker', ['version']);
} }
public static async printInfo(): Promise<void> { public static async printInfo(): Promise<void> {
await exec.exec('docker', ['info']); await Exec.exec('docker', ['info']);
} }
} }

31
src/exec.ts Normal file
View File

@@ -0,0 +1,31 @@
/**
* Copyright 2023 actions-toolkit authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import {ExecOptions, ExecOutput} from '@actions/exec';
export class Exec {
public static async exec(commandLine: string, args?: string[], options?: ExecOptions): Promise<number> {
core.debug(`Exec.exec: ${commandLine} ${args?.join(' ')}`);
return exec.exec(commandLine, args, options);
}
public static async getExecOutput(commandLine: string, args?: string[], options?: ExecOptions): Promise<ExecOutput> {
core.debug(`Exec.getExecOutput: ${commandLine} ${args?.join(' ')}`);
return exec.getExecOutput(commandLine, args, options);
}
}

View File

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

View File

@@ -59,12 +59,10 @@ export class GitHub {
try { try {
jwt = GitHub.actionsRuntimeToken; jwt = GitHub.actionsRuntimeToken;
} catch (e) { } catch (e) {
core.warning(`Cannot parse Actions Runtime Token: ${e.message}`); throw new Error(`Cannot parse GitHub Actions Runtime Token: ${e.message}`);
return;
} }
if (!jwt) { if (!jwt) {
core.warning(`ACTIONS_RUNTIME_TOKEN not set`); throw new Error(`ACTIONS_RUNTIME_TOKEN not set`);
return;
} }
try { try {
<Array<GitHubActionsRuntimeTokenAC>>JSON.parse(`${jwt.ac}`).forEach(ac => { <Array<GitHubActionsRuntimeTokenAC>>JSON.parse(`${jwt.ac}`).forEach(ac => {
@@ -85,7 +83,7 @@ export class GitHub {
core.info(`${ac.Scope}: ${permission}`); core.info(`${ac.Scope}: ${permission}`);
}); });
} catch (e) { } catch (e) {
core.warning(`Cannot parse Actions Runtime Token Access Controls: ${e.message}`); throw new Error(`Cannot parse GitHub Actions Runtime Token ACs: ${e.message}`);
} }
} }
} }

42
src/index.ts Normal file
View File

@@ -0,0 +1,42 @@
/**
* Copyright 2023 actions-toolkit authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as core from '@actions/core';
const isPost = !!process.env['STATE_isPost'];
if (!isPost) {
core.saveState('isPost', 'true');
}
/**
* Runs a GitHub Action.
* Output will be streamed to the live console.
*
* @param main runs the defined function.
* @param post runs the defined function at the end of the job if set.
* @returns Promise<void>
*/
export async function run(main: () => Promise<void>, post?: () => Promise<void>): Promise<void> {
if (!isPost) {
try {
await main();
} catch (e) {
core.setFailed(e.message);
}
} else if (post) {
await post();
}
}

View File

@@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
import {Context} from './context';
import {Buildx} from './buildx/buildx'; import {Buildx} from './buildx/buildx';
import {Install} from './buildx/install'; import {Install} from './buildx/install';
import {Builder} from './buildx/builder'; import {Builder} from './buildx/builder';
@@ -30,7 +29,6 @@ export interface ToolkitOpts {
} }
export class Toolkit { export class Toolkit {
public context: Context;
public github: GitHub; public github: GitHub;
public buildx: Buildx; public buildx: Buildx;
public buildxInstall: Install; public buildxInstall: Install;
@@ -38,11 +36,10 @@ export class Toolkit {
public buildkit: BuildKit; public buildkit: BuildKit;
constructor(opts: ToolkitOpts = {}) { constructor(opts: ToolkitOpts = {}) {
this.context = new Context();
this.github = new GitHub({token: opts.githubToken}); this.github = new GitHub({token: opts.githubToken});
this.buildx = new Buildx({context: this.context}); this.buildx = new Buildx();
this.buildxInstall = new Install({context: this.context}); this.buildxInstall = new Install();
this.builder = new Builder({context: this.context, buildx: this.buildx}); this.builder = new Builder({buildx: this.buildx});
this.buildkit = new BuildKit({context: this.context, buildx: this.buildx}); this.buildkit = new BuildKit({buildx: this.buildx});
} }
} }

View File

@@ -52,7 +52,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@actions/io@npm:^1.1.1": "@actions/io@npm:^1.1.1, @actions/io@npm:^1.1.2":
version: 1.1.2 version: 1.1.2
resolution: "@actions/io@npm:1.1.2" resolution: "@actions/io@npm:1.1.2"
checksum: 3c6583c4557abf6c95e9cfc9b6377045e65ba2c5dd4863f4feedd6be9daf4f6b60e588ab0151d5626b5f8320a37f05b8d44ab5c329b8c19f65be31b0616e1464 checksum: 3c6583c4557abf6c95e9cfc9b6377045e65ba2c5dd4863f4feedd6be9daf4f6b60e588ab0151d5626b5f8320a37f05b8d44ab5c329b8c19f65be31b0616e1464
@@ -766,6 +766,7 @@ __metadata:
"@actions/exec": ^1.1.1 "@actions/exec": ^1.1.1
"@actions/github": ^5.1.1 "@actions/github": ^5.1.1
"@actions/http-client": ^2.0.1 "@actions/http-client": ^2.0.1
"@actions/io": ^1.1.2
"@actions/tool-cache": ^2.0.1 "@actions/tool-cache": ^2.0.1
"@types/csv-parse": ^1.2.2 "@types/csv-parse": ^1.2.2
"@types/node": ^16.18.11 "@types/node": ^16.18.11