Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Update stacks when needed #123

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions aws/cf.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
// Stack is a simple wrapper around a CloudFormation Stack.
type Stack struct {
name string
status string
dnsName string
scheme string
targetGroupARN string
Expand Down Expand Up @@ -48,6 +49,14 @@ func (s *Stack) TargetGroupARN() string {
return s.targetGroupARN
}

// IsComplete returns true if the stack status is a complete state.
func (s *Stack) IsComplete() bool {
if s == nil {
return false
}
return isComplete(s.status)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to move the whole isComplete(string) body to this function, because it's only used if you have a Stack and this will reduce number of functions spread.

}

// IsDeleteInProgress returns true if the stack has already a tag
// deleteScheduled.
func (s *Stack) IsDeleteInProgress() bool {
Expand Down Expand Up @@ -290,10 +299,8 @@ func getCFStackByName(svc cloudformationiface.CloudFormationAPI, stackName strin

var stack *cloudformation.Stack
for _, s := range resp.Stacks {
if isComplete(s.StackStatus) {
stack = s
break
}
stack = s
break
}
if stack == nil {
return nil, ErrLoadBalancerStackNotReady
Expand All @@ -314,14 +321,15 @@ func mapToManagedStack(stack *cloudformation.Stack) *Stack {
scheme: parameters[parameterLoadBalancerSchemeParameter],
certificateARN: tags[certificateARNTag],
tags: tags,
status: aws.StringValue(stack.StackStatus),
}
}

// isComplete returns false by design on all other status, because
// updateIngress will ignore not completed stacks.
// Stack can never be in rollback state by design.
func isComplete(stackStatus *string) bool {
switch aws.StringValue(stackStatus) {
func isComplete(stackStatus string) bool {
switch stackStatus {
case cloudformation.StackStatusCreateComplete:
return true
case cloudformation.StackStatusUpdateComplete:
Expand All @@ -336,10 +344,6 @@ func findManagedStacks(svc cloudformationiface.CloudFormationAPI, clusterID stri
err := svc.DescribeStacksPages(&cloudformation.DescribeStacksInput{},
func(page *cloudformation.DescribeStacksOutput, lastPage bool) bool {
for _, s := range page.Stacks {
if !isComplete(s.StackStatus) {
continue
}

if isManagedStack(s.Tags, clusterID) {
stacks = append(stacks, mapToManagedStack(s))
}
Expand Down
162 changes: 147 additions & 15 deletions aws/cf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func TestStackReadiness(t *testing.T) {
{"dummy-status", false},
} {
t.Run(ti.given, func(t *testing.T) {
got := isComplete(aws.String(ti.given))
got := isComplete(ti.given)
if ti.want != got {
t.Errorf("unexpected result. wanted %+v, got %+v", ti.want, got)
}
Expand Down Expand Up @@ -195,21 +195,22 @@ func TestConvertStackParameters(t *testing.T) {

}

func TestFindingManagedStacks(t *testing.T) {
func TestFindManagedStacks(t *testing.T) {
for _, ti := range []struct {
name string
given cfMockOutputs
want []*Stack
wantErr bool
}{
{
"successful-call",
cfMockOutputs{
name: "successful-call",
given: cfMockOutputs{
describeStackPages: R(nil, nil),
describeStacks: R(&cloudformation.DescribeStacksOutput{
Stacks: []*cloudformation.Stack{
{
StackName: aws.String("managed-stack-not-ready"),
StackName: aws.String("managed-stack-not-ready"),
StackStatus: aws.String(cloudformation.StackStatusUpdateInProgress),
Tags: []*cloudformation.Tag{
cfTag(kubernetesCreatorTag, kubernetesCreatorValue),
cfTag(clusterIDTagPrefix+"test-cluster", resourceLifecycleOwned),
Expand All @@ -234,7 +235,8 @@ func TestFindingManagedStacks(t *testing.T) {
},
},
{
StackName: aws.String("managed-stack-not-ready"),
StackName: aws.String("managed-stack-not-ready"),
StackStatus: aws.String(cloudformation.StackStatusUpdateInProgress),
Tags: []*cloudformation.Tag{
cfTag(kubernetesCreatorTag, kubernetesCreatorValue),
cfTag(clusterIDTagPrefix+"test-cluster", resourceLifecycleOwned),
Expand Down Expand Up @@ -262,7 +264,19 @@ func TestFindingManagedStacks(t *testing.T) {
},
}, nil),
},
[]*Stack{
want: []*Stack{
{
name: "managed-stack-not-ready",
dnsName: "example-notready.com",
certificateARN: "cert-arn",
targetGroupARN: "tg-arn",
tags: map[string]string{
kubernetesCreatorTag: kubernetesCreatorValue,
clusterIDTagPrefix + "test-cluster": resourceLifecycleOwned,
certificateARNTag: "cert-arn",
},
status: cloudformation.StackStatusUpdateInProgress,
},
{
name: "managed-stack",
dnsName: "example.com",
Expand All @@ -273,13 +287,22 @@ func TestFindingManagedStacks(t *testing.T) {
clusterIDTagPrefix + "test-cluster": resourceLifecycleOwned,
certificateARNTag: "cert-arn",
},
status: cloudformation.StackStatusCreateComplete,
},
{
name: "managed-stack-not-ready",
tags: map[string]string{
kubernetesCreatorTag: kubernetesCreatorValue,
clusterIDTagPrefix + "test-cluster": resourceLifecycleOwned,
},
status: cloudformation.StackStatusUpdateInProgress,
},
},
false,
wantErr: false,
},
{
"no-ready-stacks",
cfMockOutputs{
name: "no-ready-stacks",
given: cfMockOutputs{
describeStackPages: R(nil, nil),
describeStacks: R(&cloudformation.DescribeStacksOutput{
Stacks: []*cloudformation.Stack{
Expand Down Expand Up @@ -310,8 +333,29 @@ func TestFindingManagedStacks(t *testing.T) {
},
}, nil),
},
[]*Stack{},
true,
want: []*Stack{
{
name: "managed-stack-not-ready",
dnsName: "example-notready.com",
targetGroupARN: "tg-arn",
tags: map[string]string{
kubernetesCreatorTag: kubernetesCreatorValue,
clusterIDTagPrefix + "test-cluster": resourceLifecycleOwned,
},
status: cloudformation.StackStatusReviewInProgress,
},
{
name: "managed-stack",
dnsName: "example.com",
targetGroupARN: "tg-arn",
tags: map[string]string{
kubernetesCreatorTag: kubernetesCreatorValue,
clusterIDTagPrefix + "test-cluster": resourceLifecycleOwned,
},
status: cloudformation.StackStatusRollbackComplete,
},
},
wantErr: false,
},
{
"failed-paging",
Expand Down Expand Up @@ -343,18 +387,94 @@ func TestFindingManagedStacks(t *testing.T) {
t.Errorf("unexpected result. wanted %+v, got %+v", ti.want, got)
}
}
})
}
}

func TestGetStack(t *testing.T) {
for _, ti := range []struct {
name string
given cfMockOutputs
want *Stack
wantErr bool
}{
{
name: "successful-call",
given: cfMockOutputs{
describeStackPages: R(nil, nil),
describeStacks: R(&cloudformation.DescribeStacksOutput{
Stacks: []*cloudformation.Stack{
{
StackName: aws.String("managed-stack"),
StackStatus: aws.String(cloudformation.StackStatusCreateComplete),
Tags: []*cloudformation.Tag{
cfTag(kubernetesCreatorTag, kubernetesCreatorValue),
cfTag(clusterIDTagPrefix+"test-cluster", resourceLifecycleOwned),
cfTag(certificateARNTag, "cert-arn"),
},
Outputs: []*cloudformation.Output{
{OutputKey: aws.String(outputLoadBalancerDNSName), OutputValue: aws.String("example.com")},
{OutputKey: aws.String(outputTargetGroupARN), OutputValue: aws.String("tg-arn")},
},
},
},
}, nil),
},
want: &Stack{
name: "managed-stack",
dnsName: "example.com",
certificateARN: "cert-arn",
targetGroupARN: "tg-arn",
tags: map[string]string{
kubernetesCreatorTag: kubernetesCreatorValue,
clusterIDTagPrefix + "test-cluster": resourceLifecycleOwned,
certificateARNTag: "cert-arn",
},
status: cloudformation.StackStatusCreateComplete,
},
wantErr: false,
},
{
name: "no-ready-stacks",
given: cfMockOutputs{
describeStackPages: R(nil, nil),
describeStacks: R(&cloudformation.DescribeStacksOutput{
Stacks: []*cloudformation.Stack{},
}, nil),
},
want: nil,
wantErr: true,
},
{
"failed-paging",
cfMockOutputs{
describeStackPages: R(nil, dummyErr),
describeStacks: R(&cloudformation.DescribeStacksOutput{}, nil),
},
nil,
true,
},
{
"failed-describe-page",
cfMockOutputs{
describeStacks: R(nil, dummyErr),
},
nil,
true,
},
} {
t.Run(ti.name, func(t *testing.T) {
c := &mockCloudFormationClient{outputs: ti.given}
s, err := getStack(c, "dontcare")
if err != nil {
if !ti.wantErr {
t.Error("unexpected error", err)
}
} else {
if !reflect.DeepEqual(ti.want[0], s) {
t.Errorf("unexpected result. wanted %+v, got %+v", ti.want[0], got)
if !reflect.DeepEqual(ti.want, s) {
t.Errorf("unexpected result. wanted %+v, got %+v", ti.want, s)
}
}

})
}
}
Expand Down Expand Up @@ -478,3 +598,15 @@ func TestDeleteTime(t *testing.T) {
}

}

func TestIsComplete(t *testing.T) {
var s *Stack
if s.IsComplete() {
t.Errorf("expected the stack to not be complete")
}

s = &Stack{status: cloudformation.StackStatusCreateComplete}
if !s.IsComplete() {
t.Errorf("expected the stack to be complete")
}
}
7 changes: 1 addition & 6 deletions controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ var (
healthCheckPort uint
healthcheckInterval time.Duration
metricsAddress string
updateStackInterval time.Duration
)

func loadSettings() error {
Expand All @@ -37,7 +36,6 @@ func loadSettings() error {
"server base url. If empty will try to use the configuration from the running cluster, else it will use InsecureConfig, that does not use encryption or authentication (use case to develop with kubectl proxy).")
flag.DurationVar(&pollingInterval, "polling-interval", 30*time.Second, "sets the polling interval for "+
"ingress resources. The flag accepts a value acceptable to time.ParseDuration")
flag.DurationVar(&updateStackInterval, "update-stack-interval", 1*time.Hour, "sets the interval for update AWS ALB stack resources, which can fix migrations, if you add for example one subnet to your VPC your ALB has not interface in that. An update stack will add these interfaces automatically. The flag accepts a value acceptable to time.ParseDuration")
flag.StringVar(&cfCustomTemplate, "cf-custom-template", "",
"filename for a custom cloud formation template to use instead of the built in")
flag.DurationVar(&creationTimeout, "creation-timeout", aws.DefaultCreationTimeout,
Expand All @@ -64,9 +62,6 @@ func loadSettings() error {
if err := loadDurationFromEnv("POLLING_INTERVAL", &pollingInterval); err != nil {
return err
}
if err := loadDurationFromEnv("UPDATE_STACK_INTERVAL", &updateStackInterval); err != nil {
return err
}

if err := loadDurationFromEnv("CREATION_TIMEOUT", &creationTimeout); err != nil {
return err
Expand Down Expand Up @@ -168,7 +163,7 @@ func main() {

go serveMetrics(metricsAddress)
quitCH := make(chan struct{})
go startPolling(quitCH, certificatesProvider, awsAdapter, kubeAdapter, pollingInterval, updateStackInterval)
go startPolling(quitCH, certificatesProvider, awsAdapter, kubeAdapter, pollingInterval)
<-quitCH

log.Printf("terminating %s", os.Args[0])
Expand Down
Loading