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  }