├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── docs-check.yaml │ ├── go-checks.yaml │ ├── release-binary.yaml │ ├── release-image.yaml │ └── unit-test.yaml ├── .gitignore ├── .golangci.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile.in ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── api └── v1alpha1 │ ├── eventlistener_types.go │ ├── groupversion_info.go │ ├── triggerservice_types.go │ └── zz_generated.deepcopy.go ├── build ├── build.sh └── lint.sh ├── cmd ├── kubetrigger │ └── main.go └── manager │ └── main.go ├── codecov.yml ├── config ├── crd │ ├── core.oam.dev_definitions.yaml │ ├── standard.oam.dev_eventlisteners.yaml │ └── standard.oam.dev_triggerservices.yaml ├── definition │ ├── bump-application-revision.yaml │ ├── create-event-listener.yaml │ ├── default.yaml │ ├── patch-resource.yaml │ ├── record-event.yaml │ └── task.yaml └── manager │ ├── deployment.yaml │ ├── ns.yaml │ ├── rbac.yaml │ ├── role.yaml │ └── trigger-role.yaml ├── controllers ├── triggerservice │ └── triggerservice_controller.go └── utils │ └── utils.go ├── docs └── img │ ├── BUILDING.md │ ├── overview.svg │ └── overview │ ├── cli-logo.svg │ ├── clickhouse-logo.svg │ ├── cron-logo.svg │ ├── dingtalk-logo.svg │ ├── grafana-logo.svg │ ├── k8s-logo.svg │ ├── kubevela-logo.svg │ ├── loki-logo.svg │ ├── mail-logo.svg │ ├── oam-logo.svg │ ├── overview.excalidraw │ ├── prometheus-logo.svg │ ├── slack-logo.svg │ ├── telegram-logo.svg │ └── webhook-logo.svg ├── examples ├── README.md ├── conf-bump-app.yaml ├── conf-create-event-listener.yaml ├── conf-cronjob.yaml ├── conf-record-event.yaml ├── sample-eventlistener.yaml ├── sample.yaml ├── triggerservice-bump-app.yaml ├── triggerservice-image-update.yaml └── triggerservice-record-event.yaml ├── go.mod ├── go.sum ├── hack ├── boilerplate │ ├── boilerplate.Dockerfile.txt │ ├── boilerplate.Makefile.txt │ ├── boilerplate.generatego.txt │ ├── boilerplate.go.txt │ ├── boilerplate.mk.txt │ ├── boilerplate.py │ ├── boilerplate.py.txt │ └── boilerplate.sh.txt ├── format-svg-image.sh └── verify-boilerplate.sh ├── makefiles ├── common.mk ├── consts.mk └── targets.mk ├── manager.mk ├── pkg ├── action │ └── action.go ├── cmd │ ├── cmd.go │ └── options.go ├── config │ ├── config.go │ ├── parser.go │ ├── parser_test.go │ └── testdata │ │ ├── golden │ │ ├── conf.cue │ │ ├── conf.json │ │ ├── conf.yaml │ │ └── conf.yml │ │ ├── invalidext │ │ └── conf.invalid │ │ └── invalidschema │ │ └── conf.yml ├── eventhandler │ └── event_handler.go ├── executor │ ├── executor.go │ └── executor_test.go ├── filter │ ├── filter.go │ └── filter_test.go ├── source │ ├── builtin │ │ ├── cronjob │ │ │ ├── config.go │ │ │ ├── config_test.go │ │ │ ├── cronjob.go │ │ │ └── cronjob_test.go │ │ └── k8sresourcewatcher │ │ │ ├── controller │ │ │ └── controller.go │ │ │ ├── k8s_resource_watcher.go │ │ │ ├── types │ │ │ └── types.go │ │ │ └── utils │ │ │ └── utils.go │ ├── registry │ │ ├── register.go │ │ └── regsitry.go │ └── types │ │ └── types.go ├── types │ └── types.go ├── util │ ├── client │ │ └── client.go │ └── cue │ │ ├── cue.go │ │ ├── cue_test.go │ │ ├── validation.go │ │ └── validation_test.go ├── version │ └── version.go └── workqueue │ ├── default_rate_limiters.go │ ├── default_rate_limiters_test.go │ ├── delaying_queue.go │ ├── delaying_queue_test.go │ ├── doc.go │ ├── indexer_delaying_queue.go │ ├── indexer_delaying_queue_test.go │ ├── main_test.go │ ├── metrics.go │ ├── metrics_test.go │ ├── parallelizer.go │ ├── parallelizer_test.go │ ├── queue.go │ ├── queue_test.go │ ├── rate_limiting_queue.go │ └── rate_limiting_queue_test.go └── trigger.mk /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file is a github code protect rule follow the codeowners https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners#example-of-a-codeowners-file 2 | 3 | * @FogDong @charlie0129 @Somefive @anoop2811 @briankane @jguionnet 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: kind/bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 14 | 15 | **To Reproduce** 16 | 23 | 24 | **Expected behavior** 25 | 28 | 29 | **Screenshots** 30 | 33 | 34 | **Kube Trigger Version** 35 | 36 | 39 | 40 | **Cluster information** 41 | 45 | 46 | **Additional context** 47 | 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Questions & Help 4 | url: https://github.com/oam-dev/kubevela/discussions 5 | about: Please ask and answer questions here. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature request" 3 | about: Suggest an idea for this project 4 | title: "[Feature]" 5 | labels: kind/feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 14 | 15 | **Describe the solution you'd like** 16 | 19 | 20 | **Describe alternatives you've considered** 21 | 24 | 25 | **Additional context** 26 | 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ### Description of your changes 3 | 4 | 11 | 12 | Fixes # 13 | 14 | I have: 15 | 16 | - [ ] Read and followed KubeVela's [contribution process](https://github.com/kubevela/kubevela/blob/master/contribute/create-pull-request.md). 17 | - [ ] Add related tests. 18 | - [ ] Run `make reviewable` to ensure this PR is ready for review. 19 | - [ ] Added `backport release-x.y` labels to auto-backport this PR if necessary. 20 | 21 | ### How has this code been tested 22 | 23 | 28 | 29 | 30 | ### Special notes for your reviewer 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/docs-check.yaml: -------------------------------------------------------------------------------- 1 | name: Docs Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release-* 8 | tags: 9 | - "v*" 10 | pull_request: 11 | branches: 12 | - main 13 | - release-* 14 | workflow_dispatch: { } 15 | 16 | env: 17 | GO_VERSION: '1.23' 18 | 19 | jobs: 20 | checks: 21 | name: Check Docs 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Install xmllint 25 | run: sudo apt-get install -y libxml2-utils 26 | 27 | - name: Checkout Code 28 | uses: actions/checkout@v3 29 | 30 | - name: Check boilerplate 31 | run: make checklicense 32 | 33 | - name: Setup Go 34 | uses: actions/setup-go@v3 35 | with: 36 | go-version: ${{ env.GO_VERSION }} 37 | cache: true 38 | 39 | # Currently not required 40 | # Will be required to auto-gen docs in the future 41 | - name: Run Go Generate 42 | run: make generate 43 | 44 | - name: Check SVG formatting 45 | run: LINT=true make svgformat -------------------------------------------------------------------------------- /.github/workflows/go-checks.yaml: -------------------------------------------------------------------------------- 1 | name: Go Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release-* 8 | tags: 9 | - "v*" 10 | pull_request: 11 | branches: 12 | - main 13 | - release-* 14 | workflow_dispatch: { } 15 | 16 | env: 17 | GO_VERSION: '1.23' 18 | # Keep this in sync with build/lint.sh 19 | GOLANGCI_VERSION: '1.60.1' 20 | USE_BUILD_CONTAINER: '1' 21 | 22 | jobs: 23 | detect-noop: 24 | name: Detect No-op Changes 25 | runs-on: ubuntu-latest 26 | outputs: 27 | noop: ${{ steps.noop.outputs.should_skip }} 28 | steps: 29 | - name: Detect No-op Changes 30 | id: noop 31 | uses: fkirc/skip-duplicate-actions@v4.0.0 32 | with: 33 | github_token: ${{ secrets.GITHUB_TOKEN }} 34 | paths_ignore: '["**.md", "**.mdx", "**.png", "**.jpg", "**.svg"]' 35 | do_not_skip: '["workflow_dispatch", "schedule", "push"]' 36 | concurrent_skipping: false 37 | 38 | checks: 39 | name: Check Go Code 40 | runs-on: ubuntu-latest 41 | needs: detect-noop 42 | if: needs.detect-noop.outputs.noop != 'true' 43 | steps: 44 | - name: Checkout Code 45 | uses: actions/checkout@v3 46 | 47 | - name: Check boilerplate 48 | run: make checklicense 49 | 50 | - name: Setup Go 51 | uses: actions/setup-go@v3 52 | with: 53 | go-version: ${{ env.GO_VERSION }} 54 | 55 | # Use this action instead of running golangci directly because it can comment on pr. 56 | - name: Lint 57 | uses: golangci/golangci-lint-action@v3 58 | with: 59 | version: v${{ env.GOLANGCI_VERSION }} 60 | 61 | - name: Check Diff 62 | run: make checkdiff 63 | 64 | test-build: 65 | name: Test Container Build 66 | runs-on: ubuntu-latest 67 | needs: detect-noop 68 | if: needs.detect-noop.outputs.noop != 'true' 69 | steps: 70 | - name: Checkout Code 71 | uses: actions/checkout@v3 72 | 73 | - name: Setup Go Caches 74 | uses: actions/cache@v3 75 | with: 76 | path: | 77 | .go/gomodcache 78 | .go/gocache 79 | key: ${{ runner.os }}-gobuildcontainer-${{ hashFiles('**/go.sum') }} 80 | restore-keys: | 81 | ${{ runner.os }}-gobuildcontainer- 82 | 83 | - name: Test Container Build 84 | run: make container 85 | -------------------------------------------------------------------------------- /.github/workflows/release-binary.yaml: -------------------------------------------------------------------------------- 1 | name: Release kube-trigger Binaries 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | workflow_dispatch: { } 8 | 9 | permissions: 10 | contents: write 11 | 12 | env: 13 | GO_VERSION: '1.23' 14 | USE_BUILD_CONTAINER: '1' 15 | 16 | jobs: 17 | kube-trigger: 18 | name: Release kube-trigger Binaries 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout Code 22 | uses: actions/checkout@v3 23 | 24 | - name: Setup Go 25 | uses: actions/setup-go@v3 26 | with: 27 | go-version: ${{ env.GO_VERSION }} 28 | cache: true 29 | 30 | - name: Setup Go Caches 31 | uses: actions/cache@v3 32 | with: 33 | path: | 34 | .go/gomodcache 35 | .go/gocache 36 | key: ${{ runner.os }}-gobuildcontainer-${{ hashFiles('**/go.sum') }} 37 | restore-keys: | 38 | ${{ runner.os }}-gobuildcontainer- 39 | 40 | - name: Show Make Variables 41 | run: make trigger-variables 42 | 43 | - name: Get Version 44 | id: get_version 45 | run: | 46 | VERSION=$(make trigger-version) 47 | echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT 48 | 49 | - name: Build Binaries for All Platforms 50 | run: make trigger-all-package 51 | 52 | - name: Attach Binaries to Release 53 | uses: softprops/action-gh-release@v1 54 | if: startsWith(github.ref, 'refs/tags/') 55 | with: 56 | fail_on_unmatched_files: true 57 | prerelease: ${{ contains(steps.get_version.outputs.VERSION, 'alpha') || contains(steps.get_version.outputs.VERSION, 'beta') }} 58 | files: | 59 | bin/kube-trigger-packages-latest/* 60 | -------------------------------------------------------------------------------- /.github/workflows/release-image.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Push Images 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "v*" 9 | workflow_dispatch: { } 10 | 11 | env: 12 | USE_BUILD_CONTAINER: '1' 13 | 14 | jobs: 15 | kube-trigger: 16 | name: Release kube-trigger Images 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout Code 20 | uses: actions/checkout@v3 21 | 22 | - name: Setup Go Caches 23 | uses: actions/cache@v3 24 | with: 25 | path: | 26 | .go/gomodcache 27 | .go/gocache 28 | key: ${{ runner.os }}-gobuildcontainer-${{ hashFiles('**/go.sum') }} 29 | restore-keys: | 30 | ${{ runner.os }}-gobuildcontainer- 31 | 32 | - name: Login ghcr.io 33 | uses: docker/login-action@v2 34 | with: 35 | registry: ghcr.io 36 | username: ${{ github.actor }} 37 | password: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: Login docker.io 40 | uses: docker/login-action@v2 41 | with: 42 | registry: docker.io 43 | username: ${{ secrets.DOCKER_USERNAME }} 44 | password: ${{ secrets.DOCKER_PASSWORD }} 45 | 46 | - name: Set up QEMU 47 | uses: docker/setup-qemu-action@v2 48 | with: 49 | platforms: arm64 50 | 51 | - name: Set up Docker Buildx 52 | id: buildx 53 | uses: docker/setup-buildx-action@v2 54 | 55 | - name: Show Available Platforms 56 | run: echo ${{ steps.buildx.outputs.platforms }} 57 | 58 | - name: Show Make Variables 59 | run: make trigger-variables 60 | 61 | - name: Build and Push Images 62 | run: make trigger-all-container-push 63 | 64 | manager: 65 | name: Release manager Images 66 | runs-on: ubuntu-latest 67 | steps: 68 | - name: Checkout Code 69 | uses: actions/checkout@v3 70 | 71 | - name: Setup Go Caches 72 | uses: actions/cache@v3 73 | with: 74 | path: | 75 | .go/gomodcache 76 | .go/gocache 77 | key: ${{ runner.os }}-gobuildcontainer-${{ hashFiles('**/go.sum') }} 78 | restore-keys: | 79 | ${{ runner.os }}-gobuildcontainer- 80 | 81 | - name: Login ghcr.io 82 | uses: docker/login-action@v2 83 | with: 84 | registry: ghcr.io 85 | username: ${{ github.actor }} 86 | password: ${{ secrets.GITHUB_TOKEN }} 87 | 88 | - name: Login docker.io 89 | uses: docker/login-action@v2 90 | with: 91 | registry: docker.io 92 | username: ${{ secrets.DOCKER_USERNAME }} 93 | password: ${{ secrets.DOCKER_PASSWORD }} 94 | 95 | - name: Set up QEMU 96 | uses: docker/setup-qemu-action@v2 97 | with: 98 | platforms: arm64 99 | 100 | - name: Set up Docker Buildx 101 | id: buildx 102 | uses: docker/setup-buildx-action@v2 103 | 104 | - name: Show Available Platforms 105 | run: echo ${{ steps.buildx.outputs.platforms }} 106 | 107 | - name: Show Make Variables 108 | run: make manager-variables 109 | 110 | - name: Build and Push Images 111 | run: make manager-all-container-push 112 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yaml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release-* 8 | tags: 9 | - "v*" 10 | pull_request: 11 | branches: 12 | - main 13 | - release-* 14 | workflow_dispatch: { } 15 | 16 | env: 17 | GO_VERSION: '1.23' 18 | 19 | jobs: 20 | detect-noop: 21 | name: Detect No-op Changes 22 | runs-on: ubuntu-latest 23 | outputs: 24 | noop: ${{ steps.noop.outputs.should_skip }} 25 | steps: 26 | - name: Detect No-op Changes 27 | id: noop 28 | uses: fkirc/skip-duplicate-actions@v4.0.0 29 | with: 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | paths_ignore: '["**.md", "**.mdx", "**.png", "**.jpg", "**.svg"]' 32 | do_not_skip: '["workflow_dispatch", "schedule", "push"]' 33 | concurrent_skipping: false 34 | 35 | unit-test: 36 | name: Run Unit Tests and Integration Tests 37 | runs-on: ubuntu-latest 38 | needs: detect-noop 39 | if: needs.detect-noop.outputs.noop != 'true' 40 | steps: 41 | - name: Checkout Code 42 | uses: actions/checkout@v3 43 | 44 | - name: Setup Go 45 | uses: actions/setup-go@v3 46 | with: 47 | go-version: ${{ env.GO_VERSION }} 48 | cache: true 49 | 50 | - name: Cache envtest binaries 51 | uses: actions/cache@v3 52 | with: 53 | path: | 54 | ~/.local/share/kubebuilder-envtest 55 | key: ${{ runner.os }}-kubebuilder-envtest-${{ hashFiles('Makefile') }} 56 | restore-keys: | 57 | ${{ runner.os }}-kubebuilder-envtest- 58 | 59 | - name: Install ginkgo 60 | run: | 61 | go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo 62 | 63 | - name: Prepare minikube 64 | # For whatever reason, certain unit tests relies on k8s. 65 | # Although such reliance should be removed, in the meantime, 66 | # we set up a local k8s cluster to run such tests. 67 | # Use minikube simply because it comes with GitHub runner. 68 | run: minikube start 69 | 70 | - name: Run tests 71 | run: make test 72 | 73 | - name: Upload coverage report 74 | uses: codecov/codecov-action@v5 75 | with: 76 | token: ${{ secrets.CODECOV_TOKEN }} 77 | files: ./cover.out 78 | flags: unittests 79 | name: codecov-umbrella 80 | fail_ci_if_error: true 81 | verbose: true 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS leaves these everywhere on SMB shares 2 | ._* 3 | # macOS misc 4 | .DS_Store 5 | 6 | # Eclipse files 7 | .classpath 8 | .project 9 | .settings/** 10 | 11 | # Files generated by JetBrains IDEs, e.g. IntelliJ IDEA 12 | .idea/ 13 | *.iml 14 | 15 | # VSCode files 16 | .vscode 17 | 18 | # Emacs save files 19 | *~ 20 | \#*\# 21 | .\#* 22 | 23 | # Vim-related files 24 | [._]*.s[a-w][a-z] 25 | [._]s[a-w][a-z] 26 | *.un~ 27 | Session.vim 28 | .netrwhist 29 | 30 | # cscope-related files 31 | cscope.* 32 | 33 | # JUnit test output from ginkgo e2e tests 34 | /junit*.xml 35 | 36 | # Mercurial files 37 | **/.hg 38 | **/.hg* 39 | 40 | # Vagrant 41 | .vagrant 42 | 43 | # Binaries for programs and plugins 44 | *.exe 45 | *.exe~ 46 | *.dll 47 | *.so 48 | *.dylib 49 | 50 | # Test binary, built with `go test -c` 51 | *.test 52 | 53 | # Output of the go coverage tool, specifically when used with LiteIDE 54 | *.out 55 | 56 | # Build output 57 | bin 58 | .go 59 | 60 | # etcd 61 | default.etcd 62 | 63 | *.tmp 64 | 65 | vendor 66 | 67 | # Generated docker ignore 68 | # This is automatically generated by Make depending on which image you want to build. 69 | .dockerignore 70 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | kube-trigger follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /Dockerfile.in: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The KubeVela Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Note: the ${BIN} needs to be replaced with the actual binary, 16 | # otherwise it won't work. A valid Dockerfile will be generated 17 | # using this template. Refer to Makefile for how it can be done. 18 | 19 | # Default value is specified and will be overridden by the one in Makefile 20 | ARG BASE_IMAGE=gcr.io/distroless/static:nonroot 21 | 22 | FROM ${BASE_IMAGE} 23 | 24 | WORKDIR / 25 | 26 | ARG TARGETARCH 27 | ARG ARCH 28 | ARG TARGETOS 29 | ARG OS 30 | ARG VERSION 31 | 32 | COPY ${BIN}-${VERSION}-${TARGETOS:-${OS:-linux}}-${TARGETARCH:-${ARCH:-amd64}} /${BIN} 33 | 34 | ENTRYPOINT ["/${BIN}"] 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The KubeVela Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Setup make 16 | include makefiles/common.mk 17 | 18 | # ===== Common Targets for subprojects (trigger and manager) ====== 19 | 20 | SUBPROJS := $(patsubst %.mk, %, $(wildcard *.mk)) 21 | 22 | # Run `make TARGET' to run TARGET for both foo and bar. 23 | # For example, `make build' will build both foo and bar binaries. 24 | 25 | # Common targets for subprojects, will be executed on all subprojects 26 | TARGETS := build \ 27 | all-build \ 28 | package \ 29 | all-package \ 30 | container \ 31 | container-push \ 32 | all-container-push \ 33 | clean \ 34 | all-clean \ 35 | version \ 36 | imageversion \ 37 | binaryname \ 38 | variables 39 | 40 | # Default target, subprojects will be called with default target too 41 | all: $(addprefix mk-all.,$(SUBPROJS)); 42 | 43 | # Default target for subprojects. make foo / make bar 44 | $(foreach p,$(SUBPROJS),$(eval \ 45 | $(p): mk-all.$(p); \ 46 | )) 47 | 48 | # Run common targets on all subprojects 49 | $(foreach t,$(TARGETS),$(eval \ 50 | $(t): $(addprefix mk-$(t).,$(SUBPROJS)); \ 51 | )) 52 | 53 | # `shell' only needs to be executed once, not on every subproject 54 | shell: $(addprefix mk-shell.,$(word 1,$(SUBPROJS))); 55 | 56 | # `help' is handled separately to show targets in this file. 57 | help: # @HELP show general help message 58 | help: 59 | echo "GENERAL_TARGETS:" 60 | grep -E '^.*: *# *@HELP' $(firstword $(MAKEFILE_LIST)) \ 61 | | sed -E 's_.*.mk:__g' \ 62 | | awk ' \ 63 | BEGIN {FS = ": *# *@HELP"}; \ 64 | { printf " %-23s %s\n", $$1, $$2 }; \ 65 | ' 66 | echo 67 | echo "Please run 'make all-help' to see the full help message for all subprojects." 68 | 69 | all-help: # @HELP show help messages for all subjects 70 | all-help: $(addprefix mk-help.,$(SUBPROJS)) 71 | 72 | # Run `make TARGET' to run TARGET for both kube-trigger and manager. 73 | # For example, `make build' will build both kube-trigger and manager binaries. 74 | 75 | # Run `make SUBPROJ-TARGET' to run TARGET for SUBPROJ. 76 | # For example, `make trigger-build' will only build kube-trigger binary. 77 | 78 | # Run `make help' to see all available targets for subprojects. Similarly, 79 | # `make trigger-help' will show help for kube-trigger. 80 | 81 | # Targets to run on a specific subproject (-) 82 | $(foreach p,$(SUBPROJS),$(eval \ 83 | $(p)-%: mk-%.$(p); \ 84 | )) 85 | 86 | mk-%: 87 | echo "# make -f $(lastword $(subst ., ,$*)).mk $(firstword $(subst ., ,$*))" 88 | $(MAKE) -f $(lastword $(subst ., ,$*)).mk $(firstword $(subst ., ,$*)) 89 | 90 | # ===== General Targets ====== 91 | 92 | # Go packages to lint or test 93 | GOCODEDIR := ./api/... ./cmd/... ./controllers/... ./pkg/... 94 | 95 | generate: # @HELP generate code 96 | generate: $(addprefix mk-generate.,$(SUBPROJS)) 97 | 98 | lint: # @HELP lint code 99 | lint: 100 | build/lint.sh $(GOCODEDIR) 101 | 102 | checklicense: # @HELP check file header 103 | checklicense: 104 | hack/verify-boilerplate.sh 105 | 106 | svgformat: # @HELP format svg images, used in docs 107 | svgformat: 108 | hack/format-svg-image.sh 109 | 110 | reviewable: # @HELP check possible issues before committing code, make your code ready to review 111 | reviewable: generate checklicense lint 112 | go mod tidy 113 | 114 | test: # @HELP run tests 115 | test: envtest 116 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" \ 117 | go test -coverprofile=cover.out $(GOCODEDIR) 118 | 119 | checkdiff: generate 120 | git --no-pager diff 121 | if ! git diff --quiet; then \ 122 | echo "Please run 'make reviewable' to include all changes"; \ 123 | false; \ 124 | fi 125 | 126 | # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. 127 | ENVTEST_K8S_VERSION := 1.31 128 | ENVTEST ?= bin/setup-envtest 129 | 130 | envtest: 131 | mkdir -p bin 132 | [ -f $(ENVTEST) ] || GOBIN=$(PWD)/bin \ 133 | go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest 134 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: oam.dev 2 | layout: 3 | - go.kubebuilder.io/v3 4 | projectName: kube-trigger 5 | repo: github.com/kubevela/kube-trigger 6 | resources: 7 | - api: 8 | crdVersion: v1 9 | namespaced: true 10 | controller: true 11 | domain: oam.dev 12 | group: standard 13 | kind: TriggerInstance 14 | path: github.com/kubevela/kube-trigger/api/v1alpha1 15 | version: v1alpha1 16 | - api: 17 | crdVersion: v1 18 | namespaced: true 19 | controller: true 20 | domain: oam.dev 21 | group: standard 22 | kind: TriggerService 23 | path: github.com/kubevela/kube-trigger/api/v1alpha1 24 | version: v1alpha1 25 | version: "3" 26 | -------------------------------------------------------------------------------- /api/v1alpha1/eventlistener_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | runtime "k8s.io/apimachinery/pkg/runtime" 22 | ) 23 | 24 | // +kubebuilder:object:root=true 25 | 26 | // EventListener is the schema for the event listener. 27 | // +genclient 28 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 29 | // +kubebuilder:resource:shortName={el} 30 | type EventListener struct { 31 | metav1.TypeMeta `json:",inline"` 32 | metav1.ObjectMeta `json:"metadata,omitempty"` 33 | // +nullable 34 | Events []Event `json:"events,omitempty"` 35 | } 36 | 37 | // +kubebuilder:object:root=true 38 | 39 | // EventListenerList contains a list of EventListener. 40 | type EventListenerList struct { 41 | metav1.TypeMeta `json:",inline"` 42 | metav1.ListMeta `json:"metadata,omitempty"` 43 | Items []EventListener `json:"items"` 44 | } 45 | 46 | // Event is the schema for the event. 47 | type Event struct { 48 | // Resource is the resource that triggers the event. 49 | Resource EventResource `json:"resource"` 50 | // Timestamp is the time when the event is triggered. 51 | Timestamp metav1.Time `json:"timestamp"` 52 | // Type is the type of the event. 53 | Type string `json:"type,omitempty"` 54 | // +kubebuilder:pruning:PreserveUnknownFields 55 | // Data is the data of the event that carries. 56 | Data *runtime.RawExtension `json:"data,omitempty"` 57 | } 58 | 59 | // EventResource is the resource that triggers the event. 60 | type EventResource struct { 61 | APIVersion string `json:"apiVersion"` 62 | Kind string `json:"kind"` 63 | Name string `json:"name"` 64 | Namespace string `json:"namespace"` 65 | } 66 | 67 | func init() { 68 | SchemeBuilder.Register(&EventListener{}, &EventListenerList{}) 69 | } 70 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the standard v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=standard.oam.dev 20 | package v1alpha1 21 | 22 | import ( 23 | "reflect" 24 | 25 | "k8s.io/apimachinery/pkg/runtime/schema" 26 | "sigs.k8s.io/controller-runtime/pkg/scheme" 27 | ) 28 | 29 | var ( 30 | // GroupVersion is group version used to register these objects. 31 | GroupVersion = schema.GroupVersion{Group: "standard.oam.dev", Version: "v1alpha1"} 32 | 33 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 34 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 35 | 36 | // AddToScheme adds the types in this group-version to the given scheme. 37 | AddToScheme = SchemeBuilder.AddToScheme 38 | ) 39 | 40 | var ( 41 | // TriggerServiceKind is the kind of TriggerService. 42 | TriggerServiceKind = reflect.TypeOf(TriggerService{}).Name() 43 | // EventListenerKind is the kind of EventListener. 44 | EventListenerKind = reflect.TypeOf(EventListener{}).Name() 45 | ) 46 | -------------------------------------------------------------------------------- /api/v1alpha1/triggerservice_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | runtime "k8s.io/apimachinery/pkg/runtime" 22 | ) 23 | 24 | // TriggerServiceSpec defines the desired state of TriggerService. 25 | type TriggerServiceSpec struct { 26 | Worker *Worker `json:"worker,omitempty"` 27 | // Config for kube-trigger 28 | Triggers []TriggerMeta `json:"triggers"` 29 | } 30 | 31 | // +kubebuilder:object:root=true 32 | 33 | // TriggerService is the Schema for the kubetriggerconfigs API. 34 | // +kubebuilder:subresource:status 35 | // +genclient 36 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 37 | // +kubebuilder:resource:shortName={ts} 38 | type TriggerService struct { 39 | metav1.TypeMeta `json:",inline"` 40 | metav1.ObjectMeta `json:"metadata,omitempty"` 41 | 42 | Spec TriggerServiceSpec `json:"spec,omitempty"` 43 | } 44 | 45 | // +kubebuilder:object:root=true 46 | 47 | // TriggerServiceList contains a list of TriggerService. 48 | type TriggerServiceList struct { 49 | metav1.TypeMeta `json:",inline"` 50 | metav1.ListMeta `json:"metadata,omitempty"` 51 | Items []TriggerService `json:"items"` 52 | } 53 | 54 | // TriggerMeta is the meta data of a trigger. 55 | type TriggerMeta struct { 56 | Source Source `json:"source"` 57 | // +optional 58 | Filter string `json:"filter,omitempty"` 59 | Action ActionMeta `json:"action"` 60 | } 61 | 62 | // ActionMeta is what users type in their configurations, specifying what action 63 | // they want to use and what properties they provided. 64 | type ActionMeta struct { 65 | // Type is the type (identifier) of this action. 66 | Type string `json:"type"` 67 | 68 | // Properties are user-provided parameters. You should parse it yourself. 69 | // +kubebuilder:pruning:PreserveUnknownFields 70 | Properties *runtime.RawExtension `json:"properties,omitempty"` 71 | } 72 | 73 | // Source defines the Source of trigger. 74 | type Source struct { 75 | Type string `json:"type"` 76 | // +kubebuilder:pruning:PreserveUnknownFields 77 | Properties *runtime.RawExtension `json:"properties"` 78 | } 79 | 80 | // Worker defines the config of the worker 81 | type Worker struct { 82 | Template string `json:"template,omitempty"` 83 | // +kubebuilder:pruning:PreserveUnknownFields 84 | Properties *runtime.RawExtension `json:"properties,omitempty"` 85 | } 86 | 87 | const ( 88 | // SourceTypeResourceWatcher is the source type for K8sResourceWatcher. 89 | SourceTypeResourceWatcher string = "resource-watcher" 90 | // SourceTypeWebhookTrigger is the source type for WebhookTrigger. 91 | SourceTypeWebhookTrigger string = "webhook-trigger" 92 | ) 93 | 94 | func init() { 95 | SchemeBuilder.Register(&TriggerService{}, &TriggerServiceList{}) 96 | } 97 | -------------------------------------------------------------------------------- /build/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright 2022 The KubeVela Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | 20 | if [ -z "${OS:-}" ]; then 21 | echo "OS must be set" 22 | exit 1 23 | fi 24 | 25 | if [ -z "${ARCH:-}" ]; then 26 | echo "ARCH must be set" 27 | exit 1 28 | fi 29 | 30 | if [ -z "${VERSION:-}" ]; then 31 | echo "VERSION must be set" 32 | exit 1 33 | fi 34 | 35 | if [ -z "${OUTPUT:-}" ]; then 36 | echo "OUTPUT must be set" 37 | exit 1 38 | fi 39 | 40 | export CGO_ENABLED=0 41 | export GOARCH="${ARCH}" 42 | export GOOS="${OS}" 43 | export GO111MODULE=on 44 | export GOFLAGS="${GOFLAGS:-} -mod=mod " 45 | 46 | printf "# BUILD output: %s\ttarget: %s/%s\tversion: %s\n" \ 47 | "${OUTPUT}" "${OS}" "${ARCH}" "${VERSION}" 48 | 49 | printf "# BUILD building for " 50 | 51 | if [ "${DEBUG:-}" != "1" ]; then 52 | # release build 53 | # trim paths, disable symbols and DWARF. 54 | goasmflags="all=-trimpath=$(pwd)" 55 | gogcflags="all=-trimpath=$(pwd)" 56 | goldflags="-s -w" 57 | 58 | printf "release...\n" 59 | else 60 | # debug build 61 | # disable optimizations and inlining 62 | gogcflags="all=-N -l" 63 | goasmflags="" 64 | goldflags="" 65 | 66 | printf "debug...\n" 67 | fi 68 | 69 | # Set some version info. 70 | always_ldflags="-X $(go list -m)/pkg/version.Version=${VERSION}" 71 | 72 | go build \ 73 | -gcflags="${gogcflags}" \ 74 | -asmflags="${goasmflags}" \ 75 | -ldflags="${always_ldflags} ${goldflags}" \ 76 | -o "${OUTPUT}" \ 77 | "$@" 78 | -------------------------------------------------------------------------------- /build/lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2022 The KubeVela Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | GOLANGCI_VERSION="1.60.1" 22 | 23 | GOLANGCI="${GOLANGCI:-golangci-lint}" 24 | 25 | if [ -f "bin/golangci-lint" ]; then 26 | GOLANGCI="bin/golangci-lint" 27 | fi 28 | 29 | function print_install_help() { 30 | echo "Automatic installation failed, you can install golangci-lint v${GOLANGCI_VERSION} manually by running:" 31 | echo " curl -sSfL \"https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh\" | sh -s -- -b \"$(pwd)/bin\" v${GOLANGCI_VERSION}" 32 | echo "It will be installed to \"$(pwd)/bin/golangci-lint\" so that it won't interfere with existing versions (if any)." 33 | exit 1 34 | } 35 | 36 | function install_golangci() { 37 | echo "Installing golangci-lint v${GOLANGCI_VERSION} ..." 38 | echo "It will be installed to \"$(pwd)/bin/golangci-lint\" so that it won't interfere with existing versions (if any)." 39 | curl -sSfL "https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh" | 40 | sh -s -- -b "$(pwd)/bin" v${GOLANGCI_VERSION} || print_install_help 41 | } 42 | 43 | if ! ${GOLANGCI} version >/dev/null 2>&1; then 44 | echo "You don't have golangci-lint installed." 2>&1 45 | install_golangci 46 | $0 "$@" 47 | exit 48 | fi 49 | 50 | CURRENT_GOLANGCI_VERSION="$(${GOLANGCI} version 2>&1)" 51 | CURRENT_GOLANGCI_VERSION="${CURRENT_GOLANGCI_VERSION#*version }" 52 | CURRENT_GOLANGCI_VERSION="${CURRENT_GOLANGCI_VERSION% built*}" 53 | 54 | if [ "${CURRENT_GOLANGCI_VERSION}" != "${GOLANGCI_VERSION}" ]; then 55 | echo "You have golangci-lint v${CURRENT_GOLANGCI_VERSION} installed, but we want v${GOLANGCI_VERSION}" 1>&2 56 | install_golangci 57 | $0 "$@" 58 | exit 59 | fi 60 | 61 | echo "# Running golangci-lint v${CURRENT_GOLANGCI_VERSION}..." 62 | 63 | ${GOLANGCI} run "$@" 64 | -------------------------------------------------------------------------------- /cmd/kubetrigger/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "os" 21 | 22 | "github.com/kubevela/kube-trigger/pkg/cmd" 23 | ) 24 | 25 | func main() { 26 | if err := cmd.NewCommand().Execute(); err != nil { 27 | os.Exit(1) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cmd/manager/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | 23 | oamv1alpha1 "github.com/kubevela/pkg/apis/oam/v1alpha1" 24 | "k8s.io/apimachinery/pkg/runtime" 25 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 26 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 27 | _ "k8s.io/client-go/plugin/pkg/client/auth" 28 | ctrl "sigs.k8s.io/controller-runtime" 29 | "sigs.k8s.io/controller-runtime/pkg/healthz" 30 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 31 | 32 | standardv1alpha1 "github.com/kubevela/kube-trigger/api/v1alpha1" 33 | "github.com/kubevela/kube-trigger/controllers/triggerservice" 34 | ) 35 | 36 | var ( 37 | scheme = runtime.NewScheme() 38 | setupLog = ctrl.Log.WithName("setup") 39 | ) 40 | 41 | //nolint:gochecknoinits 42 | func init() { 43 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 44 | 45 | utilruntime.Must(standardv1alpha1.AddToScheme(scheme)) 46 | utilruntime.Must(oamv1alpha1.AddToScheme(scheme)) 47 | //+kubebuilder:scaffold:scheme 48 | } 49 | 50 | func main() { 51 | var ( 52 | metricsAddr string 53 | enableLeaderElection bool 54 | probeAddr string 55 | ) 56 | 57 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 58 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 59 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 60 | "Enable leader election for controller manager. "+ 61 | "Enabling this will ensure there is only one active controller manager.") 62 | 63 | opts := zap.Options{ 64 | Development: true, 65 | } 66 | opts.BindFlags(flag.CommandLine) 67 | flag.Parse() 68 | 69 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 70 | 71 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 72 | Scheme: scheme, 73 | HealthProbeBindAddress: probeAddr, 74 | LeaderElection: enableLeaderElection, 75 | LeaderElectionID: "f3cd865e.oam.dev", 76 | // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily 77 | // when the Manager ends. This requires the binary to immediately end when the 78 | // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly 79 | // speeds up voluntary leader transitions as the new leader don't have to wait 80 | // LeaseDuration time first. 81 | // 82 | // In the default scaffold provided, the program ends immediately after 83 | // the manager stops, so would be fine to enable this option. However, 84 | // if you are doing or is intended to do any operation such as perform cleanups 85 | // after the manager stops then its usage might be unsafe. 86 | // LeaderElectionReleaseOnCancel: true, 87 | }) 88 | if err != nil { 89 | setupLog.Error(err, "unable to start manager") 90 | os.Exit(1) 91 | } 92 | 93 | if err = (&triggerservice.Reconciler{ 94 | Client: mgr.GetClient(), 95 | Scheme: mgr.GetScheme(), 96 | }).SetupWithManager(mgr); err != nil { 97 | setupLog.Error(err, "unable to create controller", "controller", "TriggerService") 98 | os.Exit(1) 99 | } 100 | //+kubebuilder:scaffold:builder 101 | 102 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 103 | setupLog.Error(err, "unable to set up health check") 104 | os.Exit(1) 105 | } 106 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 107 | setupLog.Error(err, "unable to set up ready check") 108 | os.Exit(1) 109 | } 110 | 111 | setupLog.Info("starting manager") 112 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 113 | setupLog.Error(err, "problem running manager") 114 | os.Exit(1) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 0.1% 6 | patch: 7 | default: 8 | target: 60% 9 | ignore: 10 | - "test/**" 11 | - "hack/**" 12 | - "**/zz_generated.deepcopy.go" 13 | -------------------------------------------------------------------------------- /config/crd/core.oam.dev_definitions.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.9.2 7 | creationTimestamp: null 8 | name: definitions.core.oam.dev 9 | spec: 10 | group: core.oam.dev 11 | names: 12 | kind: Definition 13 | listKind: DefinitionList 14 | plural: definitions 15 | shortNames: 16 | - def 17 | singular: definition 18 | scope: Namespaced 19 | versions: 20 | - additionalPrinterColumns: 21 | - jsonPath: .spec.type 22 | name: TYPE 23 | type: string 24 | name: v1alpha1 25 | schema: 26 | openAPIV3Schema: 27 | description: Definition is a internal storage for KubeVela definitions, it 28 | will never be exposed directly to end users. It will just like a configmap 29 | as internal usage, using a standalone CRD can help us optimize the efficiency 30 | for informer. 31 | properties: 32 | apiVersion: 33 | description: 'APIVersion defines the versioned schema of this representation 34 | of an object. Servers should convert recognized schemas to the latest 35 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 36 | type: string 37 | kind: 38 | description: 'Kind is a string value representing the REST resource this 39 | object represents. Servers may infer this from the endpoint the client 40 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 41 | type: string 42 | metadata: 43 | type: object 44 | spec: 45 | description: DefinitionSpec is the spec for definition 46 | properties: 47 | attributes: 48 | nullable: true 49 | type: object 50 | templates: 51 | additionalProperties: 52 | type: string 53 | type: object 54 | type: 55 | type: string 56 | required: 57 | - templates 58 | - type 59 | type: object 60 | required: 61 | - spec 62 | type: object 63 | served: true 64 | storage: true 65 | subresources: {} 66 | -------------------------------------------------------------------------------- /config/crd/standard.oam.dev_eventlisteners.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.9.0 7 | creationTimestamp: null 8 | name: eventlisteners.standard.oam.dev 9 | spec: 10 | group: standard.oam.dev 11 | names: 12 | kind: EventListener 13 | listKind: EventListenerList 14 | plural: eventlisteners 15 | shortNames: 16 | - el 17 | singular: eventlistener 18 | scope: Namespaced 19 | versions: 20 | - name: v1alpha1 21 | schema: 22 | openAPIV3Schema: 23 | description: EventListener is the schema for the event listener. 24 | properties: 25 | apiVersion: 26 | description: 'APIVersion defines the versioned schema of this representation 27 | of an object. Servers should convert recognized schemas to the latest 28 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 29 | type: string 30 | events: 31 | items: 32 | description: Event is the schema for the event. 33 | properties: 34 | data: 35 | description: Data is the data of the event that carries. 36 | type: object 37 | x-kubernetes-preserve-unknown-fields: true 38 | resource: 39 | description: Resource is the resource that triggers the event. 40 | properties: 41 | apiVersion: 42 | type: string 43 | kind: 44 | type: string 45 | name: 46 | type: string 47 | namespace: 48 | type: string 49 | required: 50 | - apiVersion 51 | - kind 52 | - name 53 | - namespace 54 | type: object 55 | timestamp: 56 | description: Timestamp is the time when the event is triggered. 57 | format: date-time 58 | type: string 59 | type: 60 | description: Type is the type of the event. 61 | type: string 62 | required: 63 | - resource 64 | - timestamp 65 | type: object 66 | nullable: true 67 | type: array 68 | kind: 69 | description: 'Kind is a string value representing the REST resource this 70 | object represents. Servers may infer this from the endpoint the client 71 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 72 | type: string 73 | metadata: 74 | type: object 75 | type: object 76 | served: true 77 | storage: true 78 | -------------------------------------------------------------------------------- /config/crd/standard.oam.dev_triggerservices.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.9.0 7 | creationTimestamp: null 8 | name: triggerservices.standard.oam.dev 9 | spec: 10 | group: standard.oam.dev 11 | names: 12 | kind: TriggerService 13 | listKind: TriggerServiceList 14 | plural: triggerservices 15 | shortNames: 16 | - ts 17 | singular: triggerservice 18 | scope: Namespaced 19 | versions: 20 | - name: v1alpha1 21 | schema: 22 | openAPIV3Schema: 23 | description: TriggerService is the Schema for the kubetriggerconfigs API. 24 | properties: 25 | apiVersion: 26 | description: 'APIVersion defines the versioned schema of this representation 27 | of an object. Servers should convert recognized schemas to the latest 28 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 29 | type: string 30 | kind: 31 | description: 'Kind is a string value representing the REST resource this 32 | object represents. Servers may infer this from the endpoint the client 33 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 34 | type: string 35 | metadata: 36 | type: object 37 | spec: 38 | description: TriggerServiceSpec defines the desired state of TriggerService. 39 | properties: 40 | triggers: 41 | description: Config for kube-trigger 42 | items: 43 | description: TriggerMeta is the meta data of a trigger. 44 | properties: 45 | action: 46 | description: ActionMeta is what users type in their configurations, 47 | specifying what action they want to use and what properties 48 | they provided. 49 | properties: 50 | properties: 51 | description: Properties are user-provided parameters. You 52 | should parse it yourself. 53 | type: object 54 | x-kubernetes-preserve-unknown-fields: true 55 | type: 56 | description: Type is the type (identifier) of this action. 57 | type: string 58 | required: 59 | - type 60 | type: object 61 | filter: 62 | type: string 63 | source: 64 | description: Source defines the Source of trigger. 65 | properties: 66 | properties: 67 | type: object 68 | x-kubernetes-preserve-unknown-fields: true 69 | type: 70 | type: string 71 | required: 72 | - properties 73 | - type 74 | type: object 75 | required: 76 | - action 77 | - source 78 | type: object 79 | type: array 80 | worker: 81 | description: Worker defines the config of the worker 82 | properties: 83 | properties: 84 | type: object 85 | x-kubernetes-preserve-unknown-fields: true 86 | template: 87 | type: string 88 | type: object 89 | required: 90 | - triggers 91 | type: object 92 | type: object 93 | served: true 94 | storage: true 95 | subresources: 96 | status: {} 97 | -------------------------------------------------------------------------------- /config/definition/bump-application-revision.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha1 2 | kind: Definition 3 | metadata: 4 | name: trigger-action-bump-application-revision 5 | namespace: vela-system 6 | spec: 7 | type: trigger-action 8 | templates: 9 | main.cue: | 10 | import ( 11 | "vela/kube" 12 | "strconv" 13 | ) 14 | 15 | items: *[] | [{...}] 16 | 17 | if parameter.nameSelector != _|_ if parameter.nameSelector.matchingLabels != _|_ { 18 | list: kube.#List & { 19 | $params: { 20 | resource: { 21 | apiVersion: "core.oam.dev/v1beta1" 22 | kind: "Application" 23 | } 24 | filter: { 25 | namespace: parameter.namespace 26 | if parameter.nameSelector.matchingLabels != _|_ { 27 | matchingLabels: parameter.nameSelector.matchingLabels 28 | } 29 | } 30 | } 31 | } 32 | items: list.$returns.items 33 | } 34 | 35 | if parameter.nameSelector != _|_ if parameter.nameSelector.fromLabel != _|_ { 36 | get: kube.#Get & { 37 | $params: { 38 | resource: { 39 | apiVersion: "core.oam.dev/v1beta1" 40 | kind: "Application" 41 | metadata: { 42 | name: context.data.metadata.labels[parameter.nameSelector.fromLabel] 43 | namespace: parameter.namespace 44 | } 45 | } 46 | } 47 | } 48 | items: [{get.$returns}] 49 | } 50 | 51 | if parameter.nameSelector == _|_ { 52 | get: kube.#Get & { 53 | $params: { 54 | resource: { 55 | apiVersion: "core.oam.dev/v1beta1" 56 | kind: "Application" 57 | metadata: { 58 | name: context.data.metadata.name 59 | namespace: parameter.namespace 60 | } 61 | } 62 | } 63 | } 64 | items: [{get.$returns}] 65 | } 66 | 67 | for index, item in items { 68 | if item.metadata.annotations["app.oam.dev/publishVersion"] != _|_ { 69 | if strconv.ParseInt(item.metadata.annotations["app.oam.dev/publishVersion"], 10, 64) != _|_ { 70 | "patch-\(index)": kube.#Patch & { 71 | $params: { 72 | resource: { 73 | apiVersion: "core.oam.dev/v1beta1" 74 | kind: "Application" 75 | metadata: { 76 | name: item.metadata.name 77 | namespace: item.metadata.namespace 78 | } 79 | } 80 | patch: { 81 | type: "merge" 82 | data: { 83 | metadata: { 84 | annotations: { 85 | "app.oam.dev/publishVersion": strconv.FormatInt(strconv.ParseInt(item.metadata.annotations["app.oam.dev/publishVersion"], 10, 64)+1, 10) 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | parameter: { 97 | // +usage=The namespace to list the resources 98 | namespace: *context.data.metadata.namespace | string 99 | // +usage=The name selector to select the app to bump revision 100 | nameSelector?: close({ 101 | fromLabel?: string 102 | }) | close({ 103 | // +usage=The label selector to filter the resources 104 | matchingLabels?: {...} 105 | }) 106 | } 107 | -------------------------------------------------------------------------------- /config/definition/create-event-listener.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha1 2 | kind: Definition 3 | metadata: 4 | name: trigger-action-create-event-listener 5 | namespace: vela-system 6 | spec: 7 | type: trigger-action 8 | templates: 9 | main.cue: | 10 | import ( 11 | "vela/kube" 12 | ) 13 | 14 | apply: kube.#Apply & { 15 | $params: { 16 | resource: { 17 | apiVersion: "standard.oam.dev/v1alpha1" 18 | kind: "EventListener" 19 | metadata: { 20 | name: context.data.metadata.name 21 | namespace: context.data.metadata.namespace 22 | if context.data.metadata.labels != _|_ { 23 | labels: context.data.metadata.labels 24 | } 25 | ownerReferences: [ 26 | { 27 | apiVersion: context.data.apiVersion 28 | kind: context.data.kind 29 | name: context.data.metadata.name 30 | uid: context.data.metadata.uid 31 | controller: true 32 | }, 33 | ] 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /config/definition/default.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha1 2 | kind: Definition 3 | metadata: 4 | name: trigger-worker-default 5 | namespace: vela-system 6 | spec: 7 | type: trigger-worker 8 | templates: 9 | main.cue: | 10 | // deployment will be renderd and applied to the cluster 11 | deployment: { 12 | apiVersion: "apps/v1" 13 | kind: "Deployment" 14 | metadata: { 15 | name: parameter.name 16 | namespace: triggerService.namespace 17 | labels: { 18 | "app.kubernetes.io/name": parameter.name 19 | "trigger.oam.dev/name": triggerService.name 20 | if parameter.labels != _|_ { 21 | parameter.labels 22 | } 23 | } 24 | if parameter.annotations != _|_ { 25 | annotations: parameter.annotations 26 | } 27 | } 28 | spec: { 29 | selector: { 30 | matchLabels: { 31 | "app.kubernetes.io/name": parameter.name 32 | "trigger.oam.dev/name": triggerService.name 33 | } 34 | } 35 | replicas: 1 36 | template: { 37 | metadata: { 38 | labels: { 39 | "app.kubernetes.io/name": parameter.name 40 | "trigger.oam.dev/name": triggerService.name 41 | if parameter.labels != _|_ { 42 | parameter.labels 43 | } 44 | } 45 | if parameter.annotations != _|_ { 46 | annotations: parameter.annotations 47 | } 48 | } 49 | spec: { 50 | securityContext: { 51 | runAsNonRoot: true 52 | seccompProfile: { 53 | type: "RuntimeDefault" 54 | } 55 | } 56 | containers: [{ 57 | workingDir: "/" 58 | args: [ 59 | "-c=/etc/kube-trigger", 60 | "--log-level=debug", 61 | "--max-retry=\(parameter.config.maxRetry)", 62 | "--retry-delay=\(parameter.config.retryDelay)", 63 | "--per-worker-qps=\(parameter.config.perWorkerQPS)", 64 | "--queue-size=\(parameter.config.queueSize)", 65 | "--timeout=\(parameter.config.timeout)", 66 | "--workers=\(parameter.config.workers)", 67 | "--log-level=\(parameter.config.logLevel)", 68 | "--multi-cluster-config-type=\(parameter.config.multiClusterConfigType)", 69 | ] 70 | image: parameter.image 71 | name: "kube-trigger" 72 | securityContext: { 73 | allowPrivilegeEscalation: false 74 | capabilities: { 75 | drop: ["ALL"] 76 | } 77 | } 78 | resources: { 79 | limits: { 80 | cpu: parameter.resource.cpu.limits 81 | memory: parameter.resource.memory.limits 82 | } 83 | requests: { 84 | cpu: parameter.resource.cpu.requests 85 | memory: parameter.resource.memory.requests 86 | } 87 | } 88 | volumeMounts: [{ 89 | mountPath: "/etc/kube-trigger" 90 | name: "config" 91 | }] 92 | }] 93 | serviceAccountName: parameter.serviceAccount 94 | terminationGracePeriodSeconds: 10 95 | volumes: [{ 96 | name: "config" 97 | configMap: { 98 | name: triggerService.name 99 | } 100 | }] 101 | if parameter.imagePullSecrets != _|_ { 102 | imagePullSecrets: parameter.imagePullSecrets 103 | } 104 | if parameter.hostAliases != _|_ { 105 | hostAliases: parameter.hostAliases 106 | } 107 | } 108 | } 109 | } 110 | } 111 | 112 | triggerService: { 113 | name: string 114 | namespace: *"vela-system" | string 115 | } 116 | 117 | parameter: { 118 | name: *triggerService.name | string 119 | labels?: [string]: string 120 | annotations?: [string]: string 121 | image: *"oamdev/kube-trigger:latest" | string 122 | resource: { 123 | cpu: { 124 | requests: *"10m" | string 125 | limits: *"500m" | string 126 | } 127 | memory: { 128 | requests: *"64Mi" | string 129 | limits: *"128Mi" | string 130 | } 131 | } 132 | serviceAccount: *"kube-trigger" | string 133 | config: { 134 | maxRetry: *5 | int 135 | retryDelay: *2 | int 136 | perWorkerQPS: *2 | int 137 | queueSize: *50 | int 138 | timeout: *10 | int 139 | workers: *4 | int 140 | logLevel: *"info" | "debug" 141 | multiClusterConfigType: *"cluster-gateway" | "cluster-gateway-secret" 142 | } 143 | imagePullSecrets?: [...{ 144 | name: string 145 | }] 146 | hostAliases?: [...{ 147 | ip?: string 148 | hostNames?: [...string] 149 | }] 150 | } 151 | -------------------------------------------------------------------------------- /config/definition/patch-resource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha1 2 | kind: Definition 3 | metadata: 4 | name: trigger-action-patch-resource 5 | namespace: vela-system 6 | spec: 7 | type: trigger-action 8 | templates: 9 | main.cue: | 10 | import ( 11 | "vela/kube" 12 | ) 13 | 14 | patchObject: kube.#Patch & { 15 | $params: { 16 | resource: { 17 | apiVersion: parameter.resource.apiVersion 18 | kind: parameter.resource.kind 19 | metadata: { 20 | name: parameter.resource.name 21 | namespace: parameter.resource.namespace 22 | } 23 | } 24 | patch: parameter.patch 25 | } 26 | } 27 | 28 | parameter: { 29 | // +usage=The resource to patch 30 | resource: { 31 | // +usage=The api version of the resource 32 | apiVersion: string 33 | // +usage=The kind of the resource 34 | kind: string 35 | // +usage=The metadata of the resource 36 | metadata: { 37 | // +usage=The name of the resource 38 | name: string 39 | // +usage=The namespace of the resource 40 | namespace: *"default" | string 41 | } 42 | } 43 | // +usage=The patch to be applied to the resource with kubernetes patch 44 | patch: *{ 45 | // +usage=The type of patch being provided 46 | type: "merge" 47 | data: {...} 48 | } | { 49 | // +usage=The type of patch being provided 50 | type: "json" 51 | data: [{...}] 52 | } | { 53 | // +usage=The type of patch being provided 54 | type: "strategic" 55 | data: {...} 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /config/definition/record-event.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha1 2 | kind: Definition 3 | metadata: 4 | name: trigger-action-record-event 5 | namespace: vela-system 6 | spec: 7 | type: trigger-action 8 | templates: 9 | main.cue: | 10 | import ( 11 | "vela/kube" 12 | "strconv" 13 | ) 14 | meta: { 15 | apiVersion: "standard.oam.dev/v1alpha1" 16 | kind: "EventListener" 17 | metadata: { 18 | if parameter.nameSelector != _|_ { 19 | name: context.data.metadata.labels[parameter.nameSelector.fromLabel] 20 | } 21 | if parameter.nameSelector == _|_ { 22 | name: context.data.metadata.name 23 | } 24 | if parameter.namespaceSelector != _|_ { 25 | namespace: context.data.metadata.labels[parameter.namespaceSelector.fromLabel] 26 | } 27 | if parameter.namespaceSelector == _|_ { 28 | namespace: context.data.metadata.namespace 29 | } 30 | } 31 | } 32 | 33 | get: kube.#Get & { 34 | $params: { 35 | resource: meta 36 | } 37 | } 38 | 39 | originalEvents: *[] | [...] 40 | 41 | if get.$returns.events != _|_ { 42 | originalEvents: get.$returns.events 43 | } 44 | 45 | events: originalEvents + [{ 46 | resource: { 47 | apiVersion: context.data.apiVersion 48 | kind: context.data.kind 49 | name: context.data.metadata.name 50 | namespace: context.data.metadata.namespace 51 | } 52 | type: context.event.type 53 | timestamp: context.timestamp 54 | }] 55 | 56 | filter: *events | [...] 57 | 58 | if len(events) > 10 { 59 | filter: events[len(events)-10:] 60 | } 61 | 62 | "patch": kube.#Patch & { 63 | $params: { 64 | resource: meta 65 | patch: { 66 | type: "merge" 67 | data: { 68 | events: filter 69 | } 70 | } 71 | } 72 | } 73 | 74 | parameter: { 75 | nameSelector?: { 76 | fromLabel: string 77 | } 78 | namespaceSelector?: { 79 | fromLabel: string 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /config/definition/task.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha1 2 | kind: Definition 3 | metadata: 4 | name: task 5 | namespace: vela-system 6 | spec: 7 | type: trigger-action 8 | templates: 9 | # create a Job resource as an action in the same namespace as the source (by default) 10 | main.cue: | 11 | import ( 12 | "vela/kube" 13 | ) 14 | 15 | apply: kube.#Apply & { 16 | $params: { 17 | resource: { 18 | apiVersion: "batch/v1" 19 | kind: "Job" 20 | metadata: { 21 | name: parameter.name 22 | namespace: parameter.namespace 23 | if context.data.metadata.labels != _|_ { 24 | labels: context.data.metadata.labels 25 | } 26 | ownerReferences: [ 27 | { 28 | apiVersion: context.data.apiVersion 29 | kind: context.data.kind 30 | name: context.data.metadata.name 31 | uid: context.data.metadata.uid 32 | controller: true 33 | }, 34 | ] 35 | } 36 | 37 | spec: { 38 | if parameter.ttlSecondsAfterFinished != _|_ { 39 | ttlSecondsAfterFinished: parameter.ttlSecondsAfterFinished 40 | } 41 | 42 | template: { 43 | spec: { 44 | restartPolicy: parameter.restart 45 | containers: [{ 46 | name: parameter.name 47 | image: parameter.image 48 | command: parameter.cmd 49 | 50 | if parameter.env == _|_ { 51 | env: [{ 52 | name: "SOURCE_NAME" 53 | value: context.data.metadata.name 54 | },{ 55 | name: "SOURCE_NAMESPACE" 56 | value: context.data.metadata.namespace 57 | }] 58 | } 59 | 60 | if parameter.env != _|_ { 61 | env: [{ 62 | name: "SOURCE_NAME" 63 | value: context.data.metadata.name 64 | },{ 65 | name: "SOURCE_NAMESPACE" 66 | value: context.data.metadata.namespace 67 | }] + parameter.env 68 | } 69 | }] 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | parameter: { 78 | // +usage=The image to run the job container on 79 | image: string 80 | 81 | // +usage=Name of the cron job 82 | name: *context.data.metadata.name | string 83 | 84 | // +usage=The namespace to create the Job in 85 | namespace: *context.data.metadata.namespace | string 86 | 87 | // +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never. 88 | restart: *"Never" | string 89 | 90 | // +usage=Number of seconds to wait before a successfully completed job is cleaned up 91 | ttlSecondsAfterFinished?: uint 92 | 93 | // +usage=Commands to run in the container 94 | cmd: [...string] 95 | 96 | // +usage=Define evironment variables for the Job container 97 | env?: [...{ 98 | // +usage=Name of the environment variable 99 | name: string 100 | // +usage=Value of the environment variable 101 | value: string 102 | }] 103 | } -------------------------------------------------------------------------------- /config/manager/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: kube-trigger-manager 5 | namespace: vela-system 6 | labels: 7 | control-plane: kube-trigger-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: kube-trigger-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: kube-trigger-manager 19 | spec: 20 | securityContext: 21 | runAsNonRoot: true 22 | seccompProfile: 23 | type: RuntimeDefault 24 | containers: 25 | - command: 26 | - /manager 27 | args: 28 | - --leader-elect 29 | image: oamdev/kube-trigger-manager:latest 30 | name: manager 31 | securityContext: 32 | allowPrivilegeEscalation: false 33 | capabilities: 34 | drop: 35 | - "ALL" 36 | livenessProbe: 37 | httpGet: 38 | path: /healthz 39 | port: 8081 40 | initialDelaySeconds: 15 41 | periodSeconds: 20 42 | readinessProbe: 43 | httpGet: 44 | path: /readyz 45 | port: 8081 46 | initialDelaySeconds: 5 47 | periodSeconds: 10 48 | # TODO(user): Configure the resources accordingly based on the project requirements. 49 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 50 | resources: 51 | limits: 52 | cpu: 500m 53 | memory: 128Mi 54 | requests: 55 | cpu: 10m 56 | memory: 64Mi 57 | serviceAccountName: kube-trigger-manager 58 | terminationGracePeriodSeconds: 10 59 | -------------------------------------------------------------------------------- /config/manager/ns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: vela-system 5 | -------------------------------------------------------------------------------- /config/manager/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: kube-trigger-manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | # TODO: use a stricter permission 9 | name: cluster-admin 10 | subjects: 11 | - kind: ServiceAccount 12 | name: kube-trigger-manager 13 | namespace: vela-system 14 | 15 | --- 16 | 17 | apiVersion: v1 18 | kind: ServiceAccount 19 | metadata: 20 | name: kube-trigger-manager 21 | namespace: vela-system 22 | 23 | --- 24 | 25 | # Leader Election RBAC 26 | 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: Role 29 | metadata: 30 | name: leader-election-role 31 | namespace: vela-system 32 | rules: 33 | - apiGroups: 34 | - "" 35 | resources: 36 | - configmaps 37 | verbs: 38 | - get 39 | - list 40 | - watch 41 | - create 42 | - update 43 | - patch 44 | - delete 45 | - apiGroups: 46 | - coordination.k8s.io 47 | resources: 48 | - leases 49 | verbs: 50 | - get 51 | - list 52 | - watch 53 | - create 54 | - update 55 | - patch 56 | - delete 57 | - apiGroups: 58 | - "" 59 | resources: 60 | - events 61 | verbs: 62 | - create 63 | - patch 64 | 65 | --- 66 | 67 | apiVersion: rbac.authorization.k8s.io/v1 68 | kind: RoleBinding 69 | metadata: 70 | name: leader-election-rolebinding 71 | namespace: vela-system 72 | roleRef: 73 | apiGroup: rbac.authorization.k8s.io 74 | kind: Role 75 | name: leader-election-role 76 | subjects: 77 | - kind: ServiceAccount 78 | name: kube-trigger-manager 79 | namespace: vela-system 80 | 81 | -------------------------------------------------------------------------------- /config/manager/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | creationTimestamp: null 6 | name: kube-trigger-manager-role 7 | rules: 8 | - apiGroups: 9 | - "" 10 | resources: 11 | - configmaps 12 | verbs: 13 | - get 14 | - update 15 | - apiGroups: 16 | - standard.oam.dev 17 | resources: 18 | - kubetriggerconfigs 19 | verbs: 20 | - create 21 | - delete 22 | - get 23 | - list 24 | - patch 25 | - update 26 | - watch 27 | - apiGroups: 28 | - standard.oam.dev 29 | resources: 30 | - kubetriggerconfigs/finalizers 31 | verbs: 32 | - update 33 | - apiGroups: 34 | - standard.oam.dev 35 | resources: 36 | - kubetriggerconfigs/status 37 | verbs: 38 | - get 39 | - patch 40 | - update 41 | - apiGroups: 42 | - standard.oam.dev 43 | resources: 44 | - kubetriggers 45 | verbs: 46 | - get 47 | - list 48 | - apiGroups: 49 | - standard.oam.dev 50 | resources: 51 | - kubetriggers/status 52 | verbs: 53 | - get 54 | -------------------------------------------------------------------------------- /config/manager/trigger-role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: kube-trigger 6 | namespace: vela-system 7 | --- 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRoleBinding 10 | metadata: 11 | name: kube-trigger 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | # We give it the highest privilege just to make sure everyone's config will work. 15 | # You SHOULD use stricter privileges that suits your needs for security reasons. 16 | kind: ClusterRole 17 | name: cluster-admin 18 | subjects: 19 | - kind: ServiceAccount 20 | name: kube-trigger 21 | namespace: vela-system -------------------------------------------------------------------------------- /controllers/utils/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "k8s.io/apimachinery/pkg/types" 22 | pointer "k8s.io/utils/ptr" 23 | 24 | standardv1alpha1 "github.com/kubevela/kube-trigger/api/v1alpha1" 25 | ) 26 | 27 | // SetOwnerReference set owner reference for trigger service. 28 | func SetOwnerReference(obj metav1.Object, ts *standardv1alpha1.TriggerService) { 29 | obj.SetOwnerReferences([]metav1.OwnerReference{{ 30 | APIVersion: standardv1alpha1.GroupVersion.String(), 31 | Kind: standardv1alpha1.TriggerServiceKind, 32 | Name: ts.Name, 33 | UID: ts.GetUID(), 34 | BlockOwnerDeletion: pointer.To(true), 35 | }}) 36 | } 37 | 38 | // GetNamespacedName get namespaced name from resource. 39 | func GetNamespacedName(obj metav1.Object) types.NamespacedName { 40 | return types.NamespacedName{ 41 | Namespace: obj.GetNamespace(), 42 | Name: obj.GetName(), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /docs/img/overview/cli-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/img/overview/clickhouse-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/img/overview/cron-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/img/overview/dingtalk-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/img/overview/grafana-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/img/overview/kubevela-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/img/overview/loki-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /docs/img/overview/mail-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/img/overview/oam-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ohm 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/img/overview/prometheus-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/img/overview/slack-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/img/overview/telegram-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/img/overview/webhook-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | kube-trigger can run as standalone or in-cluster. Let's use a real use-case as an exmaple ( 4 | see [#4418](https://github.com/kubevela/kubevela/issues/4418)). TL;DR, the user want the Application to be automatically 5 | updated whenever the ConfigMaps that are referenced by `ref-objects` are updated. 6 | 7 | ## Prerequisites 8 | 9 | - Install [KubeVela](https://kubevela.net/docs/install) in your cluster 10 | - Enable the `kube-trigger` addon 11 | ```shell 12 | vela addon enable kube-trigger 13 | ``` 14 | 15 | ## What we want to achieve? 16 | 17 | - use a `resource-watcher` Source to listen to update events of ConfigMaps 18 | - use a `cue-validator` Filter to only keep the ConfigMaps that we are interested in 19 | - trigger an `bump-application-revision` Action to update Application. 20 | 21 | As a result: 22 | 23 | - Once any of the two ConfigMaps are updated, both Applications will be updated as well. 24 | 25 | ## Try out 26 | 27 | 1. **Apply sample resources** 28 | 29 | Apply `sample.yaml` to create 2 Applications and 2 ConfigMaps in the default namespace. The changes in 2 ConfigMaps will 30 | trigger 2 Application updates. 31 | 32 | ```shell 33 | kubectl apply -f examples/sample.yaml 34 | ``` 35 | 36 | 2. **Run kube-trigger** 37 | 38 | Choose your preferred way: standalone (recommended for quick testing) or in-cluster 39 | 40 | Standalone: 41 | 42 | ```shell 43 | # Download kube-trigger binaries from releases first 44 | ./kube-trigger --config sampleconf-bump-app.yaml 45 | ``` 46 | 47 | In-Cluster: 48 | 49 | ```shell 50 | kubectl apply -f config/crd/ 51 | kubectl apply -f config/definition/ 52 | kubectl apply -f config/manager/ 53 | ``` 54 | 55 | 3. **Watch ApplicationRevision changes** so that you can see what it does. 56 | 57 | ```shell 58 | kubectl get apprev --watch 59 | ``` 60 | 61 | 4. **Edit any of the two ConfigMaps** (do this in another terminal) 62 | 63 | ```shell 64 | kubectl edit cm this-will-trigger-update-1 65 | ``` 66 | 67 | Immediately, you should see the two new ApplicationRevision created. Specifically, Applications all have updated with 68 | annotation: `app.oam.dev/publishVersion: '2/3/4...'` 69 | 70 | ## Delete resources 71 | 72 | Just replace all `kubectl apply` with `kubectl delete`, and run them in the reverse order. -------------------------------------------------------------------------------- /examples/conf-bump-app.yaml: -------------------------------------------------------------------------------- 1 | # A trigger is a group of Source, Filters, and Actions. 2 | # You can add multiple triggers. 3 | triggers: 4 | - source: 5 | type: resource-watcher 6 | properties: 7 | # We are interested in ConfigMap events. 8 | apiVersion: "v1" 9 | kind: ConfigMap 10 | namespace: default 11 | # Only watch update event. 12 | events: 13 | - update 14 | filter: | 15 | context: data: metadata: name: =~"this-will-trigger-update-.*" 16 | action: 17 | # Bump Application Revision to update Application. 18 | type: bump-application-revision 19 | properties: 20 | namespace: default 21 | # Select Applications to bump using labels. 22 | nameSelector: 23 | fromLabel: "watch-this" 24 | -------------------------------------------------------------------------------- /examples/conf-create-event-listener.yaml: -------------------------------------------------------------------------------- 1 | triggers: 2 | - source: 3 | type: resource-watcher 4 | properties: 5 | apiVersion: core.oam.dev/v1alpha1 6 | kind: WorkflowRun 7 | events: 8 | - create 9 | matchingLabels: 10 | trigger.oam.dev/watch: "true" 11 | action: 12 | type: create-event-listener 13 | -------------------------------------------------------------------------------- /examples/conf-cronjob.yaml: -------------------------------------------------------------------------------- 1 | triggers: 2 | - source: 3 | type: cronjob 4 | properties: 5 | schedule: "* * * * *" 6 | timeZone: "Asia/Shanghai" # Optional 7 | filter: "" 8 | action: 9 | # TODO: add your action here 10 | -------------------------------------------------------------------------------- /examples/conf-record-event.yaml: -------------------------------------------------------------------------------- 1 | triggers: 2 | - source: 3 | type: resource-watcher 4 | properties: 5 | apiVersion: apps/v1 6 | kind: Deployment 7 | events: 8 | - update 9 | filter: context.data.status.readyReplicas == context.data.status.replicas 10 | action: 11 | type: record-event 12 | properties: 13 | nameSelector: 14 | fromLabel: "workflowrun.oam.dev/name" 15 | -------------------------------------------------------------------------------- /examples/sample-eventlistener.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: standard.oam.dev/v1alpha1 2 | kind: EventListener 3 | metadata: 4 | name: this-will-trigger-update-1 5 | namespace: default 6 | -------------------------------------------------------------------------------- /examples/sample.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: this-will-trigger-update-1 5 | namespace: "default" 6 | labels: 7 | "watch-this": "this-will-be-updated-1" 8 | "workflowrun.oam.dev/name": "imme-reconcile" 9 | data: 10 | content: EDIT_ME_AFTER_APPLY 11 | 12 | --- 13 | apiVersion: v1 14 | kind: ConfigMap 15 | metadata: 16 | # Filters will be used to filter this name. 17 | name: this-will-trigger-update-2 18 | namespace: "default" 19 | labels: 20 | "watch-this": "this-will-be-updated-2" 21 | data: 22 | content: EDIT_ME_AFTER_APPLY 23 | 24 | --- 25 | 26 | apiVersion: core.oam.dev/v1beta1 27 | kind: Application 28 | metadata: 29 | annotations: 30 | # Notice changes here after you update the CM above/ 31 | app.oam.dev/publishVersion: "1" 32 | name: this-will-be-updated-1 33 | # Labels will be used to select which Application to bump revision. 34 | labels: 35 | "watch-this": "this-will-be-updated-1" 36 | namespace: default 37 | spec: 38 | components: [ ] 39 | 40 | --- 41 | 42 | apiVersion: core.oam.dev/v1beta1 43 | kind: Application 44 | metadata: 45 | annotations: 46 | app.oam.dev/publishVersion: "1" 47 | name: this-will-be-updated-2 48 | labels: 49 | "watch-this": "this-will-be-updated-2" 50 | namespace: default 51 | spec: 52 | components: [ ] 53 | 54 | --- 55 | apiVersion: standard.oam.dev/v1alpha1 56 | kind: EventListener 57 | metadata: 58 | name: this-will-be-updated-1 59 | namespace: default 60 | -------------------------------------------------------------------------------- /examples/triggerservice-bump-app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: standard.oam.dev/v1alpha1 2 | kind: TriggerService 3 | metadata: 4 | name: kubetrigger-sample-config 5 | namespace: default 6 | spec: 7 | # A trigger is a group of Source, Filters, and Actions. 8 | # You can add multiple triggers. 9 | triggers: 10 | - source: 11 | type: resource-watcher 12 | properties: 13 | # We are interested in ConfigMap events. 14 | apiVersion: "v1" 15 | kind: ConfigMap 16 | namespace: default 17 | # Only watch update event. 18 | events: 19 | - update 20 | filter: | 21 | context: data: metadata: name: =~"this-will-trigger-update-.*" 22 | action: 23 | # Bump Application Revision to update Application. 24 | type: bump-application-revision 25 | properties: 26 | namespace: default 27 | # Select Applications to bump using labels. 28 | nameSelector: 29 | fromLabel: "watch-this" 30 | -------------------------------------------------------------------------------- /examples/triggerservice-image-update.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: standard.oam.dev/v1alpha1 2 | kind: TriggerService 3 | metadata: 4 | name: image-rebase-trigger 5 | namespace: default 6 | spec: 7 | triggers: 8 | - source: 9 | # source is all the kpack Image resources in all the namespaces 10 | type: resource-watcher 11 | properties: 12 | apiVersion: kpack.io/v1alpha2 13 | # kpack needs to be installed on the cluster to have this resource type 14 | kind: Image 15 | events: 16 | - update 17 | 18 | # only trigger action when an Image is successfully rebased 19 | filter: > 20 | context.data.status.latestBuildReason == "STACK" && context.data.status.conditions[0].status == "True" 21 | 22 | action: 23 | type: task 24 | properties: 25 | cmd: [/bin/sh, -c, "echo Image: ${SOURCE_NAME} in namespace: ${SOURCE_NAMESPACE} has been successfully rebased at $(date)"] 26 | image: busybox 27 | name: image-update-task 28 | ttlSecondsAfterFinished: 600 29 | -------------------------------------------------------------------------------- /examples/triggerservice-record-event.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: standard.oam.dev/v1alpha1 2 | kind: TriggerService 3 | metadata: 4 | name: record-event 5 | namespace: default 6 | spec: 7 | worker: 8 | config: mul 9 | triggers: 10 | - source: 11 | type: resource-watcher 12 | properties: 13 | apiVersion: core.oam.dev/v1alpha1 14 | kind: WorkflowRun 15 | events: 16 | - create 17 | matchingLabels: 18 | trigger.oam.dev/watch: "true" 19 | action: 20 | type: create-event-listener 21 | - source: 22 | type: resource-watcher 23 | properties: 24 | clusters: 25 | - "cn-shanghai" 26 | apiVersion: apps/v1 27 | kind: Deployment 28 | events: 29 | - update 30 | filter: context.data.status.readyReplicas == context.data.status.replicas 31 | action: 32 | type: sae-record-event 33 | properties: 34 | nameSelector: 35 | fromLabel: "workflowrun.oam.dev/name" 36 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubevela/kube-trigger 2 | 3 | go 1.23.8 4 | 5 | require ( 6 | cuelang.org/go v0.14.1 7 | github.com/crossplane/crossplane-runtime v0.19.2 8 | github.com/google/go-cmp v0.7.0 9 | github.com/kubevela/pkg v1.9.3-0.20250625225831-a2894a62a307 10 | github.com/mitchellh/hashstructure/v2 v2.0.2 11 | github.com/pkg/errors v0.9.1 12 | github.com/robfig/cron/v3 v3.0.1 13 | github.com/sirupsen/logrus v1.9.3 14 | github.com/spf13/cobra v1.9.1 15 | github.com/spf13/pflag v1.0.7 16 | github.com/stretchr/testify v1.9.0 17 | golang.org/x/time v0.5.0 18 | k8s.io/api v0.31.10 19 | k8s.io/apimachinery v0.31.10 20 | k8s.io/client-go v0.31.10 21 | k8s.io/klog/v2 v2.130.1 22 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 23 | sigs.k8s.io/controller-runtime v0.19.7 24 | ) 25 | 26 | require ( 27 | github.com/NYTimes/gziphandler v1.1.1 // indirect 28 | github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 29 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect 30 | github.com/beorn7/perks v1.0.1 // indirect 31 | github.com/blang/semver/v4 v4.0.0 // indirect 32 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 33 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 34 | github.com/cockroachdb/apd/v3 v3.2.1 // indirect 35 | github.com/coreos/go-semver v0.3.1 // indirect 36 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 37 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 38 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 39 | github.com/emicklei/proto v1.14.2 // indirect 40 | github.com/evanphx/json-patch v5.6.0+incompatible // indirect 41 | github.com/evanphx/json-patch/v5 v5.9.0 // indirect 42 | github.com/felixge/httpsnoop v1.0.4 // indirect 43 | github.com/fsnotify/fsnotify v1.7.0 // indirect 44 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 45 | github.com/go-logr/logr v1.4.2 // indirect 46 | github.com/go-logr/stdr v1.2.2 // indirect 47 | github.com/go-logr/zapr v1.3.0 // indirect 48 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 49 | github.com/go-openapi/jsonreference v0.20.2 // indirect 50 | github.com/go-openapi/swag v0.23.0 // indirect 51 | github.com/go-stack/stack v1.8.1 // indirect 52 | github.com/gogo/protobuf v1.3.2 // indirect 53 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 54 | github.com/golang/protobuf v1.5.4 // indirect 55 | github.com/google/cel-go v0.20.1 // indirect 56 | github.com/google/gnostic-models v0.6.9 // indirect 57 | github.com/google/gofuzz v1.2.0 // indirect 58 | github.com/google/uuid v1.6.0 // indirect 59 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 60 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect 61 | github.com/imdario/mergo v0.3.15 // indirect 62 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 63 | github.com/jellydator/ttlcache/v3 v3.0.1 // indirect 64 | github.com/josharian/intern v1.0.0 // indirect 65 | github.com/json-iterator/go v1.1.12 // indirect 66 | github.com/klauspost/compress v1.17.10 // indirect 67 | github.com/kylelemons/godebug v1.1.0 // indirect 68 | github.com/mailru/easyjson v0.7.7 // indirect 69 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 70 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 71 | github.com/modern-go/reflect2 v1.0.2 // indirect 72 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 73 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 74 | github.com/oam-dev/cluster-gateway v1.9.2-0.20250629203450-2b04dd452b7a // indirect 75 | github.com/openshift/library-go v0.0.0-20230327085348-8477ec72b725 // indirect 76 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 77 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 78 | github.com/prometheus/client_golang v1.20.5 // indirect 79 | github.com/prometheus/client_model v0.6.1 // indirect 80 | github.com/prometheus/common v0.55.0 // indirect 81 | github.com/prometheus/procfs v0.15.1 // indirect 82 | github.com/protocolbuffers/txtpbfmt v0.0.0-20250627152318-f293424e46b5 // indirect 83 | github.com/stoewer/go-strcase v1.2.0 // indirect 84 | github.com/x448/float16 v0.8.4 // indirect 85 | go.etcd.io/etcd/api/v3 v3.5.16 // indirect 86 | go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect 87 | go.etcd.io/etcd/client/v3 v3.5.16 // indirect 88 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect 89 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect 90 | go.opentelemetry.io/otel v1.28.0 // indirect 91 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect 92 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect 93 | go.opentelemetry.io/otel/metric v1.28.0 // indirect 94 | go.opentelemetry.io/otel/sdk v1.28.0 // indirect 95 | go.opentelemetry.io/otel/trace v1.28.0 // indirect 96 | go.opentelemetry.io/proto/otlp v1.3.1 // indirect 97 | go.uber.org/multierr v1.11.0 // indirect 98 | go.uber.org/zap v1.26.0 // indirect 99 | golang.org/x/crypto v0.40.0 // indirect 100 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 101 | golang.org/x/net v0.42.0 // indirect 102 | golang.org/x/oauth2 v0.30.0 // indirect 103 | golang.org/x/sync v0.16.0 // indirect 104 | golang.org/x/sys v0.34.0 // indirect 105 | golang.org/x/term v0.33.0 // indirect 106 | golang.org/x/text v0.27.0 // indirect 107 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 108 | google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect 109 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect 110 | google.golang.org/grpc v1.67.1 // indirect 111 | google.golang.org/protobuf v1.35.1 // indirect 112 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 113 | gopkg.in/inf.v0 v0.9.1 // indirect 114 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect 115 | gopkg.in/yaml.v2 v2.4.0 // indirect 116 | gopkg.in/yaml.v3 v3.0.1 // indirect 117 | k8s.io/apiextensions-apiserver v0.31.2 // indirect 118 | k8s.io/apiserver v0.31.10 // indirect 119 | k8s.io/component-base v0.31.10 // indirect 120 | k8s.io/klog v1.0.0 // indirect 121 | k8s.io/kms v0.31.10 // indirect 122 | k8s.io/kube-openapi v0.0.0-20250610211856-8b98d1ed966a // indirect 123 | open-cluster-management.io/api v0.11.0 // indirect 124 | sigs.k8s.io/apiserver-network-proxy v0.31.4 // indirect 125 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.3 // indirect 126 | sigs.k8s.io/apiserver-runtime v1.1.2-0.20250117204231-9282f514a674 // indirect 127 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 128 | sigs.k8s.io/randfill v1.0.0 // indirect 129 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 130 | sigs.k8s.io/yaml v1.4.0 // indirect 131 | ) 132 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.Dockerfile.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The KubeVela Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.Makefile.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The KubeVela Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.generatego.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright YEAR The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.mk.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The KubeVela Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.py.txt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright YEAR The KubeVela Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.sh.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The KubeVela Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/format-svg-image.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2022 The KubeVela Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o pipefail 19 | 20 | if ! xmllint --version >/dev/null 2>&1; then 21 | echo "You don't have xmllint installed." 2>&1 22 | exit 1 23 | fi 24 | 25 | IFS=$'\n' 26 | # We don;t use find -exec for readability here. 27 | FILELIST=($(find docs -type f -name '*.svg' -print)) 28 | 29 | for i in "${FILELIST[@]}"; do 30 | TMPFILE=$(mktemp) 31 | xmllint --format "$i" >"$TMPFILE" 32 | if [[ -z "${LINT}" ]]; then 33 | cp "$TMPFILE" "$i" 34 | else 35 | diff "$TMPFILE" "$i" 36 | fi 37 | done 38 | -------------------------------------------------------------------------------- /hack/verify-boilerplate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2022 The KubeVela Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. 22 | 23 | boilerDir="${KUBE_ROOT}/hack/boilerplate" 24 | boiler="${boilerDir}/boilerplate.py" 25 | 26 | files_need_boilerplate=($(${boiler} "$@")) 27 | 28 | # Run boilerplate check 29 | if [[ ${#files_need_boilerplate[@]} -gt 0 ]]; then 30 | for file in "${files_need_boilerplate[@]}"; do 31 | echo "Boilerplate header is wrong for: ${file}" >&2 32 | done 33 | 34 | exit 1 35 | fi 36 | -------------------------------------------------------------------------------- /makefiles/common.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The KubeVela Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Set this to 1 to enable debugging output. 16 | DBG_MAKEFILE ?= 17 | ifeq ($(DBG_MAKEFILE),1) 18 | $(warning ***** starting Makefile for goal(s) "$(MAKECMDGOALS)") 19 | $(warning ***** $(shell date)) 20 | else 21 | # If we're not debugging the Makefile, don't echo recipes. 22 | MAKEFLAGS += -s 23 | endif 24 | 25 | # No, we don't want builtin rules. 26 | MAKEFLAGS += --no-builtin-rules 27 | # Get some warnings about undefined variables 28 | MAKEFLAGS += --warn-undefined-variables 29 | # Get rid of .PHONY everywhere. 30 | MAKEFLAGS += --always-make 31 | 32 | # Use bash explicitly 33 | SHELL := /usr/bin/env bash -o errexit -o pipefail -o nounset 34 | -------------------------------------------------------------------------------- /makefiles/consts.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The KubeVela Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Those variables assigned with ?= can be overridden by setting them 16 | # manually on the command line or using environment variables. 17 | 18 | # Use build container or local go sdk. If use have go installed, then 19 | # we use the local go sdk by default. Set USE_BUILD_CONTAINER to 1 manually 20 | # to use build container. 21 | USE_BUILD_CONTAINER ?= 22 | ifeq (, $(shell which go)) 23 | USE_BUILD_CONTAINER := 1 24 | endif 25 | # Go version used as the image of the build container, grabbed from go.mod 26 | GO_VERSION := $(shell grep -E '^go [[:digit:]]{1,3}\.[[:digit:]]{1,3}' go.mod | sed 's/go \([0-9]\+\.[0-9]\+\).*/\1/') 27 | # Local Go release version (only supports go1.16 and later) 28 | LOCAL_GO_VERSION := $(shell go env GOVERSION 2>/dev/null | grep -oE "go[[:digit:]]{1,3}\.[[:digit:]]{1,3}" || echo "none") 29 | ifneq (1, $(USE_BUILD_CONTAINER)) # If not using build container, whcih means user have go installed. We need some checks. 30 | # Before go1.16, there is no GOVERSION. We don't support such case, so build container will be used. 31 | ifeq (none, $(LOCAL_GO_VERSION)) 32 | $(warning You have $(shell go version | grep -oE " go[[:digit:]]{1,3}\.[[:digit:]]{1,3}.* " | xargs) locally, \ 33 | which is not supported. Containerized build environment will be used instead.) 34 | USE_BUILD_CONTAINER := 1 35 | endif 36 | # Warn if local go release version is different from what is specified in go.mod. 37 | ifneq (none, $(LOCAL_GO_VERSION)) 38 | ifneq (go$(GO_VERSION), $(LOCAL_GO_VERSION)) 39 | $(warning Your local Go release ($(LOCAL_GO_VERSION)) is different from the one that this go module assumes (go$(GO_VERSION)).) 40 | endif 41 | endif 42 | endif 43 | 44 | # Build container image 45 | BUILD_IMAGE ?= golang:$(GO_VERSION)-alpine 46 | 47 | # The base image of container artifacts 48 | BASE_IMAGE ?= gcr.io/distroless/static:nonroot 49 | 50 | # Set DEBUG to 1 to optimize binary for debugging, otherwise for release 51 | DEBUG ?= 52 | 53 | # env to passthrough to the build container 54 | GOFLAGS ?= 55 | GOPROXY ?= 56 | HTTP_PROXY ?= 57 | HTTPS_PROXY ?= 58 | 59 | # Version string, use git tag by default 60 | VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "UNKNOWN") 61 | 62 | # Container image tag, same as VERSION by default 63 | # if VERSION is not a semantic version (e.g. local uncommitted versions), then use latest 64 | IMAGE_TAG ?= $(shell bash -c " \ 65 | if [[ ! $(VERSION) =~ ^v[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(-(alpha|beta)\.[0-9]{1,3})?$$ ]]; then \ 66 | echo latest; \ 67 | else \ 68 | echo $(VERSION); \ 69 | fi") 70 | 71 | # Full Docker image name (e.g. docker.io/oamdev/kubetrigger:latest) 72 | IMAGE_REPO_TAGS ?= $(addsuffix /$(IMAGE_NAME):$(IMAGE_TAG),$(IMAGE_REPOS)) 73 | 74 | GOOS ?= 75 | GOARCH ?= 76 | # If user has not defined GOOS/GOARCH, use Go defaults. 77 | # If user don't have Go, use the os/arch of their machine. 78 | ifeq (, $(shell which go)) 79 | HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]') 80 | HOSTARCH := $(shell uname -m) 81 | ifeq ($(HOSTARCH),x86_64) 82 | HOSTARCH := amd64 83 | endif 84 | OS := $(if $(GOOS),$(GOOS),$(HOSTOS)) 85 | ARCH := $(if $(GOARCH),$(GOARCH),$(HOSTARCH)) 86 | else 87 | OS := $(if $(GOOS),$(GOOS),$(shell go env GOOS)) 88 | ARCH := $(if $(GOARCH),$(GOARCH),$(shell go env GOARCH)) 89 | endif 90 | 91 | # Windows have .exe in the binary name 92 | BIN_EXTENSION := 93 | ifeq ($(OS), windows) 94 | BIN_EXTENSION := .exe 95 | endif 96 | 97 | # Binary name 98 | BIN_BASENAME := $(BIN)$(BIN_EXTENSION) 99 | # Binary name with extended info, i.e. version-os-arch 100 | BIN_FULLNAME := $(BIN)-$(VERSION)-$(OS)-$(ARCH)$(BIN_EXTENSION) 101 | # Package filename (generated by `make package'). Use zip for Windows, tar.gz for all other platforms. 102 | PKG_FULLNAME := $(BIN_FULLNAME).tar.gz 103 | # Checksum filename 104 | CHECKSUM_FULLNAME := $(BIN)-$(VERSION)-checksums.txt 105 | ifeq ($(OS), windows) 106 | PKG_FULLNAME := $(subst .exe,,$(BIN_FULLNAME)).zip 107 | endif 108 | 109 | # This holds build output and helper tools 110 | DIST := bin 111 | # This holds build cache, if build container is used 112 | GOCACHE := .go 113 | # Full output directory 114 | BIN_OUTPUT_DIR := $(DIST)/$(BIN)-$(VERSION) 115 | PKG_OUTPUT_DIR := $(BIN_OUTPUT_DIR)/packages 116 | # Full output path with filename 117 | OUTPUT := $(BIN_OUTPUT_DIR)/$(BIN_FULLNAME) 118 | PKG_OUTPUT := $(PKG_OUTPUT_DIR)/$(PKG_FULLNAME) 119 | -------------------------------------------------------------------------------- /manager.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The KubeVela Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Setup make 16 | include makefiles/common.mk 17 | 18 | # Settings for this subproject 19 | # Entry file, containing func main 20 | ENTRY := cmd/manager/main.go 21 | # All supported platforms for binary distribution 22 | BIN_PLATFORMS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 23 | # All supported platforms for container image distribution 24 | IMAGE_PLATFORMS := linux/amd64 linux/arm64 25 | # Binary basename (.exe will be automatically added when building for Windows) 26 | BIN := manager 27 | # Container image name, without repo or tags 28 | IMAGE_NAME := kube-trigger-$(BIN) 29 | # Container image repositories to push to (supports multiple repos) 30 | IMAGE_REPOS := docker.io/oamdev ghcr.io/kubevela 31 | 32 | # Setup make variables 33 | include makefiles/consts.mk 34 | 35 | # Specific targets to this subproject 36 | manifests: # @HELP Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects 37 | manifests: controller-gen 38 | $(CONTROLLER_GEN) rbac:roleName=kube-trigger-manager-role crd webhook paths="{./api/...,./cmd/...,./controllers/...,./pkg/...}" output:crd:artifacts:config=config/crd output:rbac:dir=config/manager 39 | 40 | generate: # @HELP Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations 41 | generate: controller-gen 42 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate/boilerplate.go.txt" paths="{./api/...,./cmd/...,./controllers/...,./pkg/...}" 43 | go generate ./... 44 | 45 | install: # @HELP Install CRDs into the K8s cluster specified in ~/.kube/config 46 | install: manifests 47 | kubectl apply -f config/crd 48 | 49 | uninstall: # @HELP Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion 50 | uninstall: manifests 51 | kubectl delete --ignore-not-found -f config/crd 52 | 53 | deploy: # @HELP Deploy controller to the K8s cluster specified in ~/.kube/config 54 | deploy: manifests 55 | kubectl apply -f config/manager 56 | 57 | undeploy: # @HELP Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion 58 | undeploy: 59 | kubectl delete --ignore-not-found -f config/manager 60 | 61 | # Location to install dependencies to 62 | bin: 63 | mkdir -p bin 64 | 65 | # Tool Binaries 66 | CONTROLLER_GEN ?= bin/controller-gen 67 | 68 | # Tool Versions 69 | CONTROLLER_TOOLS_VERSION ?= v0.16.4 70 | 71 | controller-gen: bin 72 | [ -f $(CONTROLLER_GEN) ] || GOBIN=$(PWD)/bin go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) 73 | 74 | # Setup common targets 75 | include makefiles/targets.mk 76 | -------------------------------------------------------------------------------- /pkg/action/action.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package action 18 | 19 | import ( 20 | "context" 21 | "strconv" 22 | 23 | "github.com/kubevela/pkg/cue/cuex" 24 | "github.com/kubevela/pkg/util/template/definition" 25 | "github.com/mitchellh/hashstructure/v2" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | 28 | "github.com/kubevela/kube-trigger/api/v1alpha1" 29 | "github.com/kubevela/kube-trigger/pkg/executor" 30 | "github.com/kubevela/kube-trigger/pkg/types" 31 | ) 32 | 33 | // Job is the type of executor job. 34 | type Job struct { 35 | sourceType string 36 | id string 37 | context any 38 | properties any 39 | template string 40 | } 41 | 42 | var _ executor.Job = &Job{} 43 | 44 | // New creates a new job. It will fetch cached Action instance from Registry 45 | // using provided ActionMeta. sourceType and event will be passed to the Action.Run 46 | // method. 47 | func New(ctx context.Context, cli client.Client, meta v1alpha1.ActionMeta, contextData map[string]interface{}) (*Job, error) { 48 | template, err := definition.NewTemplateLoader(ctx, cli).LoadTemplate(ctx, meta.Type, definition.WithType(types.DefinitionTypeTriggerAction)) 49 | if err != nil { 50 | return nil, err 51 | } 52 | id, err := computeHash(meta) 53 | if err != nil { 54 | return nil, err 55 | } 56 | ret := Job{ 57 | id: id, 58 | template: template.Compile(), 59 | sourceType: meta.Type, 60 | context: contextData, 61 | properties: meta.Properties, 62 | } 63 | 64 | return &ret, nil 65 | } 66 | 67 | func computeHash(obj interface{}) (string, error) { 68 | // compute a hash value of any resource spec 69 | specHash, err := hashstructure.Hash(obj, hashstructure.FormatV2, nil) 70 | if err != nil { 71 | return "", err 72 | } 73 | specHashLabel := strconv.FormatUint(specHash, 16) 74 | return specHashLabel, nil 75 | } 76 | 77 | // Type return job type 78 | func (j *Job) Type() string { 79 | return j.sourceType 80 | } 81 | 82 | // ID return job id 83 | func (j *Job) ID() string { 84 | return j.id 85 | } 86 | 87 | // Run execute action 88 | func (j *Job) Run(ctx context.Context) error { 89 | v, err := cuex.CompileStringWithOptions(ctx, j.template, cuex.WithExtraData("parameter", j.properties), cuex.WithExtraData("context", j.context)) 90 | if err != nil { 91 | return err 92 | } 93 | if v.Err() != nil { 94 | return v.Err() 95 | } 96 | return nil 97 | } 98 | 99 | // AllowConcurrency returns whether the job allows concurrency. 100 | func (j *Job) AllowConcurrency() bool { 101 | return false 102 | } 103 | -------------------------------------------------------------------------------- /pkg/cmd/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cmd 18 | 19 | import ( 20 | "fmt" 21 | "time" 22 | 23 | "github.com/sirupsen/logrus" 24 | 25 | "github.com/kubevela/kube-trigger/pkg/executor" 26 | ) 27 | 28 | type option struct { 29 | LogLevel string 30 | Config string 31 | 32 | QueueSize int 33 | Workers int 34 | PerWorkerQPS int 35 | MaxRetry int 36 | RetryDelay int 37 | ActionRetry bool 38 | Timeout int 39 | 40 | RegistrySize int 41 | } 42 | 43 | const ( 44 | defaultLogLevel = "info" 45 | defaultConfig = "config.cue" 46 | 47 | defaultQueueSize = 50 48 | defaultWorkers = 4 49 | defaultPerWorkerQPS = 2 50 | defaultMaxRetry = 5 51 | defaultRetryDelay = 2 52 | defaultActionRetry = false 53 | defaultTimeout = 10 54 | 55 | defaultRegistrySize = 100 56 | 57 | // Values taken from: https://github.com/kubernetes/component-base/blob/master/config/v1alpha1/defaults.go 58 | defaultLeaseDuration = 15 * time.Second 59 | defaultRenewDeadline = 10 * time.Second 60 | defaultRetryPeriod = 2 * time.Second 61 | ) 62 | 63 | func newOption() *option { 64 | return &option{} 65 | } 66 | 67 | func (o *option) validate() error { 68 | _, err := logrus.ParseLevel(o.LogLevel) 69 | if err != nil { 70 | return err 71 | } 72 | if o.Config == "" { 73 | return fmt.Errorf("%s not specified", FlagConfig) 74 | } 75 | if o.QueueSize <= 0 { 76 | return fmt.Errorf("%s must be greater than 0", FlagQueueSize) 77 | } 78 | if o.Workers <= 0 { 79 | return fmt.Errorf("%s must be greater than 0", FlagWorkers) 80 | } 81 | if o.PerWorkerQPS <= 0 { 82 | return fmt.Errorf("%s must be greater than 0", FlagPerWorkerQPS) 83 | } 84 | if o.MaxRetry < 0 { 85 | return fmt.Errorf("%s must be greater or equal to 0", FlagMaxRetry) 86 | } 87 | if o.RetryDelay < 0 { 88 | return fmt.Errorf("%s must be greater or equal to 0", FlagRetryDelay) 89 | } 90 | if o.Timeout <= 0 { 91 | return fmt.Errorf("%s must be greater than 0", FlagTimeout) 92 | } 93 | if o.RegistrySize <= 0 { 94 | return fmt.Errorf("%s must be greater than 0", FlagRegistrySize) 95 | } 96 | return nil 97 | } 98 | 99 | func (o *option) getExecutorConfig() executor.Config { 100 | return executor.Config{ 101 | QueueSize: o.QueueSize, 102 | Workers: o.Workers, 103 | MaxJobRetries: o.MaxRetry, 104 | BaseRetryDelay: time.Second * time.Duration(o.RetryDelay), 105 | RetryJobAfterFailure: o.ActionRetry, 106 | PerWorkerQPS: o.PerWorkerQPS, 107 | Timeout: time.Second * time.Duration(o.Timeout), 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "github.com/kubevela/pkg/util/template/definition" 24 | "github.com/pkg/errors" 25 | "sigs.k8s.io/controller-runtime/pkg/client" 26 | 27 | "github.com/kubevela/kube-trigger/api/v1alpha1" 28 | sourceregistry "github.com/kubevela/kube-trigger/pkg/source/registry" 29 | "github.com/kubevela/kube-trigger/pkg/types" 30 | ) 31 | 32 | // Config is what actually stores configs in memory. 33 | // When marshalling or unmarshalling, simplified.ConfigWrapper will be used instead to 34 | // make it easier for the user to write. 35 | type Config struct { 36 | Triggers []v1alpha1.TriggerMeta `json:"triggers"` 37 | } 38 | 39 | // Validate validates config. 40 | func (c *Config) Validate(ctx context.Context, cli client.Client, sourceReg *sourceregistry.Registry) error { 41 | if len(c.Triggers) == 0 { 42 | return fmt.Errorf("no triggers found") 43 | } 44 | // TODO(charlie0129): gather all errors before returning 45 | for _, w := range c.Triggers { 46 | if _, ok := sourceReg.Get(w.Source.Type); !ok { 47 | return fmt.Errorf("no such source found: %s", w.Source.Type) 48 | } 49 | if _, err := definition.NewTemplateLoader(ctx, cli).LoadTemplate(ctx, w.Action.Type, definition.WithType(types.DefinitionTypeTriggerAction)); err != nil { 50 | return errors.WithMessagef(err, "no such action found: %s", w.Action.Type) 51 | } 52 | } 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/config/parser.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "encoding/json" 21 | "os" 22 | "path/filepath" 23 | 24 | "cuelang.org/go/cue/cuecontext" 25 | "github.com/pkg/errors" 26 | "github.com/sirupsen/logrus" 27 | "k8s.io/apimachinery/pkg/util/yaml" 28 | ) 29 | 30 | var logger = logrus.WithField("config", "parser") 31 | 32 | var parsers = map[string]func([]byte) (*Config, error){ 33 | ".cue": cueParser, 34 | ".yaml": yamlParser, 35 | ".yml": yamlParser, 36 | ".json": jsonParser, 37 | } 38 | 39 | var ( 40 | // ErrUnsupportedExtension is returned when the file extension is not supported. 41 | ErrUnsupportedExtension = errors.New("extension not supported") 42 | ) 43 | 44 | // New news a config 45 | func New() *Config { 46 | return &Config{} 47 | } 48 | 49 | // NewFromFileOrDir news a config from file or dir. 50 | func NewFromFileOrDir(path string) (*Config, error) { 51 | c := &Config{} 52 | 53 | fileInfo, err := os.Stat(path) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | if fileInfo.IsDir() { 59 | files, err := findFilesInDir(path) 60 | if err != nil { 61 | return nil, err 62 | } 63 | logger.Debugf("loading files: %v", files) 64 | for _, f := range files { 65 | subConfig, err := parseFromFile(f) 66 | if err != nil { 67 | if errors.Is(err, ErrUnsupportedExtension) { 68 | continue 69 | } 70 | return nil, errors.Wrapf(err, "reading %s failed", f) 71 | } 72 | logger.Infof("loaded config from %s", f) 73 | c.Triggers = append(c.Triggers, subConfig.Triggers...) 74 | } 75 | } else { 76 | c, err = parseFromFile(path) 77 | if err != nil { 78 | return nil, err 79 | } 80 | } 81 | 82 | return c, nil 83 | } 84 | 85 | func findFilesInDir(dir string) ([]string, error) { 86 | var files []string 87 | fs, err := os.ReadDir(dir) 88 | if err != nil { 89 | return nil, err 90 | } 91 | for _, f := range fs { 92 | if f.IsDir() { 93 | continue 94 | } 95 | files = append(files, filepath.Join(dir, f.Name())) 96 | } 97 | return files, err 98 | } 99 | 100 | func parseFromFile(path string) (*Config, error) { 101 | ext := filepath.Ext(path) 102 | parser, ok := parsers[ext] 103 | if !ok { 104 | logger.Warnf("file %s is skipped because extension %s is not supported", path, ext) 105 | return nil, ErrUnsupportedExtension 106 | } 107 | 108 | data, err := os.ReadFile(path) 109 | if err != nil { 110 | return nil, errors.Wrapf(err, "cannot read config file content") 111 | } 112 | 113 | return parser(data) 114 | } 115 | 116 | func cueParser(data []byte) (*Config, error) { 117 | c := cuecontext.New() 118 | v := c.CompileString(string(data)) 119 | jsonByte, err := v.MarshalJSON() 120 | if err != nil { 121 | return nil, err 122 | } 123 | 124 | conf := &Config{} 125 | err = json.Unmarshal(jsonByte, conf) 126 | if err != nil { 127 | return nil, err 128 | } 129 | return conf, nil 130 | } 131 | 132 | func jsonParser(data []byte) (*Config, error) { 133 | conf := &Config{} 134 | err := json.Unmarshal(data, conf) 135 | if err != nil { 136 | return nil, err 137 | } 138 | return conf, nil 139 | } 140 | 141 | func yamlParser(data []byte) (*Config, error) { 142 | conf := &Config{} 143 | err := yaml.Unmarshal(data, conf) 144 | if err != nil { 145 | return nil, err 146 | } 147 | return conf, nil 148 | } 149 | -------------------------------------------------------------------------------- /pkg/config/parser_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "reflect" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestNewFromFileOrDir(t *testing.T) { 27 | a := assert.New(t) 28 | // From file 29 | cf, err := NewFromFileOrDir("testdata/golden/conf.cue") 30 | a.NoError(err) 31 | a.Equal(1, len(cf.Triggers)) 32 | // From dir 33 | cd, err := NewFromFileOrDir("testdata/golden") 34 | a.NoError(err) 35 | a.Equal(4, len(cd.Triggers)) 36 | // Every trigger should be the same 37 | prev := cf.Triggers[0] 38 | for _, t := range cd.Triggers { 39 | reflect.DeepEqual(prev, t) 40 | } 41 | } 42 | 43 | func TestNewFromFileOrDirInvalid(t *testing.T) { 44 | a := assert.New(t) 45 | 46 | // Report error if file is invalid 47 | _, err := NewFromFileOrDir("testdata/invalidext/conf.invalid") 48 | a.Error(err) 49 | 50 | // No error if files in dir is invalid, just skips 51 | c, err := NewFromFileOrDir("testdata/invalidext") 52 | a.NoError(err) 53 | a.Equal(0, len(c.Triggers)) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/config/testdata/golden/conf.cue: -------------------------------------------------------------------------------- 1 | triggers: [{ 2 | source: { 3 | type: "resource-watcher" 4 | properties: { 5 | clusters: [ 6 | "cn-shanghai", 7 | ] 8 | apiVersion: "apps/v1" 9 | kind: "Deployment" 10 | events: [ 11 | "update", 12 | ] 13 | } 14 | } 15 | filter: "context.data.status.readyReplicas == context.data.status.replicas" 16 | action: { 17 | type: "sae-record-event" 18 | properties: nameSelector: fromLabel: "workflowrun.oam.dev/name" 19 | } 20 | }] 21 | -------------------------------------------------------------------------------- /pkg/config/testdata/golden/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "triggers": [ 3 | { 4 | "source": { 5 | "type": "resource-watcher", 6 | "properties": { 7 | "clusters": [ 8 | "cn-shanghai" 9 | ], 10 | "apiVersion": "apps/v1", 11 | "kind": "Deployment", 12 | "events": [ 13 | "update" 14 | ] 15 | } 16 | }, 17 | "filter": "context.data.status.readyReplicas == context.data.status.replicas", 18 | "action": { 19 | "type": "sae-record-event", 20 | "properties": { 21 | "nameSelector": { 22 | "fromLabel": "workflowrun.oam.dev/name" 23 | } 24 | } 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /pkg/config/testdata/golden/conf.yaml: -------------------------------------------------------------------------------- 1 | triggers: 2 | - source: 3 | type: resource-watcher 4 | properties: 5 | clusters: 6 | - "cn-shanghai" 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | events: 10 | - update 11 | filter: context.data.status.readyReplicas == context.data.status.replicas 12 | action: 13 | type: sae-record-event 14 | properties: 15 | nameSelector: 16 | fromLabel: "workflowrun.oam.dev/name" 17 | -------------------------------------------------------------------------------- /pkg/config/testdata/golden/conf.yml: -------------------------------------------------------------------------------- 1 | triggers: 2 | - source: 3 | type: resource-watcher 4 | properties: 5 | clusters: 6 | - "cn-shanghai" 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | events: 10 | - update 11 | filter: context.data.status.readyReplicas == context.data.status.replicas 12 | action: 13 | type: sae-record-event 14 | properties: 15 | nameSelector: 16 | fromLabel: "workflowrun.oam.dev/name" 17 | -------------------------------------------------------------------------------- /pkg/config/testdata/invalidext/conf.invalid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubevela/kube-trigger/484d8e5900220f2b66563b4085a6aaa25b03295d/pkg/config/testdata/invalidext/conf.invalid -------------------------------------------------------------------------------- /pkg/config/testdata/invalidschema/conf.yml: -------------------------------------------------------------------------------- 1 | triggers: 2 | - sources: 3 | type: resource-watcher 4 | properties: 5 | apiVersion: core.oam.dev/v1alpha1 6 | kind: WorkflowRun 7 | events: 8 | - create 9 | matchingLabels: 10 | trigger.oam.dev/watch: "true" 11 | action: 12 | type: create-event-listener 13 | - source: 14 | type: resource-watcher 15 | properties: 16 | clusters: 17 | - "cn-shanghai" 18 | apiVersion: apps/v1 19 | kind: Deployment 20 | events: 21 | - update 22 | filter: context.data.status.readyReplicas == context.data.status.replicas 23 | action: 24 | type: sae-record-event 25 | properties: 26 | nameSelector: 27 | fromLabel: "workflowrun.oam.dev/name" 28 | -------------------------------------------------------------------------------- /pkg/eventhandler/event_handler.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package eventhandler 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "time" 23 | 24 | "github.com/sirupsen/logrus" 25 | "sigs.k8s.io/controller-runtime/pkg/client" 26 | 27 | "github.com/kubevela/kube-trigger/api/v1alpha1" 28 | "github.com/kubevela/kube-trigger/pkg/action" 29 | "github.com/kubevela/kube-trigger/pkg/executor" 30 | "github.com/kubevela/kube-trigger/pkg/filter" 31 | ) 32 | 33 | // EventHandler is given to Source to be called. Source is responsible to call 34 | // this function. 35 | // 36 | // sourceType is what type the Source is. 37 | // 38 | // event is what event happened, containing a brief event object. Do not include 39 | // complex objects in it. For example, a resource-watcher Source may contain 40 | // what event happened (create, update, delete) in it. 41 | // 42 | // data is the detailed event, for machines to process, e.g. passed to filters to 43 | // do filtering . You may put complex objects in it. For example, 44 | // a resource-watcher Source may contain the entire object that is changed 45 | // in it. 46 | type EventHandler func(sourceType string, event interface{}, data interface{}) error 47 | 48 | // Config is the config for trigger 49 | type Config struct { 50 | Handler map[v1alpha1.ActionMeta]string 51 | Executor *executor.Executor 52 | } 53 | 54 | // New create a new EventHandler that does nothing. 55 | func New() EventHandler { 56 | return func(_ string, _ interface{}, _ interface{}) error { 57 | return nil 58 | } 59 | } 60 | 61 | // NewFromConfig creates a new EventHandler from config. 62 | func NewFromConfig(ctx context.Context, cli client.Client, actionMeta v1alpha1.ActionMeta, filterMeta string, executor *executor.Executor) EventHandler { 63 | filterLogger := logrus.WithField("eventhandler", "applyfilters") 64 | actionLogger := logrus.WithField("eventhandler", "addactionjob") 65 | return func(sourceType string, event interface{}, data interface{}) error { 66 | // TODO: use handler to handle 67 | // Apply filters 68 | context := map[string]interface{}{ 69 | "sourceType": sourceType, 70 | "event": event, 71 | "data": data, 72 | "timestamp": time.Now().Format(time.RFC3339), 73 | } 74 | kept, err := filter.ApplyFilter(ctx, context, filterMeta) 75 | if err != nil { 76 | filterLogger.Errorf("error when applying filters to event %v: %s", event, err) 77 | } 78 | if !kept { 79 | filterLogger.Debugf("event %v is filtered out", event) 80 | filterLogger.Infof("event is filtered out") 81 | return fmt.Errorf("event is filtered out") 82 | } 83 | filterLogger.Infof("event passed filters") 84 | 85 | // Run actions 86 | newJob, err := action.New(ctx, cli, actionMeta, context) 87 | if err != nil { 88 | actionLogger.Errorf("error when creating new job: %s", err) 89 | return err 90 | } 91 | err = executor.AddJob(newJob) 92 | if err != nil { 93 | actionLogger.Errorf("error when adding job to executor: %s", err) 94 | return err 95 | } 96 | 97 | return nil 98 | } 99 | } 100 | 101 | // AddHandlerBefore adds a new EventHandler to be called before e is called. 102 | func (e EventHandler) AddHandlerBefore(eh EventHandler) EventHandler { 103 | return func(sourceType string, event interface{}, data interface{}) error { 104 | err := eh(sourceType, event, data) 105 | if err != nil { 106 | return err 107 | } 108 | return e(sourceType, event, data) 109 | } 110 | } 111 | 112 | // AddHandlerAfter adds a new EventHandler to be called after e is called. 113 | func (e EventHandler) AddHandlerAfter(eh EventHandler) EventHandler { 114 | return func(sourceType string, event interface{}, data interface{}) error { 115 | err := e(sourceType, event, data) 116 | if err != nil { 117 | return err 118 | } 119 | return eh(sourceType, event, data) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /pkg/filter/filter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package filter 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "strings" 23 | 24 | "cuelang.org/go/cue" 25 | "cuelang.org/go/cue/ast" 26 | "cuelang.org/go/cue/format" 27 | "cuelang.org/go/cue/parser" 28 | "cuelang.org/go/tools/fix" 29 | "github.com/kubevela/pkg/cue/cuex" 30 | ) 31 | 32 | // ApplyFilter applies the given filter to an object. 33 | func ApplyFilter(ctx context.Context, contextData map[string]interface{}, filter string) (bool, error) { 34 | template, err := BuildFilterTemplate(filter) 35 | if err != nil { 36 | return false, err 37 | } 38 | filterVal, err := cuex.CompileStringWithOptions(ctx, template, cuex.WithExtraData("context", contextData)) 39 | if err != nil { 40 | return false, err 41 | } 42 | if filterVal.Err() != nil { 43 | return false, filterVal.Err() 44 | } 45 | result := filterVal.LookupPath(cue.ParsePath("filter")) 46 | if filterVal.LookupPath(cue.ParsePath("filter.filter")).Exists() { 47 | result = filterVal.LookupPath(cue.ParsePath("filter.filter")) 48 | } 49 | if result.Err() != nil { 50 | return false, result.Err() 51 | } 52 | resultBool, err := result.Bool() 53 | // if the result is not a bool, return true to pass the filter 54 | if err != nil { 55 | return true, nil 56 | } 57 | return resultBool, nil 58 | } 59 | 60 | // BuildFilterTemplate build filter template 61 | func BuildFilterTemplate(filter string) (string, error) { 62 | f, err := parser.ParseFile("-", filter) 63 | if err != nil { 64 | return "", err 65 | } 66 | n := fix.File(f) 67 | if n.Imports == nil { 68 | return fmt.Sprintf("filter: %s", filter), nil 69 | } 70 | var importDecls, contentDecls []ast.Decl 71 | for _, decl := range n.Decls { 72 | if importDecl, ok := decl.(*ast.ImportDecl); ok { 73 | importDecls = append(importDecls, importDecl) 74 | } else { 75 | contentDecls = append(contentDecls, decl) 76 | } 77 | } 78 | importString, err := encodeDeclsToString(importDecls) 79 | if err != nil { 80 | return "", err 81 | } 82 | contentString, err := encodeDeclsToString(contentDecls) 83 | if err != nil { 84 | return "", err 85 | } 86 | return fmt.Sprintf(filterTemplate, importString, contentString), nil 87 | } 88 | 89 | func encodeDeclsToString(decls []ast.Decl) (string, error) { 90 | bs, err := format.Node(&ast.File{Decls: decls}, format.Simplify()) 91 | if err != nil { 92 | return "", fmt.Errorf("failed to encode cue: %w", err) 93 | } 94 | return strings.TrimSpace(string(bs)), nil 95 | } 96 | 97 | var filterTemplate = ` 98 | %s 99 | filter: { 100 | %s 101 | } 102 | ` 103 | -------------------------------------------------------------------------------- /pkg/filter/filter_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package filter 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/kubevela/pkg/util/stringtools" 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestBuildFilterTemplate(t *testing.T) { 27 | testcases := map[string]struct { 28 | src string 29 | result string 30 | }{ 31 | "build filter with a single expression": { 32 | src: "a == 1", 33 | result: "filter: a == 1", 34 | }, 35 | "build filter with import declarations": { 36 | src: ` 37 | import "strings" 38 | strings.Contains("abc", "a")`, 39 | result: ` 40 | import "strings" 41 | filter: { 42 | strings.Contains("abc", "a") 43 | }`, 44 | }, 45 | } 46 | 47 | for name, testcase := range testcases { 48 | t.Run(name, func(t *testing.T) { 49 | result, err := BuildFilterTemplate(testcase.src) 50 | assert.NoError(t, err) 51 | assert.Equal(t, 52 | stringtools.TrimLeadingIndent(testcase.result), 53 | stringtools.TrimLeadingIndent(result), 54 | ) 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pkg/source/builtin/cronjob/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cronjob 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | ) 23 | 24 | // Config is the config for CronJob. 25 | type Config struct { 26 | Schedule string `json:"schedule"` 27 | TimeZone string `json:"timeZone"` 28 | } 29 | 30 | func (c *Config) String() string { 31 | // When TZ is set in schedule, ignore timeZone, just use schedule as is. 32 | // This is not the intended use case, but we want to support it. 33 | if strings.Contains(c.Schedule, "TZ") { 34 | return c.Schedule 35 | } 36 | 37 | if c.TimeZone != "" { 38 | // We don't check if the timezone is valid here. 39 | // cron lib will do it. 40 | return fmt.Sprintf("TZ=%s %s", c.TimeZone, c.Schedule) 41 | } 42 | 43 | return c.Schedule 44 | } 45 | 46 | func formatSchedule(c Config) string { 47 | // When TZ is set in schedule, warn the user. This is not the intended use case. 48 | // However, it should still work, so we can continue. 49 | if strings.Contains(c.Schedule, "TZ") { 50 | logger.Warnf("do NOT set 'TZ' in schedule, setting 'timeZone' is the preferred way. With 'TZ' set, any 'timeZone' setting will be ignored.") 51 | } 52 | 53 | return c.String() 54 | } 55 | -------------------------------------------------------------------------------- /pkg/source/builtin/cronjob/config_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cronjob 18 | 19 | import "testing" 20 | 21 | func TestFormatSchedule(t *testing.T) { 22 | tests := []struct { 23 | name string 24 | schedule string 25 | timeZone string 26 | want string 27 | }{ 28 | { 29 | name: "schedule_without_timezone", 30 | schedule: "* * * * *", 31 | timeZone: "", 32 | want: "* * * * *", 33 | }, 34 | { 35 | name: "schedule_with_timezone", 36 | schedule: "* * * * *", 37 | timeZone: "Asia/Shanghai", 38 | want: "TZ=Asia/Shanghai * * * * *", 39 | }, 40 | { 41 | name: "schedule_with_timezone_prefixed_unsupported_but_will_work", 42 | schedule: "TZ=Asia/Shanghai * * * * *", 43 | timeZone: "", 44 | want: "TZ=Asia/Shanghai * * * * *", 45 | }, 46 | } 47 | for _, tt := range tests { 48 | t.Run(tt.name, func(t *testing.T) { 49 | c := &Config{ 50 | Schedule: tt.schedule, 51 | TimeZone: tt.timeZone, 52 | } 53 | if got := formatSchedule(*c); got != tt.want { 54 | t.Errorf("String() = %v, want %v", got, tt.want) 55 | } 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /pkg/source/builtin/cronjob/cronjob.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cronjob 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | 23 | "github.com/pkg/errors" 24 | "github.com/robfig/cron/v3" 25 | "github.com/sirupsen/logrus" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | "k8s.io/apimachinery/pkg/runtime" 28 | 29 | "github.com/kubevela/kube-trigger/pkg/eventhandler" 30 | "github.com/kubevela/kube-trigger/pkg/source/types" 31 | ) 32 | 33 | func init() { 34 | logger = logrus.WithField("source", cronJobType) 35 | } 36 | 37 | var ( 38 | logger *logrus.Entry 39 | cronJobType = "cronjob" 40 | ) 41 | 42 | // CronJob triggers Actions on a schedule. 43 | type CronJob struct { 44 | config Config 45 | cronRunner *cron.Cron 46 | } 47 | 48 | var _ types.Source = &CronJob{} 49 | 50 | // New creates a new CronJob. 51 | func (c *CronJob) New() types.Source { 52 | return &CronJob{} 53 | } 54 | 55 | // Init initializes the CronJob. 56 | func (c *CronJob) Init(properties *runtime.RawExtension, eh eventhandler.EventHandler) error { 57 | b, err := properties.MarshalJSON() 58 | if err != nil { 59 | return errors.Wrapf(err, "error when parsing properties for %s", c.Type()) 60 | } 61 | err = json.Unmarshal(b, &c.config) 62 | if err != nil { 63 | return errors.Wrapf(err, "error when parsing properties for %s", c.Type()) 64 | } 65 | 66 | c.cronRunner = cron.New() 67 | sched, err := cron.ParseStandard(formatSchedule(c.config)) 68 | if err != nil { 69 | return errors.Wrapf(err, "error when parsing schedule for %s", c.Type()) 70 | } 71 | c.cronRunner.Schedule(sched, cron.FuncJob(func() { 72 | logger.Infof("schedule \"%s\" fired", c.config.String()) 73 | e := Event{ 74 | Config: c.config, 75 | TimeFired: metav1.Now(), 76 | } 77 | err := eh(c.Type(), e, e) 78 | if err != nil { 79 | logger.Infof("calling event handler failed: %s", err) 80 | } 81 | })) 82 | 83 | return nil 84 | } 85 | 86 | // Run starts the CronJob. 87 | func (c *CronJob) Run(ctx context.Context) error { 88 | go func() { 89 | logger.Infof("cronjob \"%s\" started", c.config.String()) 90 | c.cronRunner.Start() 91 | <-ctx.Done() 92 | logger.Infof("context cancelled, stoppping cronjob \"%s\"", c.config.String()) 93 | c.cronRunner.Stop() 94 | }() 95 | 96 | return nil 97 | } 98 | 99 | // Type returns the type of the CronJob. 100 | func (c *CronJob) Type() string { 101 | return cronJobType 102 | } 103 | 104 | // Singleton . 105 | func (c *CronJob) Singleton() bool { 106 | return false 107 | } 108 | 109 | // Event is the context passed to Actions. 110 | type Event struct { 111 | Config `json:",inline"` 112 | TimeFired metav1.Time `json:"timeFired"` 113 | } 114 | -------------------------------------------------------------------------------- /pkg/source/builtin/cronjob/cronjob_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cronjob 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "testing" 23 | "time" 24 | 25 | "github.com/robfig/cron/v3" 26 | "github.com/stretchr/testify/assert" 27 | "k8s.io/apimachinery/pkg/runtime" 28 | 29 | "github.com/kubevela/kube-trigger/pkg/eventhandler" 30 | ) 31 | 32 | func TestCronJob_Init(t *testing.T) { 33 | tests := []struct { 34 | name string 35 | config Config 36 | wantErr bool 37 | }{ 38 | { 39 | name: "normal", 40 | config: Config{ 41 | Schedule: "* * * * *", 42 | TimeZone: "Asia/Shanghai", 43 | }, 44 | wantErr: false, 45 | }, 46 | { 47 | name: "invalid_schedule", 48 | config: Config{ 49 | Schedule: "0 0 0 0 0", 50 | TimeZone: "", 51 | }, 52 | wantErr: true, 53 | }, 54 | { 55 | name: "invalid_timezone", 56 | config: Config{ 57 | Schedule: "* * * * *", 58 | TimeZone: "Nowhere/nowhere", 59 | }, 60 | wantErr: true, 61 | }, 62 | } 63 | for _, tt := range tests { 64 | t.Run(tt.name, func(t *testing.T) { 65 | c := (&CronJob{}).New() 66 | re := &runtime.RawExtension{} 67 | b, err := json.Marshal(tt.config) 68 | if err != nil { 69 | t.Fail() 70 | } 71 | err = re.UnmarshalJSON(b) 72 | if err != nil { 73 | t.Fail() 74 | } 75 | if err := c.Init(re, eventhandler.New()); (err != nil) != tt.wantErr { 76 | t.Errorf("Init() error = %v, wantErr %v", err, tt.wantErr) 77 | } 78 | }) 79 | } 80 | 81 | t.Run("invalid_properties", func(t *testing.T) { 82 | c := (&CronJob{}).New() 83 | re := &runtime.RawExtension{ 84 | Raw: []byte("this-is-not-valid"), 85 | Object: nil, 86 | } 87 | err := c.Init(re, eventhandler.New()) 88 | assert.Error(t, err) 89 | }) 90 | } 91 | 92 | func TestCronJob_Run(t *testing.T) { 93 | c := CronJob{ 94 | config: Config{}, 95 | cronRunner: cron.New(), 96 | } 97 | ctx, cancel := context.WithCancel(context.TODO()) 98 | go func() { 99 | _ = c.Run(ctx) 100 | }() 101 | cancel() 102 | time.Sleep(50 * time.Millisecond) 103 | } 104 | -------------------------------------------------------------------------------- /pkg/source/builtin/k8sresourcewatcher/types/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package types 18 | 19 | import ( 20 | "encoding/json" 21 | "strings" 22 | 23 | "github.com/kubevela/pkg/util/slices" 24 | ) 25 | 26 | // Config is the config for resource controller 27 | type Config struct { 28 | APIVersion string `json:"apiVersion"` 29 | Kind string `json:"kind"` 30 | Namespace string `json:"namespace,omitempty"` 31 | Events []EventType `json:"events,omitempty"` 32 | MatchingLabels map[string]string `json:"matchingLabels,omitempty"` 33 | Clusters []string `json:"clusters,omitempty"` 34 | } 35 | 36 | // Key returns the identifier of a Config. 37 | func (c *Config) Key() string { 38 | var labels string 39 | if len(c.MatchingLabels) > 0 { 40 | if b, err := json.Marshal(c.MatchingLabels); err == nil { 41 | labels = string(b) 42 | } 43 | } 44 | return strings.Join([]string{c.APIVersion, c.Kind, c.Namespace, labels}, "-") 45 | } 46 | 47 | // Merge merges two Configs. 48 | func (c *Config) Merge(new Config) { 49 | for _, event := range new.Events { 50 | if !slices.Contains(c.Events, event) { 51 | c.Events = append(c.Events, event) 52 | } 53 | } 54 | for k, v := range new.MatchingLabels { 55 | // no override 56 | if _, ok := c.MatchingLabels[k]; !ok { 57 | c.MatchingLabels[k] = v 58 | } 59 | } 60 | } 61 | 62 | // EventType is the type of the observed event. 63 | type EventType string 64 | 65 | // EventTypes 66 | const ( 67 | EventTypeCreate EventType = "create" 68 | EventTypeUpdate EventType = "update" 69 | EventTypeDelete EventType = "delete" 70 | ) 71 | 72 | // Event represent an event got from k8s api server 73 | type Event struct { 74 | Type EventType `json:"type"` 75 | Cluster string `json:"cluster"` 76 | } 77 | 78 | // InformerEvent indicate the informerEvent 79 | type InformerEvent struct { 80 | Event 81 | EventObj interface{} 82 | } 83 | -------------------------------------------------------------------------------- /pkg/source/builtin/k8sresourcewatcher/utils/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // GetObjectMetaData . 24 | func GetObjectMetaData(obj interface{}) metav1.Object { 25 | return obj.(metav1.Object) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/source/registry/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package registry 18 | 19 | import ( 20 | "github.com/kubevela/kube-trigger/pkg/source/builtin/cronjob" 21 | "github.com/kubevela/kube-trigger/pkg/source/builtin/k8sresourcewatcher" 22 | "github.com/kubevela/kube-trigger/pkg/source/types" 23 | ) 24 | 25 | // RegisterBuiltinSources register builtin sources. 26 | func RegisterBuiltinSources(reg *Registry) { 27 | registerFromInstance(reg, &k8sresourcewatcher.K8sResourceWatcher{}) 28 | registerFromInstance(reg, &cronjob.CronJob{}) 29 | } 30 | 31 | func registerFromInstance(reg *Registry, act types.Source) { 32 | ins := act 33 | insMeta := types.SourceMeta{ 34 | Type: ins.Type(), 35 | } 36 | reg.Register(insMeta, ins) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/source/registry/regsitry.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package registry 18 | 19 | import ( 20 | "sync" 21 | 22 | "github.com/kubevela/kube-trigger/pkg/source/types" 23 | ) 24 | 25 | // Registry stores builtin Sources. 26 | type Registry struct { 27 | reg sync.Map 28 | } 29 | 30 | // NewWithBuiltinSources return a Registry with builtin sources registered. 31 | // Ready for use. 32 | func NewWithBuiltinSources() *Registry { 33 | ret := New() 34 | RegisterBuiltinSources(ret) 35 | return ret 36 | } 37 | 38 | // New creates a new Registry. 39 | func New() *Registry { 40 | r := Registry{} 41 | r.reg = sync.Map{} 42 | return &r 43 | } 44 | 45 | // Register registers a Source. 46 | func (r *Registry) Register(meta types.SourceMeta, initial types.Source) { 47 | r.reg.Store(meta.Type, initial) 48 | } 49 | 50 | // Get gets a Source. 51 | func (r *Registry) Get(typ string) (types.Source, bool) { 52 | f, ok := r.reg.Load(typ) 53 | if !ok { 54 | return nil, ok 55 | } 56 | a, ok := f.(types.Source) 57 | return a, ok 58 | } 59 | -------------------------------------------------------------------------------- /pkg/source/types/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package types 18 | 19 | import ( 20 | "context" 21 | 22 | "k8s.io/apimachinery/pkg/runtime" 23 | 24 | "github.com/kubevela/kube-trigger/pkg/eventhandler" 25 | ) 26 | 27 | // Source is an interface for sources. Anything that implements this interface 28 | // can be registered as Sources, and will be executed automatically. 29 | type Source interface { 30 | // New returns a new uninitialized instance. 31 | New() Source 32 | 33 | // Init initializes this instance using user-provided properties. 34 | // Call the EventHandler when an event happened. 35 | Init(properties *runtime.RawExtension, eh eventhandler.EventHandler) error 36 | 37 | // Run starts this Source. You should handle the context so that you can 38 | // know when to exit. 39 | Run(ctx context.Context) error 40 | 41 | // Type returns the type of this Source. Name your source as something-doer, 42 | // instead of do-something. 43 | Type() string 44 | 45 | // Singleton defines if this type of Source will only be initialized once. 46 | // For example, if Singleton is true, all Source's in config with the same 47 | // type will share the same instance (New will be called only once) 48 | // and Init will be called multiple times for each Source of the same type. 49 | // Otherwise, each Source will have its own instance, i.e., each New for each Init. 50 | Singleton() bool 51 | } 52 | 53 | // SourceMeta is what users type in their configurations, specifying what source 54 | // they want to use and what properties they provided. 55 | type SourceMeta struct { 56 | // Type is the name (identifier) of this Source. 57 | Type string `json:"type"` 58 | 59 | // Properties are user-provided parameters. You should parse it yourself. 60 | Properties map[string]interface{} `json:"properties"` 61 | } 62 | -------------------------------------------------------------------------------- /pkg/types/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package types 18 | 19 | const ( 20 | // DefinitionTypeTriggerAction . 21 | DefinitionTypeTriggerAction = "trigger-action" 22 | // DefinitionTypeTriggerWorker . 23 | DefinitionTypeTriggerWorker = "trigger-worker" 24 | ) 25 | -------------------------------------------------------------------------------- /pkg/util/client/client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package client 18 | 19 | import ( 20 | oamv1alpha1 "github.com/kubevela/pkg/apis/oam/v1alpha1" 21 | "k8s.io/client-go/kubernetes/scheme" 22 | ctrl "sigs.k8s.io/controller-runtime" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | ) 25 | 26 | var k8sClient client.Client 27 | 28 | // GetClient gets a client. It creates a one if not already created. Subsequent 29 | // call will return the previously created one. It must not be called concurrently. 30 | func GetClient() (client.Client, error) { 31 | if k8sClient != nil { 32 | return k8sClient, nil 33 | } 34 | 35 | conf := ctrl.GetConfigOrDie() 36 | err := oamv1alpha1.AddToScheme(scheme.Scheme) 37 | if err != nil { 38 | return nil, err 39 | } 40 | c, err := client.New(conf, client.Options{Scheme: scheme.Scheme}) 41 | if err != nil { 42 | return nil, err 43 | } 44 | k8sClient = c 45 | return k8sClient, nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/util/cue/cue.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cue 18 | 19 | import ( 20 | "cuelang.org/go/cue" 21 | "cuelang.org/go/cue/format" 22 | "k8s.io/apimachinery/pkg/util/json" 23 | ) 24 | 25 | // Marshal marshals cue into string. 26 | func Marshal(v cue.Value) (string, error) { 27 | syn := v.Syntax(cue.Raw()) 28 | bs, err := format.Node(syn) 29 | if err != nil { 30 | return "", err 31 | } 32 | return string(bs), nil 33 | } 34 | 35 | // UnMarshal unmarshals cue value into a map. dst must be a pointer. 36 | func UnMarshal(v cue.Value, dst map[string]interface{}) error { 37 | jsonByte, err := v.MarshalJSON() 38 | if err != nil { 39 | return err 40 | } 41 | err = json.Unmarshal(jsonByte, &dst) 42 | if err != nil { 43 | return err 44 | } 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/util/cue/cue_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cue 18 | 19 | import ( 20 | "testing" 21 | 22 | "cuelang.org/go/cue/cuecontext" 23 | "github.com/stretchr/testify/require" 24 | ) 25 | 26 | func TestMarshal(t *testing.T) { 27 | cases := map[string]struct { 28 | in string 29 | out string 30 | }{ 31 | "case with regexp": { 32 | in: ` 33 | foo: =~"*" 34 | bar: 123 35 | `, 36 | out: `{ 37 | foo: =~"*" 38 | bar: 123 39 | }`, 40 | }, 41 | } 42 | r := require.New(t) 43 | for k, v := range cases { 44 | c := cuecontext.New() 45 | val := c.CompileString(v.in) 46 | str, err := Marshal(val) 47 | r.NoError(err, k) 48 | r.Equal(str, v.out, k) 49 | } 50 | } 51 | 52 | func TestUnMarshal(t *testing.T) { 53 | cases := map[string]struct { 54 | in string 55 | out map[string]interface{} 56 | err bool 57 | }{ 58 | "incomplete value": { 59 | in: ` 60 | foo: =~"*" 61 | bar: 123 62 | `, 63 | out: map[string]interface{}{}, 64 | err: true, 65 | }, 66 | "complete value": { 67 | in: ` 68 | foo: =~"." 69 | bar: "123" 70 | foo: "3" 71 | `, 72 | out: map[string]interface{}{ 73 | "foo": "3", 74 | "bar": "123", 75 | }, 76 | }, 77 | } 78 | 79 | for name, c := range cases { 80 | t.Run(name, func(t *testing.T) { 81 | r := require.New(t) 82 | cc := cuecontext.New() 83 | val := cc.CompileString(c.in) 84 | dst := make(map[string]interface{}) 85 | err := UnMarshal(val, dst) 86 | if c.err { 87 | r.Error(err) 88 | } else { 89 | r.NoError(err) 90 | r.Equal(dst, c.out) 91 | } 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkg/util/cue/validation.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cue 18 | 19 | import ( 20 | "encoding/json" 21 | 22 | "cuelang.org/go/cue/cuecontext" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | ) 25 | 26 | // ValidateAndUnMarshal validates the input against the CUE schema, and unmarshal 27 | // the input cue to output. output must be a pointer. 28 | func ValidateAndUnMarshal(schema string, input *runtime.RawExtension, output interface{}) error { 29 | inputStr, err := input.MarshalJSON() 30 | if err != nil { 31 | return err 32 | } 33 | cueCtx := cuecontext.New() 34 | v := cueCtx.CompileString(schema + "\n" + string(inputStr)) 35 | b, err := v.MarshalJSON() 36 | if err != nil { 37 | return err 38 | } 39 | return json.Unmarshal(b, output) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/util/cue/validation_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cue 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/require" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | ) 25 | 26 | func TestValidateAndUnMarshal(t *testing.T) { 27 | type testType struct { 28 | A string `json:"a"` 29 | } 30 | cases := map[string]struct { 31 | schema string 32 | in *runtime.RawExtension 33 | out testType 34 | expected testType 35 | err bool 36 | }{ 37 | "successful": { 38 | schema: `a: string`, 39 | in: &runtime.RawExtension{Raw: []byte(`{"a": "abc"}`)}, 40 | out: struct { 41 | A string `json:"a"` 42 | }{}, 43 | expected: struct { 44 | A string `json:"a"` 45 | }{A: "abc"}, 46 | err: false, 47 | }, 48 | "cannot marshal input": { 49 | schema: `a: string`, 50 | in: &runtime.RawExtension{Raw: []byte(`"a":b`)}, 51 | out: struct { 52 | A string `json:"a"` 53 | }{}, 54 | expected: testType{}, 55 | err: true, 56 | }, 57 | "cannot validate input": { 58 | schema: `a: string`, 59 | in: &runtime.RawExtension{Raw: []byte(`{"a": 1}`)}, 60 | out: struct { 61 | A string `json:"a"` 62 | }{}, 63 | expected: testType{}, 64 | err: true, 65 | }, 66 | "invalid schema": { 67 | schema: `a: `, 68 | in: &runtime.RawExtension{Raw: []byte(`{"a": 1}`)}, 69 | out: struct { 70 | A string `json:"a"` 71 | }{}, 72 | expected: testType{}, 73 | err: true, 74 | }, 75 | } 76 | for name, c := range cases { 77 | t.Run(name, func(t *testing.T) { 78 | r := require.New(t) 79 | err := ValidateAndUnMarshal(c.schema, c.in, &c.out) 80 | if c.err { 81 | r.Error(err) 82 | } else { 83 | r.NoError(err) 84 | r.Equal(c.expected, c.out) 85 | } 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package version 18 | 19 | // Version is the app-global version string, which should be substituted with a 20 | // real value during build. 21 | var Version = "UNKNOWN" 22 | -------------------------------------------------------------------------------- /pkg/workqueue/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package workqueue provides a simple queue that supports the following 18 | // features: 19 | // - Fair: items processed in the order in which they are added. 20 | // - Stingy: a single item will not be processed multiple times concurrently, 21 | // and if an item is added multiple times before it can be processed, it 22 | // will only be processed once. 23 | // - Multiple consumers and producers. In particular, it is allowed for an 24 | // item to be reenqueued while it is being processed. 25 | // - Shutdown notifications. 26 | package workqueue // import "k8s.io/client-go/util/workqueue" 27 | -------------------------------------------------------------------------------- /pkg/workqueue/indexer_delaying_queue_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package workqueue 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | "time" 23 | 24 | corev1 "k8s.io/api/core/v1" 25 | "k8s.io/apimachinery/pkg/api/meta" 26 | ) 27 | 28 | func TestIndexerDelayingQueue_Version(t *testing.T) { 29 | q := NewIndexerDelayingQueue("test", metaNamespaceKeyFunc) 30 | 31 | var p1 = &podWrap{&corev1.Pod{}} 32 | p1.ObjectMeta.Name = "ss" 33 | p1.ObjectMeta.Namespace = "abc" 34 | p1.ObjectMeta.ResourceVersion = "111" 35 | var p2 = &podWrap{p1.DeepCopy()} 36 | p2.ObjectMeta.ResourceVersion = "112" 37 | 38 | q.Add(p1) 39 | func() { 40 | item, _ := q.Get() 41 | // ... handle occur error 42 | defer q.Done(item) 43 | q.AddAfter(item, time.Second) 44 | }() 45 | q.Add(p2) 46 | item, _ := q.Get() 47 | if q.Len() != 1 { 48 | t.Errorf("expect queue len: 1, but %d", q.Len()) 49 | return 50 | } 51 | if item.(*podWrap).ObjectMeta.ResourceVersion != "112" { 52 | t.Errorf("expect the resource version : 112, but %s", item.(*podWrap).ObjectMeta.ResourceVersion) 53 | return 54 | } 55 | q.Done(item) 56 | if q.Len() != 0 { 57 | t.Errorf("expect queue len: 0, but %d", q.Len()) 58 | return 59 | } 60 | } 61 | 62 | func TestIndexerDelayingQueue_Parallel(t *testing.T) { 63 | q := NewIndexerDelayingQueue("test", metaNamespaceKeyFunc) 64 | 65 | var p1 = &podWrap{&corev1.Pod{}} 66 | p1.ObjectMeta.Name = "ss" 67 | p1.ObjectMeta.Namespace = "abc" 68 | p1.ObjectMeta.ResourceVersion = "111" 69 | var p2 = &podWrap{p1.DeepCopy()} 70 | p2.ObjectMeta.ResourceVersion = "112" 71 | 72 | q.Add(p1) 73 | first := make(chan struct{}) 74 | firstFinished := false 75 | go func() { 76 | item, _ := q.Get() 77 | close(first) 78 | time.Sleep(time.Second) 79 | firstFinished = true 80 | // ... handle occur error 81 | defer q.Done(item) 82 | q.AddAfter(item, time.Second) 83 | }() 84 | <-first 85 | q.Add(p2) 86 | item, _ := q.Get() 87 | if !firstFinished { 88 | t.Errorf("expect don't process the same key at the same time") 89 | return 90 | } 91 | if item.(*podWrap).ObjectMeta.ResourceVersion != "112" { 92 | t.Errorf("expect resource version: \"112\", but %s", item.(*podWrap).ObjectMeta.ResourceVersion) 93 | return 94 | } 95 | q.AddAfter(item, time.Second) 96 | q.Done(item) 97 | 98 | item, _ = q.Get() 99 | if item.(*podWrap).ObjectMeta.ResourceVersion != "112" { 100 | t.Errorf("expect resource version: \"112\", but %s", item.(*podWrap).ObjectMeta.ResourceVersion) 101 | return 102 | } 103 | q.Done(item) 104 | 105 | if q.Len() != 0 { 106 | t.Errorf("expect queue is empty,but %d", q.Len()) 107 | return 108 | } 109 | } 110 | 111 | func TestIndexerDelayingQueue_ShutDown(t *testing.T) { 112 | q := NewIndexerDelayingQueue("test", func(obj interface{}) (string, error) { 113 | return fmt.Sprint(obj), nil 114 | }) 115 | q.Add("abc") 116 | q.ShutDown() 117 | 118 | item, shutdown := q.Get() 119 | 120 | if shutdown { 121 | t.Errorf("expect queue open, but shutdown") 122 | return 123 | } 124 | 125 | if item != "abc" { 126 | t.Errorf("expect item: \"abc\", but %v", item) 127 | return 128 | } 129 | 130 | item, shutdown = q.Get() 131 | 132 | if !shutdown { 133 | t.Errorf("expect queue shutdown, but open") 134 | return 135 | } 136 | 137 | q.Add("xxx") 138 | item, shutdown = q.Get() 139 | if !shutdown { 140 | t.Errorf("expect queue shutdown, but open") 141 | return 142 | } 143 | 144 | if item != nil { 145 | t.Errorf("expect item: nil, but %v", item) 146 | return 147 | } 148 | } 149 | 150 | type podWrap struct { 151 | *corev1.Pod 152 | } 153 | 154 | func (w *podWrap) LessOrEqual(item interface{}) bool { 155 | return w.GetResourceVersion() <= item.(*podWrap).GetResourceVersion() 156 | } 157 | 158 | func TestBufferCap(t *testing.T) { 159 | q := NewIndexerDelayingQueue("test", func(obj interface{}) (string, error) { 160 | return fmt.Sprint(obj), nil 161 | }) 162 | for i := 1; i <= queueItemCap; i++ { 163 | q.Add(i) 164 | } 165 | start := time.Now() 166 | go func() { 167 | time.Sleep(time.Second) 168 | q.ShutDown() 169 | }() 170 | q.AddAfter(1000, 0) 171 | if time.Now().Sub(start).Seconds() >= 1 { 172 | t.Error("block AddAfter when queue overhead") 173 | } 174 | q.Add(1000) 175 | if time.Now().Sub(start).Seconds() < 1 { 176 | t.Error("can't block Add when queue overhead") 177 | } 178 | 179 | q.ShutDown() 180 | q.Add(1000) 181 | } 182 | 183 | func TestIndexerQueueLen(t *testing.T) { 184 | q := NewIndexerDelayingQueue("test", func(obj interface{}) (string, error) { 185 | return fmt.Sprint(obj), nil 186 | }) 187 | 188 | q.AddAfter(1, time.Second) 189 | q.AddAfter(1, time.Second*2) 190 | q.Add(1) 191 | if q.Len() != 1 { 192 | t.Errorf("q.Len() should be 1, but: %d", q.Len()) 193 | } 194 | item, _ := q.Get() 195 | if item != 1 { 196 | t.Errorf("q.Get() should be 1, but: %d", item) 197 | } 198 | q.Done(1) 199 | if q.Len() != 0 { 200 | t.Errorf("q.Len() should be 0, but: %d", q.Len()) 201 | } 202 | } 203 | 204 | func metaNamespaceKeyFunc(obj interface{}) (string, error) { 205 | meta, err := meta.Accessor(obj) 206 | if err != nil { 207 | return "", fmt.Errorf("object has no meta: %v", err) 208 | } 209 | if len(meta.GetNamespace()) > 0 { 210 | return meta.GetNamespace() + "/" + meta.GetName(), nil 211 | } 212 | return meta.GetName(), nil 213 | } 214 | -------------------------------------------------------------------------------- /pkg/workqueue/main_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package workqueue 18 | 19 | import ( 20 | "math/rand" 21 | "os" 22 | "testing" 23 | "time" 24 | ) 25 | 26 | func TestMain(m *testing.M) { 27 | rand.Seed(time.Now().UnixNano()) 28 | os.Exit(m.Run()) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/workqueue/parallelizer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package workqueue 18 | 19 | import ( 20 | "context" 21 | "sync" 22 | 23 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 24 | ) 25 | 26 | // DoWorkPieceFunc is a function that is called for each piece of work. 27 | type DoWorkPieceFunc func(piece int) 28 | 29 | type options struct { 30 | chunkSize int 31 | } 32 | 33 | // Options is a function that sets options on the options struct. 34 | type Options func(*options) 35 | 36 | // WithChunkSize allows to set chunks of work items to the workers, rather than 37 | // processing one by one. 38 | // It is recommended to use this option if the number of pieces significantly 39 | // higher than the number of workers and the work done for each item is small. 40 | func WithChunkSize(c int) func(*options) { 41 | return func(o *options) { 42 | o.chunkSize = c 43 | } 44 | } 45 | 46 | // ParallelizeUntil is a framework that allows for parallelizing N 47 | // independent pieces of work until done or the context is canceled. 48 | func ParallelizeUntil(ctx context.Context, workers, pieces int, doWorkPiece DoWorkPieceFunc, opts ...Options) { 49 | if pieces == 0 { 50 | return 51 | } 52 | o := options{} 53 | for _, opt := range opts { 54 | opt(&o) 55 | } 56 | chunkSize := o.chunkSize 57 | if chunkSize < 1 { 58 | chunkSize = 1 59 | } 60 | 61 | chunks := ceilDiv(pieces, chunkSize) 62 | toProcess := make(chan int, chunks) 63 | for i := 0; i < chunks; i++ { 64 | toProcess <- i 65 | } 66 | close(toProcess) 67 | 68 | var stop <-chan struct{} 69 | if ctx != nil { 70 | stop = ctx.Done() 71 | } 72 | if chunks < workers { 73 | workers = chunks 74 | } 75 | wg := sync.WaitGroup{} 76 | wg.Add(workers) 77 | for i := 0; i < workers; i++ { 78 | go func() { 79 | defer utilruntime.HandleCrash() 80 | defer wg.Done() 81 | for chunk := range toProcess { 82 | start := chunk * chunkSize 83 | end := start + chunkSize 84 | if end > pieces { 85 | end = pieces 86 | } 87 | for p := start; p < end; p++ { 88 | select { 89 | case <-stop: 90 | return 91 | default: 92 | doWorkPiece(p) 93 | } 94 | } 95 | } 96 | }() 97 | } 98 | wg.Wait() 99 | } 100 | 101 | func ceilDiv(a, b int) int { 102 | return (a + b - 1) / b 103 | } 104 | -------------------------------------------------------------------------------- /pkg/workqueue/parallelizer_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package workqueue 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "sync/atomic" 23 | "testing" 24 | 25 | "github.com/google/go-cmp/cmp" 26 | ) 27 | 28 | type testCase struct { 29 | pieces int 30 | workers int 31 | chunkSize int 32 | } 33 | 34 | func (c testCase) String() string { 35 | return fmt.Sprintf("pieces:%d,workers:%d,chunkSize:%d", c.pieces, c.workers, c.chunkSize) 36 | } 37 | 38 | var cases = []testCase{ 39 | { 40 | pieces: 1000, 41 | workers: 10, 42 | chunkSize: 1, 43 | }, 44 | { 45 | pieces: 1000, 46 | workers: 10, 47 | chunkSize: 10, 48 | }, 49 | { 50 | pieces: 1000, 51 | workers: 10, 52 | chunkSize: 100, 53 | }, 54 | { 55 | pieces: 999, 56 | workers: 10, 57 | chunkSize: 13, 58 | }, 59 | } 60 | 61 | func TestParallelizeUntil(t *testing.T) { 62 | for _, tc := range cases { 63 | t.Run(tc.String(), func(t *testing.T) { 64 | seen := make([]int32, tc.pieces) 65 | ctx := context.Background() 66 | ParallelizeUntil(ctx, tc.workers, tc.pieces, func(p int) { 67 | atomic.AddInt32(&seen[p], 1) 68 | }, WithChunkSize(tc.chunkSize)) 69 | 70 | wantSeen := make([]int32, tc.pieces) 71 | for i := 0; i < tc.pieces; i++ { 72 | wantSeen[i] = 1 73 | } 74 | if diff := cmp.Diff(wantSeen, seen); diff != "" { 75 | t.Errorf("bad number of visits (-want,+got):\n%s", diff) 76 | } 77 | }) 78 | } 79 | } 80 | 81 | func BenchmarkParallelizeUntil(b *testing.B) { 82 | for _, tc := range cases { 83 | b.Run(tc.String(), func(b *testing.B) { 84 | ctx := context.Background() 85 | isPrime := make([]bool, tc.pieces) 86 | b.ResetTimer() 87 | for c := 0; c < b.N; c++ { 88 | ParallelizeUntil(ctx, tc.workers, tc.pieces, func(p int) { 89 | isPrime[p] = calPrime(p) 90 | }, WithChunkSize(tc.chunkSize)) 91 | } 92 | b.StopTimer() 93 | want := []bool{false, false, true, true, false, true, false, true, false, false, false, true} 94 | if diff := cmp.Diff(want, isPrime[:len(want)]); diff != "" { 95 | b.Errorf("miscalculated isPrime (-want,+got):\n%s", diff) 96 | } 97 | }) 98 | } 99 | } 100 | 101 | func calPrime(p int) bool { 102 | if p <= 1 { 103 | return false 104 | } 105 | for i := 2; i*i <= p; i++ { 106 | if p%i == 0 { 107 | return false 108 | } 109 | } 110 | return true 111 | } 112 | -------------------------------------------------------------------------------- /pkg/workqueue/rate_limiting_queue.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package workqueue 18 | 19 | // RateLimitingInterface is an interface that rate limits items being added to the queue. 20 | type RateLimitingInterface interface { 21 | DelayingInterface 22 | 23 | // AddRateLimited adds an item to the workqueue after the rate limiter says it's ok 24 | AddRateLimited(item interface{}) 25 | 26 | // Forget indicates that an item is finished being retried. Doesn't matter whether it's for perm failing 27 | // or for success, we'll stop the rate limiter from tracking it. This only clears the `rateLimiter`, you 28 | // still have to call `Done` on the queue. 29 | Forget(item interface{}) 30 | 31 | // NumRequeues returns back how many times the item was requeued 32 | NumRequeues(item interface{}) int 33 | } 34 | 35 | // NewRateLimitingQueue constructs a new workqueue with rateLimited queuing ability 36 | // Remember to call Forget! If you don't, you may end up tracking failures forever. 37 | func NewRateLimitingQueue(rateLimiter RateLimiter) RateLimitingInterface { 38 | return &rateLimitingType{ 39 | DelayingInterface: NewDelayingQueue(), 40 | rateLimiter: rateLimiter, 41 | } 42 | } 43 | 44 | // NewNamedRateLimitingQueue constructs a new workqueue with rateLimited queuing ability 45 | func NewNamedRateLimitingQueue(rateLimiter RateLimiter, name string) RateLimitingInterface { 46 | return &rateLimitingType{ 47 | DelayingInterface: NewNamedDelayingQueue(name), 48 | rateLimiter: rateLimiter, 49 | } 50 | } 51 | 52 | // rateLimitingType wraps an Interface and provides rateLimited re-enquing 53 | type rateLimitingType struct { 54 | DelayingInterface 55 | 56 | rateLimiter RateLimiter 57 | } 58 | 59 | // AddRateLimited AddAfter's the item based on the time when the rate limiter says it's ok 60 | func (q *rateLimitingType) AddRateLimited(item interface{}) { 61 | q.DelayingInterface.AddAfter(item, q.rateLimiter.When(item)) 62 | } 63 | 64 | func (q *rateLimitingType) NumRequeues(item interface{}) int { 65 | return q.rateLimiter.NumRequeues(item) 66 | } 67 | 68 | func (q *rateLimitingType) Forget(item interface{}) { 69 | q.rateLimiter.Forget(item) 70 | } 71 | -------------------------------------------------------------------------------- /pkg/workqueue/rate_limiting_queue_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package workqueue 18 | 19 | import ( 20 | "testing" 21 | "time" 22 | 23 | testingclock "k8s.io/utils/clock/testing" 24 | ) 25 | 26 | func TestRateLimitingQueue(t *testing.T) { 27 | limiter := NewItemExponentialFailureRateLimiter(1*time.Millisecond, 1*time.Second) 28 | queue := NewRateLimitingQueue(limiter).(*rateLimitingType) 29 | fakeClock := testingclock.NewFakeClock(time.Now()) 30 | delayingQueue := &delayingType{ 31 | Interface: New(), 32 | clock: fakeClock, 33 | heartbeat: fakeClock.NewTicker(maxWait), 34 | stopCh: make(chan struct{}), 35 | waitingForAddCh: make(chan *waitFor, 1000), 36 | metrics: newRetryMetrics(""), 37 | } 38 | queue.DelayingInterface = delayingQueue 39 | 40 | queue.AddRateLimited("one") 41 | waitEntry := <-delayingQueue.waitingForAddCh 42 | if e, a := 1*time.Millisecond, waitEntry.readyAt.Sub(fakeClock.Now()); e != a { 43 | t.Errorf("expected %v, got %v", e, a) 44 | } 45 | queue.AddRateLimited("one") 46 | waitEntry = <-delayingQueue.waitingForAddCh 47 | if e, a := 2*time.Millisecond, waitEntry.readyAt.Sub(fakeClock.Now()); e != a { 48 | t.Errorf("expected %v, got %v", e, a) 49 | } 50 | if e, a := 2, queue.NumRequeues("one"); e != a { 51 | t.Errorf("expected %v, got %v", e, a) 52 | } 53 | 54 | queue.AddRateLimited("two") 55 | waitEntry = <-delayingQueue.waitingForAddCh 56 | if e, a := 1*time.Millisecond, waitEntry.readyAt.Sub(fakeClock.Now()); e != a { 57 | t.Errorf("expected %v, got %v", e, a) 58 | } 59 | queue.AddRateLimited("two") 60 | waitEntry = <-delayingQueue.waitingForAddCh 61 | if e, a := 2*time.Millisecond, waitEntry.readyAt.Sub(fakeClock.Now()); e != a { 62 | t.Errorf("expected %v, got %v", e, a) 63 | } 64 | 65 | queue.Forget("one") 66 | if e, a := 0, queue.NumRequeues("one"); e != a { 67 | t.Errorf("expected %v, got %v", e, a) 68 | } 69 | queue.AddRateLimited("one") 70 | waitEntry = <-delayingQueue.waitingForAddCh 71 | if e, a := 1*time.Millisecond, waitEntry.readyAt.Sub(fakeClock.Now()); e != a { 72 | t.Errorf("expected %v, got %v", e, a) 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /trigger.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The KubeVela Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Setup make 16 | include makefiles/common.mk 17 | 18 | # Settings for this subproject 19 | # Entry file, containing func main 20 | ENTRY := cmd/kubetrigger/main.go 21 | # All supported platforms for binary distribution 22 | BIN_PLATFORMS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 23 | # All supported platforms for container image distribution 24 | IMAGE_PLATFORMS := linux/amd64 linux/arm64 25 | # Binary basename (.exe will be automatically added when building for Windows) 26 | BIN := kube-trigger 27 | # Container image name, without repo or tags 28 | IMAGE_NAME := $(BIN) 29 | # Container image repositories to push to (supports multiple repos) 30 | IMAGE_REPOS := docker.io/oamdev ghcr.io/kubevela 31 | 32 | # Setup make variables 33 | include makefiles/consts.mk 34 | 35 | # Specific targets to this subproject 36 | generate: # @HELP run go generate 37 | generate: 38 | go generate ./... 39 | 40 | # Setup common targets 41 | include makefiles/targets.mk 42 | --------------------------------------------------------------------------------