From 31aabd539e7d203a716465773573716ad1727b00 Mon Sep 17 00:00:00 2001 From: Sean Smith Date: Sun, 5 Jun 2022 15:33:37 -0700 Subject: [PATCH] Add build kit support (#98) --- README.md | 31 ++-- action.yml | 4 + dist/index.js | 184 +++++++++---------- src/docker-build-push.js | 93 ++++------ src/docker.js | 64 +++---- src/github.js | 6 + src/settings.js | 6 - src/utils.js | 21 +++ tests/docker-build-push.test.js | 310 ++++++++++++++++---------------- tests/docker.test.js | 162 +++++++++-------- tests/utils.test.js | 39 ++++ 11 files changed, 488 insertions(+), 432 deletions(-) delete mode 100644 src/settings.js create mode 100644 src/utils.js create mode 100644 tests/utils.test.js diff --git a/README.md b/README.md index 552e47f..3375bd3 100644 --- a/README.md +++ b/README.md @@ -41,21 +41,22 @@ steps: ## Inputs -| Name | Description | Required | Type | -| ------------ | -------------------------------------------------------------------------------------------------------- | -------- | ------- | -| image | Docker image name | Yes | String | -| tags | Comma separated docker image tags (see [Tagging the image with GitOps](#tagging-the-image-using-gitops)) | No | List | -| addLatest | Adds the `latest` tag to the GitOps-generated tags | No | Boolean | -| addTimestamp | Suffixes a build timestamp to the branch-based Docker tag | No | Boolean | -| registry | Docker registry host | Yes | String | -| dockerfile | Location of Dockerfile (defaults to `Dockerfile`) | No | String | -| directory | Directory to pass to `docker build` command, if not project root | No | String | -| buildArgs | Docker build arguments passed via `--build-arg` | No | List | -| labels | Docker build labels passed via `--label` | No | List | -| target | Docker build target passed via `--target` | No | String | -| username | Docker registry username | No | String | -| password | Docker registry password or token | No | String | -| githubOrg | GitHub organization to push image to (if not current) | No | String | +| Name | Description | Required | Type | +| -------------- | -------------------------------------------------------------------------------------------------------- | -------- | ------- | +| image | Docker image name | Yes | String | +| tags | Comma separated docker image tags (see [Tagging the image with GitOps](#tagging-the-image-using-gitops)) | No | List | +| addLatest | Adds the `latest` tag to the GitOps-generated tags | No | Boolean | +| addTimestamp | Suffixes a build timestamp to the branch-based Docker tag | No | Boolean | +| registry | Docker registry host | Yes | String | +| dockerfile | Location of Dockerfile (defaults to `Dockerfile`) | No | String | +| directory | Directory to pass to `docker build` command, if not project root | No | String | +| buildArgs | Docker build arguments passed via `--build-arg` | No | List | +| labels | Docker build labels passed via `--label` | No | List | +| target | Docker build target passed via `--target` | No | String | +| username | Docker registry username | No | String | +| password | Docker registry password or token | No | String | +| githubOrg | GitHub organization to push image to (if not current) | No | String | +| enableBuildKit | Enables Docker BuildKit support | No | Boolean | ## Outputs diff --git a/action.yml b/action.yml index 176f4f4..8e7b226 100644 --- a/action.yml +++ b/action.yml @@ -44,6 +44,10 @@ inputs: description: "Suffixes a build timestamp to the branch-based Docker tag" required: false default: "false" + enableBuildKit: + description: "Enables Docker BuildKit support" + required: false + default: "false" outputs: imageFullName: description: "Full name of the Docker image with registry prefix and tag suffix" diff --git a/dist/index.js b/dist/index.js index 9ef426e..c4f08ba 100644 --- a/dist/index.js +++ b/dist/index.js @@ -8313,69 +8313,48 @@ function wrappy (fn, cb) { const core = __nccwpck_require__(2186); const docker = __nccwpck_require__(2439); const github = __nccwpck_require__(8396); - -const GITHUB_REGISTRY_URLS = ['docker.pkg.github.com', 'ghcr.io']; - -let image; -let registry; -let tags; -let buildArgs; -let githubOwner; -let labels; -let target; - -// Convert buildArgs from String to Array, as GH Actions currently does not support Arrays -const processBuildArgsInput = buildArgsInput => { - if (buildArgsInput) { - buildArgs = buildArgsInput.split(','); - } - - return buildArgs; -}; - -const split = stringArray => - stringArray === null || stringArray === undefined || stringArray === '' - ? undefined - : stringArray.split(',').map(value => value.trim()); - -// Get GitHub Action inputs -const processInputs = () => { - image = core.getInput('image', { required: true }); - registry = core.getInput('registry', { required: true }); - tags = split(core.getInput('tags')) || docker.createTags(); - buildArgs = processBuildArgsInput(core.getInput('buildArgs')); - githubOwner = core.getInput('githubOrg') || github.getDefaultOwner(); - labels = split(core.getInput('labels')); - target = core.getInput('target'); -}; - -const isGithubRegistry = () => GITHUB_REGISTRY_URLS.includes(registry); - -// Create the full Docker image name with registry prefix (without tag) -const createFullImageName = () => { - let imageFullName; - if (isGithubRegistry()) { - imageFullName = `${registry}/${githubOwner.toLowerCase()}/${image}`; - } else { - imageFullName = `${registry}/${image}`; - } - return imageFullName; +const { parseArray } = __nccwpck_require__(1608); + +const buildOpts = { + tags: undefined, + buildArgs: undefined, + labels: undefined, + target: undefined, + buildDir: undefined, + enableBuildKit: false }; const run = () => { try { - processInputs(); - - const imageFullName = createFullImageName(); - core.info(`Docker image name created: ${imageFullName}`); - - docker.login(); - docker.build(imageFullName, tags, buildArgs, labels, target); - docker.push(imageFullName, tags); - + // Capture action inputs + const image = core.getInput('image', { required: true }); + const registry = core.getInput('registry', { required: true }); + const username = core.getInput('username'); + const password = core.getInput('password'); + const dockerfile = core.getInput('dockerfile'); + const githubOwner = core.getInput('githubOrg') || github.getDefaultOwner(); + const addLatest = core.getInput('addLatest') === 'true'; + const addTimestamp = core.getInput('addTimestamp') === 'true'; + buildOpts.tags = parseArray(core.getInput('tags')) || docker.createTags(addLatest, addTimestamp); + buildOpts.buildArgs = parseArray(core.getInput('buildArgs')); + buildOpts.labels = parseArray(core.getInput('labels')); + buildOpts.target = core.getInput('target'); + buildOpts.buildDir = core.getInput('directory') || '.'; + buildOpts.enableBuildKit = core.getInput('enableBuildKit') === 'true'; + + // Create the Docker image name + const imageFullName = docker.createFullImageName(registry, image, githubOwner); + core.info(`Docker image name used for this build: ${imageFullName}`); + + // Log in, build & push the Docker image + docker.login(username, password, registry); + docker.build(imageFullName, dockerfile, buildOpts); + docker.push(imageFullName, buildOpts.tags); + + // Capture outputs core.setOutput('imageFullName', imageFullName); core.setOutput('imageName', image); - core.setOutput('tags', tags.join(',')); + core.setOutput('tags', buildOpts.tags.join(',')); } catch (error) { core.setFailed(error.message); } @@ -8392,21 +8371,24 @@ module.exports = run; const cp = __nccwpck_require__(2081); const core = __nccwpck_require__(2186); const fs = __nccwpck_require__(7147); -const dateFormat = __nccwpck_require__(1512); const { context } = __nccwpck_require__(5438); -const cpOptions = __nccwpck_require__(8723); +const { isGitHubTag, isBranch } = __nccwpck_require__(8396); +const { timestamp, cpOptions } = __nccwpck_require__(1608); -const isGitHubTag = ref => ref && ref.includes('refs/tags/'); - -const isBranch = ref => ref && ref.includes('refs/heads/'); +const GITHUB_REGISTRY_URLS = ['docker.pkg.github.com', 'ghcr.io']; -const timestamp = () => dateFormat(new Date(), 'yyyy-mm-dd.HHMMss'); +// Create the full Docker image name with registry prefix (without tags) +const createFullImageName = (registry, image, githubOwner) => { + if (GITHUB_REGISTRY_URLS.includes(registry)) { + return `${registry}/${githubOwner.toLowerCase()}/${image}`; + } + return `${registry}/${image}`; +}; -const createTags = () => { +// Create Docker tags based on input flags & Git branch +const createTags = (addLatest, addTimestamp) => { core.info('Creating Docker image tags...'); const { sha } = context; - const addLatest = core.getInput('addLatest') === 'true'; - const addTimestamp = core.getInput('addTimestamp') === 'true'; const ref = context.ref.toLowerCase(); const shortSha = sha.substring(0, 7); const dockerTags = []; @@ -8440,49 +8422,48 @@ const createTags = () => { return dockerTags; }; -const createBuildCommand = (dockerfile, imageName, tags, buildDir, buildArgs, labels, target) => { - const tagsSuffix = tags.map(tag => `-t ${imageName}:${tag}`).join(' '); +// Dynamically create 'docker build' command based on inputs provided +const createBuildCommand = (imageName, dockerfile, buildOpts) => { + const tagsSuffix = buildOpts.tags.map(tag => `-t ${imageName}:${tag}`).join(' '); let buildCommandPrefix = `docker build -f ${dockerfile} ${tagsSuffix}`; - if (buildArgs) { - const argsSuffix = buildArgs.map(arg => `--build-arg ${arg}`).join(' '); + if (buildOpts.buildArgs) { + const argsSuffix = buildOpts.buildArgs.map(arg => `--build-arg ${arg}`).join(' '); buildCommandPrefix = `${buildCommandPrefix} ${argsSuffix}`; } - if (labels) { - const labelsSuffix = labels.map(label => `--label ${label}`).join(' '); + if (buildOpts.labels) { + const labelsSuffix = buildOpts.labels.map(label => `--label ${label}`).join(' '); buildCommandPrefix = `${buildCommandPrefix} ${labelsSuffix}`; } - if (target) { - const targetSuffix = `--target ${target}`; - buildCommandPrefix = `${buildCommandPrefix} ${targetSuffix}`; + if (buildOpts.target) { + buildCommandPrefix = `${buildCommandPrefix} --target ${buildOpts.target}`; } - return `${buildCommandPrefix} ${buildDir}`; -}; + if (buildOpts.enableBuildKit) { + buildCommandPrefix = `DOCKER_BUILDKIT=1 ${buildCommandPrefix}`; + } -const build = (imageName, tags, buildArgs, labels, target) => { - const dockerfile = core.getInput('dockerfile'); - const buildDir = core.getInput('directory') || '.'; + return `${buildCommandPrefix} ${buildOpts.buildDir}`; +}; +// Perform 'docker build' command +const build = (imageName, dockerfile, buildOpts) => { if (!fs.existsSync(dockerfile)) { core.setFailed(`Dockerfile does not exist in location ${dockerfile}`); } - core.info(`Building Docker image ${imageName} with tags ${tags}...`); - cp.execSync(createBuildCommand(dockerfile, imageName, tags, buildDir, buildArgs, labels, target), cpOptions); + core.info(`Building Docker image ${imageName} with tags ${buildOpts.tags}...`); + cp.execSync(createBuildCommand(imageName, dockerfile, buildOpts), cpOptions); }; const isEcr = registry => registry && registry.includes('amazonaws'); const getRegion = registry => registry.substring(registry.indexOf('ecr.') + 4, registry.indexOf('.amazonaws')); -const login = () => { - const registry = core.getInput('registry', { required: true }); - const username = core.getInput('username'); - const password = core.getInput('password'); - +// Log in to provided Docker registry +const login = (username, password, registry) => { // If using ECR, use the AWS CLI login command in favor of docker login if (isEcr(registry)) { const region = getRegion(registry); @@ -8498,12 +8479,14 @@ const login = () => { } }; +// Push Docker image & all tags const push = (imageName, tags) => { core.info(`Pushing tags ${tags} for Docker image ${imageName}...`); cp.execSync(`docker push ${imageName} --all-tags`, cpOptions); }; module.exports = { + createFullImageName, createTags, build, login, @@ -8519,6 +8502,10 @@ module.exports = { const { context } = __nccwpck_require__(5438); const core = __nccwpck_require__(2186); +const isGitHubTag = ref => ref && ref.includes('refs/tags/'); + +const isBranch = ref => ref && ref.includes('refs/heads/'); + // Returns owning organization of the repo where the Action is run const getDefaultOwner = () => { let owner; @@ -8533,21 +8520,38 @@ const getDefaultOwner = () => { }; module.exports = { + isGitHubTag, + isBranch, getDefaultOwner }; /***/ }), -/***/ 8723: -/***/ ((module) => { +/***/ 1608: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const dateFormat = __nccwpck_require__(1512); + +const timestamp = () => dateFormat(new Date(), 'yyyy-mm-dd.HHMMss'); + +const parseArray = commaDelimitedString => { + if (commaDelimitedString) { + return commaDelimitedString.split(',').map(value => value.trim()); + } + return undefined; +}; const cpOptions = { maxBuffer: 50 * 1024 * 1024, stdio: 'inherit' }; -module.exports = cpOptions; +module.exports = { + timestamp, + parseArray, + cpOptions +}; /***/ }), diff --git a/src/docker-build-push.js b/src/docker-build-push.js index 0b64b15..4102b13 100644 --- a/src/docker-build-push.js +++ b/src/docker-build-push.js @@ -1,69 +1,48 @@ const core = require('@actions/core'); const docker = require('./docker'); const github = require('./github'); - -const GITHUB_REGISTRY_URLS = ['docker.pkg.github.com', 'ghcr.io']; - -let image; -let registry; -let tags; -let buildArgs; -let githubOwner; -let labels; -let target; - -// Convert buildArgs from String to Array, as GH Actions currently does not support Arrays -const processBuildArgsInput = buildArgsInput => { - if (buildArgsInput) { - buildArgs = buildArgsInput.split(','); - } - - return buildArgs; -}; - -const split = stringArray => - stringArray === null || stringArray === undefined || stringArray === '' - ? undefined - : stringArray.split(',').map(value => value.trim()); - -// Get GitHub Action inputs -const processInputs = () => { - image = core.getInput('image', { required: true }); - registry = core.getInput('registry', { required: true }); - tags = split(core.getInput('tags')) || docker.createTags(); - buildArgs = processBuildArgsInput(core.getInput('buildArgs')); - githubOwner = core.getInput('githubOrg') || github.getDefaultOwner(); - labels = split(core.getInput('labels')); - target = core.getInput('target'); -}; - -const isGithubRegistry = () => GITHUB_REGISTRY_URLS.includes(registry); - -// Create the full Docker image name with registry prefix (without tag) -const createFullImageName = () => { - let imageFullName; - if (isGithubRegistry()) { - imageFullName = `${registry}/${githubOwner.toLowerCase()}/${image}`; - } else { - imageFullName = `${registry}/${image}`; - } - return imageFullName; +const { parseArray } = require('./utils'); + +const buildOpts = { + tags: undefined, + buildArgs: undefined, + labels: undefined, + target: undefined, + buildDir: undefined, + enableBuildKit: false }; const run = () => { try { - processInputs(); - - const imageFullName = createFullImageName(); - core.info(`Docker image name created: ${imageFullName}`); - - docker.login(); - docker.build(imageFullName, tags, buildArgs, labels, target); - docker.push(imageFullName, tags); - + // Capture action inputs + const image = core.getInput('image', { required: true }); + const registry = core.getInput('registry', { required: true }); + const username = core.getInput('username'); + const password = core.getInput('password'); + const dockerfile = core.getInput('dockerfile'); + const githubOwner = core.getInput('githubOrg') || github.getDefaultOwner(); + const addLatest = core.getInput('addLatest') === 'true'; + const addTimestamp = core.getInput('addTimestamp') === 'true'; + buildOpts.tags = parseArray(core.getInput('tags')) || docker.createTags(addLatest, addTimestamp); + buildOpts.buildArgs = parseArray(core.getInput('buildArgs')); + buildOpts.labels = parseArray(core.getInput('labels')); + buildOpts.target = core.getInput('target'); + buildOpts.buildDir = core.getInput('directory') || '.'; + buildOpts.enableBuildKit = core.getInput('enableBuildKit') === 'true'; + + // Create the Docker image name + const imageFullName = docker.createFullImageName(registry, image, githubOwner); + core.info(`Docker image name used for this build: ${imageFullName}`); + + // Log in, build & push the Docker image + docker.login(username, password, registry); + docker.build(imageFullName, dockerfile, buildOpts); + docker.push(imageFullName, buildOpts.tags); + + // Capture outputs core.setOutput('imageFullName', imageFullName); core.setOutput('imageName', image); - core.setOutput('tags', tags.join(',')); + core.setOutput('tags', buildOpts.tags.join(',')); } catch (error) { core.setFailed(error.message); } diff --git a/src/docker.js b/src/docker.js index 7ef1b2c..fdbd255 100644 --- a/src/docker.js +++ b/src/docker.js @@ -1,21 +1,24 @@ const cp = require('child_process'); const core = require('@actions/core'); const fs = require('fs'); -const dateFormat = require('dateformat'); const { context } = require('@actions/github'); -const cpOptions = require('./settings'); +const { isGitHubTag, isBranch } = require('./github'); +const { timestamp, cpOptions } = require('./utils'); -const isGitHubTag = ref => ref && ref.includes('refs/tags/'); +const GITHUB_REGISTRY_URLS = ['docker.pkg.github.com', 'ghcr.io']; -const isBranch = ref => ref && ref.includes('refs/heads/'); - -const timestamp = () => dateFormat(new Date(), 'yyyy-mm-dd.HHMMss'); +// Create the full Docker image name with registry prefix (without tags) +const createFullImageName = (registry, image, githubOwner) => { + if (GITHUB_REGISTRY_URLS.includes(registry)) { + return `${registry}/${githubOwner.toLowerCase()}/${image}`; + } + return `${registry}/${image}`; +}; -const createTags = () => { +// Create Docker tags based on input flags & Git branch +const createTags = (addLatest, addTimestamp) => { core.info('Creating Docker image tags...'); const { sha } = context; - const addLatest = core.getInput('addLatest') === 'true'; - const addTimestamp = core.getInput('addTimestamp') === 'true'; const ref = context.ref.toLowerCase(); const shortSha = sha.substring(0, 7); const dockerTags = []; @@ -49,49 +52,48 @@ const createTags = () => { return dockerTags; }; -const createBuildCommand = (dockerfile, imageName, tags, buildDir, buildArgs, labels, target) => { - const tagsSuffix = tags.map(tag => `-t ${imageName}:${tag}`).join(' '); +// Dynamically create 'docker build' command based on inputs provided +const createBuildCommand = (imageName, dockerfile, buildOpts) => { + const tagsSuffix = buildOpts.tags.map(tag => `-t ${imageName}:${tag}`).join(' '); let buildCommandPrefix = `docker build -f ${dockerfile} ${tagsSuffix}`; - if (buildArgs) { - const argsSuffix = buildArgs.map(arg => `--build-arg ${arg}`).join(' '); + if (buildOpts.buildArgs) { + const argsSuffix = buildOpts.buildArgs.map(arg => `--build-arg ${arg}`).join(' '); buildCommandPrefix = `${buildCommandPrefix} ${argsSuffix}`; } - if (labels) { - const labelsSuffix = labels.map(label => `--label ${label}`).join(' '); + if (buildOpts.labels) { + const labelsSuffix = buildOpts.labels.map(label => `--label ${label}`).join(' '); buildCommandPrefix = `${buildCommandPrefix} ${labelsSuffix}`; } - if (target) { - const targetSuffix = `--target ${target}`; - buildCommandPrefix = `${buildCommandPrefix} ${targetSuffix}`; + if (buildOpts.target) { + buildCommandPrefix = `${buildCommandPrefix} --target ${buildOpts.target}`; } - return `${buildCommandPrefix} ${buildDir}`; -}; + if (buildOpts.enableBuildKit) { + buildCommandPrefix = `DOCKER_BUILDKIT=1 ${buildCommandPrefix}`; + } -const build = (imageName, tags, buildArgs, labels, target) => { - const dockerfile = core.getInput('dockerfile'); - const buildDir = core.getInput('directory') || '.'; + return `${buildCommandPrefix} ${buildOpts.buildDir}`; +}; +// Perform 'docker build' command +const build = (imageName, dockerfile, buildOpts) => { if (!fs.existsSync(dockerfile)) { core.setFailed(`Dockerfile does not exist in location ${dockerfile}`); } - core.info(`Building Docker image ${imageName} with tags ${tags}...`); - cp.execSync(createBuildCommand(dockerfile, imageName, tags, buildDir, buildArgs, labels, target), cpOptions); + core.info(`Building Docker image ${imageName} with tags ${buildOpts.tags}...`); + cp.execSync(createBuildCommand(imageName, dockerfile, buildOpts), cpOptions); }; const isEcr = registry => registry && registry.includes('amazonaws'); const getRegion = registry => registry.substring(registry.indexOf('ecr.') + 4, registry.indexOf('.amazonaws')); -const login = () => { - const registry = core.getInput('registry', { required: true }); - const username = core.getInput('username'); - const password = core.getInput('password'); - +// Log in to provided Docker registry +const login = (username, password, registry) => { // If using ECR, use the AWS CLI login command in favor of docker login if (isEcr(registry)) { const region = getRegion(registry); @@ -107,12 +109,14 @@ const login = () => { } }; +// Push Docker image & all tags const push = (imageName, tags) => { core.info(`Pushing tags ${tags} for Docker image ${imageName}...`); cp.execSync(`docker push ${imageName} --all-tags`, cpOptions); }; module.exports = { + createFullImageName, createTags, build, login, diff --git a/src/github.js b/src/github.js index f42bf3d..b89b3b9 100644 --- a/src/github.js +++ b/src/github.js @@ -1,6 +1,10 @@ const { context } = require('@actions/github'); const core = require('@actions/core'); +const isGitHubTag = ref => ref && ref.includes('refs/tags/'); + +const isBranch = ref => ref && ref.includes('refs/heads/'); + // Returns owning organization of the repo where the Action is run const getDefaultOwner = () => { let owner; @@ -15,5 +19,7 @@ const getDefaultOwner = () => { }; module.exports = { + isGitHubTag, + isBranch, getDefaultOwner }; diff --git a/src/settings.js b/src/settings.js deleted file mode 100644 index eb44e16..0000000 --- a/src/settings.js +++ /dev/null @@ -1,6 +0,0 @@ -const cpOptions = { - maxBuffer: 50 * 1024 * 1024, - stdio: 'inherit' -}; - -module.exports = cpOptions; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..5f6fa4c --- /dev/null +++ b/src/utils.js @@ -0,0 +1,21 @@ +const dateFormat = require('dateformat'); + +const timestamp = () => dateFormat(new Date(), 'yyyy-mm-dd.HHMMss'); + +const parseArray = commaDelimitedString => { + if (commaDelimitedString) { + return commaDelimitedString.split(',').map(value => value.trim()); + } + return undefined; +}; + +const cpOptions = { + maxBuffer: 50 * 1024 * 1024, + stdio: 'inherit' +}; + +module.exports = { + timestamp, + parseArray, + cpOptions +}; diff --git a/tests/docker-build-push.test.js b/tests/docker-build-push.test.js index 3dca150..0c6aa44 100644 --- a/tests/docker-build-push.test.js +++ b/tests/docker-build-push.test.js @@ -1,235 +1,231 @@ jest.mock('@actions/core'); -jest.mock('child_process'); + +jest.mock('child_process', () => ({ + ...jest.requireActual('child_process'), + execSync: jest.fn(), + setFailed: jest.fn() +})); + +jest.mock('../src/docker', () => ({ + ...jest.requireActual('../src/docker'), + createTags: jest.fn(), + push: jest.fn(), + login: jest.fn() +})); const core = require('@actions/core'); const cp = require('child_process'); const docker = require('../src/docker'); const run = require('../src/docker-build-push'); -const cpOptions = require('../src/settings'); +const { parseArray, cpOptions } = require('../src/utils'); const mockOwner = 'owner'; const mockRepoName = 'some-repo'; +const runAssertions = (imageFullName, inputs, tagOverrides) => { + // Inputs + expect(core.getInput).toHaveBeenCalledTimes(14); + + // Outputs + const tags = tagOverrides || parseArray(inputs.tags); + expect(core.setOutput).toHaveBeenCalledTimes(3); + expect(core.setOutput).toHaveBeenCalledWith('imageFullName', imageFullName); + expect(core.setOutput).toHaveBeenCalledWith('imageName', inputs.image); + expect(core.setOutput).toHaveBeenCalledWith('tags', tags.toString()); +}; + +const mockGetInput = requestResponse => name => requestResponse[name]; + +let inputs; +let imageFullName; + +const getDefaultImageName = () => `${inputs.registry}/${inputs.image}`; + beforeAll(() => { - docker.push = jest.fn(); process.env.GITHUB_REPOSITORY = `${mockOwner}/${mockRepoName}`; }); +beforeEach(() => { + jest.clearAllMocks(); + inputs = { + image: undefined, + registry: undefined, + username: undefined, + password: undefined, + addLatest: false, + addTimestamp: false, + tags: undefined, + buildArgs: undefined, + githubOrg: undefined, + labels: undefined, + target: undefined, + dockerfile: 'Dockerfile', + buildDir: '.', + enableBuildKit: undefined + }; + imageFullName = undefined; +}); + afterAll(() => { delete process.env.GITHUB_REPOSITORY; }); -const mockInputs = (image, registry, tags, buildArgs, dockerfile, githubOrg, labels, target) => { - core.getInput = jest - .fn() - .mockReturnValueOnce(image) - .mockReturnValueOnce(registry) - .mockReturnValueOnce(tags) - .mockReturnValueOnce(buildArgs) - .mockReturnValueOnce(githubOrg) - .mockReturnValueOnce(labels) - .mockReturnValueOnce(target) - .mockReturnValueOnce(dockerfile); -}; +describe('Create & push Docker image to GitHub Registry', () => { + test('Override GitHub organization', () => { + inputs.image = 'repo-name/image-name'; + inputs.registry = 'docker.pkg.github.com'; + inputs.githubOrg = 'override-org'; + imageFullName = `${inputs.registry}/${inputs.githubOrg}/${inputs.image}`; + const tagOverrides = ['staging-123']; -const mockOutputs = (imageFullName, image, tags) => { - core.setOutput = jest.fn().mockReturnValue('imageFullName', imageFullName); - core.setOutput = jest.fn().mockReturnValue('imageName', image); - core.setOutput = jest.fn().mockReturnValue('tags', tags); -}; + docker.createTags = jest.fn().mockReturnValueOnce(tagOverrides); + core.getInput = jest.fn().mockImplementation(mockGetInput(inputs)); -const convertBuildArgs = buildArgs => { - const output = buildArgs.split(','); - return output.map(arg => `--build-arg ${arg}`).join(' '); -}; + run(); -const runAssertions = (imageFullName, image, tags, dockerfile, buildArgs) => { - expect(docker.createTags).toHaveBeenCalledTimes(1); - expect(core.getInput).toHaveBeenCalledTimes(9); - expect(core.setOutput).toHaveBeenCalledTimes(3); - expect(core.setOutput).toHaveBeenCalledWith('imageFullName', imageFullName); - expect(core.setOutput).toHaveBeenCalledWith('imageName', image); - expect(core.setOutput).toHaveBeenCalledWith('tags', tags.join(',')); + runAssertions(imageFullName, inputs, tagOverrides); - if (buildArgs) { expect(cp.execSync).toHaveBeenCalledWith( - `docker build -f ${dockerfile} -t ${imageFullName}:${tags[0]} ${convertBuildArgs(buildArgs)} .`, + `docker build -f ${inputs.dockerfile} -t ${inputs.registry}/${inputs.githubOrg}/${inputs.image}:${tagOverrides} .`, cpOptions ); - } else { - expect(cp.execSync).toHaveBeenCalledWith( - `docker build -f ${dockerfile} -t ${imageFullName}:${tags[0]} .`, - cpOptions - ); - } -}; - -const createFullImageName = (registry, image) => `${registry}/${image}`; - -const createGitHubImageName = (registry, githubOrg, image) => `${registry}/${githubOrg}/${image}`; - -describe('Create & push Docker image to GitHub Registry', () => { - test('Override GitHub organization', () => { - const image = 'repo-name/image-name'; - const registry = 'docker.pkg.github.com'; - const tags = ['latest']; - const buildArgs = ''; - const dockerfile = 'Dockerfile'; - const githubOrg = 'override-org'; - const imageFullName = createGitHubImageName(registry, githubOrg, image); - - docker.login = jest.fn(); - docker.createTags = jest.fn().mockReturnValueOnce(tags); - mockInputs(image, registry, null, buildArgs, dockerfile, githubOrg); - mockOutputs(imageFullName, image, tags); - cp.execSync = jest.fn(); - - run(); - - runAssertions(imageFullName, image, tags, dockerfile); }); test('Keep default GitHub organization', () => { - const image = `${mockRepoName}/image-name`; - const registry = 'ghcr.io'; - const tags = ['latest']; - const buildArgs = ''; - const dockerfile = 'Dockerfile'; - const imageFullName = createGitHubImageName(registry, mockOwner, image); - - docker.login = jest.fn(); - docker.createTags = jest.fn().mockReturnValueOnce(tags); - mockInputs(image, registry, null, buildArgs, dockerfile, null); - mockOutputs(imageFullName, image, tags); - cp.execSync = jest.fn(); + inputs.image = `${mockRepoName}/image-name`; + inputs.registry = 'ghcr.io'; + imageFullName = `${inputs.registry}/${mockOwner}/${inputs.image}`; + const tagOverrides = ['feat-123']; + + docker.createTags = jest.fn().mockReturnValueOnce(tagOverrides); + core.getInput = jest.fn().mockImplementation(mockGetInput(inputs)); run(); - runAssertions(imageFullName, image, tags, dockerfile); - }); + runAssertions(imageFullName, inputs, tagOverrides); - test('Converts owner name to lowercase', () => { - const image = `${mockRepoName}/image-name`; - const registry = 'docker.pkg.github.com'; - const tags = ['latest']; - const buildArgs = ''; - const dockerfile = 'Dockerfile'; - const imageFullName = createGitHubImageName(registry, 'mockuser', image); + expect(cp.execSync).toHaveBeenCalledWith( + `docker build -f ${inputs.dockerfile} -t ${inputs.registry}/${mockOwner.toLowerCase()}/${ + inputs.image + }:${tagOverrides} .`, + cpOptions + ); + }); - docker.login = jest.fn(); - docker.createTags = jest.fn().mockReturnValueOnce(tags); - mockInputs(image, registry, null, buildArgs, dockerfile, null); - mockOutputs(imageFullName, image, tags); - cp.execSync = jest.fn(); + test('Converts owner name MockUser to lowercase mockuser', () => { + inputs.image = `${mockRepoName}/image-name`; + inputs.registry = 'docker.pkg.github.com'; + inputs.tags = 'latest'; + const mockOrg = `MockUser`; + process.env.GITHUB_REPOSITORY = `${mockOrg}/${mockRepoName}`; + imageFullName = `${inputs.registry}/${mockOrg.toLowerCase()}/${inputs.image}`; - process.env.GITHUB_REPOSITORY = `MockUser/${mockRepoName}`; + docker.createTags = jest.fn().mockReturnValueOnce(inputs.tags); + core.getInput = jest.fn().mockImplementation(mockGetInput(inputs)); run(); - runAssertions(imageFullName, image, tags, dockerfile); + runAssertions(imageFullName, inputs); + + expect(cp.execSync).toHaveBeenCalledWith( + `docker build -f ${inputs.dockerfile} -t ${inputs.registry}/${mockOrg.toLowerCase()}/${inputs.image}:${ + inputs.tags + } .`, + cpOptions + ); }); }); describe('Create & push Docker image to GCR', () => { test('Valid Docker inputs', () => { - const image = 'gcp-project/image'; - const registry = 'gcr.io'; - const tag = 'dev-1234667'; - const buildArgs = ''; - const dockerfile = 'Dockerfile'; - const imageFullName = createFullImageName(registry, image); - - docker.login = jest.fn(); - docker.createTags = jest.fn().mockReturnValueOnce([tag]); - mockInputs(image, registry, null, buildArgs, dockerfile); - mockOutputs(imageFullName, image, tag); - cp.execSync = jest.fn(); + inputs.image = 'gcp-project/image'; + inputs.registry = 'gcr.io'; + const tagOverrides = ['dev-1234667']; + imageFullName = getDefaultImageName(); + + docker.createTags = jest.fn().mockReturnValueOnce(tagOverrides); + core.getInput = jest.fn().mockImplementation(mockGetInput(inputs)); run(); - runAssertions(imageFullName, image, [tag], dockerfile); + runAssertions(imageFullName, inputs, tagOverrides); + + expect(cp.execSync).toHaveBeenCalledWith( + `docker build -f ${inputs.dockerfile} -t ${inputs.registry}/${inputs.image}:${tagOverrides} .`, + cpOptions + ); }); -}); -describe('Create & push Docker image with multiple tags and target', () => { test('Valid Docker inputs with two tags', () => { - const image = 'gcp-project/image'; - const registry = 'gcr.io'; - const tag1 = 'latest'; - const tag2 = 'v1'; - const inputTags = ` ${tag1}, ${tag2} `; - const outputTags = `${tag1},${tag2}`; - const buildArgs = ''; - const dockerfile = 'Dockerfile'; - const imageFullName = createFullImageName(registry, image); - const target = 'builder'; - - docker.login = jest.fn(); - docker.createTags = jest.fn().mockReturnValueOnce([tag1]); - mockInputs(image, registry, inputTags, buildArgs, dockerfile, null, null, target); - mockOutputs(imageFullName, image, outputTags); - cp.execSync = jest.fn(); + inputs.image = 'gcp-project/image'; + inputs.registry = 'gcr.io'; + inputs.tags = 'latest, v1'; + inputs.target = 'builder'; + imageFullName = getDefaultImageName(); + + docker.createTags = jest.fn().mockReturnValueOnce(inputs.tags); + core.getInput = jest.fn().mockImplementation(mockGetInput(inputs)); run(); - expect(docker.createTags).toHaveBeenCalledTimes(0); - expect(core.getInput).toHaveBeenCalledTimes(9); - expect(core.setOutput).toHaveBeenCalledTimes(3); - expect(core.setOutput).toHaveBeenCalledWith('imageFullName', imageFullName); - expect(core.setOutput).toHaveBeenCalledWith('imageName', image); - expect(core.setOutput).toHaveBeenCalledWith('tags', outputTags); + runAssertions(imageFullName, inputs); expect(cp.execSync).toHaveBeenCalledWith( - `docker build -f ${dockerfile} -t ${imageFullName}:${tag1} -t ${imageFullName}:${tag2} --target builder .`, + `docker build -f ${inputs.dockerfile} -t ${inputs.registry}/${inputs.image}:latest -t ${inputs.registry}/${inputs.image}:v1 --target ${inputs.target} .`, cpOptions ); }); -}); -describe('Create & push Docker image with build args and labels', () => { test('Valid Docker inputs with build args', () => { - const image = 'gcp-project/image'; - const registry = 'gcr.io'; - const tag = 'latest'; - const buildArgs = 'VERSION=1.1.1,BUILD_DATE=2020-01-14'; - const dockerfile = 'Dockerfile.custom'; - const imageFullName = createFullImageName(registry, image); - const labels = 'version=1.0,maintainer=mr-smithers-excellent'; - - docker.login = jest.fn(); - docker.createTags = jest.fn().mockReturnValueOnce([tag]); - mockInputs(image, registry, null, buildArgs, dockerfile, null, labels); - mockOutputs(imageFullName, image, tag); - cp.execSync = jest.fn(); + inputs.image = 'gcp-project/image'; + inputs.registry = 'gcr.io'; + inputs.tags = 'latest'; + inputs.buildArgs = 'VERSION=1.1.1,BUILD_DATE=2020-01-14'; + inputs.dockerfile = 'Dockerfile.custom'; + inputs.labels = 'version=1.0,maintainer=mr-smithers-excellent'; + imageFullName = getDefaultImageName(); + + docker.createTags = jest.fn().mockReturnValueOnce(inputs.tags); + core.getInput = jest.fn().mockImplementation(mockGetInput(inputs)); run(); - expect(docker.createTags).toHaveBeenCalledTimes(1); - expect(core.getInput).toHaveBeenCalledTimes(9); - expect(core.setOutput).toHaveBeenCalledWith('imageFullName', imageFullName); - expect(core.setOutput).toHaveBeenCalledWith('imageName', image); - expect(core.setOutput).toHaveBeenCalledWith('tags', tag); + runAssertions(imageFullName, inputs); expect(cp.execSync).toHaveBeenCalledWith( - `docker build -f ${dockerfile} -t ${registry}/${image}:${tag} --build-arg VERSION=1.1.1 --build-arg BUILD_DATE=2020-01-14 --label version=1.0 --label maintainer=mr-smithers-excellent .`, + `docker build -f ${inputs.dockerfile} -t ${inputs.registry}/${inputs.image}:latest --build-arg VERSION=1.1.1 --build-arg BUILD_DATE=2020-01-14 --label version=1.0 --label maintainer=mr-smithers-excellent .`, + cpOptions + ); + }); + + test('Enable buildKit', () => { + inputs.image = 'gcp-project/image'; + inputs.registry = 'gcr.io'; + inputs.tags = 'latest'; + inputs.enableBuildKit = 'true'; + imageFullName = getDefaultImageName(); + + docker.createTags = jest.fn().mockReturnValueOnce(inputs.tags); + core.getInput = jest.fn().mockImplementation(mockGetInput(inputs)); + + run(); + + runAssertions(imageFullName, inputs); + + expect(cp.execSync).toHaveBeenCalledWith( + `DOCKER_BUILDKIT=1 docker build -f ${inputs.dockerfile} -t ${inputs.registry}/${inputs.image}:latest .`, cpOptions ); }); -}); -describe('Create Docker image causing an error', () => { test('Docker login error', () => { - docker.createTags = jest.fn().mockReturnValueOnce(['some-tag']); - docker.build = jest.fn(); const error = 'Error: Cannot perform an interactive login from a non TTY device'; docker.login = jest.fn().mockImplementation(() => { throw new Error(error); }); - core.setFailed = jest.fn(); run(); - expect(docker.createTags).toHaveBeenCalledTimes(1); expect(core.setFailed).toHaveBeenCalledWith(error); }); }); diff --git a/tests/docker.test.js b/tests/docker.test.js index 4617488..871aa39 100644 --- a/tests/docker.test.js +++ b/tests/docker.test.js @@ -5,20 +5,22 @@ const core = require('@actions/core'); const cp = require('child_process'); const fs = require('fs'); const docker = require('../src/docker'); -const cpOptions = require('../src/settings'); +const { cpOptions } = require('../src/utils'); + +describe('Create Docker image tags', () => { + let addLatest; + let addTimestamp; -describe('Create Docker image tag from git ref', () => { beforeEach(() => { - // core.getInput.mockReturnValueOnce('false'); - // core.getInput.mockReturnValueOnce('false'); + addLatest = false; + addTimestamp = false; }); test('Create from tag push', () => { context.ref = 'refs/tags/v1.0'; context.sha = '8d93430eddafb926c668181c71f579556f68668c'; - core.getInput.mockReturnValueOnce('false'); - const tags = docker.createTags(); + const tags = docker.createTags(addLatest, addTimestamp); expect(tags).toContain('v1.0'); expect(tags.length).toEqual(1); @@ -27,9 +29,9 @@ describe('Create Docker image tag from git ref', () => { test('Create from tag push with addLatest', () => { context.ref = 'refs/tags/v1.0'; context.sha = '8d93430eddafb926c668181c71f579556f68668c'; - core.getInput.mockReturnValueOnce('true'); + addLatest = true; - const tags = docker.createTags(); + const tags = docker.createTags(addLatest, addTimestamp); expect(tags).toContain('v1.0'); expect(tags).toContain('latest'); @@ -39,9 +41,10 @@ describe('Create Docker image tag from git ref', () => { test('Create from tag push with addTimestamp', () => { context.ref = 'refs/tags/v1.0'; context.sha = '8d93430eddafb926c668181c71f579556f68668c'; - core.getInput.mockReturnValueOnce('true'); + addLatest = true; + addTimestamp = true; - const tags = docker.createTags(); + const tags = docker.createTags(addLatest, addTimestamp); expect(tags).toContain('v1.0'); expect(tags).toContain('latest'); @@ -51,9 +54,8 @@ describe('Create Docker image tag from git ref', () => { test('Create from tag push with capital letters', () => { context.ref = 'refs/tags/V1.0'; context.sha = '60336540c3df28b52b1e364a65ff5b8f6ec135b8'; - core.getInput.mockReturnValueOnce('foo'); - const tags = docker.createTags(); + const tags = docker.createTags(addLatest, addTimestamp); expect(tags).toContain('v1.0'); expect(tags.length).toEqual(1); @@ -63,7 +65,7 @@ describe('Create Docker image tag from git ref', () => { context.ref = 'refs/heads/master'; context.sha = '79d9bbba94cdbe372703f184e82c102107c71264'; - const tags = docker.createTags(); + const tags = docker.createTags(addLatest, addTimestamp); expect(tags).toContain('master-79d9bbb'); expect(tags.length).toEqual(1); @@ -72,9 +74,9 @@ describe('Create Docker image tag from git ref', () => { test('Create from dev branch push with addLatest', () => { context.ref = 'refs/heads/dev'; context.sha = '79d9bbba94cdbe372703f184e82c102107c71264'; - core.getInput.mockReturnValueOnce('true'); + addLatest = true; - const tags = docker.createTags(); + const tags = docker.createTags(addLatest, addTimestamp); expect(tags).toContain('dev-79d9bbb'); expect(tags).toContain('latest'); @@ -84,10 +86,9 @@ describe('Create Docker image tag from git ref', () => { test('Create from dev branch push with addTimestamp', () => { context.ref = 'refs/heads/dev'; context.sha = '79d9bbba94cdbe372703f184e82c102107c71264'; - core.getInput.mockReturnValueOnce('false'); - core.getInput.mockReturnValueOnce('true'); + addTimestamp = true; - const tags = docker.createTags(); + const tags = docker.createTags(addLatest, addTimestamp); expect(tags.length).toEqual(1); const tag = tags[0]; @@ -103,7 +104,7 @@ describe('Create Docker image tag from git ref', () => { context.ref = 'refs/heads/jira-123/feature/some-cool-feature'; context.sha = 'f427b0b731ed7664ce4a9fba291ab25fa2e57bd3'; - const tags = docker.createTags(); + const tags = docker.createTags(addLatest, addTimestamp); expect(tags).toContain('jira-123-feature-some-cool-feature-f427b0b'); expect(tags.length).toEqual(1); @@ -113,7 +114,7 @@ describe('Create Docker image tag from git ref', () => { context.ref = 'refs/heads/no-jira-number'; context.sha = 'd3c98d2f50ab48322994ad6f80e460bde166b32f'; - const tags = docker.createTags(); + const tags = docker.createTags(addLatest, addTimestamp); expect(tags).toContain('no-jira-number-d3c98d2'); expect(tags.length).toEqual(1); @@ -123,7 +124,7 @@ describe('Create Docker image tag from git ref', () => { context.ref = 'refs/heads/SOME-mixed-CASE-Branch'; context.sha = '152568521eb446d7b331a4e7c1215d29605bf884'; - const tags = docker.createTags(); + const tags = docker.createTags(addLatest, addTimestamp); expect(tags).toContain('some-mixed-case-branch-1525685'); expect(tags.length).toEqual(1); @@ -134,7 +135,7 @@ describe('Create Docker image tag from git ref', () => { context.sha = '89977b79ba5102dab6f3687e6c3b9c1cda878d0a'; core.setFailed = jest.fn(); - const tags = docker.createTags(); + const tags = docker.createTags(addLatest, addTimestamp); expect(tags.length).toEqual(0); expect(core.setFailed).toHaveBeenCalledWith( @@ -143,104 +144,117 @@ describe('Create Docker image tag from git ref', () => { }); }); -describe('core and cp methods', () => { - core.getInput = jest.fn(); - core.setFailed = jest.fn(); +describe('Docker build, login & push commands', () => { cp.execSync = jest.fn(); fs.existsSync = jest.fn(); + core.setFailed = jest.fn(); afterEach(() => { - core.getInput.mockReset(); - core.setFailed.mockReset(); cp.execSync.mockReset(); fs.existsSync.mockReset(); + core.setFailed.mockReset(); }); afterAll(() => { - core.getInput.mockRestore(); - core.setFailed.mockRestore(); cp.execSync.mockRestore(); fs.existsSync.mockRestore(); + core.setFailed.mockRestore(); }); describe('Build image', () => { + let buildOpts; + let dockerfile; + + beforeEach(() => { + buildOpts = { + tags: undefined, + buildArgs: undefined, + labels: undefined, + target: undefined, + buildDir: '.', + enableBuildKit: false + }; + dockerfile = 'Dockerfile'; + }); + test('No Dockerfile', () => { - const dockerfile = 'Dockerfile.nonexistent'; - core.getInput.mockReturnValueOnce(dockerfile); - fs.existsSync.mockReturnValueOnce(false); + const image = 'gcr.io/some-project/image'; + buildOpts.tags = ['v1']; + dockerfile = 'Dockerfile.nonexistent'; - docker.build('gcr.io/some-project/image', ['v1']); + docker.build(image, dockerfile, buildOpts); expect(fs.existsSync).toHaveBeenCalledWith(dockerfile); expect(core.setFailed).toHaveBeenCalledWith(`Dockerfile does not exist in location ${dockerfile}`); }); test('Dockerfile exists', () => { - core.getInput.mockReturnValueOnce('Dockerfile'); - fs.existsSync.mockReturnValueOnce(true); const image = 'gcr.io/some-project/image'; - const tag = 'v1'; + buildOpts.tags = ['v1']; + fs.existsSync = jest.fn().mockReturnValueOnce(false); - docker.build(image, [tag]); + docker.build(image, dockerfile, buildOpts); expect(fs.existsSync).toHaveBeenCalledWith('Dockerfile'); - expect(cp.execSync).toHaveBeenCalledWith(`docker build -f Dockerfile -t ${image}:${tag} .`, cpOptions); + expect(cp.execSync).toHaveBeenCalledWith(`docker build -f Dockerfile -t ${image}:${buildOpts.tags} .`, cpOptions); }); test('Build with build args', () => { - core.getInput.mockReturnValueOnce('Dockerfile'); - core.getInput.mockReturnValueOnce('.'); - fs.existsSync.mockReturnValueOnce(true); const image = 'docker.io/this-project/that-image'; - const tag = 'latest'; - const buildArgs = ['VERSION=latest', 'BUILD_DATE=2020-01-14']; + buildOpts.tags = ['latest']; + buildOpts.buildArgs = ['VERSION=latest', 'BUILD_DATE=2020-01-14']; - docker.build(image, [tag], buildArgs); + docker.build(image, dockerfile, buildOpts); expect(fs.existsSync).toHaveBeenCalledWith('Dockerfile'); expect(cp.execSync).toHaveBeenCalledWith( - `docker build -f Dockerfile -t ${image}:${tag} --build-arg VERSION=latest --build-arg BUILD_DATE=2020-01-14 .`, + `docker build -f Dockerfile -t ${image}:${buildOpts.tags} --build-arg VERSION=latest --build-arg BUILD_DATE=2020-01-14 .`, cpOptions ); }); test('Build with labels and target', () => { - core.getInput.mockReturnValueOnce('Dockerfile'); - core.getInput.mockReturnValueOnce('.'); - fs.existsSync.mockReturnValueOnce(true); const image = 'docker.io/this-project/that-image'; - const tag = 'latest'; - const labels = ['version=1.0', 'maintainer=mr-smithers-excellent']; - const target = 'builder'; + buildOpts.tags = ['latest']; + buildOpts.labels = ['version=1.0', 'maintainer=mr-smithers-excellent']; + buildOpts.target = 'builder'; - docker.build(image, [tag], null, labels, target); + docker.build(image, dockerfile, buildOpts); expect(fs.existsSync).toHaveBeenCalledWith('Dockerfile'); expect(cp.execSync).toHaveBeenCalledWith( - `docker build -f Dockerfile -t ${image}:${tag} --label version=1.0 --label maintainer=mr-smithers-excellent --target builder .`, + `docker build -f Dockerfile -t ${image}:${buildOpts.tags} --label version=1.0 --label maintainer=mr-smithers-excellent --target builder .`, cpOptions ); }); test('Build in different directory', () => { - core.getInput.mockReturnValueOnce('Dockerfile'); - const directory = 'working-dir'; - core.getInput.mockReturnValueOnce(directory); - fs.existsSync.mockReturnValueOnce(true); const image = 'gcr.io/some-project/image'; - const tag = 'v1'; + buildOpts.tags = ['v1']; + buildOpts.buildDir = 'working-dir'; - docker.build(image, [tag]); + docker.build(image, dockerfile, buildOpts); expect(fs.existsSync).toHaveBeenCalledWith('Dockerfile'); - expect(cp.execSync).toHaveBeenCalledWith(`docker build -f Dockerfile -t ${image}:${tag} ${directory}`, cpOptions); + expect(cp.execSync).toHaveBeenCalledWith( + `docker build -f Dockerfile -t ${image}:${buildOpts.tags} ${buildOpts.buildDir}`, + cpOptions + ); }); }); describe('Registry login', () => { - test('Docker Hub login', () => { - const registry = 'docker.io'; - const username = 'mrsmithers'; - const password = 'areallysecurepassword'; + let username; + let password; + let registry; + + beforeEach(() => { + username = undefined; + password = undefined; + registry = undefined; + }); - core.getInput.mockReturnValueOnce(registry).mockReturnValueOnce(username).mockReturnValueOnce(password); + test('Docker Hub login', () => { + username = 'mrsmithers'; + password = 'areallysecurepassword'; + registry = 'docker.io'; - docker.login(); + docker.login(username, password, registry); expect(cp.execSync).toHaveBeenCalledWith(`docker login -u ${username} --password-stdin ${registry}`, { input: password @@ -248,11 +262,9 @@ describe('core and cp methods', () => { }); test('ECR login', () => { - const registry = '123456789123.dkr.ecr.us-east-1.amazonaws.com'; - - core.getInput.mockReturnValueOnce(registry).mockReturnValueOnce('').mockReturnValueOnce(''); + registry = '123456789123.dkr.ecr.us-east-1.amazonaws.com'; - docker.login(); + docker.login(username, password, registry); expect(cp.execSync).toHaveBeenCalledWith( `aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${registry}` @@ -261,11 +273,9 @@ describe('core and cp methods', () => { test('ECR Windows login', () => { process.env.RUNNER_OS = 'Windows'; - const registry = '123456789123.dkr.ecr.us-east-1.amazonaws.com'; + registry = '123456789123.dkr.ecr.us-east-1.amazonaws.com'; - core.getInput.mockReturnValueOnce(registry).mockReturnValueOnce('').mockReturnValueOnce(''); - - docker.login(); + docker.login(username, password, registry); expect(cp.execSync).toHaveBeenCalledWith( `aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${registry}` @@ -273,9 +283,7 @@ describe('core and cp methods', () => { }); test("returns undefined if empty login and doesn't execute command", () => { - core.getInput.mockReturnValueOnce('').mockReturnValueOnce('').mockReturnValueOnce(''); - - docker.login(); + docker.login(username, password, registry); expect(cp.execSync.mock.calls.length).toEqual(0); }); diff --git a/tests/utils.test.js b/tests/utils.test.js new file mode 100644 index 0000000..f74eb23 --- /dev/null +++ b/tests/utils.test.js @@ -0,0 +1,39 @@ +const { parseArray } = require('../src/utils'); + +describe('Parse a comma-delimited strings', () => { + test('Parse string with spaces and return an array', () => { + const input = 'tag1, tag2, tag3'; + const value = parseArray(input); + expect(value).toEqual(['tag1', 'tag2', 'tag3']); + }); + + test('Parse string with inconsistent spaces and return an array', () => { + const input = 'tag1,tag2, tag3'; + const value = parseArray(input); + expect(value).toEqual(['tag1', 'tag2', 'tag3']); + }); + + test('Parse string with no spaces and return an array', () => { + const input = 'tag1,tag2,tag3'; + const value = parseArray(input); + expect(value).toEqual(['tag1', 'tag2', 'tag3']); + }); + + test('Parse string with no commas and return single element array', () => { + const input = 'element'; + const value = parseArray(input); + expect(value).toEqual(['element']); + }); + + test('Parse empty string and return undefined', () => { + const input = ''; + const value = parseArray(input); + expect(value).toEqual(undefined); + }); + + test('Parse undefined and return undefined', () => { + const input = undefined; + const value = parseArray(input); + expect(value).toEqual(undefined); + }); +});