Skip to content

feat: integrate function credentials #39

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

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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ test: ## Run Code Tests
go test -v -cover .

render: ## Render Examples, Requires make debug first
crossplane beta render \
crossplane \
example/echo/xr.yaml \
example/echo/composition.yaml \
example/echo/functions.yaml
Expand Down
95 changes: 29 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,50 +15,8 @@ is expected to be enhanced with the above pattern.
The `function-shell` accepts commands to run in a shell and it
returns the output to specified fields. It accepts the following parameters:

- `shellEnvVarsRef` - referencing environment variables in the
function-shell Kubernetes pod that were loaded through a
`deploymentRuntimeConfig`. The file MUST be in `JSON` format.
It can be a Kubernetes secret. `shellEnvVarsRef` requires a `name`
for the pod environment variable, and `keys` for the keys Inside
of the JSON formatted pod environment variable that have associated
values.

Example secret:

```json
{
"ENV_FOO": "foo value",
"ENV_BAR": "bar value"
}
```

Example `deploymentRuntimeConfig`:

```yaml
---
apiVersion: pkg.crossplane.io/v1beta1
kind: DeploymentRuntimeConfig
metadata:
name: function-shell
spec:
deploymentTemplate:
spec:
selector: {}
replicas: 1
template:
spec:
containers:
- name: package-runtime
args:
- --debug
env:
- name: DATADOG_SECRET
valueFrom:
secretKeyRef:
key: credentials
name: datadog-secret
```

- `shellCredentialRefs` - referencing function credentials
and the required keys.
- `shellEnvVars` - an array of environment variables with a
`key` and `value` each.
- `shellCommand` - a shell command line that can contain pipes
Expand All @@ -81,12 +39,9 @@ This repository includes the following examples

