diff --git a/README.md b/README.md index 1cf738a8..d955608d 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ To insert the image URI `amazon/amazon-ecs-sample:latest` as the image for the ` task-definition: task-definition.json container-name: web image: amazon/amazon-ecs-sample:latest + environment-variables: "LOG_LEVEL=info" - name: Deploy to Amazon ECS service uses: aws-actions/amazon-ecs-deploy-task-definition@v1 @@ -46,6 +47,9 @@ input of the second: task-definition: task-definition.json container-name: web image: amazon/amazon-ecs-sample-1:latest + environment-variables: | + LOG_LEVEL=info + ENVIRONMENT=prod - name: Modify Amazon ECS task definition with second container id: render-app-container diff --git a/action.yml b/action.yml index 3795b25b..210273ac 100644 --- a/action.yml +++ b/action.yml @@ -22,6 +22,9 @@ inputs: env-list: description: 'A list of variables to pull from the environment' required: false + environment-variables: + description: 'Variables to add to the container. Each variable is of the form KEY=value, you can specify multiple variables with multi-line YAML strings.' + 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 2402db8d..ddd1020b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1193,6 +1193,8 @@ async function run() { const containerName = core.getInput('container-name', { required: true }); const imageURI = core.getInput('image', { required: true }); + const environmentVariables = core.getInput('environment-variables', { required: false }); + // Parse the task definition const taskDefPath = path.isAbsolute(taskDefinitionFile) ? taskDefinitionFile : @@ -1214,6 +1216,44 @@ async function run() { } containerDef.image = imageURI; + if (environmentVariables) { + + // If environment array is missing, create it + if (!Array.isArray(containerDef.environment)) { + containerDef.environment = []; + } + + // Get pairs by splitting on newlines + environmentVariables.split('\n').forEach(function (line) { + // Trim whitespace + const trimmedLine = line.trim(); + // Skip if empty + if (trimmedLine.length === 0) { return; } + // Split on = + const separatorIdx = trimmedLine.indexOf("="); + // If there's nowhere to split + if (separatorIdx === -1) { + throw new Error(`Cannot parse the environment variable '${trimmedLine}'. Environment variable pairs must be of the form NAME=value.`); + } + // Build object + const variable = { + name: trimmedLine.substring(0, separatorIdx), + value: trimmedLine.substring(separatorIdx + 1), + }; + + // Search container definition environment for one matching name + const variableDef = containerDef.environment.find((e) => e.name == variable.name); + if (variableDef) { + // If found, update + variableDef.value = variable.value; + } else { + // Else, create + containerDef.environment.push(variable); + } + }) + } + + // 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 ed3ce974..f9bc415d 100644 --- a/index.js +++ b/index.js @@ -19,6 +19,8 @@ async function run() { } console.log(envList) + const environmentVariables = core.getInput('environment-variables', { required: false }); + // Parse the task definition const taskDefPath = path.isAbsolute(taskDefinitionFile) ? taskDefinitionFile : @@ -75,6 +77,44 @@ async function run() { taskDefContents.family = serviceFamily; } + if (environmentVariables) { + + // If environment array is missing, create it + if (!Array.isArray(containerDef.environment)) { + containerDef.environment = []; + } + + // Get pairs by splitting on newlines + environmentVariables.split('\n').forEach(function (line) { + // Trim whitespace + const trimmedLine = line.trim(); + // Skip if empty + if (trimmedLine.length === 0) { return; } + // Split on = + const separatorIdx = trimmedLine.indexOf("="); + // If there's nowhere to split + if (separatorIdx === -1) { + throw new Error(`Cannot parse the environment variable '${trimmedLine}'. Environment variable pairs must be of the form NAME=value.`); + } + // Build object + const variable = { + name: trimmedLine.substring(0, separatorIdx), + value: trimmedLine.substring(separatorIdx + 1), + }; + + // Search container definition environment for one matching name + const variableDef = containerDef.environment.find((e) => e.name == variable.name); + if (variableDef) { + // If found, update + variableDef.value = variable.value; + } else { + // Else, create + containerDef.environment.push(variable); + } + }) + } + + // 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 392ac5a6..d149a620 100644 --- a/index.test.js +++ b/index.test.js @@ -19,7 +19,9 @@ describe('Render task definition', () => { .mockReturnValueOnce('nginx:latest') .mockReturnValueOnce('log-group') .mockReturnValueOnce('service-family') - .mockReturnValueOnce('[ "DJANGO_ACCOUNT_ALLOW_REGISTRATION", "DJANGO_ADMIN_URL", "DJANGO_AWS_REGION_NAME", "DJANGO_AWS_S3_CUSTOM_DOMAIN", "DJANGO_AWS_STORAGE_BUCKET_NAME", "DJANGO_SECURE_SSL_REDIRECT", "DJANGO_SENTRY_LOG_LEVEL", "DJANGO_SENTRY_SAMPLE_RATE", "DJANGO_SETTINGS_MODULE", "MYSQL_DATABASE", "MYSQL_HOST", "MYSQL_PORT", "REDIS_URL", "SENTRY_DSN", "VRIFY_CDN_URL", "WEB_CONCURRENCY", "SOCIAL_AUTH_APPLE_ID_CLIENT", "SOCIAL_AUTH_APPLE_ID_AUDIENCE", "WEBAPP_ENDPOINT", "VPEAPP_ENDPOINT", "API_ENDPOINT", "CRONOFY_CLIENT_ID", "ENVIRONMENT", "MIXPANEL_API_KEY", "AMPLITUDE_API_KEY"]'); + .mockReturnValueOnce('[ "DJANGO_ACCOUNT_ALLOW_REGISTRATION", "DJANGO_ADMIN_URL", "DJANGO_AWS_REGION_NAME", "DJANGO_AWS_S3_CUSTOM_DOMAIN", "DJANGO_AWS_STORAGE_BUCKET_NAME", "DJANGO_SECURE_SSL_REDIRECT", "DJANGO_SENTRY_LOG_LEVEL", "DJANGO_SENTRY_SAMPLE_RATE", "DJANGO_SETTINGS_MODULE", "MYSQL_DATABASE", "MYSQL_HOST", "MYSQL_PORT", "REDIS_URL", "SENTRY_DSN", "VRIFY_CDN_URL", "WEB_CONCURRENCY", "SOCIAL_AUTH_APPLE_ID_CLIENT", "SOCIAL_AUTH_APPLE_ID_AUDIENCE", "WEBAPP_ENDPOINT", "VPEAPP_ENDPOINT", "API_ENDPOINT", "CRONOFY_CLIENT_ID", "ENVIRONMENT", "MIXPANEL_API_KEY", "AMPLITUDE_API_KEY"]') + .mockReturnValueOnce('nginx:latest') // image + .mockReturnValueOnce('FOO=bar\nHELLO=world'); // environment-variables process.env = Object.assign(process.env, { GITHUB_WORKSPACE: __dirname }); process.env = Object.assign(process.env, { RUNNER_TEMP: '/home/runner/work/_temp' }); @@ -41,7 +43,16 @@ describe('Render task definition', () => { "awslogs-group": "", }, }, - environment: [{name:"RUNNER_TEMP", value: "/home/runner/work/_temp"}] + environment: [ + { + name: "FOO", + value: "not bar" + }, + { + name: "DONT-TOUCH", + value: "me" + } + ] }, { name: "sidecar", @@ -78,7 +89,20 @@ describe('Render task definition', () => { "awslogs-group": "log-group", }, }, - environment: [{name:"RUNNER_TEMP", value: "/home/runner/work/_temp"}] + environment: [ + { + name: "FOO", + value: "bar" + }, + { + name: "DONT-TOUCH", + value: "me" + }, + { + name: "HELLO", + value: "world" + } + ] }, { name: "sidecar", @@ -96,7 +120,7 @@ describe('Render task definition', () => { expect(core.setOutput).toHaveBeenNthCalledWith(1, 'task-definition', 'new-task-def-file-name'); }); - test('renders a task definition at an absolute path', async () => { + test('renders a task definition at an absolute path, and with initial environment empty', async () => { core.getInput = jest .fn() .mockReturnValueOnce('/hello/task-definition.json') // task-definition @@ -104,7 +128,9 @@ describe('Render task definition', () => { .mockReturnValueOnce('nginx:latest') .mockReturnValueOnce('log-group') .mockReturnValueOnce('service-family') - .mockReturnValueOnce('["RUNNER_TEMP"]'); + .mockReturnValueOnce('["RUNNER_TEMP"]') + .mockReturnValueOnce('nginx:latest') // image + .mockReturnValueOnce('EXAMPLE=here'); // environment-variables jest.mock('/hello/task-definition.json', () => ({ family: 'task-def-family', containerDefinitions: [ @@ -142,7 +168,12 @@ describe('Render task definition', () => { "awslogs-group": "log-group", }, }, - environment: [{name:"RUNNER_TEMP", value: "/home/runner/work/_temp"}] + environment: [ + { + name: "EXAMPLE", + value: "here" + } + ] } ] }, null, 2)