github.com/in4it/ecs-deploy@v0.0.42-0.20240508120354-ed77ff16df25/provider/ecs/autoscaling.go (about)

     1  package ecs
     2  
     3  import (
     4  	"github.com/aws/aws-sdk-go/aws"
     5  	"github.com/aws/aws-sdk-go/aws/awserr"
     6  	"github.com/aws/aws-sdk-go/aws/session"
     7  	"github.com/aws/aws-sdk-go/service/applicationautoscaling"
     8  	"github.com/aws/aws-sdk-go/service/autoscaling"
     9  	"github.com/in4it/ecs-deploy/service"
    10  	"github.com/juju/loggo"
    11  
    12  	"encoding/base64"
    13  	"errors"
    14  	"strings"
    15  )
    16  
    17  // logging
    18  var autoscalingLogger = loggo.GetLogger("autoscaling")
    19  
    20  // ECR struct
    21  type AutoScaling struct {
    22  }
    23  
    24  type AutoScalingIf interface {
    25  	GetAutoScalingGroupByTag(clusterName string) (string, error)
    26  	ScaleClusterNodes(autoScalingGroupName string, change int64) error
    27  }
    28  
    29  func (a *AutoScaling) CompleteLifecycleAction(autoScalingGroupName, instanceId, action, lifecycleHookName, lifecycleToken string) error {
    30  	svc := autoscaling.New(session.New())
    31  	input := &autoscaling.CompleteLifecycleActionInput{
    32  		AutoScalingGroupName:  aws.String(autoScalingGroupName),
    33  		InstanceId:            aws.String(instanceId),
    34  		LifecycleActionResult: aws.String(action),
    35  		LifecycleActionToken:  aws.String(lifecycleToken),
    36  		LifecycleHookName:     aws.String(lifecycleHookName),
    37  	}
    38  
    39  	_, err := svc.CompleteLifecycleAction(input)
    40  	if err != nil {
    41  		if aerr, ok := err.(awserr.Error); ok {
    42  			ecsLogger.Errorf("%v", aerr.Error())
    43  		} else {
    44  			ecsLogger.Errorf("%v", err.Error())
    45  		}
    46  		return err
    47  	}
    48  	return nil
    49  }
    50  func (a *AutoScaling) CompletePendingLifecycleAction(autoScalingGroupName, instanceId, action, lifecycleHookName string) error {
    51  	svc := autoscaling.New(session.New())
    52  	input := &autoscaling.CompleteLifecycleActionInput{
    53  		AutoScalingGroupName:  aws.String(autoScalingGroupName),
    54  		InstanceId:            aws.String(instanceId),
    55  		LifecycleActionResult: aws.String(action),
    56  		LifecycleHookName:     aws.String(lifecycleHookName),
    57  	}
    58  
    59  	autoscalingLogger.Debugf("Running CompleteLifecycleAction with input: %+v", input)
    60  
    61  	_, err := svc.CompleteLifecycleAction(input)
    62  	if err != nil {
    63  		if aerr, ok := err.(awserr.Error); ok {
    64  			ecsLogger.Errorf("%v", aerr.Error())
    65  		} else {
    66  			ecsLogger.Errorf("%v", err.Error())
    67  		}
    68  		return err
    69  	}
    70  	return nil
    71  }
    72  func (a *AutoScaling) GetLifecycleHookNames(autoScalingGroupName, lifecycleHookType string) ([]string, error) {
    73  	var lifecycleHookNames []string
    74  	svc := autoscaling.New(session.New())
    75  	input := &autoscaling.DescribeLifecycleHooksInput{
    76  		AutoScalingGroupName: aws.String(autoScalingGroupName),
    77  	}
    78  
    79  	result, err := svc.DescribeLifecycleHooks(input)
    80  	if err != nil {
    81  		if aerr, ok := err.(awserr.Error); ok {
    82  			ecsLogger.Errorf("%v", aerr.Error())
    83  		} else {
    84  			ecsLogger.Errorf("%v", err.Error())
    85  		}
    86  		return lifecycleHookNames, err
    87  	}
    88  	if len(result.LifecycleHooks) == 0 {
    89  		return lifecycleHookNames, errors.New("No life cycle hooks returned")
    90  	}
    91  	for _, v := range result.LifecycleHooks {
    92  		if aws.StringValue(v.LifecycleTransition) == lifecycleHookType {
    93  			lifecycleHookNames = append(lifecycleHookNames, aws.StringValue(v.LifecycleHookName))
    94  		}
    95  	}
    96  	return lifecycleHookNames, nil
    97  }
    98  
    99  func (a *AutoScaling) CreateLaunchConfiguration(clusterName string, keyName string, instanceType string, instanceProfile string, securitygroups []string) error {
   100  	ecs := ECS{}
   101  	svc := autoscaling.New(session.New())
   102  	amiId, err := ecs.GetECSAMI()
   103  	if err != nil {
   104  		return err
   105  	}
   106  	input := &autoscaling.CreateLaunchConfigurationInput{
   107  		IamInstanceProfile:      aws.String(instanceProfile),
   108  		ImageId:                 aws.String(amiId),
   109  		InstanceType:            aws.String(instanceType),
   110  		KeyName:                 aws.String(keyName),
   111  		LaunchConfigurationName: aws.String(clusterName),
   112  		SecurityGroups:          aws.StringSlice(securitygroups),
   113  		UserData:                aws.String(base64.StdEncoding.EncodeToString([]byte("#!/bin/bash\necho 'ECS_CLUSTER=" + clusterName + "'  > /etc/ecs/ecs.config\nstart ecs\n"))),
   114  	}
   115  	ecsLogger.Debugf("createLaunchConfiguration with: %+v", input)
   116  	_, err = svc.CreateLaunchConfiguration(input)
   117  	if err != nil {
   118  		if aerr, ok := err.(awserr.Error); ok {
   119  			if strings.Contains(aerr.Message(), "Invalid IamInstanceProfile") {
   120  				ecsLogger.Debugf("Caught RetryableError: %v", aerr.Message())
   121  				return errors.New("RetryableError: Invalid IamInstanceProfile")
   122  			} else {
   123  				ecsLogger.Errorf("%v", aerr.Error())
   124  			}
   125  		} else {
   126  			ecsLogger.Errorf("%v", err.Error())
   127  		}
   128  		return err
   129  	}
   130  	return nil
   131  }
   132  func (a *AutoScaling) DeleteLaunchConfiguration(clusterName string) error {
   133  	svc := autoscaling.New(session.New())
   134  	input := &autoscaling.DeleteLaunchConfigurationInput{
   135  		LaunchConfigurationName: aws.String(clusterName),
   136  	}
   137  	_, err := svc.DeleteLaunchConfiguration(input)
   138  	if err != nil {
   139  		if aerr, ok := err.(awserr.Error); ok {
   140  			ecsLogger.Errorf("%v", aerr.Error())
   141  		} else {
   142  			ecsLogger.Errorf("%v", err.Error())
   143  		}
   144  		return err
   145  	}
   146  	return nil
   147  }
   148  func (a *AutoScaling) CreateAutoScalingGroup(clusterName string, desiredCapacity int64, maxSize int64, minSize int64, subnets []string) error {
   149  	svc := autoscaling.New(session.New())
   150  	input := &autoscaling.CreateAutoScalingGroupInput{
   151  		AutoScalingGroupName:    aws.String(clusterName),
   152  		DesiredCapacity:         aws.Int64(desiredCapacity),
   153  		HealthCheckType:         aws.String("EC2"),
   154  		LaunchConfigurationName: aws.String(clusterName),
   155  		MaxSize:                 aws.Int64(maxSize),
   156  		MinSize:                 aws.Int64(minSize),
   157  		Tags: []*autoscaling.Tag{
   158  			{Key: aws.String("Name"), Value: aws.String("ecs-" + clusterName), PropagateAtLaunch: aws.Bool(true)},
   159  			{Key: aws.String("Cluster"), Value: aws.String(clusterName), PropagateAtLaunch: aws.Bool(true)},
   160  		},
   161  		TerminationPolicies: []*string{aws.String("OldestLaunchConfiguration"), aws.String("Default")},
   162  		VPCZoneIdentifier:   aws.String(strings.Join(subnets, ",")),
   163  	}
   164  	_, err := svc.CreateAutoScalingGroup(input)
   165  	if err != nil {
   166  		if aerr, ok := err.(awserr.Error); ok {
   167  			ecsLogger.Errorf("%v", aerr.Error())
   168  		} else {
   169  			ecsLogger.Errorf("%v", err.Error())
   170  		}
   171  		return err
   172  	}
   173  	return nil
   174  }
   175  func (a *AutoScaling) WaitForAutoScalingGroupInService(clusterName string) error {
   176  	svc := autoscaling.New(session.New())
   177  	input := &autoscaling.DescribeAutoScalingGroupsInput{
   178  		AutoScalingGroupNames: []*string{aws.String(clusterName)},
   179  	}
   180  	err := svc.WaitUntilGroupInService(input)
   181  	if err != nil {
   182  		if aerr, ok := err.(awserr.Error); ok {
   183  			ecsLogger.Errorf("%v", aerr.Error())
   184  		} else {
   185  			ecsLogger.Errorf("%v", err.Error())
   186  		}
   187  		return err
   188  	}
   189  	return nil
   190  }
   191  func (a *AutoScaling) WaitForAutoScalingGroupNotExists(clusterName string) error {
   192  	svc := autoscaling.New(session.New())
   193  	input := &autoscaling.DescribeAutoScalingGroupsInput{
   194  		AutoScalingGroupNames: []*string{aws.String(clusterName)},
   195  	}
   196  	err := svc.WaitUntilGroupNotExists(input)
   197  	if err != nil {
   198  		if aerr, ok := err.(awserr.Error); ok {
   199  			ecsLogger.Errorf("%v", aerr.Error())
   200  		} else {
   201  			ecsLogger.Errorf("%v", err.Error())
   202  		}
   203  		return err
   204  	}
   205  	return nil
   206  }
   207  func (a *AutoScaling) DeleteAutoScalingGroup(clusterName string, forceDelete bool) error {
   208  	svc := autoscaling.New(session.New())
   209  	input := &autoscaling.DeleteAutoScalingGroupInput{
   210  		AutoScalingGroupName: aws.String(clusterName),
   211  		ForceDelete:          aws.Bool(forceDelete),
   212  	}
   213  	_, err := svc.DeleteAutoScalingGroup(input)
   214  	if err != nil {
   215  		if aerr, ok := err.(awserr.Error); ok {
   216  			ecsLogger.Errorf("%v", aerr.Error())
   217  		} else {
   218  			ecsLogger.Errorf("%v", err.Error())
   219  		}
   220  		return err
   221  	}
   222  	return nil
   223  }
   224  func (a *AutoScaling) ScaleClusterNodes(autoScalingGroupName string, change int64) error {
   225  	minSize, desiredCapacity, maxSize, err := a.GetClusterNodeDesiredCount(autoScalingGroupName)
   226  	if err != nil {
   227  		return err
   228  	}
   229  	if change > 0 && desiredCapacity == maxSize {
   230  		return errors.New("Cluster is at maximum capacity")
   231  	}
   232  	if change < 0 && desiredCapacity == minSize {
   233  		return errors.New("Cluster is at minimum capacity")
   234  	}
   235  
   236  	svc := autoscaling.New(session.New())
   237  	input := &autoscaling.UpdateAutoScalingGroupInput{
   238  		AutoScalingGroupName: aws.String(autoScalingGroupName),
   239  		DesiredCapacity:      aws.Int64(desiredCapacity + change),
   240  	}
   241  	_, err = svc.UpdateAutoScalingGroup(input)
   242  	if err != nil {
   243  		if aerr, ok := err.(awserr.Error); ok {
   244  			ecsLogger.Errorf("%v", aerr.Error())
   245  		} else {
   246  			ecsLogger.Errorf("%v", err.Error())
   247  		}
   248  		return err
   249  	}
   250  	return nil
   251  }
   252  func (a *AutoScaling) GetClusterNodeDesiredCount(autoScalingGroupName string) (int64, int64, int64, error) {
   253  	svc := autoscaling.New(session.New())
   254  	input := &autoscaling.DescribeAutoScalingGroupsInput{
   255  		AutoScalingGroupNames: []*string{aws.String(autoScalingGroupName)},
   256  	}
   257  	result, err := svc.DescribeAutoScalingGroups(input)
   258  	if err != nil {
   259  		if aerr, ok := err.(awserr.Error); ok {
   260  			ecsLogger.Errorf("%v", aerr.Error())
   261  		} else {
   262  			ecsLogger.Errorf("%v", err.Error())
   263  		}
   264  		return 0, 0, 0, err
   265  	}
   266  	if len(result.AutoScalingGroups) == 0 {
   267  		return 0, 0, 0, errors.New("No autoscaling groups returned")
   268  	}
   269  
   270  	return aws.Int64Value(result.AutoScalingGroups[0].MinSize),
   271  		aws.Int64Value(result.AutoScalingGroups[0].DesiredCapacity),
   272  		aws.Int64Value(result.AutoScalingGroups[0].MaxSize),
   273  		nil
   274  }
   275  func (a *AutoScaling) GetAutoScalingGroupByTag(clusterName string) (string, error) {
   276  	var result string
   277  	svc := autoscaling.New(session.New())
   278  	input := &autoscaling.DescribeAutoScalingGroupsInput{}
   279  	pageNum := 0
   280  	err := svc.DescribeAutoScalingGroupsPages(input,
   281  		func(page *autoscaling.DescribeAutoScalingGroupsOutput, lastPage bool) bool {
   282  			pageNum++
   283  			for _, v := range page.AutoScalingGroups {
   284  				for _, tag := range v.Tags {
   285  					if aws.StringValue(tag.Key) == "Cluster" && aws.StringValue(tag.Value) == clusterName {
   286  						result = aws.StringValue(v.AutoScalingGroupName)
   287  					}
   288  				}
   289  			}
   290  			return pageNum <= 100
   291  		})
   292  
   293  	if err != nil {
   294  		if aerr, ok := err.(awserr.Error); ok {
   295  			ecsLogger.Errorf(aerr.Error())
   296  		} else {
   297  			ecsLogger.Errorf(err.Error())
   298  		}
   299  	}
   300  	if result == "" {
   301  		return result, errors.New("ClusterNotFound: Could not find cluster by tag key=Cluster,Value=" + clusterName)
   302  	}
   303  	return result, nil
   304  }
   305  
   306  func (a *AutoScaling) RegisterScalableTarget(minCapacity, maxCapacity int64, resourceId, roleArn string) error {
   307  	svc := applicationautoscaling.New(session.New())
   308  	input := &applicationautoscaling.RegisterScalableTargetInput{
   309  		MinCapacity:       aws.Int64(minCapacity),
   310  		MaxCapacity:       aws.Int64(maxCapacity),
   311  		ResourceId:        aws.String(resourceId), // serviceName/clusterName/app
   312  		RoleARN:           aws.String(roleArn),
   313  		ScalableDimension: aws.String("ecs:service:DesiredCount"),
   314  		ServiceNamespace:  aws.String("ecs"),
   315  	}
   316  	_, err := svc.RegisterScalableTarget(input)
   317  	if err != nil {
   318  		if aerr, ok := err.(awserr.Error); ok {
   319  			ecsLogger.Errorf("%v", aerr.Error())
   320  		} else {
   321  			ecsLogger.Errorf("%v", err.Error())
   322  		}
   323  		return err
   324  	}
   325  	return nil
   326  }
   327  func (a *AutoScaling) DeregisterScalableTarget(resourceId string) error {
   328  	svc := applicationautoscaling.New(session.New())
   329  	input := &applicationautoscaling.DeregisterScalableTargetInput{
   330  		ResourceId:        aws.String(resourceId), // serviceName/clusterName/app
   331  		ScalableDimension: aws.String("ecs:service:DesiredCount"),
   332  		ServiceNamespace:  aws.String("ecs"),
   333  	}
   334  	_, err := svc.DeregisterScalableTarget(input)
   335  	if err != nil {
   336  		if aerr, ok := err.(awserr.Error); ok {
   337  			ecsLogger.Errorf("%v", aerr.Error())
   338  		} else {
   339  			ecsLogger.Errorf("%v", err.Error())
   340  		}
   341  		return err
   342  	}
   343  	return nil
   344  }
   345  func (a *AutoScaling) PutScalingPolicy(policyName, resourceId string, cooldown, scalingAdjustment int64) (string, error) {
   346  	svc := applicationautoscaling.New(session.New())
   347  	input := &applicationautoscaling.PutScalingPolicyInput{
   348  		PolicyName:        aws.String(policyName),
   349  		PolicyType:        aws.String("StepScaling"),
   350  		ResourceId:        aws.String(resourceId), // serviceName/clusterName/app
   351  		ScalableDimension: aws.String("ecs:service:DesiredCount"),
   352  		ServiceNamespace:  aws.String("ecs"),
   353  		StepScalingPolicyConfiguration: &applicationautoscaling.StepScalingPolicyConfiguration{
   354  			AdjustmentType: aws.String("ChangeInCapacity"),
   355  			Cooldown:       aws.Int64(cooldown),
   356  			StepAdjustments: []*applicationautoscaling.StepAdjustment{
   357  				{
   358  					MetricIntervalLowerBound: aws.Float64(0),
   359  					ScalingAdjustment:        aws.Int64(scalingAdjustment),
   360  				},
   361  			},
   362  		},
   363  	}
   364  	result, err := svc.PutScalingPolicy(input)
   365  	if err != nil {
   366  		if aerr, ok := err.(awserr.Error); ok {
   367  			ecsLogger.Errorf("%v", aerr.Error())
   368  		} else {
   369  			ecsLogger.Errorf("%v", err.Error())
   370  		}
   371  		return "", err
   372  	}
   373  	return aws.StringValue(result.PolicyARN), nil
   374  }
   375  
   376  func (a *AutoScaling) DescribeScalableTargets(resourceIds []string) ([]service.Autoscaling, error) {
   377  	var as []service.Autoscaling
   378  	var scalableTargets []*applicationautoscaling.ScalableTarget
   379  	svc := applicationautoscaling.New(session.New())
   380  	input := &applicationautoscaling.DescribeScalableTargetsInput{
   381  		ResourceIds:       aws.StringSlice(resourceIds), // serviceName/clusterName/app
   382  		ScalableDimension: aws.String("ecs:service:DesiredCount"),
   383  		ServiceNamespace:  aws.String("ecs"),
   384  	}
   385  	pageNum := 0
   386  	err := svc.DescribeScalableTargetsPages(input,
   387  		func(page *applicationautoscaling.DescribeScalableTargetsOutput, lastPage bool) bool {
   388  			pageNum++
   389  			scalableTargets = append(scalableTargets, page.ScalableTargets...)
   390  			return pageNum <= 100
   391  		})
   392  
   393  	if err != nil {
   394  		if aerr, ok := err.(awserr.Error); ok {
   395  			ecsLogger.Errorf("%v", aerr.Error())
   396  		} else {
   397  			ecsLogger.Errorf("%v", err.Error())
   398  		}
   399  		return as, err
   400  	}
   401  
   402  	for _, v := range scalableTargets {
   403  		a := service.Autoscaling{}
   404  		a.MinimumCount = aws.Int64Value(v.MinCapacity)
   405  		a.MaximumCount = aws.Int64Value(v.MaxCapacity)
   406  		as = append(as, a)
   407  	}
   408  
   409  	return as, nil
   410  }
   411  func (a *AutoScaling) DescribeScalingPolicies(policyNames []string, resourceId string) ([]service.AutoscalingPolicy, error) {
   412  	var aps []service.AutoscalingPolicy
   413  	var scalingPolicies []*applicationautoscaling.ScalingPolicy
   414  	svc := applicationautoscaling.New(session.New())
   415  	input := &applicationautoscaling.DescribeScalingPoliciesInput{
   416  		PolicyNames:       aws.StringSlice(policyNames),
   417  		ResourceId:        aws.String(resourceId), // serviceName/clusterName/app
   418  		ScalableDimension: aws.String("ecs:service:DesiredCount"),
   419  		ServiceNamespace:  aws.String("ecs"),
   420  	}
   421  	pageNum := 0
   422  	err := svc.DescribeScalingPoliciesPages(input,
   423  		func(page *applicationautoscaling.DescribeScalingPoliciesOutput, lastPage bool) bool {
   424  			pageNum++
   425  			scalingPolicies = append(scalingPolicies, page.ScalingPolicies...)
   426  			return pageNum <= 100
   427  		})
   428  
   429  	if err != nil {
   430  		if aerr, ok := err.(awserr.Error); ok {
   431  			ecsLogger.Errorf("%v", aerr.Error())
   432  		} else {
   433  			ecsLogger.Errorf("%v", err.Error())
   434  		}
   435  		return aps, err
   436  	}
   437  
   438  	for _, v := range scalingPolicies {
   439  		ap := service.AutoscalingPolicy{}
   440  		ap.PolicyName = aws.StringValue(v.PolicyName)
   441  		if len(v.StepScalingPolicyConfiguration.StepAdjustments) > 0 {
   442  			ap.ScalingAdjustment = aws.Int64Value(v.StepScalingPolicyConfiguration.StepAdjustments[0].ScalingAdjustment)
   443  		}
   444  		aps = append(aps, ap)
   445  	}
   446  
   447  	return aps, nil
   448  }
   449  
   450  func (a *AutoScaling) DeleteScalingPolicy(policyName, resourceId string) error {
   451  	svc := applicationautoscaling.New(session.New())
   452  
   453  	input := &applicationautoscaling.DeleteScalingPolicyInput{
   454  		PolicyName:        aws.String(policyName),
   455  		ResourceId:        aws.String(resourceId), // serviceName/clusterName/app
   456  		ScalableDimension: aws.String("ecs:service:DesiredCount"),
   457  		ServiceNamespace:  aws.String("ecs"),
   458  	}
   459  
   460  	_, err := svc.DeleteScalingPolicy(input)
   461  
   462  	if err != nil {
   463  		if aerr, ok := err.(awserr.Error); ok {
   464  			ecsLogger.Errorf("%v", aerr.Error())
   465  		} else {
   466  			ecsLogger.Errorf("%v", err.Error())
   467  		}
   468  		return err
   469  	}
   470  
   471  	return nil
   472  }