Skip to content

Commit

Permalink
Added resource tagging and ecs managed tagging to the compose commands
Browse files Browse the repository at this point in the history
  • Loading branch information
PettitWesley committed Feb 22, 2019
1 parent 212cc47 commit 6e67622
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 23 deletions.
1 change: 1 addition & 0 deletions ecs-cli/modules/cli/compose/entity/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ type ProjectEntity interface {
TaskDefinitionCache() cache.Cache
SetTaskDefinition(taskDefinition *ecs.TaskDefinition)
EntityType() types.Type
GetTags() ([]*ecs.Tag, error)
}
14 changes: 12 additions & 2 deletions ecs-cli/modules/cli/compose/entity/entity_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,13 @@ func GetOrCreateTaskDefinition(entity ProjectEntity) (*ecs.TaskDefinition, error
"TaskDefinition": taskDefinition,
}).Debug("Finding task definition in cache or creating if needed")

request := createRegisterTaskDefinitionRequest(taskDefinition)
tags, err := entity.GetTags()
if err != nil {
return nil, err
}

// Unfortunately, tags are not part of the task definition, rather they are a field on the Register Task Definition API
request := createRegisterTaskDefinitionRequest(taskDefinition, tags)

resp, err := entity.Context().ECSClient.RegisterTaskDefinitionIfNeeded(request, entity.TaskDefinitionCache())

Expand All @@ -111,7 +117,7 @@ func GetOrCreateTaskDefinition(entity ProjectEntity) (*ecs.TaskDefinition, error
return resp, nil
}

func createRegisterTaskDefinitionRequest(taskDefinition *ecs.TaskDefinition) *ecs.RegisterTaskDefinitionInput {
func createRegisterTaskDefinitionRequest(taskDefinition *ecs.TaskDefinition, tags []*ecs.Tag) *ecs.RegisterTaskDefinitionInput {
// Valid values for network mode are none, host or bridge. If no value
// is passed for network mode, ECS will set it to 'bridge' on most
// platforms, but Windows has different network modes. Passing nil allows ECS
Expand Down Expand Up @@ -139,6 +145,10 @@ func createRegisterTaskDefinitionRequest(taskDefinition *ecs.TaskDefinition) *ec
request.Memory = memory
}

if len(tags) > 0 {
request.Tags = tags
}

return request
}

Expand Down
13 changes: 13 additions & 0 deletions ecs-cli/modules/cli/compose/entity/mock/entity.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions ecs-cli/modules/cli/compose/entity/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type Service struct {
role string
healthCheckGP *int64
serviceRegistries []*ecs.ServiceRegistry
tags []*ecs.Tag
}

const (
Expand Down Expand Up @@ -438,6 +439,22 @@ func (s *Service) EntityType() types.Type {
return types.Service
}

func (s *Service) GetTags() ([]*ecs.Tag, error) {
if s.tags == nil {
tags := make([]*ecs.Tag, 0)
if tagVal := s.Context().CLIContext.String(flags.ResourceTagsFlag); tagVal != "" {
var err error
tags, err = utils.ParseTags(tagVal, tags)
if err != nil {
return nil, err
}
}
s.tags = tags

}
return s.tags, nil
}

// ----------- Commands' helper functions --------

func (s *Service) buildCreateServiceInput(serviceName, taskDefName string) (*ecs.CreateServiceInput, error) {
Expand Down Expand Up @@ -513,6 +530,19 @@ func (s *Service) buildCreateServiceInput(serviceName, taskDefName string) (*ecs
return nil, err
}

tags, err := s.GetTags()
if err != nil {
return nil, err
}
if len(tags) > 0 {
createServiceInput.Tags = tags
createServiceInput.PropagateTags = aws.String(ecs.PropagateTagsTaskDefinition)
}

if !s.Context().CLIContext.Bool(flags.DisableECSManagedTagsFlag) {
createServiceInput.EnableECSManagedTags = aws.Bool(true)
}

return createServiceInput, nil
}

Expand Down
57 changes: 57 additions & 0 deletions ecs-cli/modules/cli/compose/entity/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,63 @@ func TestCreateWithoutSchedulingStrategy(t *testing.T) {
)
}

func TestCreateWithResourceTags(t *testing.T) {
flagSet := flag.NewFlagSet("ecs-cli-up", 0)
flagSet.String(flags.ResourceTagsFlag, "amy=rory,smith=11", "")

expectedTags := []*ecs.Tag{
&ecs.Tag{
Key: aws.String("amy"),
Value: aws.String("rory"),
},
&ecs.Tag{
Key: aws.String("smith"),
Value: aws.String("11"),
},
}

createServiceTest(
t,
flagSet,
&config.CommandConfig{},
&utils.ECSParams{},
func(input *ecs.CreateServiceInput) {
actualTags := input.Tags
assert.ElementsMatch(t, actualTags, expectedTags, "Expected resource tags to match")
},
)
}

