Skip to content

Commit 932df41

Browse files
authored
add common ACK pkg/condition (#24)
Adds common resource condition functions to a new `pkg/condition`: - `Synced()` returns the resource's Condition of type ConditionTypeResourceSynced, or nil if the condition isn't found. - `Terminal()` returns the resource's Condition of type ConditionTypeTerminal, or nil if the condition isn't found. - `FirstOfType()` returns the first Condition of the specified type, or nil if a condition of the type isn't found on the resource. - `AllOfType()` returns a slice of `Condition` having the specified condition type. - `SetSynced()` ensures that a Condition of type ConditionTypeResourceSynced is present in the resource's Conditions collection and has a specified status, message and reason - `SetTerminal()` ensures that a Condition of type ConditionTypeResourceTerminal is present in the resource's Conditions collection and has a specified status, message and reason **IMPORTANT COMPATIBILITY NOTE**: Note that the `pkg/types.AWSResource` interface has been modified to be a composition of a new `pkg/types.ConditionManager` interface which adds a new `ReplaceConditions()` method that overwrites a resource's Conditions collection. This is a backwards-incompatible change since the `pkg/types.AWSResource` implementations generated by `aws-controllers-k8s/code-generator` (the `pkg/{RESOURCE/resource.go` files...}) will not include this new `ReplaceConditions` implementation and thus won't compile if you attempt to regenerate a controller using the ACK runtime after this patch is merged. A corresponding patch to the code-generator is coming that adds the `ReplaceCondiions` implementation. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 814ef67 commit 932df41

File tree

6 files changed

+370
-2
lines changed

6 files changed

+370
-2
lines changed

mocks/pkg/types/aws_resource.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mocks/pkg/types/condition_manager.go

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/condition/condition.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package condition
15+
16+
import (
17+
corev1 "k8s.io/api/core/v1"
18+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19+
20+
ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
21+
acktypes "github.com/aws-controllers-k8s/runtime/pkg/types"
22+
)
23+
24+
// Synced returns the Condition in the resource's Conditions collection that is
25+
// of type ConditionTypeResourceSynced. If no such condition is found, returns
26+
// nil.
27+
func Synced(subject acktypes.ConditionManager) *ackv1alpha1.Condition {
28+
return FirstOfType(subject, ackv1alpha1.ConditionTypeResourceSynced)
29+
}
30+
31+
// Terminal returns the Condition in the resource's Conditions collection that
32+
// is of type ConditionTypeTerminal. If no such condition is found, returns
33+
// nil.
34+
func Terminal(subject acktypes.ConditionManager) *ackv1alpha1.Condition {
35+
return FirstOfType(subject, ackv1alpha1.ConditionTypeTerminal)
36+
}
37+
38+
// FirstOfType returns the first Condition in the resource's Conditions
39+
// collection of the supplied type. If no such condition is found, returns nil.
40+
func FirstOfType(
41+
subject acktypes.ConditionManager,
42+
condType ackv1alpha1.ConditionType,
43+
) *ackv1alpha1.Condition {
44+
for _, condition := range subject.Conditions() {
45+
if condition.Type == condType {
46+
return condition
47+
}
48+
}
49+
return nil
50+
}
51+
52+
// AllOfType returns a slice of Conditions in the resource's Conditions
53+
// collection of the supplied type.
54+
func AllOfType(
55+
subject acktypes.ConditionManager,
56+
condType ackv1alpha1.ConditionType,
57+
) []*ackv1alpha1.Condition {
58+
res := []*ackv1alpha1.Condition{}
59+
for _, condition := range subject.Conditions() {
60+
if condition.Type == condType {
61+
res = append(res, condition)
62+
}
63+
}
64+
return res
65+
}
66+
67+
// SetSynced sets the resource's Condition of type ConditionTypeResourceSynced
68+
// to the supplied status, optional message and reason.
69+
func SetSynced(
70+
subject acktypes.ConditionManager,
71+
status corev1.ConditionStatus,
72+
message *string,
73+
reason *string,
74+
) {
75+
allConds := subject.Conditions()
76+
var c *ackv1alpha1.Condition
77+
if c = Synced(subject); c == nil {
78+
c = &ackv1alpha1.Condition{
79+
Type: ackv1alpha1.ConditionTypeResourceSynced,
80+
}
81+
allConds = append(allConds, c)
82+
}
83+
now := metav1.Now()
84+
c.LastTransitionTime = &now
85+
c.Status = status
86+
c.Message = message
87+
c.Reason = reason
88+
subject.ReplaceConditions(allConds)
89+
}
90+
91+
// SetTerminal sets the resource's Condition of type ConditionTypeTerminal to
92+
// the supplied status, optional message and reason.
93+
func SetTerminal(
94+
subject acktypes.ConditionManager,
95+
status corev1.ConditionStatus,
96+
message *string,
97+
reason *string,
98+
) {
99+
allConds := subject.Conditions()
100+
var c *ackv1alpha1.Condition
101+
if c = Terminal(subject); c == nil {
102+
c = &ackv1alpha1.Condition{
103+
Type: ackv1alpha1.ConditionTypeTerminal,
104+
}
105+
allConds = append(allConds, c)
106+
}
107+
now := metav1.Now()
108+
c.LastTransitionTime = &now
109+
c.Status = status
110+
c.Message = message
111+
c.Reason = reason
112+
subject.ReplaceConditions(allConds)
113+
}

