@@ -2,33 +2,38 @@ package main
22
33import (
44 "github.com/crossplane-contrib/function-patch-and-transform/input/v1beta1"
5+ xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1"
56 "github.com/crossplane/crossplane-runtime/v2/pkg/errors"
67 "github.com/crossplane/crossplane-runtime/v2/pkg/fieldpath"
78 "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed"
8- "github.com/crossplane/crossplane-runtime/v2/pkg/resource"
9+ xpresource "github.com/crossplane/crossplane-runtime/v2/pkg/resource"
10+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
911 "k8s.io/apimachinery/pkg/runtime"
1012 "k8s.io/apimachinery/pkg/util/json"
13+
14+ "github.com/crossplane/function-sdk-go/resource"
15+ "github.com/crossplane/function-sdk-go/resource/composed"
1116)
1217
1318// ConnectionDetailsExtractor extracts the connection details of a resource.
1419type ConnectionDetailsExtractor interface {
1520 // ExtractConnection of the supplied resource.
16- ExtractConnection (cd resource .Composed , conn managed.ConnectionDetails , cfg ... v1beta1.ConnectionDetail ) (managed.ConnectionDetails , error )
21+ ExtractConnection (cd xpresource .Composed , conn managed.ConnectionDetails , cfg ... v1beta1.ConnectionDetail ) (managed.ConnectionDetails , error )
1722}
1823
1924// A ConnectionDetailsExtractorFn is a function that satisfies
2025// ConnectionDetailsExtractor.
21- type ConnectionDetailsExtractorFn func (cd resource .Composed , conn managed.ConnectionDetails , cfg ... v1beta1.ConnectionDetail ) (managed.ConnectionDetails , error )
26+ type ConnectionDetailsExtractorFn func (cd xpresource .Composed , conn managed.ConnectionDetails , cfg ... v1beta1.ConnectionDetail ) (managed.ConnectionDetails , error )
2227
2328// ExtractConnection of the supplied resource.
24- func (fn ConnectionDetailsExtractorFn ) ExtractConnection (cd resource .Composed , conn managed.ConnectionDetails , cfg ... v1beta1.ConnectionDetail ) (managed.ConnectionDetails , error ) {
29+ func (fn ConnectionDetailsExtractorFn ) ExtractConnection (cd xpresource .Composed , conn managed.ConnectionDetails , cfg ... v1beta1.ConnectionDetail ) (managed.ConnectionDetails , error ) {
2530 return fn (cd , conn , cfg ... )
2631}
2732
2833// ExtractConnectionDetails extracts XR connection details from the supplied
2934// composed resource. If no ExtractConfigs are supplied no connection details
3035// will be returned.
31- func ExtractConnectionDetails (cd resource .Composed , data managed.ConnectionDetails , cfgs ... v1beta1.ConnectionDetail ) (managed.ConnectionDetails , error ) {
36+ func ExtractConnectionDetails (cd xpresource .Composed , data managed.ConnectionDetails , cfgs ... v1beta1.ConnectionDetail ) (managed.ConnectionDetails , error ) {
3237 out := map [string ][]byte {}
3338 for _ , cfg := range cfgs {
3439 if err := ValidateConnectionDetail (cfg ); err != nil {
@@ -76,3 +81,134 @@ func fromFieldPath(from runtime.Object, path string) ([]byte, error) {
7681
7782 return json .Marshal (in )
7883}
84+
85+ // supportsConnectionDetails determines if the given XR supports native/classic
86+ // connection details.
87+ func supportsConnectionDetails (xr * resource.Composite ) bool {
88+ // v2 modern XRs don't support connection details. They should have a
89+ // spec.crossplane field, which may be our only indication it's a v2 XR
90+ _ , err := xr .Resource .GetValue ("spec.crossplane" )
91+ return err != nil
92+ }
93+
94+ // composeConnectionSecret creates a Secret composed resource containing the
95+ // provided connection details.
96+ func composeConnectionSecret (xr * resource.Composite , details resource.ConnectionDetails , ref * v1beta1.WriteConnectionSecretToRef ) (* resource.DesiredComposed , error ) {
97+ if len (details ) == 0 {
98+ return nil , nil
99+ }
100+
101+ secret := composed .New ()
102+ secret .SetAPIVersion ("v1" )
103+ secret .SetKind ("Secret" )
104+
105+ secretRef , err := getConnectionSecretRef (xr , ref )
106+ if err != nil {
107+ return nil , errors .Wrap (err , "cannot generate connection secret reference" )
108+ }
109+ secret .SetName (secretRef .Name )
110+ secret .SetNamespace (secretRef .Namespace )
111+
112+ if err := secret .SetValue ("data" , details ); err != nil {
113+ return nil , errors .Wrap (err , "cannot set connection secret data" )
114+ }
115+
116+ if err := secret .SetValue ("type" , xpresource .SecretTypeConnection ); err != nil {
117+ return nil , errors .Wrap (err , "cannot set connection secret type" )
118+ }
119+
120+ return & resource.DesiredComposed {
121+ Resource : secret ,
122+ Ready : resource .ReadyTrue ,
123+ }, nil
124+ }
125+
126+ // getConnectionSecretRef creates a connection secret reference from the given
127+ // XR and input. The patches for the reference will be applied before the
128+ // reference is returned.
129+ func getConnectionSecretRef (xr * resource.Composite , input * v1beta1.WriteConnectionSecretToRef ) (xpv1.SecretReference , error ) {
130+ // Get the base connection secret ref to start with
131+ ref := getBaseConnectionSecretRef (xr , input )
132+
133+ // Apply patches to the base connection secret ref if they've been provided
134+ if input != nil && len (input .Patches ) > 0 {
135+ if err := applyConnectionSecretPatches (xr , & ref , input .Patches ); err != nil {
136+ return xpv1.SecretReference {}, errors .Wrap (err , "cannot apply connection secret patches" )
137+ }
138+ }
139+
140+ return ref , nil
141+ }
142+
143+ // getBaseConnectionSecretRef determines the base connection secret reference
144+ // without any patches. This reference is generated with the following
145+ // precedence:
146+ // 1. xr.spec.writeConnectionSecretToRef - this is no longer automatically added
147+ // to v2 XR schemas, but the community has been adding it manually, so if
148+ // it's present we will use it.
149+ // 2. function input.writeConnectionSecretToRef - if name or namespace is provided
150+ // then the whole ref will be used
151+ // 3. generate the reference from scratch, based on the XR name and namespace
152+ func getBaseConnectionSecretRef (xr * resource.Composite , input * v1beta1.WriteConnectionSecretToRef ) xpv1.SecretReference {
153+ // Check if XR author manually added writeConnectionSecretToRef to the XR's
154+ // schema and just use that if it exists
155+ xrRef := xr .Resource .GetWriteConnectionSecretToReference ()
156+ if xrRef != nil {
157+ return * xrRef
158+ }
159+
160+ // Use the input values if at least one of name or namespace has been provided
161+ if input != nil && (input .Name != "" || input .Namespace != "" ) {
162+ return xpv1.SecretReference {Name : input .Name , Namespace : input .Namespace }
163+ }
164+
165+ // Nothing has been provided, so generate a default name using the name of the XR
166+ return xpv1.SecretReference {
167+ Name : xr .Resource .GetName () + "-connection" ,
168+ Namespace : xr .Resource .GetNamespace (),
169+ }
170+ }
171+
172+ // applyConnectionSecretPatches applies all patches provided on the input to the
173+ // connection secret reference.
174+ func applyConnectionSecretPatches (xr * resource.Composite , ref * xpv1.SecretReference , patches []v1beta1.ConnectionSecretPatch ) error {
175+ // Convert the secret reference to an unstructured object so we can pass it to the patching logic
176+ // We use a fake (but reasonable) apiVersion and kind because the unstructured converter requires them.
177+ refObj := & unstructured.Unstructured {
178+ Object : map [string ]any {
179+ "apiVersion" : "v1" ,
180+ "kind" : "SecretReference" ,
181+ "name" : ref .Name ,
182+ "namespace" : ref .Namespace ,
183+ },
184+ }
185+
186+ for i , patch := range patches {
187+ switch patch .GetType () { //nolint:exhaustive // we only care about the patch types we support, everything else is an error
188+ case v1beta1 .PatchTypeFromCompositeFieldPath :
189+ if err := ApplyFromFieldPathPatch (& patch , xr .Resource , refObj ); err != nil {
190+ // we got an error, but if the patch policy is Optional then just skip this patch
191+ if patch .GetPolicy ().GetFromFieldPathPolicy () == v1beta1 .FromFieldPathPolicyOptional {
192+ continue
193+ }
194+ return errors .Wrapf (err , "cannot apply patch type %s at index %d" , patch .GetType (), i )
195+ }
196+ case v1beta1 .PatchTypeCombineFromComposite :
197+ if err := ApplyCombineFromVariablesPatch (& patch , xr .Resource , refObj ); err != nil {
198+ return errors .Wrapf (err , "cannot apply patch type %s at index %d" , patch .GetType (), i )
199+ }
200+ default :
201+ return errors .Errorf ("unsupported patch type %s at index %d" , patch .GetType (), i )
202+ }
203+ }
204+
205+ // Extract the patched values and return them on the reference
206+ if name , ok := refObj .Object ["name" ].(string ); ok {
207+ ref .Name = name
208+ }
209+ if namespace , ok := refObj .Object ["namespace" ].(string ); ok {
210+ ref .Namespace = namespace
211+ }
212+
213+ return nil
214+ }
0 commit comments