From b66c4b37037fbc8b3ac9cabd3d702b02851d2735 Mon Sep 17 00:00:00 2001 From: humoflife Date: Thu, 4 Apr 2024 22:16:56 -0700 Subject: [PATCH 1/3] function-shell initial implementation Signed-off-by: humoflife Signed-off-by: humoflife Signed-off-by: humoflife Signed-off-by: humoflife Signed-off-by: humoflife Signed-off-by: humoflife --- .github/ISSUE_TEMPLATE/bug_report.md | 40 +++ .github/ISSUE_TEMPLATE/feature_request.md | 24 ++ .github/PULL_REQUEST_TEMPLATE.md | 30 ++ .github/workflows/ci.yml | 164 +++++++++ .gitignore | 5 + Dockerfile | 63 ++++ Makefile | 52 +++ NOTES.txt | 9 + README.md | 312 +++++++++++++++++ clientset.go | 69 ++++ example/README.md | 44 +++ example/in-cluster/composition.yaml | 37 ++ example/in-cluster/configmap.yaml | 17 + example/in-cluster/definition.yaml | 30 ++ .../function-shell-grant-read-secrets.sh | 3 + example/in-cluster/functions.yaml | 8 + example/in-cluster/shell-claim.yaml | 6 + example/out-of-cluster/composition.yaml | 39 +++ example/out-of-cluster/functions.yaml | 11 + example/out-of-cluster/xr.yaml | 6 + fn.go | 155 +++++++++ fn_test.go | 232 +++++++++++++ go.mod | 76 ++++ go.sum | 327 ++++++++++++++++++ input/generate.go | 15 + input/v1beta1/parameters.go | 73 ++++ input/v1beta1/zz_generated.deepcopy.go | 97 ++++++ main.go | 36 ++ package/crossplane.yaml | 16 + .../template.fn.crossplane.io_parameters.yaml | 97 ++++++ renovate.json | 16 + script.go | 87 +++++ secret.go | 73 ++++ validate.go | 20 ++ 34 files changed, 2289 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/ci.yml create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 NOTES.txt create mode 100644 README.md create mode 100644 clientset.go create mode 100644 example/README.md create mode 100644 example/in-cluster/composition.yaml create mode 100644 example/in-cluster/configmap.yaml create mode 100644 example/in-cluster/definition.yaml create mode 100755 example/in-cluster/function-shell-grant-read-secrets.sh create mode 100644 example/in-cluster/functions.yaml create mode 100644 example/in-cluster/shell-claim.yaml create mode 100644 example/out-of-cluster/composition.yaml create mode 100644 example/out-of-cluster/functions.yaml create mode 100644 example/out-of-cluster/xr.yaml create mode 100644 fn.go create mode 100644 fn_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 input/generate.go create mode 100644 input/v1beta1/parameters.go create mode 100644 input/v1beta1/zz_generated.deepcopy.go create mode 100644 main.go create mode 100644 package/crossplane.yaml create mode 100644 package/input/template.fn.crossplane.io_parameters.yaml create mode 100644 renovate.json create mode 100644 script.go create mode 100644 secret.go create mode 100644 validate.go diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d834757 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug Report +about: Help us diagnose and fix bugs in this Function +labels: bug +--- + + +### What happened? + + + +### How can we reproduce it? + + +### What environment did it happen in? +Function version: + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..a9ddcf0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature Request +about: Help us make this Function more useful +labels: enhancement +--- + + +### What problem are you facing? + + +### How could this Function help solve your problem? + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..d893e4d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,30 @@ + + +### Description of your changes + + + +Fixes # + +I have: + +- [ ] Read and followed Crossplane's [contribution process]. +- [ ] Added or updated unit tests for my change. + +[contribution process]: https://git.io/fj2m9 +[docs]: https://docs.crossplane.io/contribute/contribute diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b88379a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,164 @@ +name: CI + +on: + push: + branches: + - main + - release-* + pull_request: {} + workflow_dispatch: + inputs: + version: + description: Package version (e.g. v0.1.0) + required: false + +env: + # Common versions + GO_VERSION: '1.21.3' + GOLANGCI_VERSION: 'v1.54.2' + DOCKER_BUILDX_VERSION: 'v0.11.2' + + # These environment variables are important to the Crossplane CLI install.sh + # script. They determine what version it installs. + XP_CHANNEL: master # TODO(negz): Pin to stable once v1.14 is released. + XP_VERSION: current # TODO(negz): Pin to a version once v1.14 is released. + + # This CI job will automatically push new builds to xpkg.upbound.io if the + # XPKG_ACCESS_ID and XPKG_TOKEN secrets are set in the GitHub respository (or + # organization) settings. Create a token at https://accounts.upbound.io. + XPKG_ACCESS_ID: ${{ secrets.XPKG_ACCESS_ID }} + + # The package to push, without a version tag. The default matches GitHub. For + # example xpkg.upbound.io/crossplane/function-template-go. + XPKG: xpkg.upbound.io/${{ github.repository}} + + # The package version to push. The default is 0.0.0-gitsha. + XPKG_VERSION: ${{ inputs.version }} + +jobs: + lint: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache: false # The golangci-lint action does its own caching. + + - name: Lint + uses: golangci/golangci-lint-action@v4 + with: + version: ${{ env.GOLANGCI_VERSION }} + + unit-test: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Run Unit Tests + run: go test -v -cover ./... + + # We want to build most packages for the amd64 and arm64 architectures. To + # speed this up we build single-platform packages in parallel. We then upload + # those packages to GitHub as a build artifact. The push job downloads those + # artifacts and pushes them as a single multi-platform package. + build: + runs-on: ubuntu-22.04 + strategy: + fail-fast: true + matrix: + arch: + - amd64 + - arm64 + steps: + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: all + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + version: ${{ env.DOCKER_BUILDX_VERSION }} + install: true + + - name: Checkout + uses: actions/checkout@v4 + + # We ask Docker to use GitHub Action's native caching support to speed up + # the build, per https://docs.docker.com/build/cache/backends/gha/. + - name: Build Runtime + id: image + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/${{ matrix.arch }} + cache-from: type=gha + cache-to: type=gha,mode=max + target: image + build-args: + GO_VERSION=${{ env.GO_VERSION }} + outputs: type=docker,dest=runtime-${{ matrix.arch }}.tar + + - name: Setup the Crossplane CLI + run: "curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh" + + - name: Build Package + run: ./crossplane xpkg build --package-file=${{ matrix.arch }}.xpkg --package-root=package/ --embed-runtime-image-tarball=runtime-${{ matrix.arch }}.tar + + - name: Upload Single-Platform Package + uses: actions/upload-artifact@v4 + with: + name: package-${{ matrix.arch }} + path: "*.xpkg" + if-no-files-found: error + retention-days: 1 + + # This job downloads the single-platform packages built by the build job, and + # pushes them as a multi-platform package. We only push the package it the + # XPKG_ACCESS_ID and XPKG_TOKEN secrets were provided. + push: + runs-on: ubuntu-22.04 + needs: + - build + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download Single-Platform Packages + uses: actions/download-artifact@v4 + with: + path: . + merge-multiple: true + + - name: Setup the Crossplane CLI + run: "curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh" + + - name: Login to Upbound + uses: docker/login-action@v3 + if: env.XPKG_ACCESS_ID != '' + with: + registry: xpkg.upbound.io + username: ${{ secrets.XPKG_ACCESS_ID }} + password: ${{ secrets.XPKG_TOKEN }} + + # If a version wasn't explicitly passed as a workflow_dispatch input we + # default to version v0.0.0--, for example + # v0.0.0-20231101115142-1091066df799. This is a simple implementation of + # Go's pseudo-versions: https://go.dev/ref/mod#pseudo-versions. + - name: Set Default Multi-Platform Package Version + if: env.XPKG_VERSION == '' + run: echo "XPKG_VERSION=v0.0.0-$(date -d@$(git show -s --format=%ct) +%Y%m%d%H%M%S)-$(git rev-parse --short=12 HEAD)" >> $GITHUB_ENV + + - name: Push Multi-Platform Package to Upbound + if: env.XPKG_ACCESS_ID != '' + run: "./crossplane --verbose xpkg push --package-files $(echo *.xpkg|tr ' ' ,) ${{ env.XPKG }}:${{ env.XPKG_VERSION }}" diff --git a/.gitignore b/.gitignore index 3b735ec..3f0f8df 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,8 @@ # Go workspace file go.work + +kubeconfig +function-shell +function-amd64.xpkg +function-arm64.xpkg diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..015d0ad --- /dev/null +++ b/Dockerfile @@ -0,0 +1,63 @@ +# syntax=docker/dockerfile:1 + +# We use the latest Go 1.x version unless asked to use something else. +# The GitHub Actions CI job sets this argument for a consistent Go version. +ARG GO_VERSION=1 + +# Setup the base environment. The BUILDPLATFORM is set automatically by Docker. +# The --platform=${BUILDPLATFORM} flag tells Docker to build the function using +# the OS and architecture of the host running the build, not the OS and +# 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 mkdir /scripts && chown 2000:2000 /scripts + +# TODO: Install awscli, gcloud +# RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "/tmp/awscliv2.zip" && \ +# unzip "/tmp/awscliv2.zip" && \ +# ./aws/install + +WORKDIR /fn + +# Most functions don't want or need CGo support, so we disable it. +ENV CGO_ENABLED=0 + +# We run go mod download in a separate step so that we can cache its results. +# This lets us avoid re-downloading modules if we don't need to. The type=target +# mount tells Docker to mount the current directory read-only in the WORKDIR. +# The type=cache mount tells Docker to cache the Go modules cache across builds. +RUN --mount=target=. --mount=type=cache,target=/go/pkg/mod go mod download + +# The TARGETOS and TARGETARCH args are set by docker. We set GOOS and GOARCH to +# these values to ask Go to compile a binary for these architectures. If +# TARGETOS and TARGETOS are different from BUILDPLATFORM, Go will cross compile +# for us (e.g. compile a linux/amd64 binary on a linux/arm64 build machine). +ARG TARGETOS +ARG TARGETARCH + +# Build the function binary. The type=target mount tells Docker to mount the +# current directory read-only in the WORKDIR. The type=cache mount tells Docker +# to cache the Go modules cache across builds. +RUN --mount=target=. \ + --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /function . + +# Produce the Function image. We use a very lightweight 'distroless' +# Python3 image that includes useful commands but not build tools used +# in previous stages. +# FROM python:3.12 +FROM gcr.io/distroless/python3-debian12 AS image + +WORKDIR / +COPY --from=build --chown=2000:2000 /scripts /scripts +COPY --from=build /bin /bin +COPY --from=build /etc /etc +COPY --from=build /lib /lib +COPY --from=build /tmp /tmp +COPY --from=build /usr /usr +COPY --from=build /function /function +EXPOSE 9443 +USER nonroot:nonroot +ENTRYPOINT ["/function"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6fa6418 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ +REPO_URL="xpkg.upbound.io/crossplane-contrib/function-shell" +VERSION_TAG="v0.0.6" +#PACKAGE_FILES="function-amd64.xpkg,function-arm64.xpkg" +PACKAGE_FILES="function-arm64.xpkg" + +help: ## Print help for targets with comments + @printf "For more targets and info see comments in Makefile.\n\n" + @grep -E '^[a-zA-Z0-9._-]+:.*## .*$$' Makefile | sort | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' + +#all: docker-build-amd64 docker-build-arm64 xpkg-build-amd64 xpkg-build-arm64 xpkg-push +all: docker-build-amd64 docker-build-arm64 xpkg-build-arm64 xpkg-push + +docker-build-amd64: ## Build AMD64 Docker Image + docker build . --quiet --platform=linux/amd64 --tag=runtime-amd64 + +docker-build-arm64: ## Build ARM64 Docker Image + docker build . --quiet --platform=linux/arm64 --tag=runtime-arm64 + +xpkg-build-amd64: ## Build AMD64 Composition Function XPKG + crossplane xpkg build \ + --package-root=package \ + --embed-runtime-image=runtime-amd64 \ + --package-file=function-amd64.xpkg + +xpkg-build-arm64: ## Build ARM64 Composition Function XPKG + crossplane xpkg build \ + --package-root=package \ + --embed-runtime-image=runtime-arm64 \ + --package-file=function-arm64.xpkg + +xpkg-push: ## Push XPKG Package Files, Requires Upbound login + up xpkg push ${REPO_URL}:${VERSION_TAG} -f ${PACKAGE_FILES} + +lint: ## Lint the Code + golangci-lint run + +fn-build: ## Build Function Code + go generate ./... + go build . + +test: ## Run Code Tests + go test -v -cover . + +render: ## Render Examples, Requires make debug first + crossplane beta render \ + example/out-of-cluster/xr.yaml \ + example/out-of-cluster/composition.yaml \ + example/out-of-cluster/functions.yaml + +debug: ## Run Shell Function For Rendering Examples + go run . --insecure --debug diff --git a/NOTES.txt b/NOTES.txt new file mode 100644 index 0000000..cfe6c74 --- /dev/null +++ b/NOTES.txt @@ -0,0 +1,9 @@ +To get started: + +1. Replace `function-template-go` with your function in `go.mod`, + `package/crossplane.yaml`, and any Go imports. (You can also do this + automatically by running the `./init.sh ` script.) +2. Update `input/v1beta1/` to reflect your desired input (and run `go generate`) +3. Add your logic to `RunFunction` in `fn.go` +4. Add tests for your logic in `fn_test.go` +5. Update `README.md`, to be about your function! \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f01aef6 --- /dev/null +++ b/README.md @@ -0,0 +1,312 @@ +# function-shell + +This Crossplane composition function accepts commands to run in a shell and it +returns the output to specified fields. It accepts the following parameters: +- `shellScriptsConfigMapsRef` - referencing at least one Kuberneres [`ConfigMap`](https://kubernetes.io/docs/concepts/configuration/configmap/) with at least one shell script. This script can be written in an arbitrary +shell language, for example bash or python3. +- `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: + +``` +{ + "ENV_FOO": "foo value", + "ENV_BAR": "bar value" +} +``` +- `shellEnvVars` - an array of environment variables with a `key` and `value` each. +- `shellCommand` - a shell command line that can contain pipes and redirects and calling multiple programs. +- `shellCommandField` - a reference to a field that contains the shell command line that should be run. +- `stdoutField` - the path to the field where the shell 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 + +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 +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 +[jq](https://jqlang.github.io/jq/) and filtered for the ids. + +The `function-shell` writes the dashboard ids to the +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 +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. + +``` +#!/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 `ConfigMap` that contains 2 example scripts. +When you experiment with scripts in ConfigMaps, apply the yaml to the +desired namespace, e.g. `kubectl -n crossplane-system apply -f +example/in-cluster/configmap.yaml`. It is recommended to use +the namespace where the `function-shell` pod is running. +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: function-shell-script +data: + hello-from-python.py: | + #!/usr/bin/python3 + + print ( "hello from python" ) + get-datadog-dashboard-ids.sh: | + #!/bin/bash + + 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'; +``` + +The composition reads a datadog secret that looks like below. +Replace `YOUR_API_KEY` and `YOUR_APP_KEY` with your respective keys. +```yaml +{ + "DATADOG_API_KEY": "YOUR_API_KEY", + "DATADOG_APP_KEY": "YOIR_APP_KEY" +} +``` + +```yaml +--- +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: + name: function-shell + input: + apiVersion: shell.fn.crossplane.io/v1beta1 + kind: Parameters + shellScriptsConfigMapsRef: + - scriptnames: + - hello-from-python.py + - get-datadog-dashboard-ids.sh + name: function-shell-script + namespace: upbound-system + shellEnvVarsSecretRef: + name: datadog-secret + namespace: upbound-system + key: credentials + shellEnvVars: + - key: DATADOG_API_URL + value: "https://api.datadoghq.com/api/v1/dashboard" + shellCommand: | + /scripts/get-datadog-dashboard-ids.sh + python3 /scripts/hello-from-python.py|awk '{print $3}'|tr "p" "P" + stdoutField: status.atFunction.shell.stdout + stderrField: status.atFunction.shell.stderr +``` + +The composition is called through the following `claim`. + +``` +--- +apiVersion: upbound.io/v1alpha1 +kind: Shell +metadata: + name: shell-1 +spec: {} +``` + +The API definition is as follows. Note that the API contains status fields that +are populated by `function-shell`. + +``` +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 +``` + +The `crossplane beta trace` output after applying the in-cluster +shell-claim.yaml is as follows: +``` +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 +``` + +The `XShell/shell-1-ttfbh` yaml output looks as per below. Notice the dashboard +ids in the `status.atFunction.shell.stdout` field, and the `curl` stderr output +in the `status.atFunction.shell.stderr` field. + +``` +apiVersion: upbound.io/v1alpha1 +kind: XShell +metadata: + creationTimestamp: "2024-04-11T02:31:54Z" + finalizers: + - composite.apiextensions.crossplane.io + generateName: shell-1- + generation: 17 + labels: + crossplane.io/claim-name: shell-1 + crossplane.io/claim-namespace: default + crossplane.io/composite: shell-1-wjjs4 + name: shell-1-wjjs4 + resourceVersion: "2577566" + uid: 77d24f9f-96db-4758-9155-9364ad227d0a +spec: + claimRef: + apiVersion: upbound.io/v1alpha1 + kind: Shell + name: shell-1 + namespace: default + compositionRef: + name: shell.upbound.io + compositionRevisionRef: + name: shell.upbound.io-2403237 + compositionUpdatePolicy: Automatic + resourceRefs: [] +status: + atFunction: + shell: + 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" + stdout: |- + "vn4-agn-ftd" + "9pt-bhb-uwj" + "6su-nff-222" + "sm3-cxs-q98" + "ssx-sci-uvi" + "3fd-h4e-7w6" + "qth-94z-ip5" + Python + conditions: + - lastTransitionTime: "2024-04-11T02:53:01Z" + reason: ReconcileSuccess + status: "True" + type: Synced + - lastTransitionTime: "2024-04-11T02:31:55Z" + reason: Available + status: "True" + type: Ready +``` + +# Development and test + +## Function code generation +``` +go generate ./... +``` + +## Build the function's runtime image - see Dockerfile +``` +docker build . --tag=runtime +``` + +## Render example function output +In Terminal 1 +``` +go run . --insecure --debug +``` + +In Terminal 2 +``` +crossplane beta render \ + example/out-of-cluster/xr.yaml \ + example/out-of-cluster/composition.yaml \ + example/out-of-cluster/functions.yaml +``` + +## Lint code +``` +golangci-lint run +``` + +## Run tests +``` +go test -v -cover . +``` + +## Docker build amd64 image +``` +docker build . --quiet --platform=linux/amd64 --tag runtime-amd64 +``` + +## Docker build arm64 image +``` +docker build . --quiet --platform=linux/arm64 --tag runtime-arm64 +``` + +## Crossplane build amd64 package +``` +crossplane xpkg build \ + --package-root=package \ + --embed-runtime-image=runtime-amd64 \ + --package-file=function-amd64.xpkg +``` + +## Crossplane build arm64 package +``` +crossplane xpkg build \ + --package-root=package \ + --embed-runtime-image=runtime-arm64 \ + --package-file=function-arm64.xpkg +``` + +# References +[functions]: https://docs.crossplane.io/latest/concepts/composition-functions +[go]: https://go.dev +[function guide]: https://docs.crossplane.io/knowledge-base/guides/write-a-composition-function-in-go +[package docs]: https://pkg.go.dev/github.com/crossplane/function-sdk-go +[docker]: https://www.docker.com +[cli]: https://docs.crossplane.io/latest/cli diff --git a/clientset.go b/clientset.go new file mode 100644 index 0000000..2f37f64 --- /dev/null +++ b/clientset.go @@ -0,0 +1,69 @@ +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/example/README.md b/example/README.md new file mode 100644 index 0000000..a5853b7 --- /dev/null +++ b/example/README.md @@ -0,0 +1,44 @@ +# Example manifests + +You can run your function locally and test it using `crossplane beta 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 beta render xr.yaml composition.yaml functions.yaml -r +--- +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 +``` + + +Make a Kubernetes secret for API and APP keys and credentials +in the form of a JSON file. +``` +{ + "API_KEY": "...masked.value.here...", + "APP_KEY": "...masked.value.here..." +} +``` +For example, it can be created as follows: +``` +kubectl -n upbound-system \ + create secret generic datadog-secret \ + --from-literal=credentials="${DATADOG_ENV_VARS_JSON}" \ + --dry-run=client \ + -o yaml|\ + kubectl apply -f - +``` diff --git a/example/in-cluster/composition.yaml b/example/in-cluster/composition.yaml new file mode 100644 index 0000000..407ada3 --- /dev/null +++ b/example/in-cluster/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 + shellScriptsConfigMapsRef: + - scriptnames: + - hello-from-python.py + - get-datadog-dashboard-ids.sh + name: function-shell-script + namespace: upbound-system + shellEnvVarsSecretRef: + key: credentials + name: datadog-secret + namespace: upbound-system + shellEnvVars: + - key: DATADOG_API_URL + value: "https://api.datadoghq.com/api/v1/dashboard" + shellCommand: | + /scripts/get-datadog-dashboard-ids.sh + python3 /scripts/hello-from-python.py|awk '{print $3}'|tr "p" "P" + stdoutField: status.atFunction.shell.stdout + stderrField: status.atFunction.shell.stderr diff --git a/example/in-cluster/configmap.yaml b/example/in-cluster/configmap.yaml new file mode 100644 index 0000000..c02c353 --- /dev/null +++ b/example/in-cluster/configmap.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: function-shell-script +data: + hello-from-python.py: | + #!/usr/bin/python3 + + print ( "hello from python" ) + get-datadog-dashboard-ids.sh: | + #!/bin/bash + + 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'; diff --git a/example/in-cluster/definition.yaml b/example/in-cluster/definition.yaml new file mode 100644 index 0000000..62d97b1 --- /dev/null +++ b/example/in-cluster/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/in-cluster/function-shell-grant-read-secrets.sh b/example/in-cluster/function-shell-grant-read-secrets.sh new file mode 100755 index 0000000..7c5a507 --- /dev/null +++ b/example/in-cluster/function-shell-grant-read-secrets.sh @@ -0,0 +1,3 @@ +#!/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/in-cluster/functions.yaml b/example/in-cluster/functions.yaml new file mode 100644 index 0000000..e781c0a --- /dev/null +++ b/example/in-cluster/functions.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: function-shell +spec: + package: xpkg.upbound.io/crossplane-contrib/function-shell:v0.0.6 + packagePullPolicy: Always diff --git a/example/in-cluster/shell-claim.yaml b/example/in-cluster/shell-claim.yaml new file mode 100644 index 0000000..4f508dc --- /dev/null +++ b/example/in-cluster/shell-claim.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: upbound.io/v1alpha1 +kind: Shell +metadata: + name: shell-1 +spec: {} diff --git a/example/out-of-cluster/composition.yaml b/example/out-of-cluster/composition.yaml new file mode 100644 index 0000000..76c3401 --- /dev/null +++ b/example/out-of-cluster/composition.yaml @@ -0,0 +1,39 @@ +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/v1beta1 + kind: Parameters + shellScriptsConfigMapsRef: + - scriptnames: + - py.py + - bash.sh + name: function-shell-script + namespace: upbound-system + shellEnvVarsSecretRef: + name: datadog-secret + namespace: upbound-system + key: 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';\ + ls -l ./py.py;\ + python3 ./py.py + stdoutField: status.atFunction.shell.stdout + stderrField: status.atFunction.shell.stderr diff --git a/example/out-of-cluster/functions.yaml b/example/out-of-cluster/functions.yaml new file mode 100644 index 0000000..88cbbd7 --- /dev/null +++ b/example/out-of-cluster/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/out-of-cluster/xr.yaml b/example/out-of-cluster/xr.yaml new file mode 100644 index 0000000..25472b9 --- /dev/null +++ b/example/out-of-cluster/xr.yaml @@ -0,0 +1,6 @@ +# Replace this with your XR! +apiVersion: example.crossplane.io/v1 +kind: XR +metadata: + name: example-xr +spec: {} diff --git a/fn.go b/fn.go new file mode 100644 index 0000000..8637b98 --- /dev/null +++ b/fn.go @@ -0,0 +1,155 @@ +package main + +import ( + "bytes" + "context" + "strings" + + "github.com/crossplane-contrib/function-shell/input/v1beta1" + "github.com/crossplane/crossplane-runtime/pkg/errors" + "github.com/crossplane/crossplane-runtime/pkg/logging" + fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1" + "github.com/crossplane/function-sdk-go/request" + "github.com/crossplane/function-sdk-go/response" + "github.com/keegancsmith/shell" +) + +// Function returns whatever response you ask it to. +type Function struct { + fnv1beta1.UnimplementedFunctionRunnerServiceServer + + log logging.Logger +} + +// RunFunction runs the Function. +func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) { + f.log.Info("Running function", "tag", req.GetMeta().GetTag()) + + rsp := response.To(req, response.DefaultTTL) + + in := &v1beta1.Parameters{} + if err := request.GetInput(req, in); err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot get Function from input")) + return rsp, nil + } + + oxr, err := request.GetObservedCompositeResource(req) + if err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot get observed composite resource from %T", req)) + return rsp, nil + } + + // Our input is an opaque object nested in a Composition. Let's validate it + if err := ValidateParameters(in, oxr); err != nil { + response.Fatal(rsp, errors.Wrap(err, "invalid Function input")) + return rsp, nil + } + + log := f.log.WithValues( + "oxr-version", oxr.Resource.GetAPIVersion(), + "oxr-kind", oxr.Resource.GetKind(), + "oxr-name", oxr.Resource.GetName(), + ) + + dxr, err := request.GetDesiredCompositeResource(req) + if err != nil { + response.Fatal(rsp, errors.Wrap(err, "cannot get desired composite resource")) + return rsp, nil + } + + dxr.Resource.SetAPIVersion(oxr.Resource.GetAPIVersion()) + dxr.Resource.SetKind(oxr.Resource.GetKind()) + + stdoutField := in.StdoutField + if len(in.StdoutField) == 0 { + stdoutField = "status.atFunction.shell.stdout" + } + stderrField := in.StderrField + if len(in.StderrField) == 0 { + stderrField = "status.atFunction.shell.stderr" + } + + var shellScripts map[string][]string + if in.ShellScriptsConfigMapsRef != nil { + shellScripts, err = loadShellScripts(log, in.ShellScriptsConfigMapsRef) + if err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot process shell script ConfigMaps")) + return rsp, nil + } + } + + copyShellScripts := "" + if len(shellScripts) > 0 { + for shellScriptName, shellScript := range shellScripts { + copyShellScripts = "rm -f ./scripts/" + shellScriptName + ";" + for _, line := range shellScript { + escapedLine := strings.ReplaceAll(line, "'", "\"'\"") + copyShellScripts = copyShellScripts + "echo '" + escapedLine + "'>> ./scripts/" + shellScriptName + ";" + } + copyShellScripts = copyShellScripts + "/bin/chmod +x ./scripts/" + shellScriptName + ";" + } + } + + shellCmd := "" + if len(in.ShellCommand) == 0 && len(in.ShellCommandField) == 0 { + log.Info("no shell command in in.ShellCommand nor in.ShellCommandField") + return rsp, nil + } + + if len(in.ShellCommand) > 0 { + shellCmd = in.ShellCommand + } + + // Prefer shell cmd from field over direct function input + if len(in.ShellCommandField) > 0 { + shellCmd = in.ShellCommandField + } + + var shellEnvVars = make(map[string]string) + for _, envVar := range in.ShellEnvVars { + shellEnvVars[envVar.Key] = envVar.Value + } + + if in.ShellEnvVarsSecretRef != (v1beta1.ShellEnvVarsSecretRef{}) { + shellEnvVars, err = addShellEnvVarsFromSecret(in.ShellEnvVarsSecretRef, 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)) + return rsp, nil + } + } + + var exportCmds string + //exportCmds = "export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin;" + for k, v := range shellEnvVars { + exportCmds = exportCmds + "export " + k + "=\"" + v + "\";" + } + + log.Info(shellCmd) + + var stdout, stderr bytes.Buffer + cmd := shell.Commandf(exportCmds + copyShellScripts + shellCmd) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err = cmd.Run() + if err != nil { + response.Fatal(rsp, errors.Wrapf(err, "shellCmd %s for %s failed", shellCmd, oxr.Resource.GetKind())) + return rsp, nil + } + out := strings.TrimSpace(stdout.String()) + err = dxr.Resource.SetValue(stdoutField, out) + if err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot set field %s to %s for %s", stdoutField, out, oxr.Resource.GetKind())) + return rsp, nil + } + err = dxr.Resource.SetValue(stderrField, strings.TrimSpace(stderr.String())) + if err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot set field %s to %s for %s", stderrField, out, oxr.Resource.GetKind())) + return rsp, nil + } + if err := response.SetDesiredCompositeResource(rsp, dxr); err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot set desired composite resources from %T", req)) + return rsp, nil + } + + return rsp, nil +} diff --git a/fn_test.go b/fn_test.go new file mode 100644 index 0000000..37bf9d7 --- /dev/null +++ b/fn_test.go @@ -0,0 +1,232 @@ +package main + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/durationpb" + + "github.com/crossplane/crossplane-runtime/pkg/logging" + fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1" + "github.com/crossplane/function-sdk-go/resource" + "github.com/crossplane/function-sdk-go/response" +) + +func TestRunFunction(t *testing.T) { + + type args struct { + ctx context.Context + req *fnv1beta1.RunFunctionRequest + } + type want struct { + rsp *fnv1beta1.RunFunctionResponse + err error + } + + cases := map[string]struct { + reason string + args args + want want + }{ + "ResponseIsParametersRequired": { + reason: "The Function should return a fatal result if no input was specified", + args: args{ + req: &fnv1beta1.RunFunctionRequest{ + Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, + Input: resource.MustStructJSON(`{ + "apiVersion": "template.fn.crossplane.io/v1beta1", + "kind": "Parameters" + }`), + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Meta: &fnv1beta1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)}, + Results: []*fnv1beta1.Result{ + { + Severity: fnv1beta1.Severity_SEVERITY_FATAL, + Message: "invalid Function input: parameters: Required value: one of ShellCommand or ShellCommandField is required", + }, + }, + }, + }, + }, + "ResponseIsEmptyShellCommand": { + reason: "The Function should return a response when after a script is run", + args: args{ + req: &fnv1beta1.RunFunctionRequest{ + Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, + Input: resource.MustStructJSON(`{ + "apiVersion": "template.fn.crossplane.io/v1beta1", + "kind": "Parameters", + "shellCommand": "" + }`), + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Meta: &fnv1beta1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)}, + Results: []*fnv1beta1.Result{ + { + Severity: fnv1beta1.Severity_SEVERITY_FATAL, + Message: "invalid Function input: parameters: Required value: one of ShellCommand or ShellCommandField is required", + }, + }, + }, + }, + }, + "ResponseIsEcho": { + reason: "The Function should write stdout to the specified field", + args: args{ + req: &fnv1beta1.RunFunctionRequest{ + Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, + Input: resource.MustStructJSON(`{ + "apiVersion": "template.fn.crossplane.io/v1beta1", + "kind": "Parameters", + "shellCommand": "echo foo", + "stdoutField": "spec.atFunction.shell.stdout" + }`), + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Meta: &fnv1beta1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)}, + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(`{ + "apiVersion": "", + "kind": "", + "spec": { + "atFunction": { + "shell": { + "stdout": "foo" + } + } + }, + "status": { + "atFunction": { + "shell": { + "stderr": "" + } + } + } + }`), + }, + }, + }, + }, + }, + "ResponseIsErrorWhenShellCommandNotFound": { + reason: "The Function should write to the specified stderr field when the shellCommand is not found", + args: args{ + req: &fnv1beta1.RunFunctionRequest{ + Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, + Input: resource.MustStructJSON(`{ + "apiVersion": "template.fn.crossplane.io/v1beta1", + "kind": "Parameters", + "shellCommand": "unkown-shell-command", + "stdoutField": "spec.atFunction.shell.stdout", + "stderrField": "spec.atFunction.shell.stderr" + }`), + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Meta: &fnv1beta1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)}, + Results: []*fnv1beta1.Result{ + { + Severity: fnv1beta1.Severity_SEVERITY_FATAL, + Message: "shellCmd unkown-shell-command for failed: exit status 127", + }, + }, + }, + }, + }, + "ResponseIsEchoEnvVar": { + reason: "The Function should accept and use environment variables", + args: args{ + req: &fnv1beta1.RunFunctionRequest{ + Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, + Input: resource.MustStructJSON(`{ + "apiVersion": "template.fn.crossplane.io/v1beta1", + "kind": "Parameters", + "shellEnvVars": [{"key": "TEST_ENV_VAR", "value": "foo"}], + "shellCommand": "echo ${TEST_ENV_VAR}", + "stdoutField": "spec.atFunction.shell.stdout" + }`), + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Meta: &fnv1beta1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)}, + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(`{ + "apiVersion": "", + "kind": "", + "spec": { + "atFunction": { + "shell": { + "stdout": "foo" + } + } + }, + "status": { + "atFunction": { + "shell": { + "stderr": "" + } + } + } + }`), + }, + }, + }, + }, + }, + "ResponseIsSecretError": { + reason: "The Function should return an error when secrets are not loadable", + args: args{ + req: &fnv1beta1.RunFunctionRequest{ + Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, + Input: resource.MustStructJSON(`{ + "apiVersion": "template.fn.crossplane.io/v1beta1", + "kind": "Parameters", + "shellEnvVarsSecretRef": {"name": "test-secret", "namespace": "crossplane-system", "key": "credentials"}, + "shellCommand": "echo testing", + "stdoutField": "spec.atFunction.shell.stdout" + }`), + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Meta: &fnv1beta1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)}, + Results: []*fnv1beta1.Result{ + { + Severity: fnv1beta1.Severity_SEVERITY_FATAL, + Message: "cannot process contents of secret test-secret in namespace crossplane-system: Secret test-secret in namespace crossplane-system not found\n", + }, + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + f := &Function{log: logging.NewNopLogger()} + rsp, err := f.RunFunction(tc.args.ctx, tc.args.req) + + if diff := cmp.Diff(tc.want.rsp, rsp, protocmp.Transform()); diff != "" { + t.Errorf("%s\nf.RunFunction(...): -want rsp, +got rsp:\n%s", tc.reason, diff) + } + + if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" { + t.Errorf("%s\nf.RunFunction(...): -want err, +got err:\n%s", tc.reason, diff) + } + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..986e315 --- /dev/null +++ b/go.mod @@ -0,0 +1,76 @@ +module github.com/crossplane-contrib/function-shell + +go 1.21 + +toolchain go1.21.3 + +require ( + github.com/alecthomas/kong v0.8.1 + github.com/crossplane/crossplane-runtime v1.15.1 + github.com/crossplane/function-sdk-go v0.2.0 + github.com/google/go-cmp v0.6.0 + github.com/keegancsmith/shell v0.0.0-20160208231706-ccb53e0c7c5c + google.golang.org/protobuf v1.32.0 + gopkg.in/yaml.v2 v2.4.0 + k8s.io/apimachinery v0.29.2 + k8s.io/client-go v0.29.1 + sigs.k8s.io/controller-tools v0.14.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch/v5 v5.8.0 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/go-json-experiment/json v0.0.0-20231013223334-54c864be5b8d // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gobuffalo/flect v1.0.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/oauth2 v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.17.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect + google.golang.org/grpc v1.61.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.29.1 // indirect + k8s.io/apiextensions-apiserver v0.29.1 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect + sigs.k8s.io/controller-runtime v0.17.0 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..82b5cd2 --- /dev/null +++ b/go.sum @@ -0,0 +1,327 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0= +github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA= +github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY= +github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= +github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= +github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= +github.com/antchfx/htmlquery v1.2.4 h1:qLteofCMe/KGovBI6SQgmou2QNyedFUW+pE+BpeZ494= +github.com/antchfx/htmlquery v1.2.4/go.mod h1:2xO6iu3EVWs7R2JYqBbp8YzG50gj/ofqs5/0VZoDZLc= +github.com/antchfx/xpath v1.2.0 h1:mbwv7co+x0RwgeGAOHdrKy89GvHaGvxxBtPK0uF9Zr8= +github.com/antchfx/xpath v1.2.0/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/crossplane/crossplane-runtime v1.15.1 h1:g1h75tNYOQT152IUNxs8ZgSsRFQKrZN9z69KefMujXs= +github.com/crossplane/crossplane-runtime v1.15.1/go.mod h1:kRcJjJQmBFrR2n/KhwL8wYS7xNfq3D8eK4JliEScOHI= +github.com/crossplane/function-sdk-go v0.2.0 h1:4r+dXeGgwOC2XehJlHsHlkdkUsGW8PzkiyPPd2cshQs= +github.com/crossplane/function-sdk-go v0.2.0/go.mod h1:AvaWMHeKvzzE0vODLBrU5njOzW6sm61Ou4js9OdBUXM= +github.com/crossplane/upjet v1.1.0-rc.0.0.20231227120826-4cb45f9104ac h1:T1MTxsPAE/Cs0/EAGjeC29H9O/rO81yol2/5qGsf888= +github.com/crossplane/upjet v1.1.0-rc.0.0.20231227120826-4cb45f9104ac/go.mod h1:t9etxIdYaxgyvFPBToikm5zBHi8RIpX8N4mTH77lQFM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro= +github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-json-experiment/json v0.0.0-20231013223334-54c864be5b8d h1:zqfo2jECgX5eYQseB/X+uV4Y5ocGOG/vG/LTztUCyPA= +github.com/go-json-experiment/json v0.0.0-20231013223334-54c864be5b8d/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= +github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8= +github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hcl/v2 v2.17.0 h1:z1XvSUyXd1HP10U4lrLg5e0JMVz6CPaJvAgxM0KNZVY= +github.com/hashicorp/hcl/v2 v2.17.0/go.mod h1:gJyW2PTShkJqQBKpAmPO3yxMxIuoXkOF2TpqXzrQyx4= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/terraform-json v0.17.0 h1:EiA1Wp07nknYQAiv+jIt4dX4Cq5crgP+TsTE45MjMmM= +github.com/hashicorp/terraform-json v0.17.0/go.mod h1:Huy6zt6euxaY9knPAFKjUITn8QxUFIe9VuSzb4zn/0o= +github.com/hashicorp/terraform-plugin-go v0.16.0 h1:DSOQ0rz5FUiVO4NUzMs8ln9gsPgHMTsfns7Nk+6gPuE= +github.com/hashicorp/terraform-plugin-go v0.16.0/go.mod h1:4sn8bFuDbt+2+Yztt35IbOrvZc0zyEi87gJzsTgCES8= +github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= +github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1 h1:G9WAfb8LHeCxu7Ae8nc1agZlQOSCUWsb610iAogBhCs= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1/go.mod h1:xcOSYlRVdPLmDUoqPhO9fiO/YCN/l6MGYeTzGt5jgkQ= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/keegancsmith/shell v0.0.0-20160208231706-ccb53e0c7c5c h1:6wy/0GuTK44yNuZ36eaqww+vWdVrFqByuixpwCczb3M= +github.com/keegancsmith/shell v0.0.0-20160208231706-ccb53e0c7c5c/go.mod h1:qbjfLhTSXb/4ZbhLyMVBWsgwT3KBdhkYbGGN0qdHHQs= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= +github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tmccombs/hcl2json v0.3.3 h1:+DLNYqpWE0CsOQiEZu+OZm5ZBImake3wtITYxQ8uLFQ= +github.com/tmccombs/hcl2json v0.3.3/go.mod h1:Y2chtz2x9bAeRTvSibVRVgbLJhLJXKlUeIvjeVdnm4w= +github.com/upbound/provider-aws v0.47.1 h1:Z+eAy9Ut4suVrx79pkzhsYTC6uvxNW2jkwAQCUVbq3g= +github.com/upbound/provider-aws v0.47.1/go.mod h1:kYxEeLtZv1CJKbc+O1IribFA47Oqkuso3hSo5vdwptU= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0= +github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= +k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= +k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw= +k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU= +k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= +k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= +k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= +k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= +k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s= +sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= +sigs.k8s.io/controller-tools v0.14.0 h1:rnNoCC5wSXlrNoBKKzL70LNJKIQKEzT6lloG6/LF73A= +sigs.k8s.io/controller-tools v0.14.0/go.mod h1:TV7uOtNNnnR72SpzhStvPkoS/U5ir0nMudrkrC4M9Sc= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/input/generate.go b/input/generate.go new file mode 100644 index 0000000..551821d --- /dev/null +++ b/input/generate.go @@ -0,0 +1,15 @@ +//go:build generate +// +build generate + +// NOTE(negz): See the below link for details on what is happening here. +// https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module + +// Remove existing and generate new input manifests +//go:generate rm -rf ../package/input/ +//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen paths=./v1beta1 object crd:crdVersions=v1 output:artifacts:config=../package/input + +package input + +import ( + _ "sigs.k8s.io/controller-tools/cmd/controller-gen" //nolint:typecheck +) diff --git a/input/v1beta1/parameters.go b/input/v1beta1/parameters.go new file mode 100644 index 0000000..67585b4 --- /dev/null +++ b/input/v1beta1/parameters.go @@ -0,0 +1,73 @@ +// Package v1beta1 contains the input type for this Function +// +kubebuilder:object:generate=true +// +groupName=template.fn.crossplane.io +// +versionName=v1beta1 +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// This isn't a custom resource, in the sense that we never install its CRD. +// It is a KRM-like object, so we generate a CRD to describe its schema. + +// Input can be used to provide input to this Function. +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// +kubebuilder:resource:categories=crossplane +type Parameters struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // shellEnvVarSecretRef + // +optional + ShellEnvVarsSecretRef ShellEnvVarsSecretRef `json:"shellEnvVarsSecretRef,omitempty"` + + // shellEnvVars + // +optional + ShellEnvVars []ShellEnvVar `json:"shellEnvVars,omitempty"` + + // shellScriptFiles + // +optional + ShellScriptsConfigMapsRef []ShellScriptsConfigMapRef `json:"shellScriptsConfigMapsRef"` + + // shellCmd + // +optional + ShellCommand string `json:"shellCommand,omitempty"` + + // shellCmdField + // +optional + ShellCommandField string `json:"shellCommandField,omitempty"` + + // stdoutField + // +optional + StdoutField string `json:"stdoutField,omitempty"` + + // stderrField + // +optional + StderrField string `json:"stderrField,omitempty"` +} + +type ShellEnvVar struct { + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` +} + +type ShellEnvVarsSecretRef struct { + // The Key whose value is the secret + Key string `json:"key,omitempty"` + // Name of the Kubernetes secret + Name string `json:"name,omitempty"` + // Namespace where Kubernetes secret resides + Namespace string `json:"namespace,omitempty"` +} + +type ShellScriptsConfigMapRef struct { + // The name of the script entries in a ConfigMap + // Each ConfigMap can contain multiple scripts + ScriptNames []string `json:"scriptnames,omitempty"` + // Name of Kubernetes ConfigMap + Name string `json:"name,omitempty"` + // Namespace where Kubernetes ConfigMap resides + Namespace string `json:"namespace,omitempty"` +} diff --git a/input/v1beta1/zz_generated.deepcopy.go b/input/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000..6c85de0 --- /dev/null +++ b/input/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,97 @@ +//go:build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Parameters) DeepCopyInto(out *Parameters) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.ShellEnvVarsSecretRef = in.ShellEnvVarsSecretRef + if in.ShellEnvVars != nil { + in, out := &in.ShellEnvVars, &out.ShellEnvVars + *out = make([]ShellEnvVar, len(*in)) + copy(*out, *in) + } + if in.ShellScriptsConfigMapsRef != nil { + in, out := &in.ShellScriptsConfigMapsRef, &out.ShellScriptsConfigMapsRef + *out = make([]ShellScriptsConfigMapRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Parameters. +func (in *Parameters) DeepCopy() *Parameters { + if in == nil { + return nil + } + out := new(Parameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Parameters) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShellEnvVar) DeepCopyInto(out *ShellEnvVar) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShellEnvVar. +func (in *ShellEnvVar) DeepCopy() *ShellEnvVar { + if in == nil { + return nil + } + out := new(ShellEnvVar) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShellEnvVarsSecretRef) DeepCopyInto(out *ShellEnvVarsSecretRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShellEnvVarsSecretRef. +func (in *ShellEnvVarsSecretRef) DeepCopy() *ShellEnvVarsSecretRef { + if in == nil { + return nil + } + out := new(ShellEnvVarsSecretRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShellScriptsConfigMapRef) DeepCopyInto(out *ShellScriptsConfigMapRef) { + *out = *in + if in.ScriptNames != nil { + in, out := &in.ScriptNames, &out.ScriptNames + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShellScriptsConfigMapRef. +func (in *ShellScriptsConfigMapRef) DeepCopy() *ShellScriptsConfigMapRef { + if in == nil { + return nil + } + out := new(ShellScriptsConfigMapRef) + in.DeepCopyInto(out) + return out +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..88f0def --- /dev/null +++ b/main.go @@ -0,0 +1,36 @@ +// Package main implements a Composition Function. +package main + +import ( + "github.com/alecthomas/kong" + + "github.com/crossplane/function-sdk-go" +) + +// CLI of this Function. +type CLI struct { + Debug bool `short:"d" help:"Emit debug logs in addition to info logs."` + + Network string `help:"Network on which to listen for gRPC connections." default:"tcp"` + Address string `help:"Address at which to listen for gRPC connections." default:":9443"` + TLSCertsDir string `help:"Directory containing server certs (tls.key, tls.crt) and the CA used to verify client certificates (ca.crt)" env:"TLS_SERVER_CERTS_DIR"` + Insecure bool `help:"Run without mTLS credentials. If you supply this flag --tls-server-certs-dir will be ignored."` +} + +// Run this Function. +func (c *CLI) Run() error { + log, err := function.NewLogger(c.Debug) + if err != nil { + return err + } + + return function.Serve(&Function{log: log}, + function.Listen(c.Network, c.Address), + function.MTLSCertificates(c.TLSCertsDir), + function.Insecure(c.Insecure)) +} + +func main() { + ctx := kong.Parse(&CLI{}, kong.Description("A Crossplane Composition Function.")) + ctx.FatalIfErrorf(ctx.Run()) +} diff --git a/package/crossplane.yaml b/package/crossplane.yaml new file mode 100644 index 0000000..413894e --- /dev/null +++ b/package/crossplane.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: meta.pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: function-shell + annotations: + meta.crossplane.io/maintainer: Markus Schweig + meta.crossplane.io/source: xpkg.upbound.io/crossplane-contrib/function-shell + meta.crossplane.io/license: Apache-2.0 + meta.crossplane.io/description: | + A function for running shell commands. + meta.crossplane.io/readme: | + A function for running shell commands. +spec: + crossplane: + version: ">=v1.14.0-up.1" diff --git a/package/input/template.fn.crossplane.io_parameters.yaml b/package/input/template.fn.crossplane.io_parameters.yaml new file mode 100644 index 0000000..3886f87 --- /dev/null +++ b/package/input/template.fn.crossplane.io_parameters.yaml @@ -0,0 +1,97 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: parameters.template.fn.crossplane.io +spec: + group: template.fn.crossplane.io + names: + categories: + - crossplane + kind: Parameters + listKind: ParametersList + plural: parameters + singular: parameters + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: Input can be used to provide input to this Function. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + shellCommand: + description: shellCmd + type: string + shellCommandField: + description: shellCmdField + type: string + shellEnvVars: + description: shellEnvVars + items: + properties: + key: + type: string + value: + type: string + type: object + type: array + shellEnvVarsSecretRef: + description: shellEnvVarSecretRef + properties: + key: + description: The Key whose value is the secret + type: string + name: + description: Name of the Kubernetes secret + type: string + namespace: + description: Namespace where Kubernetes secret resides + type: string + type: object + shellScriptsConfigMapsRef: + description: shellScriptFiles + items: + properties: + name: + description: Name of Kubernetes ConfigMap + type: string + namespace: + description: Namespace where Kubernetes ConfigMap resides + type: string + scriptnames: + description: |- + The name of the script entries in a ConfigMap + Each ConfigMap can contain multiple scripts + items: + type: string + type: array + type: object + type: array + stderrField: + description: stderrField + type: string + stdoutField: + description: stdoutField + type: string + type: object + served: true + storage: true diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..82b19d0 --- /dev/null +++ b/renovate.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ], + "crossplane": { + "fileMatch": ["(^|/)example/.*\\.ya?ml$"] + }, + "packageRules": [ + { + "matchManagers": ["crossplane"], + "matchFileNames": ["example/**"], + "groupName": "examples" + } + ] +} diff --git a/script.go b/script.go new file mode 100644 index 0000000..2633bce --- /dev/null +++ b/script.go @@ -0,0 +1,87 @@ +package main + +import ( + "context" + "fmt" + "maps" + "os" + "strings" + + "github.com/crossplane-contrib/function-shell/input/v1beta1" + "github.com/crossplane/crossplane-runtime/pkg/logging" + "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 loadShellScripts(log logging.Logger, shellScriptsConfigMapsRef []v1beta1.ShellScriptsConfigMapRef) (map[string][]string, error) { + shellScripts := make(map[string][]string) + + for _, shellScriptsConfigMapRef := range shellScriptsConfigMapsRef { + newShellScripts, err := getShellScriptsFromConfigMap(shellScriptsConfigMapRef) + if err != nil { + log.Info("unable to get shell scripts from ", shellScriptsConfigMapRef.Name) + continue + } + maps.Copy(shellScripts, newShellScripts) + } + + return shellScripts, nil +} + +func getShellScriptsFromConfigMap(shellScriptsConfigMapRef v1beta1.ShellScriptsConfigMapRef) (map[string][]string, error) { + var clientset *kubernetes.Clientset + scripts := make(map[string][]string) + + _, err := os.OpenFile("/var/run/secrets/kubernetes.io", os.O_RDWR, 0666) + if os.IsNotExist(err) { + clientset, err = outOfClusterClient() + if err != nil { + return scripts, err + } + } else { + clientset, err = inClusterClient() + if err != nil { + return scripts, err + } + } + + scripts, err = getScripts(clientset, shellScriptsConfigMapRef) + if err != nil { + return scripts, err + } + + return scripts, nil +} + +func getScripts(clientset *kubernetes.Clientset, shellScriptsConfigMapRef v1beta1.ShellScriptsConfigMapRef) (map[string][]string, error) { + scripts := make(map[string][]string) + scriptNames := shellScriptsConfigMapRef.ScriptNames + name := shellScriptsConfigMapRef.Name + namespace := shellScriptsConfigMapRef.Namespace + + scriptConfigMap, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + if errors.IsNotFound(err) { + err = fmt.Errorf("Script ConfigMap %s in namespace %s not found\n", name, namespace) + return scripts, err + } + + if statusError, isStatus := err.(*errors.StatusError); isStatus { + err = fmt.Errorf("Error getting script in ConfigMap %s in namespace %s: %v\n", + name, namespace, statusError.ErrStatus.Message) + return scripts, err + } + + if err != nil { + return scripts, err + } + + for _, scriptName := range scriptNames { + for _, scriptLine := range strings.Split(scriptConfigMap.Data[scriptName], "\n") { + scripts[scriptName] = append(scripts[scriptName], scriptLine) + } + } + + return scripts, nil +} diff --git a/secret.go b/secret.go new file mode 100644 index 0000000..54f648c --- /dev/null +++ b/secret.go @@ -0,0 +1,73 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "maps" + "os" + + "github.com/crossplane-contrib/function-shell/input/v1beta1" + 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 v1beta1.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 +} diff --git a/validate.go b/validate.go new file mode 100644 index 0000000..2e239d9 --- /dev/null +++ b/validate.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/crossplane-contrib/function-shell/input/v1beta1" + "github.com/crossplane/function-sdk-go/resource" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// ValidateParameters validates the Parameters object. +func ValidateParameters(p *v1beta1.Parameters, oxr *resource.Composite) *field.Error { + if p.ShellCommand == "" && p.ShellCommandField == "" { + return field.Required(field.NewPath("parameters"), "one of ShellCommand or ShellCommandField is required") + } + + if p.ShellCommand != "" && p.ShellCommandField != "" { + return field.Required(field.NewPath("parameters"), "exactly one of ShellCommand or ShellCommandField is required") + } + + return nil +} From 0537e206444c937b4a50ec5d65885a255a7488d7 Mon Sep 17 00:00:00 2001 From: humoflife Date: Wed, 17 Apr 2024 15:51:49 -0700 Subject: [PATCH 2/3] updated docs, and scriptNames field Signed-off-by: humoflife --- .github/ISSUE_TEMPLATE/bug_report.md | 13 ++- .github/ISSUE_TEMPLATE/feature_request.md | 8 +- .github/PULL_REQUEST_TEMPLATE.md | 7 +- README.md | 107 ++++++++++++------ example/README.md | 44 ------- example/in-cluster/composition.yaml | 2 +- example/out-of-cluster/composition.yaml | 2 +- input/v1beta1/parameters.go | 2 +- .../template.fn.crossplane.io_parameters.yaml | 2 +- 9 files changed, 93 insertions(+), 94 deletions(-) delete mode 100644 example/README.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d834757..ee1ceba 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -3,6 +3,9 @@ name: Bug Report about: Help us diagnose and fix bugs in this Function labels: bug --- + +# Bug Report + -### What happened? +## What happened? + +## How can we reproduce it? -### How can we reproduce it? -### What environment did it happen in? -Function version: +## What environment did it happen in? + +Function version: -### What problem are you facing? +## What problem are you facing? + -### How could this Function help solve your problem? +## How could this Function help solve your problem? + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d893e4d..5e3c081 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,7 +6,7 @@ Crossplane pull request. Find us in https://slack.crossplane.io/messages/dev if you need any help contributing. --> -### Description of your changes +# Description of your changes -Fixes # +Fixes # I have: -- [ ] Read and followed Crossplane's [contribution process]. +- [ ] Read and followed Crossplane's +[contribution process][contribution process]. Also see [docs][docs]. - [ ] Added or updated unit tests for my change. [contribution process]: https://git.io/fj2m9 diff --git a/README.md b/README.md index f01aef6..13dc3e7 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,39 @@ # function-shell -This Crossplane composition function accepts commands to run in a shell and it +This Crossplane composition [function][functions] is written in [go][go] +following this [function guide][function guide]. It runs in a [docker][docker] +container. The [package docs][package docs] are a useful reference when +writing functions. + +The `function-shell` accepts commands to run in a shell and it returns the output to specified fields. It accepts the following parameters: -- `shellScriptsConfigMapsRef` - referencing at least one Kuberneres [`ConfigMap`](https://kubernetes.io/docs/concepts/configuration/configmap/) with at least one shell script. This script can be written in an arbitrary + +- `shellScriptsConfigMapsRef` - referencing at least one Kuberneres +[`ConfigMap`](https://kubernetes.io/docs/concepts/configuration/configmap/) +with at least one shell script. This script can be written in an arbitrary shell language, for example bash or python3. - `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: -``` +```json { "ENV_FOO": "foo value", "ENV_BAR": "bar value" } ``` -- `shellEnvVars` - an array of environment variables with a `key` and `value` each. -- `shellCommand` - a shell command line that can contain pipes and redirects and calling multiple programs. -- `shellCommandField` - a reference to a field that contains the shell command line that should be run. -- `stdoutField` - the path to the field where the shell standard output should be written. -- `stderrField` - the path to the field where the shell standard error output should be written. + +- `shellEnvVars` - an array of environment variables with a +`key` and `value` each. +- `shellCommand` - a shell command line that can contain pipes +and redirects and calling multiple programs. +- `shellCommandField` - a reference to a field that contains +the shell command line that should be run. +- `stdoutField` - the path to the field where the shell +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 @@ -46,11 +60,13 @@ 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}" +kubectl create clusterrolebinding function-shell-admin-binding \ + --clusterrole cluster-admin \ + --serviceaccount="${SA}" ``` The composition reads a `ConfigMap` that contains 2 example scripts. @@ -58,6 +74,7 @@ When you experiment with scripts in ConfigMaps, apply the yaml to the desired namespace, e.g. `kubectl -n crossplane-system apply -f example/in-cluster/configmap.yaml`. It is recommended to use the namespace where the `function-shell` pod is running. + ```yaml apiVersion: v1 kind: ConfigMap @@ -80,7 +97,8 @@ data: The composition reads a datadog secret that looks like below. Replace `YOUR_API_KEY` and `YOUR_APP_KEY` with your respective keys. -```yaml + +```json { "DATADOG_API_KEY": "YOUR_API_KEY", "DATADOG_APP_KEY": "YOIR_APP_KEY" @@ -106,7 +124,7 @@ spec: apiVersion: shell.fn.crossplane.io/v1beta1 kind: Parameters shellScriptsConfigMapsRef: - - scriptnames: + - scriptNames: - hello-from-python.py - get-datadog-dashboard-ids.sh name: function-shell-script @@ -127,7 +145,7 @@ spec: The composition is called through the following `claim`. -``` +```yaml --- apiVersion: upbound.io/v1alpha1 kind: Shell @@ -139,7 +157,7 @@ spec: {} 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 kind: CompositeResourceDefinition metadata: @@ -174,7 +192,8 @@ spec: 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 @@ -185,7 +204,7 @@ The `XShell/shell-1-ttfbh` yaml output looks as per below. Notice the dashboard ids in the `status.atFunction.shell.stdout` field, and the `curl` stderr output in the `status.atFunction.shell.stderr` field. -``` +```yaml apiVersion: upbound.io/v1alpha1 kind: XShell metadata: @@ -241,69 +260,83 @@ status: type: Ready ``` -# Development and test +## Development and test -## Function code generation -``` +Crossplane has a [cli][cli] with useful commands for building packages. + +### Function code generation + +```shell go generate ./... ``` -## Build the function's runtime image - see Dockerfile -``` +### Build the function's runtime image - see Dockerfile + +```shell docker build . --tag=runtime ``` -## Render example function output +### Render example function output + In Terminal 1 -``` + +```shell go run . --insecure --debug ``` In Terminal 2 -``` + +```shell crossplane beta render \ example/out-of-cluster/xr.yaml \ example/out-of-cluster/composition.yaml \ example/out-of-cluster/functions.yaml ``` -## Lint code -``` +### Lint code + +```shell golangci-lint run ``` -## Run tests -``` +### Run tests + +```shell go test -v -cover . ``` -## Docker build amd64 image -``` +### Docker build amd64 image + +```shell docker build . --quiet --platform=linux/amd64 --tag runtime-amd64 ``` -## Docker build arm64 image -``` +### Docker build arm64 image + +```shell docker build . --quiet --platform=linux/arm64 --tag runtime-arm64 ``` -## Crossplane build amd64 package -``` +### Crossplane build amd64 package + +```shell crossplane xpkg build \ --package-root=package \ --embed-runtime-image=runtime-amd64 \ --package-file=function-amd64.xpkg ``` -## Crossplane build arm64 package -``` +### Crossplane build arm64 package + +```shell crossplane xpkg build \ --package-root=package \ --embed-runtime-image=runtime-arm64 \ --package-file=function-arm64.xpkg ``` -# References +## References + [functions]: https://docs.crossplane.io/latest/concepts/composition-functions [go]: https://go.dev [function guide]: https://docs.crossplane.io/knowledge-base/guides/write-a-composition-function-in-go diff --git a/example/README.md b/example/README.md deleted file mode 100644 index a5853b7..0000000 --- a/example/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Example manifests - -You can run your function locally and test it using `crossplane beta 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 beta render xr.yaml composition.yaml functions.yaml -r ---- -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 -``` - - -Make a Kubernetes secret for API and APP keys and credentials -in the form of a JSON file. -``` -{ - "API_KEY": "...masked.value.here...", - "APP_KEY": "...masked.value.here..." -} -``` -For example, it can be created as follows: -``` -kubectl -n upbound-system \ - create secret generic datadog-secret \ - --from-literal=credentials="${DATADOG_ENV_VARS_JSON}" \ - --dry-run=client \ - -o yaml|\ - kubectl apply -f - -``` diff --git a/example/in-cluster/composition.yaml b/example/in-cluster/composition.yaml index 407ada3..ed77d02 100644 --- a/example/in-cluster/composition.yaml +++ b/example/in-cluster/composition.yaml @@ -18,7 +18,7 @@ spec: apiVersion: shell.fn.crossplane.io/v1beta1 kind: Parameters shellScriptsConfigMapsRef: - - scriptnames: + - scriptNames: - hello-from-python.py - get-datadog-dashboard-ids.sh name: function-shell-script diff --git a/example/out-of-cluster/composition.yaml b/example/out-of-cluster/composition.yaml index 76c3401..8ee0167 100644 --- a/example/out-of-cluster/composition.yaml +++ b/example/out-of-cluster/composition.yaml @@ -15,7 +15,7 @@ spec: apiVersion: shell.fn.crossplane.io/v1beta1 kind: Parameters shellScriptsConfigMapsRef: - - scriptnames: + - scriptNames: - py.py - bash.sh name: function-shell-script diff --git a/input/v1beta1/parameters.go b/input/v1beta1/parameters.go index 67585b4..25b0f3e 100644 --- a/input/v1beta1/parameters.go +++ b/input/v1beta1/parameters.go @@ -65,7 +65,7 @@ type ShellEnvVarsSecretRef struct { type ShellScriptsConfigMapRef struct { // The name of the script entries in a ConfigMap // Each ConfigMap can contain multiple scripts - ScriptNames []string `json:"scriptnames,omitempty"` + ScriptNames []string `json:"scriptNames,omitempty"` // Name of Kubernetes ConfigMap Name string `json:"name,omitempty"` // Namespace where Kubernetes ConfigMap resides diff --git a/package/input/template.fn.crossplane.io_parameters.yaml b/package/input/template.fn.crossplane.io_parameters.yaml index 3886f87..d5888a4 100644 --- a/package/input/template.fn.crossplane.io_parameters.yaml +++ b/package/input/template.fn.crossplane.io_parameters.yaml @@ -77,7 +77,7 @@ spec: namespace: description: Namespace where Kubernetes ConfigMap resides type: string - scriptnames: + scriptNames: description: |- The name of the script entries in a ConfigMap Each ConfigMap can contain multiple scripts From 944e255cb0dfc8f18704f99e843448b48d74019f Mon Sep 17 00:00:00 2001 From: humoflife Date: Wed, 17 Apr 2024 16:50:06 -0700 Subject: [PATCH 3/3] moved function to v1alpha1 because API will evolve pending upstream function sdk changes Signed-off-by: humoflife --- README.md | 7 +++++++ example/in-cluster/composition.yaml | 2 +- example/out-of-cluster/composition.yaml | 12 ++---------- fn.go | 6 +++--- fn_test.go | 12 ++++++------ input/generate.go | 2 +- input/{v1beta1 => v1alpha1}/parameters.go | 6 +++--- input/{v1beta1 => v1alpha1}/zz_generated.deepcopy.go | 2 +- .../input/template.fn.crossplane.io_parameters.yaml | 2 +- script.go | 8 ++++---- secret.go | 4 ++-- validate.go | 4 ++-- 12 files changed, 33 insertions(+), 34 deletions(-) rename input/{v1beta1 => v1alpha1}/parameters.go (95%) rename input/{v1beta1 => v1alpha1}/zz_generated.deepcopy.go (99%) diff --git a/README.md b/README.md index 13dc3e7..376a84c 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,13 @@ following this [function guide][function guide]. It runs in a [docker][docker] container. The [package docs][package docs] are a useful reference when writing functions. +This is the `v1alpha1` version of `function-shell`. +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. + The `function-shell` accepts commands to run in a shell and it returns the output to specified fields. It accepts the following parameters: diff --git a/example/in-cluster/composition.yaml b/example/in-cluster/composition.yaml index ed77d02..4969e91 100644 --- a/example/in-cluster/composition.yaml +++ b/example/in-cluster/composition.yaml @@ -15,7 +15,7 @@ spec: # name: crossplane-contrib-function-shell name: function-shell input: - apiVersion: shell.fn.crossplane.io/v1beta1 + apiVersion: shell.fn.crossplane.io/v1alpha1 kind: Parameters shellScriptsConfigMapsRef: - scriptNames: diff --git a/example/out-of-cluster/composition.yaml b/example/out-of-cluster/composition.yaml index 8ee0167..79b7a03 100644 --- a/example/out-of-cluster/composition.yaml +++ b/example/out-of-cluster/composition.yaml @@ -12,14 +12,8 @@ spec: functionRef: name: function-shell input: - apiVersion: shell.fn.crossplane.io/v1beta1 + apiVersion: shell.fn.crossplane.io/v1alpha1 kind: Parameters - shellScriptsConfigMapsRef: - - scriptNames: - - py.py - - bash.sh - name: function-shell-script - namespace: upbound-system shellEnvVarsSecretRef: name: datadog-secret namespace: upbound-system @@ -32,8 +26,6 @@ spec: -H "Accept: application/json" \ -H "DD-API-KEY: ${DATADOG_API_KEY}" \ -H "DD-APPLICATION-KEY: ${DATADOG_APP_KEY}"|\ - jq '.dashboards[] .id';\ - ls -l ./py.py;\ - python3 ./py.py + jq '.dashboards[] .id'; stdoutField: status.atFunction.shell.stdout stderrField: status.atFunction.shell.stderr diff --git a/fn.go b/fn.go index 8637b98..d11c2e2 100644 --- a/fn.go +++ b/fn.go @@ -5,7 +5,7 @@ import ( "context" "strings" - "github.com/crossplane-contrib/function-shell/input/v1beta1" + "github.com/crossplane-contrib/function-shell/input/v1alpha1" "github.com/crossplane/crossplane-runtime/pkg/errors" "github.com/crossplane/crossplane-runtime/pkg/logging" fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1" @@ -27,7 +27,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ rsp := response.To(req, response.DefaultTTL) - in := &v1beta1.Parameters{} + in := &v1alpha1.Parameters{} if err := request.GetInput(req, in); err != nil { response.Fatal(rsp, errors.Wrapf(err, "cannot get Function from input")) return rsp, nil @@ -110,7 +110,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ shellEnvVars[envVar.Key] = envVar.Value } - if in.ShellEnvVarsSecretRef != (v1beta1.ShellEnvVarsSecretRef{}) { + if in.ShellEnvVarsSecretRef != (v1alpha1.ShellEnvVarsSecretRef{}) { shellEnvVars, err = addShellEnvVarsFromSecret(in.ShellEnvVarsSecretRef, 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)) diff --git a/fn_test.go b/fn_test.go index 37bf9d7..a58cec0 100644 --- a/fn_test.go +++ b/fn_test.go @@ -37,7 +37,7 @@ func TestRunFunction(t *testing.T) { req: &fnv1beta1.RunFunctionRequest{ Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, Input: resource.MustStructJSON(`{ - "apiVersion": "template.fn.crossplane.io/v1beta1", + "apiVersion": "template.fn.crossplane.io/v1alpha1", "kind": "Parameters" }`), }, @@ -60,7 +60,7 @@ func TestRunFunction(t *testing.T) { req: &fnv1beta1.RunFunctionRequest{ Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, Input: resource.MustStructJSON(`{ - "apiVersion": "template.fn.crossplane.io/v1beta1", + "apiVersion": "template.fn.crossplane.io/v1alpha1", "kind": "Parameters", "shellCommand": "" }`), @@ -84,7 +84,7 @@ func TestRunFunction(t *testing.T) { req: &fnv1beta1.RunFunctionRequest{ Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, Input: resource.MustStructJSON(`{ - "apiVersion": "template.fn.crossplane.io/v1beta1", + "apiVersion": "template.fn.crossplane.io/v1alpha1", "kind": "Parameters", "shellCommand": "echo foo", "stdoutField": "spec.atFunction.shell.stdout" @@ -125,7 +125,7 @@ func TestRunFunction(t *testing.T) { req: &fnv1beta1.RunFunctionRequest{ Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, Input: resource.MustStructJSON(`{ - "apiVersion": "template.fn.crossplane.io/v1beta1", + "apiVersion": "template.fn.crossplane.io/v1alpha1", "kind": "Parameters", "shellCommand": "unkown-shell-command", "stdoutField": "spec.atFunction.shell.stdout", @@ -151,7 +151,7 @@ func TestRunFunction(t *testing.T) { req: &fnv1beta1.RunFunctionRequest{ Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, Input: resource.MustStructJSON(`{ - "apiVersion": "template.fn.crossplane.io/v1beta1", + "apiVersion": "template.fn.crossplane.io/v1alpha1", "kind": "Parameters", "shellEnvVars": [{"key": "TEST_ENV_VAR", "value": "foo"}], "shellCommand": "echo ${TEST_ENV_VAR}", @@ -193,7 +193,7 @@ func TestRunFunction(t *testing.T) { req: &fnv1beta1.RunFunctionRequest{ Meta: &fnv1beta1.RequestMeta{Tag: "hello"}, Input: resource.MustStructJSON(`{ - "apiVersion": "template.fn.crossplane.io/v1beta1", + "apiVersion": "template.fn.crossplane.io/v1alpha1", "kind": "Parameters", "shellEnvVarsSecretRef": {"name": "test-secret", "namespace": "crossplane-system", "key": "credentials"}, "shellCommand": "echo testing", diff --git a/input/generate.go b/input/generate.go index 551821d..1507747 100644 --- a/input/generate.go +++ b/input/generate.go @@ -6,7 +6,7 @@ // Remove existing and generate new input manifests //go:generate rm -rf ../package/input/ -//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen paths=./v1beta1 object crd:crdVersions=v1 output:artifacts:config=../package/input +//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen paths=./v1alpha1 object crd:crdVersions=v1 output:artifacts:config=../package/input package input diff --git a/input/v1beta1/parameters.go b/input/v1alpha1/parameters.go similarity index 95% rename from input/v1beta1/parameters.go rename to input/v1alpha1/parameters.go index 25b0f3e..c2994c7 100644 --- a/input/v1beta1/parameters.go +++ b/input/v1alpha1/parameters.go @@ -1,8 +1,8 @@ -// Package v1beta1 contains the input type for this Function +// Package v1alpha1 contains the input type for this Function // +kubebuilder:object:generate=true // +groupName=template.fn.crossplane.io -// +versionName=v1beta1 -package v1beta1 +// +versionName=v1alpha1 +package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/input/v1beta1/zz_generated.deepcopy.go b/input/v1alpha1/zz_generated.deepcopy.go similarity index 99% rename from input/v1beta1/zz_generated.deepcopy.go rename to input/v1alpha1/zz_generated.deepcopy.go index 6c85de0..dbe1bb2 100644 --- a/input/v1beta1/zz_generated.deepcopy.go +++ b/input/v1alpha1/zz_generated.deepcopy.go @@ -2,7 +2,7 @@ // Code generated by controller-gen. DO NOT EDIT. -package v1beta1 +package v1alpha1 import ( runtime "k8s.io/apimachinery/pkg/runtime" diff --git a/package/input/template.fn.crossplane.io_parameters.yaml b/package/input/template.fn.crossplane.io_parameters.yaml index d5888a4..1300ec8 100644 --- a/package/input/template.fn.crossplane.io_parameters.yaml +++ b/package/input/template.fn.crossplane.io_parameters.yaml @@ -16,7 +16,7 @@ spec: singular: parameters scope: Namespaced versions: - - name: v1beta1 + - name: v1alpha1 schema: openAPIV3Schema: description: Input can be used to provide input to this Function. diff --git a/script.go b/script.go index 2633bce..afe2775 100644 --- a/script.go +++ b/script.go @@ -7,7 +7,7 @@ import ( "os" "strings" - "github.com/crossplane-contrib/function-shell/input/v1beta1" + "github.com/crossplane-contrib/function-shell/input/v1alpha1" "github.com/crossplane/crossplane-runtime/pkg/logging" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -15,7 +15,7 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" ) -func loadShellScripts(log logging.Logger, shellScriptsConfigMapsRef []v1beta1.ShellScriptsConfigMapRef) (map[string][]string, error) { +func loadShellScripts(log logging.Logger, shellScriptsConfigMapsRef []v1alpha1.ShellScriptsConfigMapRef) (map[string][]string, error) { shellScripts := make(map[string][]string) for _, shellScriptsConfigMapRef := range shellScriptsConfigMapsRef { @@ -30,7 +30,7 @@ func loadShellScripts(log logging.Logger, shellScriptsConfigMapsRef []v1beta1.Sh return shellScripts, nil } -func getShellScriptsFromConfigMap(shellScriptsConfigMapRef v1beta1.ShellScriptsConfigMapRef) (map[string][]string, error) { +func getShellScriptsFromConfigMap(shellScriptsConfigMapRef v1alpha1.ShellScriptsConfigMapRef) (map[string][]string, error) { var clientset *kubernetes.Clientset scripts := make(map[string][]string) @@ -55,7 +55,7 @@ func getShellScriptsFromConfigMap(shellScriptsConfigMapRef v1beta1.ShellScriptsC return scripts, nil } -func getScripts(clientset *kubernetes.Clientset, shellScriptsConfigMapRef v1beta1.ShellScriptsConfigMapRef) (map[string][]string, error) { +func getScripts(clientset *kubernetes.Clientset, shellScriptsConfigMapRef v1alpha1.ShellScriptsConfigMapRef) (map[string][]string, error) { scripts := make(map[string][]string) scriptNames := shellScriptsConfigMapRef.ScriptNames name := shellScriptsConfigMapRef.Name diff --git a/secret.go b/secret.go index 54f648c..bed240e 100644 --- a/secret.go +++ b/secret.go @@ -7,7 +7,7 @@ import ( "maps" "os" - "github.com/crossplane-contrib/function-shell/input/v1beta1" + "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" @@ -15,7 +15,7 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" ) -func addShellEnvVarsFromSecret(secretRef v1beta1.ShellEnvVarsSecretRef, shellEnvVars map[string]string) (map[string]string, error) { +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) diff --git a/validate.go b/validate.go index 2e239d9..8b9ca72 100644 --- a/validate.go +++ b/validate.go @@ -1,13 +1,13 @@ package main import ( - "github.com/crossplane-contrib/function-shell/input/v1beta1" + "github.com/crossplane-contrib/function-shell/input/v1alpha1" "github.com/crossplane/function-sdk-go/resource" "k8s.io/apimachinery/pkg/util/validation/field" ) // ValidateParameters validates the Parameters object. -func ValidateParameters(p *v1beta1.Parameters, oxr *resource.Composite) *field.Error { +func ValidateParameters(p *v1alpha1.Parameters, oxr *resource.Composite) *field.Error { if p.ShellCommand == "" && p.ShellCommandField == "" { return field.Required(field.NewPath("parameters"), "one of ShellCommand or ShellCommandField is required") }