pkg/condition/condition_test.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package condition_test
15+
16+
import (
17+
"testing"
18+
19+
"github.com/stretchr/testify/assert"
20+
"github.com/stretchr/testify/mock"
21+
22+
ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
23+
ackcond "github.com/aws-controllers-k8s/runtime/pkg/condition"
24+
corev1 "k8s.io/api/core/v1"
25+
26+
ackmocks "github.com/aws-controllers-k8s/runtime/mocks/pkg/types"
27+
)
28+
29+
func TestConditionGetters(t *testing.T) {
30+
assert := assert.New(t)
31+
32+
conds := []*ackv1alpha1.Condition{}
33+
34+
r := &ackmocks.AWSResource{}
35+
r.On("Conditions").Return(conds)
36+
37+
got := ackcond.Synced(r)
38+
assert.Nil(got)
39+
40+
got = ackcond.Terminal(r)
41+
assert.Nil(got)
42+
43+
conds = append(conds, &ackv1alpha1.Condition{
44+
Type: ackv1alpha1.ConditionTypeResourceSynced,
45+
Status: corev1.ConditionFalse,
46+
})
47+
48+
r = &ackmocks.AWSResource{}
49+
r.On("Conditions").Return(conds)
50+
51+
got = ackcond.Synced(r)
52+
assert.NotNil(got)
53+
54+
got = ackcond.Terminal(r)
55+
assert.Nil(got)
56+
57+
conds = append(conds, &ackv1alpha1.Condition{
58+
Type: ackv1alpha1.ConditionTypeTerminal,
59+
Status: corev1.ConditionFalse,
60+
})
61+
62+
r = &ackmocks.AWSResource{}
63+
r.On("Conditions").Return(conds)
64+
65+
got = ackcond.Synced(r)
66+
assert.NotNil(got)
67+
68+
got = ackcond.Terminal(r)
69+
assert.NotNil(got)
70+
71+
gotAll := ackcond.AllOfType(r, ackv1alpha1.ConditionTypeAdvisory)
72+
assert.Empty(gotAll)
73+
74+
msg1 := "advice 1"
75+
conds = append(conds, &ackv1alpha1.Condition{
76+
Type: ackv1alpha1.ConditionTypeAdvisory,
77+
Status: corev1.ConditionTrue,
78+
Message: &msg1,
79+
})
80+
81+
msg2 := "advice 2"
82+
conds = append(conds, &ackv1alpha1.Condition{
83+
Type: ackv1alpha1.ConditionTypeAdvisory,
84+
Status: corev1.ConditionTrue,
85+
Message: &msg2,
86+
})
87+
88+
r = &ackmocks.AWSResource{}
89+
r.On("Conditions").Return(conds)
90+
91+
gotAll = ackcond.AllOfType(r, ackv1alpha1.ConditionTypeAdvisory)
92+
assert.NotEmpty(gotAll)
93+
assert.Equal(len(gotAll), 2)
94+
}
95+
96+
func TestConditionSetters(t *testing.T) {
97+
r := &ackmocks.AWSResource{}
98+
r.On("Conditions").Return([]*ackv1alpha1.Condition{})
99+
100+
// Ensure that if there is no synced condition, it gets added...
101+
r.On(
102+
"ReplaceConditions",
103+
mock.MatchedBy(func(subject []*ackv1alpha1.Condition) bool {
104+
if len(subject) != 1 {
105+
return false
106+
}
107+
// We need to ignore timestamps for LastTransitionTime in our argument
108+
// assertions...
109+
return (subject[0].Type == ackv1alpha1.ConditionTypeResourceSynced &&
110+
subject[0].Status == corev1.ConditionTrue &&
111+
subject[0].Message == nil &&
112+
subject[0].Reason == nil)
113+
}),
114+
)
115+
116+
ackcond.SetSynced(r, corev1.ConditionTrue, nil, nil)
117+
118+
// Ensure that SetSynced doesn't overwrite any other conditions...
119+
r = &ackmocks.AWSResource{}
120+
r.On("Conditions").Return(
121+
[]*ackv1alpha1.Condition{
122+
&ackv1alpha1.Condition{
123+
Type: ackv1alpha1.ConditionTypeTerminal,
124+
Status: corev1.ConditionTrue,
125+
},
126+
},
127+
)
128+
r.On(
129+
"ReplaceConditions",
130+
mock.MatchedBy(func(subject []*ackv1alpha1.Condition) bool {
131+
if len(subject) != 2 {
132+
return false
133+
}
134+
return (subject[0].Type == ackv1alpha1.ConditionTypeTerminal &&
135+
subject[0].Status == corev1.ConditionTrue &&
136+
subject[1].Type == ackv1alpha1.ConditionTypeResourceSynced &&
137+
subject[1].Status == corev1.ConditionFalse)
138+
}),
139+
)
140+
141+
ackcond.SetSynced(r, corev1.ConditionFalse, nil, nil)
142+
143+
// Ensure that SetSynced overwrites an existing synced condition...
144+
r = &ackmocks.AWSResource{}
145+
r.On("Conditions").Return(
146+
[]*ackv1alpha1.Condition{
147+
&ackv1alpha1.Condition{
148+
Type: ackv1alpha1.ConditionTypeResourceSynced,
149+
Status: corev1.ConditionFalse,
150+
},
151+
},
152+
)
153+
r.On(
154+
"ReplaceConditions",
155+
mock.MatchedBy(func(subject []*ackv1alpha1.Condition) bool {
156+
if len(subject) != 1 {
157+
return false
158+
}
159+
return (subject[0].Type == ackv1alpha1.ConditionTypeResourceSynced &&
160+
subject[0].Status == corev1.ConditionTrue)
161+
}),
162+
)
163+
164+
ackcond.SetSynced(r, corev1.ConditionTrue, nil, nil)
165+
166+
msg1 := "message 1"
167+
reason1 := "reason 1"
168+
169+
// Ensure that if there is no terminal condition, it gets added...
170+
r = &ackmocks.AWSResource{}
171+
r.On("Conditions").Return([]*ackv1alpha1.Condition{})
172+
r.On(
173+
"ReplaceConditions",
174+
mock.MatchedBy(func(subject []*ackv1alpha1.Condition) bool {
175+
if len(subject) != 1 {
176+
return false
177+
}
178+
// We need to ignore timestamps for LastTransitionTime in our argument
179+
// assertions...
180+
return (subject[0].Type == ackv1alpha1.ConditionTypeTerminal &&
181+
subject[0].Status == corev1.ConditionTrue &&
182+
subject[0].Message == &msg1 &&
183+
subject[0].Reason == &reason1)
184+
}),
185+
)
186+
187+
ackcond.SetTerminal(r, corev1.ConditionTrue, &msg1, &reason1)
188+
}

