Skip to content

Commit edbf2e7

Browse files
authored
Merge pull request #151 from mcfarlanem/secretref-functionality
feat: Add utility function to retrieve function credentials
2 parents 8eba707 + e9bbdb4 commit edbf2e7

File tree

7 files changed

+174
-0
lines changed

7 files changed

+174
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# getCredentialData
2+
The getCredentialData function is a utility function used to facilitate the retrieval of a function credential. Upon successful retrieval, the function returns the data of the credential. If the credential cannot be located or is unreachable, it returns nil.
3+
4+
## Testing This Function Locally
5+
6+
You can run your function locally and test it with [`crossplane render`](https://docs.crossplane.io/latest/cli/command-reference/#render/)
7+
8+
```shell {copy-lines="1-3"}
9+
crossplane render xr.yaml composition.yaml functions.yaml \
10+
--function-credentials=credentials.yaml \
11+
--include-context
12+
---
13+
apiVersion: example.crossplane.io/v1beta1
14+
kind: XR
15+
metadata:
16+
name: example
17+
status:
18+
conditions:
19+
- lastTransitionTime: "2024-01-01T00:00:00Z"
20+
reason: Available
21+
status: "True"
22+
type: Ready
23+
---
24+
apiVersion: render.crossplane.io/v1beta1
25+
fields:
26+
password: bar
27+
username: foo
28+
kind: Context
29+
```
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
apiVersion: apiextensions.crossplane.io/v1
2+
kind: Composition
3+
metadata:
4+
name: example-function-get-credential-data
5+
spec:
6+
compositeTypeRef:
7+
apiVersion: example.crossplane.io/v1beta1
8+
kind: XR
9+
mode: Pipeline
10+
pipeline:
11+
- step: render-templates
12+
functionRef:
13+
name: function-go-templating
14+
credentials:
15+
- name: foo-creds
16+
secretRef:
17+
name: foo-creds
18+
namespace: default
19+
source: Secret
20+
input:
21+
apiVersion: gotemplating.fn.crossplane.io/v1beta1
22+
kind: GoTemplate
23+
source: Inline
24+
inline:
25+
template: |
26+
---
27+
apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1
28+
kind: Context
29+
data:
30+
username: {{ ( getCredentialData . "foo-creds" ).username | toString }}
31+
password: {{ ( getCredentialData . "foo-creds" ).password | toString }}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
name: foo-creds
5+
namespace: default
6+
type: Opaque
7+
data:
8+
username: Zm9v # foo
9+
password: YmFy # bar
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: pkg.crossplane.io/v1beta1
2+
kind: Function
3+
metadata:
4+
name: function-go-templating
5+
annotations:
6+
render.crossplane.io/runtime: Development
7+
spec:
8+
package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.10.0
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
apiVersion: example.crossplane.io/v1beta1
2+
kind: XR
3+
metadata:
4+
name: example
5+
spec: {}

function_maps.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import (
1212
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
1313
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
1414
"github.com/crossplane/function-sdk-go/errors"
15+
fnv1 "github.com/crossplane/function-sdk-go/proto/v1"
16+
"google.golang.org/protobuf/encoding/protojson"
1517
"gopkg.in/yaml.v3"
18+
"k8s.io/apimachinery/pkg/util/json"
1619
)
1720

1821
const recursionMaxNums = 1000
@@ -27,6 +30,7 @@ var funcMaps = []template.FuncMap{
2730
"getComposedResource": getComposedResource,
2831
"getCompositeResource": getCompositeResource,
2932
"getExtraResources": getExtraResources,
33+
"getCredentialData": getCredentialData,
3034
},
3135
}
3236

@@ -143,3 +147,31 @@ func getExtraResources(req map[string]any, name string) []any {
143147

144148
return ers
145149
}
150+
151+
func getCredentialData(mReq map[string]any, credName string) map[string][]byte {
152+
req, err := convertFromMap(mReq)
153+
if err != nil {
154+
return nil
155+
}
156+
157+
switch req.GetCredentials()[credName].GetSource().(type) {
158+
case *fnv1.Credentials_CredentialData:
159+
return req.GetCredentials()[credName].GetCredentialData().GetData()
160+
default:
161+
return nil
162+
}
163+
}
164+
165+
func convertFromMap(mReq map[string]any) (*fnv1.RunFunctionRequest, error) {
166+
jReq, err := json.Marshal(&mReq)
167+
if err != nil {
168+
return nil, errors.Wrap(err, "cannot marshal map[string]any to json")
169+
}
170+
171+
req := &fnv1.RunFunctionRequest{}
172+
if err := protojson.Unmarshal(jReq, req); err != nil {
173+
return nil, errors.Wrap(err, "cannot unmarshal request from json to proto")
174+
}
175+
176+
return req, nil
177+
}

function_maps_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"google.golang.org/protobuf/testing/protocmp"
1111

1212
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
13+
fnv1 "github.com/crossplane/function-sdk-go/proto/v1"
1314
)
1415

1516
func Test_fromYaml(t *testing.T) {
@@ -581,3 +582,62 @@ func Test_getExtraResources(t *testing.T) {
581582
})
582583
}
583584
}
585+
586+
func Test_getCredentialData(t *testing.T) {
587+
type args struct {
588+
req *fnv1.RunFunctionRequest
589+
}
590+
591+
type want struct {
592+
data map[string][]byte
593+
}
594+
595+
cases := map[string]struct {
596+
reason string
597+
args args
598+
want want
599+
}{
600+
"RetrieveFunctionCredential": {
601+
reason: "Should successfully retrieve the function credential",
602+
args: args{
603+
req: &fnv1.RunFunctionRequest{
604+
Credentials: map[string]*fnv1.Credentials{
605+
"foo-creds": {
606+
Source: &fnv1.Credentials_CredentialData{
607+
CredentialData: &fnv1.CredentialData{
608+
Data: map[string][]byte{
609+
"password": []byte("secret"),
610+
},
611+
},
612+
},
613+
},
614+
},
615+
},
616+
},
617+
want: want{
618+
data: map[string][]byte{
619+
"password": []byte("secret"),
620+
},
621+
},
622+
},
623+
"FunctionCredentialNotFound": {
624+
reason: "Should return nil if the function credential is not found",
625+
args: args{
626+
req: &fnv1.RunFunctionRequest{
627+
Credentials: map[string]*fnv1.Credentials{},
628+
},
629+
},
630+
want: want{data: nil},
631+
},
632+
}
633+
634+
for name, tc := range cases {
635+
t.Run(name, func(t *testing.T) {
636+
req, _ := convertToMap(tc.args.req)
637+
got := getCredentialData(req, "foo-creds")
638+
if diff := cmp.Diff(tc.want.data, got); diff != "" {
639+
t.Errorf("%s\ngetCredentialData(...): -want data, +got data:\n%s", tc.reason, diff)
640+
}
641+
})
642+
}
643+
}

0 commit comments

Comments
 (0)