Skip to content

Commit

Permalink
alb rule changes + cognito support (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
wardviaene authored Apr 26, 2019
1 parent c1fd661 commit 888be43
Show file tree
Hide file tree
Showing 14 changed files with 764 additions and 113 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ ecs-deploy-linux-amd64
webapp/node_modules/
webapp/dist/
vendor/
bin/
.vscode/
13 changes: 8 additions & 5 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

[[constraint]]
name = "github.com/aws/aws-sdk-go"
version = "1.15.88"
version = "1.19.15"

[[constraint]]
name = "github.com/crewjam/saml"
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ deps:
build-server:
GOOS=linux GOARCH=${GOARCH} go build ${LDFLAGS} -o ${SERVER_BINARY}-linux-${GOARCH} cmd/ecs-deploy/main.go

build-server-darwin:
GOOS=darwin GOARCH=${GOARCH} go build ${LDFLAGS} -o ${SERVER_BINARY}-linux-${GOARCH} cmd/ecs-deploy/main.go

build-client:
GOOS=linux GOARCH=${GOARCH} go build ${LDFLAGS} -o ${CLIENT_BINARY}-linux-${GOARCH} cmd/ecs-client/main.go
build-client-darwin:
GOOS=darwin GOARCH=${GOARCH} go build ${LDFLAGS} -o ${CLIENT_BINARY}-linux-${GOARCH} cmd/ecs-client/main.go

build-server-static:
CGO_ENABLED=0 GOOS=linux GOARCH=${GOARCH} go build -a -installsuffix cgo ${LDFLAGS} -o ${SERVER_BINARY}-linux-${GOARCH} cmd/ecs-deploy/main.go
Expand Down
14 changes: 8 additions & 6 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ package api

import (
"testing"

"github.com/in4it/ecs-deploy/service"
)

func TestDeployServiceValidator(t *testing.T) {
// api object
a := API{}

// test with 2 characters
d := Deploy{
Containers: []*DeployContainer{
d := service.Deploy{
Containers: []*service.DeployContainer{
{
ContainerName: "abc",
},
Expand All @@ -23,8 +25,8 @@ func TestDeployServiceValidator(t *testing.T) {
}

// test with 3 characters
d = Deploy{
Containers: []*DeployContainer{
d = service.Deploy{
Containers: []*service.DeployContainer{
{
ContainerName: "abc",
},
Expand All @@ -38,8 +40,8 @@ func TestDeployServiceValidator(t *testing.T) {

// test with wrong container name
serviceName = "myservice"
d = Deploy{
Containers: []*DeployContainer{
d = service.Deploy{
Containers: []*service.DeployContainer{
{
ContainerName: "ab",
},
Expand Down
146 changes: 118 additions & 28 deletions api/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ func (c *Controller) Deploy(serviceName string, d service.Deploy) (*service.Depl
if err != nil {
return nil, err
}
c.updateDeployment(d, ddLast, serviceName, taskDefArn, iamRoleArn)
err = c.updateDeployment(d, ddLast, serviceName, taskDefArn, iamRoleArn)
if err != nil {
controllerLogger.Errorf("updateDeployment failed: %s", err)
}
}

// Mark previous deployment as aborted if still running
Expand Down Expand Up @@ -272,6 +275,21 @@ func (c *Controller) updateDeployment(d service.Deploy, ddLast *service.DynamoDe
s.UpdateServiceListeners(s.ClusterName, s.ServiceName, listeners)
// don't update ecs service later
updateECSService = false
} else {
// check for rules changes
if c.rulesChanged(d, ddLast) {
controllerLogger.Infof("Recreating alb rules for: " + serviceName)
// recreate rules
err = c.deleteRulesForTarget(serviceName, d, targetGroupArn, alb)
if err != nil {
controllerLogger.Infof("Couldn't delete existing rules for target: " + serviceName)
}
// create new rules
_, err := c.createRulesForTarget(serviceName, d, targetGroupArn, alb)
if err != nil {
return err
}
}
}
}
ps := ecs.Paramstore{}
Expand Down Expand Up @@ -314,6 +332,29 @@ func (c *Controller) updateDeployment(d service.Deploy, ddLast *service.DynamoDe
}
return nil
}

func (c *Controller) rulesChanged(d service.Deploy, ddLast *service.DynamoDeployment) bool {
if len(d.RuleConditions) != len(ddLast.DeployData.RuleConditions) {
return true
}

// sort rule conditions
sortedRuleCondition := d.RuleConditions
ddLastSortedRuleCondition := ddLast.DeployData.RuleConditions
sort.Sort(ruleConditionSort(sortedRuleCondition))
sort.Sort(ruleConditionSort(ddLastSortedRuleCondition))
// loop over rule conditions to compare them
for k, v := range sortedRuleCondition {
v2 := ddLastSortedRuleCondition[k]
if !cmp.Equal(v, v2) {
return true
}
}

return false

}

func (c *Controller) redeploy(serviceName, time string) (*service.DeployResult, error) {
s := service.NewService()
dd, err := s.GetDeployment(serviceName, time)
Expand Down Expand Up @@ -431,13 +472,78 @@ func (c *Controller) deleteRulesForTarget(serviceName string, d service.Deploy,
if err != nil {
return err
}
ruleArns := alb.GetRulesByTargetGroupArn(*targetGroupArn)
for _, ruleArn := range ruleArns {
ruleArnsToDelete := alb.GetRulesByTargetGroupArn(*targetGroupArn)
authRuleArns := alb.GetRuleByTargetGroupArnWithAuth(*targetGroupArn)
for _, authRuleArn := range authRuleArns {
conditionField, conditionValue := alb.GetConditionsForRule(authRuleArn)
controllerLogger.Debugf("deleteRulesForTarget: found authRule with conditionField %s and conditionValue %s", strings.Join(conditionField, ","), strings.Join(conditionValue, ","))
httpListener := alb.GetListenerArnForProtocol("http")
if httpListener != "" {
ruleArn, _, err := alb.FindRule(httpListener, "", conditionField, conditionValue)
if err != nil {
controllerLogger.Debugf("deleteRulesForTarget: rule not found: %s", err)
}
if ruleArn != nil {
ruleArnsToDelete = append(ruleArnsToDelete, *ruleArn)
}

}
}
for _, ruleArn := range ruleArnsToDelete {
alb.DeleteRule(ruleArn)
}
return nil
}

// delete rule for a targetgroup with specific listener
func (c *Controller) deleteRuleForTargetWithListener(serviceName string, r *service.DeployRuleConditions, targetGroupArn *string, alb *ecs.ALB, listener string) error {
_, conditionField, conditionValue := c.getALBConditionFieldAndValue(*r, alb.GetDomain())
err := alb.GetRulesForAllListeners()
if err != nil {
return err
}
ruleArn, _, err := alb.FindRule(listener, *targetGroupArn, conditionField, conditionValue)
if err != nil {
return err
}
return alb.DeleteRule(*ruleArn)
}

// Update rule for a specific targetGroups
func (c *Controller) UpdateRuleForTarget(serviceName string, r *service.DeployRuleConditions, rLast *service.DeployRuleConditions, targetGroupArn *string, alb *ecs.ALB, listener string) error {
_, conditionField, conditionValue := c.getALBConditionFieldAndValue(*rLast, alb.GetDomain())
err := alb.GetRulesForAllListeners()
if err != nil {
return err
}
ruleArn, _, err := alb.FindRule(alb.GetListenerArnForProtocol(listener), *targetGroupArn, conditionField, conditionValue)
if err != nil {
return err
}
ruleType, _, conditionValue := c.getALBConditionFieldAndValue(*rLast, alb.GetDomain())

// if cognito is set, a redirect is needed instead (cognito doesn't work with http)
if strings.ToLower(listener) == "http" && r.CognitoAuth.ClientName != "" {
return alb.UpdateRuleToHTTPSRedirect(*targetGroupArn, *ruleArn, ruleType, conditionValue)
}

return alb.UpdateRule(*targetGroupArn, *ruleArn, ruleType, conditionValue, r.CognitoAuth)

}

func (c *Controller) getALBConditionFieldAndValue(r service.DeployRuleConditions, domain string) (string, []string, []string) {
if r.PathPattern != "" && r.Hostname != "" {
return "combined", []string{"path-pattern", "host-header"}, []string{r.PathPattern, r.Hostname + "." + domain}
}
if r.PathPattern != "" {
return "pathPattern", []string{"path-pattern"}, []string{r.PathPattern}
}
if r.Hostname != "" {
return "hostname", []string{"host-header"}, []string{r.Hostname + "." + domain}
}
return "", []string{}, []string{}
}

// Deploy rules for a specific targetGroup
func (c *Controller) createRulesForTarget(serviceName string, d service.Deploy, targetGroupArn *string, alb *ecs.ALB) ([]string, error) {
var listeners []string
Expand All @@ -450,32 +556,16 @@ func (c *Controller) createRulesForTarget(serviceName string, d service.Deploy,
if len(d.RuleConditions) > 0 {
// create rules based on conditions
var newRules int
for _, r := range d.RuleConditions {
if r.PathPattern != "" && r.Hostname != "" {
rules := []string{r.PathPattern, r.Hostname}
l, err := alb.CreateRuleForListeners("combined", r.Listeners, *targetGroupArn, rules, (priority + 10 + int64(newRules)))
if err != nil {
return nil, err
}
newRules += len(r.Listeners)
listeners = append(listeners, l...)
} else if r.PathPattern != "" {
rules := []string{r.PathPattern}
l, err := alb.CreateRuleForListeners("pathPattern", r.Listeners, *targetGroupArn, rules, (priority + 10 + int64(newRules)))
if err != nil {
return nil, err
}
newRules += len(r.Listeners)
listeners = append(listeners, l...)
} else if r.Hostname != "" {
rules := []string{r.Hostname}
l, err := alb.CreateRuleForListeners("hostname", r.Listeners, *targetGroupArn, rules, (priority + 10 + int64(newRules)))
if err != nil {
return nil, err
}
newRules += len(r.Listeners)
listeners = append(listeners, l...)
ruleConditionsSorted := d.RuleConditions
sort.Sort(ruleConditionSort(ruleConditionsSorted))
for _, r := range ruleConditionsSorted {
ruleType, _, conditionValue := c.getALBConditionFieldAndValue(*r, alb.GetDomain())
l, err := alb.CreateRuleForListeners(ruleType, r.Listeners, *targetGroupArn, conditionValue, (priority + 10 + int64(newRules)), r.CognitoAuth)
if err != nil {
return nil, err
}
newRules += len(r.Listeners)
listeners = append(listeners, l...)
}
} else {
// create default rules ( /servicename path on all listeners )
Expand Down
17 changes: 17 additions & 0 deletions api/sort.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package api

import (
"github.com/in4it/ecs-deploy/service"
)

type ruleConditionSort []*service.DeployRuleConditions

func (s ruleConditionSort) Len() int {
return len(s)
}
func (s ruleConditionSort) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s ruleConditionSort) Less(i, j int) bool {
return len(s[i].PathPattern) > len(s[j].PathPattern)
}
43 changes: 43 additions & 0 deletions api/sort_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package api

import (
"sort"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/in4it/ecs-deploy/service"
)

func TestRuleConditionSort(t *testing.T) {
conditions := []*service.DeployRuleConditions{
{
Hostname: "test",
},
{
Hostname: "test",
PathPattern: "/api",
},
{
Hostname: "test",
PathPattern: "/api/v1",
},
}
conditionsSorted := []*service.DeployRuleConditions{
{
Hostname: "test",
PathPattern: "/api/v1",
},
{
Hostname: "test",
PathPattern: "/api",
},
{
Hostname: "test",
},
}
sort.Sort(ruleConditionSort(conditions))

if !cmp.Equal(conditions, conditionsSorted) {
t.Errorf("Conditions is not correctly sorted")
}
}
Loading

0 comments on commit 888be43

Please sign in to comment.