pkg/types/aws_resource.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,11 @@ type RuntimeMetaObject interface {
3232
// AWSResource represents a custom resource object in the Kubernetes API that
3333
// corresponds to a resource in an AWS service API.
3434
type AWSResource interface {
35+
ConditionManager
3536
// Identifiers returns an AWSResourceIdentifiers object containing various
3637
// identifying information, including the AWS account ID that owns the
3738
// resource, the resource's AWS Resource Name (ARN)
3839
Identifiers() AWSResourceIdentifiers
39-
// Conditions returns the ACK Conditions collection for the AWSResource
40-
Conditions() []*ackv1alpha1.Condition
4140
// IsBeingDeleted returns true if the Kubernetes resource has a non-zero
4241
// deletion timestamp
4342
IsBeingDeleted() bool

pkg/types/condition_manager.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package types
15+
16+
import (
17+
ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
18+
)
19+
20+
// ConditionManager describes a thing that can set and retrieve Condition
21+
// objects.
22+
type ConditionManager interface {
23+
// Conditions returns the ACK Conditions collection for the AWSResource
24+
Conditions() []*ackv1alpha1.Condition
25+
// ReplaceConditions replaces the resource's set of Condition structs with
26+
// the supplied slice of Conditions.
27+
ReplaceConditions([]*ackv1alpha1.Condition)
28+
}

0 commit comments

Comments
 (0)