From ea4661b0778d3432f5d15d8160fab98ded54254b Mon Sep 17 00:00:00 2001 From: Gaurav Mann Date: Mon, 25 Sep 2023 17:35:17 +0530 Subject: [PATCH 1/6] OBS-393 Simplify user interface for ECS installation --- cmd/internal/ecs/README.md | 73 +++++ cmd/internal/ecs/add.go | 521 ++++-------------------------------- cmd/internal/ecs/aws_api.go | 207 +------------- cmd/internal/ecs/ecs.go | 49 ++-- 4 files changed, 154 insertions(+), 696 deletions(-) create mode 100644 cmd/internal/ecs/README.md diff --git a/cmd/internal/ecs/README.md b/cmd/internal/ecs/README.md new file mode 100644 index 00000000..0cc80ea9 --- /dev/null +++ b/cmd/internal/ecs/README.md @@ -0,0 +1,73 @@ +### Amazon Elastic Container Service (ECS) + +### Introduction + +- The Postman Live Collection Agent(LCA) attaches as a side car to the specified service +- Postman collection is populated with endpoints observed from the traffic arrving on your service + +- Both EC2 and Fargate capactiy providers are supported + +### Pre-requistites +- AWS credentials stored at `~/.aws/credentials` +- Your aws credentails **must have** these AWS permissions [Setup ECS Permissions](#setup-aws-ecs-permissions) +- ECS service must have public internet access. [Docs: Ensure Internet Access](#ensure-internet-access) + +### Usage + +``` +POSTMAN_API_KEY= postman-lc-agent ecs add \ +--collection \ +--region \ +--cluster \ +--service +``` + +**NOTE**: Updating your service with newly modified task definition might take time, please check AWS console for the progress. + +#### Additional Configuration + +- See help menu for further configuration +``` +postman-lc-agent ecs --help +``` + + + +### Uninstall +- Update your ECS service to old revision of task definition. + +### Setup AWS ECS Permissions + +- Attach the following policy to your aws profile + +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ecs:UpdateService", + "ecs:RegisterTaskDefinition", + "ecs:DescribeServices", + "ecs:TagResource", + "ecs:DescribeTaskDefinition", + "ecs:DescribeClusters" + ], + "Resource": "*" + } + ] +} +``` +- **Instead** of the above policy [AmazonECS_FullAccess](https://docs.aws.amazon.com/AmazonECS/latest/userguide/security-iam-awsmanpol.html#security-iam-awsmanpol-AmazonECS_FullAccess) can also be used to ensure easy authoraization. + +### Ensure internet access +#### Fargate tasks +- When using a public subnet, you can assign a public IP address to the task ENI. +- When using a private subnet, the subnet can have a NAT gateway attached. +- AWS Docs: See [Task networking for tasks hosted on Fargate](https://docs.aws.amazon.com/AmazonECS/latest/userguide/fargate-task-networking.html). + +#### EC2 tasks +- Tasks must be launched in private subnets with NAT gateway. +- For more information, see [Task networking for tasks that are hosted on Amazon EC2 instances](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html) + diff --git a/cmd/internal/ecs/add.go b/cmd/internal/ecs/add.go index a9eb5a77..1ebff144 100644 --- a/cmd/internal/ecs/add.go +++ b/cmd/internal/ecs/add.go @@ -3,12 +3,9 @@ package ecs import ( "context" "fmt" - "os" - "sort" "strings" "time" - "github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2/terminal" "github.com/akitasoftware/akita-cli/cfg" "github.com/akitasoftware/akita-cli/cmd/internal/cmderr" @@ -19,7 +16,6 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ecs" "github.com/aws/aws-sdk-go-v2/service/ecs/types" "github.com/pkg/errors" - "golang.org/x/term" ) // Helper function for reporting telemetry @@ -61,7 +57,6 @@ type AddWorkflow struct { awsProfile string awsConfig aws.Config awsRegion string - awsRegions []string ecsClient *ecs.Client @@ -138,22 +133,22 @@ func RunAddWorkflow() error { // State machine ASCII art: // -// init ---> fillFromFlags --> modifyTask +// init // | // V // --> getProfile // | | // | V -// |-> getRegion --> findClusterAndRegion -// | | | -// | V | -// -- getCluster | -// | ^ | | -// | | V | -// |- getTask <------------ +// |-> getRegion +// | | +// | V +// -- getCluster +// | ^ | +// | | V +// |- getService // | ^ | // | | V -// |-getService +// |-getTask // | // | getSecret // | [disabled] @@ -174,64 +169,24 @@ func RunAddWorkflow() error { // waitForRestart // // -// Backtracking occurs when there are permission errors, an empty result, or -// the user asks to go back a step. -// -// Initial state: check if running interactively, if so then start -// with collecting AWS profile.` +// Initial state func initState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], err error) { reportStep("Start Add to ECS") - // Check if running interactively. - // TODO: I didn't see a way to do this from go-survey directly. - if !term.IsTerminal(int(os.Stdin.Fd())) { - return fillFromFlags(wf) - } - return awf_next(getProfileState) } -// Ask the user to specify a profile; "" is fine to use the default profile. -// TODO: it seems very difficult to present a list (which is what I was trying -// to do orginally) because the SDK doesn't provide an API to do that, and -// its config file parser is internal. +// Load credentials for awsProfile, if not specified "default" profile is used func getProfileState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], err error) { reportStep("Get AWS Profile") - if awsProfileFlag != "" { - wf.awsProfile = awsProfileFlag - if err = wf.createConfig(); err != nil { - if errors.Is(err, NoSuchProfileError) { - printer.Errorf("The AWS credentials file does not have profile %q. The error from the AWS library is shown below.\n") - } - return awf_error(errors.Wrap(err, "Error loading AWS credentials")) - } - - return awf_next(getRegionState) - } - - err = survey.AskOne( - &survey.Input{ - Message: "Which of your AWS profiles should be used to configure ECS?", - Help: "Enter the name of the AWS profile you use for configuring ECS, or leave blank to try the default profile. Akita needs this information to identify which AWS credentials to use.", - // Use the existing value as the default in case we repeat this step - Default: wf.awsProfile, - }, - &wf.awsProfile, - ) - if err != nil { - return awf_error(err) - } - + wf.awsProfile = awsProfileFlag if err = wf.createConfig(); err != nil { if errors.Is(err, NoSuchProfileError) { - printer.Errorf("Could not find AWS credentials for profile %q. Please try again or hit Ctrl+C to exit.\n", wf.awsProfile) - wf.awsProfile = "default" - return awf_next(getProfileState) + printer.Errorf("The AWS credentials file does not have profile %q. The error from the AWS library is shown below.\n") } - printer.Errorf("Could not load the AWS config file. The error from the AWS library is shown below. Please send this log message to observability-support@postman.com for assistance.\n", err) - return awf_error(errors.Wrapf(err, "Error loading AWS credentials")) + return awf_error(errors.Wrap(err, "Error loading AWS credentials")) } printer.Infof("Successfully loaded AWS credentials for profile %q\n", wf.awsProfile) @@ -239,285 +194,64 @@ func getProfileState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowS return awf_next(getRegionState) } -const findAllClustersOption = "Search all regions." -const goBackOption = "Return to previous choice." - // Ask the user to select a region. func getRegionState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], err error) { reportStep("Get AWS Region") - if awsRegionFlag != "" { - wf.awsRegion = awsRegionFlag - wf.createClient(wf.awsRegion) - return awf_next(getClusterState) - } - - if wf.awsRegions == nil { - wf.awsRegions = wf.listAWSRegions() - } - - err = survey.AskOne( - &survey.Select{ - Message: "In which AWS region is your ECS cluster?", - Help: "Select the AWS region where you run the ECS cluster with the task you want to modify. You can select 'Search all regions' and we will search for all ECS clusters you can access.", - Options: append([]string{findAllClustersOption}, wf.awsRegions...), - Default: wf.awsConfig.Region, - }, - &wf.awsRegion, - ) - if err != nil { - return awf_error(err) - } - - if wf.awsRegion == findAllClustersOption { - return awf_next(findClusterAndRegionState) - } - + wf.awsRegion = awsRegionFlag wf.createClient(wf.awsRegion) return awf_next(getClusterState) } -// Search all regions for ECS clusters. The reason this is not the default -// is because it is rather slow. -func findClusterAndRegionState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], err error) { - reportStep("Get ECS Cluster and Region") - printer.Infof("Searching all regions for ECS clusters. This may take a minute to complete.\n") - - arnToRegion := make(map[arn]string, 0) - arnToName := make(map[arn]string, 0) - for _, region := range wf.awsRegions { - wf.createClient(region) - clusters, err := wf.listECSClusters() - if err != nil { - printer.Warningf("Skipping region %q, error: %v\n", region, err) - continue - } - if len(clusters) > 0 { - printer.Infof("Found %d clusters in region %q.\n", len(clusters), region) - for a, n := range clusters { - arnToRegion[a] = region - arnToName[a] = n - } - } - } - - if len(arnToRegion) == 0 { - printer.Errorf("Could not find any ECS clusters in any region. Please select a different profile or hit Ctrl+C to exit.\n") - return awf_next(getProfileState) - } - - choices := make([]string, 0, len(arnToName)) - for c, _ := range arnToName { - choices = append(choices, string(c)) - } - sort.Strings(choices) - - var clusterAnswer string - err = survey.AskOne( - &survey.Select{ - Message: "In which cluster does your application run?", - Help: "Select ECS cluster with the task you want to modify.", - Options: choices, - Description: func(value string, _ int) string { - name := arnToName[arn(value)] - if name == "" { - return "" - } - return name + " in " + arnToRegion[arn(value)] - }, - }, - &clusterAnswer, - ) - if err != nil { - return awf_error(err) - } - - wf.ecsClusterARN = arn(clusterAnswer) - wf.ecsCluster = arnToName[wf.ecsClusterARN] - wf.awsRegion = arnToRegion[wf.ecsClusterARN] - wf.createClient(wf.awsRegion) - - return awf_next(getTaskState) -} - -func (wf *AddWorkflow) loadClusterFromFlag() (nextState optionals.Optional[AddWorkflowState], err error) { - if strings.HasPrefix(ecsClusterFlag, "arn:") { - clusterName, err := wf.getClusterName(arn(ecsClusterFlag)) - if err != nil { - if errors.Is(err, NoSuchClusterError) { - return awf_error(fmt.Errorf("Could not find cluster with ARN %q in region %s", ecsClusterFlag, wf.awsRegion)) - } - return awf_error(errors.Wrap(err, "Error accessing cluster")) - } - wf.ecsClusterARN = arn(ecsClusterFlag) - wf.ecsCluster = clusterName - return awf_next(getTaskState) - } else { - clusters, listErr := wf.listECSClusters() - if listErr != nil { - return awf_error(errors.Wrap(err, "Error listing clusters")) - } - for a, name := range clusters { - if name == ecsClusterFlag { - printer.Infof("Found cluster %q matching name %q.\n", a, name) - wf.ecsClusterARN = a - wf.ecsCluster = name - return awf_next(getTaskState) - } - } - return awf_error(fmt.Errorf("No cluster found with name %q", ecsClusterFlag)) - } -} - -// Find all ECS clusters in the selected region. +// Get cluster state func getClusterState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], err error) { reportStep("Get ECS Cluster") - - if ecsClusterFlag != "" { - return wf.loadClusterFromFlag() - } - - clusters, listErr := wf.listECSClusters() - if listErr != nil { - var uoe UnauthorizedOperationError - if errors.As(listErr, &uoe) { - // Permissions error, pick a different profile or region (or quit.) - printer.Errorf("The provided credentials do not have permission to perform %s on ECS in region %s.\n", - uoe.OperationName, wf.awsConfig.Region) - printer.Infof("Please pick a different profile or region, or assign this permission in AWS IAM.\n") - return awf_next(getProfileState) + clusterName, err := wf.getClusterName(arn(ecsClusterFlag)) + if err != nil { + if errors.Is(err, NoSuchClusterError) { + return awf_error(fmt.Errorf("could not find cluster with ARN %q in region %s", ecsClusterFlag, wf.awsRegion)) } - printer.Errorf("Could not list ECS clusters: %v\n", listErr) - return awf_error(errors.New("Error while listing ECS clusters; try using the --cluster flag instead.")) - } - - if len(clusters) == 0 { - printer.Errorf("Could not find any ECS clusters in this region. Please select a different one or hit Ctrl+C to exit.\n") - return awf_next(getRegionState) + return awf_error(errors.Wrap(err, "Error accessing cluster")) } + wf.ecsClusterARN = arn(ecsClusterFlag) + wf.ecsCluster = clusterName + printer.Infof("Successfully fetched ECS cluster with ARN %q\n", wf.ecsClusterARN) + return awf_next(getServiceState) +} - printer.Infof("Found %d clusters in region %q.\n", len(clusters), wf.awsRegion) +// Find ECS service using the ARN. +func getServiceState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], err error) { + reportStep("Get ECS Service") - choices := make([]string, 0, len(clusters)) - for c, _ := range clusters { - choices = append(choices, string(c)) - } - sort.Strings(choices) - choices = append(choices, goBackOption) - - var clusterAnswer string - err = survey.AskOne( - &survey.Select{ - Message: "In which cluster does your application run?", - Help: "Select ECS cluster with the task definition you want to modify.", - Options: choices, - Description: func(value string, _ int) string { - return clusters[arn(value)] - }, - }, - &clusterAnswer, - ) + service, err := wf.getService(arn(ecsServiceFlag)) if err != nil { - return awf_error(err) + return awf_error(errors.Wrap(err, "Error accessing service")) } - if clusterAnswer == goBackOption { - return awf_next(getRegionState) - } - wf.ecsClusterARN = arn(clusterAnswer) - wf.ecsCluster = clusters[wf.ecsClusterARN] - + wf.ecsService = aws.ToString(service.ServiceName) + wf.ecsServiceARN = arn(ecsServiceFlag) + wf.ecsTaskDefinitionARN = arn(*service.TaskDefinition) + printer.Infof("Successfully fetched ECS service with ARN %q\n", wf.ecsServiceARN) return awf_next(getTaskState) } -func (wf *AddWorkflow) loadTaskFromFlag() (nextState optionals.Optional[AddWorkflowState], err error) { - // This call will work even if the flag is an ARN or a family:revision string. - // TODO: should we check for those? Or just allow it? - output, tags, describeErr := wf.getLatestECSTaskDefinition(ecsTaskDefinitionFlag) - if describeErr != nil { - var uoe UnauthorizedOperationError - if errors.As(describeErr, &uoe) { - printer.Errorf("The provided credentials do not have permission to perform %s on the task definition %q.\n", - uoe.OperationName, wf.ecsTaskDefinitionFamily) - } - return awf_error(errors.Wrap(describeErr, "Error loading task definition")) - } - wf.ecsTaskDefinition = output - wf.ecsTaskDefinitionFamily = aws.ToString(output.Family) - wf.ecsTaskDefinitionARN = arn(aws.ToString(output.TaskDefinitionArn)) - wf.ecsTaskDefinitionTags = tags - return awf_next(getServiceState) -} - -// Find all task definitions. These are not technically tied to a cluster, but they are tied to a region. -// We could move this to immediately after picking the region, but it has to be after the combined -// region/cluster choice, so it's somewhat more consistent to do it here? +// Describe task definition, using ecsTaskDefinitionArn fetched from ECS describeService +// in previous step func getTaskState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], err error) { reportStep("Get ECS Task Definition") - if ecsTaskDefinitionFlag != "" { - return wf.loadTaskFromFlag() - } - - tasks, listErr := wf.listECSTaskDefinitionFamilies() - if listErr != nil { - var uoe UnauthorizedOperationError - if errors.As(listErr, &uoe) { - // Permissions error, go all the way back to profile selection. - printer.Errorf("The provided credentials do not have permission to perform %s in the region %s.\n", - uoe.OperationName, wf.awsRegion) - printer.Infof("Please choose a different profile or region, or assign this permission in AWS IAM.\n") - return awf_next(getProfileState) - } - printer.Errorf("Could not list ECS task definitions: %v\n", listErr) - return awf_error(errors.New("Error while listing ECS task definitions; try using the --task flag instead.")) - } - - if len(tasks) == 0 { - printer.Errorf("Could not find any ECS tasks in this cluster. Please select a different one or hit Ctrl+C to exit.\n") - return awf_next(getClusterState) - } - - printer.Infof("Found %d task definitions.\n", len(tasks)) - - sort.Strings(tasks) - tasks = append(tasks, goBackOption) - - var taskAnswer string - err = survey.AskOne( - &survey.Select{ - Message: "Which task should be monitored?", - Help: "Select the ECS task definition to modify. We will add the Postman Live Collections Agent as a sidecar to the task.", - Options: tasks, - }, - &taskAnswer, - ) - if err != nil { - return awf_error(err) - } - - if taskAnswer == goBackOption { - return awf_next(getClusterState) - } - wf.ecsTaskDefinitionFamily = taskAnswer - - // Load the task definition (if we don't have permission, retry.) - output, tags, describeErr := wf.getLatestECSTaskDefinition(wf.ecsTaskDefinitionFamily) + output, tags, describeErr := wf.getECSTaskDefinition(arn(wf.ecsTaskDefinitionARN)) if describeErr != nil { var uoe UnauthorizedOperationError if errors.As(describeErr, &uoe) { printer.Errorf("The provided credentials do not have permission to perform %s on the task definition %q.\n", uoe.OperationName, wf.ecsTaskDefinitionFamily) - printer.Infof("Please choose a different task definition, or assign this permission in AWS IAM.\n") - return awf_next(getTaskState) } - printer.Errorf("Could not load ECS task definition: %v\n", describeErr) - return awf_error(errors.New("Error while loading ECS task definition; please contact observability-support@postman.com for assistance.")) + return awf_error(errors.Wrap(describeErr, "Error loading task definition")) } - wf.ecsTaskDefinition = output + wf.ecsTaskDefinitionFamily = aws.ToString(output.Family) wf.ecsTaskDefinitionARN = arn(aws.ToString(output.TaskDefinitionArn)) wf.ecsTaskDefinitionTags = tags - // Check that the task definition was not already modified. for _, tag := range tags { switch aws.ToString(tag.Key) { @@ -525,7 +259,7 @@ func getTaskState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowStat printer.Errorf("The selected task definition already has the tag \"%s=%s\", indicating it was previously modified.\n", aws.ToString(tag.Key), aws.ToString(tag.Value)) printer.Infof("Please select a different task definition, or remove this tag.\n") - return awf_next(getTaskState) + return awf_next(confirmState) } } @@ -534,12 +268,13 @@ func getTaskState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowStat image := aws.ToString(container.Image) if matchesImage(image, postmanECRImage) || matchesImage(image, akitaECRImage) || matchesImage(image, akitaDockerImage) { printer.Errorf("The selected task definition already has the image %q; postman-lc-agent is already installed.\n", image) - printer.Infof("Please select a different task definition, or hit Ctrl+C to exit.\n") - return awf_next(getTaskState) + printer.Infof("Please provide a different service or delete the task definition\n %q", wf.ecsTaskDefinitionARN) + return awf_done() } } - return awf_next(getServiceState) + printer.Infof("Successfully fetched ECS task with ARN %q\n", wf.ecsTaskDefinitionARN) + return awf_next(confirmState) } func matchesImage(imageName, baseName string) bool { @@ -547,93 +282,6 @@ func matchesImage(imageName, baseName string) bool { return imageTokens[0] == baseName } -func (wf *AddWorkflow) loadServiceFromFlag() (nextState optionals.Optional[AddWorkflowState], err error) { - if strings.HasPrefix(ecsServiceFlag, "arn:") { - service, err := wf.getServiceWithMatchingTask(arn(ecsServiceFlag)) - if err != nil { - return awf_error(errors.Wrap(err, "Error accessing service")) - } - wf.ecsService = aws.ToString(service.ServiceName) - wf.ecsServiceARN = arn(ecsServiceFlag) - return awf_next(confirmState) - } - - services, listErr := wf.listECSServices() - if listErr != nil { - return awf_error(errors.Wrap(err, "Error listing services")) - } - for a, name := range services { - if name == ecsServiceFlag { - printer.Infof("Found service %q matching name %q.\n", a, name) - wf.ecsServiceARN = a - wf.ecsService = name - return awf_next(confirmState) - } - } - return awf_error(fmt.Errorf("No service found with name %q that uses task definition %q", ecsServiceFlag, wf.ecsTaskDefinitionFamily)) -} - -// Find all services in the cluster that match the task definition. -func getServiceState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], err error) { - reportStep("Get ECS Service") - - if ecsServiceFlag != "" { - return wf.loadServiceFromFlag() - } - - services, listErr := wf.listECSServices() - if listErr != nil { - var uoe UnauthorizedOperationError - if errors.As(listErr, &uoe) { - printer.Errorf("The provided credentials do not have permission to perform %s in the cluster %q.\n", - uoe.OperationName, wf.ecsCluster) - printer.Infof("Please choose a different cluster, or assign this permission in AWS IAM.\n") - return awf_next(getClusterState) - } - printer.Errorf("Could not list ECS services: %v\n", listErr) - return awf_error(errors.New("Error while listing ECS services; try using the --service flag instead.")) - } - - if len(services) == 0 { - printer.Errorf("Could not find any ECS services in cluster %q that use task definition %q. Please select a different task definition or hit Ctrl+C to exit.\n", - wf.ecsCluster, wf.ecsTaskDefinitionFamily) - return awf_next(getTaskState) - } - - printer.Infof("Found %d services in cluster %q with task definition %q.\n", len(services), wf.ecsCluster, wf.ecsTaskDefinitionFamily) - - choices := make([]string, 0, len(services)) - for c, _ := range services { - choices = append(choices, string(c)) - } - sort.Strings(choices) - choices = append(choices, goBackOption) - // TODO: allow skipping this step? - - var serviceAnswer string - err = survey.AskOne( - &survey.Select{ - Message: "Which service should be updated to use the modified task definition?", - Help: "Select the ECS service that will be updated with the modified task definition, so it can be monitored.", - Options: choices, - Description: func(value string, _ int) string { - return services[arn(value)] - }, - }, - &serviceAnswer, - ) - if err != nil { - return awf_error(err) - } - if serviceAnswer == goBackOption { - return awf_next(getTaskState) - } - wf.ecsServiceARN = arn(serviceAnswer) - wf.ecsService = services[wf.ecsServiceARN] - - return awf_next(confirmState) -} - func getSecretState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], err error) { reportStep("Get Akita Secrets") @@ -668,7 +316,7 @@ func (wf *AddWorkflow) showPlannedChanges() { defaultKeySecretName, wf.awsRegion) } } - printer.Infof("Create a new version %d of task definition %q which includes the Postman Live Collections Agent as a sidecar.\n", + printer.Infof("Create a new version of task definition %q which includes the Postman Live Collections Agent as a sidecar.\n", wf.ecsTaskDefinition.Revision+1, wf.ecsTaskDefinitionFamily) printer.Infof("Update service %q in cluster %q to the new task definition.\n", wf.ecsService, wf.ecsCluster) @@ -685,79 +333,6 @@ func confirmState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowStat return awf_done() } - proceed := false - prompt := &survey.Confirm{ - Message: "Proceed with the changes?", - } - survey.AskOne(prompt, &proceed) - - if !proceed { - // TODO: let the user back up instead? - // (I realized one problem with this is if the last step had a flag, they are just - // stucke anyway.) - printer.Infof("No changes applied; exiting.\n") - reportStep("Changes Rejected") - return awf_done() - } - - return awf_next(modifyTaskState) -} - -// Run non-interactively and attempt to fill in all information from -// command-line flags. -func fillFromFlags(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], err error) { - reportStep("Fill ECS Info From Flags") - - // Try to use default profile, "", if none specified - if err = wf.createConfig(); err != nil { - // TODO: understand error cases - printer.Errorf("Error from AWS SDK: %v\n", err) - return awf_error(fmt.Errorf("Could not find AWS credentials for profile %q", awsProfileFlag)) - } - - // Default region is OK only if there there is a .config file with one. - // TODO: how do we check this? - // it looks like "an AWS region is required" happens on the first call - wf.createClientWithDefaultRegion() - - // The rest of these are easy because they're mandatory. - if ecsClusterFlag == "" { - return awf_error(UsageErrorf("Must specify an ECS cluster to operate on.")) - } - _, err = wf.loadClusterFromFlag() - if err != nil { - return awf_error(err) - } - - if ecsTaskDefinitionFlag == "" { - return awf_error(UsageErrorf("Must specify an ECS task definition to modify.")) - } - _, err = wf.loadTaskFromFlag() - if err != nil { - return awf_error(err) - } - - // TODO: could we support adding to a task but not restarting a service? - if ecsServiceFlag == "" { - return awf_error(UsageErrorf("Must specify an ECS service to modify.")) - } - _, err = wf.loadServiceFromFlag() - if err != nil { - return awf_error(err) - } - - wf.akitaSecrets, err = wf.checkAkitaSecrets() - if err != nil { - return awf_error(err) - } - - wf.showPlannedChanges() - - if dryRunFlag { - printer.Infof("Not making any changes due to -dry-run flag.\n") - return awf_done() - } - return awf_next(modifyTaskState) } @@ -871,6 +446,8 @@ func modifyTaskState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowS printer.Infof("Please start over with a different profile, or add this permission in IAM.\n") return awf_error(errors.New("Failed to update the ECS task definition due to insufficient permissions.")) } + printer.Errorf("Could not register an ECS task definition. The error from the AWS library is shown below. Please send this log message to observability-support@postman.com for assistance.\n", err) + return awf_error(errors.Wrap(err, "Error registering task definition")) } printer.Infof("Registered task definition %q revision %d.\n", aws.ToString(output.TaskDefinition.Family), @@ -912,6 +489,8 @@ func updateServiceState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkfl wf.ecsServiceARN, uoe.OperationName) return awf_error(errors.New("Failed to update the ECS service due to insufficient permissions.")) } + printer.Errorf("Could not update the ECS service %q. The error from the AWS library is shown below. Please send this log message to observability-support@postman.com for assistance.\n", wf.ecsServiceARN, err) + return awf_error(errors.Wrapf(err, "Error updating ECS service %q", wf.ecsServiceARN)) } printer.Infof("Updated service %q with new version of task definition.\n", wf.ecsService) diff --git a/cmd/internal/ecs/aws_api.go b/cmd/internal/ecs/aws_api.go index b5d1b273..d6c276d2 100644 --- a/cmd/internal/ecs/aws_api.go +++ b/cmd/internal/ecs/aws_api.go @@ -161,77 +161,6 @@ var publicAWSRegions = []string{ "us-west-2", } -// List all regions in alphabetical order. On error fall back to the precanned list. -func (wf *AddWorkflow) listAWSRegions() (result []string) { - defer func() { sort.Strings(result) }() - - // Need a region to list the regions, unfortunately. - if wf.awsConfig.Region == "" { - wf.awsConfig.Region = "us-east-1" - } - - ec2Client := ec2.NewFromConfig(wf.awsConfig) - - out, err := ec2Client.DescribeRegions(wf.ctx, &ec2.DescribeRegionsInput{ - AllRegions: aws.Bool(false), - }) - if err != nil { - telemetry.Error("AWS EC2 DescribeRegions", err) - - if _, ok := isUnauthorized(err); ok { - printer.Warningf("Failed to list available regions, because you are not authorized to make the DescribeRegions call in region %v. Falling back to a precompiled list.\n", wf.awsConfig.Region) - return publicAWSRegions - } - - printer.Warningf("Failed to list available regions from AWS; falling back to a precompiled list. Error was: %v\n", err) - return publicAWSRegions - } - - if len(out.Regions) == 0 { - // Could a user have all regions disabled? - printer.Warningf("List of available regions from AWS was empty. Falling back to a precompiled list.\n", err) - return publicAWSRegions - } - - ret := make([]string, 0, len(out.Regions)) - for _, r := range out.Regions { - if r.RegionName != nil { - ret = append(ret, *r.RegionName) - } - } - return ret -} - -// List all clusters for the current region, by arn and user-assigned name -func (wf *AddWorkflow) listECSClusters() (map[arn]string, error) { - input := &ecs.ListClustersInput{} - return ListAWSObjectsByName[ - *ecs.ListClustersOutput, - *ecs.DescribeClustersInput, - *ecs.DescribeClustersOutput, - types.Cluster]( - wf.ctx, - "Clusters", - ecs.NewListClustersPaginator(wf.ecsClient, input), - func(output *ecs.ListClustersOutput) []arn { - return stringsToArns(output.ClusterArns) - }, - func(arns []arn) *ecs.DescribeClustersInput { - return &ecs.DescribeClustersInput{ - Clusters: arnsToStrings(arns), - } - }, - func(ctx context.Context, input *ecs.DescribeClustersInput) (*ecs.DescribeClustersOutput, error) { - return wf.ecsClient.DescribeClusters(ctx, input) - }, - func(output *ecs.DescribeClustersOutput) []types.Cluster { - return output.Clusters - }, - func(t types.Cluster) (arn, string) { - return arn(aws.ToString(t.ClusterArn)), aws.ToString(t.ClusterName) - }, - ) -} // Verify that a cluster exists with the given ARN. // Returns its name, or else NoSuchClusterError if search is empty. @@ -276,30 +205,10 @@ func stringsToArns(arns []string) []arn { return ret } -// List all tasks definition families, by name -func (wf *AddWorkflow) listECSTaskDefinitionFamilies() ([]string, error) { - // TODO: Lists only active tasks, should we permit inactive ones too? - input := &ecs.ListTaskDefinitionFamiliesInput{ - Status: types.TaskDefinitionFamilyStatusActive, - } - - families := make([]string, 0) - paginator := ecs.NewListTaskDefinitionFamiliesPaginator(wf.ecsClient, input) - for paginator.HasMorePages() { - output, err := paginator.NextPage(wf.ctx) - if err != nil { - telemetry.Error("AWS ECS ListTaskDefinitionFamilies", err) - return nil, wrapUnauthorized(err) - } - families = append(families, output.Families...) - } - return families, nil -} - -// Look up the most recent version of a task definition -func (wf *AddWorkflow) getLatestECSTaskDefinition(family string) (*types.TaskDefinition, []types.Tag, error) { +// Look up ECS task definition using ARN +func (wf *AddWorkflow) getECSTaskDefinition(ecsTaskDefinitionARN arn) (*types.TaskDefinition, []types.Tag, error) { input := &ecs.DescribeTaskDefinitionInput{ - TaskDefinition: aws.String(family), + TaskDefinition: ecsTaskDefinitionARN.Use(), } output, err := wf.ecsClient.DescribeTaskDefinition(wf.ctx, input) @@ -310,85 +219,7 @@ func (wf *AddWorkflow) getLatestECSTaskDefinition(family string) (*types.TaskDef return output.TaskDefinition, output.Tags, nil } -// List all services for the current cluster, by arn and user-assigned name -// Filter to only those using the task family we identified! -func (wf *AddWorkflow) listECSServices() (map[arn]string, error) { - // Lists both Fargate and ECS services - input := &ecs.ListServicesInput{ - Cluster: wf.ecsClusterARN.Use(), - } - - // Cache of ARN to family - arnToFamily := map[arn]string{ - wf.ecsTaskDefinitionARN: wf.ecsTaskDefinitionFamily, - } - - // Check whether the given Task ARN has Family equal to that of the chosen task definition. - taskInFamily := func(serviceARN arn, taskARN arn) bool { - if family, cached := arnToFamily[taskARN]; cached { - return family == wf.ecsTaskDefinitionFamily - } - input := &ecs.DescribeTaskDefinitionInput{ - TaskDefinition: taskARN.Use(), - } - output, err := wf.ecsClient.DescribeTaskDefinition(wf.ctx, input) - if err != nil { - telemetry.Error("AWS ECS DescribeTaskDefinition", err) - if uoe, unauth := isUnauthorized(err); unauth { - printer.Warningf("Skipping service %q because the provided credentials are unauthorized for %s on %q.\n", - serviceARN, uoe.OperationName, taskARN) - } else { - printer.Warningf("Skipping service %q because of an error checking its task definition: %v\n", serviceARN, err) - } - return false - } - family := aws.ToString(output.TaskDefinition.Family) - arnToFamily[taskARN] = family - return family == wf.ecsTaskDefinitionFamily - } - - // Include only those services sharing the correct family. - filterFunc := func(output *ecs.DescribeServicesOutput) []types.Service { - filtered := make([]types.Service, 0) - for _, s := range output.Services { - // s.TaskDefinition is an ARN, but we want to match by family. - // We could try parsing the ARN? But I think the correct route is - // to look up the task definition, if unknown. - if taskInFamily(arn(aws.ToString(s.ServiceArn)), arn(aws.ToString(s.TaskDefinition))) { - filtered = append(filtered, s) - } - } - return filtered - } - - return ListAWSObjectsByName[ - *ecs.ListServicesOutput, - *ecs.DescribeServicesInput, - *ecs.DescribeServicesOutput, - types.Service]( - wf.ctx, - "Services", - ecs.NewListServicesPaginator(wf.ecsClient, input), - func(output *ecs.ListServicesOutput) []arn { - return stringsToArns(output.ServiceArns) - }, - func(arns []arn) *ecs.DescribeServicesInput { - return &ecs.DescribeServicesInput{ - Cluster: wf.ecsClusterARN.Use(), - Services: arnsToStrings(arns), - } - }, - func(ctx context.Context, input *ecs.DescribeServicesInput) (*ecs.DescribeServicesOutput, error) { - return wf.ecsClient.DescribeServices(ctx, input) - }, - filterFunc, - func(t types.Service) (arn, string) { - return arn(aws.ToString(t.ServiceArn)), aws.ToString(t.ServiceName) - }, - ) -} - -// Look up a service and check that its task definition matches. +// Look up a ECS service using ARN func (wf *AddWorkflow) getService(serviceARN arn) (*types.Service, error) { input := &ecs.DescribeServicesInput{ Services: []string{string(serviceARN)}, @@ -401,37 +232,11 @@ func (wf *AddWorkflow) getService(serviceARN arn) (*types.Service, error) { return nil, wrapUnauthorizedFor(err, serviceARN) } if len(output.Services) == 0 { - return nil, fmt.Errorf("No service with ARN %q", serviceARN) + return nil, fmt.Errorf("no service with ARN %q", serviceARN) } return &output.Services[0], nil } -func (wf *AddWorkflow) getServiceWithMatchingTask(serviceARN arn) (*types.Service, error) { - svc, err := wf.getService(serviceARN) - if err != nil { - return nil, err - } - - taskARN := arn(aws.ToString(svc.TaskDefinition)) - taskInput := &ecs.DescribeTaskDefinitionInput{ - TaskDefinition: taskARN.Use(), - } - taskOutput, err := wf.ecsClient.DescribeTaskDefinition(wf.ctx, taskInput) - if err != nil { - telemetry.Error("AWS ECS DescribeTaskDefinition", err) - return nil, wrapUnauthorizedFor(err, taskARN) - } - - family := aws.ToString(taskOutput.TaskDefinition.Family) - if family != wf.ecsTaskDefinitionFamily { - printer.Warningf("Service %q has task definition %q, which does not match family %q.", - serviceARN, taskARN, wf.ecsTaskDefinitionFamily) - return nil, fmt.Errorf("Mismatch between service and task definition; please choose a different task or service.") - } - - return svc, nil -} - var noDeploymentFound = errors.New("No deployment found") // Returns the deployment of the given service that matches the ECS task @@ -450,7 +255,7 @@ func (wf *AddWorkflow) GetDeploymentMatchingTask(serviceARN arn) (string, types. } } - return "", types.Deployment{}, errors.New("No deployment found") + return "", types.Deployment{}, errors.New("no deployment found") } func (wf *AddWorkflow) GetDeploymentByID(serviceARN arn, deploymentID string) (types.Deployment, error) { diff --git a/cmd/internal/ecs/ecs.go b/cmd/internal/ecs/ecs.go index e5c67ca3..418664bc 100644 --- a/cmd/internal/ecs/ecs.go +++ b/cmd/internal/ecs/ecs.go @@ -2,6 +2,7 @@ package ecs import ( "fmt" + "strings" "github.com/akitasoftware/akita-cli/cmd/internal/cmderr" "github.com/akitasoftware/akita-cli/rest" @@ -12,20 +13,16 @@ import ( ) var ( - // Mandatory flag: Postman collection id - collectionId string - - // Any of these will be interactively prompted if not given on the command line. - // On the other hand, to run non-interactively then all of them *must* be given. - awsProfileFlag string - awsRegionFlag string - ecsClusterFlag string - ecsServiceFlag string - ecsTaskDefinitionFlag string - + // Mandatory flags + collectionId string + awsRegionFlag string + ecsClusterFlag string + ecsServiceFlag string + + // Optional Flags + awsProfileFlag string // Location of credentials file. awsCredentialsFlag string - // Print out the steps that would be taken, but do not do them dryRunFlag bool ) @@ -63,16 +60,14 @@ var RemoveFromECSCmd = &cobra.Command{ func init() { // TODO: add the ability to specify the credentials directly instead of via an AWS profile? Cmd.PersistentFlags().StringVar(&collectionId, "collection", "", "Your Postman collection ID") - Cmd.PersistentFlags().StringVar(&awsProfileFlag, "profile", "", "Which of your AWS profiles to use to access ECS.") + Cmd.MarkPersistentFlagRequired("collection") + Cmd.PersistentFlags().StringVar(&awsProfileFlag, "profile", "default", "Which of your AWS profiles to use to access ECS.") Cmd.PersistentFlags().StringVar(&awsRegionFlag, "region", "", "The AWS region in which your ECS cluster resides.") - Cmd.PersistentFlags().StringVar(&ecsClusterFlag, "cluster", "", "The name or ARN of your ECS cluster.") - Cmd.PersistentFlags().StringVar(&ecsServiceFlag, "service", "", "The name or ARN of your ECS service.") - Cmd.PersistentFlags().StringVar( - &ecsTaskDefinitionFlag, - "task", - "", - "The name of your ECS task definition to modify.", - ) + Cmd.MarkPersistentFlagRequired("region") + Cmd.PersistentFlags().StringVar(&ecsClusterFlag, "cluster", "", "The ARN of your ECS cluster.") + Cmd.MarkPersistentFlagRequired("cluster") + Cmd.PersistentFlags().StringVar(&ecsServiceFlag, "service", "", "The ARN of your ECS service.") + Cmd.MarkPersistentFlagRequired("service") Cmd.PersistentFlags().BoolVar( &dryRunFlag, "dry-run", @@ -95,10 +90,16 @@ func addAgentToECS(cmd *cobra.Command, args []string) error { return err } - // Check collecton Id's existence - if collectionId == "" { - return errors.New("Must specify the ID of your collection with the --collection flag.") + // Check if cluster and service flags specify ARN + if !strings.HasPrefix(ecsClusterFlag, "arn:") { + return errors.New("Please copy the full ARN of your ECS cluster from AWS console") } + + // Check if cluster and service flags specify ARN + if !strings.HasPrefix(ecsServiceFlag, "arn:") { + return errors.New("Please copy the full ARN of your ECS service from AWS console") + } + frontClient := rest.NewFrontClient(rest.Domain, telemetry.GetClientID()) _, err = util.GetOrCreateServiceIDByPostmanCollectionID(frontClient, collectionId) if err != nil { From 4900614e20ae08712dcb508761fe9b18f664ac69 Mon Sep 17 00:00:00 2001 From: Gaurav Mann Date: Mon, 25 Sep 2023 17:49:24 +0530 Subject: [PATCH 2/6] Remove unused imports --- cmd/internal/ecs/aws_api.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cmd/internal/ecs/aws_api.go b/cmd/internal/ecs/aws_api.go index d6c276d2..66509991 100644 --- a/cmd/internal/ecs/aws_api.go +++ b/cmd/internal/ecs/aws_api.go @@ -1,16 +1,12 @@ package ecs import ( - "context" "errors" "fmt" - "sort" - "github.com/akitasoftware/akita-cli/printer" "github.com/akitasoftware/akita-cli/telemetry" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ecs" "github.com/aws/aws-sdk-go-v2/service/ecs/types" smithy "github.com/aws/smithy-go" From f3d1db2a6fb8ac30dc226e80fd47327ba9dcea04 Mon Sep 17 00:00:00 2001 From: Nermina <136389852+nerminamiller-postman@users.noreply.github.com> Date: Mon, 25 Sep 2023 09:55:43 -0400 Subject: [PATCH 3/6] [DOC] Readme editorial update (#238) Small editorial updates --- cmd/internal/ecs/README.md | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/cmd/internal/ecs/README.md b/cmd/internal/ecs/README.md index 0cc80ea9..202754ae 100644 --- a/cmd/internal/ecs/README.md +++ b/cmd/internal/ecs/README.md @@ -2,15 +2,15 @@ ### Introduction -- The Postman Live Collection Agent(LCA) attaches as a side car to the specified service -- Postman collection is populated with endpoints observed from the traffic arrving on your service +- The Postman Live Collection Agent (LCA) attaches as a sidecar to the specified service. +- The Postman collection is populated with endpoints observed from the traffic arriving at your service. +- Both EC2 and Fargate capacity providers are supported. -- Both EC2 and Fargate capactiy providers are supported +### Prerequisites -### Pre-requistites - AWS credentials stored at `~/.aws/credentials` -- Your aws credentails **must have** these AWS permissions [Setup ECS Permissions](#setup-aws-ecs-permissions) -- ECS service must have public internet access. [Docs: Ensure Internet Access](#ensure-internet-access) +- Your AWS credentails **must have** these AWS permissions [Setup ECS Permissions](#setup-aws-ecs-permissions) +- ECS service must have public internet access. For more information, see [Ensure Internet Access](#ensure-internet-access), ### Usage @@ -22,23 +22,23 @@ POSTMAN_API_KEY= postman-lc-agent ecs add \ --service ``` -**NOTE**: Updating your service with newly modified task definition might take time, please check AWS console for the progress. +**NOTE**: Updating your service with the newly modified task definition might take time. Please check the AWS console for progress. -#### Additional Configuration +#### Additional configuration + +- See the help menu for further configuration. -- See help menu for further configuration ``` postman-lc-agent ecs --help ``` - - ### Uninstall -- Update your ECS service to old revision of task definition. -### Setup AWS ECS Permissions +- Update your ECS service to the old revision of the task definition. + +### Set Up AWS ECS permissions -- Attach the following policy to your aws profile +- Attach the following policy to your AWS profile. ``` { @@ -59,15 +59,18 @@ postman-lc-agent ecs --help ] } ``` -- **Instead** of the above policy [AmazonECS_FullAccess](https://docs.aws.amazon.com/AmazonECS/latest/userguide/security-iam-awsmanpol.html#security-iam-awsmanpol-AmazonECS_FullAccess) can also be used to ensure easy authoraization. +- **Instead** of the above policy, [AmazonECS_FullAccess](https://docs.aws.amazon.com/AmazonECS/latest/userguide/security-iam-awsmanpol.html#security-iam-awsmanpol-AmazonECS_FullAccess) can also be used to ensure easy authoraization. ### Ensure internet access + #### Fargate tasks + - When using a public subnet, you can assign a public IP address to the task ENI. - When using a private subnet, the subnet can have a NAT gateway attached. - AWS Docs: See [Task networking for tasks hosted on Fargate](https://docs.aws.amazon.com/AmazonECS/latest/userguide/fargate-task-networking.html). #### EC2 tasks + - Tasks must be launched in private subnets with NAT gateway. - For more information, see [Task networking for tasks that are hosted on Amazon EC2 instances](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html) From e7ec98f47f8edfaea56f94a47ec74269c1a751a3 Mon Sep 17 00:00:00 2001 From: Nermina <136389852+nerminamiller-postman@users.noreply.github.com> Date: Tue, 26 Sep 2023 04:46:57 -0400 Subject: [PATCH 4/6] Update README.md (#240) --- cmd/internal/ecs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/internal/ecs/README.md b/cmd/internal/ecs/README.md index 202754ae..5614c1d6 100644 --- a/cmd/internal/ecs/README.md +++ b/cmd/internal/ecs/README.md @@ -36,7 +36,7 @@ postman-lc-agent ecs --help - Update your ECS service to the old revision of the task definition. -### Set Up AWS ECS permissions +### Set up AWS ECS permissions - Attach the following policy to your AWS profile. @@ -59,7 +59,7 @@ postman-lc-agent ecs --help ] } ``` -- **Instead** of the above policy, [AmazonECS_FullAccess](https://docs.aws.amazon.com/AmazonECS/latest/userguide/security-iam-awsmanpol.html#security-iam-awsmanpol-AmazonECS_FullAccess) can also be used to ensure easy authoraization. +- **Instead** of the above policy, [AmazonECS_FullAccess](https://docs.aws.amazon.com/AmazonECS/latest/userguide/security-iam-awsmanpol.html#security-iam-awsmanpol-AmazonECS_FullAccess) can also be used to ensure easy authorization. ### Ensure internet access From 9de70b0a9394116b3111d0b36f37f44dbe64227a Mon Sep 17 00:00:00 2001 From: Nermina <136389852+nerminamiller-postman@users.noreply.github.com> Date: Tue, 26 Sep 2023 04:47:16 -0400 Subject: [PATCH 5/6] Update README.md (#239) Fixes a small typo --- cmd/internal/ecs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/internal/ecs/README.md b/cmd/internal/ecs/README.md index 5614c1d6..3f160d5d 100644 --- a/cmd/internal/ecs/README.md +++ b/cmd/internal/ecs/README.md @@ -9,7 +9,7 @@ ### Prerequisites - AWS credentials stored at `~/.aws/credentials` -- Your AWS credentails **must have** these AWS permissions [Setup ECS Permissions](#setup-aws-ecs-permissions) +- Your AWS credentials **must have** these AWS permissions [Setup ECS Permissions](#setup-aws-ecs-permissions) - ECS service must have public internet access. For more information, see [Ensure Internet Access](#ensure-internet-access), ### Usage From a9430437ba6121004fdcf9e2dc0e8b10aaab5e7b Mon Sep 17 00:00:00 2001 From: Gaurav Mann Date: Tue, 26 Sep 2023 14:24:36 +0530 Subject: [PATCH 6/6] use AWS_PROFILE env var, minor fixes --- cmd/internal/ecs/add.go | 13 ++++++++++--- cmd/internal/ecs/ecs.go | 6 +++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cmd/internal/ecs/add.go b/cmd/internal/ecs/add.go index d1e398c2..211792f6 100644 --- a/cmd/internal/ecs/add.go +++ b/cmd/internal/ecs/add.go @@ -3,6 +3,7 @@ package ecs import ( "context" "fmt" + "os" "strings" "time" @@ -181,7 +182,13 @@ func initState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], func getProfileState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], err error) { reportStep("Get AWS Profile") - wf.awsProfile = awsProfileFlag + if awsProfileFlag != "" { + wf.awsProfile = awsProfileFlag + } else if val, ok := os.LookupEnv("AWS_PROFILE"); ok { + wf.awsProfile = val + } else { + wf.awsProfile = "default" + } if err = wf.createConfig(); err != nil { if errors.Is(err, NoSuchProfileError) { printer.Errorf("The AWS credentials file does not have profile %q. The error from the AWS library is shown below.\n") @@ -268,7 +275,7 @@ func getTaskState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowStat image := aws.ToString(container.Image) if matchesImage(image, postmanECRImage) || matchesImage(image, akitaECRImage) || matchesImage(image, akitaDockerImage) { printer.Errorf("The selected task definition already has the image %q; postman-lc-agent is already installed.\n", image) - printer.Infof("Please provide a different service or delete the task definition\n %q", wf.ecsTaskDefinitionARN) + printer.Infof("Please provide a different service or update the service to an older version of task definition without postman-lc-agent %q \n ", wf.ecsTaskDefinitionARN) return awf_done() } } @@ -317,7 +324,7 @@ func (wf *AddWorkflow) showPlannedChanges() { } } printer.Infof("Create a new version of task definition %q which includes the Postman Live Collections Agent as a sidecar.\n", - wf.ecsTaskDefinition.Revision+1, wf.ecsTaskDefinitionFamily) + wf.ecsTaskDefinitionFamily) printer.Infof("Update service %q in cluster %q to the new task definition.\n", wf.ecsService, wf.ecsCluster) } diff --git a/cmd/internal/ecs/ecs.go b/cmd/internal/ecs/ecs.go index 418664bc..cbe739de 100644 --- a/cmd/internal/ecs/ecs.go +++ b/cmd/internal/ecs/ecs.go @@ -61,7 +61,7 @@ func init() { // TODO: add the ability to specify the credentials directly instead of via an AWS profile? Cmd.PersistentFlags().StringVar(&collectionId, "collection", "", "Your Postman collection ID") Cmd.MarkPersistentFlagRequired("collection") - Cmd.PersistentFlags().StringVar(&awsProfileFlag, "profile", "default", "Which of your AWS profiles to use to access ECS.") + Cmd.PersistentFlags().StringVar(&awsProfileFlag, "profile", "", "Which of your AWS profiles to use to access ECS.") Cmd.PersistentFlags().StringVar(&awsRegionFlag, "region", "", "The AWS region in which your ECS cluster resides.") Cmd.MarkPersistentFlagRequired("region") Cmd.PersistentFlags().StringVar(&ecsClusterFlag, "cluster", "", "The ARN of your ECS cluster.") @@ -92,12 +92,12 @@ func addAgentToECS(cmd *cobra.Command, args []string) error { // Check if cluster and service flags specify ARN if !strings.HasPrefix(ecsClusterFlag, "arn:") { - return errors.New("Please copy the full ARN of your ECS cluster from AWS console") + return errors.New("Please copy the full ARN of your ECS cluster from the AWS console") } // Check if cluster and service flags specify ARN if !strings.HasPrefix(ecsServiceFlag, "arn:") { - return errors.New("Please copy the full ARN of your ECS service from AWS console") + return errors.New("Please copy the full ARN of your ECS service from the AWS console") } frontClient := rest.NewFrontClient(rest.Domain, telemetry.GetClientID())