-
Notifications
You must be signed in to change notification settings - Fork 101
/
resource.go
318 lines (269 loc) · 11 KB
/
resource.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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/*
Copyright 2019 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 resource
import (
"context"
"encoding/json"
"strings"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/meta"
)
// Supported resources with all of these annotations will be fully or partially
// propagated to the named resource of the same kind, assuming it exists and
// consents to propagation.
const (
AnnotationKeyPropagateToPrefix = "to.propagate.crossplane.io"
AnnotationKeyPropagateFromNamespace = "from.propagate.crossplane.io/namespace"
AnnotationKeyPropagateFromName = "from.propagate.crossplane.io/name"
AnnotationKeyPropagateFromUID = "from.propagate.crossplane.io/uid"
AnnotationDelimiter = "/"
)
// External resources are tagged/labelled with the following keys in the cloud
// provider API if the type supports.
const (
ExternalResourceTagKeyKind = "crossplane-kind"
ExternalResourceTagKeyName = "crossplane-name"
ExternalResourceTagKeyClass = "crossplane-class"
ExternalResourceTagKeyProvider = "crossplane-provider"
)
// A ClaimKind contains the type metadata for a kind of resource claim.
type ClaimKind schema.GroupVersionKind
// A ClassKind contains the type metadata for a kind of resource class.
type ClassKind schema.GroupVersionKind
// List returns the list kind associated with a ClassKind.
func (k ClassKind) List() schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: k.Group,
Version: k.Version,
Kind: k.Kind + "List",
}
}
// A ManagedKind contains the type metadata for a kind of managed
type ManagedKind schema.GroupVersionKind
// A TargetKind contains the type metadata for a kind of target resource.
type TargetKind schema.GroupVersionKind
// A LocalConnectionSecretOwner may create and manage a connection secret in its
// own namespace.
type LocalConnectionSecretOwner interface {
runtime.Object
metav1.Object
LocalConnectionSecretWriterTo
}
// A ManagedConnectionPropagator is responsible for propagating information
// required to connect to a managed resource (for example the connection secret)
// from the managed resource to its resource claim.
type ManagedConnectionPropagator interface {
PropagateConnection(ctx context.Context, o LocalConnectionSecretOwner, mg Managed) error
}
// A ManagedConnectionPropagatorFn is a function that satisfies the
// ManagedConnectionPropagator interface.
type ManagedConnectionPropagatorFn func(ctx context.Context, o LocalConnectionSecretOwner, mg Managed) error
// PropagateConnection information from the supplied managed resource to the
// supplied resource claim.
func (fn ManagedConnectionPropagatorFn) PropagateConnection(ctx context.Context, o LocalConnectionSecretOwner, mg Managed) error {
return fn(ctx, o, mg)
}
// LocalConnectionSecretFor creates a connection secret in the namespace of the
// supplied LocalConnectionSecretOwner, assumed to be of the supplied kind.
func LocalConnectionSecretFor(o LocalConnectionSecretOwner, kind schema.GroupVersionKind) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: o.GetNamespace(),
Name: o.GetWriteConnectionSecretToReference().Name,
OwnerReferences: []metav1.OwnerReference{meta.AsController(meta.ReferenceTo(o, kind))},
},
Data: make(map[string][]byte),
}
}
// A ConnectionSecretOwner may create and manage a connection secret in an
// arbitrary namespace.
type ConnectionSecretOwner interface {
runtime.Object
metav1.Object
ConnectionSecretWriterTo
}
// ConnectionSecretFor creates a connection for the supplied
// ConnectionSecretOwner, assumed to be of the supplied kind. The secret is
// written to 'default' namespace if the ConnectionSecretOwner does not specify
// a namespace.
func ConnectionSecretFor(o ConnectionSecretOwner, kind schema.GroupVersionKind) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: o.GetWriteConnectionSecretToReference().Namespace,
Name: o.GetWriteConnectionSecretToReference().Name,
OwnerReferences: []metav1.OwnerReference{meta.AsController(meta.ReferenceTo(o, kind))},
},
Data: make(map[string][]byte),
}
}
// MustCreateObject returns a new Object of the supplied kind. It panics if the
// kind is unknown to the supplied ObjectCreator.
func MustCreateObject(kind schema.GroupVersionKind, oc runtime.ObjectCreater) runtime.Object {
obj, err := oc.New(kind)
if err != nil {
panic(err)
}
return obj
}
// GetKind returns the GroupVersionKind of the supplied object. It return an
// error if the object is unknown to the supplied ObjectTyper, the object is
// unversioned, or the object does not have exactly one registered kind.
func GetKind(obj runtime.Object, ot runtime.ObjectTyper) (schema.GroupVersionKind, error) {
kinds, unversioned, err := ot.ObjectKinds(obj)
if err != nil {
return schema.GroupVersionKind{}, errors.Wrap(err, "cannot get kind of supplied object")
}
if unversioned {
return schema.GroupVersionKind{}, errors.New("supplied object is unversioned")
}
if len(kinds) != 1 {
return schema.GroupVersionKind{}, errors.New("supplied object does not have exactly one kind")
}
return kinds[0], nil
}
// MustGetKind returns the GroupVersionKind of the supplied object. It panics if
// the object is unknown to the supplied ObjectTyper, the object is unversioned,
// or the object does not have exactly one registered kind.
func MustGetKind(obj runtime.Object, ot runtime.ObjectTyper) schema.GroupVersionKind {
gvk, err := GetKind(obj, ot)
if err != nil {
panic(err)
}
return gvk
}
// An ErrorIs function returns true if an error satisfies a particular condition.
type ErrorIs func(err error) bool
// Ignore any errors that satisfy the supplied ErrorIs function by returning
// nil. Errors that do not satisfy the suppled function are returned unmodified.
func Ignore(is ErrorIs, err error) error {
if is(err) {
return nil
}
return err
}
// IgnoreNotFound returns the supplied error, or nil if the error indicates a
// Kubernetes resource was not found.
func IgnoreNotFound(err error) error {
return Ignore(kerrors.IsNotFound, err)
}
// ResolveClassClaimValues validates the supplied claim value against the
// supplied resource class value. If both are non-zero they must match.
func ResolveClassClaimValues(classValue, claimValue string) (string, error) {
if classValue == "" {
return claimValue, nil
}
if claimValue == "" {
return classValue, nil
}
if classValue != claimValue {
return "", errors.Errorf("claim value [%s] does not match class value [%s]", claimValue, classValue)
}
return claimValue, nil
}
// SetBindable indicates that the supplied Bindable is ready for binding to
// another Bindable, such as a resource claim or managed resource by setting its
// binding phase to "Unbound". It is a no-op for Bindables in phases "Bound" or
// "Released", because these phases may not transition back to "Unbound".
func SetBindable(b Bindable) {
switch b.GetBindingPhase() {
case v1alpha1.BindingPhaseBound, v1alpha1.BindingPhaseReleased:
return
default:
b.SetBindingPhase(v1alpha1.BindingPhaseUnbound)
}
}
// IsBindable returns true if the supplied Bindable is ready for binding to
// another Bindable, such as a resource claim or managed
func IsBindable(b Bindable) bool {
return b.GetBindingPhase() == v1alpha1.BindingPhaseUnbound
}
// IsBound returns true if the supplied Bindable is bound to another Bindable,
// such as a resource claim or managed
func IsBound(b Bindable) bool {
return b.GetBindingPhase() == v1alpha1.BindingPhaseBound
}
// IsConditionTrue returns if condition status is true
func IsConditionTrue(c v1alpha1.Condition) bool {
return c.Status == corev1.ConditionTrue
}
// An Applicator applies changes to an object.
type Applicator interface {
Apply(context.Context, client.Client, runtime.Object, ...ApplyOption) error
}
// An ApplyFn is a function that satisfies the Applicator interface.
type ApplyFn func(context.Context, client.Client, runtime.Object, ...ApplyOption) error
// Apply changes to the supplied object.
func (fn ApplyFn) Apply(ctx context.Context, c client.Client, o runtime.Object, ao ...ApplyOption) error {
return fn(ctx, c, o, ao...)
}
// An ApplyOption is a function that checks for a condition before patch.
type ApplyOption func(ctx context.Context, current, desired runtime.Object) error
// ControllersMustMatch requires any existing object to have a controller
// reference, and for that controller reference to match the controller
// reference of the supplied object.
func ControllersMustMatch() ApplyOption {
return func(_ context.Context, current, desired runtime.Object) error {
if !meta.HaveSameController(current.(metav1.Object), desired.(metav1.Object)) {
return errors.New("existing object has a different (or no) controller")
}
return nil
}
}
// Apply changes to the supplied object. The object will be created if it does
// not exist, or patched if it does.
func Apply(ctx context.Context, c client.Client, o runtime.Object, ao ...ApplyOption) error {
m, ok := o.(metav1.Object)
if !ok {
return errors.New("cannot access object metadata")
}
desired := o.DeepCopyObject()
err := c.Get(ctx, types.NamespacedName{Name: m.GetName(), Namespace: m.GetNamespace()}, o)
if kerrors.IsNotFound(err) {
return errors.Wrap(c.Create(ctx, o), "cannot create object")
}
if err != nil {
return errors.Wrap(err, "cannot get object")
}
for _, fn := range ao {
if err := fn(ctx, o, desired); err != nil {
return err
}
}
return errors.Wrap(c.Patch(ctx, o, &patch{desired}), "cannot patch object")
}
type patch struct{ from runtime.Object }
func (p *patch) Type() types.PatchType { return types.MergePatchType }
func (p *patch) Data(_ runtime.Object) ([]byte, error) { return json.Marshal(p.from) }
// GetExternalTags returns the identifying tags to be used to tag the external
// resource in provider API.
func GetExternalTags(mg Managed) map[string]string {
tags := map[string]string{
ExternalResourceTagKeyKind: strings.ToLower(mg.GetObjectKind().GroupVersionKind().GroupKind().String()),
ExternalResourceTagKeyName: mg.GetName(),
}
if mg.GetClassReference() != nil {
tags[ExternalResourceTagKeyClass] = mg.GetClassReference().Name
}
if mg.GetProviderReference() != nil {
tags[ExternalResourceTagKeyProvider] = mg.GetProviderReference().Name
}
return tags
}