diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 29e38fb..3f2a40b 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -12,63 +12,63 @@ }, { "ImportPath": "github.com/aws/aws-sdk-go/aws", - "Comment": "v0.7.0-7-ga28ecdc", - "Rev": "a28ecdc9741b7905b5198059c94aed20868ffc08" + "Comment": "v0.7.2", + "Rev": "bc51d580d7481037e80ab618ea538869a8ade84c" }, { "ImportPath": "github.com/aws/aws-sdk-go/internal/endpoints", - "Comment": "v0.7.0-7-ga28ecdc", - "Rev": "a28ecdc9741b7905b5198059c94aed20868ffc08" + "Comment": "v0.7.2", + "Rev": "bc51d580d7481037e80ab618ea538869a8ade84c" }, { "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/json/jsonutil", - "Comment": "v0.7.0-7-ga28ecdc", - "Rev": "a28ecdc9741b7905b5198059c94aed20868ffc08" + "Comment": "v0.7.2", + "Rev": "bc51d580d7481037e80ab618ea538869a8ade84c" }, { "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/jsonrpc", - "Comment": "v0.7.0-7-ga28ecdc", - "Rev": "a28ecdc9741b7905b5198059c94aed20868ffc08" + "Comment": "v0.7.2", + "Rev": "bc51d580d7481037e80ab618ea538869a8ade84c" }, { "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/query", - "Comment": "v0.7.0-7-ga28ecdc", - "Rev": "a28ecdc9741b7905b5198059c94aed20868ffc08" + "Comment": "v0.7.2", + "Rev": "bc51d580d7481037e80ab618ea538869a8ade84c" }, { "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/rest", - "Comment": "v0.7.0-7-ga28ecdc", - "Rev": "a28ecdc9741b7905b5198059c94aed20868ffc08" + "Comment": "v0.7.2", + "Rev": "bc51d580d7481037e80ab618ea538869a8ade84c" }, { "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil", - "Comment": "v0.7.0-7-ga28ecdc", - "Rev": "a28ecdc9741b7905b5198059c94aed20868ffc08" + "Comment": "v0.7.2", + "Rev": "bc51d580d7481037e80ab618ea538869a8ade84c" }, { "ImportPath": "github.com/aws/aws-sdk-go/internal/signer/v4", - "Comment": "v0.7.0-7-ga28ecdc", - "Rev": "a28ecdc9741b7905b5198059c94aed20868ffc08" + "Comment": "v0.7.2", + "Rev": "bc51d580d7481037e80ab618ea538869a8ade84c" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/autoscaling", - "Comment": "v0.7.0-7-ga28ecdc", - "Rev": "a28ecdc9741b7905b5198059c94aed20868ffc08" + "Comment": "v0.7.2", + "Rev": "bc51d580d7481037e80ab618ea538869a8ade84c" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/ecs", - "Comment": "v0.7.0-7-ga28ecdc", - "Rev": "a28ecdc9741b7905b5198059c94aed20868ffc08" + "Comment": "v0.7.2", + "Rev": "bc51d580d7481037e80ab618ea538869a8ade84c" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/elb", - "Comment": "v0.7.0-7-ga28ecdc", - "Rev": "a28ecdc9741b7905b5198059c94aed20868ffc08" + "Comment": "v0.7.2", + "Rev": "bc51d580d7481037e80ab618ea538869a8ade84c" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/sns", - "Comment": "v0.7.0-7-ga28ecdc", - "Rev": "a28ecdc9741b7905b5198059c94aed20868ffc08" + "Comment": "v0.7.2", + "Rev": "bc51d580d7481037e80ab618ea538869a8ade84c" }, { "ImportPath": "github.com/codegangsta/cli", diff --git a/README.md b/README.md index 437236d..def6ef7 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Apply definition. ### Blue Green Deployment -ecs-formation supports blue-green deployment. +ecs-formation supports blue-green deployment. #### Requirements on ecs-formation @@ -146,7 +146,7 @@ ecs-formation supports blue-green deployment. #### Define Blue Green Deployment -Make management file of Blue Green Deployment file in bluegreen directory. +Make management file of Blue Green Deployment file in bluegreen directory. ```bash (path-to-path/test-ecs-formation/bluegreen) $ vim test-bluegreen.yml @@ -174,12 +174,31 @@ Apply blue green deployment. (path-to-path/test-ecs-formation $ ecs-formation bluegreen apply ``` -if with `--nodeploy` option, not update services. Only swap ELB on blue and green groups. +if with `--nodeploy` option, not update services. Only swap ELB on blue and green groups. ```bash (path-to-path/test-ecs-formation $ ecs-formation bluegreen apply --nodeploy ``` +If autoscaling group have several different ELB, you should specify array property of `chain_elb`. ecs-formation can swap `chain_elb` ELB group with main ELB group at the same time. + +```Ruby +(path-to-path/test-ecs-formation/bluegreen) $ vim test-bluegreen.yml +blue: + cluster: test-blue + service: test-service + autoscaling_group: test-blue-asg +green: + cluster: test-green + service: test-service + autoscaling_group: test-green-asg +primary_elb: test-elb-primary +standby_elb: test-elb-standby +chain_elb: + - primary_elb: test-internal-elb-primary + standby_elb: test-internal-elb-standby +``` + License === See [LICENSE](LICENSE). diff --git a/aws/ecs_service_api.go b/aws/ecs_service_api.go index da495c4..fc7d9cd 100644 --- a/aws/ecs_service_api.go +++ b/aws/ecs_service_api.go @@ -3,7 +3,6 @@ package aws import ( "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/service/ecs" - "github.com/stormcat24/ecs-formation/schema" "github.com/aws/aws-sdk-go/aws" ) @@ -12,7 +11,7 @@ type EcsServiceApi struct { Region *string } -func (self *EcsServiceApi) CreateService(cluster string, service schema.Service) (*ecs.CreateServiceOutput, error) { +func (self *EcsServiceApi) CreateService(cluster string, service string, desiredCount int64, lb []*ecs.LoadBalancer, taskDef string, role string) (*ecs.CreateServiceOutput, error) { svc := ecs.New(&aws.Config{ Region: self.Region, @@ -20,21 +19,21 @@ func (self *EcsServiceApi) CreateService(cluster string, service schema.Service) }) params := &ecs.CreateServiceInput{ - ServiceName: aws.String(service.Name), + ServiceName: aws.String(service), Cluster: aws.String(cluster), - DesiredCount: &service.DesiredCount, - LoadBalancers: toLoadBalancers(&service.LoadBalancers), - TaskDefinition: aws.String(service.TaskDefinition), + DesiredCount: &desiredCount, + LoadBalancers: lb, + TaskDefinition: aws.String(taskDef), } - if service.Role != "" { - params.Role = aws.String(service.Role) + if role != "" { + params.Role = aws.String(role) } return svc.CreateService(params) } -func (self *EcsServiceApi) UpdateService(cluster string, service schema.Service) (*ecs.UpdateServiceOutput, error) { +func (self *EcsServiceApi) UpdateService(cluster string, service string, desiredCount int64, taskDef string) (*ecs.UpdateServiceOutput, error) { svc := ecs.New(&aws.Config{ Region: self.Region, @@ -43,9 +42,9 @@ func (self *EcsServiceApi) UpdateService(cluster string, service schema.Service) params := &ecs.UpdateServiceInput{ Cluster: aws.String(cluster), - Service: aws.String(service.Name), - DesiredCount: &service.DesiredCount, - TaskDefinition: aws.String(service.TaskDefinition), + Service: aws.String(service), + DesiredCount: &desiredCount, + TaskDefinition: aws.String(taskDef), } return svc.UpdateService(params) diff --git a/aws/ecs_task_api.go b/aws/ecs_task_api.go index bb6f15f..5494e9b 100644 --- a/aws/ecs_task_api.go +++ b/aws/ecs_task_api.go @@ -4,9 +4,6 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ecs" - "github.com/stormcat24/ecs-formation/schema" - "github.com/stormcat24/ecs-formation/util" - "strings" ) type EcsTaskApi struct { @@ -28,76 +25,13 @@ func (self *EcsTaskApi) DescribeTaskDefinition(defName string) (*ecs.DescribeTas return svc.DescribeTaskDefinition(params) } -func (self *EcsTaskApi) RegisterTaskDefinition(taskName string, containers []*schema.ContainerDefinition) (*ecs.RegisterTaskDefinitionOutput, error) { +func (self *EcsTaskApi) RegisterTaskDefinition(taskName string, conDefs []*ecs.ContainerDefinition, volumes []*ecs.Volume) (*ecs.RegisterTaskDefinitionOutput, error) { svc := ecs.New(&aws.Config{ Region: self.Region, Credentials: self.Credentials, }) - conDefs := []*ecs.ContainerDefinition{} - volumes := []*ecs.Volume{} - - for _, con := range containers { - - var commands []*string - if (len(con.Command) > 0) { - for _, token := range strings.Split(con.Command, " ") { - commands = append(commands, aws.String(token)) - } - } else { - commands = nil - } - - var entryPoints []*string - if (len(con.EntryPoint) > 0) { - for _, token := range strings.Split(con.EntryPoint, " ") { - entryPoints = append(entryPoints, aws.String(token)) - } - } else { - entryPoints = nil - } - - portMappings, err := toPortMappings(con.Ports) - if err != nil { - return &ecs.RegisterTaskDefinitionOutput{}, err - } - - volumeItems, err := CreateVolumeInfoItems(con.Volumes) - if err != nil { - return &ecs.RegisterTaskDefinitionOutput{}, err - } - - mountPoints := []*ecs.MountPoint{} - for _, vp := range volumeItems { - volumes = append(volumes, vp.Volume) - - mountPoints = append(mountPoints, vp.MountPoint) - } - - volumesFrom, err := toVolumesFroms(con.VolumesFrom) - if err != nil { - return &ecs.RegisterTaskDefinitionOutput{}, err - } - - conDef := &ecs.ContainerDefinition{ - CPU: &con.CpuUnits, - Command: commands, - EntryPoint: entryPoints, - Environment: toKeyValuePairs(con.Environment), - Essential: &con.Essential, - Image: aws.String(con.Image), - Links: util.ConvertPointerString(con.Links), - Memory: &con.Memory, - MountPoints: mountPoints, - Name: aws.String(con.Name), - PortMappings: portMappings, - VolumesFrom: volumesFrom, - } - - conDefs = append(conDefs, conDef) - } - params := &ecs.RegisterTaskDefinitionInput{ ContainerDefinitions: conDefs, Family: aws.String(taskName), diff --git a/bluegreen/bluegreen.go b/bluegreen/bluegreen.go index 62d91a3..86062ae 100644 --- a/bluegreen/bluegreen.go +++ b/bluegreen/bluegreen.go @@ -3,9 +3,7 @@ package bluegreen import ( "io/ioutil" "github.com/stormcat24/ecs-formation/aws" - "github.com/stormcat24/ecs-formation/schema" "strings" - "github.com/stormcat24/ecs-formation/plan" "time" "fmt" "errors" @@ -13,13 +11,14 @@ import ( "github.com/stormcat24/ecs-formation/logger" "github.com/str1ngs/ansi/color" "regexp" + "gopkg.in/yaml.v2" ) type BlueGreenController struct { - Ecs *aws.AwsManager + Ecs *aws.AwsManager ClusterController *service.ServiceController - blueGreenMap map[string]*schema.BlueGreen - TargetResource string + blueGreenMap map[string]*BlueGreen + TargetResource string } func NewBlueGreenController(ecs *aws.AwsManager, projectDir string, targetResource string) (*BlueGreenController, error) { @@ -45,12 +44,12 @@ func NewBlueGreenController(ecs *aws.AwsManager, projectDir string, targetResour return con, nil } -func (self *BlueGreenController) searchBlueGreen(projectDir string) (map[string]*schema.BlueGreen, error) { +func (self *BlueGreenController) searchBlueGreen(projectDir string) (map[string]*BlueGreen, error) { clusterDir := projectDir + "/bluegreen" files, err := ioutil.ReadDir(clusterDir) - merged := map[string]*schema.BlueGreen{} + merged := map[string]*BlueGreen{} if err != nil { return merged, err @@ -64,7 +63,7 @@ func (self *BlueGreenController) searchBlueGreen(projectDir string) (map[string] tokens := filePattern.FindStringSubmatch(file.Name()) name := tokens[1] - bg, err := schema.CreateBlueGreen(content) + bg, err := CreateBlueGreen(content) if err != nil { return merged, err } @@ -75,13 +74,21 @@ func (self *BlueGreenController) searchBlueGreen(projectDir string) (map[string] return merged, nil } -func (self *BlueGreenController) GetBlueGreenMap() map[string]*schema.BlueGreen { +func CreateBlueGreen(data []byte) (*BlueGreen, error) { + + bg := &BlueGreen{} + err := yaml.Unmarshal(data, bg) + return bg, err +} + + +func (self *BlueGreenController) GetBlueGreenMap() map[string]*BlueGreen { return self.blueGreenMap } -func (self *BlueGreenController) CreateBlueGreenPlans(bgmap map[string]*schema.BlueGreen, cplans []*plan.ServiceUpdatePlan) ([]*plan.BlueGreenPlan, error) { +func (self *BlueGreenController) CreateBlueGreenPlans(bgmap map[string]*BlueGreen, cplans []*service.ServiceUpdatePlan) ([]*BlueGreenPlan, error) { - bgPlans := []*plan.BlueGreenPlan{} + bgPlans := []*BlueGreenPlan{} for name, bg := range bgmap { @@ -123,25 +130,26 @@ func (self *BlueGreenController) CreateBlueGreenPlans(bgmap map[string]*schema.B return bgPlans, nil } -func (self *BlueGreenController) CreateBlueGreenPlan(bluegreen *schema.BlueGreen, cplans []*plan.ServiceUpdatePlan) (*plan.BlueGreenPlan, error) { +func (self *BlueGreenController) CreateBlueGreenPlan(bluegreen *BlueGreen, cplans []*service.ServiceUpdatePlan) (*BlueGreenPlan, error) { blue := bluegreen.Blue green := bluegreen.Green - clusterMap := make(map[string]*plan.ServiceUpdatePlan, len(cplans)) + clusterMap := make(map[string]*service.ServiceUpdatePlan, len(cplans)) for _, cp := range cplans { clusterMap[cp.Name] = cp } - bgPlan := plan.BlueGreenPlan{ - Blue: &plan.ServiceSet{ + bgPlan := BlueGreenPlan{ + Blue: &ServiceSet{ ClusterUpdatePlan: clusterMap[blue.Cluster], }, - Green: &plan.ServiceSet{ + Green: &ServiceSet{ ClusterUpdatePlan: clusterMap[green.Cluster], }, PrimaryElb: bluegreen.PrimaryElb, StandbyElb: bluegreen.StandbyElb, + ChainElb: bluegreen.ChainElb, } // describe services @@ -164,7 +172,7 @@ func (self *BlueGreenController) CreateBlueGreenPlan(bluegreen *schema.BlueGreen } // describe autoscaling group - asgmap, err := self.Ecs.AutoscalingApi().DescribeAutoScalingGroups([]string { + asgmap, err := self.Ecs.AutoscalingApi().DescribeAutoScalingGroups([]string{ blue.AutoscalingGroup, green.AutoscalingGroup, }) @@ -185,7 +193,7 @@ func (self *BlueGreenController) CreateBlueGreenPlan(bluegreen *schema.BlueGreen } -func (self *BlueGreenController) ApplyBlueGreenDeploys(plans []*plan.BlueGreenPlan, nodeploy bool) error { +func (self *BlueGreenController) ApplyBlueGreenDeploys(plans []*BlueGreenPlan, nodeploy bool) error { for _, plan := range plans { if err := self.ApplyBlueGreenDeploy(plan, nodeploy); err != nil { @@ -196,7 +204,7 @@ func (self *BlueGreenController) ApplyBlueGreenDeploys(plans []*plan.BlueGreenPl return nil } -func (self *BlueGreenController) ApplyBlueGreenDeploy(bgplan *plan.BlueGreenPlan, nodeploy bool) error { +func (self *BlueGreenController) ApplyBlueGreenDeploy(bgplan *BlueGreenPlan, nodeploy bool) error { apias := self.Ecs.AutoscalingApi() @@ -204,8 +212,8 @@ func (self *BlueGreenController) ApplyBlueGreenDeploy(bgplan *plan.BlueGreenPlan var currentLabel *color.Escape var nextLabel *color.Escape - var current *plan.ServiceSet - var next *plan.ServiceSet + var current *ServiceSet + var next *ServiceSet primaryLb := bgplan.PrimaryElb standbyLb := bgplan.StandbyElb if targetGreen { @@ -220,6 +228,13 @@ func (self *BlueGreenController) ApplyBlueGreenDeploy(bgplan *plan.BlueGreenPlan nextLabel = color.Cyan("blue") } + primaryGroup := []string{primaryLb} + standbyGroup := []string{standbyLb} + for _, entry := range bgplan.ChainElb { + primaryGroup = append(primaryGroup, entry.PrimaryElb) + standbyGroup = append(standbyGroup, entry.StandbyElb) + } + logger.Main.Infof("Current status is '%s'", currentLabel) logger.Main.Infof("Start Blue-Green Deployment: %s to %s ...", currentLabel, nextLabel) if nodeploy { @@ -233,46 +248,41 @@ func (self *BlueGreenController) ApplyBlueGreenDeploy(bgplan *plan.BlueGreenPlan } // attach next group to primary lb - _, erratt := apias.AttachLoadBalancers(*next.AutoScalingGroup.AutoScalingGroupName, []string{ - primaryLb, - }) - if erratt != nil { - return erratt + if _, err := apias.AttachLoadBalancers(*next.AutoScalingGroup.AutoScalingGroupName, primaryGroup); err != nil { + return err + } + for _, e := range primaryGroup { + logger.Main.Infof("Attached to attach %s group to %s(primary).", nextLabel, e) } - logger.Main.Infof("Attached to attach %s group to %s(primary).", nextLabel, primaryLb) - errwlb := self.waitLoadBalancer(*next.AutoScalingGroup.AutoScalingGroupName, primaryLb) - if errwlb != nil { - return errwlb + if err := self.waitLoadBalancer(*next.AutoScalingGroup.AutoScalingGroupName, primaryLb); err != nil { + return err } logger.Main.Infof("Added %s group to primary", nextLabel) // detach current group from primary lb - _, errelbb := apias.DetachLoadBalancers(*current.AutoScalingGroup.AutoScalingGroupName, []string{ - primaryLb, - }) - if errelbb != nil { - return errelbb + if _, err := apias.DetachLoadBalancers(*current.AutoScalingGroup.AutoScalingGroupName, primaryGroup); err != nil { + return err + } + for _, e := range primaryGroup { + logger.Main.Infof("Detached %s group from %s(primary).", currentLabel, e) } - logger.Main.Infof("Detached %s group from %s(primary).", currentLabel, primaryLb) // detach next group from standby lb - _, errelbg := apias.DetachLoadBalancers(*next.AutoScalingGroup.AutoScalingGroupName, []string{ - standbyLb, - }) - if errelbg != nil { - return errelbg + if _, err := apias.DetachLoadBalancers(*next.AutoScalingGroup.AutoScalingGroupName, standbyGroup); err != nil { + return err + } + for _, e := range standbyGroup { + logger.Main.Infof("Detached %s group from %s(standby).", nextLabel, e) } - logger.Main.Infof("Detached %s group from %s(standby).", nextLabel, standbyLb) // attach current group to standby lb - _, errelba := apias.AttachLoadBalancers(*current.AutoScalingGroup.AutoScalingGroupName, []string{ - standbyLb, - }) - if errelba != nil { - return errelba + if _, err := apias.AttachLoadBalancers(*current.AutoScalingGroup.AutoScalingGroupName, standbyGroup); err != nil { + return err + } + for _, e := range standbyGroup { + logger.Main.Infof("Attached %s group to %s(standby).", currentLabel, e) } - logger.Main.Infof("Attached %s group to %s(standby).", currentLabel, standbyLb) return nil } diff --git a/bluegreen/plan.go b/bluegreen/plan.go new file mode 100644 index 0000000..cd8a526 --- /dev/null +++ b/bluegreen/plan.go @@ -0,0 +1,33 @@ +package bluegreen + +import ( + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/stormcat24/ecs-formation/service" +) + +type BlueGreenPlan struct { + Blue *ServiceSet + Green *ServiceSet + PrimaryElb string + StandbyElb string + ChainElb []BlueGreenChainElb +} + +func (self *BlueGreenPlan) IsBlueWithPrimaryElb() bool { + + for _, lb := range self.Blue.AutoScalingGroup.LoadBalancerNames { + if *lb == self.PrimaryElb { + return true + } + } + + return false +} + +type ServiceSet struct { + CurrentService *ecs.Service + NewService *BlueGreenTarget + AutoScalingGroup *autoscaling.Group + ClusterUpdatePlan *service.ServiceUpdatePlan +} \ No newline at end of file diff --git a/bluegreen/schema.go b/bluegreen/schema.go new file mode 100644 index 0000000..0fec4ba --- /dev/null +++ b/bluegreen/schema.go @@ -0,0 +1,20 @@ +package bluegreen + +type BlueGreen struct { + Blue BlueGreenTarget `yaml:"blue"` + Green BlueGreenTarget `yaml:"green"` + PrimaryElb string `yaml:"primary_elb"` + StandbyElb string `yaml:"standby_elb"` + ChainElb []BlueGreenChainElb `yaml:"chain_elb"` +} + +type BlueGreenChainElb struct { + PrimaryElb string `yaml:"primary_elb"` + StandbyElb string `yaml:"standby_elb"` +} + +type BlueGreenTarget struct { + Cluster string `yaml:"cluster"` + Service string `yaml:"service"` + AutoscalingGroup string `yaml:"autoscaling_group"` +} diff --git a/main.go b/main.go index 913895e..c3584b1 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,7 @@ func main() { app.Author = "Akinori Yamada(@stormcat24)" app.Email = "a.yamada24@gmail.com" app.Commands = operation.Commands - app.Flags = []cli.Flag { + app.Flags = []cli.Flag{ cli.StringFlag{ Name: "sns-topic, s", Usage: "AWS SNS Topic Name", diff --git a/operation/commands.go b/operation/commands.go index e77f9c5..b6548b2 100644 --- a/operation/commands.go +++ b/operation/commands.go @@ -10,7 +10,6 @@ import ( "github.com/stormcat24/ecs-formation/task" "strings" "github.com/str1ngs/ansi/color" - "github.com/stormcat24/ecs-formation/plan" "github.com/stormcat24/ecs-formation/util" "github.com/stormcat24/ecs-formation/bluegreen" "github.com/stormcat24/ecs-formation/logger" @@ -225,7 +224,7 @@ func doBluegreen(c *cli.Context) { } } -func createClusterPlans(controller *service.ServiceController, projectDir string, jsonOutput bool) ([]*plan.ServiceUpdatePlan, error) { +func createClusterPlans(controller *service.ServiceController, projectDir string, jsonOutput bool) ([]*service.ServiceUpdatePlan, error) { if jsonOutput { util.Output = false @@ -238,7 +237,7 @@ func createClusterPlans(controller *service.ServiceController, projectDir string plans, err := controller.CreateServiceUpdatePlans() if err != nil { - return []*plan.ServiceUpdatePlan{}, err + return []*service.ServiceUpdatePlan{}, err } for _, plan := range plans { @@ -299,7 +298,7 @@ func createClusterPlans(controller *service.ServiceController, projectDir string return plans, nil } -func createTaskPlans(controller *task.TaskDefinitionController, projectDir string) []*plan.TaskUpdatePlan { +func createTaskPlans(controller *task.TaskDefinitionController, projectDir string) []*task.TaskUpdatePlan { taskDefs := controller.GetTaskDefinitionMap() plans := controller.CreateTaskUpdatePlans(taskDefs) @@ -322,7 +321,7 @@ func createTaskPlans(controller *task.TaskDefinitionController, projectDir strin return plans } -func createBlueGreenPlans(controller *bluegreen.BlueGreenController, jsonOutput bool) ([]*plan.BlueGreenPlan, error) { +func createBlueGreenPlans(controller *bluegreen.BlueGreenController, jsonOutput bool) ([]*bluegreen.BlueGreenPlan, error) { if jsonOutput { util.Output = false @@ -335,7 +334,7 @@ func createBlueGreenPlans(controller *bluegreen.BlueGreenController, jsonOutput cplans, errcp := controller.ClusterController.CreateServiceUpdatePlans() if errcp != nil { - return []*plan.BlueGreenPlan{}, errcp + return []*bluegreen.BlueGreenPlan{}, errcp } bgplans, errbgp := controller.CreateBlueGreenPlans(bgmap, cplans) diff --git a/operation/version.go b/operation/version.go index 4ff9618..a286eb4 100644 --- a/operation/version.go +++ b/operation/version.go @@ -1,3 +1,3 @@ package operation -const Version string = "0.1.4" +const Version string = "0.1.5" diff --git a/plan/plan.go b/plan/plan.go deleted file mode 100644 index a27c1d3..0000000 --- a/plan/plan.go +++ /dev/null @@ -1,49 +0,0 @@ -package plan - -import ( - "github.com/aws/aws-sdk-go/service/ecs" - "github.com/stormcat24/ecs-formation/schema" - "github.com/aws/aws-sdk-go/service/autoscaling" -) - -type ServiceUpdatePlan struct { - Name string - InstanceARNs []*string - CurrentServices map[string]*ecs.Service - NewServices map[string]*schema.Service -} - -type TaskUpdatePlan struct { - Name string - NewContainers map[string]*schema.ContainerDefinition -} - -type UpdateContainer struct { - Before *ecs.ContainerDefinition - After *schema.ContainerDefinition -} - -type BlueGreenPlan struct { - Blue *ServiceSet - Green *ServiceSet - PrimaryElb string - StandbyElb string -} - -func (self *BlueGreenPlan) IsBlueWithPrimaryElb() bool { - - for _, lb := range self.Blue.AutoScalingGroup.LoadBalancerNames { - if *lb == self.PrimaryElb { - return true - } - } - - return false -} - -type ServiceSet struct { - CurrentService *ecs.Service - NewService *schema.BlueGreenTarget - AutoScalingGroup *autoscaling.Group - ClusterUpdatePlan *ServiceUpdatePlan -} \ No newline at end of file diff --git a/schema/bluegreen.go b/schema/bluegreen.go deleted file mode 100644 index 43b3279..0000000 --- a/schema/bluegreen.go +++ /dev/null @@ -1,14 +0,0 @@ -package schema - -type BlueGreen struct { - Blue BlueGreenTarget `yaml:"blue"` - Green BlueGreenTarget `yaml:"green"` - PrimaryElb string `yaml:"primary_elb"` - StandbyElb string `yaml:"standby_elb"` -} - -type BlueGreenTarget struct { - Cluster string `yaml:"cluster"` - Service string `yaml:"service"` - AutoscalingGroup string `yaml:"autoscaling_group"` -} diff --git a/service/plan.go b/service/plan.go new file mode 100644 index 0000000..7bb4d4e --- /dev/null +++ b/service/plan.go @@ -0,0 +1,10 @@ +package service + +import "github.com/aws/aws-sdk-go/service/ecs" + +type ServiceUpdatePlan struct { + Name string + InstanceARNs []*string + CurrentServices map[string]*ecs.Service + NewServices map[string]*Service +} \ No newline at end of file diff --git a/schema/service.go b/service/schema.go similarity index 84% rename from schema/service.go rename to service/schema.go index 98d6116..b52e099 100644 --- a/schema/service.go +++ b/service/schema.go @@ -1,6 +1,8 @@ -package schema +package service -import "gopkg.in/yaml.v2" +import ( + "gopkg.in/yaml.v2" +) type Cluster struct { @@ -21,7 +23,7 @@ func (self *Service) FindLoadBalancerByContainer(conname string, port int64) *Lo for _, lb := range self.LoadBalancers { if lb.ContainerName == conname && - lb.ContainerPort == port { + lb.ContainerPort == port { return &lb } } @@ -57,11 +59,4 @@ func CreateServiceMap(data []byte) (map[string]Service, error) { } return servicesMap, err -} - -func CreateBlueGreen(data []byte) (*BlueGreen, error) { - - bg := &BlueGreen{} - err := yaml.Unmarshal(data, bg) - return bg, err -} +} \ No newline at end of file diff --git a/service/service.go b/service/service.go index f6c17a3..74ebd26 100644 --- a/service/service.go +++ b/service/service.go @@ -2,7 +2,6 @@ package service import ( "io/ioutil" - "github.com/stormcat24/ecs-formation/schema" "fmt" "strings" "regexp" @@ -10,7 +9,6 @@ import ( "os" "github.com/str1ngs/ansi/color" "github.com/aws/aws-sdk-go/service/ecs" - "github.com/stormcat24/ecs-formation/plan" "github.com/stormcat24/ecs-formation/logger" "time" "errors" @@ -28,7 +26,7 @@ const ( type ServiceController struct { Ecs *aws.AwsManager TargetResource string - clusters []schema.Cluster + clusters []Cluster } func NewServiceController(ecs *aws.AwsManager, projectDir string, targetResource string) (*ServiceController, error) { @@ -51,12 +49,12 @@ func NewServiceController(ecs *aws.AwsManager, projectDir string, targetResource return con, nil } -func (self *ServiceController) searchServices(projectDir string) ([]schema.Cluster, error) { +func (self *ServiceController) searchServices(projectDir string) ([]Cluster, error) { clusterDir := projectDir + "/service" files, err := ioutil.ReadDir(clusterDir) - clusters := []schema.Cluster{} + clusters := []Cluster{} if err != nil { return clusters, err @@ -71,8 +69,8 @@ func (self *ServiceController) searchServices(projectDir string) ([]schema.Clust tokens := filePattern.FindStringSubmatch(file.Name()) clusterName := tokens[1] - serviceMap, _ := schema.CreateServiceMap(content) - cluster := schema.Cluster{ + serviceMap, _ := CreateServiceMap(content) + cluster := Cluster{ Name: clusterName, Services: serviceMap, } @@ -84,13 +82,13 @@ func (self *ServiceController) searchServices(projectDir string) ([]schema.Clust return clusters, nil } -func (self *ServiceController) GetClusters() []schema.Cluster { +func (self *ServiceController) GetClusters() []Cluster { return self.clusters } -func (self *ServiceController) CreateServiceUpdatePlans() ([]*plan.ServiceUpdatePlan, error) { +func (self *ServiceController) CreateServiceUpdatePlans() ([]*ServiceUpdatePlan, error) { - plans := []*plan.ServiceUpdatePlan{} + plans := []*ServiceUpdatePlan{} for _, cluster := range self.GetClusters() { if len(self.TargetResource) == 0 || self.TargetResource == cluster.Name { cp, err := self.CreateServiceUpdatePlan(cluster) @@ -104,46 +102,46 @@ func (self *ServiceController) CreateServiceUpdatePlans() ([]*plan.ServiceUpdate return plans, nil } -func (self *ServiceController) CreateServiceUpdatePlan(cluster schema.Cluster) (*plan.ServiceUpdatePlan, error) { +func (self *ServiceController) CreateServiceUpdatePlan(cluster Cluster) (*ServiceUpdatePlan, error) { clusterApi := self.Ecs.ClusterApi() output, errdc := clusterApi.DescribeClusters([]*string{&cluster.Name}) if errdc != nil { - return &plan.ServiceUpdatePlan{}, errdc + return &ServiceUpdatePlan{}, errdc } if len(output.Failures) > 0 { - return &plan.ServiceUpdatePlan{}, errors.New(fmt.Sprintf("Cluster '%s' not found", cluster.Name)) + return &ServiceUpdatePlan{}, errors.New(fmt.Sprintf("Cluster '%s' not found", cluster.Name)) } rlci, errlci := clusterApi.ListContainerInstances(cluster.Name) if errlci != nil { - return &plan.ServiceUpdatePlan{}, errlci + return &ServiceUpdatePlan{}, errlci } if len(rlci.ContainerInstanceARNs) == 0 { - return &plan.ServiceUpdatePlan{}, errors.New(fmt.Sprintf("ECS instances not found in cluster '%s' not found", cluster.Name)) + return &ServiceUpdatePlan{}, errors.New(fmt.Sprintf("ECS instances not found in cluster '%s' not found", cluster.Name)) } target := output.Clusters[0] if *target.Status != "ACTIVE" { - return &plan.ServiceUpdatePlan{}, errors.New(fmt.Sprintf("Cluster '%s' is not ACTIVE.", cluster.Name)) + return &ServiceUpdatePlan{}, errors.New(fmt.Sprintf("Cluster '%s' is not ACTIVE.", cluster.Name)) } serviceApi := self.Ecs.ServiceApi() resListServices, errls := serviceApi.ListServices(cluster.Name) if errls != nil { - return &plan.ServiceUpdatePlan{}, errls + return &ServiceUpdatePlan{}, errls } currentServices := map[string]*ecs.Service{} if len(resListServices.ServiceARNs) > 0 { resDescribeService, errds := serviceApi.DescribeService(cluster.Name, resListServices.ServiceARNs) if errds != nil { - return &plan.ServiceUpdatePlan{}, errds + return &ServiceUpdatePlan{}, errds } for _, service := range resDescribeService.Services { @@ -151,13 +149,13 @@ func (self *ServiceController) CreateServiceUpdatePlan(cluster schema.Cluster) ( } } - newServices := map[string]*schema.Service{} + newServices := map[string]*Service{} for name, newService := range cluster.Services { s := newService newServices[name] = &s } - return &plan.ServiceUpdatePlan{ + return &ServiceUpdatePlan{ Name: cluster.Name, InstanceARNs: rlci.ContainerInstanceARNs, CurrentServices: currentServices, @@ -165,7 +163,7 @@ func (self *ServiceController) CreateServiceUpdatePlan(cluster schema.Cluster) ( }, nil } -func (self *ServiceController) ApplyServicePlans(plans []*plan.ServiceUpdatePlan) { +func (self *ServiceController) ApplyServicePlans(plans []*ServiceUpdatePlan) { logger.Main.Info("Start apply serivces...") @@ -177,17 +175,14 @@ func (self *ServiceController) ApplyServicePlans(plans []*plan.ServiceUpdatePlan } } -func (self *ServiceController) ApplyServicePlan(plan *plan.ServiceUpdatePlan) error { +func (self *ServiceController) ApplyServicePlan(plan *ServiceUpdatePlan) error { api := self.Ecs.ServiceApi() for _, current := range plan.CurrentServices { // set desired_count = 0 - if _, err := api.UpdateService(plan.Name, schema.Service{ - Name: *current.ServiceName, - DesiredCount: 0, - }); err != nil { + if _, err := api.UpdateService(plan.Name, *current.ServiceName, 0, *current.TaskDefinition); err != nil { return err } @@ -214,14 +209,7 @@ func (self *ServiceController) ApplyServicePlan(plan *plan.ServiceUpdatePlan) er for _, add := range plan.NewServices { - result, err := api.CreateService(plan.Name, schema.Service{ - Name: add.Name, - DesiredCount: add.DesiredCount, - LoadBalancers: add.LoadBalancers, - TaskDefinition: add.TaskDefinition, - Role: add.Role, - }) - + result, err := api.CreateService(plan.Name, add.Name, add.DesiredCount, toLoadBalancers(&add.LoadBalancers), add.TaskDefinition, add.Role) if err != nil { return err } @@ -236,6 +224,21 @@ func (self *ServiceController) ApplyServicePlan(plan *plan.ServiceUpdatePlan) er return nil } +func toLoadBalancers(values *[]LoadBalancer) []*ecs.LoadBalancer { + + loadBalancers := []*ecs.LoadBalancer{} + for _, lb := range *values { + loadBalancers = append(loadBalancers, &ecs.LoadBalancer{ + LoadBalancerName: &lb.Name, + ContainerName: &lb.ContainerName, + ContainerPort: &lb.ContainerPort, + }) + } + + return loadBalancers +} + + func (self *ServiceController) waitStoppingService(cluster string, service string) error { api := self.Ecs.ServiceApi() diff --git a/aws/converter.go b/task/converter.go similarity index 87% rename from aws/converter.go rename to task/converter.go index e0bc6b5..7af34cf 100644 --- a/aws/converter.go +++ b/task/converter.go @@ -1,4 +1,4 @@ -package aws +package task import ( "github.com/aws/aws-sdk-go/service/ecs" @@ -8,7 +8,6 @@ import ( "regexp" "errors" "fmt" - "github.com/stormcat24/ecs-formation/schema" ) var portPattern = regexp.MustCompile(`^(\d+)\/(tcp|udp)$`) @@ -98,20 +97,6 @@ func toPortMapping(value string) (*ecs.PortMapping, error) { } } -func toLoadBalancers(values *[]schema.LoadBalancer) []*ecs.LoadBalancer { - - loadBalancers := []*ecs.LoadBalancer{} - for _, lb := range *values { - loadBalancers = append(loadBalancers, &ecs.LoadBalancer{ - LoadBalancerName: &lb.Name, - ContainerName: &lb.ContainerName, - ContainerPort: &lb.ContainerPort, - }) - } - - return loadBalancers -} - func toVolumesFroms(values []string) ([]*ecs.VolumeFrom, error) { volumes := []*ecs.VolumeFrom{} diff --git a/aws/converter_test.go b/task/converter_test.go similarity index 99% rename from aws/converter_test.go rename to task/converter_test.go index 9d94593..fd4f126 100644 --- a/aws/converter_test.go +++ b/task/converter_test.go @@ -1,4 +1,4 @@ -package aws +package task import ( "testing" diff --git a/task/plan.go b/task/plan.go new file mode 100644 index 0000000..5034ee3 --- /dev/null +++ b/task/plan.go @@ -0,0 +1,12 @@ +package task +import "github.com/aws/aws-sdk-go/service/ecs" + +type TaskUpdatePlan struct { + Name string + NewContainers map[string]*ContainerDefinition +} + +type UpdateContainer struct { + Before *ecs.ContainerDefinition + After *ContainerDefinition +} diff --git a/schema/task.go b/task/schema.go similarity index 98% rename from schema/task.go rename to task/schema.go index 004e65b..093828d 100644 --- a/schema/task.go +++ b/task/schema.go @@ -1,4 +1,4 @@ -package schema +package task import ( "gopkg.in/yaml.v2" diff --git a/task/task.go b/task/task.go index 24c3171..be8c3f0 100644 --- a/task/task.go +++ b/task/task.go @@ -2,23 +2,23 @@ package task import ( "io/ioutil" - "github.com/stormcat24/ecs-formation/schema" "strings" "regexp" - "github.com/stormcat24/ecs-formation/aws" - "github.com/stormcat24/ecs-formation/plan" + efaws "github.com/stormcat24/ecs-formation/aws" "github.com/stormcat24/ecs-formation/logger" "github.com/aws/aws-sdk-go/service/ecs" "time" + "github.com/aws/aws-sdk-go/aws" + "github.com/stormcat24/ecs-formation/util" ) type TaskDefinitionController struct { - Ecs *aws.AwsManager + Ecs *efaws.AwsManager TargetResource string - defmap map[string]*schema.TaskDefinition + defmap map[string]*TaskDefinition } -func NewTaskDefinitionController(ecs *aws.AwsManager, projectDir string, targetResource string) (*TaskDefinitionController, error) { +func NewTaskDefinitionController(ecs *efaws.AwsManager, projectDir string, targetResource string) (*TaskDefinitionController, error) { con := &TaskDefinitionController{ Ecs: ecs, @@ -37,16 +37,16 @@ func NewTaskDefinitionController(ecs *aws.AwsManager, projectDir string, targetR return con, nil } -func (self *TaskDefinitionController) GetTaskDefinitionMap() map[string]*schema.TaskDefinition { +func (self *TaskDefinitionController) GetTaskDefinitionMap() map[string]*TaskDefinition { return self.defmap } -func (self *TaskDefinitionController) searchTaskDefinitions(projectDir string) (map[string]*schema.TaskDefinition, error) { +func (self *TaskDefinitionController) searchTaskDefinitions(projectDir string) (map[string]*TaskDefinition, error) { taskDir := projectDir + "/task" files, err := ioutil.ReadDir(taskDir) - taskDefMap := map[string]*schema.TaskDefinition{} + taskDefMap := map[string]*TaskDefinition{} if err != nil { return taskDefMap, err @@ -61,7 +61,7 @@ func (self *TaskDefinitionController) searchTaskDefinitions(projectDir string) ( tokens := filePattern.FindStringSubmatch(file.Name()) taskDefName := tokens[1] - taskDefinition, _ := schema.CreateTaskDefinition(taskDefName, content) + taskDefinition, _ := CreateTaskDefinition(taskDefName, content) taskDefMap[taskDefName] = taskDefinition } @@ -70,9 +70,9 @@ func (self *TaskDefinitionController) searchTaskDefinitions(projectDir string) ( return taskDefMap, nil } -func (self *TaskDefinitionController) CreateTaskUpdatePlans(tasks map[string]*schema.TaskDefinition) []*plan.TaskUpdatePlan { +func (self *TaskDefinitionController) CreateTaskUpdatePlans(tasks map[string]*TaskDefinition) []*TaskUpdatePlan { - plans := []*plan.TaskUpdatePlan{} + plans := []*TaskUpdatePlan{} for _, task := range tasks { if len(self.TargetResource) == 0 || self.TargetResource == task.Name { plans = append(plans, self.CreateTaskUpdatePlan(task)) @@ -82,21 +82,21 @@ func (self *TaskDefinitionController) CreateTaskUpdatePlans(tasks map[string]*sc return plans } -func (self *TaskDefinitionController) CreateTaskUpdatePlan(task *schema.TaskDefinition) *plan.TaskUpdatePlan { +func (self *TaskDefinitionController) CreateTaskUpdatePlan(task *TaskDefinition) *TaskUpdatePlan { - newContainers := map[string]*schema.ContainerDefinition{} + newContainers := map[string]*ContainerDefinition{} for _, con := range task.ContainerDefinitions { newContainers[con.Name] = con } - return &plan.TaskUpdatePlan{ + return &TaskUpdatePlan{ Name: task.Name, NewContainers: newContainers, } } -func (self *TaskDefinitionController) ApplyTaskDefinitionPlans(plans []*plan.TaskUpdatePlan) ([]*ecs.RegisterTaskDefinitionOutput, error) { +func (self *TaskDefinitionController) ApplyTaskDefinitionPlans(plans []*TaskUpdatePlan) ([]*ecs.RegisterTaskDefinitionOutput, error) { logger.Main.Info("Start apply Task definitions...") @@ -116,12 +116,75 @@ func (self *TaskDefinitionController) ApplyTaskDefinitionPlans(plans []*plan.Tas return outputs, nil } -func (self *TaskDefinitionController) ApplyTaskDefinitionPlan(task *plan.TaskUpdatePlan) (*ecs.RegisterTaskDefinitionOutput, error) { +func (self *TaskDefinitionController) ApplyTaskDefinitionPlan(task *TaskUpdatePlan) (*ecs.RegisterTaskDefinitionOutput, error) { - containers := []*schema.ContainerDefinition{} + containers := []*ContainerDefinition{} for _, con := range task.NewContainers { containers = append(containers, con) } - return self.Ecs.TaskApi().RegisterTaskDefinition(task.Name, containers) + conDefs := []*ecs.ContainerDefinition{} + volumes := []*ecs.Volume{} + + for _, con := range containers { + + var commands []*string + if (len(con.Command) > 0) { + for _, token := range strings.Split(con.Command, " ") { + commands = append(commands, aws.String(token)) + } + } else { + commands = nil + } + + var entryPoints []*string + if (len(con.EntryPoint) > 0) { + for _, token := range strings.Split(con.EntryPoint, " ") { + entryPoints = append(entryPoints, aws.String(token)) + } + } else { + entryPoints = nil + } + + portMappings, err := toPortMappings(con.Ports) + if err != nil { + return &ecs.RegisterTaskDefinitionOutput{}, err + } + + volumeItems, err := CreateVolumeInfoItems(con.Volumes) + if err != nil { + return &ecs.RegisterTaskDefinitionOutput{}, err + } + + mountPoints := []*ecs.MountPoint{} + for _, vp := range volumeItems { + volumes = append(volumes, vp.Volume) + + mountPoints = append(mountPoints, vp.MountPoint) + } + + volumesFrom, err := toVolumesFroms(con.VolumesFrom) + if err != nil { + return &ecs.RegisterTaskDefinitionOutput{}, err + } + + conDef := &ecs.ContainerDefinition{ + CPU: &con.CpuUnits, + Command: commands, + EntryPoint: entryPoints, + Environment: toKeyValuePairs(con.Environment), + Essential: &con.Essential, + Image: aws.String(con.Image), + Links: util.ConvertPointerString(con.Links), + Memory: &con.Memory, + MountPoints: mountPoints, + Name: aws.String(con.Name), + PortMappings: portMappings, + VolumesFrom: volumesFrom, + } + + conDefs = append(conDefs, conDef) + } + + return self.Ecs.TaskApi().RegisterTaskDefinition(task.Name, conDefs, volumes) } diff --git a/aws/volume.go b/task/volume.go similarity index 99% rename from aws/volume.go rename to task/volume.go index 40f694a..1f1f4a3 100644 --- a/aws/volume.go +++ b/task/volume.go @@ -1,4 +1,5 @@ -package aws +package task + import ( "github.com/aws/aws-sdk-go/service/ecs" "errors" diff --git a/aws/volume_test.go b/task/volume_test.go similarity index 99% rename from aws/volume_test.go rename to task/volume_test.go index 87073a0..0a19cff 100644 --- a/aws/volume_test.go +++ b/task/volume_test.go @@ -1,4 +1,4 @@ -package aws +package task import ( "testing"