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

     1  package ecs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/aws/aws-sdk-go/aws"
     7  	"github.com/aws/aws-sdk-go/aws/awserr"
     8  	"github.com/aws/aws-sdk-go/aws/session"
     9  	"github.com/aws/aws-sdk-go/service/acm"
    10  	"github.com/aws/aws-sdk-go/service/elbv2"
    11  	"github.com/in4it/ecs-deploy/service"
    12  	"github.com/in4it/ecs-deploy/util"
    13  	"github.com/juju/loggo"
    14  
    15  	"errors"
    16  	"strconv"
    17  	"strings"
    18  )
    19  
    20  // logging
    21  var albLogger = loggo.GetLogger("alb")
    22  
    23  // ALB struct
    24  type ALB struct {
    25  	loadBalancerName string
    26  	loadBalancerArn  string
    27  	VpcId            string
    28  	Listeners        []*elbv2.Listener
    29  	Domain           string
    30  	Rules            map[string][]*elbv2.Rule
    31  	DnsName          string
    32  }
    33  
    34  func NewALB(loadBalancerName string) (*ALB, error) {
    35  	a := ALB{}
    36  	a.loadBalancerName = loadBalancerName
    37  	// retrieve vpcId and loadBalancerArn
    38  	svc := elbv2.New(session.New())
    39  	input := &elbv2.DescribeLoadBalancersInput{
    40  		Names: []*string{
    41  			aws.String(loadBalancerName),
    42  		},
    43  	}
    44  
    45  	result, err := svc.DescribeLoadBalancers(input)
    46  	if err != nil {
    47  		if aerr, ok := err.(awserr.Error); ok {
    48  			switch aerr.Code() {
    49  			case elbv2.ErrCodeLoadBalancerNotFoundException:
    50  				albLogger.Errorf(elbv2.ErrCodeLoadBalancerNotFoundException+": %v", aerr.Error())
    51  			default:
    52  				albLogger.Errorf(aerr.Error())
    53  			}
    54  		} else {
    55  			// Print the error, cast err to awserr.Error to get the Code and
    56  			// Message from an error.
    57  			albLogger.Errorf(err.Error())
    58  		}
    59  		return nil, errors.New("Could not describe loadbalancer")
    60  	} else if len(result.LoadBalancers) == 0 {
    61  		return nil, errors.New("Could not describe loadbalancer (no elements returned)")
    62  	}
    63  	a.loadBalancerArn = *result.LoadBalancers[0].LoadBalancerArn
    64  	a.loadBalancerName = *result.LoadBalancers[0].LoadBalancerName
    65  	a.VpcId = *result.LoadBalancers[0].VpcId
    66  
    67  	// get listeners
    68  	err = a.GetListeners()
    69  	if err != nil {
    70  		return nil, err
    71  	} else if len(result.LoadBalancers) == 0 {
    72  		return nil, errors.New("Could not get listeners for loadbalancer (no elements returned)")
    73  	}
    74  	// get domain (if SSL cert is attached)
    75  	err = a.GetDomainUsingCertificate()
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	return &a, nil
    81  }
    82  
    83  // get the listeners for the loadbalancer
    84  func NewALBAndCreate(loadBalancerName, ipAddressType string, scheme string, securityGroups []string, subnets []string, lbType string) (*ALB, error) {
    85  	a := ALB{}
    86  	svc := elbv2.New(session.New())
    87  	input := &elbv2.CreateLoadBalancerInput{
    88  		IpAddressType:  aws.String(ipAddressType),
    89  		Name:           aws.String(loadBalancerName),
    90  		Scheme:         aws.String(scheme),
    91  		SecurityGroups: aws.StringSlice(securityGroups),
    92  		Subnets:        aws.StringSlice(subnets),
    93  		Type:           aws.String(lbType),
    94  	}
    95  
    96  	result, err := svc.CreateLoadBalancer(input)
    97  	if err != nil {
    98  		if aerr, ok := err.(awserr.Error); ok {
    99  			albLogger.Errorf(aerr.Error())
   100  			return nil, aerr
   101  		}
   102  		albLogger.Errorf(err.Error())
   103  		return nil, err
   104  	}
   105  	if len(result.LoadBalancers) == 0 {
   106  		return nil, errors.New("No loadbalancers returned")
   107  	}
   108  	a.loadBalancerArn = aws.StringValue(result.LoadBalancers[0].LoadBalancerArn)
   109  	a.DnsName = aws.StringValue(result.LoadBalancers[0].DNSName)
   110  	a.VpcId = aws.StringValue(result.LoadBalancers[0].VpcId)
   111  	return &a, nil
   112  }
   113  
   114  func (a *ALB) DeleteLoadBalancer() error {
   115  	svc := elbv2.New(session.New())
   116  	input := &elbv2.DeleteLoadBalancerInput{
   117  		LoadBalancerArn: aws.String(a.loadBalancerArn),
   118  	}
   119  	_, err := svc.DeleteLoadBalancer(input)
   120  	if err != nil {
   121  		if aerr, ok := err.(awserr.Error); ok {
   122  			albLogger.Errorf(aerr.Error())
   123  			return aerr
   124  		}
   125  		albLogger.Errorf(err.Error())
   126  		return err
   127  	}
   128  	return nil
   129  }
   130  
   131  func (a *ALB) CreateListener(protocol string, port int64, targetGroupArn string) error {
   132  	// only HTTP is supported for now
   133  	svc := elbv2.New(session.New())
   134  	input := &elbv2.CreateListenerInput{
   135  		LoadBalancerArn: aws.String(a.loadBalancerArn),
   136  		Port:            aws.Int64(port),
   137  		Protocol:        aws.String(protocol),
   138  		DefaultActions: []*elbv2.Action{
   139  			{Type: aws.String("forward"), TargetGroupArn: aws.String(targetGroupArn)},
   140  		},
   141  	}
   142  
   143  	result, err := svc.CreateListener(input)
   144  	if err != nil {
   145  		if aerr, ok := err.(awserr.Error); ok {
   146  			albLogger.Errorf(aerr.Error())
   147  		} else {
   148  			albLogger.Errorf(err.Error())
   149  		}
   150  		return err
   151  	}
   152  	if len(result.Listeners) == 0 {
   153  		return errors.New("No listeners returned")
   154  	}
   155  	a.Listeners = append(a.Listeners, result.Listeners[0])
   156  	return nil
   157  }
   158  func (a *ALB) DeleteListener(listenerArn string) error {
   159  	svc := elbv2.New(session.New())
   160  	input := &elbv2.DeleteListenerInput{
   161  		ListenerArn: aws.String(listenerArn),
   162  	}
   163  
   164  	_, err := svc.DeleteListener(input)
   165  	if err != nil {
   166  		if aerr, ok := err.(awserr.Error); ok {
   167  			albLogger.Errorf(aerr.Error())
   168  		} else {
   169  			albLogger.Errorf(err.Error())
   170  		}
   171  		return err
   172  	}
   173  	return nil
   174  }
   175  
   176  // get the listeners for the loadbalancer
   177  func (a *ALB) GetListeners() error {
   178  	svc := elbv2.New(session.New())
   179  	input := &elbv2.DescribeListenersInput{LoadBalancerArn: aws.String(a.loadBalancerArn)}
   180  
   181  	result, err := svc.DescribeListeners(input)
   182  	if err != nil {
   183  		if aerr, ok := err.(awserr.Error); ok {
   184  			switch aerr.Code() {
   185  			case elbv2.ErrCodeListenerNotFoundException:
   186  				albLogger.Errorf(elbv2.ErrCodeListenerNotFoundException+": %v", aerr.Error())
   187  			case elbv2.ErrCodeLoadBalancerNotFoundException:
   188  				albLogger.Errorf(elbv2.ErrCodeLoadBalancerNotFoundException+": %v", aerr.Error())
   189  			default:
   190  				albLogger.Errorf(aerr.Error())
   191  			}
   192  		} else {
   193  			albLogger.Errorf(err.Error())
   194  		}
   195  		return errors.New("Could not get Listeners for loadbalancer")
   196  	}
   197  	for _, l := range result.Listeners {
   198  		a.Listeners = append(a.Listeners, l)
   199  	}
   200  	return nil
   201  }
   202  
   203  // get the domain using certificates
   204  func (a *ALB) GetDomainUsingCertificate() error {
   205  	svc := acm.New(session.New())
   206  	for _, l := range a.Listeners {
   207  		for _, c := range l.Certificates {
   208  			albLogger.Debugf("ALB Certificate found with arn: %v", *c.CertificateArn)
   209  			input := &acm.DescribeCertificateInput{
   210  				CertificateArn: c.CertificateArn,
   211  			}
   212  
   213  			result, err := svc.DescribeCertificate(input)
   214  			if err != nil {
   215  				if aerr, ok := err.(awserr.Error); ok {
   216  					switch aerr.Code() {
   217  					case acm.ErrCodeResourceNotFoundException:
   218  						albLogger.Errorf(acm.ErrCodeResourceNotFoundException+": %v", aerr.Error())
   219  					case acm.ErrCodeInvalidArnException:
   220  						albLogger.Errorf(acm.ErrCodeInvalidArnException+": %v", aerr.Error())
   221  					default:
   222  						albLogger.Errorf(aerr.Error())
   223  					}
   224  				} else {
   225  					albLogger.Errorf(err.Error())
   226  				}
   227  				return errors.New("Could not describe certificate")
   228  			}
   229  			albLogger.Debugf("Domain found through ALB certificate: %v", *result.Certificate.DomainName)
   230  			s := strings.Split(*result.Certificate.DomainName, ".")
   231  			if len(s) >= 2 {
   232  				a.Domain = s[len(s)-2] + "." + s[len(s)-1]
   233  			}
   234  			return nil
   235  		}
   236  	}
   237  	return nil
   238  }
   239  
   240  func (a *ALB) CreateTargetGroup(serviceName string, d service.Deploy) (*string, error) {
   241  	svc := elbv2.New(session.New())
   242  	input := &elbv2.CreateTargetGroupInput{
   243  		Name:     aws.String(util.TruncateString(serviceName, 32)),
   244  		VpcId:    aws.String(a.VpcId),
   245  		Port:     aws.Int64(d.ServicePort),
   246  		Protocol: aws.String(d.ServiceProtocol),
   247  	}
   248  	if d.HealthCheck.HealthyThreshold != 0 {
   249  		input.SetHealthyThresholdCount(d.HealthCheck.HealthyThreshold)
   250  	}
   251  	if d.HealthCheck.UnhealthyThreshold != 0 {
   252  		input.SetUnhealthyThresholdCount(d.HealthCheck.UnhealthyThreshold)
   253  	}
   254  	if d.HealthCheck.Path != "" {
   255  		input.SetHealthCheckPath(d.HealthCheck.Path)
   256  	}
   257  	if d.HealthCheck.Port != "" {
   258  		input.SetHealthCheckPort(d.HealthCheck.Port)
   259  	}
   260  	if d.HealthCheck.Protocol != "" {
   261  		input.SetHealthCheckProtocol(d.HealthCheck.Protocol)
   262  	}
   263  	if d.HealthCheck.Interval != 0 {
   264  		input.SetHealthCheckIntervalSeconds(d.HealthCheck.Interval)
   265  	}
   266  	if d.HealthCheck.Matcher != "" {
   267  		input.SetMatcher(&elbv2.Matcher{HttpCode: aws.String(d.HealthCheck.Matcher)})
   268  	}
   269  	if d.HealthCheck.Timeout > 0 {
   270  		input.SetHealthCheckTimeoutSeconds(d.HealthCheck.Timeout)
   271  	}
   272  	if d.NetworkMode == "awsvpc" && len(d.NetworkConfiguration.Subnets) > 0 {
   273  		input.SetTargetType("ip")
   274  	}
   275  
   276  	result, err := svc.CreateTargetGroup(input)
   277  	if err != nil {
   278  		if aerr, ok := err.(awserr.Error); ok {
   279  			switch aerr.Code() {
   280  			case elbv2.ErrCodeDuplicateTargetGroupNameException:
   281  				albLogger.Errorf(elbv2.ErrCodeDuplicateTargetGroupNameException+": %v", aerr.Error())
   282  			case elbv2.ErrCodeTooManyTargetGroupsException:
   283  				albLogger.Errorf(elbv2.ErrCodeTooManyTargetGroupsException+": %v", aerr.Error())
   284  			case elbv2.ErrCodeInvalidConfigurationRequestException:
   285  				albLogger.Errorf(elbv2.ErrCodeInvalidConfigurationRequestException+": %v", aerr.Error())
   286  			default:
   287  				albLogger.Errorf(aerr.Error())
   288  			}
   289  		} else {
   290  			// Print the error, cast err to awserr.Error to get the Code and
   291  			// Message from an error.
   292  			albLogger.Errorf(err.Error())
   293  		}
   294  		return nil, errors.New("Could not create target group")
   295  	} else if len(result.TargetGroups) == 0 {
   296  		return nil, errors.New("Could not create target group (target group list is empty)")
   297  	}
   298  	return result.TargetGroups[0].TargetGroupArn, nil
   299  }
   300  func (a *ALB) DeleteTargetGroup(targetGroupArn string) error {
   301  	svc := elbv2.New(session.New())
   302  	input := &elbv2.DeleteTargetGroupInput{
   303  		TargetGroupArn: aws.String(targetGroupArn),
   304  	}
   305  	_, err := svc.DeleteTargetGroup(input)
   306  	if err != nil {
   307  		if aerr, ok := err.(awserr.Error); ok {
   308  			albLogger.Errorf(aerr.Error())
   309  		} else {
   310  			albLogger.Errorf(err.Error())
   311  		}
   312  		return err
   313  	}
   314  	return nil
   315  }
   316  
   317  func (a *ALB) GetHighestRule() (int64, error) {
   318  	var highest int64
   319  	svc := elbv2.New(session.New())
   320  
   321  	for _, listener := range a.Listeners {
   322  		input := &elbv2.DescribeRulesInput{ListenerArn: listener.ListenerArn}
   323  
   324  		c := true // parse more pages if c is true
   325  		result, err := svc.DescribeRules(input)
   326  		for c {
   327  			if err != nil {
   328  				if aerr, ok := err.(awserr.Error); ok {
   329  					switch aerr.Code() {
   330  					case elbv2.ErrCodeListenerNotFoundException:
   331  						albLogger.Errorf(elbv2.ErrCodeListenerNotFoundException+": %v", aerr.Error())
   332  					case elbv2.ErrCodeRuleNotFoundException:
   333  						albLogger.Errorf(elbv2.ErrCodeRuleNotFoundException+": %v", aerr.Error())
   334  					default:
   335  						albLogger.Errorf(aerr.Error())
   336  					}
   337  				} else {
   338  					// Print the error, cast err to awserr.Error to get the Code and
   339  					// Message from an error.
   340  					albLogger.Errorf(err.Error())
   341  				}
   342  				return 0, errors.New("Could not describe alb listener rules")
   343  			}
   344  
   345  			albLogger.Tracef("Looping rules: %+v", result.Rules)
   346  			for _, rule := range result.Rules {
   347  				if i, _ := strconv.ParseInt(*rule.Priority, 10, 64); i > highest {
   348  					albLogger.Debugf("Found rule with priority: %d", i)
   349  					highest = i
   350  				}
   351  			}
   352  			if result.NextMarker == nil || len(*result.NextMarker) == 0 {
   353  				c = false
   354  			} else {
   355  				input.SetMarker(*result.NextMarker)
   356  				result, err = svc.DescribeRules(input)
   357  			}
   358  		}
   359  	}
   360  
   361  	albLogger.Debugf("Higest rule: %d", highest)
   362  
   363  	return highest, nil
   364  }
   365  
   366  func (a *ALB) CreateRuleForAllListeners(ruleType string, targetGroupArn string, rules []string, priority int64) ([]string, error) {
   367  	var listeners []string
   368  	for _, l := range a.Listeners {
   369  		err := a.CreateRule(ruleType, *l.ListenerArn, targetGroupArn, rules, priority, service.DeployRuleConditionsCognitoAuth{})
   370  		if err != nil {
   371  			return nil, err
   372  		}
   373  		listeners = append(listeners, *l.ListenerArn)
   374  	}
   375  	return listeners, nil
   376  }
   377  
   378  func (a *ALB) CreateRuleForListeners(ruleType string, listeners []string, targetGroupArn string, rules []string, priority int64, cognitoAuth service.DeployRuleConditionsCognitoAuth) ([]string, error) {
   379  	retListeners := a.getListenersArnForProtocol(listeners)
   380  	for proto, listener := range retListeners {
   381  		var err error
   382  		// if cognito is set, a redirect is needed instead (cognito doesn't work with http)
   383  		if proto == "http" && cognitoAuth.ClientName != "" {
   384  			err = a.CreateHTTPSRedirectRule(ruleType, listener, targetGroupArn, rules, priority)
   385  		} else {
   386  			err = a.CreateRule(ruleType, listener, targetGroupArn, rules, priority, cognitoAuth)
   387  		}
   388  		if err != nil {
   389  			return nil, err
   390  		}
   391  	}
   392  	listenerArns := []string{}
   393  	for _, v := range retListeners {
   394  		listenerArns = append(listenerArns, v)
   395  	}
   396  	return listenerArns, nil
   397  }
   398  
   399  func (a *ALB) getListenersArnForProtocol(listeners []string) map[string]string {
   400  	listenersArn := make(map[string]string)
   401  	for _, l := range a.Listeners {
   402  		for _, l2 := range listeners {
   403  			if l.Protocol != nil && strings.ToLower(aws.StringValue(l.Protocol)) == strings.ToLower(l2) {
   404  				listenersArn[strings.ToLower(aws.StringValue(l.Protocol))] = aws.StringValue(l.ListenerArn)
   405  			}
   406  		}
   407  	}
   408  	for k, v := range listenersArn {
   409  		albLogger.Debugf("getListenersArnForProtocol: resolved %s to %s", k, v)
   410  	}
   411  
   412  	return listenersArn
   413  }
   414  
   415  /*
   416   * Gets listeners ARN based on http / https string
   417   */
   418  func (a *ALB) GetListenerArnForProtocol(listener string) string {
   419  	listeners := a.getListenersArnForProtocol([]string{listener})
   420  	if val, ok := listeners[listener]; ok {
   421  		return val
   422  	}
   423  	return ""
   424  }
   425  
   426  /*
   427   * modify an existing rule to a https redirect
   428   */
   429  func (a *ALB) UpdateRuleToHTTPSRedirect(targetGroupArn, ruleArn string, ruleType string, rules []string) error {
   430  	svc := elbv2.New(session.New())
   431  	input := &elbv2.ModifyRuleInput{
   432  		Actions: []*elbv2.Action{
   433  			{
   434  				RedirectConfig: &elbv2.RedirectActionConfig{
   435  					Protocol:   aws.String("HTTPS"),
   436  					StatusCode: aws.String("HTTP_301"),
   437  					Port:       aws.String("443"),
   438  				},
   439  				Type: aws.String("redirect"),
   440  			},
   441  		},
   442  		RuleArn: aws.String(ruleArn),
   443  	}
   444  	conditions, err := a.getRuleConditions(ruleType, rules)
   445  	if err != nil {
   446  		return err
   447  	}
   448  	input.SetConditions(conditions)
   449  
   450  	_, err = svc.ModifyRule(input)
   451  	if err != nil {
   452  		if aerr, ok := err.(awserr.Error); ok {
   453  			albLogger.Errorf(aerr.Error())
   454  		} else {
   455  			albLogger.Errorf(err.Error())
   456  		}
   457  		return errors.New("Could not modify alb rule")
   458  	}
   459  	return nil
   460  }
   461  
   462  func (a *ALB) UpdateRule(targetGroupArn, ruleArn string, ruleType string, rules []string, cognitoAuth service.DeployRuleConditionsCognitoAuth) error {
   463  	svc := elbv2.New(session.New())
   464  	input := &elbv2.ModifyRuleInput{
   465  		Actions: []*elbv2.Action{
   466  			{
   467  				TargetGroupArn: aws.String(targetGroupArn),
   468  				Type:           aws.String("forward"),
   469  			},
   470  		},
   471  		RuleArn: aws.String(ruleArn),
   472  	}
   473  	conditions, err := a.getRuleConditions(ruleType, rules)
   474  	if err != nil {
   475  		return err
   476  	}
   477  	input.SetConditions(conditions)
   478  
   479  	// cognito
   480  	if cognitoAuth.UserPoolName != "" && cognitoAuth.ClientName != "" {
   481  		cognitoAction, err := a.getCognitoAction(targetGroupArn, cognitoAuth)
   482  		if err != nil {
   483  			return err
   484  		}
   485  		input.SetActions(cognitoAction)
   486  	}
   487  	_, err = svc.ModifyRule(input)
   488  	if err != nil {
   489  		if aerr, ok := err.(awserr.Error); ok {
   490  			albLogger.Errorf(aerr.Error())
   491  		} else {
   492  			albLogger.Errorf(err.Error())
   493  		}
   494  		return errors.New("Could not modify alb rule")
   495  	}
   496  	return nil
   497  }
   498  
   499  func (a *ALB) getRuleConditions(ruleType string, rules []string) ([]*elbv2.RuleCondition, error) {
   500  	if ruleType == "pathPattern" {
   501  		if len(rules) != 1 {
   502  			return nil, errors.New("Wrong number of rules (expected 1, got " + strconv.Itoa(len(rules)) + ")")
   503  		}
   504  		return []*elbv2.RuleCondition{
   505  			{
   506  				Field:  aws.String("path-pattern"),
   507  				Values: []*string{aws.String(rules[0])},
   508  			},
   509  		}, nil
   510  	} else if ruleType == "hostname" {
   511  		if len(rules) != 1 {
   512  			return nil, errors.New("Wrong number of rules (expected 1, got " + strconv.Itoa(len(rules)) + ")")
   513  		}
   514  		hostname := rules[0]
   515  		return []*elbv2.RuleCondition{
   516  			{
   517  				Field:  aws.String("host-header"),
   518  				Values: []*string{aws.String(hostname)},
   519  			},
   520  		}, nil
   521  	} else if ruleType == "combined" {
   522  		if len(rules) != 2 {
   523  			return nil, errors.New("Wrong number of rules (expected 2, got " + strconv.Itoa(len(rules)) + ")")
   524  		}
   525  		hostname := rules[1]
   526  		return []*elbv2.RuleCondition{
   527  			{
   528  				Field:  aws.String("path-pattern"),
   529  				Values: []*string{aws.String(rules[0])},
   530  			},
   531  			{
   532  				Field:  aws.String("host-header"),
   533  				Values: []*string{aws.String(hostname)},
   534  			},
   535  		}, nil
   536  
   537  	} else {
   538  		return nil, errors.New("ruleType not recognized: " + ruleType)
   539  	}
   540  }
   541  
   542  func (a *ALB) CreateHTTPSRedirectRule(ruleType string, listenerArn string, targetGroupArn string, rules []string, priority int64) error {
   543  	svc := elbv2.New(session.New())
   544  	input := &elbv2.CreateRuleInput{
   545  		Actions: []*elbv2.Action{
   546  			{
   547  				RedirectConfig: &elbv2.RedirectActionConfig{
   548  					Protocol:   aws.String("HTTPS"),
   549  					StatusCode: aws.String("HTTP_301"),
   550  					Port:       aws.String("443"),
   551  				},
   552  				Type: aws.String("redirect"),
   553  			},
   554  		},
   555  		ListenerArn: aws.String(listenerArn),
   556  		Priority:    aws.Int64(priority),
   557  	}
   558  	conditions, err := a.getRuleConditions(ruleType, rules)
   559  	if err != nil {
   560  		return err
   561  	}
   562  	input.SetConditions(conditions)
   563  
   564  	_, err = svc.CreateRule(input)
   565  	if err != nil {
   566  		albLogger.Errorf(err.Error())
   567  		return fmt.Errorf("Could not create alb rule: %+v", input)
   568  	}
   569  	return nil
   570  }
   571  
   572  func (a *ALB) CreateRule(ruleType string, listenerArn string, targetGroupArn string, rules []string, priority int64, cognitoAuth service.DeployRuleConditionsCognitoAuth) error {
   573  	svc := elbv2.New(session.New())
   574  	input := &elbv2.CreateRuleInput{
   575  		Actions: []*elbv2.Action{
   576  			{
   577  				TargetGroupArn: aws.String(targetGroupArn),
   578  				Type:           aws.String("forward"),
   579  			},
   580  		},
   581  		ListenerArn: aws.String(listenerArn),
   582  		Priority:    aws.Int64(priority),
   583  	}
   584  	conditions, err := a.getRuleConditions(ruleType, rules)
   585  	if err != nil {
   586  		return err
   587  	}
   588  	input.SetConditions(conditions)
   589  
   590  	// cognito
   591  	if cognitoAuth.UserPoolName != "" && cognitoAuth.ClientName != "" {
   592  		cognitoAction, err := a.getCognitoAction(targetGroupArn, cognitoAuth)
   593  		if err != nil {
   594  			return err
   595  		}
   596  		input.SetActions(cognitoAction)
   597  	}
   598  
   599  	_, err = svc.CreateRule(input)
   600  	if err != nil {
   601  		albLogger.Errorf(err.Error())
   602  		return errors.New("Could not create alb rule")
   603  	}
   604  	return nil
   605  }
   606  
   607  // get rules by listener
   608  func (a *ALB) GetRulesForAllListeners() error {
   609  	a.Rules = make(map[string][]*elbv2.Rule)
   610  	svc := elbv2.New(session.New())
   611  
   612  	for _, l := range a.Listeners {
   613  		input := &elbv2.DescribeRulesInput{ListenerArn: aws.String(*l.ListenerArn)}
   614  
   615  		result, err := svc.DescribeRules(input)
   616  		if err != nil {
   617  			if aerr, ok := err.(awserr.Error); ok {
   618  				switch aerr.Code() {
   619  				case elbv2.ErrCodeListenerNotFoundException:
   620  					albLogger.Errorf(elbv2.ErrCodeListenerNotFoundException+": %v", aerr.Error())
   621  				case elbv2.ErrCodeRuleNotFoundException:
   622  					albLogger.Errorf(elbv2.ErrCodeRuleNotFoundException+": %v", aerr.Error())
   623  				default:
   624  					albLogger.Errorf(aerr.Error())
   625  				}
   626  			} else {
   627  				albLogger.Errorf(err.Error())
   628  			}
   629  			return errors.New("Could not get Listeners for loadbalancer")
   630  		}
   631  		for _, r := range result.Rules {
   632  			a.Rules[*l.ListenerArn] = append(a.Rules[*l.ListenerArn], r)
   633  			if len(r.Conditions) != 0 && len(r.Conditions[0].Values) != 0 {
   634  				albLogger.Debugf("Importing rule: %+v (prio: %v)", *r.Conditions[0].Values[0], *r.Priority)
   635  			}
   636  		}
   637  	}
   638  	return nil
   639  }
   640  func (a *ALB) GetRulesByTargetGroupArn(targetGroupArn string) []string {
   641  	var result []string
   642  	for _, rules := range a.Rules {
   643  		for _, rule := range rules {
   644  			for _, ruleAction := range rule.Actions {
   645  				if aws.StringValue(ruleAction.TargetGroupArn) == targetGroupArn {
   646  					result = append(result, aws.StringValue(rule.RuleArn))
   647  				}
   648  			}
   649  		}
   650  	}
   651  	return result
   652  }
   653  func (a *ALB) GetRuleByTargetGroupArnWithAuth(targetGroupArn string) []string {
   654  	var result []string
   655  	for _, rules := range a.Rules {
   656  		for _, rule := range rules {
   657  			foundAuthType := false
   658  			for _, ruleAction := range rule.Actions {
   659  				if aws.StringValue(ruleAction.Type) == "authenticate-cognito" {
   660  					foundAuthType = true
   661  				}
   662  			}
   663  			if foundAuthType {
   664  				for _, ruleAction := range rule.Actions {
   665  					if aws.StringValue(ruleAction.TargetGroupArn) == targetGroupArn {
   666  						result = append(result, aws.StringValue(rule.RuleArn))
   667  					}
   668  				}
   669  			}
   670  		}
   671  	}
   672  	return result
   673  }
   674  func (a *ALB) GetConditionsForRule(ruleArn string) ([]string, []string) {
   675  	conditionFields := []string{}
   676  	conditionValues := []string{}
   677  	for _, rules := range a.Rules {
   678  		for _, rule := range rules {
   679  			if aws.StringValue(rule.RuleArn) == ruleArn {
   680  				for _, condition := range rule.Conditions {
   681  					if aws.StringValue(condition.Field) == "path-pattern" || aws.StringValue(condition.Field) == "host-header" {
   682  						conditionFields = append(conditionFields, aws.StringValue(condition.Field))
   683  						if len(condition.Values) >= 1 {
   684  							conditionValues = append(conditionValues, aws.StringValue(condition.Values[0]))
   685  						}
   686  					}
   687  				}
   688  			}
   689  		}
   690  	}
   691  	return conditionFields, conditionValues
   692  }
   693  
   694  func (a *ALB) GetTargetGroupArn(serviceName string) (*string, error) {
   695  	svc := elbv2.New(session.New())
   696  	input := &elbv2.DescribeTargetGroupsInput{
   697  		Names: []*string{aws.String(util.TruncateString(serviceName, 32))},
   698  	}
   699  
   700  	result, err := svc.DescribeTargetGroups(input)
   701  	if err != nil {
   702  		if aerr, ok := err.(awserr.Error); ok {
   703  			switch aerr.Code() {
   704  			case elbv2.ErrCodeLoadBalancerNotFoundException:
   705  				albLogger.Errorf(elbv2.ErrCodeLoadBalancerNotFoundException+": %v", aerr.Error())
   706  			case elbv2.ErrCodeTargetGroupNotFoundException:
   707  				albLogger.Errorf(elbv2.ErrCodeTargetGroupNotFoundException+": %v", aerr.Error())
   708  			default:
   709  				albLogger.Errorf(aerr.Error())
   710  			}
   711  		} else {
   712  			albLogger.Errorf(err.Error())
   713  		}
   714  		return nil, err
   715  	}
   716  	if len(result.TargetGroups) == 1 {
   717  		return result.TargetGroups[0].TargetGroupArn, nil
   718  	} else {
   719  		if len(result.TargetGroups) == 0 {
   720  			return nil, errors.New("No ALB target group found for service: " + serviceName)
   721  		} else {
   722  			return nil, errors.New("Multiple target groups found for service: " + serviceName + " (" + fmt.Sprintf("%d", len(result.TargetGroups)) + ")")
   723  		}
   724  	}
   725  }
   726  func (a *ALB) GetDomain() string {
   727  	return util.GetEnv("LOADBALANCER_DOMAIN", a.Domain)
   728  }
   729  
   730  /*
   731   * FindRule tries to find a matching rule in the Rules map
   732   */
   733  func (a *ALB) FindRule(listener string, targetGroupArn string, conditionField []string, conditionValue []string) (*string, *string, error) {
   734  	albLogger.Debugf("Find Rule: listener %s, targetGroupArn %s, conditionField %s, conditionValue %s", listener, targetGroupArn, strings.Join(conditionField, ","), strings.Join(conditionValue, ","))
   735  
   736  	if len(conditionField) != len(conditionValue) {
   737  		return nil, nil, errors.New("conditionField length not equal to conditionValue length")
   738  	}
   739  	// examine rules
   740  	if rules, ok := a.Rules[listener]; ok {
   741  		for _, r := range rules {
   742  			for _, a := range r.Actions {
   743  				if (aws.StringValue(a.Type) == "forward" && aws.StringValue(a.TargetGroupArn) == targetGroupArn) || aws.StringValue(a.Type) == "redirect" {
   744  					// possible action match found, checking conditions
   745  					matchingConditions := []bool{}
   746  					for _, c := range r.Conditions {
   747  						match := false
   748  						for i := range conditionField {
   749  							if aws.StringValue(c.Field) == conditionField[i] && len(c.Values) > 0 && aws.StringValue(c.Values[0]) == conditionValue[i] {
   750  								match = true
   751  							}
   752  						}
   753  						matchingConditions = append(matchingConditions, match)
   754  					}
   755  					if len(matchingConditions) == len(conditionField) && util.IsBoolArrayTrue(matchingConditions) {
   756  						return r.RuleArn, r.Priority, nil
   757  					}
   758  				}
   759  			}
   760  		}
   761  	} else {
   762  		return nil, nil, errors.New("Listener not found in rule list")
   763  	}
   764  	return nil, nil, errors.New("Priority not found for rule: listener " + listener + ", targetGroupArn: " + targetGroupArn + ", Field: " + strings.Join(conditionField, ",") + ", Value: " + strings.Join(conditionValue, ","))
   765  }
   766  
   767  func (a *ALB) UpdateHealthCheck(targetGroupArn string, healthCheck service.DeployHealthCheck) error {
   768  	svc := elbv2.New(session.New())
   769  	input := &elbv2.ModifyTargetGroupInput{
   770  		TargetGroupArn: aws.String(targetGroupArn),
   771  	}
   772  	if healthCheck.HealthyThreshold != 0 {
   773  		input.SetHealthyThresholdCount(healthCheck.HealthyThreshold)
   774  	}
   775  	if healthCheck.UnhealthyThreshold != 0 {
   776  		input.SetUnhealthyThresholdCount(healthCheck.UnhealthyThreshold)
   777  	}
   778  	if healthCheck.Path != "" {
   779  		input.SetHealthCheckPath(healthCheck.Path)
   780  	}
   781  	if healthCheck.Port != "" {
   782  		input.SetHealthCheckPort(healthCheck.Port)
   783  	}
   784  	if healthCheck.Protocol != "" {
   785  		input.SetHealthCheckProtocol(healthCheck.Protocol)
   786  	}
   787  	if healthCheck.Interval != 0 {
   788  		input.SetHealthCheckIntervalSeconds(healthCheck.Interval)
   789  	}
   790  	if healthCheck.Matcher != "" {
   791  		input.SetMatcher(&elbv2.Matcher{HttpCode: aws.String(healthCheck.Matcher)})
   792  	}
   793  	if healthCheck.Timeout > 0 {
   794  		input.SetHealthCheckTimeoutSeconds(healthCheck.Timeout)
   795  	}
   796  	_, err := svc.ModifyTargetGroup(input)
   797  	if err != nil {
   798  		if aerr, ok := err.(awserr.Error); ok {
   799  			albLogger.Errorf(aerr.Error())
   800  			return aerr
   801  		}
   802  		albLogger.Errorf(err.Error())
   803  		return err
   804  	}
   805  	return nil
   806  }
   807  
   808  func (a *ALB) ModifyTargetGroupAttributes(targetGroupArn string, d service.Deploy) error {
   809  	svc := elbv2.New(session.New())
   810  	input := &elbv2.ModifyTargetGroupAttributesInput{
   811  		TargetGroupArn: aws.String(targetGroupArn),
   812  		Attributes:     []*elbv2.TargetGroupAttribute{},
   813  	}
   814  
   815  	if d.DeregistrationDelay != -1 {
   816  		delay := strconv.FormatInt(d.DeregistrationDelay, 10)
   817  		input.Attributes = append(input.Attributes, &elbv2.TargetGroupAttribute{Key: aws.String("deregistration_delay.timeout_seconds"), Value: aws.String(delay)})
   818  	}
   819  
   820  	if d.Stickiness.Enabled {
   821  		input.Attributes = append(input.Attributes, &elbv2.TargetGroupAttribute{Key: aws.String("stickiness.enabled"), Value: aws.String("true")})
   822  		input.Attributes = append(input.Attributes, &elbv2.TargetGroupAttribute{Key: aws.String("stickiness.type"), Value: aws.String("lb_cookie")})
   823  		if d.Stickiness.Duration != -1 {
   824  			sd := strconv.FormatInt(d.Stickiness.Duration, 10)
   825  			input.Attributes = append(input.Attributes, &elbv2.TargetGroupAttribute{Key: aws.String("stickiness.lb_cookie.duration_seconds"), Value: aws.String(sd)})
   826  		}
   827  	}
   828  
   829  	if len(input.Attributes) == 0 {
   830  		albLogger.Errorf("Tried to modify target group, but no attributes were passed")
   831  		return nil
   832  	}
   833  
   834  	_, err := svc.ModifyTargetGroupAttributes(input)
   835  	if err != nil {
   836  		if aerr, ok := err.(awserr.Error); ok {
   837  			albLogger.Errorf(aerr.Error())
   838  			return aerr
   839  		}
   840  		albLogger.Errorf(err.Error())
   841  		return err
   842  	}
   843  	return nil
   844  }
   845  func (a *ALB) DeleteRule(ruleArn string) error {
   846  	svc := elbv2.New(session.New())
   847  	input := &elbv2.DeleteRuleInput{
   848  		RuleArn: aws.String(ruleArn),
   849  	}
   850  
   851  	albLogger.Debugf("Deleting ALB Rule: %v", ruleArn)
   852  	_, err := svc.DeleteRule(input)
   853  	if err != nil {
   854  		if aerr, ok := err.(awserr.Error); ok {
   855  			ecsLogger.Errorf("%v", aerr.Error())
   856  		} else {
   857  			ecsLogger.Errorf("%v", err.Error())
   858  		}
   859  		return err
   860  	}
   861  	return nil
   862  }
   863  func (a *ALB) getCognitoAction(targetGroupArn string, cognitoAuth service.DeployRuleConditionsCognitoAuth) ([]*elbv2.Action, error) {
   864  	// get cognito user pool info
   865  	cognito := CognitoIdp{}
   866  	userPoolArn, userPoolClientID, userPoolDomain, err := cognito.getUserPoolInfo(cognitoAuth.UserPoolName, cognitoAuth.ClientName)
   867  	if err != nil {
   868  		return nil, err
   869  	}
   870  	return []*elbv2.Action{
   871  		{
   872  			AuthenticateCognitoConfig: &elbv2.AuthenticateCognitoActionConfig{
   873  				OnUnauthenticatedRequest: aws.String("deny"),
   874  				UserPoolArn:              aws.String(userPoolArn),
   875  				UserPoolClientId:         aws.String(userPoolClientID),
   876  				UserPoolDomain:           aws.String(userPoolDomain),
   877  			},
   878  			Type:  aws.String("authenticate-cognito"),
   879  			Order: aws.Int64(1),
   880  		},
   881  		{
   882  			TargetGroupArn: aws.String(targetGroupArn),
   883  			Type:           aws.String("forward"),
   884  			Order:          aws.Int64(2),
   885  		},
   886  	}, nil
   887  }