func TestCreateWithECSManagedTags(t *testing.T) {
flagSet := flag.NewFlagSet("ecs-cli-up", 0)

createServiceTest(
t,
flagSet,
&config.CommandConfig{},
&utils.ECSParams{},
func(input *ecs.CreateServiceInput) {
// feature is enabled by default in the CLI
assert.True(t, aws.BoolValue(input.EnableECSManagedTags), "Expected ECS Managed Tags to be enabled")
},
)
}

func TestCreateWithECSManagedTagsDisabled(t *testing.T) {
flagSet := flag.NewFlagSet("ecs-cli-up", 0)
flagSet.Bool(flags.DisableECSManagedTagsFlag, true, "")

createServiceTest(
t,
flagSet,
&config.CommandConfig{},
&utils.ECSParams{},
func(input *ecs.CreateServiceInput) {
assert.False(t, aws.BoolValue(input.EnableECSManagedTags), "Expected ECS Managed Tags to be disabled")
},
)
}

//////////////////////////////////////
// Helpers for CreateService tests //
/////////////////////////////////////
Expand Down
29 changes: 29 additions & 0 deletions ecs-cli/modules/cli/compose/entity/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Task struct {
cache cache.Cache
ecsContext *context.ECSContext
timeSleeper *utils.TimeSleeper
tags []*ecs.Tag
}

// NewTask creates an instance of a Task and also sets up a cache for task definition
Expand Down Expand Up @@ -366,6 +367,18 @@ func (t *Task) buildRunTaskInput(taskDefinition string, count int, overrides map
runTaskInput.LaunchType = aws.String(launchType)
}

tags, err := t.GetTags()
if err != nil {
return nil, err
}
if len(tags) > 0 {
runTaskInput.Tags = tags
}

if !t.Context().CLIContext.Bool(flags.DisableECSManagedTagsFlag) {
runTaskInput.EnableECSManagedTags = aws.Bool(true)
}

return runTaskInput, nil
}

Expand Down Expand Up @@ -448,6 +461,22 @@ func (t *Task) up(forceUpdate bool) error {
return nil
}

func (t *Task) GetTags() ([]*ecs.Tag, error) {
if t.tags == nil {
tags := make([]*ecs.Tag, 0)
if tagVal := t.Context().CLIContext.String(flags.ResourceTagsFlag); tagVal != "" {
var err error
tags, err = utils.ParseTags(tagVal, tags)
if err != nil {
return nil, err
}
}
t.tags = tags

}
return t.tags, nil
}

// ---------- naming utils -----------

