Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c83ac79
add generate-cmd flag to task run
Lou1415926 Apr 21, 2021
3c24575
update condition to prompt for app/env
Lou1415926 Apr 21, 2021
c5f08d5
rearange code and add missing test
Lou1415926 Apr 21, 2021
8ca34f9
fix unit test
Lou1415926 Apr 21, 2021
b7a64aa
Merge branch 'mainline' into generate/task-run
Lou1415926 Apr 22, 2021
b90ada6
address feedback to change var name and flag description
Lou1415926 Apr 22, 2021
fb8c9f7
Merge branch 'generate/task-run' of github.com:Lou1415926/copilot-cli…
Lou1415926 Apr 22, 2021
32d489c
address feedback to change remaining var name
Lou1415926 Apr 22, 2021
5027eb1
refactor generator into ecs pkg
Lou1415926 Apr 29, 2021
a17af88
update task run
Lou1415926 Apr 29, 2021
522fac5
update public function comment
Lou1415926 Apr 29, 2021
c1a5603
move private function to bottom
Lou1415926 Apr 30, 2021
e1a3100
addd file header
Lou1415926 Apr 30, 2021
3b387f3
rename variable
Lou1415926 Apr 30, 2021
191ea9e
bulk address feedback on error messaging
Lou1415926 May 5, 2021
b2ae755
implement unit test for runTaskCommand()
Lou1415926 May 5, 2021
6d139c3
address feedback to improve flag message, error message, and comment
Lou1415926 May 5, 2021
f6dde4a
address feedback to improve logging message
Lou1415926 May 5, 2021
d68b6f0
fix
Lou1415926 May 5, 2021
b7c5417
abstract away clistringer
Lou1415926 May 5, 2021
c61de75
Merge branch 'mainline' into generate/task-run
Lou1415926 May 5, 2021
4699160
Merge branch 'mainline' into generate/task-run
mergify[bot] May 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,6 @@ gen-mocks: tools
${GOBIN}/mockgen -package=mocks -destination=./internal/pkg/list/mocks/mock_list.go -source=./internal/pkg/list/list.go
${GOBIN}/mockgen -package=mocks -destination=./internal/pkg/initialize/mocks/mock_workload.go -source=./internal/pkg/initialize/workload.go
${GOBIN}/mockgen -package=mocks -destination=./internal/pkg/ecs/mocks/mock_ecs.go -source=./internal/pkg/ecs/ecs.go
${GOBIN}/mockgen -package=mocks -destination=./internal/pkg/generator/mocks/mock_ecs_service.go -source=./internal/pkg/generator/ecs_service.go
${GOBIN}/mockgen -package=mocks -destination=./internal/pkg/generator/mocks/mock_service.go -source=./internal/pkg/generator/service.go
${GOBIN}/mockgen -package=mocks -destination=./internal/pkg/ecs/mocks/mock_run_task_request.go -source=./internal/pkg/ecs/run_task_request.go


