Skip to content

Commit

Permalink
Use aws-sdk-go instead of aws-cli!
Browse files Browse the repository at this point in the history
  • Loading branch information
fujiwara committed Nov 8, 2017
1 parent 7c49407 commit 49c065b
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 114 deletions.
27 changes: 21 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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...
Expand All @@ -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
Expand Down
6 changes: 4 additions & 2 deletions cmd/ecspresso/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(&region, "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)")
Expand All @@ -28,6 +29,7 @@ func _main() int {
flag.Parse()

c := ecspresso.Config{
Region: region,
Service: service,
Cluster: cluster,
TaskDefinitionPath: path,
Expand Down
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
131 changes: 48 additions & 83 deletions ecspresso.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")

Expand Down Expand Up @@ -123,75 +110,53 @@ 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
}

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
}
29 changes: 6 additions & 23 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}

0 comments on commit 49c065b

Please sign in to comment.