From 49c065b917d50ff9aec4e0f86862038a2c3ccb51 Mon Sep 17 00:00:00 2001 From: FUJIWARA Shunichiro Date: Wed, 8 Nov 2017 17:49:57 +0900 Subject: [PATCH] Use aws-sdk-go instead of aws-cli! --- README.md | 27 +++++++-- cmd/ecspresso/main.go | 6 +- config.go | 1 + ecspresso.go | 131 ++++++++++++++++-------------------------- service.go | 29 ++-------- 5 files changed, 80 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 5634037e..e25d7a2a 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,14 @@ ecspresso is a deployment tool for Amazon ECS. # Usage ``` -$ ecspresso +$ ecspresso -h +Usage of ecspresso: -cluster string ECS cluster name(required) + -config string + Config file + -region string + aws region -service string ECS service name(required) -task-definition string @@ -29,10 +34,24 @@ ecspresso works as below. - Update a service definition. - Wait a service stable. +### Configuration file + +YAML format. + +```yaml +region: ap-northeast-1 +cluster: default +service: myService +task_definition: myTask.json +timeout: 5m +``` + +Keys are equal to comand line options. + ## Example ``` -$ ecspresso -cluster default -service myService -task-definition app.json +$ ecspresso -region ap-northeast-1 -cluster default -service myService -task-definition myTask.json 2017/11/07 09:07:12 myService/default Starting ecspresso 2017/11/07 09:07:12 myService/default Creating a new task definition by app.json 2017/11/07 09:07:12 myService/default Registering a new task definition... @@ -42,10 +61,6 @@ $ ecspresso -cluster default -service myService -task-definition app.json 2017/11/07 09:10:02 myService/default Service is stable now. Completed! ``` -# Requirements - -- aws-cli - # LICENCE MIT diff --git a/cmd/ecspresso/main.go b/cmd/ecspresso/main.go index ef45e9ab..0e9463a1 100644 --- a/cmd/ecspresso/main.go +++ b/cmd/ecspresso/main.go @@ -16,10 +16,11 @@ func main() { func _main() int { var ( - conf, service, cluster, path string - timeout int + region, conf, service, cluster, path string + timeout int ) + flag.StringVar(®ion, "region", os.Getenv("AWS_REGION"), "aws region") flag.StringVar(&conf, "config", "", "Config file") flag.StringVar(&service, "service", "", "ECS service name(required)") flag.StringVar(&cluster, "cluster", "", "ECS cluster name(required)") @@ -28,6 +29,7 @@ func _main() int { flag.Parse() c := ecspresso.Config{ + Region: region, Service: service, Cluster: cluster, TaskDefinitionPath: path, diff --git a/config.go b/config.go index 05f9f09e..000981ce 100644 --- a/config.go +++ b/config.go @@ -6,6 +6,7 @@ import ( ) type Config struct { + Region string `yaml:"region"` Service string `yaml:"service"` Cluster string `yaml:"cluster"` TaskDefinitionPath string `yaml:"task_definition"` diff --git a/ecspresso.go b/ecspresso.go index dfbf144e..6746e17b 100644 --- a/ecspresso.go +++ b/ecspresso.go @@ -2,60 +2,43 @@ package ecspresso import ( "context" - "encoding/json" "fmt" "log" - "os" - "os/exec" "time" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ecs" "github.com/kayac/go-config" - "github.com/pkg/errors" ) -type TaskDefinitionContainer struct { - TaskDefinition TaskDefinition `yaml:"taskDefinition" json:"taskDefinition"` -} - -type TaskDefinition struct { - ContainerDefinitions []map[string]interface{} `yaml:"containerDefinitions" json:"containerDefinitions"` - Family string `yaml:"family" json:"family"` - NetworkMode string `yaml:"networkMode" json:"networkMode"` - PlacementConstraints []map[string]string `yaml:"placementConstraints" json:"placementConstraints"` - RequiresAttributes []map[string]string `yaml:"requiresAttributes" json:"requiresAttributes"` - Revision int `yaml:"revision" json:"revision"` - Status string `yaml:"status" json:"status"` - TaskRoleArn string `yaml:"taskRoleArn" json:"taskRoleArn"` - Volumes []map[string]interface{} `yaml:"volumes" yaml:"json"` -} - -func (t *TaskDefinition) Name() string { - return fmt.Sprintf("%s:%d", t.Family, t.Revision) +func taskDefinitionName(t *ecs.TaskDefinition) string { + return fmt.Sprintf("%s:%d", *t.Family, *t.Revision) } type App struct { + ecs *ecs.ECS Service string Cluster string - TaskDefinition *TaskDefinition - Registered *TaskDefinition + TaskDefinition *ecs.TaskDefinition + Registered *ecs.TaskDefinition +} + +func (d *App) DescribeServicesInput() *ecs.DescribeServicesInput { + return &ecs.DescribeServicesInput{ + Cluster: aws.String(d.Cluster), + Services: []*string{aws.String(d.Service)}, + } } func (d *App) DescribeServiceDeployments(ctx context.Context) error { - b, err := awsECS(ctx, "describe-services", - "--service", d.Service, - "--cluster", d.Cluster, - ) + out, err := d.ecs.DescribeServicesWithContext(ctx, d.DescribeServicesInput()) if err != nil { - d.Log(string(b)) - return err - } - var sc ServiceContainer - if err := json.Unmarshal(b, &sc); err != nil { return err } - if len(sc.Services) > 0 { - for _, dep := range sc.Services[0].Deployments { - d.Log(dep.String()) + if len(out.Services) > 0 { + for _, dep := range out.Services[0].Deployments { + d.Log(formatDeployment(dep)) } } return nil @@ -69,9 +52,13 @@ func Run(conf *Config) error { defer cancel() } + sess := session.Must(session.NewSession( + &aws.Config{Region: aws.String(conf.Region)}, + )) d := &App{ Service: conf.Service, Cluster: conf.Cluster, + ecs: ecs.New(sess), } d.Log("Starting ecspresso") @@ -123,19 +110,19 @@ func (d *App) WaitServiceStable(ctx context.Context) error { } }() - _, err := awsECS(ctx, "wait", "services-stable", - "--service", d.Service, - "--cluster", d.Cluster, - ) - return err + return d.ecs.WaitUntilServicesStableWithContext(ctx, d.DescribeServicesInput()) } func (d *App) UpdateService(ctx context.Context) error { d.Log("Updating service...") - _, err := awsECS(ctx, "update-service", - "--service", d.Service, - "--cluster", d.Cluster, - "--task-definition", d.Registered.Name(), + + _, err := d.ecs.UpdateServiceWithContext( + ctx, + &ecs.UpdateServiceInput{ + Service: aws.String(d.Service), + Cluster: aws.String(d.Cluster), + TaskDefinition: d.Registered.TaskDefinitionArn, + }, ) return err } @@ -143,55 +130,33 @@ func (d *App) UpdateService(ctx context.Context) error { func (d *App) RegisterTaskDefinition(ctx context.Context) error { d.Log("Registering a new task definition...") - b, err := awsECS(ctx, "register-task-definition", - "--output", "json", - "--family", d.TaskDefinition.Family, - "--task-role-arn", d.TaskDefinition.TaskRoleArn, - "--network-mode", d.TaskDefinition.NetworkMode, - "--volumes", toJSON(d.TaskDefinition.Volumes), - "--placement-constraints", toJSON(d.TaskDefinition.PlacementConstraints), - "--container-definitions", toJSON(d.TaskDefinition.ContainerDefinitions), + out, err := d.ecs.RegisterTaskDefinitionWithContext( + ctx, + &ecs.RegisterTaskDefinitionInput{ + Family: d.TaskDefinition.Family, + TaskRoleArn: d.TaskDefinition.TaskRoleArn, + NetworkMode: d.TaskDefinition.NetworkMode, + Volumes: d.TaskDefinition.Volumes, + PlacementConstraints: d.TaskDefinition.PlacementConstraints, + ContainerDefinitions: d.TaskDefinition.ContainerDefinitions, + }, ) if err != nil { return err } - var res TaskDefinitionContainer - if err := json.Unmarshal(b, &res); err != nil { - return errors.Wrap(err, "register-task-definition parse response failed") - } - d.Log("Task definition is registered", res.TaskDefinition.Name()) - d.Registered = &res.TaskDefinition + d.Log("Task definition is registered", taskDefinitionName(out.TaskDefinition)) + d.Registered = out.TaskDefinition return nil } func (d *App) LoadTaskDefinition(path string) error { d.Log("Creating a new task definition by", path) - var c TaskDefinitionContainer + c := struct { + TaskDefinition *ecs.TaskDefinition + }{} if err := config.LoadWithEnvJSON(&c, path); err != nil { return err } - d.TaskDefinition = &c.TaskDefinition + d.TaskDefinition = c.TaskDefinition return nil } - -func toJSON(v interface{}) string { - b, err := json.Marshal(v) - if err != nil { - panic(err) - } - return string(b) -} - -func awsECS(ctx context.Context, subCommand string, args ...string) ([]byte, error) { - _args := []string{"ecs", subCommand} - _args = append(_args, args...) - cmd := exec.CommandContext(ctx, "aws", _args...) - b, err := cmd.Output() - if err != nil { - if _e, ok := err.(*exec.ExitError); ok { - fmt.Fprintln(os.Stderr, string(_e.Stderr)) - } - return nil, errors.Wrap(err, subCommand+" failed") - } - return b, nil -} diff --git a/service.go b/service.go index b6e8cdae..0746cb9c 100644 --- a/service.go +++ b/service.go @@ -3,33 +3,16 @@ package ecspresso import ( "fmt" "strings" -) - -type ServiceContainer struct { - Services []Service `json:"services"` -} -type Service struct { - Deployments []Deployments `json:"deployments"` -} - -type Deployments struct { - ID string `json:"id"` - DesiredCount int `json:"desiredCount"` - PendingCount int `json:"pendingCount"` - RunningCount int `json:"runningCount"` - Status string `json:"status"` - TaskDefinition string `json:"taskDefinition"` - CreatedAt float64 `json:"createdAt"` - UpdatedAt float64 `json:"updatedAt"` -} + "github.com/aws/aws-sdk-go/service/ecs" +) -func (d Deployments) String() string { - td := strings.Split(d.TaskDefinition, "/") +func formatDeployment(d *ecs.Deployment) string { + td := strings.Split(*d.TaskDefinition, "/") return fmt.Sprintf( "%8s %s desired:%d pending:%d running:%d", - d.Status, + *d.Status, td[len(td)-1], - d.DesiredCount, d.PendingCount, d.RunningCount, + *d.DesiredCount, *d.PendingCount, *d.RunningCount, ) }