diff --git a/README.md b/README.md index c251d044..d16605ac 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,9 @@ input of the second: environment-variables: | LOG_LEVEL=info ENVIRONMENT=prod + docker-labels: | + SERVICE=service + VERSION=version - name: Modify Amazon ECS task definition with second container id: render-app-container diff --git a/action.yml b/action.yml index ff4b9ce6..f6133706 100644 --- a/action.yml +++ b/action.yml @@ -22,6 +22,9 @@ inputs: log-configuration-options: description: "Create/Override options inside logConfiguration. Each variable is of the form key=value, you can specify multiple variables with multi-line YAML strings." required: false + docker-labels: + description: "Create/Override options inside dockerLabels. Each variable is key=value, you can specify multiple variables with multi-line YAML." + required: false outputs: task-definition: description: 'The path to the rendered task definition file' diff --git a/dist/index.js b/dist/index.js index 78011330..c50da19d 100644 --- a/dist/index.js +++ b/dist/index.js @@ -20,6 +20,7 @@ async function run() { const logConfigurationLogDriver = core.getInput("log-configuration-log-driver", { required: false }); const logConfigurationOptions = core.getInput("log-configuration-options", { required: false }); + const dockerLabels = core.getInput('docker-labels', { required: false }); // Parse the task definition const taskDefPath = path.isAbsolute(taskDefinitionFile) ? @@ -103,6 +104,24 @@ async function run() { }) } + if (dockerLabels) { + // If dockerLabels object is missing, create it + if (!containerDef.dockerLabels) { containerDef.dockerLabels = {} } + + // Get pairs by splitting on newlines + dockerLabels.split('\n').forEach(function (label) { + // Trim whitespace + label = label.trim(); + if (label && label.length) { + if (label.indexOf("=") == -1 ) { + throw new Error(`Can't parse logConfiguration option ${label}. Must be in key=value format, one per line`); + } + const [key, value] = label.split("="); + containerDef.dockerLabels[key] = value; + } + }) + } + // Write out a new task definition file var updatedTaskDefFile = tmp.fileSync({ tmpdir: process.env.RUNNER_TEMP, diff --git a/index.js b/index.js index 8be46604..a957841c 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,7 @@ async function run() { const logConfigurationLogDriver = core.getInput("log-configuration-log-driver", { required: false }); const logConfigurationOptions = core.getInput("log-configuration-options", { required: false }); + const dockerLabels = core.getInput('docker-labels', { required: false }); // Parse the task definition const taskDefPath = path.isAbsolute(taskDefinitionFile) ? @@ -97,6 +98,24 @@ async function run() { }) } + if (dockerLabels) { + // If dockerLabels object is missing, create it + if (!containerDef.dockerLabels) { containerDef.dockerLabels = {} } + + // Get pairs by splitting on newlines + dockerLabels.split('\n').forEach(function (label) { + // Trim whitespace + label = label.trim(); + if (label && label.length) { + if (label.indexOf("=") == -1 ) { + throw new Error(`Can't parse logConfiguration option ${label}. Must be in key=value format, one per line`); + } + const [key, value] = label.split("="); + containerDef.dockerLabels[key] = value; + } + }) + } + // Write out a new task definition file var updatedTaskDefFile = tmp.fileSync({ tmpdir: process.env.RUNNER_TEMP, diff --git a/index.test.js b/index.test.js index e02fade2..eb15b112 100644 --- a/index.test.js +++ b/index.test.js @@ -224,6 +224,105 @@ describe('Render task definition', () => { expect(core.setFailed).toBeCalledWith('Task definition file does not exist: does-not-exist-task-definition.json'); }); + test('renders a task definition with docker labels', async () => { + core.getInput = jest + .fn() + .mockReturnValueOnce('task-definition.json') + .mockReturnValueOnce('web') + .mockReturnValueOnce('nginx:latest') + .mockReturnValueOnce('EXAMPLE=here') + .mockReturnValueOnce('awslogs') + .mockReturnValueOnce('awslogs-create-group=true\nawslogs-group=/ecs/web\nawslogs-region=us-east-1\nawslogs-stream-prefix=ecs') + .mockReturnValueOnce('key1=value1\nkey2=value2'); + + await run(); + + expect(tmp.fileSync).toHaveBeenNthCalledWith(1, { + tmpdir: '/home/runner/work/_temp', + prefix: 'task-definition-', + postfix: '.json', + keep: true, + discardDescriptor: true + }); + + expect(fs.writeFileSync).toHaveBeenNthCalledWith(1, 'new-task-def-file-name', + JSON.stringify({ + family: 'task-def-family', + containerDefinitions: [ + { + name: "web", + image: "nginx:latest", + environment: [ + { + name: "FOO", + value: "bar" + }, + { + name: "DONT-TOUCH", + value: "me" + }, + { + name: "HELLO", + value: "world" + }, + { + name: "EXAMPLE", + value: "here" + } + ], + logConfiguration: { + logDriver: "awslogs", + options: { + "awslogs-create-group": "true", + "awslogs-group": "/ecs/web", + "awslogs-region": "us-east-1", + "awslogs-stream-prefix": "ecs" + } + }, + dockerLabels : { + "key1":"value1", + "key2":"value2" + } + }, + { + name: "sidecar", + image: "hello" + } + ] + }, null, 2) + ); + }); + + test('renders a task definition at an absolute path with bad docker labels', async () => { + core.getInput = jest + .fn() + .mockReturnValueOnce('/hello/task-definition.json') + .mockReturnValueOnce('web') + .mockReturnValueOnce('nginx:latest') + .mockReturnValueOnce('EXAMPLE=here') + .mockReturnValueOnce('awslogs') + .mockReturnValueOnce('awslogs-create-group=true\nawslogs-group=/ecs/web\nawslogs-region=us-east-1\nawslogs-stream-prefix=ecs') + .mockReturnValueOnce('key1=update_value1\nkey2\nkey3=value3'); + + jest.mock('/hello/task-definition.json', () => ({ + family: 'task-def-family', + containerDefinitions: [ + { + name: "web", + image: "some-other-image", + dockerLabels : { + "key1":"value1", + "key2":"value2" + } + } + ] + }), { virtual: true }); + + await run(); + + expect(core.setFailed).toBeCalledWith('Can\'t parse logConfiguration option key2. Must be in key=value format, one per line'); + }); + test('error returned for non-JSON task definition contents', async () => { jest.mock('./non-json-task-definition.json', () => ("hello"), { virtual: true });