forked from crossplane-contrib/function-patch-and-transform
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconnection.go
More file actions
214 lines (187 loc) · 8.4 KB
/
connection.go
File metadata and controls
214 lines (187 loc) · 8.4 KB
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
package main
import (
"github.com/crossplane-contrib/function-patch-and-transform/input/v1beta1"
xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1"
"github.com/crossplane/crossplane-runtime/v2/pkg/errors"
"github.com/crossplane/crossplane-runtime/v2/pkg/fieldpath"
"github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed"
xpresource "github.com/crossplane/crossplane-runtime/v2/pkg/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/json"
"github.com/crossplane/function-sdk-go/resource"
"github.com/crossplane/function-sdk-go/resource/composed"
)
// ConnectionDetailsExtractor extracts the connection details of a resource.
type ConnectionDetailsExtractor interface {
// ExtractConnection of the supplied resource.
ExtractConnection(cd xpresource.Composed, conn managed.ConnectionDetails, cfg ...v1beta1.ConnectionDetail) (managed.ConnectionDetails, error)
}
// A ConnectionDetailsExtractorFn is a function that satisfies
// ConnectionDetailsExtractor.
type ConnectionDetailsExtractorFn func(cd xpresource.Composed, conn managed.ConnectionDetails, cfg ...v1beta1.ConnectionDetail) (managed.ConnectionDetails, error)
// ExtractConnection of the supplied resource.
func (fn ConnectionDetailsExtractorFn) ExtractConnection(cd xpresource.Composed, conn managed.ConnectionDetails, cfg ...v1beta1.ConnectionDetail) (managed.ConnectionDetails, error) {
return fn(cd, conn, cfg...)
}
// ExtractConnectionDetails extracts XR connection details from the supplied
// composed resource. If no ExtractConfigs are supplied no connection details
// will be returned.
func ExtractConnectionDetails(cd xpresource.Composed, data managed.ConnectionDetails, cfgs ...v1beta1.ConnectionDetail) (managed.ConnectionDetails, error) {
out := map[string][]byte{}
for _, cfg := range cfgs {
if err := ValidateConnectionDetail(cfg); err != nil {
return nil, errors.Wrap(err, "invalid")
}
switch cfg.Type {
case v1beta1.ConnectionDetailTypeFromValue:
out[cfg.Name] = []byte(*cfg.Value)
case v1beta1.ConnectionDetailTypeFromConnectionSecretKey:
if data[*cfg.FromConnectionSecretKey] == nil {
// We don't consider this an error because it's possible the
// key will still be written at some point in the future.
continue
}
out[cfg.Name] = data[*cfg.FromConnectionSecretKey]
case v1beta1.ConnectionDetailTypeFromFieldPath:
// Note we're checking that the error _is_ nil. If we hit an error
// we silently avoid including this connection secret. It's possible
// the path will start existing with a valid value in future.
if b, err := fromFieldPath(cd, *cfg.FromFieldPath); err == nil {
out[cfg.Name] = b
}
}
}
return out, nil
}
// fromFieldPath tries to read the value from the supplied field path first as a
// plain string. If this fails, it falls back to reading it as JSON.
func fromFieldPath(from runtime.Object, path string) ([]byte, error) {
fromMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(from)
if err != nil {
return nil, err
}
str, err := fieldpath.Pave(fromMap).GetString(path)
if err == nil {
return []byte(str), nil
}
in, err := fieldpath.Pave(fromMap).GetValue(path)
if err != nil {
return nil, err
}
return json.Marshal(in)
}
// supportsConnectionDetails determines if the given XR supports native/classic
// connection details.
func supportsConnectionDetails(xr *resource.Composite) bool {
// v2 modern XRs don't support connection details. They should have a
// spec.crossplane field, which may be our only indication it's a v2 XR
_, err := xr.Resource.GetValue("spec.crossplane")
return err != nil
}
// composeConnectionSecret creates a Secret composed resource containing the
// provided connection details.
func composeConnectionSecret(xr *resource.Composite, details resource.ConnectionDetails, ref *v1beta1.WriteConnectionSecretToRef) (*resource.DesiredComposed, error) {
if len(details) == 0 {
return nil, nil
}
secret := composed.New()
secret.SetAPIVersion("v1")
secret.SetKind("Secret")
secretRef, err := getConnectionSecretRef(xr, ref)
if err != nil {
return nil, errors.Wrap(err, "cannot generate connection secret reference")
}
secret.SetName(secretRef.Name)
secret.SetNamespace(secretRef.Namespace)
if err := secret.SetValue("data", details); err != nil {
return nil, errors.Wrap(err, "cannot set connection secret data")
}
if err := secret.SetValue("type", xpresource.SecretTypeConnection); err != nil {
return nil, errors.Wrap(err, "cannot set connection secret type")
}
return &resource.DesiredComposed{
Resource: secret,
Ready: resource.ReadyTrue,
}, nil
}
// getConnectionSecretRef creates a connection secret reference from the given
// XR and input. The patches for the reference will be applied before the
// reference is returned.
func getConnectionSecretRef(xr *resource.Composite, input *v1beta1.WriteConnectionSecretToRef) (xpv1.SecretReference, error) {
// Get the base connection secret ref to start with
ref := getBaseConnectionSecretRef(xr, input)
// Apply patches to the base connection secret ref if they've been provided
if input != nil && len(input.Patches) > 0 {
if err := applyConnectionSecretPatches(xr, &ref, input.Patches); err != nil {
return xpv1.SecretReference{}, errors.Wrap(err, "cannot apply connection secret patches")
}
}
return ref, nil
}
// getBaseConnectionSecretRef determines the base connection secret reference
// without any patches. This reference is generated with the following
// precedence:
// 1. xr.spec.writeConnectionSecretToRef - this is no longer automatically added
// to v2 XR schemas, but the community has been adding it manually, so if
// it's present we will use it.
// 2. function input.writeConnectionSecretToRef - if name or namespace is provided
// then the whole ref will be used
// 3. generate the reference from scratch, based on the XR name and namespace
func getBaseConnectionSecretRef(xr *resource.Composite, input *v1beta1.WriteConnectionSecretToRef) xpv1.SecretReference {
// Check if XR author manually added writeConnectionSecretToRef to the XR's
// schema and just use that if it exists
xrRef := xr.Resource.GetWriteConnectionSecretToReference()
if xrRef != nil {
return *xrRef
}
// Use the input values if at least one of name or namespace has been provided
if input != nil && (input.Name != "" || input.Namespace != "") {
return xpv1.SecretReference{Name: input.Name, Namespace: input.Namespace}
}
// Nothing has been provided, so generate a default name using the name of the XR
return xpv1.SecretReference{
Name: xr.Resource.GetName() + "-connection",
Namespace: xr.Resource.GetNamespace(),
}
}
// applyConnectionSecretPatches applies all patches provided on the input to the
// connection secret reference.
func applyConnectionSecretPatches(xr *resource.Composite, ref *xpv1.SecretReference, patches []v1beta1.ConnectionSecretPatch) error {
// Convert the secret reference to an unstructured object so we can pass it to the patching logic
// We use a fake (but reasonable) apiVersion and kind because the unstructured converter requires them.
refObj := &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "v1",
"kind": "SecretReference",
"name": ref.Name,
"namespace": ref.Namespace,
},
}
for i, patch := range patches {
switch patch.GetType() { //nolint:exhaustive // we only care about the patch types we support, everything else is an error
case v1beta1.PatchTypeFromCompositeFieldPath:
if err := ApplyFromFieldPathPatch(&patch, xr.Resource, refObj); err != nil {
// we got an error, but if the patch policy is Optional then just skip this patch
if patch.GetPolicy().GetFromFieldPathPolicy() == v1beta1.FromFieldPathPolicyOptional {
continue
}
return errors.Wrapf(err, "cannot apply patch type %s at index %d", patch.GetType(), i)
}
case v1beta1.PatchTypeCombineFromComposite:
if err := ApplyCombineFromVariablesPatch(&patch, xr.Resource, refObj); err != nil {
return errors.Wrapf(err, "cannot apply patch type %s at index %d", patch.GetType(), i)
}
default:
return errors.Errorf("unsupported patch type %s at index %d", patch.GetType(), i)
}
}
// Extract the patched values and return them on the reference
if name, ok := refObj.Object["name"].(string); ok {
ref.Name = name
}
if namespace, ok := refObj.Object["namespace"].(string); ok {
ref.Namespace = namespace
}
return nil
}