diff --git a/Dockerfile b/Dockerfile index 1c5c8a3..fb4162b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ ARG GO_VERSION=1 # architecture that we're building the function for. FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION} AS build -RUN apt-get update && apt-get install -y jq unzip zsh +RUN apt-get update && apt-get install -y coreutils jq unzip zsh RUN mkdir /scripts && chown 2000:2000 /scripts # TODO: Install awscli, gcloud diff --git a/Makefile b/Makefile index 4b7ebf2..a0db3e6 100644 --- a/Makefile +++ b/Makefile @@ -45,9 +45,9 @@ test: ## Run Code Tests render: ## Render Examples, Requires make debug first crossplane beta render \ - example/xr.yaml \ - example/composition.yaml \ - example/functions.yaml + example/echo/xr.yaml \ + example/echo/composition.yaml \ + example/echo/functions.yaml debug: ## Run Shell Function For Rendering Examples go run . --insecure --debug diff --git a/README.md b/README.md index b01200e..37ada72 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,20 @@ Once [this pull request](https://github.com/crossplane/crossplane/pull/5543) to introduce how to support passing credentials to composition functions has been merged, the current functionality for how to pass secrets in `function-shell` -is expected to follow the above pattern. +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: -- `shellEnvVarsSecretRef` - referencing environment variables in a -Kubernetes secret. `shellEnvVarsSecretRef` requires a `name`, a -`namespace` and a `key` for the secret. Inside of it, the shell -expects a JSON structure with key value environment variables. Example: +- `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 { @@ -27,6 +32,33 @@ expects a JSON structure with key value environment variables. Example: } ``` +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 +``` + - `shellEnvVars` - an array of environment variables with a `key` and `value` each. - `shellCommand` - a shell command line that can contain pipes @@ -38,13 +70,23 @@ standard output should be written. - `stderrField` - the path to the field where the shell standard error output should be written. -## Practical Example: Obtain Dashboard Ids from Datadog +## Examples + +This repository includes the following examples +- echo +- datadog-dashboard-ids +- ip-addr-validation + +## Example: Obtain Dashboard Ids from Datadog 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 location -of a Kubernetes secret where the `DATADOG_API_KEY` and `DATADOG_APP_KEY` -environment variable values are stored. The Datadog API endpoint is passed +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 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 @@ -55,24 +97,12 @@ specified output status field, and any output that went to stderr into the specified stderr status field. The composition is for illustration purposes only. When using the -`function-shell` in your own compositions, you may want to patch function input +`function-shell` in your own compositions, +you may want to patch function input from claim and other composition field values. -Note: `function-shell` requires permissions in form of a `rolebinding` to -read secrets and perform other actions that may be prohibited by default. Below -is a `clusterrolebinding` that will work, but you should exercise appropriate -caution when setting function permissions. - -```shell -#!/bin/bash -NS="crossplane-system" # Replace with the namespace you use, e.g. upbound-system -SA=$(kubectl -n ${NS} get sa -o name | grep function-shell | sed -e 's|serviceaccount\/|${NS}:|g') -kubectl create clusterrolebinding function-shell-admin-binding \ - --clusterrole cluster-admin \ - --serviceaccount="${SA}" -``` - -The composition reads a datadog secret that looks like below. +The `deploymentRuntimeConfig` reads a datadog secret +that looks like below. Replace `YOUR_API_KEY` and `YOUR_APP_KEY` with your respective keys. ```json @@ -96,28 +126,33 @@ spec: pipeline: - step: shell 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 - shellEnvVarsSecretRef: - name: datadog-secret - namespace: upbound-system - key: credentials + # 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 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'; + 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' stdoutField: status.atFunction.shell.stdout stderrField: status.atFunction.shell.stderr ``` -The composition is called through the following `claim`. +The composition is selected through the following `XR`. ```yaml --- @@ -128,8 +163,8 @@ metadata: spec: {} ``` -The API definition is as follows. Note that the API contains status fields that -are populated by `function-shell`. +The API definition is as follows. Note that the API +contains status fields that are populated by `function-shell`. ```yaml apiVersion: apiextensions.crossplane.io/v1 @@ -168,10 +203,10 @@ The `crossplane beta trace` output after applying the in-cluster shell-claim.yaml is as follows: ```shell -crossplane beta trace shell.upbound.io/shell-1 -NAME SYNCED READY STATUS -Shell/shell-1 (default) True True Available -└─ XShell/shell-1-ttfbh True True Available +crossplane beta trace shell.upbound.io/datadog-dashboard-ids +NAME SYNCED READY STATUS +Shell/datadog-dashboard-ids (default) True True Available +└─ XShell/datadog-dashboard-ids-cbb6x True True Available ``` The `XShell/shell-1-ttfbh` yaml output looks as per below. Notice the dashboard @@ -179,31 +214,32 @@ ids in the `status.atFunction.shell.stdout` field, and the `curl` stderr output in the `status.atFunction.shell.stderr` field. ```yaml +kubectl get XShell/datadog-dashboard-ids-cbb6x -o yaml apiVersion: upbound.io/v1alpha1 kind: XShell metadata: - creationTimestamp: "2024-04-11T02:31:54Z" + creationTimestamp: "2024-04-24T04:15:53Z" finalizers: - composite.apiextensions.crossplane.io - generateName: shell-1- - generation: 17 + generateName: datadog-dashboard-ids- + generation: 6 labels: - crossplane.io/claim-name: shell-1 + crossplane.io/claim-name: datadog-dashboard-ids crossplane.io/claim-namespace: default - crossplane.io/composite: shell-1-wjjs4 - name: shell-1-wjjs4 - resourceVersion: "2577566" - uid: 77d24f9f-96db-4758-9155-9364ad227d0a + crossplane.io/composite: datadog-dashboard-ids-cbb6x + name: datadog-dashboard-ids-cbb6x + resourceVersion: "167413" + uid: 601d3f66-80df-4f1a-8917-533ea05255cc spec: claimRef: apiVersion: upbound.io/v1alpha1 kind: Shell - name: shell-1 + name: datadog-dashboard-ids namespace: default compositionRef: name: shell.upbound.io compositionRevisionRef: - name: shell.upbound.io-2403237 + name: shell.upbound.io-e981893 compositionUpdatePolicy: Automatic resourceRefs: [] status: @@ -212,8 +248,8 @@ status: stderr: "% Total % Received % Xferd Average Speed Time Time Time \ Current\n Dload Upload Total Spent \ Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- - --:--:-- --:--:-- 0\r100 4255 100 4255 0 0 8862 0 --:--:-- - --:--:-- --:--:-- 8864" + --:--:-- --:--:-- 0\r100 4255 100 4255 0 0 10361 0 --:--:-- + --:--:-- --:--:-- 10378" stdout: |- "vn4-agn-ftd" "9pt-bhb-uwj" @@ -222,13 +258,12 @@ status: "ssx-sci-uvi" "3fd-h4e-7w6" "qth-94z-ip5" - Python conditions: - - lastTransitionTime: "2024-04-11T02:53:01Z" + - lastTransitionTime: "2024-04-24T04:20:09Z" reason: ReconcileSuccess status: "True" type: Synced - - lastTransitionTime: "2024-04-11T02:31:55Z" + - lastTransitionTime: "2024-04-24T04:15:54Z" reason: Available status: "True" type: Ready diff --git a/clientset.go b/clientset.go deleted file mode 100644 index 2f37f64..0000000 --- a/clientset.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - "flag" - "path/filepath" - - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" -) - -var clientsetObtained bool -var clientsetGlobal *kubernetes.Clientset - -func inClusterClient() (*kubernetes.Clientset, error) { - if clientsetObtained { - return clientsetGlobal, nil - } - - var clientset *kubernetes.Clientset - - // creates the in-cluster config - config, err := rest.InClusterConfig() - if err != nil { - return clientset, err - } - - // creates the clientset - clientset, err = kubernetes.NewForConfig(config) - if err != nil { - return clientset, err - } - - clientsetObtained = true - clientsetGlobal = clientset - return clientset, nil -} - -func outOfClusterClient() (*kubernetes.Clientset, error) { - if clientsetObtained { - return clientsetGlobal, nil - } - - var clientset *kubernetes.Clientset - var kubeconfig *string - if home := homedir.HomeDir(); home != "" { - kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file") - } else { - kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file") - } - - // use the current context in kubeconfig - config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) - if err != nil { - return clientset, err - } - - // create the clientset - clientset, err = kubernetes.NewForConfig(config) - if err != nil { - return clientset, err - } - - clientsetObtained = true - clientsetGlobal = clientset - return clientset, nil -} diff --git a/env.go b/env.go new file mode 100644 index 0000000..0590d8b --- /dev/null +++ b/env.go @@ -0,0 +1,21 @@ +package main + +import ( + "encoding/json" + "os" + + "github.com/crossplane-contrib/function-shell/input/v1alpha1" +) + +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 +} diff --git a/example/datadog-dashboard-ids/composition.yaml b/example/datadog-dashboard-ids/composition.yaml new file mode 100644 index 0000000..96af201 --- /dev/null +++ b/example/datadog-dashboard-ids/composition.yaml @@ -0,0 +1,37 @@ +--- +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: shell.upbound.io +spec: + compositeTypeRef: + apiVersion: upbound.io/v1alpha1 + kind: XShell + mode: Pipeline + pipeline: + - step: shell + 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 + 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' + stdoutField: status.atFunction.shell.stdout + stderrField: status.atFunction.shell.stderr diff --git a/example/datadog-dashboard-ids/definition.yaml b/example/datadog-dashboard-ids/definition.yaml new file mode 100644 index 0000000..62d97b1 --- /dev/null +++ b/example/datadog-dashboard-ids/definition.yaml @@ -0,0 +1,30 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: CompositeResourceDefinition +metadata: + name: xshells.upbound.io +spec: + group: upbound.io + names: + kind: XShell + plural: xshells + claimNames: + kind: Shell + plural: shells + defaultCompositionRef: + name: shell.upbound.io + versions: + - name: v1alpha1 + served: true + referenceable: true + schema: + openAPIV3Schema: + properties: + spec: + properties: + cmd: + type: string + status: + properties: + atFunction: + type: object + x-kubernetes-preserve-unknown-fields: true diff --git a/example/datadog-dashboard-ids/deployment-runtime-config.yaml b/example/datadog-dashboard-ids/deployment-runtime-config.yaml new file mode 100644 index 0000000..ac1effc --- /dev/null +++ b/example/datadog-dashboard-ids/deployment-runtime-config.yaml @@ -0,0 +1,21 @@ +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 diff --git a/example/datadog-dashboard-ids/functions.yaml b/example/datadog-dashboard-ids/functions.yaml new file mode 100644 index 0000000..13fdfe2 --- /dev/null +++ b/example/datadog-dashboard-ids/functions.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: function-shell +spec: + package: xpkg.upbound.io/crossplane-contrib/function-shell:v0.1.0 + packagePullPolicy: Always + runtimeConfigRef: + apiVersion: pkg.crossplane.io/v1beta1 + kind: DeploymentRuntimeConfig + name: function-shell diff --git a/example/datadog-dashboard-ids/xr.yaml b/example/datadog-dashboard-ids/xr.yaml new file mode 100644 index 0000000..bfef2b7 --- /dev/null +++ b/example/datadog-dashboard-ids/xr.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: upbound.io/v1alpha1 +kind: Shell +metadata: + name: datadog-dashboard-ids +spec: {} diff --git a/example/composition.yaml b/example/echo/composition.yaml similarity index 52% rename from example/composition.yaml rename to example/echo/composition.yaml index 79b7a03..534d8ab 100644 --- a/example/composition.yaml +++ b/example/echo/composition.yaml @@ -14,18 +14,10 @@ spec: input: apiVersion: shell.fn.crossplane.io/v1alpha1 kind: Parameters - shellEnvVarsSecretRef: - name: datadog-secret - namespace: upbound-system - key: credentials shellEnvVars: - - key: DATADOG_API_URL - value: "https://api.datadoghq.com/api/v1/dashboard" + - key: ECHO + value: "SGVsbG8gZnJvbSBzaGVsbAo=" 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 ${ECHO}|base64 -d|sed s/^h/H/ stdoutField: status.atFunction.shell.stdout stderrField: status.atFunction.shell.stderr diff --git a/example/functions.yaml b/example/echo/functions.yaml similarity index 100% rename from example/functions.yaml rename to example/echo/functions.yaml diff --git a/example/xr.yaml b/example/echo/xr.yaml similarity index 57% rename from example/xr.yaml rename to example/echo/xr.yaml index 25472b9..6ecefb1 100644 --- a/example/xr.yaml +++ b/example/echo/xr.yaml @@ -1,6 +1,6 @@ -# Replace this with your XR! +--- apiVersion: example.crossplane.io/v1 kind: XR metadata: - name: example-xr + name: example-echo spec: {} diff --git a/example/function-shell-grant-read-secrets.sh b/example/function-shell-grant-read-secrets.sh deleted file mode 100755 index 7c5a507..0000000 --- a/example/function-shell-grant-read-secrets.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -SA=$(kubectl -n upbound-system get sa -o name | grep function-shell | sed -e 's|serviceaccount\/|upbound-system:|g') -kubectl create clusterrolebinding function-shell-admin-binding --clusterrole cluster-admin --serviceaccount="${SA}" diff --git a/example/ip-addr-validation/composition.yaml b/example/ip-addr-validation/composition.yaml new file mode 100644 index 0000000..f12e2dd --- /dev/null +++ b/example/ip-addr-validation/composition.yaml @@ -0,0 +1,27 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: shell-example +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: XR + mode: Pipeline + pipeline: + - step: shell + functionRef: + name: function-shell + input: + apiVersion: shell.fn.crossplane.io/v1alpha1 + kind: Parameters + shellEnvVars: + - key: IP_ADDRESS + value: "10.10.52.34" + shellCommand: | + if [[ $IP_ADDRESS =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "valid" + else + echo "invalid" + fi + stdoutField: status.atFunction.shell.stdout + stderrField: status.atFunction.shell.stderr diff --git a/example/ip-addr-validation/functions.yaml b/example/ip-addr-validation/functions.yaml new file mode 100644 index 0000000..88cbbd7 --- /dev/null +++ b/example/ip-addr-validation/functions.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: function-shell + annotations: + # This tells crossplane beta render to connect to the function locally. + render.crossplane.io/runtime: Development +spec: + # This is ignored when using the Development runtime. + package: function-shell diff --git a/example/ip-addr-validation/xr.yaml b/example/ip-addr-validation/xr.yaml new file mode 100644 index 0000000..869b4f7 --- /dev/null +++ b/example/ip-addr-validation/xr.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: example.crossplane.io/v1 +kind: XR +metadata: + name: example-ip-addr-validation +spec: {} diff --git a/fn.go b/fn.go index d36204e..9369d18 100644 --- a/fn.go +++ b/fn.go @@ -90,10 +90,10 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ shellEnvVars[envVar.Key] = envVar.Value } - if in.ShellEnvVarsSecretRef != (v1alpha1.ShellEnvVarsSecretRef{}) { - shellEnvVars, err = addShellEnvVarsFromSecret(in.ShellEnvVarsSecretRef, shellEnvVars) + if len(in.ShellEnvVarsRef.Keys) > 0 { + shellEnvVars, err = addShellEnvVarsFromRef(in.ShellEnvVarsRef, shellEnvVars) if err != nil { - response.Fatal(rsp, errors.Wrapf(err, "cannot process contents of secret %s in namespace %s", in.ShellEnvVarsSecretRef.Name, in.ShellEnvVarsSecretRef.Namespace)) + response.Fatal(rsp, errors.Wrapf(err, "cannot process contents of shellEnvVarsRef %s", in.ShellEnvVarsRef.Name)) return rsp, nil } } diff --git a/input/v1alpha1/parameters.go b/input/v1alpha1/parameters.go index f5d4d6c..3c6aaea 100644 --- a/input/v1alpha1/parameters.go +++ b/input/v1alpha1/parameters.go @@ -19,9 +19,9 @@ type Parameters struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // shellEnvVarSecretRef + // shellEnvVarsRef // +optional - ShellEnvVarsSecretRef ShellEnvVarsSecretRef `json:"shellEnvVarsSecretRef,omitempty"` + ShellEnvVarsRef ShellEnvVarsRef `json:"shellEnvVarsRef,omitempty"` // shellEnvVars // +optional @@ -49,11 +49,9 @@ type ShellEnvVar struct { Value string `json:"value,omitempty"` } -type ShellEnvVarsSecretRef struct { +type ShellEnvVarsRef struct { // The Key whose value is the secret - Key string `json:"key,omitempty"` - // Name of the Kubernetes secret + Keys []string `json:"keys,omitempty"` + // Name of the enviroment variable Name string `json:"name,omitempty"` - // Namespace where Kubernetes secret resides - Namespace string `json:"namespace,omitempty"` } diff --git a/input/v1alpha1/zz_generated.deepcopy.go b/input/v1alpha1/zz_generated.deepcopy.go index 0c205cf..4dee1ad 100644 --- a/input/v1alpha1/zz_generated.deepcopy.go +++ b/input/v1alpha1/zz_generated.deepcopy.go @@ -13,7 +13,7 @@ func (in *Parameters) DeepCopyInto(out *Parameters) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.ShellEnvVarsSecretRef = in.ShellEnvVarsSecretRef + in.ShellEnvVarsRef.DeepCopyInto(&out.ShellEnvVarsRef) if in.ShellEnvVars != nil { in, out := &in.ShellEnvVars, &out.ShellEnvVars *out = make([]ShellEnvVar, len(*in)) @@ -55,16 +55,21 @@ func (in *ShellEnvVar) DeepCopy() *ShellEnvVar { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ShellEnvVarsSecretRef) DeepCopyInto(out *ShellEnvVarsSecretRef) { +func (in *ShellEnvVarsRef) DeepCopyInto(out *ShellEnvVarsRef) { *out = *in + if in.Keys != nil { + in, out := &in.Keys, &out.Keys + *out = make([]string, len(*in)) + copy(*out, *in) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShellEnvVarsSecretRef. -func (in *ShellEnvVarsSecretRef) DeepCopy() *ShellEnvVarsSecretRef { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShellEnvVarsRef. +func (in *ShellEnvVarsRef) DeepCopy() *ShellEnvVarsRef { if in == nil { return nil } - out := new(ShellEnvVarsSecretRef) + out := new(ShellEnvVarsRef) in.DeepCopyInto(out) return out } diff --git a/package/input/template.fn.crossplane.io_parameters.yaml b/package/input/template.fn.crossplane.io_parameters.yaml index b85aeba..1d0fe1b 100644 --- a/package/input/template.fn.crossplane.io_parameters.yaml +++ b/package/input/template.fn.crossplane.io_parameters.yaml @@ -54,17 +54,16 @@ spec: type: string type: object type: array - shellEnvVarsSecretRef: - description: shellEnvVarSecretRef + shellEnvVarsRef: + description: shellEnvVarsRef properties: - key: + keys: description: The Key whose value is the secret - type: string + items: + type: string + type: array name: - description: Name of the Kubernetes secret - type: string - namespace: - description: Namespace where Kubernetes secret resides + description: Name of the enviroment variable type: string type: object stderrField: diff --git a/secret.go b/secret.go deleted file mode 100644 index 56735c6..0000000 --- a/secret.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "maps" - "os" - - "github.com/crossplane-contrib/function-shell/input/v1alpha1" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth" -) - -func addShellEnvVarsFromSecret(secretRef v1alpha1.ShellEnvVarsSecretRef, shellEnvVars map[string]string) (map[string]string, error) { - var clientset *kubernetes.Clientset - - _, err := os.OpenFile("/var/run/secrets/kubernetes.io", os.O_RDWR, 0666) - if os.IsNotExist(err) { - clientset, err = outOfClusterClient() - if err != nil { - return shellEnvVars, err - } - } else { - clientset, err = inClusterClient() - if err != nil { - return shellEnvVars, err - } - } - - secret, err := getSecret(clientset, secretRef.Name, secretRef.Namespace) - if err != nil { - return shellEnvVars, err - } - secretEnvVars, err := getSecretEnvVars(secret, secretRef.Key) - if err != nil { - return shellEnvVars, err - } - - maps.Copy(shellEnvVars, secretEnvVars) - return shellEnvVars, nil -} - -func getSecretEnvVars(secret *v1.Secret, key string) (map[string]string, error) { - var envVarsData map[string]string - if err := json.Unmarshal(secret.Data[key], &envVarsData); err != nil { - return map[string]string(nil), err - } - return envVarsData, nil -} - -func getSecret(clientset *kubernetes.Clientset, name, namespace string) (*v1.Secret, error) { - secret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{}) - if errors.IsNotFound(err) { - err = fmt.Errorf("Secret %s in namespace %s not found\n", name, namespace) - return secret, err - } - - if statusError, isStatus := err.(*errors.StatusError); isStatus { - err = fmt.Errorf("Error getting secret %s in namespace %s: %v\n", - name, namespace, statusError.ErrStatus.Message) - return secret, err - } - - if err != nil { - return secret, err - } - - return secret, nil -} \ No newline at end of file