Skip to content

Commit

Permalink
feat: Add utility function to retrieve function credentials
Browse files Browse the repository at this point in the history
Signed-off-by: Mashama McFarlane <[email protected]>
  • Loading branch information
Mashama McFarlane committed Jan 28, 2025
1 parent 2924b4c commit b6c9dc7
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 0 deletions.
29 changes: 29 additions & 0 deletions example/functions/getCredentialData/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# getCredentialData
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.

## Testing This Function Locally

You can run your function locally and test it with [`crossplane render`](https://docs.crossplane.io/v1.18/cli/command-reference/#render/)

```shell {copy-lines="1-3"}
crossplane render xr.yaml composition.yaml functions.yaml \
--function-credentials=credentials.yaml \
--include-context
---
apiVersion: example.crossplane.io/v1beta1
kind: XR
metadata:
name: example
status:
conditions:
- lastTransitionTime: "2024-01-01T00:00:00Z"
reason: Available
status: "True"
type: Ready
---
apiVersion: render.crossplane.io/v1beta1
fields:
password: bar
username: foo
kind: Context
```
31 changes: 31 additions & 0 deletions example/functions/getCredentialData/composition.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: example-function-get-credential-data
spec:
compositeTypeRef:
apiVersion: example.crossplane.io/v1beta1
kind: XR
mode: Pipeline
pipeline:
- step: render-templates
functionRef:
name: function-go-templating
credentials:
- name: foo-creds
secretRef:
name: foo-creds
namespace: default
source: Secret
input:
apiVersion: gotemplating.fn.crossplane.io/v1beta1
kind: GoTemplate
source: Inline
inline:
template: |
---
apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1
kind: Context
data:
username: {{ ( getCredentialData . "foo-creds" ).username | toString }}
password: {{ ( getCredentialData . "foo-creds" ).password | toString }}
9 changes: 9 additions & 0 deletions example/functions/getCredentialData/credentials.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: foo-creds
namespace: default
type: Opaque
data:
username: Zm9v # foo
password: YmFy # bar
8 changes: 8 additions & 0 deletions example/functions/getCredentialData/functions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-go-templating
annotations:
render.crossplane.io/runtime: Development
spec:
package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.8.0
5 changes: 5 additions & 0 deletions example/functions/getCredentialData/xr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: example.crossplane.io/v1beta1
kind: XR
metadata:
name: example
spec: {}
35 changes: 35 additions & 0 deletions function_maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import (
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
"github.com/crossplane/function-sdk-go/errors"
fnv1 "github.com/crossplane/function-sdk-go/proto/v1"
"google.golang.org/protobuf/encoding/protojson"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/util/json"
)

const recursionMaxNums = 1000
Expand All @@ -26,6 +29,7 @@ var funcMaps = []template.FuncMap{
"setResourceNameAnnotation": setResourceNameAnnotation,
"getComposedResource": getComposedResource,
"getCompositeResource": getCompositeResource,
"getCredentialData": getCredentialData,
},
}

Expand Down Expand Up @@ -130,3 +134,34 @@ func getCompositeResource(req map[string]any) map[string]any {

return cr
}

func getCredentialData(mReq map[string]any, credName string) map[string][]byte {
req, err := convertFromMap(mReq)
if err != nil {
return nil
}

var data map[string][]byte
switch req.GetCredentials()[credName].GetSource().(type) {
case *fnv1.Credentials_CredentialData:
data = req.GetCredentials()[credName].GetCredentialData().GetData()
default:
return nil
}

return data
}

func convertFromMap(mReq map[string]any) (*fnv1.RunFunctionRequest, error) {
jReq, err := json.Marshal(&mReq)
if err != nil {
return nil, errors.Wrap(err, "cannot marshal map[string]any to json")
}

req := &fnv1.RunFunctionRequest{}
if err := protojson.Unmarshal(jReq, req); err != nil {
return nil, errors.Wrap(err, "cannot unmarshal request from json to proto")
}

return req, nil
}
60 changes: 60 additions & 0 deletions function_maps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"google.golang.org/protobuf/testing/protocmp"

v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
fnv1 "github.com/crossplane/function-sdk-go/proto/v1"
)

func Test_fromYaml(t *testing.T) {
Expand Down Expand Up @@ -475,3 +476,62 @@ func Test_getCompositeResource(t *testing.T) {
})
}
}

func Test_getCredentialData(t *testing.T) {
type args struct {
req *fnv1.RunFunctionRequest
}

type want struct {
data map[string][]byte
}

cases := map[string]struct {
reason string
args args
want want
}{
"RetrieveFunctionCredential": {
reason: "Should successfully retrieve the function credential",
args: args{
req: &fnv1.RunFunctionRequest{
Credentials: map[string]*fnv1.Credentials{
"foo-creds": {
Source: &fnv1.Credentials_CredentialData{
CredentialData: &fnv1.CredentialData{
Data: map[string][]byte{
"password": []byte("secret"),
},
},
},
},
},
},
},
want: want{
data: map[string][]byte{
"password": []byte("secret"),
},
},
},
"FunctionCredentialNotFound": {
reason: "Should return nil if the function credential is not found",
args: args{
req: &fnv1.RunFunctionRequest{
Credentials: map[string]*fnv1.Credentials{},
},
},
want: want{data: nil},
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
req, _ := convertToMap(tc.args.req)
got := getCredentialData(req, "foo-creds")
if diff := cmp.Diff(tc.want.data, got); diff != "" {
t.Errorf("%s\ngetCredentialData(...): -want data, +got data:\n%s", tc.reason, diff)
}
})
}
}

0 comments on commit b6c9dc7

Please sign in to comment.