Skip to content

Commit c056ae9

Browse files
committed
refactor shared api types to common package
Signed-off-by: Erhan Cagirici <[email protected]>
1 parent 54f050c commit c056ae9

File tree

15 files changed

+945
-477
lines changed

15 files changed

+945
-477
lines changed

.golangci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,13 @@ linters:
219219
- staticcheck
220220
text: 'SA1019: .+ is deprecated: Use Composition Functions instead.'
221221

222+
# Some shared structs in apis/common/v1 are moved to
223+
# apis/common. To preserve a backward-compatible directory structure
224+
# package had to be named common, which we suppress.
225+
- linters:
226+
- revive
227+
text: "var-naming: avoid meaningless package names"
228+
path: apis/common
222229
paths:
223230
- zz_generated\..+\.go$
224231
- .+\.pb.go$

apis/common/condition.go

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
/*
2+
Copyright 2019 The Crossplane Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package common
18+
19+
import (
20+
"sort"
21+
22+
corev1 "k8s.io/api/core/v1"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
)
25+
26+
// A ConditionType represents a condition a resource could be in.
27+
type ConditionType string
28+
29+
// Condition types.
30+
const (
31+
// TypeReady resources are believed to be ready to handle work.
32+
TypeReady ConditionType = "Ready"
33+
34+
// TypeSynced resources are believed to be in sync with the
35+
// Kubernetes resources that manage their lifecycle.
36+
TypeSynced ConditionType = "Synced"
37+
38+
// TypeHealthy resources are believed to be in a healthy state and to have all
39+
// of their child resources in a healthy state. For example, a claim is
40+
// healthy when the claim is synced and the underlying composite resource is
41+
// both synced and healthy. A composite resource is healthy when the composite
42+
// resource is synced and all composed resources are synced and, if
43+
// applicable, healthy (e.g., the composed resource is a composite resource).
44+
// TODO: This condition is not yet implemented. It is currently just reserved
45+
// as a system condition. See the tracking issue for more details
46+
// https://github.com/crossplane/crossplane/issues/5643.
47+
TypeHealthy ConditionType = "Healthy"
48+
)
49+
50+
// A ConditionReason represents the reason a resource is in a condition.
51+
type ConditionReason string
52+
53+
// Reasons a resource is or is not ready.
54+
const (
55+
ReasonAvailable ConditionReason = "Available"
56+
ReasonUnavailable ConditionReason = "Unavailable"
57+
ReasonCreating ConditionReason = "Creating"
58+
ReasonDeleting ConditionReason = "Deleting"
59+
)
60+
61+
// Reasons a resource is or is not synced.
62+
const (
63+
ReasonReconcileSuccess ConditionReason = "ReconcileSuccess"
64+
ReasonReconcileError ConditionReason = "ReconcileError"
65+
ReasonReconcilePaused ConditionReason = "ReconcilePaused"
66+
)
67+
68+
// See https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
69+
70+
// A Condition that may apply to a resource.
71+
type Condition struct { //nolint:recvcheck // False positive - only has non-pointer methods AFAICT.
72+
// Type of this condition. At most one of each condition type may apply to
73+
// a resource at any point in time.
74+
Type ConditionType `json:"type"`
75+
76+
// Status of this condition; is it currently True, False, or Unknown?
77+
Status corev1.ConditionStatus `json:"status"`
78+
79+
// LastTransitionTime is the last time this condition transitioned from one
80+
// status to another.
81+
LastTransitionTime metav1.Time `json:"lastTransitionTime"`
82+
83+
// A Reason for this condition's last transition from one status to another.
84+
Reason ConditionReason `json:"reason"`
85+
86+
// A Message containing details about this condition's last transition from
87+
// one status to another, if any.
88+
// +optional
89+
Message string `json:"message,omitempty"`
90+
91+
// ObservedGeneration represents the .metadata.generation that the condition was set based upon.
92+
// For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
93+
// with respect to the current state of the instance.
94+
// +optional
95+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
96+
}
97+
98+
// Equal returns true if the condition is identical to the supplied condition,
99+
// ignoring the LastTransitionTime.
100+
func (c Condition) Equal(other Condition) bool {
101+
return c.Type == other.Type &&
102+
c.Status == other.Status &&
103+
c.Reason == other.Reason &&
104+
c.Message == other.Message &&
105+
c.ObservedGeneration == other.ObservedGeneration
106+
}
107+
108+
// WithMessage returns a condition by adding the provided message to existing
109+
// condition.
110+
func (c Condition) WithMessage(msg string) Condition {
111+
c.Message = msg
112+
return c
113+
}
114+
115+
// WithObservedGeneration returns a condition by adding the provided observed generation
116+
// to existing condition.
117+
func (c Condition) WithObservedGeneration(gen int64) Condition {
118+
c.ObservedGeneration = gen
119+
return c
120+
}
121+
122+
// IsSystemConditionType returns true if the condition is owned by the
123+
// Crossplane system (e.g, Ready, Synced, Healthy).
124+
func IsSystemConditionType(t ConditionType) bool {
125+
switch t {
126+
case TypeReady, TypeSynced, TypeHealthy:
127+
return true
128+
}
129+
130+
return false
131+
}
132+
133+
// NOTE(negz): Conditions are implemented as a slice rather than a map to comply
134+
// with Kubernetes API conventions. Ideally we'd comply by using a map that
135+
// marshalled to a JSON array, but doing so confuses the CRD schema generator.
136+
// https://github.com/kubernetes/community/blob/9bf8cd/contributors/devel/sig-architecture/api-conventions.md#lists-of-named-subobjects-preferred-over-maps
137+
138+
// NOTE(negz): Do not manipulate Conditions directly. Use the Set method.
139+
140+
// A ConditionedStatus reflects the observed status of a resource. Only
141+
// one condition of each type may exist.
142+
type ConditionedStatus struct {
143+
// Conditions of the resource.
144+
// +listType=map
145+
// +listMapKey=type
146+
// +optional
147+
Conditions []Condition `json:"conditions,omitempty"`
148+
}
149+
150+
// NewConditionedStatus returns a stat with the supplied conditions set.
151+
func NewConditionedStatus(c ...Condition) *ConditionedStatus {
152+
s := &ConditionedStatus{}
153+
s.SetConditions(c...)
154+
155+
return s
156+
}
157+
158+
// GetCondition returns the condition for the given ConditionType if exists,
159+
// otherwise returns nil.
160+
func (s *ConditionedStatus) GetCondition(ct ConditionType) Condition {
161+
for _, c := range s.Conditions {
162+
if c.Type == ct {
163+
return c
164+
}
165+
}
166+
167+
return Condition{Type: ct, Status: corev1.ConditionUnknown}
168+
}
169+
170+
// SetConditions sets the supplied conditions, replacing any existing conditions
171+
// of the same type. This is a no-op if all supplied conditions are identical,
172+
// ignoring the last transition time, to those already set.
173+
func (s *ConditionedStatus) SetConditions(c ...Condition) {
174+
for _, cond := range c {
175+
exists := false
176+
177+
for i, existing := range s.Conditions {
178+
if existing.Type != cond.Type {
179+
continue
180+
}
181+
182+
if existing.Equal(cond) {
183+
exists = true
184+
continue
185+
}
186+
187+
s.Conditions[i] = cond
188+
exists = true
189+
}
190+
191+
if !exists {
192+
s.Conditions = append(s.Conditions, cond)
193+
}
194+
}
195+
}
196+
197+
// Equal returns true if the status is identical to the supplied status,
198+
// ignoring the LastTransitionTimes and order of statuses.
199+
func (s *ConditionedStatus) Equal(other *ConditionedStatus) bool {
200+
if s == nil || other == nil {
201+
return s == nil && other == nil
202+
}
203+
204+
if len(other.Conditions) != len(s.Conditions) {
205+
return false
206+
}
207+
208+
sc := make([]Condition, len(s.Conditions))
209+
copy(sc, s.Conditions)
210+
211+
oc := make([]Condition, len(other.Conditions))
212+
copy(oc, other.Conditions)
213+
214+
// We should not have more than one condition of each type.
215+
sort.Slice(sc, func(i, j int) bool { return sc[i].Type < sc[j].Type })
216+
sort.Slice(oc, func(i, j int) bool { return oc[i].Type < oc[j].Type })
217+
218+
for i := range sc {
219+
if !sc[i].Equal(oc[i]) {
220+
return false
221+
}
222+
}
223+
224+
return true
225+
}
226+
227+
// Creating returns a condition that indicates the resource is currently
228+
// being created.
229+
func Creating() Condition {
230+
return Condition{
231+
Type: TypeReady,
232+
Status: corev1.ConditionFalse,
233+
LastTransitionTime: metav1.Now(),
234+
Reason: ReasonCreating,
235+
}
236+
}
237+
238+
// Deleting returns a condition that indicates the resource is currently
239+
// being deleted.
240+
func Deleting() Condition {
241+
return Condition{
242+
Type: TypeReady,
243+
Status: corev1.ConditionFalse,
244+
LastTransitionTime: metav1.Now(),
245+
Reason: ReasonDeleting,
246+
}
247+
}
248+
249+
// Available returns a condition that indicates the resource is
250+
// currently observed to be available for use.
251+
func Available() Condition {
252+
return Condition{
253+
Type: TypeReady,
254+
Status: corev1.ConditionTrue,
255+
LastTransitionTime: metav1.Now(),
256+
Reason: ReasonAvailable,
257+
}
258+
}
259+
260+
// Unavailable returns a condition that indicates the resource is not
261+
// currently available for use. Unavailable should be set only when Crossplane
262+
// expects the resource to be available but knows it is not, for example
263+
// because its API reports it is unhealthy.
264+
func Unavailable() Condition {
265+
return Condition{
266+
Type: TypeReady,
267+
Status: corev1.ConditionFalse,
268+
LastTransitionTime: metav1.Now(),
269+
Reason: ReasonUnavailable,
270+
}
271+
}
272+
273+
// ReconcileSuccess returns a condition indicating that Crossplane successfully
274+
// completed the most recent reconciliation of the resource.
275+
func ReconcileSuccess() Condition {
276+
return Condition{
277+
Type: TypeSynced,
278+
Status: corev1.ConditionTrue,
279+
LastTransitionTime: metav1.Now(),
280+
Reason: ReasonReconcileSuccess,
281+
}
282+
}
283+
284+
// ReconcileError returns a condition indicating that Crossplane encountered an
285+
// error while reconciling the resource. This could mean Crossplane was
286+
// unable to update the resource to reflect its desired state, or that
287+
// Crossplane was unable to determine the current actual state of the resource.
288+
func ReconcileError(err error) Condition {
289+
return Condition{
290+
Type: TypeSynced,
291+
Status: corev1.ConditionFalse,
292+
LastTransitionTime: metav1.Now(),
293+
Reason: ReasonReconcileError,
294+
Message: err.Error(),
295+
}
296+
}
297+
298+
// ReconcilePaused returns a condition that indicates reconciliation on
299+
// the managed resource is paused via the pause annotation.
300+
func ReconcilePaused() Condition {
301+
return Condition{
302+
Type: TypeSynced,
303+
Status: corev1.ConditionFalse,
304+
LastTransitionTime: metav1.Now(),
305+
Reason: ReasonReconcilePaused,
306+
}
307+
}

apis/common/v1/condition_test.go renamed to apis/common/condition_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package v1
17+
package common
1818

1919
import (
2020
"testing"

apis/common/doc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Package common contains core API types used by most Crossplane resources.
2+
// +kubebuilder:object:generate=true
3+
package common

apis/common/merge.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright 2021 The Crossplane Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package common
18+
19+
import (
20+
"dario.cat/mergo"
21+
)
22+
23+
// MergeOptions Specifies merge options on a field path.
24+
type MergeOptions struct { // TODO(aru): add more options that control merging behavior
25+
// Specifies that already existing values in a merged map should be preserved
26+
// +optional
27+
KeepMapValues *bool `json:"keepMapValues,omitempty"`
28+
// Specifies that already existing elements in a merged slice should be preserved
29+
// +optional
30+
AppendSlice *bool `json:"appendSlice,omitempty"`
31+
}
32+
33+
// MergoConfiguration the default behavior is to replace maps and slices.
34+
func (mo *MergeOptions) MergoConfiguration() []func(*mergo.Config) {
35+
config := []func(*mergo.Config){mergo.WithOverride}
36+
if mo == nil {
37+
return config
38+
}
39+
40+
if mo.KeepMapValues != nil && *mo.KeepMapValues {
41+
config = config[:0]
42+
}
43+
44+
if mo.AppendSlice != nil && *mo.AppendSlice {
45+
config = append(config, mergo.WithAppendSlice)
46+
}
47+
48+
return config
49+
}
50+
51+
// IsAppendSlice returns true if mo.AppendSlice is set to true.
52+
func (mo *MergeOptions) IsAppendSlice() bool {
53+
return mo != nil && mo.AppendSlice != nil && *mo.AppendSlice
54+
}

apis/common/v1/merge_test.go renamed to apis/common/merge_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package v1
17+
package common
1818

1919
import (
2020
"reflect"

0 commit comments

Comments
 (0)