Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add utility function to retrieve function credentials #151

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(req map[string]any, credName string) map[string][]byte {
credentials, err := convertFromMap(req)
if err != nil {
return nil
}

var data map[string][]byte
switch credentials.GetCredentials()[credName].GetSource().(type) {
case *fnv1.Credentials_CredentialData:
data = credentials.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)
}
})
}
}