github.com/in4it/ecs-deploy@v0.0.42-0.20240508120354-ed77ff16df25/api/controller.go (about) 1 package api 2 3 import ( 4 "github.com/google/go-cmp/cmp" 5 "github.com/in4it/ecs-deploy/integrations" 6 "github.com/in4it/ecs-deploy/provider/ecs" 7 "github.com/in4it/ecs-deploy/service" 8 "github.com/in4it/ecs-deploy/util" 9 "github.com/juju/loggo" 10 11 "errors" 12 "fmt" 13 "io/ioutil" 14 "os" 15 "sort" 16 "strconv" 17 "strings" 18 "time" 19 ) 20 21 // Controller struct 22 type Controller struct { 23 } 24 25 // controller interface (for tests) 26 type ControllerIf interface { 27 describeServices() ([]service.RunningService, error) 28 getServices() ([]*service.DynamoServicesElement, error) 29 } 30 31 // logging 32 var controllerLogger = loggo.GetLogger("controller") 33 34 func (c *Controller) createRepository(repository string) (*string, error) { 35 // create service in ECR if not exists 36 ecr := ecs.ECR{RepositoryName: repository} 37 err := ecr.CreateRepository() 38 if err != nil { 39 controllerLogger.Errorf("Could not create repository %v: %v", repository, err) 40 return nil, errors.New("CouldNotCreateRepository") 41 } 42 msg := fmt.Sprintf("Service: %v - ECR: %v", repository, ecr.RepositoryURI) 43 return &msg, nil 44 } 45 46 func (c *Controller) Deploy(serviceName string, d service.Deploy) (*service.DeployResult, error) { 47 // get last deployment 48 s := service.NewService() 49 s.ServiceName = serviceName 50 s.ClusterName = d.Cluster 51 ddLast, err := s.GetLastDeploy() 52 if err != nil { 53 if !strings.HasPrefix(err.Error(), "NoItemsFound") { 54 controllerLogger.Errorf("Error while getting last deployment for %v: %v", serviceName, err) 55 return nil, err 56 } 57 } 58 // validate 59 for _, container := range d.Containers { 60 if container.Memory == 0 && container.MemoryReservation == 0 { 61 controllerLogger.Errorf("Could not deploy %v: Memory / MemoryReservation not set", serviceName) 62 return nil, errors.New("At least one of 'memory' or 'memoryReservation' must be specified within the container specification.") 63 } 64 } 65 66 // create role if role doesn't exists 67 iam := ecs.IAM{} 68 iamRoleArn, err := iam.RoleExists("ecs-" + serviceName) 69 if err == nil && iamRoleArn == nil { 70 if util.GetEnv("AWS_RESOURCE_CREATION_ENABLED", "yes") == "yes" { 71 // role does not exist, create it 72 controllerLogger.Debugf("Role does not exist, creating: ecs-%v", serviceName) 73 iamRoleArn, err = iam.CreateRoleWithPermissionBoundary("ecs-"+serviceName, iam.GetEcsTaskIAMTrust(), util.GetEnv("ECS_TASK_ROLE_PERMISSION_BOUNDARY_ARN", "")) 74 if err != nil { 75 return nil, err 76 } 77 // optionally add a policy 78 ps := ecs.Paramstore{} 79 if ps.IsEnabled() { 80 namespace := d.EnvNamespace 81 if namespace == "" { 82 namespace = serviceName 83 } 84 controllerLogger.Debugf("Paramstore enabled, putting role: paramstore-%v", namespace) 85 err = iam.PutRolePolicy("ecs-"+serviceName, "paramstore-"+namespace, ps.GetParamstoreIAMPolicy(namespace)) 86 if err != nil { 87 return nil, err 88 } 89 } 90 } else { 91 return nil, errors.New("IAM Task Role not found and resource creation is disabled") 92 } 93 } else if err != nil { 94 return nil, err 95 } 96 97 // retrieving secrets 98 secrets := make(map[string]string) 99 if util.GetEnv("PARAMSTORE_INJECT", "no") == "yes" { 100 ps := ecs.Paramstore{} 101 if ps.IsEnabled() { 102 err := ps.GetParameters("/"+util.GetEnv("PARAMSTORE_PREFIX", "")+"-"+util.GetEnv("AWS_ACCOUNT_ENV", "")+"/"+serviceName+"/", false) 103 if err != nil { 104 return nil, err 105 } 106 for _, v := range ps.Parameters { 107 keyName := strings.Split(v.Name, "/") 108 secrets[keyName[len(keyName)-1]] = v.Arn 109 } 110 } 111 } 112 113 // create task definition 114 e := ecs.ECS{ServiceName: serviceName, IamRoleArn: *iamRoleArn, ClusterName: d.Cluster} 115 taskDefArn, err := e.CreateTaskDefinition(d, secrets) 116 if err != nil { 117 controllerLogger.Errorf("Could not create task def %v", serviceName) 118 return nil, err 119 } 120 controllerLogger.Debugf("Created task definition: %v", *taskDefArn) 121 122 // update service with new task (update desired instance in case of difference) 123 controllerLogger.Debugf("Updating service: %v with taskdefarn: %v", serviceName, *taskDefArn) 124 serviceExists, err := e.ServiceExists(serviceName) 125 if err == nil && !serviceExists { 126 controllerLogger.Debugf("service (%v) not found, creating...", serviceName) 127 if util.GetEnv("AWS_RESOURCE_CREATION_ENABLED", "yes") == "yes" { 128 s.Listeners, err = c.createService(serviceName, d, taskDefArn) 129 if err != nil { 130 controllerLogger.Errorf("Could not create service %v: %s", serviceName, err) 131 return nil, err 132 } 133 // create service in dynamodb 134 err = c.checkAndCreateServiceInDynamo(s, d) 135 if err != nil { 136 return nil, err 137 } 138 } else { 139 return nil, errors.New("ECS Service not found and resource creation is disabled") 140 } 141 } else if err != nil { 142 return nil, errors.New("Error during checking whether service exists") 143 } else { 144 // create service in dynamodb 145 err = c.checkAndCreateServiceInDynamo(s, d) 146 if err != nil { 147 return nil, err 148 } 149 err = c.updateDeployment(d, ddLast, serviceName, taskDefArn, iamRoleArn) 150 if err != nil { 151 controllerLogger.Errorf("updateDeployment failed: %s", err) 152 } 153 } 154 155 // Mark previous deployment as aborted if still running 156 if ddLast != nil && ddLast.Status == "running" { 157 err = s.SetDeploymentStatus(ddLast, "aborted") 158 if err != nil { 159 controllerLogger.Errorf("Could not set status of %v to aborted: %v", serviceName, err) 160 return nil, err 161 } 162 } 163 164 // write changes in db 165 dd, err := s.NewDeployment(taskDefArn, &d) 166 if err != nil { 167 controllerLogger.Errorf("Could not create/update service (%v) in db: %v", serviceName, err) 168 return nil, err 169 } 170 171 // run goroutine to update status of service 172 var notification integrations.Notification 173 if util.GetEnv("SLACK_WEBHOOKS", "") != "" { 174 notification = integrations.NewSlack() 175 } else { 176 notification = integrations.NewDummy() 177 } 178 go e.LaunchWaitUntilServicesStable(dd, ddLast, notification) 179 180 ret := &service.DeployResult{ 181 ServiceName: serviceName, 182 ClusterName: d.Cluster, 183 TaskDefinitionArn: *taskDefArn, 184 DeploymentTime: dd.Time, 185 } 186 return ret, nil 187 } 188 189 func (c *Controller) updateDeployment(d service.Deploy, ddLast *service.DynamoDeployment, serviceName string, taskDefArn *string, iamRoleArn *string) error { 190 s := service.NewService() 191 s.ServiceName = serviceName 192 s.ClusterName = d.Cluster 193 e := ecs.ECS{ServiceName: serviceName, IamRoleArn: *iamRoleArn, ClusterName: d.Cluster, TaskDefArn: taskDefArn} 194 updateECSService := true 195 // compare with previous deployment if there is one 196 if ddLast != nil { 197 var err error 198 if strings.ToLower(d.ServiceProtocol) != "none" { 199 var alb *ecs.ALB 200 if d.LoadBalancer == "" { 201 alb, err = ecs.NewALB(d.Cluster) 202 } else { 203 alb, err = ecs.NewALB(d.LoadBalancer) 204 } 205 targetGroupArn, err := alb.GetTargetGroupArn(serviceName) 206 if err != nil { 207 return err 208 } 209 // update healthchecks if changed 210 if !cmp.Equal(ddLast.DeployData.HealthCheck, d.HealthCheck) { 211 controllerLogger.Debugf("Updating ecs healthcheck: %v", serviceName) 212 alb.UpdateHealthCheck(*targetGroupArn, d.HealthCheck) 213 } 214 // update target group attributes if changed 215 if !cmp.Equal(ddLast.DeployData.Stickiness, d.Stickiness) || ddLast.DeployData.DeregistrationDelay != d.DeregistrationDelay { 216 err = alb.ModifyTargetGroupAttributes(*targetGroupArn, d) 217 if err != nil { 218 return err 219 } 220 } 221 // update loadbalancer if changed 222 var noLBChange bool 223 if ddLast.DeployData.LoadBalancer == "" && strings.ToLower(d.LoadBalancer) == strings.ToLower(d.Cluster) { 224 noLBChange = true 225 } 226 if strings.ToLower(d.LoadBalancer) != strings.ToLower(ddLast.DeployData.LoadBalancer) && !noLBChange && strings.ToLower(d.ServiceProtocol) != "none" { 227 controllerLogger.Infof("LoadBalancer change detected for service %s", serviceName) 228 // delete old loadbalancer rules 229 var oldAlb *ecs.ALB 230 if ddLast.DeployData.LoadBalancer == "" { 231 oldAlb, err = ecs.NewALB(ddLast.DeployData.Cluster) 232 } else { 233 oldAlb, err = ecs.NewALB(ddLast.DeployData.LoadBalancer) 234 } 235 err = c.deleteRulesForTarget(serviceName, d, targetGroupArn, oldAlb) 236 if err != nil { 237 238 } 239 // delete target group 240 controllerLogger.Debugf("Deleting target group for service: %v", serviceName) 241 err = oldAlb.DeleteTargetGroup(*targetGroupArn) 242 if err != nil { 243 return err 244 } 245 // create new target group 246 controllerLogger.Debugf("Creating target group for service: %v", serviceName) 247 newTargetGroupArn, err := alb.CreateTargetGroup(serviceName, d) 248 if err != nil { 249 return err 250 } 251 // modify target group attributes 252 if d.DeregistrationDelay != -1 || d.Stickiness.Enabled { 253 err = alb.ModifyTargetGroupAttributes(*newTargetGroupArn, d) 254 if err != nil { 255 return err 256 } 257 } 258 // create new rules 259 listeners, err := c.createRulesForTarget(serviceName, d, newTargetGroupArn, alb) 260 s.Listeners = listeners 261 if err != nil { 262 return err 263 } 264 // recreating ecs service 265 controllerLogger.Infof("Recreating ecs service: %v", serviceName) 266 err = e.DeleteService(d.Cluster, serviceName) 267 if err != nil { 268 return err 269 } 270 err = e.WaitUntilServicesInactive(d.Cluster, serviceName) 271 if err != nil { 272 return err 273 } 274 // create ecs service 275 e.TargetGroupArn = newTargetGroupArn 276 err = e.CreateService(d) 277 if err != nil { 278 return err 279 } 280 // update listeners 281 s.UpdateServiceListeners(s.ClusterName, s.ServiceName, listeners) 282 // don't update ecs service later 283 updateECSService = false 284 } else { 285 // check for rules changes 286 if c.rulesChanged(d, ddLast) { 287 controllerLogger.Infof("Recreating alb rules for: " + serviceName) 288 // recreate rules 289 err = c.deleteRulesForTarget(serviceName, d, targetGroupArn, alb) 290 if err != nil { 291 controllerLogger.Infof("Couldn't delete existing rules for target: " + serviceName) 292 } 293 // create new rules 294 _, err := c.createRulesForTarget(serviceName, d, targetGroupArn, alb) 295 if err != nil { 296 return err 297 } 298 } 299 } 300 } 301 ps := ecs.Paramstore{} 302 if ps.IsEnabled() { 303 iam := ecs.IAM{} 304 thisNamespace, lastNamespace := d.EnvNamespace, ddLast.DeployData.EnvNamespace 305 if thisNamespace == "" { 306 thisNamespace = serviceName 307 } 308 if lastNamespace == "" { 309 lastNamespace = serviceName 310 } 311 if thisNamespace != lastNamespace { 312 controllerLogger.Debugf("Paramstore enabled, putting role: paramstore-%v", serviceName) 313 err = iam.DeleteRolePolicy("ecs-"+serviceName, "paramstore-"+lastNamespace) 314 if err != nil { 315 return err 316 } 317 err = iam.PutRolePolicy("ecs-"+serviceName, "paramstore-"+thisNamespace, ps.GetParamstoreIAMPolicy(thisNamespace)) 318 if err != nil { 319 return err 320 } 321 } 322 } 323 // update memory limits if changed 324 if !e.IsEqualContainerLimits(d, *ddLast.DeployData) { 325 cpuReservation, cpuLimit, memoryReservation, memoryLimit := e.GetContainerLimits(d) 326 s.UpdateServiceLimits(s.ClusterName, s.ServiceName, cpuReservation, cpuLimit, memoryReservation, memoryLimit) 327 } 328 } 329 // update service 330 if updateECSService { 331 var err error 332 _, err = e.UpdateService(serviceName, taskDefArn, d) 333 controllerLogger.Debugf("Updating ecs service: %v", serviceName) 334 if err != nil { 335 controllerLogger.Errorf("Could not update service %v: %v", serviceName, err) 336 return err 337 } 338 } 339 return nil 340 } 341 342 func (c *Controller) rulesChanged(d service.Deploy, ddLast *service.DynamoDeployment) bool { 343 if len(d.RuleConditions) != len(ddLast.DeployData.RuleConditions) { 344 return true 345 } 346 347 // sort rule conditions 348 sortedRuleCondition := d.RuleConditions 349 ddLastSortedRuleCondition := ddLast.DeployData.RuleConditions 350 sort.Sort(ruleConditionSort(sortedRuleCondition)) 351 sort.Sort(ruleConditionSort(ddLastSortedRuleCondition)) 352 // loop over rule conditions to compare them 353 for k, v := range sortedRuleCondition { 354 v2 := ddLastSortedRuleCondition[k] 355 if !cmp.Equal(v, v2) { 356 return true 357 } 358 } 359 360 return false 361 362 } 363 364 func (c *Controller) redeploy(serviceName, time string) (*service.DeployResult, error) { 365 s := service.NewService() 366 dd, err := s.GetDeployment(serviceName, time) 367 if err != nil { 368 return nil, err 369 } 370 371 controllerLogger.Debugf("Redeploying %v_%v", serviceName, time) 372 373 ret, err := c.Deploy(serviceName, *dd.DeployData) 374 375 if err != nil { 376 return nil, err 377 } 378 379 return ret, nil 380 } 381 382 // service not found, create ALB target group + rule 383 func (c *Controller) createService(serviceName string, d service.Deploy, taskDefArn *string) ([]string, error) { 384 iam := ecs.IAM{} 385 var targetGroupArn *string 386 var listeners []string 387 var alb *ecs.ALB 388 var err error 389 if d.LoadBalancer != "" { 390 alb, err = ecs.NewALB(d.LoadBalancer) 391 } else { 392 alb, err = ecs.NewALB(d.Cluster) 393 } 394 if err != nil { 395 return nil, err 396 } 397 398 // create target group 399 if strings.ToLower(d.ServiceProtocol) != "none" { 400 var err error 401 controllerLogger.Debugf("Creating target group for service: %v", serviceName) 402 targetGroupArn, err = alb.CreateTargetGroup(serviceName, d) 403 if err != nil { 404 return nil, err 405 } 406 // modify target group attributes 407 if d.DeregistrationDelay != -1 || d.Stickiness.Enabled { 408 err = alb.ModifyTargetGroupAttributes(*targetGroupArn, d) 409 if err != nil { 410 return nil, err 411 } 412 } 413 414 // deploy rules for target group 415 listeners, err = c.createRulesForTarget(serviceName, d, targetGroupArn, alb) 416 if err != nil { 417 return nil, err 418 } 419 } 420 421 // check whether ecs-service-role exists 422 controllerLogger.Debugf("Checking whether role exists: %v", util.GetEnv("AWS_ECS_SERVICE_ROLE", "ecs-service-role")) 423 iamServiceRoleArn, err := iam.RoleExists(util.GetEnv("AWS_ECS_SERVICE_ROLE", "ecs-service-role")) 424 if err == nil && iamServiceRoleArn == nil { 425 controllerLogger.Debugf("Creating ecs service role") 426 _, err = iam.CreateRole(util.GetEnv("AWS_ECS_SERVICE_ROLE", "ecs-service-role"), iam.GetEcsServiceIAMTrust()) 427 if err != nil { 428 return nil, err 429 } 430 controllerLogger.Debugf("Attaching ecs service role") 431 err = iam.AttachRolePolicy(util.GetEnv("AWS_ECS_SERVICE_ROLE", "ecs-service-role"), iam.GetEcsServicePolicy()) 432 if err != nil { 433 return nil, err 434 } 435 } else if err != nil { 436 return nil, errors.New("Error during checking whether ecs service role exists") 437 } 438 439 // create ecs service 440 controllerLogger.Debugf("Creating ecs service: %v", serviceName) 441 e := ecs.ECS{ServiceName: serviceName, TaskDefArn: taskDefArn, TargetGroupArn: targetGroupArn} 442 err = e.CreateService(d) 443 if err != nil { 444 return nil, err 445 } 446 return listeners, nil 447 } 448 func (c *Controller) checkAndCreateServiceInDynamo(s *service.Service, d service.Deploy) error { 449 serviceExistsInDynamo, err := s.ServiceExistsInDynamo() 450 if err == nil && !serviceExistsInDynamo { 451 err = c.createServiceInDynamo(s, d) 452 if err != nil { 453 controllerLogger.Errorf("Could not create service %v in dynamodb", s.ServiceName) 454 return err 455 } 456 } 457 return nil 458 } 459 460 func (c *Controller) createServiceInDynamo(s *service.Service, d service.Deploy) error { 461 var err error 462 e := ecs.ECS{ServiceName: s.ServiceName} 463 464 dsEl := &service.DynamoServicesElement{S: s.ServiceName, C: s.ClusterName, Listeners: s.Listeners} 465 dsEl.CpuReservation, dsEl.CpuLimit, dsEl.MemoryReservation, dsEl.MemoryLimit = e.GetContainerLimits(d) 466 467 err = s.CreateService(dsEl) 468 if err != nil { 469 controllerLogger.Errorf("Could not create/update service (%v) in db: %v", s.ServiceName, err) 470 return err 471 } 472 return nil 473 } 474 475 // Deploy rules for a specific targetGroup 476 func (c *Controller) deleteRulesForTarget(serviceName string, d service.Deploy, targetGroupArn *string, alb *ecs.ALB) error { 477 err := alb.GetRulesForAllListeners() 478 if err != nil { 479 return err 480 } 481 ruleArnsToDelete := alb.GetRulesByTargetGroupArn(*targetGroupArn) 482 authRuleArns := alb.GetRuleByTargetGroupArnWithAuth(*targetGroupArn) 483 for _, authRuleArn := range authRuleArns { 484 conditionField, conditionValue := alb.GetConditionsForRule(authRuleArn) 485 controllerLogger.Debugf("deleteRulesForTarget: found authRule with conditionField %s and conditionValue %s", strings.Join(conditionField, ","), strings.Join(conditionValue, ",")) 486 httpListener := alb.GetListenerArnForProtocol("http") 487 if httpListener != "" { 488 ruleArn, _, err := alb.FindRule(httpListener, "", conditionField, conditionValue) 489 if err != nil { 490 controllerLogger.Debugf("deleteRulesForTarget: rule not found: %s", err) 491 } 492 if ruleArn != nil { 493 ruleArnsToDelete = append(ruleArnsToDelete, *ruleArn) 494 } 495 496 } 497 } 498 for _, ruleArn := range ruleArnsToDelete { 499 alb.DeleteRule(ruleArn) 500 } 501 return nil 502 } 503 504 // delete rule for a targetgroup with specific listener 505 func (c *Controller) deleteRuleForTargetWithListener(serviceName string, r *service.DeployRuleConditions, targetGroupArn *string, alb *ecs.ALB, listener string) error { 506 _, conditionField, conditionValue := c.getALBConditionFieldAndValue(*r, alb.GetDomain()) 507 err := alb.GetRulesForAllListeners() 508 if err != nil { 509 return err 510 } 511 ruleArn, _, err := alb.FindRule(listener, *targetGroupArn, conditionField, conditionValue) 512 if err != nil { 513 return err 514 } 515 return alb.DeleteRule(*ruleArn) 516 } 517 518 // Update rule for a specific targetGroups 519 func (c *Controller) UpdateRuleForTarget(serviceName string, r *service.DeployRuleConditions, rLast *service.DeployRuleConditions, targetGroupArn *string, alb *ecs.ALB, listener string) error { 520 _, conditionField, conditionValue := c.getALBConditionFieldAndValue(*rLast, alb.GetDomain()) 521 err := alb.GetRulesForAllListeners() 522 if err != nil { 523 return err 524 } 525 ruleArn, _, err := alb.FindRule(alb.GetListenerArnForProtocol(listener), *targetGroupArn, conditionField, conditionValue) 526 if err != nil { 527 return err 528 } 529 ruleType, _, conditionValue := c.getALBConditionFieldAndValue(*rLast, alb.GetDomain()) 530 531 // if cognito is set, a redirect is needed instead (cognito doesn't work with http) 532 if strings.ToLower(listener) == "http" && r.CognitoAuth.ClientName != "" { 533 return alb.UpdateRuleToHTTPSRedirect(*targetGroupArn, *ruleArn, ruleType, conditionValue) 534 } 535 536 return alb.UpdateRule(*targetGroupArn, *ruleArn, ruleType, conditionValue, r.CognitoAuth) 537 538 } 539 540 func (c *Controller) getALBConditionFieldAndValue(r service.DeployRuleConditions, domain string) (string, []string, []string) { 541 if r.PathPattern != "" && r.Hostname != "" { 542 return "combined", []string{"path-pattern", "host-header"}, []string{r.PathPattern, r.Hostname + "." + domain} 543 } 544 if r.PathPattern != "" { 545 return "pathPattern", []string{"path-pattern"}, []string{r.PathPattern} 546 } 547 if r.Hostname != "" { 548 return "hostname", []string{"host-header"}, []string{r.Hostname + "." + domain} 549 } 550 return "", []string{}, []string{} 551 } 552 553 // Deploy rules for a specific targetGroup 554 func (c *Controller) createRulesForTarget(serviceName string, d service.Deploy, targetGroupArn *string, alb *ecs.ALB) ([]string, error) { 555 var listeners []string 556 // get last priority number 557 priority, err := alb.GetHighestRule() 558 if err != nil { 559 return nil, err 560 } 561 562 if len(d.RuleConditions) > 0 { 563 // create rules based on conditions 564 var newRules int 565 ruleConditionsSorted := d.RuleConditions 566 sort.Sort(ruleConditionSort(ruleConditionsSorted)) 567 for _, r := range ruleConditionsSorted { 568 ruleType, _, conditionValue := c.getALBConditionFieldAndValue(*r, alb.GetDomain()) 569 l, err := alb.CreateRuleForListeners(ruleType, r.Listeners, *targetGroupArn, conditionValue, (priority + 10 + int64(newRules)), r.CognitoAuth) 570 if err != nil { 571 return nil, err 572 } 573 newRules += len(r.Listeners) 574 listeners = append(listeners, l...) 575 } 576 } else { 577 // create default rules ( /servicename path on all listeners ) 578 controllerLogger.Debugf("Creating alb rule(s) service: %v", serviceName) 579 rules := []string{"/" + serviceName} 580 l, err := alb.CreateRuleForAllListeners("pathPattern", *targetGroupArn, rules, (priority + 10)) 581 if err != nil { 582 return nil, err 583 } 584 rules = []string{"/" + serviceName + "/*"} 585 _, err = alb.CreateRuleForAllListeners("pathPattern", *targetGroupArn, rules, (priority + 11)) 586 if err != nil { 587 return nil, err 588 } 589 listeners = append(listeners, l...) 590 } 591 return listeners, nil 592 } 593 594 func (c *Controller) getDeploys() ([]service.DynamoDeployment, error) { 595 s := service.NewService() 596 return s.GetDeploys("byMonth", 20) 597 } 598 func (c *Controller) getDeploysForService(serviceName string) ([]service.DynamoDeployment, error) { 599 s := service.NewService() 600 return s.GetDeploysForService(serviceName) 601 } 602 func (c *Controller) getServices() ([]*service.DynamoServicesElement, error) { 603 s := service.NewService() 604 var ds service.DynamoServices 605 err := s.GetServices(&ds) 606 return ds.Services, err 607 } 608 609 func (c *Controller) describeServices() ([]service.RunningService, error) { 610 var rss []service.RunningService 611 showEvents := false 612 showTasks := false 613 showStoppedTasks := false 614 services := make(map[string][]*string) 615 e := ecs.ECS{} 616 dss, _ := c.getServices() 617 for _, ds := range dss { 618 services[ds.C] = append(services[ds.C], &ds.S) 619 } 620 for clusterName, serviceList := range services { 621 newRss, err := e.DescribeServices(clusterName, serviceList, showEvents, showTasks, showStoppedTasks) 622 if err != nil { 623 return []service.RunningService{}, err 624 } 625 rss = append(rss, newRss...) 626 } 627 628 sort.Slice(rss, func(i, j int) bool { 629 if strings.Compare(rss[i].ServiceName, rss[j].ServiceName) == 1 { 630 return false 631 } 632 return true 633 }) 634 635 return rss, nil 636 } 637 func (c *Controller) describeService(serviceName string) (service.RunningService, error) { 638 var rs service.RunningService 639 showEvents := true 640 showTasks := true 641 showStoppedTasks := false 642 e := ecs.ECS{} 643 dss, _ := c.getServices() 644 for _, ds := range dss { 645 if ds.S == serviceName { 646 rss, err := e.DescribeServices(ds.C, []*string{&serviceName}, showEvents, showTasks, showStoppedTasks) 647 if err != nil { 648 return rs, err 649 } 650 if len(rss) != 1 { 651 return rs, errors.New("Empty RunningService object returned") 652 } 653 rs = rss[0] 654 return rs, nil 655 } 656 } 657 return rs, errors.New("Service " + serviceName + " not found") 658 } 659 func (c *Controller) describeServiceVersions(serviceName string) ([]service.ServiceVersion, error) { 660 var imageName string 661 var sv []service.ServiceVersion 662 s := service.NewService() 663 s.ServiceName = serviceName 664 ecr := ecs.ECR{} 665 // get last service to know container name 666 ddLast, err := s.GetLastDeploy() 667 if err != nil { 668 return sv, err 669 } 670 // get image linked with main container 671 for _, container := range ddLast.DeployData.Containers { 672 if container.ContainerName == serviceName { 673 if container.ContainerImage != "" { 674 imageName = container.ContainerImage 675 } else { 676 imageName = serviceName 677 } 678 } 679 } 680 if imageName == "" { 681 return sv, errors.New("Couldn't find imageName for service " + serviceName) 682 } 683 // get image tags 684 tags, err := ecr.ListImagesWithTag(imageName) 685 if err != nil { 686 return sv, err 687 } 688 // populate last deployed on 689 sv, err = s.GetServiceVersionsByTags(serviceName, imageName, tags) 690 if err != nil { 691 return sv, err 692 } 693 return sv, nil 694 } 695 func (c *Controller) getDeploymentStatus(serviceName, time string) (*service.DeployResult, error) { 696 s := service.NewService() 697 dd, err := s.GetDeployment(serviceName, time) 698 if err != nil { 699 return nil, err 700 } 701 ret := &service.DeployResult{ 702 ClusterName: dd.DeployData.Cluster, 703 ServiceName: serviceName, 704 DeploymentTime: dd.Time, 705 Status: dd.Status, 706 DeployError: dd.DeployError, 707 TaskDefinitionArn: *dd.TaskDefinitionArn, 708 } 709 return ret, nil 710 } 711 func (c *Controller) getDeployment(serviceName, time string) (*service.Deploy, error) { 712 s := service.NewService() 713 dd, err := s.GetDeployment(serviceName, time) 714 if err != nil { 715 return nil, err 716 } 717 return dd.DeployData, nil 718 } 719 func (c *Controller) getServiceParameters(serviceName, userId, creds string) (map[string]ecs.Parameter, string, error) { 720 var err error 721 p := ecs.Paramstore{} 722 role := util.GetEnv("PARAMSTORE_ASSUME_ROLE", "") 723 if role != "" { 724 creds, err = p.AssumeRole(role, userId, creds) 725 if err != nil { 726 return p.Parameters, creds, err 727 } 728 } 729 err = p.GetParameters("/"+util.GetEnv("PARAMSTORE_PREFIX", "")+"-"+util.GetEnv("AWS_ACCOUNT_ENV", "")+"/"+serviceName+"/", false) 730 if err != nil { 731 return p.Parameters, creds, err 732 } 733 return p.Parameters, creds, nil 734 } 735 func (c *Controller) putServiceParameter(serviceName, userId, creds string, parameter service.DeployServiceParameter) (map[string]int64, string, error) { 736 var err error 737 p := ecs.Paramstore{} 738 res := make(map[string]int64) 739 role := util.GetEnv("PARAMSTORE_ASSUME_ROLE", "") 740 if role != "" { 741 creds, err = p.AssumeRole(role, userId, creds) 742 if err != nil { 743 return res, creds, err 744 } 745 } 746 version, err := p.PutParameter(serviceName, parameter) 747 748 res["version"] = *version 749 750 return res, creds, err 751 } 752 func (c *Controller) deleteServiceParameter(serviceName, userId, creds, parameter string) (string, error) { 753 var err error 754 p := ecs.Paramstore{} 755 role := util.GetEnv("PARAMSTORE_ASSUME_ROLE", "") 756 if role != "" { 757 creds, err = p.AssumeRole(role, userId, creds) 758 if err != nil { 759 return creds, err 760 } 761 } 762 err = p.DeleteParameter(serviceName, parameter) 763 764 return creds, err 765 } 766 767 func (c *Controller) deleteService(serviceName string) error { 768 var ds *service.DynamoServices 769 var clusterName string 770 s := service.NewService() 771 err := s.GetServices(ds) 772 if err != nil { 773 return err 774 } 775 for _, v := range ds.Services { 776 if v.S == serviceName { 777 clusterName = v.C 778 } 779 } 780 alb, err := ecs.NewALB(clusterName) 781 if err != nil { 782 return err 783 } 784 targetGroupArn, err := alb.GetTargetGroupArn(serviceName) 785 if err != nil { 786 return err 787 } 788 err = alb.DeleteTargetGroup(*targetGroupArn) 789 if err != nil { 790 return err 791 } 792 return nil 793 } 794 func (c *Controller) scaleService(serviceName string, desiredCount int64) error { 795 s := service.NewService() 796 s.ServiceName = serviceName 797 clusterName, err := s.GetClusterName() 798 if err != nil { 799 return err 800 } 801 s.SetScalingProperty(desiredCount) 802 e := ecs.ECS{} 803 e.ManualScaleService(clusterName, serviceName, desiredCount) 804 return nil 805 } 806 807 func (c *Controller) runTask(serviceName string, runTask service.RunTask) (string, error) { 808 s := service.NewService() 809 s.ServiceName = serviceName 810 var taskArn string 811 clusterName, err := s.GetClusterName() 812 if err != nil { 813 return taskArn, err 814 } 815 dd, err := s.GetLastDeploy() 816 if err != nil { 817 return taskArn, err 818 } 819 e := ecs.ECS{} 820 taskDefinition, err := e.GetTaskDefinition(clusterName, serviceName) 821 if err != nil { 822 return taskArn, err 823 } 824 taskArn, err = e.RunTask(clusterName, taskDefinition, runTask, *dd.DeployData) 825 if err != nil { 826 return taskArn, err 827 } 828 err = s.SetManualTasksArn(taskArn) 829 if err != nil { 830 return taskArn, err 831 } 832 return taskArn, nil 833 } 834 func (c *Controller) describeTaskDefinition(serviceName string) (ecs.TaskDefinition, error) { 835 var taskDefinition ecs.TaskDefinition 836 s := service.NewService() 837 s.ServiceName = serviceName 838 clusterName, err := s.GetClusterName() 839 if err != nil { 840 return taskDefinition, err 841 } 842 e := ecs.ECS{} 843 taskDefinitionName, err := e.GetTaskDefinition(clusterName, serviceName) 844 if err != nil { 845 return taskDefinition, err 846 } 847 taskDefinition, err = e.DescribeTaskDefinition(taskDefinitionName) 848 if err != nil { 849 return taskDefinition, err 850 } 851 return taskDefinition, nil 852 } 853 func (c *Controller) listTasks(serviceName string) ([]service.RunningTask, error) { 854 var tasks []service.RunningTask 855 var taskArns []*string 856 s := service.NewService() 857 s.ServiceName = serviceName 858 clusterName, err := s.GetClusterName() 859 if err != nil { 860 return tasks, err 861 } 862 e := ecs.ECS{} 863 runningTasks, err := e.ListTasks(clusterName, serviceName, "RUNNING", "family") 864 if err != nil { 865 return tasks, err 866 } 867 stoppedTasks, err := e.ListTasks(clusterName, serviceName, "STOPPED", "family") 868 if err != nil { 869 return tasks, err 870 } 871 taskArns = append(taskArns, runningTasks...) 872 taskArns = append(taskArns, stoppedTasks...) 873 tasks, err = e.DescribeTasks(clusterName, taskArns) 874 if err != nil { 875 return tasks, err 876 } 877 return tasks, nil 878 } 879 func (c *Controller) getServiceLogs(serviceName, taskArn, containerName string, start, end time.Time) (ecs.CloudWatchLog, error) { 880 cw := ecs.CloudWatch{} 881 return cw.GetLogEventsByTime(util.GetEnv("CLOUDWATCH_LOGS_PREFIX", "")+"-"+util.GetEnv("AWS_ACCOUNT_ENV", ""), containerName+"/"+containerName+"/"+taskArn, start, end, "") 882 } 883 884 func (c *Controller) Resume() error { 885 migration := Migration{} 886 s := service.NewService() 887 // check api version of database 888 dbApiVersion, err := s.GetApiVersion() 889 if err != nil { 890 if err.Error() == "dynamo: no item found" { 891 controllerLogger.Infof("Database is empty - starting app for the first time") 892 err = s.InitDB(apiVersion) 893 if err != nil { 894 return err 895 } 896 } else { 897 return err 898 } 899 } 900 if dbApiVersion != migration.getApiVersion() { 901 err := migration.run(dbApiVersion) 902 if err != nil { 903 return err 904 } 905 } 906 907 // check whether anything needs to be resumed 908 e := ecs.ECS{} 909 dds, err := s.GetDeploys("byDay", 20) 910 if err != nil { 911 return err 912 } 913 for i, dd := range dds { 914 if dd.Status == "running" { 915 // run goroutine to update status of service 916 controllerLogger.Infof("Starting waitUntilServiceStable for %v", dd.ServiceName) 917 var ddLast service.DynamoDeployment 918 if i == 0 { 919 ddLast = dds[i] 920 } else { 921 ddLast = dds[i-1] 922 } 923 var notification integrations.Notification 924 if util.GetEnv("SLACK_WEBHOOKS", "") != "" { 925 notification = integrations.NewSlack() 926 } else { 927 notification = integrations.NewDummy() 928 } 929 go e.LaunchWaitUntilServicesStable(&dds[i], &ddLast, notification) 930 931 } 932 } 933 // check for nodes draining 934 autoscaling := ecs.AutoScaling{} 935 services := make(map[string][]string) 936 dss, _ := c.getServices() 937 for i, ds := range dss { 938 services[ds.C] = append(services[ds.C], dss[i].S) 939 } 940 for clusterName, _ := range services { 941 var clusterNotFound bool 942 autoScalingGroupName, err := autoscaling.GetAutoScalingGroupByTag(clusterName) 943 if err != nil { 944 if strings.HasPrefix(err.Error(), "ClusterNotFound:") { 945 controllerLogger.Infof("Cluster %v not running - skipping resume for this cluster", clusterName) 946 clusterNotFound = true 947 } else { 948 return err 949 } 950 } 951 if !clusterNotFound { 952 // get cluster info 953 ciArns, err := e.ListContainerInstances(clusterName) 954 if err != nil { 955 return err 956 } 957 cis, err := e.DescribeContainerInstances(clusterName, ciArns) 958 if err != nil { 959 return err 960 } 961 // check for lifecycle hook 962 var lifecycleHookNotFound bool 963 hn, err := autoscaling.GetLifecycleHookNames(autoScalingGroupName, "autoscaling:EC2_INSTANCE_TERMINATING") 964 if err != nil || len(hn) == 0 { 965 controllerLogger.Errorf("Cluster %v doesn't have a lifecycle hook", clusterName) 966 lifecycleHookNotFound = true 967 } 968 if !lifecycleHookNotFound { 969 dc, err := s.GetClusterInfo() 970 if err != nil { 971 return err 972 } 973 for _, ci := range cis { 974 if ci.Status == "DRAINING" { 975 // write new record to switch container instance to draining (in case there's a record left with DRAINING) 976 var writeRecord bool 977 if dc != nil { 978 for i, dcci := range dc.ContainerInstances { 979 if clusterName == dcci.ClusterName && ci.Ec2InstanceId == dcci.ContainerInstanceId && dcci.Status != "DRAINING" { 980 dc.ContainerInstances[i].Status = "DRAINING" 981 writeRecord = true 982 } 983 } 984 } 985 if writeRecord { 986 s.PutClusterInfo(*dc, clusterName, "no", "") 987 } 988 // launch wait for drained 989 controllerLogger.Infof("Launching waitForDrainedNode for cluster=%v, instance=%v, autoscalingGroupName=%v", clusterName, ci.Ec2InstanceId, autoScalingGroupName) 990 go e.LaunchWaitForDrainedNode(clusterName, ci.ContainerInstanceArn, ci.Ec2InstanceId, autoScalingGroupName, hn[0], "") 991 } 992 } 993 } 994 // TODO: check for pending autoscaling actions 995 if len(cis) == 0 { 996 return errors.New("Couldn't retrieve any EC2 Container instances") 997 } 998 f, err := e.ConvertResourceToRir(cis[0].RegisteredResources) 999 if err != nil { 1000 return err 1001 } 1002 asc := AutoscalingController{} 1003 registeredInstanceCpu := f.RegisteredCpu 1004 registeredInstanceMemory := f.RegisteredMemory 1005 for _, scalingOp := range []string{"up", "down"} { 1006 period, interval := asc.getAutoscalingPeriodInterval(scalingOp) 1007 startTime := time.Now().Add(-1 * time.Duration(period) * time.Duration(interval) * time.Second) 1008 _, pendingAction, err := s.GetScalingActivity(clusterName, startTime) 1009 if err != nil { 1010 return err 1011 } 1012 if pendingAction == scalingOp { 1013 cc := &Controller{} 1014 autoscaling := &ecs.AutoScaling{} 1015 controllerLogger.Infof("Launching process for pending scaling operation: %s ", pendingAction) 1016 go asc.launchProcessPendingScalingOp(clusterName, pendingAction, registeredInstanceCpu, registeredInstanceMemory, s, cc, autoscaling) 1017 } 1018 } 1019 } 1020 } 1021 // Start autoscaling polling if enabled 1022 autoscalingStrategies := strings.Split(util.GetEnv("AUTOSCALING_STRATEGIES", ""), ",") 1023 for _, v := range autoscalingStrategies { 1024 if strings.ToLower(v) == "polling" { 1025 asc := AutoscalingController{} 1026 controllerLogger.Debugf("Starting AutoscalingPollingStrategy in goroutine") 1027 go asc.startAutoscalingPollingStrategy() 1028 } 1029 } 1030 controllerLogger.Debugf("Finished controller resume. Checked %d services", len(dds)) 1031 return err 1032 } 1033 1034 func (c *Controller) Bootstrap(b *Flags) error { 1035 var ecsDeploy = service.Deploy{ 1036 Cluster: b.ClusterName, 1037 ServiceName: "ecs-deploy", 1038 ServicePort: 8080, 1039 ServiceProtocol: "HTTP", 1040 DesiredCount: 1, 1041 MinimumHealthyPercent: 100, 1042 MaximumPercent: 200, 1043 Containers: []*service.DeployContainer{ 1044 { 1045 ContainerName: "ecs-deploy", 1046 ContainerPort: 8080, 1047 ContainerImage: "ecs-deploy", 1048 ContainerURI: "index.docker.io/in4it/ecs-deploy:latest", 1049 Essential: true, 1050 MemoryReservation: 128, 1051 CPUReservation: 64, 1052 Environment: []*service.DeployContainerEnvironment{ 1053 { 1054 Name: "PARAMSTORE_ENABLED", 1055 Value: "yes", 1056 }, 1057 }, 1058 }, 1059 }, 1060 HealthCheck: service.DeployHealthCheck{ 1061 HealthyThreshold: 3, 1062 UnhealthyThreshold: 3, 1063 Path: "/ecs-deploy/health", 1064 }, 1065 } 1066 e := ecs.ECS{} 1067 iam := ecs.IAM{} 1068 paramstore := ecs.Paramstore{} 1069 s := service.NewService() 1070 cloudwatch := ecs.CloudWatch{} 1071 autoscaling := ecs.AutoScaling{} 1072 roleName := "ecs-" + b.ClusterName 1073 instanceProfile := "ecs-" + b.ClusterName 1074 deployPassword := util.RandStringBytesMaskImprSrc(8) 1075 1076 // create dynamodb table 1077 err := s.CreateTable() 1078 if err != nil && !strings.HasPrefix(err.Error(), "ResourceInUseException") { 1079 return err 1080 } 1081 1082 // create instance profile for cluster 1083 err = iam.GetAccountId() 1084 if err != nil { 1085 return err 1086 } 1087 _, err = iam.CreateRole(roleName, iam.GetEC2IAMTrust()) 1088 if err != nil { 1089 return err 1090 } 1091 var ec2RolePolicy string 1092 if b.CloudwatchLogsEnabled { 1093 r, err := ioutil.ReadFile("templates/iam/ecs-ec2-policy-logs.json") 1094 if err != nil { 1095 return err 1096 } 1097 ec2RolePolicy = strings.Replace(string(r), "${LOGS_RESOURCE}", "arn:aws:logs:"+b.Region+":"+iam.AccountId+":log-group:"+b.CloudwatchLogsPrefix+"-"+b.Environment+":*", -1) 1098 } else { 1099 r, err := ioutil.ReadFile("templates/iam/ecs-ec2-policy.json") 1100 if err != nil { 1101 return err 1102 } 1103 ec2RolePolicy = string(r) 1104 } 1105 iam.PutRolePolicy(roleName, "ecs-ec2-policy", ec2RolePolicy) 1106 1107 // wait for role instance profile to exist 1108 err = iam.CreateInstanceProfile(roleName) 1109 if err != nil { 1110 return err 1111 } 1112 err = iam.AddRoleToInstanceProfile(roleName, roleName) 1113 if err != nil { 1114 return err 1115 } 1116 fmt.Println("Waiting until instance profile exists...") 1117 err = iam.WaitUntilInstanceProfileExists(roleName) 1118 if err != nil { 1119 return err 1120 } 1121 // import key 1122 r, err := ioutil.ReadFile(util.GetEnv("HOME", "") + "/.ssh/" + b.KeyName) 1123 if err != nil { 1124 return err 1125 } 1126 pubKey, err := e.GetPubKeyFromPrivateKey(string(r)) 1127 if err != nil { 1128 return err 1129 } 1130 e.ImportKeyPair(b.ClusterName, pubKey) 1131 1132 // create launch configuration 1133 err = autoscaling.CreateLaunchConfiguration(b.ClusterName, b.KeyName, b.InstanceType, instanceProfile, strings.Split(b.EcsSecurityGroups, ",")) 1134 if err != nil { 1135 for i := 0; i < 5 && err != nil; i++ { 1136 if strings.HasPrefix(err.Error(), "RetryableError:") { 1137 fmt.Printf("Error: %v - waiting 10s and retrying...\n", err.Error()) 1138 time.Sleep(10 * time.Second) 1139 err = autoscaling.CreateLaunchConfiguration(b.ClusterName, b.KeyName, b.InstanceType, instanceProfile, strings.Split(b.EcsSecurityGroups, ",")) 1140 } 1141 } 1142 if err != nil { 1143 return err 1144 } 1145 } 1146 1147 // create autoscaling group 1148 intEcsDesiredSize, _ := strconv.ParseInt(b.EcsDesiredSize, 10, 64) 1149 intEcsMaxSize, _ := strconv.ParseInt(b.EcsMaxSize, 10, 64) 1150 intEcsMinSize, _ := strconv.ParseInt(b.EcsMinSize, 10, 64) 1151 autoscaling.CreateAutoScalingGroup(b.ClusterName, intEcsDesiredSize, intEcsMaxSize, intEcsMinSize, strings.Split(b.EcsSubnets, ",")) 1152 if err != nil { 1153 return err 1154 } 1155 1156 // create log group 1157 if b.CloudwatchLogsEnabled { 1158 err = cloudwatch.CreateLogGroup(b.ClusterName, b.CloudwatchLogsPrefix+"-"+b.Environment) 1159 if err != nil { 1160 return err 1161 } 1162 } 1163 // create cluster 1164 clusterArn, err := e.CreateCluster(b.ClusterName) 1165 if err != nil { 1166 return err 1167 } 1168 fmt.Printf("Created ECS Cluster with ARN: %v\n", *clusterArn) 1169 if b.AlbSecurityGroups == "" || b.EcsSubnets == "" { 1170 return errors.New("Incorrect test arguments supplied") 1171 } 1172 if len(b.LoadBalancers) == 0 { 1173 b.LoadBalancers = []service.LoadBalancer{ 1174 { 1175 Name: b.ClusterName, 1176 IPAddressType: "ipv4", 1177 Scheme: "internet-facing", 1178 Type: "application", 1179 }, 1180 } 1181 } 1182 var albs []*ecs.ALB 1183 // create load balancer, default target, and listener 1184 for _, v := range b.LoadBalancers { 1185 alb, err := ecs.NewALBAndCreate(v.Name, v.IPAddressType, v.Scheme, strings.Split(b.AlbSecurityGroups, ","), strings.Split(b.EcsSubnets, ","), v.Type) 1186 if err != nil { 1187 return err 1188 } 1189 defaultTargetGroupArn, err := alb.CreateTargetGroup(v.Name+"-ecs-deploy", ecsDeploy /* ecs deploy object */) 1190 if err != nil { 1191 return err 1192 } 1193 err = alb.CreateListener("HTTP", 80, *defaultTargetGroupArn) 1194 if err != nil { 1195 return err 1196 } 1197 albs = append(albs, alb) 1198 } 1199 // create env vars 1200 if b.ParamstoreEnabled { 1201 parameters := []service.DeployServiceParameter{ 1202 {Name: "PARAMSTORE_ENABLED", Value: "yes"}, 1203 {Name: "PARAMSTORE_PREFIX", Value: b.ParamstorePrefix}, 1204 {Name: "JWT_SECRET", Value: util.RandStringBytesMaskImprSrc(32)}, 1205 {Name: "DEPLOY_PASSWORD", Value: deployPassword}, 1206 {Name: "URL_PREFIX", Value: "/ecs-deploy"}, 1207 } 1208 if b.ParamstoreKmsArn != "" { 1209 parameters = append(parameters, service.DeployServiceParameter{Name: "PARAMSTORE_KMS_ARN", Value: b.ParamstoreKmsArn}) 1210 } 1211 if b.CloudwatchLogsEnabled { 1212 parameters = append(parameters, service.DeployServiceParameter{Name: "CLOUDWATCH_LOGS_ENABLED", Value: "yes"}) 1213 parameters = append(parameters, service.DeployServiceParameter{Name: "CLOUDWATCH_LOGS_PREFIX", Value: b.CloudwatchLogsPrefix}) 1214 } 1215 paramstore.Bootstrap("ecs-deploy", b.ParamstorePrefix, b.Environment, parameters) 1216 // retrieve keys from parameter store and set as environment variable 1217 os.Setenv("PARAMSTORE_ENABLED", "yes") 1218 err = paramstore.RetrieveKeys() 1219 if err != nil { 1220 return err 1221 } 1222 } 1223 1224 // wait for autoscaling group to be in service 1225 fmt.Println("Waiting for autoscaling group to be in service...") 1226 err = autoscaling.WaitForAutoScalingGroupInService(b.ClusterName) 1227 if err != nil { 1228 return err 1229 } 1230 if !b.DisableEcsDeploy { 1231 iamRoleArn, err := iam.RoleExists("ecs-ecs-deploy") 1232 if err == nil && iamRoleArn == nil { 1233 _, err := iam.CreateRole("ecs-ecs-deploy", iam.GetEcsTaskIAMTrust()) 1234 if err != nil { 1235 return err 1236 } 1237 } 1238 r, err := ioutil.ReadFile("templates/iam/ecs-deploy-task.json") 1239 if err != nil { 1240 return err 1241 } 1242 ecsDeployRolePolicy := strings.Replace(string(r), "${ACCOUNT_ID}", iam.AccountId, -1) 1243 ecsDeployRolePolicy = strings.Replace(ecsDeployRolePolicy, "${AWS_REGION}", b.Region, -1) 1244 err = iam.PutRolePolicy("ecs-ecs-deploy", "ecs-deploy", ecsDeployRolePolicy) 1245 if err != nil { 1246 return err 1247 } 1248 _, err = c.Deploy(ecsDeploy.ServiceName, ecsDeploy) 1249 s.ServiceName = ecsDeploy.ServiceName 1250 var deployed bool 1251 for i := 0; i < 30 && !deployed; i++ { 1252 dd, err := s.GetLastDeploy() 1253 if err != nil { 1254 return err 1255 } 1256 if dd != nil && dd.Status == "success" { 1257 deployed = true 1258 } else if dd != nil && dd.Status == "failed" { 1259 return errors.New("Deployment of ecs-deploy failed") 1260 } else { 1261 fmt.Printf("Waiting for %v to to be deployed (status: %v)\n", ecsDeploy.ServiceName, dd.Status) 1262 time.Sleep(30 * time.Second) 1263 } 1264 } 1265 } 1266 fmt.Println("") 1267 fmt.Println("===============================================") 1268 fmt.Println("=== Successfully bootstrapped ecs-deploy ===") 1269 fmt.Println("===============================================") 1270 for _, alb := range albs { 1271 fmt.Printf(" URL: http://%v/ecs-deploy \n", alb.DnsName) 1272 } 1273 fmt.Printf(" Login: deploy \n") 1274 fmt.Printf(" Password: %v \n", deployPassword) 1275 fmt.Println("===============================================") 1276 fmt.Println("") 1277 return nil 1278 } 1279 1280 func (c *Controller) DeleteCluster(b *Flags) error { 1281 iam := ecs.IAM{} 1282 e := ecs.ECS{} 1283 autoscaling := ecs.AutoScaling{} 1284 clusterName := b.ClusterName 1285 roleName := "ecs-" + clusterName 1286 cloudwatch := ecs.CloudWatch{} 1287 err := autoscaling.DeleteAutoScalingGroup(clusterName, true) 1288 if err != nil { 1289 return err 1290 } 1291 err = autoscaling.DeleteLaunchConfiguration(clusterName) 1292 if err != nil { 1293 return err 1294 } 1295 err = e.DeleteKeyPair(clusterName) 1296 if err != nil { 1297 return err 1298 } 1299 err = iam.DeleteRolePolicy(roleName, "ecs-ec2-policy") 1300 if err != nil { 1301 return err 1302 } 1303 err = iam.RemoveRoleFromInstanceProfile(roleName, roleName) 1304 if err != nil { 1305 return err 1306 } 1307 err = iam.DeleteInstanceProfile(roleName) 1308 if err != nil { 1309 return err 1310 } 1311 err = iam.DeleteRole(roleName) 1312 if err != nil { 1313 return err 1314 } 1315 if len(b.LoadBalancers) == 0 { 1316 b.LoadBalancers = []service.LoadBalancer{ 1317 { 1318 Name: b.ClusterName, 1319 IPAddressType: "ipv4", 1320 Scheme: "internet-facing", 1321 Type: "application", 1322 }, 1323 } 1324 } 1325 for _, v := range b.LoadBalancers { 1326 alb, err := ecs.NewALB(v.Name) 1327 if err != nil { 1328 return err 1329 } 1330 for _, v := range alb.Listeners { 1331 err = alb.DeleteListener(*v.ListenerArn) 1332 if err != nil { 1333 return err 1334 } 1335 } 1336 serviceArns, err := e.ListServices(clusterName) 1337 if err != nil { 1338 return err 1339 } 1340 services, err := e.DescribeServices(clusterName, serviceArns, false, false, false) 1341 for _, v := range services { 1342 targetGroup, _ := alb.GetTargetGroupArn(v.ServiceName) 1343 if targetGroup != nil { 1344 alb.DeleteTargetGroup(*targetGroup) 1345 } 1346 err = e.DeleteService(clusterName, v.ServiceName) 1347 if err != nil { 1348 return err 1349 } 1350 err = e.WaitUntilServicesInactive(clusterName, v.ServiceName) 1351 if err != nil { 1352 return err 1353 } 1354 } 1355 err = alb.DeleteLoadBalancer() 1356 if err != nil { 1357 return err 1358 } 1359 } 1360 fmt.Println("Wait for autoscaling group to not exist") 1361 err = autoscaling.WaitForAutoScalingGroupNotExists(clusterName) 1362 if err != nil { 1363 return err 1364 } 1365 var drained bool 1366 fmt.Println("Waiting for EC2 instances to drain from ECS cluster") 1367 for i := 0; i < 5 && !drained; i++ { 1368 instanceArns, err := e.ListContainerInstances(clusterName) 1369 if err != nil { 1370 return err 1371 } 1372 if len(instanceArns) == 0 { 1373 drained = true 1374 } else { 1375 time.Sleep(5 * time.Second) 1376 } 1377 } 1378 err = e.DeleteCluster(clusterName) 1379 if err != nil { 1380 return err 1381 } 1382 err = cloudwatch.DeleteLogGroup(b.CloudwatchLogsPrefix + "-" + b.Environment) 1383 if err != nil { 1384 return err 1385 } 1386 return nil 1387 } 1388 1389 func (c *Controller) putServiceAutoscaling(serviceName string, autoscaling service.Autoscaling) (string, error) { 1390 var result string 1391 var writeChanges bool 1392 // validation 1393 if autoscaling.MinimumCount == 0 && autoscaling.MaximumCount == 0 { 1394 return result, errors.New("minimumCount / maximumCount missing") 1395 } 1396 // autoscaling 1397 as := ecs.AutoScaling{} 1398 cloudwatch := ecs.CloudWatch{} 1399 iam := ecs.IAM{} 1400 s := service.NewService() 1401 s.ServiceName = serviceName 1402 clusterName, err := s.GetClusterName() 1403 if err != nil { 1404 return result, err 1405 } 1406 dd, err := s.GetLastDeploy() 1407 if err != nil { 1408 return result, err 1409 } 1410 resourceId := "service/" + clusterName + "/" + serviceName 1411 // check whether iam role exists 1412 var autoscalingRoleArn *string 1413 autoscalingRoleName := "ecs-app-autoscaling-role" 1414 autoscalingRoleArn, err = iam.RoleExists(autoscalingRoleName) 1415 if err != nil { 1416 return result, err 1417 } 1418 if err == nil && autoscalingRoleArn == nil { 1419 autoscalingRoleArn, err = iam.CreateRole(autoscalingRoleName, iam.GetEcsAppAutoscalingIAMTrust()) 1420 if err != nil { 1421 return result, err 1422 } 1423 err = iam.AttachRolePolicy(autoscalingRoleName, "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole") 1424 if err != nil { 1425 return result, err 1426 } 1427 } 1428 // register scalable target 1429 if dd.Scaling.Autoscaling.ResourceId == "" { 1430 err = as.RegisterScalableTarget(autoscaling.MinimumCount, autoscaling.MaximumCount, resourceId, *autoscalingRoleArn) 1431 if err != nil { 1432 return result, err 1433 } 1434 } else { 1435 // describe -> check difference, apply difference 1436 a, err := as.DescribeScalableTargets([]string{resourceId}) 1437 if err != nil { 1438 return result, err 1439 } 1440 if len(a) == 0 { 1441 return result, errors.New("Couldn't describe scalable target") 1442 } 1443 if a[0].MinimumCount != autoscaling.MinimumCount || a[0].MaximumCount != autoscaling.MaximumCount { 1444 err = as.RegisterScalableTarget(autoscaling.MinimumCount, autoscaling.MaximumCount, resourceId, *autoscalingRoleArn) 1445 if err != nil { 1446 return result, err 1447 } 1448 } 1449 } 1450 // change desired count if necessary 1451 if dd.Scaling.DesiredCount != autoscaling.DesiredCount { 1452 e := ecs.ECS{} 1453 e.ManualScaleService(clusterName, serviceName, autoscaling.DesiredCount) 1454 writeChanges = true 1455 } 1456 // Add Autoscaling policy 1457 if len(autoscaling.Policies) > 0 { 1458 for _, p := range autoscaling.Policies { 1459 // autoscaling up or down? 1460 var autoscalingType string 1461 if p.ScalingAdjustment > 0 { 1462 autoscalingType = "up" 1463 } else { 1464 autoscalingType = "down" 1465 } 1466 // metric name 1467 var metricName, metricNamespace string 1468 if p.Metric == "cpu" { 1469 metricName = "CPUUtilization" 1470 metricNamespace = "AWS/ECS" 1471 } else { 1472 metricName = "MemoryUtilization" 1473 metricNamespace = "AWS/ECS" 1474 } 1475 // autoscaling policies 1476 autoscalingPolicyArns := make(map[string]struct{}) 1477 for _, v := range dd.Scaling.Autoscaling.PolicyNames { 1478 autoscalingPolicyArns[v] = struct{}{} 1479 } 1480 // set policy name 1481 policyName := serviceName + "-" + p.Metric + "-" + autoscalingType 1482 if _, exists := autoscalingPolicyArns[policyName]; !exists { 1483 // put scaling policy 1484 scalingPolicyArn, err := as.PutScalingPolicy(policyName, resourceId, 300, p.ScalingAdjustment) 1485 if err != nil { 1486 return result, err 1487 } 1488 // put metric alarm 1489 err = cloudwatch.PutMetricAlarm(serviceName, clusterName, policyName, []string{scalingPolicyArn}, policyName, p.DatapointsToAlarm, metricName, metricNamespace, p.Period, p.Threshold, strings.Title(p.ComparisonOperator), strings.Title(p.ThresholdStatistic), p.EvaluationPeriods) 1490 if err != nil { 1491 return result, err 1492 } 1493 // write changes to the database 1494 writeChanges = true 1495 dd.Scaling.Autoscaling.PolicyNames = append(dd.Scaling.Autoscaling.PolicyNames, policyName) 1496 } 1497 } 1498 } 1499 1500 if writeChanges { 1501 err = s.SetAutoscalingProperties(autoscaling.DesiredCount, resourceId, dd.Scaling.Autoscaling.PolicyNames) 1502 if err != nil { 1503 return result, err 1504 } 1505 } 1506 1507 return "OK", nil 1508 } 1509 func (c *Controller) getServiceAutoscaling(serviceName string) (service.Autoscaling, error) { 1510 var a service.Autoscaling 1511 e := ecs.ECS{} 1512 s := service.NewService() 1513 s.ServiceName = serviceName 1514 clusterName, err := s.GetClusterName() 1515 autoscaling := ecs.AutoScaling{} 1516 cloudwatch := ecs.CloudWatch{} 1517 1518 // get last deploy 1519 dd, err := s.GetLastDeploy() 1520 if err != nil { 1521 return a, err 1522 } 1523 1524 if dd.Scaling.Autoscaling.ResourceId == "" { 1525 return a, nil 1526 } 1527 1528 // get min, max capacity 1529 as, err := autoscaling.DescribeScalableTargets([]string{dd.Scaling.Autoscaling.ResourceId}) 1530 if err != nil { 1531 return a, err 1532 } 1533 if len(as) == 0 { 1534 return a, errors.New("No scalable target returned") 1535 } 1536 a = as[0] 1537 1538 // get desiredCount 1539 runningService, err := e.DescribeService(clusterName, serviceName, false, false, false) 1540 if err != nil { 1541 return a, err 1542 } 1543 a.DesiredCount = runningService.DesiredCount 1544 1545 // get policy 1546 apsPolicy, err := autoscaling.DescribeScalingPolicies(dd.Scaling.Autoscaling.PolicyNames, dd.Scaling.Autoscaling.ResourceId) 1547 if err != nil { 1548 return a, err 1549 } 1550 // get alarm 1551 aps, err := cloudwatch.DescribeAlarms(dd.Scaling.Autoscaling.PolicyNames) 1552 if err != nil { 1553 return a, err 1554 } 1555 1556 for k, v := range aps { 1557 for _, v2 := range apsPolicy { 1558 if v.PolicyName == v2.PolicyName { 1559 aps[k].ScalingAdjustment = v2.ScalingAdjustment 1560 } 1561 } 1562 } 1563 1564 a.Policies = aps 1565 1566 return a, nil 1567 } 1568 func (c *Controller) deleteServiceAutoscalingPolicy(serviceName, policyName string) error { 1569 s := service.NewService() 1570 s.ServiceName = serviceName 1571 autoscaling := ecs.AutoScaling{} 1572 cloudwatch := ecs.CloudWatch{} 1573 1574 // get last deploy 1575 dd, err := s.GetLastDeploy() 1576 if err != nil { 1577 return err 1578 } 1579 1580 if dd.Scaling.Autoscaling.ResourceId == "" { 1581 return errors.New("Autoscaling not active for service") 1582 } 1583 1584 var newPolicyNames []string 1585 var found bool 1586 for _, v := range dd.Scaling.Autoscaling.PolicyNames { 1587 if v == policyName { 1588 found = true 1589 } else { 1590 newPolicyNames = append(newPolicyNames, v) 1591 } 1592 } 1593 if !found { 1594 return fmt.Errorf("Autoscaling policy %v not found", policyName) 1595 } 1596 1597 err = autoscaling.DeleteScalingPolicy(policyName, dd.Scaling.Autoscaling.ResourceId) 1598 if err != nil { 1599 return err 1600 } 1601 1602 err = cloudwatch.DeleteAlarms([]string{policyName}) 1603 if err != nil { 1604 return err 1605 } 1606 1607 // write changes to db 1608 err = s.SetAutoscalingProperties(dd.Scaling.DesiredCount, dd.Scaling.Autoscaling.ResourceId, newPolicyNames) 1609 if err != nil { 1610 return err 1611 } 1612 1613 return nil 1614 } 1615 1616 func (c *Controller) deleteServiceAutoscaling(serviceName string) error { 1617 s := service.NewService() 1618 s.ServiceName = serviceName 1619 autoscaling := ecs.AutoScaling{} 1620 cloudwatch := ecs.CloudWatch{} 1621 1622 // get last deploy 1623 dd, err := s.GetLastDeploy() 1624 if err != nil { 1625 return err 1626 } 1627 1628 if dd.Scaling.Autoscaling.ResourceId == "" { 1629 return errors.New("Autoscaling not active for service") 1630 } 1631 1632 for _, policyName := range dd.Scaling.Autoscaling.PolicyNames { 1633 err = autoscaling.DeleteScalingPolicy(policyName, dd.Scaling.Autoscaling.ResourceId) 1634 if err != nil { 1635 return err 1636 } 1637 1638 err = cloudwatch.DeleteAlarms([]string{policyName}) 1639 if err != nil { 1640 return err 1641 } 1642 } 1643 1644 err = autoscaling.DeregisterScalableTarget(dd.Scaling.Autoscaling.ResourceId) 1645 if err != nil { 1646 return err 1647 } 1648 1649 // write changes to db 1650 err = s.SetAutoscalingProperties(dd.Scaling.DesiredCount, "", []string{}) 1651 if err != nil { 1652 return err 1653 } 1654 1655 return nil 1656 }