Skip to content

Commit

Permalink
update ecs commands to take a service spec file (#33)
Browse files Browse the repository at this point in the history
More futureproof and versatile than trying to pass just the network configuration.
  • Loading branch information
naphatkrit authored Jan 3, 2024
1 parent 84dec82 commit 2c14a17
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 30 deletions.
94 changes: 77 additions & 17 deletions cmd/pvn-wrapper/awsecs/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/pkg/errors"
"github.com/prodvana/pvn-wrapper/cmdutil"
Expand Down Expand Up @@ -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"
Expand All @@ -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) {
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
17 changes: 4 additions & 13 deletions cmd/pvn-wrapper/awsecs/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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"))
}

0 comments on commit 2c14a17

Please sign in to comment.