-
Notifications
You must be signed in to change notification settings - Fork 421
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(stack): add task to deploy/cfn/stack package and add template f…
…or task (#1101) This PR adds a CloudFormation template for a task stack, and implement stack configuration methods for task. Related #702 By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
- Loading branch information
1 parent
bdb8f3a
commit 6ade3a2
Showing
6 changed files
with
437 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package stack | ||
|
||
import ( | ||
"fmt" | ||
"github.com/aws/copilot-cli/internal/pkg/config" | ||
"strconv" | ||
|
||
"github.com/aws/copilot-cli/internal/pkg/deploy" | ||
"github.com/aws/copilot-cli/internal/pkg/template" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/service/cloudformation" | ||
) | ||
|
||
const ( | ||
taskTemplatePath = "task/cf.yml" | ||
|
||
taskNameParamKey = "TaskName" | ||
taskCPUParamKey = "TaskCPU" | ||
taskMemoryParamKey = "TaskMemory" | ||
taskLogRetentionParamKey = "LogRetention" | ||
|
||
taskContainerImageParamKey = "ContainerImage" | ||
taskTaskRoleParamKey = "TaskRole" | ||
taskCommandParamKey = "Command" | ||
|
||
taskLogRetentionInDays = "1" | ||
) | ||
|
||
type taskStackConfig struct { | ||
*deploy.CreateTaskResourcesInput | ||
parser template.ReadParser | ||
} | ||
|
||
// NewTaskStackConfig sets up a struct that provides stack configurations for CloudFormation | ||
// to deploy the task resources stack. | ||
func NewTaskStackConfig(taskOpts *deploy.CreateTaskResourcesInput) *taskStackConfig { | ||
return &taskStackConfig{ | ||
CreateTaskResourcesInput: taskOpts, | ||
parser: template.New(), | ||
} | ||
} | ||
|
||
// StackName returns the name of the CloudFormation stack for the task. | ||
func (t *taskStackConfig) StackName() string { | ||
return NameForTask(t.Name) | ||
} | ||
|
||
// Template returns the task CloudFormation template. | ||
func (t *taskStackConfig) Template() (string, error) { | ||
content, err := t.parser.Parse(taskTemplatePath, struct{ | ||
EnvVars map[string]string | ||
}{ | ||
EnvVars: t.EnvVars, | ||
}) | ||
if err != nil { | ||
return "", fmt.Errorf("read template for task stack: %w", err) | ||
} | ||
return content.String(), nil | ||
} | ||
|
||
// Parameters returns the parameter values to be passed to the task CloudFormation template. | ||
func (t *taskStackConfig) Parameters() ([]*cloudformation.Parameter, error) { | ||
return []*cloudformation.Parameter{ | ||
{ | ||
ParameterKey: aws.String(taskNameParamKey), | ||
ParameterValue: aws.String(t.Name), | ||
}, | ||
{ | ||
ParameterKey: aws.String(taskCPUParamKey), | ||
ParameterValue: aws.String(strconv.Itoa(t.CPU)), | ||
}, | ||
{ | ||
ParameterKey: aws.String(taskMemoryParamKey), | ||
ParameterValue: aws.String(strconv.Itoa(t.Memory)), | ||
}, | ||
{ | ||
ParameterKey: aws.String(taskLogRetentionParamKey), | ||
ParameterValue: aws.String(taskLogRetentionInDays), | ||
}, | ||
{ | ||
ParameterKey: aws.String(taskContainerImageParamKey), | ||
ParameterValue: aws.String(t.Image), | ||
}, | ||
{ | ||
ParameterKey: aws.String(taskTaskRoleParamKey), | ||
ParameterValue: aws.String(t.TaskRole), | ||
}, | ||
{ | ||
ParameterKey: aws.String(taskCommandParamKey), | ||
ParameterValue: aws.String(t.Command), | ||
}, | ||
}, nil | ||
} | ||
|
||
// Tags returns the tags that should be applied to the task CloudFormation. | ||
func (t *taskStackConfig) Tags() []*cloudformation.Tag { | ||
appEnvTags := make(map[string]string) | ||
|
||
if t.Env != config.EnvNameNone { | ||
appEnvTags[deploy.AppTagKey] = t.App | ||
appEnvTags[deploy.EnvTagKey] = t.Env | ||
} | ||
|
||
return mergeAndFlattenTags(appEnvTags, map[string]string{ | ||
deploy.TaskTagKey: t.Name, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
package stack | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"github.com/aws/copilot-cli/internal/pkg/config" | ||
"github.com/aws/copilot-cli/internal/pkg/deploy" | ||
"testing" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/service/cloudformation" | ||
"github.com/aws/copilot-cli/internal/pkg/template" | ||
"github.com/aws/copilot-cli/internal/pkg/template/mocks" | ||
"github.com/golang/mock/gomock" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
const ( | ||
testTaskName = "my-task" | ||
) | ||
|
||
func TestTaskStackConfig_Template(t *testing.T) { | ||
testCases := map[string]struct { | ||
mockReadParser func(m *mocks.MockReadParser) | ||
|
||
wantedTemplate string | ||
wantedError error | ||
}{ | ||
"should return error if unable to read": { | ||
mockReadParser: func(m *mocks.MockReadParser) { | ||
m.EXPECT().Parse(taskTemplatePath, gomock.Any()).Return(nil, errors.New("error reading template")) | ||
}, | ||
wantedError: errors.New("read template for task stack: error reading template"), | ||
}, | ||
"should return template body when present": { | ||
mockReadParser: func(m *mocks.MockReadParser) { | ||
m.EXPECT().Parse(taskTemplatePath, gomock.Any()).Return(&template.Content{ | ||
Buffer: bytes.NewBufferString("This is the task template"), | ||
}, nil) | ||
}, | ||
wantedTemplate: "This is the task template", | ||
}, | ||
} | ||
|
||
for name, tc := range testCases { | ||
t.Run(name, func(t *testing.T) { | ||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
mockReadParser := mocks.NewMockReadParser(ctrl) | ||
if tc.mockReadParser != nil { | ||
tc.mockReadParser(mockReadParser) | ||
} | ||
|
||
taskInput := deploy.CreateTaskResourcesInput{} | ||
|
||
taskStackConfig := &taskStackConfig{ | ||
CreateTaskResourcesInput: &taskInput, | ||
parser: mockReadParser, | ||
} | ||
|
||
got, err := taskStackConfig.Template() | ||
|
||
if tc.wantedError != nil { | ||
require.EqualError(t, tc.wantedError, err.Error()) | ||
} else { | ||
require.Equal(t, tc.wantedTemplate, got) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestTaskStackConfig_Parameters(t *testing.T) { | ||
expectedParams := []*cloudformation.Parameter{ | ||
{ | ||
ParameterKey: aws.String(taskNameParamKey), | ||
ParameterValue: aws.String("my-task"), | ||
}, | ||
{ | ||
ParameterKey: aws.String(taskContainerImageParamKey), | ||
ParameterValue: aws.String("7456.dkr.ecr.us-east-2.amazonaws.com/my-task:0.1"), | ||
}, | ||
{ | ||
ParameterKey: aws.String(taskCPUParamKey), | ||
ParameterValue: aws.String("256"), | ||
}, | ||
{ | ||
ParameterKey: aws.String(taskMemoryParamKey), | ||
ParameterValue: aws.String("512"), | ||
}, | ||
{ | ||
ParameterKey: aws.String(taskLogRetentionParamKey), | ||
ParameterValue: aws.String(taskLogRetentionInDays), | ||
}, | ||
{ | ||
ParameterKey: aws.String(taskTaskRoleParamKey), | ||
ParameterValue: aws.String("task-role"), | ||
}, | ||
{ | ||
ParameterKey: aws.String(taskCommandParamKey), | ||
ParameterValue: aws.String("echo hooray"), | ||
}, | ||
} | ||
|
||
taskInput := deploy.CreateTaskResourcesInput{ | ||
Name: "my-task", | ||
CPU: 256, | ||
Memory: 512, | ||
|
||
Image: "7456.dkr.ecr.us-east-2.amazonaws.com/my-task:0.1", | ||
TaskRole: "task-role", | ||
Command: "echo hooray", | ||
} | ||
|
||
task := &taskStackConfig{ | ||
CreateTaskResourcesInput: &taskInput, | ||
} | ||
params, _ := task.Parameters() | ||
require.ElementsMatch(t, expectedParams, params) | ||
} | ||
|
||
func TestTaskStackConfig_StackName(t *testing.T) { | ||
taskInput := deploy.CreateTaskResourcesInput{ | ||
Name: "my-task", | ||
} | ||
|
||
task := &taskStackConfig{ | ||
CreateTaskResourcesInput: &taskInput, | ||
} | ||
got := task.StackName() | ||
require.Equal(t, got, fmt.Sprintf("task-%s", testTaskName)) | ||
} | ||
|
||
func TestTaskStackConfig_Tags(t *testing.T) { | ||
testCases := map[string]struct { | ||
input deploy.CreateTaskResourcesInput | ||
|
||
expectedTags []*cloudformation.Tag | ||
}{ | ||
"with app and env": { | ||
input: deploy.CreateTaskResourcesInput{ | ||
Name: "my-task", | ||
|
||
App: "my-app", | ||
Env: "test", | ||
}, | ||
|
||
expectedTags: []*cloudformation.Tag{ | ||
{ | ||
Key: aws.String(deploy.TaskTagKey), | ||
Value: aws.String("my-task"), | ||
}, | ||
{ | ||
Key: aws.String(deploy.AppTagKey), | ||
Value: aws.String("my-app"), | ||
}, | ||
{ | ||
Key: aws.String(deploy.EnvTagKey), | ||
Value: aws.String("test"), | ||
}, | ||
}, | ||
}, | ||
"input without app or env": { | ||
input: deploy.CreateTaskResourcesInput{ | ||
Name: "my-task", | ||
|
||
Env: config.EnvNameNone, | ||
}, | ||
|
||
expectedTags: []*cloudformation.Tag{ | ||
{ | ||
Key: aws.String(deploy.TaskTagKey), | ||
Value: aws.String("my-task"), | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
|
||
for name, tc := range testCases { | ||
t.Run(name, func(t *testing.T) { | ||
taskStackConfig := &taskStackConfig{ | ||
CreateTaskResourcesInput: &tc.input, | ||
} | ||
tags := taskStackConfig.Tags() | ||
|
||
require.ElementsMatch(t, tc.expectedTags, tags) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// Package deploy holds the structures to deploy infrastructure resources. | ||
// This file defines service deployment resources. | ||
package deploy | ||
|
||
// CreateTaskResourcesInput holds the fields required to create a task stack. | ||
type CreateTaskResourcesInput struct { | ||
Name string | ||
CPU int | ||
Memory int | ||
|
||
Image string | ||
TaskRole string | ||
Command string | ||
EnvVars map[string]string | ||
|
||
App string | ||
Env string | ||
} |
Oops, something went wrong.