37 changes: 21 additions & 16 deletions internal/pkg/cli/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,22 @@ const (
storageRDSInitialDBFlag = "initial-db"
storageRDSParameterGroupFlag = "parameter-group"

taskGroupNameFlag = "task-group-name"
countFlag = "count"
cpuFlag = "cpu"
memoryFlag = "memory"
imageFlag = "image"
taskRoleFlag = "task-role"
executionRoleFlag = "execution-role"
clusterFlag = "cluster"
subnetsFlag = "subnets"
securityGroupsFlag = "security-groups"
envVarsFlag = "env-vars"
secretsFlag = "secrets"
commandFlag = "command"
entrypointFlag = "entrypoint"
taskDefaultFlag = "default"
taskGroupNameFlag = "task-group-name"
countFlag = "count"
cpuFlag = "cpu"
memoryFlag = "memory"
imageFlag = "image"
taskRoleFlag = "task-role"
executionRoleFlag = "execution-role"
clusterFlag = "cluster"
subnetsFlag = "subnets"
securityGroupsFlag = "security-groups"
envVarsFlag = "env-vars"
secretsFlag = "secrets"
commandFlag = "command"
entrypointFlag = "entrypoint"
taskDefaultFlag = "default"
generateCommandFlag = "generate-cmd"

vpcIDFlag = "import-vpc-id"
publicSubnetsFlag = "import-public-subnets"
Expand Down Expand Up @@ -235,7 +236,11 @@ Must be either "MySQL" or "PostgreSQL".`
taskGroupFlagDescription = `Optional. The group name of the task.
Tasks with the same group name share the same set of resources.
(default directory name)`
taskImageTagFlagDescription = `Optional. The container image tag in addition to "latest".`
taskImageTagFlagDescription = `Optional. The container image tag in addition to "latest".`
generateCommandFlagDescription = `Optional. Generate a command with a pre-filled value for each flag.
To use it for an ECS service, specify --generate-cmd <cluster name>/<service name>.
Alternatively, if the service is created with Copilot, specify --generate-cmd <application>/<environment>/<service>.
Cannot be specified with any other flags.`

vpcIDFlagDescription = "Optional. Use an existing VPC ID."
publicSubnetsFlagDescription = "Optional. Use existing public subnet IDs."
Expand Down
4 changes: 4 additions & 0 deletions internal/pkg/cli/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,3 +569,7 @@ type codestar interface {
type publicIPGetter interface {
PublicIP(ENI string) (string, error)
}

type cliStringer interface {
CLIString() string
}
105 changes: 101 additions & 4 deletions internal/pkg/cli/task_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"path/filepath"
"strings"

"github.com/aws/aws-sdk-go/aws/arn"

awscloudformation "github.com/aws/copilot-cli/internal/pkg/aws/cloudformation"
"github.com/aws/copilot-cli/internal/pkg/describe"
"github.com/aws/copilot-cli/internal/pkg/logging"
Expand Down Expand Up @@ -78,8 +80,8 @@ type runTaskVars struct {

taskRole string
executionRole string
cluster string

cluster string
subnets []string
securityGroups []string
env string
Expand All @@ -92,12 +94,14 @@ type runTaskVars struct {
entrypoint string
resourceTags map[string]string

follow bool
follow bool
generateCommandTarget string
}

type runTaskOpts struct {
runTaskVars
isDockerfileSet bool
nFlag int

// Interfaces to interact with dependencies.
fs afero.Fs
Expand All @@ -116,11 +120,15 @@ type runTaskOpts struct {
sess *session.Session
targetEnvironment *config.Environment

// Configurer methods.
// Configurer functions.
configureRuntimeOpts func() error
configureRepository func() error
// NOTE: configureEventsWriter is only called when tailing logs (i.e. --follow is specified)
configureEventsWriter func(tasks []*task.Task)

// Functions to generate a task run command.
runTaskRequestFromECSService func(client ecs.ECSServiceDescriber, cluster, service string) (*ecs.RunTaskRequest, error)
runTaskRequestFromService func(client ecs.ServiceDescriber, app, env, svc string) (*ecs.RunTaskRequest, error)
}

func newTaskRunOpts(vars runTaskVars) (*runTaskOpts, error) {
Expand Down Expand Up @@ -163,6 +171,9 @@ func newTaskRunOpts(vars runTaskVars) (*runTaskOpts, error) {
opts.configureEventsWriter = func(tasks []*task.Task) {
opts.eventsWriter = logging.NewTaskClient(opts.sess, opts.groupName, tasks)
}

opts.runTaskRequestFromECSService = ecs.RunTaskRequestFromECSService
opts.runTaskRequestFromService = ecs.RunTaskRequestFromService
return &opts, nil
}

Expand Down Expand Up @@ -247,6 +258,12 @@ func (o *runTaskOpts) configureSessAndEnv() error {

// Validate returns an error if the flag values passed by the user are invalid.
func (o *runTaskOpts) Validate() error {
if o.generateCommandTarget != "" {
if o.nFlag >= 2 {
return errors.New("cannot specify `--generate-cmd` with any other flag")
}
}

if o.count <= 0 {
return errNumNotPositive
}
Expand Down Expand Up @@ -383,6 +400,9 @@ func (o *runTaskOpts) validateFlagsWithSecurityGroups() error {

// Ask prompts the user for any required or important fields that are not provided.
func (o *runTaskOpts) Ask() error {
if o.generateCommandTarget != "" {
return nil
}
if o.shouldPromptForAppEnv() {
if err := o.askAppName(); err != nil {
return err
Expand All @@ -398,7 +418,7 @@ func (o *runTaskOpts) shouldPromptForAppEnv() bool {
// NOTE: if security groups are specified but subnets are not, then we use the default subnets with the
// specified security groups.
useDefault := o.useDefaultSubnetsAndCluster || (o.securityGroups != nil && o.subnets == nil && o.cluster == "")
useConfig := o.subnets != nil
useConfig := o.subnets != nil || o.cluster != ""

// if user hasn't specified that they want to use the default subnets, and that they didn't provide specific subnets
// that they want to use, then we prompt.
Expand All @@ -407,6 +427,10 @@ func (o *runTaskOpts) shouldPromptForAppEnv() bool {

// Execute deploys and runs the task.
func (o *runTaskOpts) Execute() error {
if o.generateCommandTarget != "" {
return o.generateCommand()
}

if o.groupName == "" {
dir, err := os.Getwd()
if err != nil {
Expand Down Expand Up @@ -481,6 +505,76 @@ func (o *runTaskOpts) Execute() error {
return nil
}

func (o *runTaskOpts) generateCommand() error {
command, err := o.runTaskCommand()
if err != nil {
return err
}
log.Infoln(command.CLIString())
return nil
}

func (o *runTaskOpts) runTaskCommand() (cliStringer, error) {
var cmd cliStringer
sess, err := sessions.NewProvider().Default()
if err != nil {
return nil, fmt.Errorf("get default session: %s", err)
}

if arn.IsARN(o.generateCommandTarget) {
clusterName, serviceName, err := o.parseARN()
if err != nil {
return nil, err
}
return o.runTaskCommandFromECSService(sess, clusterName, serviceName)
}

parts := strings.Split(o.generateCommandTarget, "/")
switch len(parts) {
case 2:
clusterName, serviceName := parts[0], parts[1]
cmd, err = o.runTaskCommandFromECSService(sess, clusterName, serviceName)
if err != nil {
return nil, err
}
case 3:
appName, envName, serviceName := parts[0], parts[1], parts[2]
cmd, err = o.runTaskRequestFromService(ecs.New(sess), appName, envName, serviceName)
if err != nil {
return nil, fmt.Errorf("generate task run command from service %s of application %s deployed in environment %s: %w", serviceName, appName, envName, err)
}
default:
return nil, errors.New("invalid input to --generate-cmd: must be of one the form <cluster>/<service> or <app>/<env>/<workload>")
}

return cmd, nil
}

func (o *runTaskOpts) parseARN() (string, string, error) {
svcARN := awsecs.ServiceArn(o.generateCommandTarget)
clusterName, err := svcARN.ClusterName()
if err != nil {
return "", "", fmt.Errorf("extract cluster name from arn %s: %w", svcARN, err)
}
serviceName, err := svcARN.ServiceName()
if err != nil {
return "", "", fmt.Errorf("extract service name from arn %s: %w", svcARN, err)
}
return clusterName, serviceName, nil
}

func (o *runTaskOpts) runTaskCommandFromECSService(sess *session.Session, clusterName, serviceName string) (cliStringer, error) {
cmd, err := o.runTaskRequestFromECSService(awsecs.New(sess), clusterName, serviceName)
if err != nil {
var errMultipleContainers *ecs.ErrMultipleContainersInTaskDef
if errors.As(err, &errMultipleContainers) {
log.Errorln("`copilot task run` does not support running more than one container.")
}
return nil, fmt.Errorf("generate task run command from ECS service %s: %w", clusterName+"/"+serviceName, err)
}
return cmd, nil
}

func (o *runTaskOpts) displayLogStream() error {
if err := o.eventsWriter.WriteEventsUntilStopped(); err != nil {
return fmt.Errorf("write events: %w", err)
Expand Down Expand Up @@ -687,6 +781,7 @@ Run a task with a command.
/code $ copilot task run --command "python migrate-script.py"`,
RunE: runCmdE(func(cmd *cobra.Command, args []string) error {
opts, err := newTaskRunOpts(vars)
opts.nFlag = cmd.Flags().NFlag()
Comment thread
Lou1415926 marked this conversation as resolved.
if err != nil {
return err
}
Expand Down Expand Up @@ -737,5 +832,7 @@ Run a task with a command.
cmd.Flags().StringToStringVar(&vars.resourceTags, resourceTagsFlag, nil, resourceTagsFlagDescription)

cmd.Flags().BoolVar(&vars.follow, followFlag, false, followFlagDescription)
cmd.Flags().StringVar(&vars.generateCommandTarget, generateCommandFlag, "", generateCommandFlagDescription)

return cmd
}
Loading