-
Notifications
You must be signed in to change notification settings - Fork 903
/
ready.go
255 lines (220 loc) · 8.27 KB
/
ready.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/*
Copyright 2022 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package composite
import (
"context"
corev1 "k8s.io/api/core/v1"
"k8s.io/utils/ptr"
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
"github.com/crossplane/crossplane-runtime/pkg/resource"
v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
)
// Error strings.
const (
errInvalidCheck = "invalid"
errPaveObject = "cannot lookup field paths in supplied object"
errFmtRequiresFieldPath = "type %q requires a field path"
errFmtRequiresMatchString = "type %q requires a match string"
errFmtRequiresMatchConditions = "type %q requires a valid match condition"
errFmtRequiresMatchInteger = "type %q requires a match integer"
errFmtUnknownCheck = "unknown type %q"
errFmtRunCheck = "cannot run readiness check at index %d"
)
// ReadinessCheckType is used for readiness check types.
type ReadinessCheckType string
// The possible values for readiness check type.
const (
ReadinessCheckTypeNonEmpty ReadinessCheckType = "NonEmpty"
ReadinessCheckTypeMatchString ReadinessCheckType = "MatchString"
ReadinessCheckTypeMatchInteger ReadinessCheckType = "MatchInteger"
// discussion regarding MatchBool vs MatchTrue/MatchFalse:
// https://github.com/crossplane/crossplane/pull/4399#discussion_r1277225375
ReadinessCheckTypeMatchTrue ReadinessCheckType = "MatchTrue"
ReadinessCheckTypeMatchFalse ReadinessCheckType = "MatchFalse"
ReadinessCheckTypeMatchCondition ReadinessCheckType = "MatchCondition"
ReadinessCheckTypeNone ReadinessCheckType = "None"
)
// ReadinessCheck is used to indicate how to tell whether a resource is ready
// for consumption.
type ReadinessCheck struct {
// Type indicates the type of probe you'd like to use.
Type ReadinessCheckType
// FieldPath shows the path of the field whose value will be used.
FieldPath *string
// MatchString is the value you'd like to match if you're using "MatchString" type.
MatchString *string
// MatchInt is the value you'd like to match if you're using "MatchInt" type.
MatchInteger *int64
// MatchCondition is the condition you'd like to match if you're using "MatchCondition" type.
MatchCondition *MatchConditionReadinessCheck
}
// MatchConditionReadinessCheck is used to indicate how to tell whether a resource is ready
// for consumption.
type MatchConditionReadinessCheck struct {
// Type indicates the type of condition you'd like to use.
Type xpv1.ConditionType
// Status is the status of the condition you'd like to match.
Status corev1.ConditionStatus
}
// ReadinessCheckFromV1 derives a ReadinessCheck from the supplied v1.ReadinessCheck.
func ReadinessCheckFromV1(in *v1.ReadinessCheck) ReadinessCheck {
if in == nil {
return ReadinessCheck{}
}
out := ReadinessCheck{
Type: ReadinessCheckType(in.Type),
}
if in.FieldPath != "" {
out.FieldPath = ptr.To(in.FieldPath)
}
// NOTE(negz): ComposedTemplate doesn't use pointer values for optional
// strings, so today the empty string and 0 are equivalent to "unset".
if in.MatchString != "" {
out.MatchString = ptr.To(in.MatchString)
}
if in.MatchInteger != 0 {
out.MatchInteger = ptr.To[int64](in.MatchInteger)
}
if in.MatchCondition != nil {
out.MatchCondition = &MatchConditionReadinessCheck{
Type: in.MatchCondition.Type,
Status: in.MatchCondition.Status,
}
}
return out
}
// ReadinessChecksFromComposedTemplate derives readiness checks from the supplied
// composed template.
func ReadinessChecksFromComposedTemplate(t *v1.ComposedTemplate) []ReadinessCheck {
if t == nil {
return nil
}
out := make([]ReadinessCheck, len(t.ReadinessChecks))
for i := range t.ReadinessChecks {
out[i] = ReadinessCheckFromV1(&t.ReadinessChecks[i])
}
return out
}
// TODO(negz): Ideally we'd validate P&T readiness checks (which are specified
// in the Composition) using a webhook. We still need to validate the output of
// a Composition Function Pipeline, though.
// Validate returns an error if the readiness check is invalid.
func (c ReadinessCheck) Validate() error {
switch c.Type {
case ReadinessCheckTypeNone:
// This type has no dependencies.
return nil
case ReadinessCheckTypeNonEmpty, ReadinessCheckTypeMatchTrue, ReadinessCheckTypeMatchFalse:
// This type only needs a field path.
case ReadinessCheckTypeMatchString:
if c.MatchString == nil {
return errors.Errorf(errFmtRequiresMatchString, c.Type)
}
case ReadinessCheckTypeMatchInteger:
if c.MatchInteger == nil {
return errors.Errorf(errFmtRequiresMatchInteger, c.Type)
}
case ReadinessCheckTypeMatchCondition:
if c.MatchCondition == nil {
return errors.Errorf(errFmtRequiresMatchConditions, c.Type)
}
return nil
default:
return errors.Errorf(errFmtUnknownCheck, c.Type)
}
if c.FieldPath == nil {
return errors.Errorf(errFmtRequiresFieldPath, c.Type)
}
return nil
}
// IsReady runs the readiness check against the supplied object.
func (c ReadinessCheck) IsReady(p *fieldpath.Paved, o ConditionedObject) (bool, error) {
if err := c.Validate(); err != nil {
return false, errors.Wrap(err, errInvalidCheck)
}
switch c.Type {
case ReadinessCheckTypeNone:
return true, nil
case ReadinessCheckTypeNonEmpty:
if _, err := p.GetValue(*c.FieldPath); err != nil {
return false, resource.Ignore(fieldpath.IsNotFound, err)
}
return true, nil
case ReadinessCheckTypeMatchString:
val, err := p.GetString(*c.FieldPath)
if err != nil {
return false, resource.Ignore(fieldpath.IsNotFound, err)
}
return val == *c.MatchString, nil
case ReadinessCheckTypeMatchInteger:
val, err := p.GetInteger(*c.FieldPath)
if err != nil {
return false, resource.Ignore(fieldpath.IsNotFound, err)
}
return val == *c.MatchInteger, nil
case ReadinessCheckTypeMatchCondition:
val := o.GetCondition(c.MatchCondition.Type)
return val.Status == c.MatchCondition.Status, nil
case ReadinessCheckTypeMatchFalse:
val, err := p.GetBool(*c.FieldPath)
if err != nil {
return false, resource.Ignore(fieldpath.IsNotFound, err)
}
return val == false, nil //nolint:gosimple // returning '!val' here as suggested hurts readability
case ReadinessCheckTypeMatchTrue:
val, err := p.GetBool(*c.FieldPath)
if err != nil {
return false, resource.Ignore(fieldpath.IsNotFound, err)
}
return val == true, nil //nolint:gosimple // returning 'val' here as suggested hurts readability
}
return false, nil
}
// A ReadinessChecker checks whether a composed resource is ready or not.
type ReadinessChecker interface {
IsReady(ctx context.Context, o ConditionedObject, rc ...ReadinessCheck) (ready bool, err error)
}
// A ReadinessCheckerFn checks whether a composed resource is ready or not.
type ReadinessCheckerFn func(ctx context.Context, o ConditionedObject, rc ...ReadinessCheck) (ready bool, err error)
// IsReady reports whether a composed resource is ready or not.
func (fn ReadinessCheckerFn) IsReady(ctx context.Context, o ConditionedObject, rc ...ReadinessCheck) (ready bool, err error) {
return fn(ctx, o, rc...)
}
// A ConditionedObject is a runtime object with conditions.
type ConditionedObject interface {
resource.Object
resource.Conditioned
}
// IsReady returns whether the composed resource is ready.
func IsReady(_ context.Context, o ConditionedObject, rc ...ReadinessCheck) (bool, error) {
// kept as a safety net, but defaulting should ensure this is never hit
if len(rc) == 0 {
return resource.IsConditionTrue(o.GetCondition(xpv1.TypeReady)), nil
}
paved, err := fieldpath.PaveObject(o)
if err != nil {
return false, errors.Wrap(err, errPaveObject)
}
for i := range rc {
ready, err := rc[i].IsReady(paved, o)
if err != nil {
return false, errors.Wrapf(err, errFmtRunCheck, i)
}
if !ready {
return false, nil
}
}
return true, nil
}