func getFormattedContainerName(task *ecs.Task, container *ecs.Container) string {
Expand Down
47 changes: 47 additions & 0 deletions ecs-cli/modules/cli/compose/entity/task/task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/context"
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/entity"
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/ecs/mock"
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/commands/flags"
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/config"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ecs"
Expand Down Expand Up @@ -63,6 +64,52 @@ func TestTaskCreate(t *testing.T) {
assert.Equal(t, aws.StringValue(respTaskDef.TaskDefinitionArn), aws.StringValue(task.TaskDefinition().TaskDefinitionArn), "Expected TaskDefArn to match.")
}

func TestTaskCreateWithTags(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

taskDefinition := ecs.TaskDefinition{
Family: aws.String("family"),
ContainerDefinitions: []*ecs.ContainerDefinition{},
Volumes: []*ecs.Volume{},
}
respTaskDef := taskDefinition
respTaskDef.TaskDefinitionArn = aws.String("taskDefinitionArn")

flagSet := flag.NewFlagSet("ecs-cli", 0)
flagSet.String(flags.ResourceTagsFlag, "holmes=watson", "")
cliContext := cli.NewContext(nil, flagSet, nil)

mockEcs := mock_ecs.NewMockECSClient(ctrl)

context := &context.ECSContext{
ECSClient: mockEcs,
CommandConfig: &config.CommandConfig{},
CLIContext: cliContext,
}

expectedTags := []*ecs.Tag{
&ecs.Tag{
Key: aws.String("holmes"),
Value: aws.String("watson"),
},
}

mockEcs.EXPECT().RegisterTaskDefinitionIfNeeded(gomock.Any(), gomock.Any()).Do(func(x, y interface{}) {
// verify input fields
req := x.(*ecs.RegisterTaskDefinitionInput)
assert.Equal(t, aws.StringValue(taskDefinition.Family), aws.StringValue(req.Family), "Expected Task Definition family to match.")
assert.ElementsMatch(t, expectedTags, req.Tags, "Expected resource tags to match")
}).Return(&respTaskDef, nil)

task := NewTask(context)
task.SetTaskDefinition(&taskDefinition)

err := task.Create()
assert.NoError(t, err, "Unexpected error while create")
assert.Equal(t, aws.StringValue(respTaskDef.TaskDefinitionArn), aws.StringValue(task.TaskDefinition().TaskDefinitionArn), "Expected TaskDefArn to match.")
}

func TestTaskInfoFilterLocal(t *testing.T) {
entity.TestInfo(func(context *context.ECSContext) entity.ProjectEntity {
return NewTask(context)
Expand Down
13 changes: 0 additions & 13 deletions ecs-cli/modules/clients/aws/ecs/mock/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 27 additions & 5 deletions ecs-cli/modules/commands/compose/compose_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func createCommand(factory composeFactory.ProjectFactory) cli.Command {
Name: "create",
Usage: "Creates an ECS task definition from your compose file. Note that we do not recommend using plain text environment variables for sensitive information, such as credential data.",
Action: compose.WithProject(factory, compose.ProjectCreate, false),
Flags: flags.AppendFlags(flags.OptionalConfigFlags(), flags.OptionalLaunchTypeFlag(), flags.OptionalCreateLogsFlag()),
Flags: flags.AppendFlags(flags.OptionalConfigFlags(), flags.OptionalLaunchTypeFlag(), flags.OptionalCreateLogsFlag(), resourceTagsFlag(false)),
OnUsageError: flags.UsageErrorFactory("create"),
}
}
Expand All @@ -132,7 +132,7 @@ func upCommand(factory composeFactory.ProjectFactory) cli.Command {
Name: "up",
Usage: "Creates an ECS task definition from your compose file (if it does not already exist) and runs one instance of that task on your cluster (a combination of create and start).",
Action: compose.WithProject(factory, compose.ProjectUp, false),
Flags: flags.AppendFlags(flags.OptionalConfigFlags(), flags.OptionalLaunchTypeFlag(), flags.OptionalCreateLogsFlag(), flags.OptionalForceUpdateFlag()),
Flags: flags.AppendFlags(flags.OptionalConfigFlags(), flags.OptionalLaunchTypeFlag(), flags.OptionalCreateLogsFlag(), flags.OptionalForceUpdateFlag(), resourceTagsFlag(true), disableECSManagedTagsFlag()),
OnUsageError: flags.UsageErrorFactory("up"),
}
}
Expand All @@ -142,7 +142,7 @@ func startCommand(factory composeFactory.ProjectFactory) cli.Command {
Name: "start",
Usage: "Starts a single task from the task definition created from your compose file.",
Action: compose.WithProject(factory, compose.ProjectStart, false),
Flags: flags.AppendFlags(flags.OptionalConfigFlags(), flags.OptionalLaunchTypeFlag(), flags.OptionalCreateLogsFlag()),
Flags: flags.AppendFlags(flags.OptionalConfigFlags(), flags.OptionalLaunchTypeFlag(), flags.OptionalCreateLogsFlag(), resourceTagsFlag(true), disableECSManagedTagsFlag()),
OnUsageError: flags.UsageErrorFactory("start"),
}
}
Expand All @@ -153,7 +153,7 @@ func runCommand(factory composeFactory.ProjectFactory) cli.Command {
Usage: "Starts all containers overriding commands with the supplied one-off commands for the containers.",
ArgsUsage: "[CONTAINER_NAME] [\"COMMAND ...\"] [CONTAINER_NAME] [\"COMMAND ...\"] ...",
Action: compose.WithProject(factory, compose.ProjectRun, false),
Flags: flags.OptionalConfigFlags(),
Flags: flags.AppendFlags(flags.OptionalConfigFlags(), resourceTagsFlag(true), disableECSManagedTagsFlag()),
OnUsageError: flags.UsageErrorFactory("run"),
}
}
Expand All @@ -174,7 +174,29 @@ func scaleCommand(factory composeFactory.ProjectFactory) cli.Command {
Name: "scale",
Usage: "ecs-cli compose scale [count] - scales the number of running tasks to the specified count.",
Action: compose.WithProject(factory, compose.ProjectScale, false),
Flags: flags.AppendFlags(flags.OptionalConfigFlags(), flags.OptionalLaunchTypeFlag()),
Flags: flags.AppendFlags(flags.OptionalConfigFlags(), flags.OptionalLaunchTypeFlag(), resourceTagsFlag(true), disableECSManagedTagsFlag()),
OnUsageError: flags.UsageErrorFactory("scale"),
}
}

func resourceTagsFlag(runTasks bool) []cli.Flag {
usage := "[Optional] Specify resource tags for your Task Definition. Specify tags in the format 'key1=value1,key2=value2,key3=value3'."
if runTasks {
usage = "[Optional] Specify resource tags for your ECS Tasks and Task Definition. Specify tags in the format 'key1=value1,key2=value2,key3=value3'."
}
return []cli.Flag{
cli.StringFlag{
Name: flags.ResourceTagsFlag,
Usage: usage,
},
}
}

func disableECSManagedTagsFlag() []cli.Flag {
return []cli.Flag{
cli.BoolFlag{
Name: flags.DisableECSManagedTagsFlag,
Usage: "[Optional] Disable ECS Managed Tags (A Cluster name tag will not be automatically added to tasks).",
},
}
}
Loading

0 comments on commit 6e67622

Please sign in to comment.