├── .DEREK.yml ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build.yaml │ └── publish.yaml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── config.go ├── go.mod ├── go.sum ├── main.go ├── main_test.go ├── types ├── credentials.go ├── cron_function.go └── scheduler.go └── version └── version.go /.DEREK.yml: -------------------------------------------------------------------------------- 1 | redirect: https://raw.githubusercontent.com/openfaas/faas/master/.DEREK.yml 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /cron-connector 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## My actions before raising this issue 4 | - [ ] Followed the [troubleshooting guide](https://docs.openfaas.com/deployment/troubleshooting/) 5 | - [ ] Read/searched [the docs](https://docs.openfaas.com/) 6 | - [ ] Searched [past issues](/issues) 7 | 8 | 9 | 10 | 11 | ## Expected Behaviour 12 | 13 | 14 | 15 | 16 | ## Current Behaviour 17 | 18 | 19 | 20 | 21 | ## Possible Solution 22 | 23 | 24 | 25 | 26 | ## Steps to Reproduce (for bugs) 27 | 28 | 29 | 1. 30 | 2. 31 | 3. 32 | 4. 33 | 34 | ## Context 35 | 36 | 37 | 38 | 39 | ## Your Environment 40 | 41 | * FaaS-CLI version ( Full output from: `faas-cli version` ): 42 | 43 | * Docker version `docker version` (e.g. Docker 17.0.05 ): 44 | 45 | * Are you using Docker Swarm or Kubernetes (FaaS-netes)? 46 | 47 | * Operating System and version (e.g. Linux, Windows, MacOS): 48 | 49 | * Code example or link to GitHub repo or gist to reproduce problem: 50 | 51 | * Other diagnostic information / logs from [troubleshooting guide](https://docs.openfaas.com/deployment/troubleshooting) 52 | 53 | ## Next steps 54 | 55 | You may [join Slack](https://docs.openfaas.com/community) for community support. 56 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | - [ ] I have raised an issue to propose this change ([required](https://github.com/openfaas/faas/blob/master/CONTRIBUTING.md)) 10 | - [ ] My issue has received approval from the maintainers or lead with the `design/approved` label 11 | 12 | 13 | ## How Has This Been Tested? 14 | 15 | 16 | 17 | 18 | 19 | ## Types of changes 20 | 21 | - [ ] Bug fix (non-breaking change which fixes an issue) 22 | - [ ] New feature (non-breaking change which adds functionality) 23 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 24 | 25 | 26 | ## Checklist: 27 | 28 | 29 | - [ ] My code follows the code style of this project. 30 | - [ ] My change requires a change to the documentation. 31 | - [ ] I have updated the documentation accordingly. 32 | - [ ] I've read the [CONTRIBUTION](https://github.com/openfaas/faas/blob/master/CONTRIBUTING.md) guide 33 | - [ ] I have signed-off my commits with `git commit -s` 34 | - [ ] I have added tests to cover my changes. 35 | - [ ] All new and existing tests passed. 36 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ '*' ] 6 | pull_request: 7 | branches: [ '*' ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@master 14 | - name: Install Go 15 | uses: actions/setup-go@master 16 | with: 17 | go-version: "1.23.x" 18 | - name: Set up QEMU 19 | uses: docker/setup-qemu-action@v3 20 | - name: Set up Docker Buildx 21 | uses: docker/setup-buildx-action@v3 22 | 23 | - name: Build multi-arch containers for validation only 24 | uses: docker/build-push-action@v6 25 | with: 26 | context: . 27 | file: ./Dockerfile 28 | outputs: "type=image,push=false" 29 | platforms: linux/amd64,linux/arm64 30 | tags: | 31 | ghcr.io/openfaas/cron-connector:${{ github.sha }} -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | 14 | - uses: actions/checkout@master 15 | - name: Install Go 16 | uses: actions/setup-go@master 17 | with: 18 | go-version: 1.23.x 19 | - name: Set up QEMU 20 | uses: docker/setup-qemu-action@v3 21 | 22 | - name: Set up Docker Buildx 23 | uses: docker/setup-buildx-action@v3 24 | 25 | - name: Get TAG 26 | id: get_tag 27 | run: echo TAG=${GITHUB_REF#refs/tags/} >> $GITHUB_ENV 28 | 29 | - name: Get git commit 30 | id: get_git_commit 31 | run: echo "GIT_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV 32 | - name: Get version 33 | id: get_version 34 | run: echo "VERSION=$(git describe --tags --dirty)" >> $GITHUB_ENV 35 | - name: Get Repo Owner 36 | id: get_repo_owner 37 | run: echo "REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" > $GITHUB_ENV 38 | 39 | - name: Login to Docker Registry 40 | uses: docker/login-action@v3 41 | with: 42 | username: ${{ github.repository_owner }} 43 | password: ${{ secrets.GITHUB_TOKEN }} 44 | registry: ghcr.io 45 | 46 | - name: Push containers 47 | uses: docker/build-push-action@v6 48 | with: 49 | context: . 50 | file: ./Dockerfile 51 | outputs: "type=registry,push=true" 52 | platforms: linux/amd64,linux/arm64 53 | build-args: | 54 | GIT_COMMIT=${{env.GIT_COMMIT}} 55 | VERSION=${{env.VERSION}} 56 | tags: | 57 | ghcr.io/${{ env.REPO_OWNER }}/cron-connector:${{ github.sha }} 58 | ghcr.io/${{ env.REPO_OWNER }}/cron-connector:${{ env.TAG }} 59 | ghcr.io/${{ env.REPO_OWNER }}/cron-connector:latest 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cron-connector.exe 2 | .idea/ 3 | cron-connector 4 | 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/openfaas/license-check:0.4.2 AS license-check 2 | 3 | FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.23 AS build 4 | 5 | ARG TARGETPLATFORM 6 | ARG BUILDPLATFORM 7 | ARG TARGETOS 8 | ARG TARGETARCH 9 | 10 | ENV CGO_ENABLED=0 11 | ENV GO111MODULE=on 12 | 13 | COPY --from=license-check /license-check /usr/bin/ 14 | 15 | WORKDIR /go/src/github.com/openfaas/cron-connector 16 | COPY . . 17 | 18 | RUN license-check -path /go/src/github.com/openfaas/cron-connector/ --verbose=false "Alex Ellis" "OpenFaaS Author(s)" 19 | RUN gofmt -l -d $(find . -type f -name '*.go' -not -path "./vendor/*") 20 | RUN CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go test -v ./... 21 | 22 | RUN VERSION=$(git describe --all --exact-match `git rev-parse HEAD` | grep tags | sed 's/tags\///') \ 23 | && GIT_COMMIT=$(git rev-list -1 HEAD) \ 24 | && GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=${CGO_ENABLED} go build \ 25 | --ldflags "-s -w \ 26 | -X github.com/openfaas/cron-connector/version.GitCommit=${GIT_COMMIT}\ 27 | -X github.com/openfaas/cron-connector/version.Version=${VERSION}" \ 28 | -o cron-connector . 29 | 30 | FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.21.2 AS ship 31 | LABEL org.label-schema.license="MIT" \ 32 | org.label-schema.vcs-url="https://github.com/openfaas/cron-connector" \ 33 | org.label-schema.vcs-type="Git" \ 34 | org.label-schema.name="openfaas/cron-connector" \ 35 | org.label-schema.vendor="openfaas" \ 36 | org.label-schema.docker.schema-version="1.0" 37 | 38 | RUN apk --no-cache add \ 39 | ca-certificates 40 | 41 | RUN addgroup -S app \ 42 | && adduser -S -g app app 43 | 44 | WORKDIR /home/app 45 | 46 | ENV http_proxy "" 47 | ENV https_proxy "" 48 | 49 | COPY --from=build /go/src/github.com/openfaas/cron-connector/cron-connector . 50 | RUN chown -R app:app ./ 51 | 52 | USER app 53 | 54 | CMD ["./cron-connector"] 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 OpenFaaS Author(s) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | IMG_NAME?=cron-connector 2 | 3 | TAG?=dev 4 | PLATFORMS?=linux/amd64,linux/arm/v7,linux/arm64 5 | OWNER?=alexellis2 6 | SERVER?=docker.io 7 | 8 | VERSION := $(shell git describe --tags --dirty) 9 | GIT_COMMIT := $(shell git rev-parse HEAD) 10 | 11 | export DOCKER_CLI_EXPERIMENTAL=enabled 12 | export DOCKER_BUILDKIT=1 13 | 14 | .PHONY: publish-buildx-all 15 | publish-buildx-all: 16 | @echo $(SERVER)/$(OWNER)/$(IMG_NAME):$(TAG) && \ 17 | docker buildx create --use --name=multiarch --node=multiarch && \ 18 | docker buildx build \ 19 | --platform $(PLATFORMS) \ 20 | --push=true \ 21 | --build-arg GIT_COMMIT=$(GIT_COMMIT) \ 22 | --build-arg VERSION=$(VERSION) \ 23 | --tag $(SERVER)/$(OWNER)/$(IMG_NAME):$(TAG) \ 24 | . 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Invoke your functions on a cron schedule 2 | 3 | This is a cron event connector for OpenFaaS. This was built to provide a timer interface to trigger OpenFaaS functions. Also checkout [OpenFaaS docs on cron](https://docs.openfaas.com/reference/cron/) for other methods on how you can run functions triggered by cron. 4 | 5 | This project was forked from [zeerorg/cron-connector](https://github.com/openfaas/cron-connector) to enable prompt updates and patches for end-users. 6 | 7 | ## How to Use 8 | 9 | First, [deploy OpenFaaS with faasd or Kubernetes](https://docs.openfaas.com/deployment/) 10 | 11 | ### Deploy the connector for Kubernetes 12 | 13 | For Kubernetes, see: [Scheduling function runs](https://docs.openfaas.com/reference/cron/) 14 | 15 | ### Deploy the connector for faasd 16 | 17 | For faasd, see [Serverless For Everyone Else](https://gumroad.com/l/serverless-for-everyone-else). 18 | 19 | ### Trigger a function from Cron 20 | 21 | The function should have 2 annotations: 22 | 23 | 1. `topic` annotation should be `cron-function`. 24 | 2. `schedule` annotation should be the cron schedule on which to invoke function 25 | 26 | For example, we may have a function "nodeinfo" which we want to invoke every 5 minutes: 27 | 28 | Deploy via the CLI: 29 | 30 | ```bash 31 | faas-cli store deploy nodeinfo \ 32 | --annotation schedule="*/5 * * * *" \ 33 | --annotation topic=cron-function 34 | ``` 35 | 36 | Or via `stack.yml`: 37 | 38 | ```yaml 39 | functions: 40 | nodeinfo: 41 | image: functions/nodeinfo 42 | annotations: 43 | topic: cron-function 44 | schedule: "*/5 * * * *" 45 | ``` 46 | 47 | You can learn how to create and test the [Cron syntax here](https://crontab.guru/every-5-minutes). 48 | 49 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/openfaas/connector-sdk/types" 9 | ) 10 | 11 | func getControllerConfig() (*types.ControllerConfig, error) { 12 | gURL, ok := os.LookupEnv("gateway_url") 13 | 14 | if !ok { 15 | return nil, fmt.Errorf("gateway_url environment variable not set") 16 | } 17 | 18 | asynchronousInvocation := false 19 | if val, exists := os.LookupEnv("asynchronous_invocation"); exists { 20 | asynchronousInvocation = (val == "1" || val == "true") 21 | } 22 | 23 | contentType := "text/plain" 24 | if v, exists := os.LookupEnv("content_type"); exists && len(v) > 0 { 25 | contentType = v 26 | } 27 | 28 | var printResponseBody bool 29 | if val, exists := os.LookupEnv("print_response_body"); exists { 30 | printResponseBody = (val == "1" || val == "true") 31 | } 32 | 33 | rebuildInterval := time.Second * 10 34 | 35 | if val, exists := os.LookupEnv("rebuild_interval"); exists { 36 | d, err := time.ParseDuration(val) 37 | if err != nil { 38 | return nil, err 39 | } 40 | rebuildInterval = d 41 | } 42 | 43 | return &types.ControllerConfig{ 44 | RebuildInterval: rebuildInterval, 45 | GatewayURL: gURL, 46 | AsyncFunctionInvocation: asynchronousInvocation, 47 | ContentType: contentType, 48 | PrintResponse: true, 49 | PrintResponseBody: printResponseBody, 50 | PrintRequestBody: false, 51 | }, nil 52 | } 53 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/openfaas/cron-connector 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.3 6 | 7 | require ( 8 | github.com/openfaas/connector-sdk v0.8.0 9 | github.com/openfaas/faas-cli v0.0.0-20250116111659-b368a1ccedbb 10 | github.com/openfaas/faas-provider v0.25.4 11 | github.com/openfaas/go-sdk v0.2.14 12 | github.com/robfig/cron/v3 v3.0.1 13 | ) 14 | 15 | require ( 16 | dario.cat/mergo v1.0.0 // indirect 17 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect 18 | github.com/Masterminds/semver v1.5.0 // indirect 19 | github.com/Microsoft/go-winio v0.6.2 // indirect 20 | github.com/ProtonMail/go-crypto v1.1.3 // indirect 21 | github.com/VividCortex/ewma v1.2.0 // indirect 22 | github.com/alexellis/arkade v0.0.0-20250120150820-889135fd0412 // indirect 23 | github.com/alexellis/go-execute/v2 v2.2.1 // indirect 24 | github.com/alexellis/hmac v1.3.0 // indirect 25 | github.com/alexellis/hmac/v2 v2.0.0 // indirect 26 | github.com/beorn7/perks v1.0.1 // indirect 27 | github.com/bep/debounce v1.2.1 // indirect 28 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 29 | github.com/cheggaaa/pb/v3 v3.1.6 // indirect 30 | github.com/cloudflare/circl v1.3.7 // indirect 31 | github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect 32 | github.com/cyphar/filepath-securejoin v0.3.6 // indirect 33 | github.com/distribution/reference v0.6.0 // indirect 34 | github.com/docker/cli v27.5.0+incompatible // indirect 35 | github.com/docker/distribution v2.8.3+incompatible // indirect 36 | github.com/docker/docker-credential-helpers v0.8.2 // indirect 37 | github.com/docker/go-units v0.5.0 // indirect 38 | github.com/drone/envsubst v1.0.3 // indirect 39 | github.com/emirpasic/gods v1.18.1 // indirect 40 | github.com/fatih/color v1.18.0 // indirect 41 | github.com/fsnotify/fsnotify v1.8.0 // indirect 42 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 43 | github.com/go-git/go-billy/v5 v5.6.2 // indirect 44 | github.com/go-git/go-git/v5 v5.13.1 // indirect 45 | github.com/gogo/protobuf v1.3.2 // indirect 46 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 47 | github.com/google/go-containerregistry v0.20.3 // indirect 48 | github.com/gorilla/mux v1.8.1 // indirect 49 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 50 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 51 | github.com/kevinburke/ssh_config v1.2.0 // indirect 52 | github.com/klauspost/compress v1.17.11 // indirect 53 | github.com/kr/text v0.2.0 // indirect 54 | github.com/magefile/mage v1.14.0 // indirect 55 | github.com/mattn/go-colorable v0.1.14 // indirect 56 | github.com/mattn/go-isatty v0.0.20 // indirect 57 | github.com/mattn/go-runewidth v0.0.16 // indirect 58 | github.com/mitchellh/go-homedir v1.1.0 // indirect 59 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 60 | github.com/moby/term v0.5.2 // indirect 61 | github.com/morikuni/aec v1.0.0 // indirect 62 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 63 | github.com/nats-io/nats.go v1.38.0 // indirect 64 | github.com/nats-io/nkeys v0.4.9 // indirect 65 | github.com/nats-io/nuid v1.0.1 // indirect 66 | github.com/nats-io/stan.go v0.10.4 // indirect 67 | github.com/olekukonko/tablewriter v0.0.5 // indirect 68 | github.com/opencontainers/go-digest v1.0.0 // indirect 69 | github.com/opencontainers/image-spec v1.1.0 // indirect 70 | github.com/openfaas/faas/gateway v0.0.0-20241209094132-4e20249bc070 // indirect 71 | github.com/openfaas/nats-queue-worker v0.0.0-20241209095010-619158b8e41f // indirect 72 | github.com/otiai10/copy v1.14.1 // indirect 73 | github.com/otiai10/mint v1.6.3 // indirect 74 | github.com/pjbgf/sha1cd v0.3.0 // indirect 75 | github.com/pkg/errors v0.9.1 // indirect 76 | github.com/prometheus/client_golang v1.20.5 // indirect 77 | github.com/prometheus/client_model v0.6.1 // indirect 78 | github.com/prometheus/common v0.62.0 // indirect 79 | github.com/prometheus/procfs v0.15.1 // indirect 80 | github.com/rivo/uniseg v0.4.7 // indirect 81 | github.com/ryanuber/go-glob v1.0.0 // indirect 82 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect 83 | github.com/sethvargo/go-password v0.3.1 // indirect 84 | github.com/sirupsen/logrus v1.9.3 // indirect 85 | github.com/skeema/knownhosts v1.3.0 // indirect 86 | github.com/spf13/cobra v1.8.1 // indirect 87 | github.com/spf13/pflag v1.0.5 // indirect 88 | github.com/vbatts/tar-split v0.11.7 // indirect 89 | github.com/xanzy/ssh-agent v0.3.3 // indirect 90 | golang.org/x/crypto v0.32.0 // indirect 91 | golang.org/x/mod v0.22.0 // indirect 92 | golang.org/x/net v0.34.0 // indirect 93 | golang.org/x/sync v0.10.0 // indirect 94 | golang.org/x/sys v0.29.0 // indirect 95 | google.golang.org/protobuf v1.36.3 // indirect 96 | gopkg.in/warnings.v0 v0.1.2 // indirect 97 | gopkg.in/yaml.v2 v2.4.0 // indirect 98 | gopkg.in/yaml.v3 v3.0.1 // indirect 99 | ) 100 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= 2 | dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= 4 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 5 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= 6 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 7 | github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= 8 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 9 | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 10 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 11 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 12 | github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= 13 | github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= 14 | github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= 15 | github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= 16 | github.com/alexellis/arkade v0.0.0-20241106114343-b0cf2fbdb998 h1:Bv4+RuafrJMDniG5bpy7EB/nZ/gsN+al8PpulLvQL64= 17 | github.com/alexellis/arkade v0.0.0-20241106114343-b0cf2fbdb998/go.mod h1:na4mIMGtiby6rjuzXMDtrmuGwaakDBntz52rSseKfkA= 18 | github.com/alexellis/arkade v0.0.0-20250120150820-889135fd0412 h1:Mfz0UJqz+o6uECgt342uNEfabkrFJLGv6FSdiVFEuG8= 19 | github.com/alexellis/arkade v0.0.0-20250120150820-889135fd0412/go.mod h1:Wk94jjYg7qrHZ2UMXPoF/mEUwen4bm3DYqW98nZe2vw= 20 | github.com/alexellis/go-execute v0.5.0 h1:L8kgNlFzNbJov7jrInlaig7i6ZUSz/tYYmqvb8dyD0s= 21 | github.com/alexellis/go-execute/v2 v2.2.1 h1:4Ye3jiCKQarstODOEmqDSRCqxMHLkC92Bhse743RdOI= 22 | github.com/alexellis/go-execute/v2 v2.2.1/go.mod h1:FMdRnUTiFAmYXcv23txrp3VYZfLo24nMpiIneWgKHTQ= 23 | github.com/alexellis/hmac v1.3.0 h1:DJl5wfuhwj2IjG9XRXzPY6bHZYrwrARFTotpxX3KS08= 24 | github.com/alexellis/hmac v1.3.0/go.mod h1:WmZwlIfB7EQaDuiScnQoMSs3K+1UalW/7ExXP3Cc2zU= 25 | github.com/alexellis/hmac/v2 v2.0.0 h1:/sH/UJxDXPpJorUeg2DudeKSeUrWPF32Yamw2TiDoOQ= 26 | github.com/alexellis/hmac/v2 v2.0.0/go.mod h1:O7hZZgTfh5fp5+vAamzodZPlbw+aQK+nnrrJNHsEvL0= 27 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 28 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 29 | github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= 30 | github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= 31 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 32 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 33 | github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk= 34 | github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6jM60XI= 35 | github.com/cheggaaa/pb/v3 v3.1.6 h1:h0x+vd7EiUohAJ29DJtJy+SNAc55t/elW3jCD086EXk= 36 | github.com/cheggaaa/pb/v3 v3.1.6/go.mod h1:urxmfVtaxT+9aWk92DbsvXFZtNSWQSO5TRAp+MJ3l1s= 37 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 38 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 39 | github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= 40 | github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= 41 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 42 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 43 | github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= 44 | github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 45 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 46 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 47 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 48 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 49 | github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI= 50 | github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 51 | github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM= 52 | github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 53 | github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= 54 | github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 55 | github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= 56 | github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= 57 | github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= 58 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 59 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 60 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 61 | github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= 62 | github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= 63 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 64 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 65 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 66 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 67 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 68 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 69 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 70 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 71 | github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= 72 | github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= 73 | github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= 74 | github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= 75 | github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= 76 | github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= 77 | github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= 78 | github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= 79 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 80 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 81 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 82 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 83 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 84 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 85 | github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo= 86 | github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8= 87 | github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= 88 | github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI= 89 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 90 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 91 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 92 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 93 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 94 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 95 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 96 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 97 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 98 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 99 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 100 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 101 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 102 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 103 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 104 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 105 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 106 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 107 | github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= 108 | github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= 109 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 110 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 111 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 112 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 113 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 114 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 115 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 116 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 117 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 118 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 119 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 120 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 121 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 122 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 123 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 124 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 125 | github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= 126 | github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 127 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 128 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 129 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 130 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 131 | github.com/nats-io/nats.go v1.22.1/go.mod h1:tLqubohF7t4z3du1QDPYJIQQyhb4wl6DhjxEajSI7UA= 132 | github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= 133 | github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= 134 | github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA= 135 | github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw= 136 | github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= 137 | github.com/nats-io/nkeys v0.4.8 h1:+wee30071y3vCZAYRsnrmIPaOe47A/SkK/UBDPdIV70= 138 | github.com/nats-io/nkeys v0.4.8/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= 139 | github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= 140 | github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE= 141 | github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= 142 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 143 | github.com/nats-io/stan.go v0.10.4 h1:19GS/eD1SeQJaVkeM9EkvEYattnvnWrZ3wkSWSw4uXw= 144 | github.com/nats-io/stan.go v0.10.4/go.mod h1:3XJXH8GagrGqajoO/9+HgPyKV5MWsv7S5ccdda+pc6k= 145 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 146 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 147 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 148 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 149 | github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 150 | github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 151 | github.com/openfaas/connector-sdk v0.6.7 h1:mWZI9Iuy6nXGNJDNw5g4nuRxxejzjdsDCxsfniEoOiA= 152 | github.com/openfaas/connector-sdk v0.6.7/go.mod h1:lT/mT7uDsXoOhk8ZaoRnhbE0y+LgJA3dM0ZXom2SlBg= 153 | github.com/openfaas/connector-sdk v0.7.1 h1:vkV/9YBQ62u7wwtrT3o+TBX6lj2VAAGlZbjHKPrDCas= 154 | github.com/openfaas/connector-sdk v0.7.1/go.mod h1:FLve1edhVPCfoVrIHc459AZegzPZ+nlvcTopOKWIHVk= 155 | github.com/openfaas/connector-sdk v0.8.0 h1:Sp3cb9nlU2f77DCqOgeF01Ze822puwLOB+22kAulFhA= 156 | github.com/openfaas/connector-sdk v0.8.0/go.mod h1:JL671hFB8EL8owp/vL+EwnL9enNEG+WCjQzIQk4vf5g= 157 | github.com/openfaas/faas-cli v0.0.0-20220806094027-3534df71572f h1:M0qf2WVEG0SrWZ09HXgSZZ9fU6mID7WkeNzHOas0sNw= 158 | github.com/openfaas/faas-cli v0.0.0-20220806094027-3534df71572f/go.mod h1:520XGF62aIsW7kXKwYYv/mHHhNo8tTfFB43KkaGM9hU= 159 | github.com/openfaas/faas-cli v0.0.0-20250116111659-b368a1ccedbb h1:wSA6hYnZaQ4eSqx7kFTp5/YSHxzzazftrKkUfhXVVeE= 160 | github.com/openfaas/faas-cli v0.0.0-20250116111659-b368a1ccedbb/go.mod h1:Gngfh6rYmXGnXpvnHyrYluGPvz4nUVwKu43Vq3VXAgk= 161 | github.com/openfaas/faas-provider v0.19.0 h1:1dv4HDkWa9/yVkUll23/06y9lf8+tISOxYoHwBXZaJI= 162 | github.com/openfaas/faas-provider v0.19.0/go.mod h1:Farrp+9Med8LeK3aoYpqplMP8f5ebTILbCSLg2LPLZk= 163 | github.com/openfaas/faas-provider v0.25.4 h1:Cly/M8/Q+OOn8qFxxeaZGyC5B2x4f+RSU28hej+1WcM= 164 | github.com/openfaas/faas-provider v0.25.4/go.mod h1:t6RSPCvNfiqYEzf/CtdIj+0OItdyK6IzrOca1EOLNSg= 165 | github.com/openfaas/faas/gateway v0.0.0-20220805080331-b87b96ae456e h1:EvsykDs5uQInPrngfF+grh3qDUdgIdv6TSOAR/tlwJg= 166 | github.com/openfaas/faas/gateway v0.0.0-20220805080331-b87b96ae456e/go.mod h1:czLBBaq5pOcVR6Zwm7rXu57iBe9IxDpBVj37eW8Nw48= 167 | github.com/openfaas/faas/gateway v0.0.0-20241209094132-4e20249bc070 h1:aeRDLXgwSOcMwUmZzXZsKf5GLoplZDF122SJVR62WgQ= 168 | github.com/openfaas/faas/gateway v0.0.0-20241209094132-4e20249bc070/go.mod h1:j5eUH36/clgztwBYFoXZ/s1UFQ9Alzt8rb50gh7L5Ns= 169 | github.com/openfaas/go-sdk v0.2.14 h1:N3bq0yparYZR6pR1AhZiPGvV8GAmQ1UfjmCOGaZMs68= 170 | github.com/openfaas/go-sdk v0.2.14/go.mod h1:DrKUCQ4F8L2cJOmWHNoX8zB8LQIZc/4hRgAtT4t0s4k= 171 | github.com/openfaas/nats-queue-worker v0.0.0-20231219105451-b94918cb8a24 h1:IeFWHV+vpviPvmAVrh6SsW19PkU+7+Pu/CLrFIe1gtc= 172 | github.com/openfaas/nats-queue-worker v0.0.0-20231219105451-b94918cb8a24/go.mod h1:SR1bzVXQaZoZS+wWmvO7bXZE6V9S9bukR9J5Kynr/vc= 173 | github.com/openfaas/nats-queue-worker v0.0.0-20241209095010-619158b8e41f h1:MvV6yh8Z/4bOuVlMu3+ZmxN/nGxSE+wVOf0VnO8fVVM= 174 | github.com/openfaas/nats-queue-worker v0.0.0-20241209095010-619158b8e41f/go.mod h1:ciUiwIQdpheqdo/VnO1F/AIZyYh/Tf/3uo4CAVw4zas= 175 | github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= 176 | github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= 177 | github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= 178 | github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= 179 | github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= 180 | github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= 181 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 182 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 183 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 184 | github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= 185 | github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 186 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 187 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 188 | github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= 189 | github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= 190 | github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= 191 | github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= 192 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 193 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 194 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 195 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 196 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 197 | github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 198 | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 199 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 200 | github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= 201 | github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 202 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= 203 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 204 | github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU= 205 | github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs= 206 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 207 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 208 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 209 | github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= 210 | github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= 211 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 212 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 213 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 214 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 215 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 216 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 217 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 218 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 219 | github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= 220 | github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= 221 | github.com/vbatts/tar-split v0.11.7 h1:ixZ93pO/GmvaZw4Vq9OwmfZK/kc2zKdPfu0B+gYqs3U= 222 | github.com/vbatts/tar-split v0.11.7/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= 223 | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 224 | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 225 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 226 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 227 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 228 | go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= 229 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 230 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 231 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 232 | golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 233 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 234 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 235 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= 236 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 237 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 238 | golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= 239 | golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 240 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 241 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 242 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 243 | golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= 244 | golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 245 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 246 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 247 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 248 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 249 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 250 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 251 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 252 | golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= 253 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 254 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 255 | golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= 256 | golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 257 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 258 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 259 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 260 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 261 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 262 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 263 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 264 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 265 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 266 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 267 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 268 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 269 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 270 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 271 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 272 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 273 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 274 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 275 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 276 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 277 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 278 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 279 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 280 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 281 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 282 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 283 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 284 | golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= 285 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 286 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 287 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 288 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 289 | golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 290 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 291 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 292 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 293 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 294 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 295 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 296 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 297 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 298 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 299 | google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= 300 | google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 301 | google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= 302 | google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 303 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 304 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 305 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 306 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 307 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 308 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 309 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 310 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 311 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 312 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 313 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 314 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) OpenFaaS Author(s) 2021. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "log" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "time" 14 | 15 | sdk "github.com/openfaas/go-sdk" 16 | 17 | "github.com/openfaas/connector-sdk/types" 18 | crontypes "github.com/openfaas/cron-connector/types" 19 | "github.com/openfaas/cron-connector/version" 20 | ptypes "github.com/openfaas/faas-provider/types" 21 | ) 22 | 23 | // topic is the value of the "topic" annotation to look for 24 | // on functions, to decide to include them for invocation 25 | const topic = "cron-function" 26 | 27 | func main() { 28 | config, err := getControllerConfig() 29 | if err != nil { 30 | fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) 31 | os.Exit(1) 32 | } 33 | 34 | rebuildTimeout := time.Second * 5 35 | if val, exists := os.LookupEnv("rebuild_timeout"); exists { 36 | rebuildTimeout, err = time.ParseDuration(val) 37 | if err != nil { 38 | log.Printf("Error: %s\n", err.Error()) 39 | os.Exit(1) 40 | } 41 | } 42 | 43 | sha, ver := version.GetReleaseInfo() 44 | log.Printf("Version: %s\tCommit: %s\n", sha, ver) 45 | log.Printf("Gateway URL: %s", config.GatewayURL) 46 | log.Printf("Async Invocation: %v", config.AsyncFunctionInvocation) 47 | log.Printf("Rebuild interval: %s\tRebuild timeout: %s", config.RebuildInterval, rebuildTimeout) 48 | 49 | httpClient := types.MakeClient(config.UpstreamTimeout) 50 | invoker := types.NewInvoker( 51 | gatewayRoute(config), 52 | httpClient, 53 | config.ContentType, 54 | config.PrintResponse, 55 | config.PrintRequestBody, 56 | "openfaas-ce/cron-connector") 57 | 58 | go func() { 59 | for { 60 | r := <-invoker.Responses 61 | if r.Error != nil { 62 | log.Printf("Error with %s: %s", r.Function, r.Error) 63 | } else { 64 | duration := fmt.Sprintf("%.2fs", r.Duration.Seconds()) 65 | if r.Duration < time.Second*1 { 66 | duration = fmt.Sprintf("%dms", r.Duration.Milliseconds()) 67 | } 68 | log.Printf("Response: %s [%d] (%s)", 69 | r.Function, 70 | r.Status, 71 | duration) 72 | } 73 | } 74 | }() 75 | 76 | auth, err := crontypes.GetClientAuth() 77 | if err != nil { 78 | log.Fatalf("Failed to get auth credentials: %s", err) 79 | } 80 | 81 | cronScheduler := crontypes.NewScheduler() 82 | cronScheduler.Start() 83 | 84 | u, err := url.Parse(config.GatewayURL) 85 | if err != nil { 86 | log.Fatalf("Failed to parse gateway URL: %s", err) 87 | } 88 | 89 | if err := startFunctionProbe(u, config.RebuildInterval, rebuildTimeout, topic, config, cronScheduler, invoker, auth); err != nil { 90 | fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) 91 | os.Exit(1) 92 | } 93 | } 94 | 95 | func gatewayRoute(config *types.ControllerConfig) string { 96 | if config.AsyncFunctionInvocation { 97 | return fmt.Sprintf("%s/%s", config.GatewayURL, "async-function") 98 | } 99 | 100 | return fmt.Sprintf("%s/%s", config.GatewayURL, "function") 101 | } 102 | 103 | // BasicAuth basic authentication for the the gateway 104 | type BasicAuth struct { 105 | Username string 106 | Password string 107 | } 108 | 109 | // Set set Authorization header on request 110 | func (auth *BasicAuth) Set(req *http.Request) error { 111 | req.SetBasicAuth(auth.Username, auth.Password) 112 | return nil 113 | } 114 | 115 | func startFunctionProbe(gatewayURL *url.URL, interval time.Duration, probeTimeout time.Duration, topic string, c *types.ControllerConfig, cronScheduler *crontypes.Scheduler, invoker *types.Invoker, auth sdk.ClientAuth) error { 116 | runningFuncs := make(crontypes.ScheduledFunctions, 0) 117 | 118 | httpClient := &http.Client{} 119 | httpClient.Timeout = probeTimeout 120 | 121 | sdkClient := sdk.NewClient(gatewayURL, auth, httpClient) 122 | 123 | ctx := context.Background() 124 | 125 | ticker := time.NewTicker(interval) 126 | defer ticker.Stop() 127 | 128 | for { 129 | <-ticker.C 130 | 131 | namespaces, err := sdkClient.GetNamespaces(ctx) 132 | if err != nil { 133 | log.Printf("error listing namespaces: %s", err) 134 | continue 135 | } 136 | 137 | for _, namespace := range namespaces { 138 | functions, err := sdkClient.GetFunctions(ctx, namespace) 139 | if err != nil { 140 | log.Printf("error listing functions in %s: %s", namespace, err) 141 | continue 142 | } 143 | 144 | newCronFunctions := requestsToCronFunctions(functions, namespace, topic) 145 | addFuncs, deleteFuncs := getNewAndDeleteFuncs(newCronFunctions, runningFuncs, namespace) 146 | 147 | for _, function := range deleteFuncs { 148 | log.Printf("Removed: %s [%s]", 149 | function.Function.String(), 150 | function.Function.Schedule) 151 | 152 | cronScheduler.Remove(function) 153 | } 154 | 155 | newScheduledFuncs := make(crontypes.ScheduledFunctions, 0) 156 | 157 | for _, function := range addFuncs { 158 | f, err := cronScheduler.AddCronFunction(function, invoker) 159 | if err != nil { 160 | log.Printf("can't add function: %s, %s", function.String(), err) 161 | continue 162 | } 163 | 164 | newScheduledFuncs = append(newScheduledFuncs, f) 165 | log.Printf("Added: %s [%s]", function.String(), function.Schedule) 166 | } 167 | 168 | runningFuncs = updateScheduledFunctions(runningFuncs, newScheduledFuncs, deleteFuncs) 169 | } 170 | } 171 | } 172 | 173 | // requestsToCronFunctions converts an array of types.FunctionStatus object 174 | // to CronFunction, ignoring those that cannot be converted 175 | func requestsToCronFunctions(functions []ptypes.FunctionStatus, namespace string, topic string) crontypes.CronFunctions { 176 | newCronFuncs := make(crontypes.CronFunctions, 0) 177 | for _, function := range functions { 178 | cF, err := crontypes.ToCronFunction(function, namespace, topic) 179 | if err != nil { 180 | continue 181 | } 182 | newCronFuncs = append(newCronFuncs, cF) 183 | } 184 | return newCronFuncs 185 | } 186 | 187 | // getNewAndDeleteFuncs takes new functions and running cron functions and returns 188 | // functions that need to be added and that need to be deleted 189 | func getNewAndDeleteFuncs(newFuncs crontypes.CronFunctions, oldFuncs crontypes.ScheduledFunctions, namespace string) (crontypes.CronFunctions, crontypes.ScheduledFunctions) { 190 | addFuncs := make(crontypes.CronFunctions, 0) 191 | deleteFuncs := make(crontypes.ScheduledFunctions, 0) 192 | 193 | for _, function := range newFuncs { 194 | if !oldFuncs.Contains(&function) { 195 | addFuncs = append(addFuncs, function) 196 | } 197 | } 198 | 199 | for _, function := range oldFuncs { 200 | if !newFuncs.Contains(&function.Function) && function.Function.Namespace == namespace { 201 | deleteFuncs = append(deleteFuncs, function) 202 | } 203 | } 204 | 205 | return addFuncs, deleteFuncs 206 | } 207 | 208 | // updateScheduledFunctions updates the scheduled function with 209 | // added functions and removes deleted functions 210 | func updateScheduledFunctions(running, added, deleted crontypes.ScheduledFunctions) crontypes.ScheduledFunctions { 211 | updatedSchedule := make(crontypes.ScheduledFunctions, 0) 212 | 213 | for _, function := range running { 214 | if !deleted.Contains(&function.Function) { 215 | updatedSchedule = append(updatedSchedule, function) 216 | } 217 | } 218 | 219 | updatedSchedule = append(updatedSchedule, added...) 220 | 221 | return updatedSchedule 222 | } 223 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) OpenFaaS Author(s) 2021. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | package main 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/openfaas/connector-sdk/types" 9 | cfunction "github.com/openfaas/cron-connector/types" 10 | ptypes "github.com/openfaas/faas-provider/types" 11 | ) 12 | 13 | func TestgetNewAndDeleteFuncs(t *testing.T) { 14 | newCronFunctions := make(cfunction.CronFunctions, 3) 15 | defaultReq := ptypes.FunctionStatus{} 16 | newCronFunctions[0] = cfunction.CronFunction{FuncData: defaultReq, Name: "test_function_unchanged", Namespace: "openfaas-fn", Schedule: "* * * * *"} 17 | newCronFunctions[1] = cfunction.CronFunction{FuncData: defaultReq, Name: "test_function_to_add", Namespace: "openfaas-fn", Schedule: "* * * * *"} 18 | newCronFunctions[2] = cfunction.CronFunction{FuncData: defaultReq, Name: "test_function_to_update", Namespace: "openfaas-fn", Schedule: "*/5 * * * *"} 19 | 20 | oldFuncs := make(cfunction.ScheduledFunctions, 3) 21 | oldFuncs[0] = cfunction.ScheduledFunction{Function: cfunction.CronFunction{FuncData: defaultReq, Name: "test_function_unchanged", Namespace: "openfaas-fn", Schedule: "* * * * *"}, ID: 0} 22 | oldFuncs[1] = cfunction.ScheduledFunction{Function: cfunction.CronFunction{FuncData: defaultReq, Name: "test_function_to_delete", Namespace: "openfaas-fn", Schedule: "* * * * *"}, ID: 0} 23 | oldFuncs[2] = cfunction.ScheduledFunction{Function: cfunction.CronFunction{FuncData: defaultReq, Name: "test_function_to_update", Namespace: "openfaas-fn", Schedule: "* * * * *"}, ID: 0} 24 | 25 | addFuncs, deleteFuncs := getNewAndDeleteFuncs(newCronFunctions, oldFuncs, "openfaas-fn") 26 | if !deleteFuncs.Contains(&oldFuncs[1].Function) { 27 | t.Error("function was not deleted") 28 | } 29 | 30 | if !addFuncs.Contains(&newCronFunctions[1]) { 31 | t.Error("function was not added") 32 | } 33 | 34 | if !deleteFuncs.Contains(&oldFuncs[2].Function) && !addFuncs.Contains(&newCronFunctions[2]) { 35 | t.Error("function will not be updated") 36 | } 37 | 38 | if addFuncs.Contains(&newCronFunctions[0]) || deleteFuncs.Contains(&newCronFunctions[0]) { 39 | t.Error("function should be left as it is") 40 | } 41 | } 42 | 43 | func TestNamespaceFuncs(t *testing.T) { 44 | newCronFunctions := make(cfunction.CronFunctions, 3) 45 | defaultReq := ptypes.FunctionStatus{} 46 | newCronFunctions[0] = cfunction.CronFunction{FuncData: defaultReq, Name: "test_function_one", Namespace: "openfaas-fn", Schedule: "* * * * *"} 47 | newCronFunctions[1] = cfunction.CronFunction{FuncData: defaultReq, Name: "test_function_one", Namespace: "custom", Schedule: "* * * * *"} 48 | newCronFunctions[2] = cfunction.CronFunction{FuncData: defaultReq, Name: "test_function_to_update", Namespace: "openfaas-fn", Schedule: "*/5 * * * *"} 49 | 50 | oldFuncs := make(cfunction.ScheduledFunctions, 3) 51 | oldFuncs[0] = cfunction.ScheduledFunction{Function: cfunction.CronFunction{FuncData: defaultReq, Name: "test_function_one", Namespace: "openfaas-fn", Schedule: "* * * * *"}, ID: 0} 52 | oldFuncs[1] = cfunction.ScheduledFunction{Function: cfunction.CronFunction{FuncData: defaultReq, Name: "test_function_to_delete", Namespace: "openfaas-fn", Schedule: "* * * * *"}, ID: 0} 53 | oldFuncs[2] = cfunction.ScheduledFunction{Function: cfunction.CronFunction{FuncData: defaultReq, Name: "test_function_to_update", Namespace: "openfaas-fn", Schedule: "* * * * *"}, ID: 0} 54 | 55 | addFuncs, deleteFuncs := getNewAndDeleteFuncs(newCronFunctions, oldFuncs, "openfaas-fn") 56 | if !deleteFuncs.Contains(&oldFuncs[1].Function) { 57 | t.Error("function was not deleted") 58 | } 59 | 60 | if !addFuncs.Contains(&newCronFunctions[1]) { 61 | t.Error("function was not added") 62 | } 63 | 64 | if !deleteFuncs.Contains(&oldFuncs[2].Function) && !addFuncs.Contains(&newCronFunctions[2]) { 65 | t.Error("function will not be updated") 66 | } 67 | 68 | if addFuncs.Contains(&newCronFunctions[0]) || deleteFuncs.Contains(&newCronFunctions[0]) { 69 | t.Error("function should be left as it is") 70 | } 71 | } 72 | 73 | func TestGatewayRoute_Async(t *testing.T) { 74 | testscases := []struct { 75 | GatewayURL string 76 | ExpectedGatewayURL string 77 | AsyncFunctionInvocation bool 78 | }{ 79 | { 80 | GatewayURL: "http://localhost:8080", 81 | AsyncFunctionInvocation: true, 82 | ExpectedGatewayURL: "http://localhost:8080/async-function", 83 | }, 84 | { 85 | GatewayURL: "http://localhost:8080", 86 | AsyncFunctionInvocation: false, 87 | ExpectedGatewayURL: "http://localhost:8080/function", 88 | }, 89 | } 90 | 91 | for _, test := range testscases { 92 | config := &types.ControllerConfig{ 93 | GatewayURL: test.GatewayURL, 94 | AsyncFunctionInvocation: test.AsyncFunctionInvocation, 95 | } 96 | 97 | val := gatewayRoute(config) 98 | if val != test.ExpectedGatewayURL { 99 | t.Errorf("expected: %s, got: %s", test.ExpectedGatewayURL, val) 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /types/credentials.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) OpenFaaS Author(s) 2024. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | package types 5 | 6 | import ( 7 | "context" 8 | b64 "encoding/base64" 9 | "fmt" 10 | "os" 11 | "strings" 12 | 13 | execute "github.com/alexellis/go-execute/v2" 14 | "github.com/openfaas/faas-provider/auth" 15 | sdk "github.com/openfaas/go-sdk" 16 | ) 17 | 18 | // GetClientAuth returns authentication credentials for OpenFaaS. The appropriate credentials are returned based on 19 | // the configured authentication mode. If basic_auth=true basic auth credentials are returned. If system_issuer is configured, 20 | // access token credentials are returned. Empty credentials are returned of non of the previous modes is configured. 21 | // An error is returned if obtaining the credentials fails. 22 | func GetClientAuth() (sdk.ClientAuth, error) { 23 | 24 | if val, ok := os.LookupEnv("basic_auth"); ok && len(val) > 0 { 25 | if val == "true" || val == "1" { 26 | return getBasicAuthCredentials() 27 | } 28 | } 29 | 30 | return nil, nil 31 | } 32 | 33 | func getBasicAuthCredentials() (sdk.ClientAuth, error) { 34 | if _, ok := os.LookupEnv("secret_mount_path"); ok { 35 | reader := auth.ReadBasicAuthFromDisk{} 36 | reader.SecretMountPath = os.Getenv("secret_mount_path") 37 | 38 | creds, err := reader.Read() 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return &sdk.BasicAuth{ 44 | Username: creds.User, 45 | Password: creds.Password, 46 | }, nil 47 | } 48 | 49 | if _, err := os.Stat("/var/run/secrets/kubernetes.io"); err != nil && os.IsNotExist(err) { 50 | username := "admin" 51 | creds := &auth.BasicAuthCredentials{ 52 | User: username, 53 | Password: LookupPasswordViaKubectl(), 54 | } 55 | 56 | return &sdk.BasicAuth{ 57 | Username: creds.User, 58 | Password: creds.Password, 59 | }, nil 60 | } 61 | 62 | return nil, fmt.Errorf("no basic auth credentials provided") 63 | } 64 | 65 | func LookupPasswordViaKubectl() string { 66 | 67 | ctx := context.Background() 68 | cmd := execute.ExecTask{ 69 | Command: "kubectl", 70 | Args: []string{"get", "secret", "-n", "openfaas", "basic-auth", "-o", "jsonpath='{.data.basic-auth-password}'"}, 71 | StreamStdio: false, 72 | PrintCommand: false, 73 | } 74 | 75 | res, err := cmd.Execute(ctx) 76 | if err != nil { 77 | panic(err) 78 | } 79 | 80 | if res.ExitCode != 0 { 81 | panic("Non-zero exit code: " + res.Stderr) 82 | } 83 | resOut := strings.Trim(res.Stdout, "\\'") 84 | 85 | decoded, err := b64.StdEncoding.DecodeString(resOut) 86 | if err != nil { 87 | panic(err) 88 | } 89 | password := strings.TrimSpace(string(decoded)) 90 | 91 | return password 92 | } 93 | -------------------------------------------------------------------------------- /types/cron_function.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) OpenFaaS Author(s) 2021. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | package types 5 | 6 | import ( 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/openfaas/connector-sdk/types" 14 | ptypes "github.com/openfaas/faas-provider/types" 15 | ) 16 | 17 | // CronFunction depicts an OpenFaaS function which is invoked by cron 18 | type CronFunction struct { 19 | FuncData ptypes.FunctionStatus 20 | Name string 21 | Namespace string 22 | Schedule string 23 | } 24 | 25 | func (c *CronFunction) String() string { 26 | if len(c.Namespace) > 0 { 27 | return fmt.Sprintf("%s.%s", c.Name, c.Namespace) 28 | } 29 | 30 | return c.Name 31 | } 32 | 33 | // CronFunctions a list of CronFunction 34 | type CronFunctions []CronFunction 35 | 36 | // Contains returns true if the provided CronFunction object is in list 37 | func (c *CronFunctions) Contains(cf *CronFunction) bool { 38 | for _, f := range *c { 39 | if f.Name == cf.Name && 40 | f.Namespace == cf.Namespace && 41 | f.Schedule == cf.Schedule { 42 | return true 43 | } 44 | } 45 | return false 46 | } 47 | 48 | // ToCronFunction converts a ptypes.FunctionStatus object to the CronFunction 49 | // and returns error if it is not possible 50 | func ToCronFunction(f ptypes.FunctionStatus, namespace string, topic string) (CronFunction, error) { 51 | if f.Annotations == nil { 52 | return CronFunction{}, fmt.Errorf("%s has no annotations", f.Name) 53 | } 54 | 55 | fTopic := (*f.Annotations)["topic"] 56 | fSchedule := (*f.Annotations)["schedule"] 57 | 58 | if fTopic != topic { 59 | return CronFunction{}, fmt.Errorf("%s has wrong topic: %s", fTopic, f.Name) 60 | } 61 | 62 | if !CheckSchedule(fSchedule) { 63 | return CronFunction{}, fmt.Errorf("%s has wrong cron schedule: %s", f.Name, fSchedule) 64 | } 65 | 66 | return CronFunction{ 67 | FuncData: f, 68 | Name: f.Name, 69 | Namespace: namespace, 70 | Schedule: fSchedule, 71 | }, nil 72 | } 73 | 74 | // InvokeFunction Invokes the cron function 75 | func (c CronFunction) InvokeFunction(i *types.Invoker) (*[]byte, error) { 76 | 77 | name := c.Name 78 | topic := (*c.FuncData.Annotations)["topic"] 79 | 80 | headers := http.Header{ 81 | "X-Topic": {topic}, 82 | "X-Connector": {"cron-connector"}, 83 | } 84 | 85 | gwURL := fmt.Sprintf("%s/%s", i.GatewayURL, c.String()) 86 | 87 | req, err := http.NewRequest(http.MethodPost, gwURL, nil) 88 | if err != nil { 89 | return nil, fmt.Errorf("failed to create http request to %s %w", gwURL, err) 90 | } 91 | 92 | for k, v := range headers { 93 | req.Header[k] = v 94 | } 95 | 96 | if req.Body != nil { 97 | defer req.Body.Close() 98 | } 99 | start := time.Now() 100 | 101 | var body *[]byte 102 | res, err := i.Client.Do(req) 103 | if err != nil { 104 | i.Responses <- types.InvokerResponse{ 105 | Error: fmt.Errorf("unable to invoke %s %w", c.String(), err), 106 | Function: name, 107 | Topic: topic, 108 | Status: http.StatusServiceUnavailable, 109 | Duration: time.Since(start), 110 | } 111 | return nil, err 112 | } 113 | 114 | if res.Body != nil { 115 | defer res.Body.Close() 116 | bytesOut, err := ioutil.ReadAll(res.Body) 117 | 118 | if err != nil { 119 | log.Printf("Error reading body") 120 | i.Responses <- types.InvokerResponse{ 121 | Error: fmt.Errorf("unable to invoke %s %w", c.String(), err), 122 | Status: http.StatusServiceUnavailable, 123 | Function: name, 124 | Topic: topic, 125 | Duration: time.Since(start), 126 | } 127 | 128 | return nil, fmt.Errorf("unable to read body %s", err) 129 | } 130 | 131 | body = &bytesOut 132 | } 133 | 134 | i.Responses <- types.InvokerResponse{ 135 | Body: body, 136 | Status: res.StatusCode, 137 | Header: &res.Header, 138 | Function: name, 139 | Topic: topic, 140 | Duration: time.Since(start), 141 | } 142 | 143 | return body, nil 144 | } 145 | -------------------------------------------------------------------------------- /types/scheduler.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) OpenFaaS Author(s) 2021. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | package types 5 | 6 | import ( 7 | "log" 8 | 9 | "github.com/openfaas/connector-sdk/types" 10 | cron "github.com/robfig/cron/v3" 11 | ) 12 | 13 | // EntryID type redifined for this package 14 | type EntryID cron.EntryID 15 | 16 | var standardParser = cron.NewParser( 17 | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor, 18 | ) 19 | 20 | // Scheduler is an interface which talks with cron scheduler 21 | type Scheduler struct { 22 | main *cron.Cron 23 | } 24 | 25 | // ScheduledFunction is a CronFunction that has been scheduled to run 26 | type ScheduledFunction struct { 27 | 28 | // Function is CronFunction object which is running 29 | Function CronFunction 30 | 31 | // Id is the entryid for the scheduled function 32 | ID EntryID 33 | } 34 | 35 | // ScheduledFunctions is an array of ScheduledFunction 36 | type ScheduledFunctions []ScheduledFunction 37 | 38 | // NewScheduler returns a scheduler 39 | func NewScheduler() *Scheduler { 40 | return &Scheduler{ 41 | cron.New(cron.WithParser(standardParser)), 42 | } 43 | } 44 | 45 | // Start a scheduler in new go routine 46 | func (s *Scheduler) Start() { 47 | s.main.Start() 48 | } 49 | 50 | // AddCronFunction adds a function to cron 51 | func (s *Scheduler) AddCronFunction(c CronFunction, invoker *types.Invoker) (ScheduledFunction, error) { 52 | eID, err := s.main.AddFunc(c.Schedule, func() { 53 | log.Printf("Invoking: %s [%s]", c.String(), c.Schedule) 54 | if _, err := c.InvokeFunction(invoker); err != nil { 55 | log.Printf("Error: %s", err) 56 | } 57 | }) 58 | return ScheduledFunction{c, EntryID(eID)}, err 59 | } 60 | 61 | // Remove removes the function from scheduler 62 | func (s *Scheduler) Remove(function ScheduledFunction) { 63 | s.main.Remove(cron.EntryID(function.ID)) 64 | } 65 | 66 | // CheckSchedule returns true if the schedule string is compliant with cron 67 | func CheckSchedule(schedule string) bool { 68 | _, err := standardParser.Parse(schedule) 69 | return err == nil 70 | } 71 | 72 | // Contains returns true if the ScheduledFunctions array contains the CronFunction 73 | func (functions *ScheduledFunctions) Contains(cronFunc *CronFunction) bool { 74 | for _, f := range *functions { 75 | if f.Function.Name == cronFunc.Name && 76 | f.Function.Namespace == cronFunc.Namespace && 77 | f.Function.Schedule == cronFunc.Schedule { 78 | return true 79 | } 80 | } 81 | 82 | return false 83 | } 84 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 OpenFaaS Author(s) 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | package version 5 | 6 | var ( 7 | // Version release version of the provider 8 | Version string 9 | 10 | // GitCommit SHA of the last git commit 11 | GitCommit string 12 | 13 | // DevVersion string for the development version 14 | DevVersion = "dev" 15 | ) 16 | 17 | // BuildVersion returns current version of the provider 18 | func BuildVersion() string { 19 | if len(Version) == 0 { 20 | return DevVersion 21 | } 22 | return Version 23 | } 24 | 25 | // GetReleaseInfo includes the SHA and the release version 26 | func GetReleaseInfo() (sha, release string) { 27 | return GitCommit, BuildVersion() 28 | } 29 | --------------------------------------------------------------------------------