From 2c14a170d55b9957b7beabef6a5b57de6b2e6cc5 Mon Sep 17 00:00:00 2001 From: Naphat Sanguansin Date: Wed, 3 Jan 2024 22:11:00 +0700 Subject: [PATCH] update ecs commands to take a service spec file (#33) More futureproof and versatile than trying to pass just the network configuration. --- cmd/pvn-wrapper/awsecs/apply.go | 94 ++++++++++++++++++++++++++------ cmd/pvn-wrapper/awsecs/common.go | 17 ++---- 2 files changed, 81 insertions(+), 30 deletions(-) diff --git a/cmd/pvn-wrapper/awsecs/apply.go b/cmd/pvn-wrapper/awsecs/apply.go index 33871c9..09f1fd8 100644 --- a/cmd/pvn-wrapper/awsecs/apply.go +++ b/cmd/pvn-wrapper/awsecs/apply.go @@ -7,7 +7,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" "github.com/pkg/errors" "github.com/prodvana/pvn-wrapper/cmdutil" @@ -248,6 +247,68 @@ func patchTaskDefinition(taskDefPath, pvnServiceId, pvnServiceVersion string) (s return tempFile.Name(), nil } +func patchServiceSpec(serviceSpecPath string, ecsServiceName, ecsCluster, taskArn string, forUpdate bool) (string, error) { + serviceSpec, err := os.ReadFile(serviceSpecPath) + if err != nil { + return "", errors.Wrap(err, "failed to read service spec file") + } + var untypedDef map[string]interface{} + if err := json.Unmarshal(serviceSpec, &untypedDef); err != nil { + return "", errors.Wrapf(err, "failed to unmarshal service spec file: %s", string(serviceSpec)) + } + + serviceName, hasServiceName := untypedDef["serviceName"] + if hasServiceName { + serviceNameString, ok := serviceName.(string) + if !ok { + return "", errors.Wrapf(err, "unexpected type for serviceName: %T", serviceName) + } + if serviceNameString != ecsServiceName { + return "", errors.Errorf("serviceName in service spec file does not match ECS service name from Prodvana Service config. Got %s, want %s", serviceNameString, ecsServiceName) + } + } + // delete the service field, as it's passed differently on update vs. create and handled on cli + delete(untypedDef, "serviceName") + delete(untypedDef, "service") + cluster, hasCluster := untypedDef["cluster"] + if hasCluster { + clusterString, ok := cluster.(string) + if !ok { + return "", errors.Wrapf(err, "unexpected type for cluster: %T", cluster) + } + if clusterString != ecsCluster { + return "", errors.Errorf("cluster in service spec file does not match ECS cluster name from Prodvana Runtime config. Got %s, want %s", clusterString, ecsCluster) + } + } else { + untypedDef["cluster"] = ecsCluster + } + + untypedDef["taskDefinition"] = taskArn + + if forUpdate { + delete(untypedDef, "launchType") + } + + updatedTaskDef, err := json.Marshal(untypedDef) + if err != nil { + return "", errors.Wrap(err, "failed to marshal") + } + + tempFile, err := os.CreateTemp("", "ecs-task-definition") + if err != nil { + return "", errors.Wrap(err, "failed to make tempfile") + } + + if _, err := tempFile.Write(updatedTaskDef); err != nil { + return "", errors.Wrap(err, "failed to write to tempfile") + } + if err := tempFile.Close(); err != nil { + return "", errors.Wrap(err, "failed to close tempfile") + } + + return tempFile.Name(), nil +} + func serviceMissing(output *describeServicesOutput) bool { if len(output.Failures) > 0 { return output.Failures[0].Reason == "MISSING" @@ -273,26 +334,26 @@ var applyCmd = &cobra.Command{ if err != nil { return err } - networkConfigurations := []string{ - fmt.Sprintf("subnets=[%s]", strings.Join(commonFlags.subnets, ",")), - fmt.Sprintf("securityGroups=[%s]", strings.Join(commonFlags.securityGroups, ",")), - } - if commonFlags.assignPublicIp { - networkConfigurations = append(networkConfigurations, "assignPublicIp=ENABLED") - } commonArgs := []string{ - "--task-definition", - taskArn, "--propagate-tags=TASK_DEFINITION", - "--cluster", + "--cluster", // must be set regardless of serviceSpec, in case updateTaskDefinitionOnly is set commonFlags.ecsClusterName, } if !commonFlags.updateTaskDefinitionOnly { + newServiceSpecPath, err := patchServiceSpec( + commonFlags.serviceSpecFile, + commonFlags.ecsServiceName, + commonFlags.ecsClusterName, + taskArn, + !serviceMissing(serviceOutput), + ) + if err != nil { + return err + } + defer func() { _ = os.Remove(newServiceSpecPath) }() commonArgs = append(commonArgs, - "--desired-count", - fmt.Sprintf("%d", commonFlags.desiredCount), - "--network-configuration", - fmt.Sprintf("awsvpcConfiguration={%s}", strings.Join(networkConfigurations, ",")), + "--cli-input-json", + fmt.Sprintf("file://%s", newServiceSpecPath), ) } if serviceMissing(serviceOutput) { @@ -306,7 +367,6 @@ var applyCmd = &cobra.Command{ "create-service", "--service-name", commonFlags.ecsServiceName, - "--launch-type=FARGATE", }, commonArgs...)...) err := cmdutil.RunCmd(createCmd) if err != nil { @@ -319,7 +379,7 @@ var applyCmd = &cobra.Command{ "ecs", "update-service", "--service", - commonFlags.ecsServiceName, + commonFlags.ecsServiceName, // must be set regardless of serviceSpec, in case updateTaskDefinitionOnly is set }, commonArgs...)...) err := cmdutil.RunCmd(updateCmd) if err != nil { diff --git a/cmd/pvn-wrapper/awsecs/common.go b/cmd/pvn-wrapper/awsecs/common.go index 4ad2eab..04cd181 100644 --- a/cmd/pvn-wrapper/awsecs/common.go +++ b/cmd/pvn-wrapper/awsecs/common.go @@ -7,20 +7,19 @@ import ( var commonFlags = struct { taskDefinitionFile string + serviceSpecFile string ecsClusterName string ecsServiceName string pvnServiceId string pvnServiceVersion string - desiredCount int - subnets []string - securityGroups []string - assignPublicIp bool updateTaskDefinitionOnly bool }{} func registerCommonFlags(cmd *cobra.Command) { - cmd.Flags().StringVar(&commonFlags.taskDefinitionFile, "task-definition-file", "", "Path to task definition file") + cmd.Flags().StringVar(&commonFlags.taskDefinitionFile, "task-definition-file", "", "Path to ECS task definition file") cmdutil.Must(cmd.MarkFlagRequired("task-definition-file")) + cmd.Flags().StringVar(&commonFlags.serviceSpecFile, "service-spec-file", "", "Path to ECS service spec file") + cmdutil.Must(cmd.MarkFlagRequired("service-spec-file")) cmd.Flags().StringVar(&commonFlags.ecsServiceName, "ecs-service-name", "", "Name of ECS service") cmdutil.Must(cmd.MarkFlagRequired("ecs-service-name")) cmd.Flags().StringVar(&commonFlags.ecsClusterName, "ecs-cluster-name", "", "Name of ECS cluster") @@ -29,14 +28,6 @@ func registerCommonFlags(cmd *cobra.Command) { cmdutil.Must(cmd.MarkFlagRequired("pvn-service-id")) cmd.Flags().StringVar(&commonFlags.pvnServiceVersion, "pvn-service-version", "", "Prodvana Service Version") cmdutil.Must(cmd.MarkFlagRequired("pvn-service-version")) - cmd.Flags().IntVar(&commonFlags.desiredCount, "desired-count", 0, "Number of instances desired") - cmdutil.Must(cmd.MarkFlagRequired("desired-count")) - cmd.Flags().StringSliceVar(&commonFlags.subnets, "subnets", nil, "Subnets to use") - cmdutil.Must(cmd.MarkFlagRequired("subnets")) - cmd.Flags().StringSliceVar(&commonFlags.securityGroups, "security-groups", nil, "Security groups to use") - cmdutil.Must(cmd.MarkFlagRequired("security-groups")) - cmd.Flags().BoolVar(&commonFlags.assignPublicIp, "assign-public-ip", false, "Assign public IP") - cmdutil.Must(cmd.MarkFlagRequired("assign-public-ip")) cmd.Flags().BoolVar(&commonFlags.updateTaskDefinitionOnly, "update-task-definition-only", false, "Update task definition only") cmdutil.Must(cmd.MarkFlagRequired("update-task-definition-only")) }