diff --git a/README.md b/README.md index 61e592a..b9702b9 100644 --- a/README.md +++ b/README.md @@ -24,15 +24,16 @@ steps: ## Inputs -| Name | Description | Required | -|------------|-----------------------------------------------------------------------------------------|----------| -| image | Docker image name | Yes | -| tag | Docker image tag (see [Tagging the image with GitOps](#tagging-the-image-using-gitops)) | No | -| registry | Docker registry host | Yes | -| dockerfile | Location of Dockerfile (defaults to `Dockerfile`) | No | -| buildArgs | Docker build arguments in format `KEY=VALUE,KEY=VALUE` | No | -| username | Docker registry username | No | -| password | Docker registry password or token | No | +| Name | Description | Required | +|--------------------|-----------------------------------------------------------------------------------------|----------| +| image | Docker image name | Yes | +| tag | Docker image tag (see [Tagging the image with GitOps](#tagging-the-image-using-gitops)) | No | +| registry | Docker registry host | Yes | +| dockerfile | Location of Dockerfile (defaults to `Dockerfile`) | No | +| buildArgs | Docker build arguments in format `KEY=VALUE,KEY=VALUE` | No | +| username | Docker registry username | No | +| password | Docker registry password or token | No | +| useBranchTimestamp | A boolean to determine whether to add a timestamp to branch-based tags | No | ## Examples @@ -105,3 +106,5 @@ on: ``` This will tag the Docker image with only the semver part of the tag `v1.0.0`. + +If `useBranchTimestamp` option is `true`, add a timestamp (ms) to branch-based image tags. Github tag-based tags are unaffected. diff --git a/dist/index.js b/dist/index.js index 91a3ff5..1f1ee7e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -536,10 +536,7 @@ const isGitHubTag = ref => ref && ref.includes('refs/tags/'); const isReleaseTag = ref => ref && ref.includes('refs/tags/release/'); -const isMasterBranch = ref => ref && ref === 'refs/heads/master'; - -const isNotMasterBranch = ref => - ref && ref.includes('refs/heads/') && ref !== 'refs/heads/master'; +const isBranch = ref => ref && ref.includes('refs/heads/'); const createBuildCommand = (dockerfile, imageName, target, buildArgs) => { let buildCommandPrefix = `docker build -f ${dockerfile} -t ${imageName}`; @@ -554,7 +551,7 @@ const createBuildCommand = (dockerfile, imageName, target, buildArgs) => { return `${buildCommandPrefix} .`; }; -const createTag = () => { +const createTag = useBranchTimestamp => { core.info('Creating Docker image tag...'); const { sha } = context; const ref = context.ref.toLowerCase(); @@ -569,17 +566,18 @@ const createTag = () => { // If GitHub tag exists, use it as the Docker tag dockerTag = ref.replace('refs/tags/', ''); } - } else if (isMasterBranch(ref)) { - // If we're on the master branch, use master-{GIT_SHORT_SHA} as the Docker tag - dockerTag = `master-${shortSha}`; - } else if (isNotMasterBranch(ref)) { - // If we're on a non-master branch, use branch-prefix-{GIT_SHORT_SHA) as the Docker tag + } else if (isBranch(ref)) { + // If we're not building a tag, use branch-prefix-{GIT_SHORT_SHA) as the Docker tag // refs/heads/jira-123/feature/something const branchName = ref.replace('refs/heads/', ''); - const branchPrefix = branchName.includes('/') - ? branchName.substring(0, branchName.indexOf('/')) - : branchName; - dockerTag = `${branchPrefix}-${shortSha}`; + const safeBranchName = branchName + .replace(/[^\w.-]+/g, '-') + .replace(/^[^\w]+/, '') + .substring(0, 120); + dockerTag = `${safeBranchName}-${shortSha}`; + if (useBranchTimestamp) { + dockerTag = `${dockerTag}-${Date.now()}`; + } } else { core.setFailed( 'Unsupported GitHub event - only supports push https://help.github.com/en/articles/events-that-trigger-workflows#push-event-push', @@ -1618,7 +1616,8 @@ const run = () => { // Get GitHub Action inputs const image = core.getInput('image', { required: true }); const registry = core.getInput('registry', { required: true }); - const tag = core.getInput('tag') || docker.createTag(); + const useBranchTimestamp = core.getInput('useBranchTimestamp') || false; + const tag = core.getInput('tag') || docker.createTag(useBranchTimestamp); const buildArgs = processBuildArgsInput(core.getInput('buildArgs')); const target = core.getInput('target') || false; diff --git a/src/docker-build-push.js b/src/docker-build-push.js index e564b6c..1d57918 100644 --- a/src/docker-build-push.js +++ b/src/docker-build-push.js @@ -16,7 +16,8 @@ const run = () => { // Get GitHub Action inputs const image = core.getInput('image', { required: true }); const registry = core.getInput('registry', { required: true }); - const tag = core.getInput('tag') || docker.createTag(); + const useBranchTimestamp = core.getInput('useBranchTimestamp') || false; + const tag = core.getInput('tag') || docker.createTag(useBranchTimestamp); const buildArgs = processBuildArgsInput(core.getInput('buildArgs')); const target = core.getInput('target') || false; diff --git a/src/docker.js b/src/docker.js index 6a50b75..7444d6c 100644 --- a/src/docker.js +++ b/src/docker.js @@ -8,10 +8,7 @@ const isGitHubTag = ref => ref && ref.includes('refs/tags/'); const isReleaseTag = ref => ref && ref.includes('refs/tags/release/'); -const isMasterBranch = ref => ref && ref === 'refs/heads/master'; - -const isNotMasterBranch = ref => - ref && ref.includes('refs/heads/') && ref !== 'refs/heads/master'; +const isBranch = ref => ref && ref.includes('refs/heads/'); const createBuildCommand = (dockerfile, imageName, target, buildArgs) => { let buildCommandPrefix = `docker build -f ${dockerfile} -t ${imageName}`; @@ -26,7 +23,7 @@ const createBuildCommand = (dockerfile, imageName, target, buildArgs) => { return `${buildCommandPrefix} .`; }; -const createTag = () => { +const createTag = useBranchTimestamp => { core.info('Creating Docker image tag...'); const { sha } = context; const ref = context.ref.toLowerCase(); @@ -41,17 +38,18 @@ const createTag = () => { // If GitHub tag exists, use it as the Docker tag dockerTag = ref.replace('refs/tags/', ''); } - } else if (isMasterBranch(ref)) { - // If we're on the master branch, use master-{GIT_SHORT_SHA} as the Docker tag - dockerTag = `master-${shortSha}`; - } else if (isNotMasterBranch(ref)) { - // If we're on a non-master branch, use branch-prefix-{GIT_SHORT_SHA) as the Docker tag + } else if (isBranch(ref)) { + // If we're not building a tag, use branch-prefix-{GIT_SHORT_SHA) as the Docker tag // refs/heads/jira-123/feature/something const branchName = ref.replace('refs/heads/', ''); - const branchPrefix = branchName.includes('/') - ? branchName.substring(0, branchName.indexOf('/')) - : branchName; - dockerTag = `${branchPrefix}-${shortSha}`; + const safeBranchName = branchName + .replace(/[^\w.-]+/g, '-') + .replace(/^[^\w]+/, '') + .substring(0, 120); + dockerTag = `${safeBranchName}-${shortSha}`; + if (useBranchTimestamp) { + dockerTag = `${dockerTag}-${Date.now()}`; + } } else { core.setFailed( 'Unsupported GitHub event - only supports push https://help.github.com/en/articles/events-that-trigger-workflows#push-event-push', diff --git a/tests/docker-build-push.test.js b/tests/docker-build-push.test.js index 775cb11..bdcf317 100644 --- a/tests/docker-build-push.test.js +++ b/tests/docker-build-push.test.js @@ -11,11 +11,20 @@ beforeAll(() => { docker.push = jest.fn(); }); -const mockInputs = (image, registry, tag, buildArgs, target, dockerfile) => { +const mockInputs = ( + image, + registry, + useBranchTimestamp, + tag, + buildArgs, + target, + dockerfile, +) => { core.getInput = jest .fn() .mockReturnValueOnce(image) .mockReturnValueOnce(registry) + .mockReturnValueOnce(useBranchTimestamp) .mockReturnValueOnce(tag) .mockReturnValueOnce(buildArgs) .mockReturnValueOnce(target) @@ -29,10 +38,19 @@ describe('Create & push Docker image', () => { const tag = 'master-1234567'; const buildArgs = ''; const dockerfile = 'Dockerfile'; + const useBranchTimestamp = false; docker.login = jest.fn(); docker.createTag = jest.fn().mockReturnValueOnce(tag); - mockInputs(image, registry, null, buildArgs, false, dockerfile); + mockInputs( + image, + registry, + useBranchTimestamp, + null, + buildArgs, + false, + dockerfile, + ); core.setOutput = jest .fn() .mockReturnValueOnce('imageFullName', `${registry}/${image}:${tag}`); @@ -41,7 +59,7 @@ describe('Create & push Docker image', () => { run(); expect(docker.createTag).toHaveBeenCalledTimes(1); - expect(core.getInput).toHaveBeenCalledTimes(6); + expect(core.getInput).toHaveBeenCalledTimes(7); expect(core.setOutput).toHaveBeenCalledWith( 'imageFullName', `${registry}/${image}:${tag}`, @@ -63,10 +81,19 @@ describe('Create & push Docker image with build args', () => { const tag = 'latest'; const buildArgs = 'VERSION=1.1.1,BUILD_DATE=2020-01-14'; const dockerfile = 'Dockerfile.custom'; + const useBranchTimestamp = false; docker.login = jest.fn(); docker.createTag = jest.fn().mockReturnValueOnce(tag); - mockInputs(image, registry, null, buildArgs, false, dockerfile); + mockInputs( + image, + registry, + useBranchTimestamp, + null, + buildArgs, + false, + dockerfile, + ); core.setOutput = jest .fn() .mockReturnValueOnce('imageFullName', `${registry}/${image}:${tag}`); @@ -75,7 +102,7 @@ describe('Create & push Docker image with build args', () => { run(); expect(docker.createTag).toHaveBeenCalledTimes(1); - expect(core.getInput).toHaveBeenCalledTimes(6); + expect(core.getInput).toHaveBeenCalledTimes(7); expect(core.setOutput).toHaveBeenCalledWith( 'imageFullName', `${registry}/${image}:${tag}`, diff --git a/tests/docker.test.js b/tests/docker.test.js index 8a9467d..602fb8f 100644 --- a/tests/docker.test.js +++ b/tests/docker.test.js @@ -47,7 +47,9 @@ describe('Create Docker image tag from git ref', () => { context.ref = 'refs/heads/jira-123/feature/some-cool-feature'; context.sha = 'f427b0b731ed7664ce4a9fba291ab25fa2e57bd3'; - expect(docker.createTag()).toBe('jira-123-f427b0b'); + expect(docker.createTag()).toBe( + 'jira-123-feature-some-cool-feature-f427b0b', + ); }); test('Create from feature branch without Jira number', () => {