The composition calls the `function-shell` instructing it to obtain dashboard
ids from a [Datadog](https://www.datadoghq.com/) account.
For this, the composition specifies the name of a Kubernetes
pod environment variable called `DATADOG_SECRET`. This environment
variable was populated with the `JSON` of a Kubernetes datadog-secret
through a deploymentRuntimeConfig. The `JSON` includes the
`DATADOG_API_KEY` and `DATADOG_APP_KEY`
keys and their values. The Datadog API endpoint is passed
For this, the composition specifies the name of a function credential called
`DATADOG_SECRET` referencing a Kubernetes secret containing `DATADOG_API_KEY`
and `DATADOG_APP_KEY` keys and their values. The Datadog API endpoint is passed
in a clear text `DATADOG_API_URL` environment variable. The shell command
uses a `curl` to the endpoint with a header that contains the access
credentials. The command output is piped into
Expand All @@ -101,15 +56,18 @@ The composition is for illustration purposes only. When using the
you may want to patch function input
from claim and other composition field values.

The `deploymentRuntimeConfig` reads a datadog secret
that looks like below.
Replace `YOUR_API_KEY` and `YOUR_APP_KEY` with your respective keys.

```json
{
"DATADOG_API_KEY": "YOUR_API_KEY",
"DATADOG_APP_KEY": "YOIR_APP_KEY"
}
```yaml
apiVersion: v1
kind: Secret
metadata:
name: datadog-secret
namespace: default
type: Opaque
data:
DATADOG_API_KEY: Zm9v # your api key
DATADOG_APP_KEY: YmFy # your app key
```

```yaml
Expand All @@ -125,21 +83,26 @@ spec:
mode: Pipeline
pipeline:
- step: shell
credentials:
- name: DATADOG_SECRET
secretRef:
namespace: default
name: datadog-secret
source: Secret
functionRef:
# When installed through a package manager, use
# name: crossplane-contrib-function-shell
name: function-shell
input:
apiVersion: shell.fn.crossplane.io/v1beta1
kind: Parameters
# Load shellEnvVarsRef from a Kubernetes secret
# through a deploymentRuntimeConfig into the
# function-shell pod.
shellEnvVarsRef:
name: DATADOG_SECRET
keys:
- DATADOG_API_KEY
- DATADOG_APP_KEY
# Load shellCredentialsRef from a Kubernetes secret
# into the function-shell pod.
shellCredentialRefs:
- name: DATADOG_SECRET
keys:
- DATADOG_API_KEY
- DATADOG_APP_KEY
shellEnvVars:
- key: DATADOG_API_URL
value: "https://api.datadoghq.com/api/v1/dashboard"
Expand Down Expand Up @@ -296,7 +259,7 @@ go run . --insecure --debug
In Terminal 2

```shell
crossplane beta render \
crossplane \
example/out-of-cluster/xr.yaml \
example/out-of-cluster/composition.yaml \
example/out-of-cluster/functions.yaml
Expand Down
20 changes: 2 additions & 18 deletions env.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,18 @@
package main

import (
"encoding/json"
"fmt"
"os"
"regexp"

"github.com/crossplane-contrib/function-shell/input/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
fnv1 "github.com/crossplane/function-sdk-go/proto/v1"
"github.com/crossplane/function-sdk-go/request"
"github.com/crossplane/function-sdk-go/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func addShellEnvVarsFromRef(envVarsRef v1alpha1.ShellEnvVarsRef, shellEnvVars map[string]string) (map[string]string, error) {
var envVarsData map[string]string

envVars := os.Getenv(envVarsRef.Name)
if err := json.Unmarshal([]byte(envVars), &envVarsData); err != nil {
return shellEnvVars, err
}
for _, key := range envVarsRef.Keys {
shellEnvVars[key] = envVarsData[key]
}
return shellEnvVars, nil
}

func fromValueRef(req *fnv1beta1.RunFunctionRequest, path string) (string, error) {
func fromValueRef(req *fnv1.RunFunctionRequest, path string) (string, error) {
// Check for context key presence and capture context key and path
contextRegex := regexp.MustCompile(`^context\[(.+?)].(.+)$`)
if match := contextRegex.FindStringSubmatch(path); match != nil {
Expand Down
12 changes: 6 additions & 6 deletions env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"testing"

fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
fnv1 "github.com/crossplane/function-sdk-go/proto/v1"
"github.com/crossplane/function-sdk-go/resource"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
Expand All @@ -13,7 +13,7 @@ import (
func TestFromValueRef(t *testing.T) {

type args struct {
req *fnv1beta1.RunFunctionRequest
req *fnv1.RunFunctionRequest
path string
}

Expand All @@ -30,9 +30,9 @@ func TestFromValueRef(t *testing.T) {
"FromCompositeValid": {
reason: "If composite path is valid, it should be returned.",
args: args{
req: &fnv1beta1.RunFunctionRequest{
Observed: &fnv1beta1.State{
Composite: &fnv1beta1.Resource{
req: &fnv1.RunFunctionRequest{
Observed: &fnv1.State{
Composite: &fnv1.Resource{
Resource: resource.MustStructJSON(`{
"apiVersion": "",
"kind": "",
Expand All @@ -53,7 +53,7 @@ func TestFromValueRef(t *testing.T) {
"FromContextValid": {
reason: "If composite path is valid, it should be returned.",
args: args{
req: &fnv1beta1.RunFunctionRequest{
req: &fnv1.RunFunctionRequest{
Context: resource.MustStructJSON(`{
"apiextensions.crossplane.io/foo": {
"bar": "baz"
Expand Down
44 changes: 0 additions & 44 deletions example/README.md

This file was deleted.

2 changes: 1 addition & 1 deletion example/aws/functions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ kind: Function
metadata:
name: function-shell
annotations:
# This tells crossplane beta render to connect to the function locally.
# This tells crossplane render to connect to the function locally.
render.crossplane.io/runtime: Development
spec:
# This is ignored when using the Development runtime.
Expand Down
27 changes: 27 additions & 0 deletions example/datadog-dashboard-ids/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Example manifests

You can run your function locally and test it using `crossplane render`
with these example manifests.

```shell
# Run the function locally
$ go run . --insecure --debug
```

```shell
# Then, in another terminal, call it with these example manifests
$ crossplane render xr.yaml composition.yaml functions.yaml \
--function-credentials=credentials.yaml \
--include-function-results
---
apiVersion: example.crossplane.io/v1
kind: XR
metadata:
name: example-xr
---
apiVersion: render.crossplane.io/v1beta1
kind: Result
message: I was run with input "Hello world"!
severity: SEVERITY_NORMAL
step: run-the-template
```
26 changes: 14 additions & 12 deletions example/datadog-dashboard-ids/composition.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,30 @@ spec:
mode: Pipeline
pipeline:
- step: shell
credentials:
- name: DATADOG_SECRET
secretRef:
namespace: default
name: datadog-secret
source: Secret
functionRef:
# When installed through a package manager, use
# name: crossplane-contrib-function-shell
name: function-shell
input:
apiVersion: shell.fn.crossplane.io/v1beta1
kind: Parameters
# Load shellEnvVarsRef from a Kubernetes secret
# through a deploymentRuntimeConfig into the
# function-shell pod.
shellEnvVarsRef:
name: DATADOG_SECRET
keys:
- DATADOG_API_KEY
- DATADOG_APP_KEY
# Load shellCredentialsRef from a Kubernetes secret
# into the function-shell pod.
shellCredentialRefs:
- name: DATADOG_SECRET
keys:
- credentials
shellEnvVars:
- key: DATADOG_API_URL
value: "https://api.datadoghq.com/api/v1/dashboard"
shellCommand: |
curl -X GET "${DATADOG_API_URL}" \
-H "Accept: application/json" \
-H "DD-API-KEY: ${DATADOG_API_KEY}" \
-H "DD-APPLICATION-KEY: ${DATADOG_APP_KEY}"|jq '.dashboards[] .id'
echo "url=${DATADOG_API_URL}"
echo "credentials=$(echo ${credentials})"
stdoutField: status.atFunction.shell.stdout
stderrField: status.atFunction.shell.stderr
8 changes: 8 additions & 0 deletions example/datadog-dashboard-ids/credentials.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: datadog-secret
namespace: default
type: Opaque
data:
credentials: ewogICJEQVRBRE9HX0FQSV9LRVkiOiAiZm9vIiwKICAiREFUQURPR19BUFBfS0VZIjogImJhciIKfQo=
21 changes: 0 additions & 21 deletions example/datadog-dashboard-ids/deployment-runtime-config.yaml

This file was deleted.

8 changes: 2 additions & 6 deletions example/datadog-dashboard-ids/functions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@ kind: Function
metadata:
name: function-shell
annotations:
# This tells crossplane beta render to connect to the functi on locally.
# This tells crossplane render to connect to the function locally.
render.crossplane.io/runtime: Development
spec:
# This is ignored when using the Development runtime.
package: package: xpkg.upbound.io/crossplane-contrib/function-shell:v0.3.0
package: xpkg.upbound.io/crossplane-contrib/function-shell:v0.3.0
packagePullPolicy: Always
runtimeConfigRef:
apiVersion: pkg.crossplane.io/v1beta1
kind: DeploymentRuntimeConfig
name: function-shell
Loading