├── .DS_Store ├── docs ├── .DS_Store ├── getting-started │ ├── images │ │ ├── trigger-webhook.png │ │ └── trigger-webhook2.png │ ├── sa.yaml │ ├── secret.yaml │ ├── image-repo-secret.yaml │ ├── persistent-volume-1.yaml │ ├── rbac │ │ ├── clusterrolebinding.yaml │ │ ├── admin-role.yaml │ │ └── webhook-role.yaml │ ├── static-ingress-rule.yaml │ ├── ingress-run.yaml │ ├── webhook-run.yaml │ ├── create-webhook.yaml │ ├── triggers.yaml │ ├── create-ingress.yaml │ ├── pipeline_BACKUP.yaml │ ├── pipeline.yaml │ └── README.md ├── resources.yaml ├── metrics.md ├── README.md ├── events.md ├── triggers.md ├── install.md ├── clusterinterceptors.md ├── triggertemplates.md ├── troubleshooting.md ├── triggerbindings.md ├── images │ └── TriggerFlow.svg ├── cel_expressions.md ├── interceptors.md └── eventlisteners.md └── ocean-api ├── .DS_Store ├── README.md ├── app.js ├── package.json ├── node-app.yaml ├── .gitignore └── package-lock.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jspruance/build-pipeline-poc/main/.DS_Store -------------------------------------------------------------------------------- /docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jspruance/build-pipeline-poc/main/docs/.DS_Store -------------------------------------------------------------------------------- /ocean-api/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jspruance/build-pipeline-poc/main/ocean-api/.DS_Store -------------------------------------------------------------------------------- /ocean-api/README.md: -------------------------------------------------------------------------------- 1 | # ocean-api 2 | Sample NodeJS app for testing Kubernetes & Tekton operations for the Dome build cluster. 3 | -------------------------------------------------------------------------------- /docs/getting-started/images/trigger-webhook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jspruance/build-pipeline-poc/main/docs/getting-started/images/trigger-webhook.png -------------------------------------------------------------------------------- /docs/getting-started/images/trigger-webhook2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jspruance/build-pipeline-poc/main/docs/getting-started/images/trigger-webhook2.png -------------------------------------------------------------------------------- /docs/getting-started/sa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: buildpacks-service-account 5 | secrets: 6 | - name: docker-user-pass -------------------------------------------------------------------------------- /docs/getting-started/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: webhook-secret 5 | namespace: getting-started 6 | stringData: 7 | token: 8 | secret: random-string-data 9 | -------------------------------------------------------------------------------- /docs/resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: buildpacks-source-pvc 5 | spec: 6 | accessModes: 7 | - ReadWriteOnce 8 | resources: 9 | requests: 10 | storage: 500Mi -------------------------------------------------------------------------------- /docs/getting-started/image-repo-secret.yaml: -------------------------------------------------------------------------------- 1 | kubectl create secret docker-registry docker-user-pass \ 2 | --docker-username=jspruancedome \ 3 | --docker-password=Molokova37! \ 4 | --docker-server=https://registry.hub.docker.com \ 5 | --namespace default -------------------------------------------------------------------------------- /ocean-api/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | const port = 3000 4 | 5 | app.get('/', (req, res) => { 6 | res.send('Hello World!') 7 | }) 8 | 9 | app.listen(port, () => { 10 | console.log(`Example app listening on port ${port}`) 11 | }) -------------------------------------------------------------------------------- /docs/getting-started/persistent-volume-1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolume 3 | metadata: 4 | name: buildpacks-source-pv-1 5 | labels: 6 | type: local 7 | spec: 8 | storageClassName: manual 9 | capacity: 10 | storage: 600Mi 11 | accessModes: 12 | - ReadWriteOnce 13 | hostPath: 14 | path: "/mnt/data" -------------------------------------------------------------------------------- /ocean-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ocean-api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.18.1", 13 | "mongodb": "^4.10.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/getting-started/rbac/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: tekton-triggers-getting-started-clusterbinding 5 | subjects: 6 | - kind: ServiceAccount 7 | name: tekton-triggers-example-sa 8 | namespace: getting-started 9 | roleRef: 10 | apiGroup: rbac.authorization.k8s.io 11 | kind: ClusterRole 12 | name: tekton-triggers-example-clusterrole -------------------------------------------------------------------------------- /docs/getting-started/static-ingress-rule.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: getting-started 5 | annotations: 6 | kubernetes.io/ingress.class: nginx 7 | spec: 8 | rules: 9 | - host: 10 | http: 11 | paths: 12 | - path: /testpath 13 | pathType: Prefix 14 | backend: 15 | service: 16 | name: el-getting-started-listener 17 | port: 18 | number: 8080 -------------------------------------------------------------------------------- /docs/getting-started/ingress-run.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1beta1 2 | kind: TaskRun 3 | metadata: 4 | name: create-ingress-run 5 | namespace: getting-started 6 | spec: 7 | taskRef: 8 | name: create-ingress 9 | params: 10 | - name: CreateCertificate 11 | value: "true" 12 | - name: CertificateKeyPassphrase 13 | value: asecretphrase 14 | - name: CertificateSecretName 15 | value: ingresssecret 16 | - name: ExternalDomain 17 | value: 45.79.240.174 18 | - name: Service 19 | value: getting-started 20 | - name: ServicePort 21 | value: "8080" 22 | timeout: 1000s 23 | serviceAccountName: tekton-triggers-createwebhook 24 | -------------------------------------------------------------------------------- /ocean-api/node-app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: ocean-api 5 | labels: 6 | app: ocean-api 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: ocean-api 12 | template: 13 | metadata: 14 | labels: 15 | app: ocean-api 16 | spec: 17 | containers: 18 | - name: ocean-api 19 | image: jspruancedome/ocean-api 20 | ports: 21 | - containerPort: 3000 22 | --- 23 | apiVersion: v1 24 | kind: Service 25 | metadata: 26 | name: ocean-api-service 27 | spec: 28 | selector: 29 | app: ocean-api 30 | type: LoadBalancer 31 | ports: 32 | - protocol: TCP 33 | port: 3000 34 | targetPort: 3000 35 | nodePort: 31000 -------------------------------------------------------------------------------- /docs/getting-started/webhook-run.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1beta1 2 | kind: TaskRun 3 | metadata: 4 | name: create-webhook-run 5 | namespace: getting-started 6 | spec: 7 | taskRef: 8 | name: create-webhook 9 | params: 10 | - name: GitHubOrg 11 | value: "" 12 | - name: GitHubUser 13 | value: "jspruance" 14 | - name: GitHubRepo 15 | value: "ulmaceae" 16 | - name: GitHubSecretName 17 | value: webhook-secret 18 | - name: GitHubAccessTokenKey 19 | value: token 20 | - name: GitHubSecretStringKey 21 | value: secret 22 | - name: ExternalDomain 23 | value: 45.79.240.174 24 | # If you are using github enterprise, provide a value for GitHubDomain 25 | # - name: GitHubDomain 26 | # value: git.corp.com 27 | timeout: 1000s 28 | serviceAccountName: tekton-triggers-createwebhook 29 | -------------------------------------------------------------------------------- /docs/getting-started/rbac/admin-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: tekton-triggers-example-sa 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: RoleBinding 8 | metadata: 9 | name: triggers-example-eventlistener-binding 10 | subjects: 11 | - kind: ServiceAccount 12 | name: tekton-triggers-example-sa 13 | roleRef: 14 | apiGroup: rbac.authorization.k8s.io 15 | kind: ClusterRole 16 | name: tekton-triggers-eventlistener-roles 17 | --- 18 | apiVersion: rbac.authorization.k8s.io/v1 19 | kind: ClusterRoleBinding 20 | metadata: 21 | name: triggers-example-eventlistener-clusterbinding 22 | subjects: 23 | - kind: ServiceAccount 24 | name: tekton-triggers-example-sa 25 | namespace: getting-started 26 | roleRef: 27 | apiGroup: rbac.authorization.k8s.io 28 | kind: ClusterRole 29 | name: tekton-triggers-eventlistener-clusterroles 30 | -------------------------------------------------------------------------------- /docs/metrics.md: -------------------------------------------------------------------------------- 1 | 7 | # Tekton Triggers Controller Metrics 8 | 9 | The following tekton triggers metrics are available at `controller-service` on port `9000`. 10 | 11 | We expose several kinds of exporters, including Prometheus, Google Stackdriver, and many others. You can set them up using [observability configuration](../config/config-observability.yaml). 12 | 13 | | Name | Type | Description | 14 | | ---------- | ----------- | ----------- | 15 | | `controller_clusterinterceptor_count` | Gauge | Number of clusterinteceptor | 16 | | `controller_eventlistener_count` | Gauge | Number of eventlistener | 17 | | `controller_clustertriggerbinding_count` | Gauge | Number of clustertriggerbinding | 18 | | `controller_triggerbinding_count` | Gauge | Number of triggerbinding | 19 | | `controller_triggertemplate_count` | Gauge | Number of triggertemplate | 20 | 21 | -------------------------------------------------------------------------------- /docs/getting-started/rbac/webhook-role.yaml: -------------------------------------------------------------------------------- 1 | kind: Role 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: tekton-triggers-createwebhook 5 | namespace: getting-started 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - secrets 11 | verbs: 12 | - get 13 | - list 14 | - create 15 | - update 16 | - delete 17 | - apiGroups: 18 | - triggers.tekton.dev 19 | resources: 20 | - eventlisteners 21 | verbs: 22 | - get 23 | - list 24 | - create 25 | - update 26 | - delete 27 | - apiGroups: 28 | - extensions 29 | - networking.k8s.io 30 | resources: 31 | - ingresses 32 | verbs: 33 | - create 34 | - get 35 | - list 36 | - delete 37 | - update 38 | --- 39 | apiVersion: v1 40 | kind: ServiceAccount 41 | metadata: 42 | name: tekton-triggers-createwebhook 43 | namespace: getting-started 44 | --- 45 | apiVersion: rbac.authorization.k8s.io/v1 46 | kind: RoleBinding 47 | metadata: 48 | name: tekton-triggers-createwebhook 49 | namespace: getting-started 50 | subjects: 51 | - kind: ServiceAccount 52 | name: tekton-triggers-createwebhook 53 | namespace: getting-started 54 | roleRef: 55 | apiGroup: rbac.authorization.k8s.io 56 | kind: Role 57 | name: tekton-triggers-createwebhook 58 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Build Pipeline 2 | 3 | ## Setup 4 | 5 | kubectl create namespace getting-started 6 | 7 | kubectl -n getting-started apply -f ./docs/getting-started/rbac/admin-role.yaml -f ./docs/getting-started/rbac/clusterrolebinding.yaml 8 | 9 | // kubectl -n getting-started apply -f ./docs/getting-started/rbac/webhook-role.yaml 10 | 11 | kubectl -n getting-started apply -f https://raw.githubusercontent.com/tektoncd/catalog/master/task/git-clone/0.4/git-clone.yaml 12 | 13 | kubectl -n getting-started apply -f https://raw.githubusercontent.com/tektoncd/catalog/master/task/buildpacks/0.3/buildpacks.yaml 14 | 15 | kubectl -n getting-started apply -f ./docs/getting-started/static-ingress-rule.yaml 16 | 17 | kubectl -n getting-started apply -f docs/getting-started/ingress-run.yaml 18 | 19 | kubectl create secret docker-registry docker-user-pass \ 20 | --docker-username=jspruancedome \ 21 | --docker-password=Molokova37! \ 22 | --docker-server=https://registry.hub.docker.com \ 23 | --namespace default 24 | 25 | kubectl -n getting-started apply -f ./docs/getting-started/sa.yaml 26 | 27 | kubectl -n getting-started apply -f ./docs/resources.yaml 28 | 29 | kubectl -n getting-started apply -f ./docs/getting-started/pipeline.yaml 30 | 31 | kubectl -n getting-started apply -f ./docs/getting-started/triggers.yaml 32 | 33 | // kubectl -n getting-started apply -f docs/getting-started/secret.yaml 34 | 35 | // kubectl -n getting-started apply -f docs/getting-started/webhook-run.yaml 36 | 37 | 38 | 39 | In browser, should return a JSON payload. 40 | 41 | ## Test 42 | 43 | git commit -a -m "build commit" --allow-empty && git push origin main 44 | 45 | kubectl -n getting-started logs -l tekton.dev/pipeline=getting-started-pipeline --all-containers 46 | 47 | ## Tear down 48 | kubectl delete namespace getting-started -------------------------------------------------------------------------------- /docs/events.md: -------------------------------------------------------------------------------- 1 | 7 | # Events in Tekton Triggers 8 | 9 | Triggers event controller emits [Kubernetes events](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#event-v1-core) 10 | when EventListener get request to process `Triggers`. This allows you to monitor and react to what's happening during execution by 11 | retrieving those events using the `kubectl get events` command. 12 | 13 | ## Events in `EventListener` 14 | 15 | `EventListener` emit events for the following `Reasons`: 16 | 17 | - `Started`: emitted the first time when the `EventListener` received request. 18 | - `Succeeded`: emitted when eventlistener received request and process all triggers request. 19 | - `Done`: emitted when its done with eventlistener handler. 20 | - `Failed`: emitted if triggers failed to process the request. 21 | 22 | ## Events format 23 | 24 | Resource |Event |Event Type 25 | :-------------------|:---------:|:---------------------------------------------------------- 26 | `EventListener` | `Started` | `dev.tekton.event.triggers.started.v1` 27 | `EventListener` | `Succeed` | `dev.tekton.event.triggers.successful.v1` 28 | `EventListener` | `Done` | `dev.tekton.event.triggers.done.v1` 29 | `EventListener` | `Failed` | `dev.tekton.event.triggers.failed.v1` 30 | 31 | ##Note 32 | By default Kubernetes events are disabled for EventListener. 33 | 34 | To enable Kubernetes events add/update controller.yaml with below arg 35 | ```yaml 36 | spec: 37 | serviceAccountName: tekton-triggers-controller 38 | containers: 39 | - name: tekton-triggers-controller 40 | image: "ko://github.com/tektoncd/triggers/cmd/controller" 41 | args: [ 42 | "-el-events", "enable", 43 | ``` 44 | Default value is **disable**. 45 | 46 | To view the events execute below command 47 | 48 | `kubectl get events` 49 | -------------------------------------------------------------------------------- /docs/getting-started/create-webhook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1beta1 2 | kind: Task 3 | metadata: 4 | name: create-webhook 5 | spec: 6 | volumes: 7 | - name: github-secret 8 | secret: 9 | secretName: $(params.GitHubSecretName) 10 | 11 | params: 12 | - name: ExternalDomain 13 | description: "The external domain for the EventListener e.g. `$(params.EventListenerName)..nip.io`" 14 | - name: GitHubUser 15 | description: "The GitHub user" 16 | - name: GitHubRepo 17 | description: "The GitHub repo where the webhook will be created" 18 | - name: GitHubOrg 19 | description: "The GitHub organization where the webhook will be created" 20 | - name: GitHubSecretName 21 | description: "The Secret name for GitHub access token. This is always mounted and must exist" 22 | - name: GitHubAccessTokenKey 23 | description: "The GitHub access token key name" 24 | - name: GitHubSecretStringKey 25 | description: "The GitHub secret string key name" 26 | - name: GitHubDomain 27 | description: "The GitHub domain. Override for GitHub Enterprise" 28 | default: "github.com" 29 | - name: WebhookEvents 30 | description: "List of events the webhook will send notifications for" 31 | default: '[\"push\",\"pull_request\"]' 32 | steps: 33 | - name: create-webhook 34 | image: curlimages/curl:latest 35 | volumeMounts: 36 | - name: github-secret 37 | mountPath: /var/secret 38 | command: 39 | - sh 40 | args: 41 | - -ce 42 | - | 43 | set -e 44 | echo "Create Webhook" 45 | if [ $(params.GitHubDomain) = "github.com" ];then 46 | curl -v -d "{\"name\": \"web\",\"active\": true,\"events\": $(params.WebhookEvents),\"config\": {\"url\": \"https://$(params.ExternalDomain)\",\"content_type\": \"json\",\"insecure_ssl\": \"1\" ,\"secret\": \"$(cat /var/secret/$(params.GitHubSecretStringKey))\"}}" -X POST -u $(params.GitHubUser):$(cat /var/secret/$(params.GitHubAccessTokenKey)) -L https://api.github.com/repos/$(params.GitHubOrg)/$(params.GitHubRepo)/hooks 47 | else 48 | curl -d "{\"name\": \"web\",\"active\": true,\"events\": $(params.WebhookEvents),\"config\": {\"url\": \"https://$(params.ExternalDomain)/\",\"content_type\": \"json\",\"insecure_ssl\": \"1\" ,\"secret\": \"$(cat /var/secret/$(params.GitHubSecretStringKey))\"}}" -X POST -u $(params.GitHubUser):$(cat /var/secret/$(params.GitHubAccessTokenKey)) -L https://$(params.GitHubDomain)/api/v3/repos/$(params.GitHubOrg)/$(params.GitHubRepo)/hooks 49 | fi 50 | -------------------------------------------------------------------------------- /ocean-api/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* -------------------------------------------------------------------------------- /docs/getting-started/triggers.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: triggers.tekton.dev/v1alpha1 2 | kind: TriggerTemplate 3 | metadata: 4 | name: getting-started-triggertemplate 5 | namespace: getting-started 6 | spec: 7 | params: 8 | - name: gitrevision 9 | description: The git revision 10 | default: master 11 | - name: gitrepositoryurl 12 | description: The git repository url 13 | - name: namespace 14 | description: The namespace to create the resources 15 | resourcetemplates: 16 | - apiVersion: tekton.dev/v1beta1 17 | kind: PipelineRun 18 | metadata: 19 | generateName: getting-started-pipeline-run- 20 | namespace: $(tt.params.namespace) 21 | spec: 22 | serviceAccountName: tekton-triggers-example-sa 23 | pipelineRef: 24 | name: getting-started-pipeline 25 | workspaces: 26 | - name: source-workspace 27 | subPath: source 28 | persistentVolumeClaim: 29 | claimName: buildpacks-source-pvc 30 | # - name: cache-workspace 31 | # subPath: cache 32 | # persistentVolumeClaim: 33 | # claimName: buildpacks-source-pvc 34 | resources: 35 | - name: source-repo 36 | resourceSpec: 37 | type: git 38 | params: 39 | - name: revision 40 | value: $(tt.params.gitrevision) 41 | - name: url 42 | value: $(tt.params.gitrepositoryurl) 43 | - name: image-source 44 | resourceSpec: 45 | type: image 46 | params: 47 | - name: url 48 | value: hub.docker.com/repository/docker/jspruancedome/ocean-api # docker-repo-location.com/repo:getting-started 49 | - name: event-to-sink 50 | resourceSpec: 51 | type: cloudEvent 52 | params: 53 | - name: targetURI 54 | value: http://event-display.getting-started.svc.cluster.local 55 | params: 56 | - name: image 57 | value: hub.docker.com/repository/docker/jspruancedome/ocean-api # This defines the name of output image 58 | --- 59 | apiVersion: triggers.tekton.dev/v1alpha1 60 | kind: TriggerBinding 61 | metadata: 62 | name: getting-started-pipelinebinding 63 | namespace: getting-started 64 | spec: 65 | params: 66 | - name: gitrevision 67 | value: $(body.head_commit.id) 68 | - name: namespace 69 | value: getting-started 70 | - name: gitrepositoryurl 71 | value: "https://github.com/$(body.repository.full_name)" 72 | --- 73 | apiVersion: triggers.tekton.dev/v1alpha1 74 | kind: EventListener 75 | metadata: 76 | name: getting-started-listener 77 | namespace: getting-started 78 | spec: 79 | serviceAccountName: tekton-triggers-example-sa 80 | triggers: 81 | - bindings: 82 | - ref: getting-started-pipelinebinding 83 | template: 84 | ref: getting-started-triggertemplate 85 | -------------------------------------------------------------------------------- /docs/triggers.md: -------------------------------------------------------------------------------- 1 | 7 | # `Triggers` 8 | 9 | A `Trigger` specifies what happens when the [`EventListener`](./eventlisteners.md) detects an event. A `Trigger` specifies a [`TriggerTemplate`](./triggertemplates.md), 10 | a [`TriggerBinding`](./triggerbindings.md), and optionally an [`Interceptor`](./interceptors.md). 11 | 12 | ## Structure of a `Trigger` 13 | 14 | When creating a `Trigger` definition you must specify the required fields and can also specify any of the optional fields listed below: 15 | 16 | - Required: 17 | - [`apiVersion`][kubernetes-overview] - Specifies the API version; for example `triggers.tekton.dev/v1alpha1`. 18 | - [`kind`][kubernetes-overview] - Specifies that this resource object is a `Trigger` object. 19 | - [`metadata`][kubernetes-overview] - Specifies metadata to uniquely identify this `Trigger` object; for example a `name`. 20 | - [`spec`][kubernetes-overview] - Specifies the configuration information for this Trigger object, including: 21 | - [`bindings`] - (Optional) Specifies a list of field bindings; each binding can either reference an existing `TriggerBinding` or embedded a `TriggerBinding` 22 | definition using a `name`/`value` pair. 23 | - [`template`] - Specifies the corresponding `TriggerTemplate` either as a reference as an embedded `TriggerTemplate` definition. 24 | - [`interceptors`] - (Optional) specifies one or more `Interceptors` that will process the payload data before passing it to the `TriggerTemplate`. 25 | - [`serviceAccountName`] - (Optional) Specifies the `ServiceAccount` to supply to the `EventListener` to instantiate/execute the target resources. 26 | 27 | Below is an example `Trigger` definition: 28 | 29 | 30 | ```YAML 31 | apiVersion: triggers.tekton.dev/v1beta1 32 | kind: Trigger 33 | metadata: 34 | name: trigger 35 | spec: 36 | interceptors: 37 | - ref: 38 | name: "cel" 39 | params: 40 | - name: "filter" 41 | value: "header.match('X-GitHub-Event', 'pull_request')" 42 | - name: "overlays" 43 | value: 44 | - key: extensions.truncated_sha 45 | expression: "body.pull_request.head.sha.truncate(7)" 46 | bindings: 47 | - ref: pipeline-binding 48 | template: 49 | ref: pipeline-template 50 | ``` 51 | 52 | ## Specifying the corresponding `TriggerTemplate` 53 | 54 | In the `template` field, you can do one of the following: 55 | 56 | * Use the `name` parameter to reference an external `TriggerTemplate` object, or 57 | 58 | * Use the `spec` parameter to directly embed a `TriggerTemplate` definition. 59 | 60 | For example: 61 | 62 | ```yaml 63 | # Example: embedded TriggerTemplate definition 64 | triggers: 65 | - name: "my-trigger" 66 | template: 67 | spec: 68 | params: 69 | - name: "my-param-name" 70 | resourcetemplates: 71 | - apiVersion: "tekton.dev/v1beta1" 72 | kind: TaskRun 73 | metadata: 74 | generateName: "pr-run-" 75 | spec: 76 | taskSpec: 77 | steps: 78 | - image: ubuntu 79 | script: echo "hello there" 80 | ``` 81 | 82 | [kubernetes-overview]: 83 | https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields 84 | 85 | -------------------------------------------------------------------------------- /docs/getting-started/create-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1beta1 2 | kind: Task 3 | metadata: 4 | name: create-ingress 5 | spec: 6 | volumes: 7 | - name: work 8 | emptyDir: {} 9 | 10 | params: 11 | - name: CreateCertificate 12 | description: "Enables/disables the creation of a self-signed certificate for $(params.ExternalDomain)" 13 | default: "true" 14 | - name: CertificateKeyPassphrase 15 | description: "Phrase that protects private key. This must be provided when the self-signed certificate is created" 16 | - name: CertificateSecretName 17 | description: "Secret name for Ingress certificate. The Secret should not exist if the self-signed certificate creation is enabled" 18 | - name: ExternalDomain 19 | description: "The external domain for the EventListener e.g. `$(params.EventListenerName).PROXYIP.nip.io`" 20 | - name: Service 21 | description: "The name of the Service used in the Ingress. This will also be the name of the Ingress." 22 | - name: ServicePort 23 | description: "The service port that the ingress is being created on" 24 | - name: ServiceUID 25 | description: "The uid of the service. If set, this creates an owner reference on the service" 26 | default: "" 27 | 28 | steps: 29 | - name: generate-certificate 30 | image: frapsoft/openssl 31 | volumeMounts: 32 | - name: work 33 | mountPath: /var/tmp/work 34 | command: 35 | - sh 36 | args: 37 | - -ce 38 | - | 39 | set -e 40 | cat < 8 | 9 | This document shows you how to install and set up Tekton Triggers. 10 | 11 | ## Prerequisites 12 | 13 | - [Kubernetes] cluster version 1.18 or later. 14 | - [Kubectl]. 15 | - [Tekton Pipelines][pipelines-install]. 16 | - Grant `cluster-admin` privileges to the user that installed Tekton Pipelines. See 17 | the [kubernetes role-based access control (RBAC) docs][rbac]. 18 | 19 | ## Installation 20 | 21 | 1. Log on to your Kubernetes cluster with the same user account that installed 22 | Tekton Pipelines. 23 | 24 | 1. Depending on which version of Tekton Triggers you want to install, run one 25 | of the following commands: 26 | 27 | - **Latest official release** 28 | 29 | ```bash 30 | kubectl apply --filename \ 31 | https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml 32 | kubectl apply --filename \ 33 | https://storage.googleapis.com/tekton-releases/triggers/latest/interceptors.yaml 34 | ``` 35 | 36 | - **Nightly release** 37 | 38 | ```bash 39 | kubectl apply --filename \ 40 | https://storage.googleapis.com/tekton-releases-nightly/triggers/latest/release.yaml 41 | kubectl apply --filename \ 42 | https://storage.googleapis.com/tekton-releases-nightly/triggers/latest/interceptors.yaml 43 | ``` 44 | 45 | - **Specific Release** 46 | 47 | ```bash 48 | kubectl apply --filename \ 49 | https://storage.googleapis.com/tekton-releases/triggers/previous/VERSION_NUMBER/release.yaml 50 | kubectl apply --filename \ 51 | https://storage.googleapis.com/tekton-releases/triggers/previous/VERSION_NUMBER/interceptors.yaml 52 | ``` 53 | 54 | Replace `VERSION_NUMBER` with the numbered version you want to install. 55 | For example, `v0.19.1`. 56 | 57 | - **Untagged Release** 58 | 59 | If your container runtime does not support `image-reference:tag@digest` (for 60 | example, `cri-o` used in OpenShift 4.x): 61 | 62 | ```bash 63 | kubectl apply --filename \ 64 | https://storage.googleapis.com/tekton-releases/triggers/latest/release.notags.yaml 65 | kubectl apply --filename \ 66 | https://storage.googleapis.com/tekton-releases/triggers/latest/interceptors.notags.yaml 67 | ``` 68 | 69 | 1. To monitor the installation, run: 70 | 71 | ```bash 72 | kubectl get pods --namespace tekton-pipelines --watch 73 | ``` 74 | 75 | When all components show `1/1` under the `READY` column, the installation is 76 | complete. Hit *Ctrl + C* to stop monitoring. 77 | 78 | ## Customization options 79 | 80 | You can customize the behavior of the Triggers Controller changing some values 81 | in the `config/feature-flags-triggers.yaml` file. 82 | 83 | + Enable alpha features. Set the value of `enable-api-fields:` to `"alpha"`, the 84 | default value is `"stable"`. This flag only applies to the v1beta1 API 85 | version. 86 | 87 | + Exclude labels. Set the `labels-exclusion-pattern:` field to a regex pattern. 88 | Labels that match this pattern are excluded from getting added to the 89 | resources created by the EventListener. By default this field is empty, so all 90 | labels added to the EventListener are propagated down. 91 | 92 | ## Further reading 93 | 94 | + [Get started with Tekton Triggers][get-started] 95 | + [Explore Tekton Triggers code examples][code-examples] 96 | 97 | [kubernetes]: https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/ 98 | [kubectl]: https://kubernetes.io/docs/tasks/tools/#kubectl 99 | [triggers]: https://tekton.dev/docs/triggers/ 100 | [get-started]: https://github.com/tektoncd/triggers/blob/main/docs/getting-started 101 | [code-examples]: https://github.com/tektoncd/triggers/tree/main/examples 102 | [pipelines-install]: https://github.com/tektoncd/pipeline/blob/main/docs/install.md 103 | [rbac]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ 104 | -------------------------------------------------------------------------------- /docs/getting-started/pipeline.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # The getting-started Pipeline will run through several tasks: 3 | # - source is pulled from git 4 | # - source is built into an image by img 5 | # - image output is pushed to ECR 6 | # - cloudevent emitted 7 | apiVersion: tekton.dev/v1beta1 8 | kind: Pipeline 9 | metadata: 10 | name: getting-started-pipeline 11 | namespace: getting-started 12 | spec: 13 | params: 14 | - name: image 15 | type: string 16 | description: image URL to push 17 | resources: 18 | - name: source-repo 19 | type: git 20 | - name: image-source 21 | type: image 22 | - name: event-to-sink 23 | type: cloudEvent 24 | workspaces: 25 | - name: source-workspace # Directory where application source is located. (REQUIRED) 26 | #- name: cache-workspace # Directory where cache is stored (OPTIONAL) 27 | tasks: 28 | - name: fetch-repository # This task fetches a repository from github, using the `git-clone` task you installed 29 | taskRef: 30 | name: git-clone 31 | workspaces: 32 | - name: output 33 | workspace: source-workspace 34 | params: 35 | - name: url 36 | value: https://github.com/jspruance/ocean-api.git 37 | - name: subdirectory 38 | value: "" 39 | - name: deleteExisting 40 | value: "true" 41 | - name: buildpacks # This task uses the `buildpacks` task to build the application 42 | taskRef: 43 | name: buildpacks 44 | runAfter: 45 | - fetch-repository 46 | workspaces: 47 | - name: source 48 | workspace: source-workspace 49 | #- name: cache 50 | # workspace: cache-workspace 51 | params: 52 | - name: APP_IMAGE 53 | value: "$(params.image)" 54 | - name: SOURCE_SUBPATH 55 | value: "" # This is the path within the samples repo you want to build (OPTIONAL, default: "") 56 | - name: BUILDER_IMAGE 57 | value: paketobuildpacks/builder:base # This is the builder we want the task to use (REQUIRED) 58 | - name: display-results 59 | runAfter: 60 | - buildpacks 61 | taskSpec: 62 | steps: 63 | - name: print 64 | image: docker.io/library/bash:5.1.4@sha256:b208215a4655538be652b2769d82e576bc4d0a2bb132144c060efc5be8c3f5d6 65 | script: | 66 | #!/usr/bin/env bash 67 | set -e 68 | echo "Digest of created app image: $(params.DIGEST)" 69 | params: 70 | - name: DIGEST 71 | params: 72 | - name: DIGEST 73 | value: $(tasks.buildpacks.results.APP_IMAGE_DIGEST) 74 | --- 75 | apiVersion: tekton.dev/v1beta1 76 | kind: Task 77 | metadata: 78 | name: deploy-locally 79 | namespace: getting-started 80 | spec: 81 | resources: 82 | inputs: 83 | - name: image-source 84 | type: image 85 | outputs: 86 | - name: event-to-sink 87 | type: cloudEvent 88 | steps: 89 | - name: run-kubectl 90 | image: lachlanevenson/k8s-kubectl 91 | command: ["kubectl"] 92 | args: 93 | - "run" 94 | - "tekton-triggers-built-me" 95 | - "--image" 96 | - "$(resources.inputs.image-source.url)" 97 | - "--env=PORT=8080" 98 | --- 99 | apiVersion: tekton.dev/v1beta1 100 | kind: Task 101 | metadata: 102 | name: build-docker-image 103 | namespace: getting-started 104 | spec: 105 | params: 106 | - name: pathToContext 107 | description: 108 | The build directory used by img 109 | default: /workspace/source-repo 110 | - name: pathToDockerFile 111 | type: string 112 | description: The path to the dockerfile to build 113 | default: $(resources.inputs.source-repo.path)/Dockerfile 114 | resources: 115 | inputs: 116 | - name: source-repo 117 | type: git 118 | outputs: 119 | - name: builtImage 120 | type: image 121 | steps: 122 | - name: build-and-push 123 | image: gcr.io/kaniko-project/executor:v0.16.0 124 | command: 125 | - /kaniko/executor 126 | args: 127 | - --dockerfile=$(params.pathToDockerFile) 128 | - --destination=$(resources.outputs.builtImage.url) 129 | - --context=$(params.pathToContext) 130 | --- 131 | # Finally, we need something to receive our cloudevent announcing success! 132 | # That is this services only purpose 133 | apiVersion: v1 134 | kind: Service 135 | metadata: 136 | name: event-display 137 | namespace: getting-started 138 | labels: 139 | app: event-display 140 | spec: 141 | type: ClusterIP 142 | ports: 143 | - name: listener 144 | port: 8080 145 | protocol: TCP 146 | selector: 147 | app: event-display 148 | --- 149 | apiVersion: v1 150 | kind: Pod 151 | metadata: 152 | name: event-display 153 | namespace: getting-started 154 | labels: 155 | app: event-display 156 | spec: 157 | hostname: event-display 158 | containers: 159 | - image: gcr.io/knative-releases/github.com/knative/eventing-sources/cmd/event_display 160 | name: web 161 | -------------------------------------------------------------------------------- /docs/clusterinterceptors.md: -------------------------------------------------------------------------------- 1 | 7 | # `ClusterInterceptors` 8 | 9 | Tekton Triggers ships with the `ClusterInterceptor` Custom Resource Definition (CRD), which allows you to implement a custom cluster-scoped Webhook-style `Interceptor`. 10 | 11 | A `ClusterInterceptor` specifies an external Kubernetes v1 Service running custom business logic that receives the event payload from the 12 | `EventListener` via an HTTP request and returns a processed version of the payload along with an HTTP 200 response. The `ClusterInterceptor` can also 13 | halt processing if the event payload does not meet criteria you have configured as well as add extra fields that are accessible in the `EventListener's` 14 | top-level `extensions` field to other [`Interceptors`](interceptors.md) and `ClusterInterceptors` chained with it and the associated `TriggerBinding`. 15 | 16 | ## Structure of a `ClusterInterceptor` 17 | 18 | A `ClusterInterceptor` definition consists of the following fields: 19 | 20 | - Required: 21 | - [`apiVersion`][kubernetes-overview] - specifies the target API version, for example `triggers.tekton.dev/v1alpha1` 22 | - [`kind`][kubernetes-overview] - specifies that this Kubernetes resource is a `ClusterInterceptor` object 23 | - [`metadata`][kubernetes-overview] - specifies data that uniquely identifies this `ClusterInterceptor` object, for example a `name` 24 | - [`spec`][kubernetes-overview] - specifies the configuration information for this `ClusterInterceptor` object, including: 25 | - [`clientConfig`] - specifies how a client, such as an `EventListener` communicates with this `ClusterInterceptor` object 26 | 27 | [kubernetes-overview]: 28 | https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields 29 | 30 | ## Configuring the client of the `ClusterInterceptor` 31 | 32 | The `clientConfig` field specifies the client, such as an `EventListener` and how it communicates with the `ClusterInterceptor` to exchange 33 | event payload and other data. You can configure this field in one of the following ways: 34 | 35 | - Specify the `url` field and as its value a URL at which the corresponding Kubernetes service listens for incoming requests from this `ClusterInterceptor` 36 | - Specify the `service` field and within it reference the corresponding Kubernetes service that's listening for incoming requests from this `ClusterInterceptor` 37 | 38 | For example: 39 | 40 | ```yaml 41 | spec: 42 | clientConfig: 43 | url: "http://interceptor-svc.default.svc/" 44 | --- 45 | spec: 46 | clientConfig: 47 | service: 48 | name: "my-interceptor-svc" 49 | namespace: "default" 50 | path: "/optional-path" # optional 51 | port: 8081 # defaults to 80 52 | ``` 53 | 54 | ## Configuring a Kubernetes Service for the `ClusterInterceptor` 55 | 56 | The Kubernetes object running the custom business logic for your `ClusterInterceptor` must meet the following criteria: 57 | 58 | - Fronted by a regular Kubernetes v1 Service listening on an HTTP port (default port is 80) 59 | - Accepts an HTTP `POST` request that contains an [`InterceptorRequest`](https://pkg.go.dev/github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1#InterceptorRequest) 60 | as a JSON body 61 | - Returns an HTTP 200 OK response that contains an [`InterceptorResponse`](https://pkg.go.dev/github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1#InterceptorResponse) 62 | as a JSON body. If the trigger processing should continue, the interceptor should set the `continue` field in the response to `true`. If the processing should be stopped, the interceptor should set the `continue` field to `false` and also provide additional information detailing the error in the `status` field. 63 | - Returns a response other than HTTP 200 OK only if payload processing halts due to a catastrophic failure. 64 | 65 | ### Running ClusterInterceptor as HTTPS 66 | 67 | Triggers now run clusterinterceptor as `https` server in order to support end to end secure connection and here is a [TEP](https://github.com/tektoncd/community/blob/main/teps/0102-https-connection-to-triggers-interceptor.md) which gives more detail about this support. 68 | 69 | By default Triggers run all core interceptor (GitHub, GitLab, BitBucket, CEL) as `HTTPS`. 70 | 71 | Triggers expose a new optional field `caBundle` as part of clusterinterceptor spec. 72 | 73 | Example: 74 | ```yaml 75 | spec: 76 | clientConfig: 77 | caBundle: 78 | service: 79 | name: "my-interceptor-svc" 80 | namespace: "default" 81 | path: "/optional-path" # optional 82 | port: 8443 83 | ``` 84 | 85 | Triggers uses knative pkg to generate key, cert, cacert and fill caBundle for core interceptors (GitHub, GitLab, BitBucket, CEL). 86 | 87 | Triggers now support writing custom interceptor for both `http` and `https`. Support of `http` for custom interceptor will be there for 1-2 releases, later it will be removed and only `https` will be supported. 88 | 89 | End user who write `https` custom interceptor need to pass `caBundle` as well as label 90 | ```yaml 91 | labels: 92 | server/type: https 93 | ``` 94 | to `ClusterInterceptor` in order to make secure connection with eventlistener. 95 | 96 | Here is the reference for writing [https server for custom interceptor](https://github.com/tektoncd/triggers/blob/main/cmd/interceptors/main.go). 97 | -------------------------------------------------------------------------------- /docs/triggertemplates.md: -------------------------------------------------------------------------------- 1 | 7 | # `TriggerTemplates` 8 | 9 | A `TriggerTemplate` is a resource that specifies a blueprint for the resource, such as a `TaskRun` or `PipelineRun`, that you want to instantiate 10 | and/or execute when your `EventListener` detects an event. It exposes parameters that you can use anywhere within your resource's template. 11 | 12 | `TriggerTemplates` currently support the following [Tekton Pipelines](https://github.com/tektoncd/pipeline) resources: 13 | 14 | `v1alpha1 ` | `v1beta1` 15 | ---------------------|--------- 16 | `Pipeline` | `Pipeline` 17 | `PipelineRun` | `PipelineRun` 18 | `Task` | `Task` 19 | `TaskRun` | `TaskRun` 20 | `ClusterTask` | `ClusterTask` 21 | `Condition` | 22 | `PipelineResource` | 23 | 24 | ## Structure of a `TriggerTemplate` 25 | 26 | Below is an example `TriggerTemplate` definition: 27 | 28 | 29 | ```YAML 30 | apiVersion: triggers.tekton.dev/v1beta1 31 | kind: TriggerTemplate 32 | metadata: 33 | name: pipeline-template 34 | spec: 35 | params: 36 | - name: gitrevision 37 | description: The git revision 38 | default: main 39 | - name: gitrepositoryurl 40 | description: The git repository url 41 | - name: message 42 | description: The message to print 43 | default: This is the default message 44 | - name: contenttype 45 | description: The Content-Type of the event 46 | resourcetemplates: 47 | - apiVersion: tekton.dev/v1beta1 48 | kind: PipelineRun 49 | metadata: 50 | generateName: simple-pipeline-run- 51 | spec: 52 | pipelineRef: 53 | name: simple-pipeline 54 | params: 55 | - name: message 56 | value: $(tt.params.message) 57 | - name: contenttype 58 | value: $(tt.params.contenttype) 59 | - name: git-revision 60 | value: $(tt.params.gitrevision) 61 | - name: git-url 62 | value: $(tt.params.gitrepositoryurl) 63 | workspaces: 64 | - name: git-source 65 | emptyDir: {} 66 | ``` 67 | 68 | Keep the following in mind: 69 | 70 | * If you don't specify the namespace, Tekton resolves it to the namespace of the `EventListener` that specifies the given `TriggerTemplate`. 71 | 72 | * The `$(uid)` variable is implicitly available to the resource templates you specify in your `TriggerTemplate` with a random value, just like 73 | the postfix generated by the Kubernetes `generateName` metadata field. This can be useful for resource templates that use internal references. 74 | 75 | * Tekton adds the following labels to all resource templates within a `TriggerTemplate`: 76 | 77 | * `tekton.dev/eventlistenter`:`` to help with housekeeping and garbage collection. 78 | * `tekton.dev/triggers-eventid`:`` to track resources created by a specific event. 79 | 80 | * To support arbitrary resource types, Tekton resolves resource templates internally as byte blobs. Because of this, Tekton only validates these 81 | resources when processing an event rather than at the creation of the `TriggerTemplate`. Thus, you can **only** specify Tekton resources in a 82 | `TriggerTemplate`. 83 | 84 | * As of Tekton Pipelines [0.8.0](https://github.com/tektoncd/pipeline/releases/tag/v0.8.0), you can embed resource definitions directly in 85 | your `TriggerTemplate` definition. To prevent a race condition between creating and using resources, you **must** embed each resource definition 86 | within the `PipelineRun` or `TaskRun` that uses that resource. 87 | 88 | ## Specifying parameters 89 | 90 | A `TriggerTemplate` allows you to declare parameters supplied by the associated `TriggerBinding` and/or `EventListener` as follows: 91 | 92 | * Declare your parameters in the `params` section of the `TriggerTemplate` definition. 93 | 94 | * You must specify a `name` and can optionally specify a `description` and a `default` value. 95 | 96 | * Tekton applies the value of the `default` field for each entry in the `params` array of your `TriggerTemplate` if it can't find a corresponding 97 | value in the associated `TriggerBinding` or cannot successfully extract the value from an HTTP header or body payload. 98 | 99 | * You can reference `tt.params` in the `resourcetemplates` section of your `TriggerTemplate` to make your `TriggerTemplate` reusable. 100 | 101 | * When you specify parameters in your resource template definitions, Tekton replaces the specified string with the parameter name, for example `$(tt.params.name)`. 102 | Therefore, simple string and number value replacements work fine directly in your YAML file. However, if a string has a numerical prefix, such as `123abcd`, 103 | Tekton can misinterpret it to be a number and throw an error. In such cases, enclose the affected parameter key in quotes (`"`). 104 | 105 | 106 | ## Embedding JSON objects within resource templates 107 | 108 | Tekton no longer replaces quotes (`"`) with escaped quotes (`\"`) and does not perform any escaping on variables in your resource templates. 109 | If you are embedding JSON objects as variables in your templates, you **must not** enclose them with quotes (`"`). If you have existing `TriggerTemplates` 110 | that use escaped quotes, add an annotation to work around this behavior change. 111 | 112 | For example, consider the following JSON object: 113 | 114 | ```json 115 | { 116 | "title": "this is \"demo\" body", 117 | "object": { 118 | "name": "testing" 119 | } 120 | } 121 | ``` 122 | 123 | If your `TriggerBinding` extracts `$(body.title)` then Tekton inserts it into your `TriggerTemplate` as `this is a \"demo\" body`. 124 | To work around this, annotate the `TriggerTemplate` as follows: 125 | 126 | ```yaml 127 | apiVersion: triggers.tekton.dev/v1alpha1 128 | kind: TriggerTemplate 129 | metadata: 130 | name: escaped-tt 131 | annotations: 132 | triggers.tekton.dev/old-escape-quotes: "true" 133 | spec: 134 | params: 135 | - name: title 136 | description: The title from the incoming body 137 | ``` 138 | 139 | This way, Tekton passes the value as `this is a \""demo\"" body`, which in itself is not valid JSON code; however, if you use a value with `$(body.object)` 140 | in a resource template that specifically passes it as a quoted string, then this workaround restores normal operation. This can also be useful for parsing 141 | a string containing JSON code in a command. 142 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | 6 | # Troubleshooting Tekton Triggers 7 | 8 | This page describes the debugging methods you can use to diagnose and fix issues with Tekton Triggers. 9 | 10 | ## Gathering `EventListener` logs 11 | 12 | You can gather `EventListener` logs using the Tekton `tkn` CLI tool or the Kubernetes `kubectl` CLI tool. 13 | 14 | Use the following `tkn` command to gather `EventListener` logs: 15 | 16 | ```shell 17 | $ tkn eventlistener logs 18 | ``` 19 | See the `tkn` CLI tool [documentation page](https://github.com/tektoncd/cli/blob/main/docs/cmd/tkn_eventlistener_logs.md) for this config for more information. 20 | 21 | Use the following `kubectl` command to gather `EventListener` logs: 22 | 23 | ```shell 24 | $ kubectl logs deploy/el- -n 25 | ``` 26 | 27 | To get a list of `EventListeners` for a given namespace, use the following command: 28 | 29 | ```shell 30 | $ kubectl get el -n 31 | NAME ADDRESS AVAILABLE REASON 32 | test-event-listener http://el-test-event-listener.default.svc.cluster.local:8080 True MinimumReplicasAvailable 33 | ``` 34 | 35 | ## Configuring debug logging for `EventListeners` 36 | 37 | `EventListeners` uses [`ConfigMap`](https://kubernetes.io/docs/concepts/configuration/configmap/) named `config-logging-triggers` 38 | in the namespace(typically tekton-pipelines) of the target Tekton Triggers controller. To view this `ConfigMap`, use the following command: 39 | 40 | ```shell 41 | $ kubectl get cm -n tekton-pipelines 42 | NAME DATA AGE 43 | config-logging-triggers 2 28m 44 | ``` 45 | 46 | Below is a typical `config-logging-listeners` `ConfigMap`: 47 | 48 | ```yaml 49 | apiVersion: v1 50 | kind: ConfigMap 51 | metadata: 52 | name: config-logging-triggers 53 | data: 54 | loglevel.eventlistener: info 55 | zap-logger-config: '{"level": "info","development": false,"sampling": {"initial": 56 | 100,"thereafter": 100},"outputPaths": ["stdout"],"errorOutputPaths": ["stderr"],"encoding": 57 | "json","encoderConfig": {"timeKey": "ts","levelKey": "level","nameKey": "logger","callerKey": 58 | "caller","messageKey": "msg","stacktraceKey": "stacktrace","lineEnding": "","levelEncoder": 59 | "","timeEncoder": "iso8601","durationEncoder": "","callerEncoder": ""}}' 60 | ``` 61 | 62 | To enable debug-level logging in Tekton Triggers, use the following command: 63 | 64 | ```shell 65 | $ kubectl patch cm config-logging-triggers -n tekton-pipelines -p '{"data": {"zap-logger-config": "{\n \"level\": \"debug\",\n \"development\": false,\n \"sampling\": {\n \"initial\": 100,\n \"thereafter\": 100\n },\n \"outputPaths\": [\"stdout\"],\n \"errorOutputPaths\": [\"stderr\"],\n \"encoding\": \"json\",\n \"encoderConfig\": {\n \"timeKey\": \"ts\",\n \"levelKey\": \"level\",\n \"nameKey\": \"logger\",\n \"callerKey\": \"caller\",\n \"messageKey\": \"msg\",\n \"stacktraceKey\": \"stacktrace\",\n \"lineEnding\": \"\",\n \"levelEncoder\": \"\",\n \"timeEncoder\": \"iso8601\",\n \"durationEncoder\": \"\",\n \"callerEncoder\": \"\"\n }\n}\n"}}' 66 | ``` 67 | 68 | You need to delete deployment of EventListener so that above configuration are applied to Environment variables of Deployment. 69 | ```shell 70 | $ kubectl delete deployment/el-{EventListenerName} 71 | ``` 72 | 73 | The `EventListener` will start with the new logging level. For example: 74 | 75 | ```json 76 | {"level":"info","ts":"2022-07-26T16:02:26.191Z","caller":"logging/config.go:116","msg":"Successfully created the logger."} 77 | {"level":"info","ts":"2022-07-26T16:02:26.191Z","caller":"logging/config.go:117","msg":"Logging level set to: debug"} 78 | ``` 79 | 80 | From this point on, every HTTP request that the `EventListener` logs contains additional information. For example: 81 | 82 | ```json 83 | {"level":"debug","ts":"2021-02-10T08:42:30.915Z","logger":"eventlistener","caller":"sink/sink.go:93","msg":"EventListener: demo-event-listener in Namespace: default handling event (EventID: 9x4mb) with path /testing, payload: {\"testing\": \"value\"} and header: map[Accept:[*/*] Content-Length:[20] Content-Type:[application/x-www-form-urlencoded] User-Agent:[curl/7.61.1] X-Auth:[testing]]","knative.dev/controller":"eventlistener","/triggers-eventid":"9x4mb","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"github.com/tektoncd/triggers/pkg/sink/sink.go","line":"93","function":"github.com/tektoncd/triggers/pkg/sink.Sink.HandleEvent"}} 84 | ``` 85 | 86 | **WARNING**: The `EventListener` logs all payload headers verbatim. This includes any sensitive information the headers might contain. 87 | 88 | To disable debug-level logging, use the following command: 89 | 90 | ```shell 91 | $ kubectl patch cm config-logging-triggers -n -p '{"data": {"loglevel.eventlistener": "info"}}' 92 | ``` 93 | 94 | This returns the logging level to `info`. 95 | 96 | ## Troubleshooting JSONPath issues 97 | 98 | You may see the following message in your logs: 99 | 100 | ```json 101 | {"level":"error","ts":"2021-02-10T08:43:47.409Z","logger":"eventlistener","caller":"sink/sink.go:230","msg":"failed to ApplyEventValuesToParams: failed to replace JSONPath value for param message: $(body.message): message is not found","knative.dev/controller":"eventlistener","/triggers-eventid":"c8f88","/trigger":"demo-trigger","logging.googleapis.com/labels":{},"logging.googleapis.com/sourceLocation":{"file":"github.com/tektoncd/triggers/pkg/sink/sink.go","line":"230","function":"github.com/tektoncd/triggers/pkg/sink.Sink.processTrigger"},"stacktrace":"github.com/tektoncd/triggers/pkg/sink.Sink.processTrigger\n\tgithub.com/tektoncd/triggers/pkg/sink/sink.go:230\ngithub.com/tektoncd/triggers/pkg/sink.Sink.HandleEvent.func1\n\tgithub.com/tektoncd/triggers/pkg/sink/sink.go:125"} 102 | ``` 103 | 104 | This means that the selected `Interceptor` is unable to parse the structure of the received JSON payload. To troubleshoot this, you must capture and inspect the payload for inconsistencies. 105 | 106 | ## Troubleshooting signature and token errors 107 | 108 | When sending a hook protected by a secret, GitHub includes an `X-Hub-Signature` object in the header, while GitLab includes an `X-GitLab-Token` object. 109 | You may see `no X-Hub-Signature set` and/or `no X-GitLab-Token header set` errors in your logs in one of the following scenarios: 110 | 111 | * If you specify a secret in your `Interceptor` but don't specify it in the hook. 112 | * You are sending unsigned payloads to an `Interceptor` that expects signed payloads. 113 | 114 | Note that depending on how you have configured your Tekton Triggers stack, these errors may not indicate an actual problem. For example, your stack may 115 | include some `Interceptors` that expect signed payloads and some that expected unsigned payloads. Since Tekton Triggers processes all `Interceptors` 116 | concurrently, `Interceptors` that expect signed payloads will log the above errors, while `Interceptors` that expect unsigned payloads will process 117 | those payloads successfully. 118 | -------------------------------------------------------------------------------- /docs/getting-started/README.md: -------------------------------------------------------------------------------- 1 | # Tutorial: Getting started with Tekton Triggers 2 | 3 | The following tutorial walks you through building and deploying a Docker image using 4 | Tekton Triggers to detect a GitHub webhook request and execute a `Pipeline`. 5 | 6 | ## Overview 7 | 8 | In this tutorial, you will: 9 | 10 | 1. Set up a [`Pipeline`](https://github.com/tektoncd/pipeline/blob/main/docs/pipelines.md) that builds a Docker image using 11 | [kaniko](https://github.com/GoogleContainerTools/kaniko) and deploys it locally on your Kubernetes cluster. The workflow 12 | in the `Pipeline` is as follows: 13 | 1. Retrieve the source code. 14 | 1. Build and push the source code into a Docker image. 15 | 1. Push the image to the specified repository. 16 | 1. Run the image locally. 17 | 18 | 2. Set up an [`EventListener`](https://github.com/tektoncd/triggers/blob/main/docs/eventlisteners.md) that accepts and processes GitHub push events. 19 | 20 | 3. Set up a [`TriggerTemplate`](https://github.com/tektoncd/triggers/blob/main/docs/triggertemplates.md) that instantiates a 21 | [`PipelineResource`](https://github.com/tektoncd/pipeline/blob/main/docs/resources.md) and executes a [`PipelineRun`](https://github.com/tektoncd/pipeline/blob/main/docs/pipelineruns.md) 22 | and its associated ['TaskRuns'](https://github.com/tektoncd/pipeline/blob/main/docs/taskruns.md) when the `EventListener` detects the push event from a GitHub repository. 23 | 24 | 4. Run the completed stack to experience Tekton Triggers in action. 25 | 26 | ## Prerequisites 27 | 28 | Before you begin, you must satisfy the following prerequisites: 29 | 30 | - [Set up a Kubernetes cluster](https://kubernetes.io/docs/setup/) that you can publicly access over the Internet. 31 | - [Install Tekton Pipelines](https://github.com/tektoncd/pipeline/blob/master/docs/install.md#installing-tekton-pipelines). 32 | Tekton Triggers installs on top of Tekton Pipelines. 33 | - [Install Tekton Triggers](../install.md). 34 | - Have a GitHub repository and select a Dockerfile within that repository as your build object. 35 | For this tutorial, you can fork our [example repo](https://github.com/iancoffey/ulmaceae). 36 | You must clone the selected repository locally. 37 | 38 | ## Configure your cluster 39 | 40 | Now that you have your Kubernetes cluster up and running, you must set up your namespace and RBAC. 41 | You will keep all of the artifacts for this tutorial within this namespace. This way, you can easily 42 | start over by deleting and recreating this namespace if necessary. 43 | 44 | **Note:** Record your `ingress` sub-domain or the external IP address of your 45 | cluster as you will need it to create your GitHub webhook later in this tutorial. 46 | 47 | Configure your cluster as follows: 48 | 49 | 1. Create a namespace named `getting-started` using the following command: 50 | 51 | ``` 52 | kubectl create namespace getting-started 53 | ``` 54 | 55 | 2. Create the [`admin` user, role, and rolebinding](./rbac/admin-role.yaml) using the following command: 56 | 57 | ``` 58 | kubectl -n getting-started apply -f ./docs/getting-started/rbac/admin-role.yaml \ 59 | -f ./docs/getting-started/rbac/clusterrolebinding.yaml 60 | ``` 61 | 3. (Optional) If you have already provisioned a cluster secret for a "Let's Encrypt" certificate, 62 | you must export it and then import it into your `getting-started` namespace. For example: 63 | 64 | ```bash 65 | kubectl get secret --namespace= -o yaml |\ 66 | grep -v '^\s*namespace:\s' |\ 67 | kubectl apply --namespace= -f - 68 | ``` 69 | 4. Create the [`create-webhook` user, role, and rolebinding](./rbac/webhook-role.yaml) using the following command: 70 | 71 | ``` 72 | kubectl -n getting-started apply -f ./docs/getting-started/rbac/webhook-role.yaml 73 | ``` 74 | This allows your webhook to work with Tekton Triggers. 75 | 76 | 5. (Optional) If your cluster doesn't have access to your Docker registry, you must add a secret to both your cluster 77 | and the `pipeline.yaml` file in this tutorial as follows: 78 | 1. Add a secret to your cluster as described in [Configuring `Task` execution credentials](https://github.com/tektoncd/pipeline/blob/main/docs/tutorial.md#configuring-task-execution-credentials). 79 | 2. Add the secret you created in the previous step to your `pipeline.yaml` file by adding the following to each `Task` within the file: 80 | 81 | ``` 82 | env: 83 | - name: "DOCKER_CONFIG" 84 | value: "/tekton/home/.docker/" 85 | ``` 86 | 87 | ## Install the example resources 88 | 89 | You are now ready to install the example resources to use in the tutorial: 90 | 91 | - A `Pipeline` 92 | - A `TriggerTemplate` 93 | - A `TriggerBinding` 94 | - An `EventListener` 95 | 96 | 1. Install the example [`Pipeline`](./pipeline.yaml) using the following command: 97 | 98 | ``` 99 | kubectl -n getting-started apply -f ./docs/getting-started/pipeline.yaml 100 | ``` 101 | 102 | 2. Install the example [Triggers resources](./triggers.yaml) as follows: 103 | 1. Update the `triggers.yaml` file with the repository to which you want your `Pipeline` to push 104 | the Docker image binary by replacing the `DOCKERREPO-REPLACEME` placeholder string throughout 105 | the file. 106 | 2. Apply the updated `triggers.yaml` file on your cluster using the following command: 107 | ``` 108 | kubectl -n getting-started apply -f ./docs/getting-started/triggers.yaml 109 | ``` 110 | 111 | Your Tekton stack is now configured to detect and respond to GitHub events. 112 | 113 | ## Create and execute the ingress and webhook `Tasks` 114 | 115 | Now, you must create and execute the following `Tasks`: 116 | - Ingress `Task` - exposes the `EventListener` at a publicly accessible address to which 117 | the GitHub webhook can send events. 118 | - Webhook `Task` - creates the Github webhook that sends events to your `EventListener`. 119 | 120 | 1. Create the ingress `Task`: 121 | 122 | ``` 123 | kubectl -n getting-started apply -f ./docs/getting-started/create-ingress.yaml 124 | ``` 125 | 126 | 2. Create the webhook `Task`: 127 | 128 | ``` 129 | kubectl -n getting-started apply -f ./docs/getting-started/create-webhook.yaml 130 | ``` 131 | 132 | 3. Update the `TaskRun` for the ingress `Task`. At the minimum, you must update the `ExternalDomain` 133 | field in the `docs/getting-started/ingress-run.yaml` file to match your DNS name. You might also 134 | need to modify other settings as appropriate. 135 | 136 | 4. Run the ingress `Task`: 137 | 138 | ``` 139 | kubectl -n getting-started apply -f docs/getting-started/ingress-run.yaml 140 | ``` 141 | 142 | 5. Create a [GitHub Personal Access Token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line#creating-a-token) 143 | with the following access privileges: 144 | - `public_repo` 145 | - `admin:repo_hook` 146 | 147 | This token can contain any plain text string. 148 | 149 | 6. Add the token to the `docs/getting-started/secret.yaml` file. Do NOT `base64`-encode the token when adding it to the `secret.yaml` file. 150 | 151 | 7. Create the required secret with the following command: 152 | ``` 153 | kubectl -n getting-started apply -f docs/getting-started/secret.yaml 154 | ``` 155 | 156 | 8. Update the `TaskRun` for the webhook `Task`. At the minimum, you must update the following fields 157 | in the `docs/getting-started/webhook-run.yaml` file: 158 | - `GitHubOrg` - the GitHub organization you're using for the namespace in this tutorial. 159 | - `GitHubUser` - your GitHub username. 160 | - `GitHubRepo` - the GitHub repository you're using for this tutorial. 161 | - `ExternalDomain` - set this to a value appropriate to your environment: the external domain of the event listener instance. 162 | - `GitHubDomain` (optional) - if you are using github enterprise, set this to your GitHub domain (e.g. `git.corp.com`) 163 | 164 | 9. Run the webhook `Task`: 165 | ``` 166 | kubectl -n getting-started apply -f docs/getting-started/webhook-run.yaml 167 | ``` 168 | 169 | ## Run the completed Tekton Triggers stack 170 | 171 | You are now ready to experience Tekton Triggers in action! Do the following: 172 | 173 | 1. Make an empty commit and push it to your repository: 174 | ``` 175 | git commit -a -m "build commit" --allow-empty && git push origin mybranch 176 | ``` 177 | 178 | 2. Monitor the execution of your `Tasks`: 179 | - Monitor the image builder `Task` using the following command: 180 | ``` 181 | kubectl logs -l somelabel=somekey --all-containers 182 | ``` 183 | - Monitor the deployer `Task` using the following command: 184 | ``` 185 | kubectl -n getting-started logs -l tekton.dev/pipeline=getting-started-pipeline --all-containers 186 | ``` 187 | 188 | You can see that the system is working and that pushing images to your repository results in 189 | a running `Pod` using the following command: 190 | ``` 191 | kubectl -n getting-started logs tekton-triggers-built-me --all-containers 192 | ``` 193 | 194 | Congratulations! Your new image has been retrieved, tested, vetted, built, docker-pushed and pulled, 195 | and is now running on your cluster as a `Pod`. 196 | 197 | ## Cleaning up 198 | 199 | To clean up, simply delete the `getting-started` namespace using the following command: 200 | ``` 201 | kubectl delete namespace getting-started 202 | ``` 203 | -------------------------------------------------------------------------------- /docs/triggerbindings.md: -------------------------------------------------------------------------------- 1 | 7 | # `TriggerBindings` 8 | 9 | A `TriggerBinding` allows you to extract fields from an event payload and bind them to named parameters that can then be 10 | used in a [`TriggerTemplate`](./triggertemplates.md). For instance, one can extract the commit SHA from an incoming event 11 | and pass it on to create a TaskRun that clones a repo at that particular commit. 12 | 13 | **Note:** Trigger uses the parameter name to match TriggerBinding params to TriggerTemplate params. In order to pass 14 | information, the param name used in the binding must match the param name used in the template. 15 | 16 | ## Structure of a `TriggerBinding` 17 | 18 | There are 3 way of declaring a `TriggerBinding`: 19 | 20 | 1. Inline or embedded inside a `Trigger` 21 | 22 | 2. Using the `TriggerBinding` custom resource so that the binding params can be reused across multiple trigger 23 | 24 | 3. Using the `ClusterTriggerBinding` custom resource in order to promote reuse within an entire cluster 25 | 26 | 27 | ### Inline bindings 28 | 29 | The simplest way to declare bindings is within a Trigger itself: 30 | 31 | ```yaml 32 | apiVersion: triggers.tekton.dev/v1beta1 33 | kind: Trigger 34 | metadata: 35 | name: push-trigger 36 | spec: 37 | bindings: 38 | - name: gitrevision 39 | value: $(body.head_commit.id) 40 | - name: gitrepositoryurl 41 | value: $(body.repository.url) 42 | template: 43 | ref: git-clone-template 44 | ``` 45 | 46 | ### `TriggerBindings` 47 | 48 | The binding used above can be put in a `TriggerBinding` CRD in order to be reused across multiple triggers: 49 | 50 | ```YAML 51 | apiVersion: triggers.tekton.dev/v1beta1 52 | kind: TriggerBinding 53 | metadata: 54 | name: pipeline-binding 55 | spec: 56 | params: 57 | - name: gitrevision 58 | value: $(body.head_commit.id) 59 | - name: gitrepositoryurl 60 | value: $(body.repository.url) 61 | - name: contenttype 62 | value: $(header.Content-Type) 63 | ``` 64 | 65 | A `Trigger` resource can then refer to this binding: 66 | 67 | ```yaml 68 | apiVersion: triggers.tekton.dev/v1beta1 69 | kind: Trigger 70 | metadata: 71 | name: push-trigger 72 | spec: 73 | bindings: 74 | - ref: pipeline-binding 75 | template: 76 | ref: git-clone-template 77 | ``` 78 | 79 | ### `ClusterTriggerBindings` 80 | 81 | A `ClusterTriggerBinding` is a cluster-scoped `TriggerBinding` that you can reuse across your entire cluster. 82 | You can reference a `ClusterTriggerBinding` in any `Trigger` in any namespace. You can specify multiple 83 | `ClusterTriggerBindings` within your `Trigger` as well as specify the same `ClusterTriggerBinding` in multiple 84 | `Triggers`. 85 | 86 | Below is an example `ClusterTriggerBinding` definition: 87 | 88 | 89 | ```YAML 90 | apiVersion: triggers.tekton.dev/v1beta1 91 | kind: ClusterTriggerBinding 92 | metadata: 93 | name: pipeline-clusterbinding 94 | spec: 95 | params: 96 | - name: gitrevision 97 | value: $(body.head_commit.id) 98 | - name: gitrepositoryurl 99 | value: $(body.repository.url) 100 | - name: contenttype 101 | value: $(header.Content-Type) 102 | ``` 103 | 104 | When referencing a `ClusterTriggerBinding`, you must specify a `kind` value within the `bindings` field. 105 | The default is `TriggerBinding` which denotes a namespaced `TriggerBinding`. For example: 106 | 107 | 108 | ```YAML 109 | --- 110 | apiVersion: triggers.tekton.dev/v1beta1 111 | kind: EventListener 112 | metadata: 113 | name: listener-clustertriggerbinding 114 | spec: 115 | serviceAccountName: tekton-triggers-example-sa 116 | triggers: 117 | - name: foo-trig 118 | bindings: 119 | - ref: pipeline-clusterbinding 120 | kind: ClusterTriggerBinding 121 | - ref: message-clusterbinding 122 | kind: ClusterTriggerBinding 123 | template: 124 | ref: pipeline-template 125 | ``` 126 | 127 | ## Specifying parameters 128 | 129 | A `TriggerBinding` allows you to specify parameters (`params`) that Tekton passes to the corresponding `TriggerTemplate`. 130 | For each parameter, you must specify a `name` and a `value` field with the appropriate values. 131 | 132 | ## Accessing data in HTTP JSON payloads 133 | 134 | Tekton can use a `TriggerBinding` to access data in the headers and body of an HTTP JSON payload. To do so, it uses 135 | JSONPath expressions encapsulated within a `$()` wrapper. Keys in HTTP JSON headers are case-sensitive. 136 | 137 | For example, below is a valid expression: 138 | 139 | ```shell 140 | $(body.key1) 141 | $(.body.key) 142 | ``` 143 | 144 | On the other hand, the expressions below are invalid: 145 | 146 | ```shell 147 | .body.key1 # INVALID - Expression is not wrapped in `$()`. 148 | $({body) # INVALID - Trailing curly brace is missing. 149 | ``` 150 | 151 | If a `$()` wrapper is embedded inside another `$()` wrapper, Tekton parses the contents of the innermost wrapper 152 | as the JSONPath expression. For example: 153 | 154 | ```shell script 155 | $($(body.b)) # Parsed as $(body.b) 156 | $($($(body.b))) # Parsed as $(body.b) 157 | ``` 158 | 159 | 160 | ## Accessing data added by [`Interceptors`](./interceptors.md) 161 | 162 | An `interceptor` can add additional useful data that can be used by a `TriggerBinding`. Data added by interceptors can be 163 | accessed under the top-level `extensions` field e.g. `$(extensions.field-name)`. In the following example, the CEL 164 | interceptor adds a field that is then accessed by the trigger binding: 165 | 166 | ```yaml 167 | apiVersion: triggers.tekton.dev/v1beta1 168 | kind: Trigger 169 | metadata: 170 | name: push-trigger 171 | spec: 172 | interceptors: 173 | - name: add-truncated-sha 174 | ref: 175 | name: "cel" 176 | params: 177 | - name: "overlays" 178 | value: 179 | - key: truncated_sha 180 | expression: "body.pull_request.head.sha.truncate(7)" 181 | bindings: 182 | - name: truncated_sha 183 | value: $(extensions.truncated_sha) 184 | template: 185 | ref: git-clone-template 186 | ``` 187 | 188 | ## Accessing EventListener Event Context 189 | 190 | The EventListener has a set of internal data points that are maintained for the complete processing 191 | of a single event. These values are available for use in `TriggerBinding` objects. 192 | 193 | This data can be accessed on the `context` parameter, as an example: 194 | 195 | ```shell 196 | $(context.eventID) # access the internal eventID of the request 197 | ``` 198 | 199 | ## Accessing JSON keys containing periods (`.`) 200 | 201 | To access a JSON key that contains a period (`.`), you must escape the period with a backslash (`\.`). For example: 202 | 203 | ```shell script 204 | # Body contains a `tekton.dev` field: {"body": {"tekton.dev": "triggers"}} 205 | $(body.tekton\.dev) -> "triggers" 206 | ``` 207 | 208 | ## Fallback to default values 209 | 210 | If Tekton fails to resolve the JSONPath expressions you have configured against the HTTP JSON payload, it 211 | falls back to the `default` value in the corresponding `TriggerTemplate`, if specified. 212 | 213 | 214 | ## Field binding examples 215 | 216 | Below are some examples of Tekton performing field binding based on the most commonly used field definitions: 217 | 218 | ```shell 219 | 220 | `$(body)` -> replaced by the entire body 221 | 222 | $(body) -> "{"key1": "value1", "key2": {"key3": "value3"}, "key4": ["value4", "value5", "value6"]}" 223 | 224 | $(body.key1) -> "value1" 225 | 226 | $(body.key2) -> "{"key3": "value3"}" 227 | 228 | $(body.key2.key3) -> "value3" 229 | 230 | $(body.key4[0]) -> "value4" 231 | 232 | $(body.key4[0:2]) -> "{"value4", "value5"}" 233 | 234 | # $(header) -> replaced by all headers from the event 235 | 236 | $(header) -> "{"One":["one"], "Two":["one","two","three"]}" 237 | 238 | $(header.One) -> "one" 239 | 240 | $(header.one) -> "one" 241 | 242 | $(header.Two) -> "one two three" 243 | 244 | $(header.Two[1]) -> "two" 245 | ``` 246 | 247 | ## Specifying multiple bindings 248 | 249 | You can specify multiple bindings within the `Trigger` definition in your [`EventListener`](eventlisteners.md). 250 | This allows you to reuse as well as mix-and-match your bindings across multiple `Trigger` definitions. For 251 | example, you can create a `Trigger` with a binding that extracts event data and another binding that provides 252 | information on the deployment environment: 253 | 254 | ```yaml 255 | apiVersion: triggers.tekton.dev/v1alpha1 256 | kind: TriggerBinding 257 | metadata: 258 | name: event-binding 259 | spec: 260 | params: 261 | - name: gitrevision 262 | value: $(body.head_commit.id) 263 | - name: gitrepositoryurl 264 | value: $(body.repository.url) 265 | --- 266 | apiVersion: triggers.tekton.dev/v1alpha1 267 | kind: TriggerBinding 268 | metadata: 269 | name: prod-env 270 | spec: 271 | params: 272 | - name: environment 273 | value: prod 274 | --- 275 | apiVersion: triggers.tekton.dev/v1alpha1 276 | kind: TriggerBinding 277 | metadata: 278 | name: staging-env 279 | spec: 280 | params: 281 | - name: environment 282 | value: staging 283 | --- 284 | apiVersion: triggers.tekton.dev/v1alpha1 285 | kind: EventListener 286 | metadata: 287 | name: listener 288 | spec: 289 | triggers: 290 | - name: prod-trigger 291 | bindings: 292 | - ref: event-binding 293 | - ref: prod-env 294 | template: 295 | ref: pipeline-template 296 | - name: staging-trigger 297 | bindings: 298 | - ref: event-binding 299 | - ref: staging-env 300 | template: 301 | ref: pipeline-template 302 | ``` 303 | 304 | ## Troubleshooting `TriggerBindings` 305 | 306 | You can use the `binding-eval` tool to evaluate your `TriggerBinding` against a specific HTTP request 307 | to determine the parameters that Tekton generates from that request when your corresponding `Trigger` executes. 308 | 309 | To install the `binding-eval` tool use the following command: 310 | 311 | ```sh 312 | $ go install github.com/tektoncd/triggers/cmd/binding-eval@{version} 313 | ``` 314 | 315 | Below is an example of using the tool to evaluate a `TriggerBinding`: 316 | 317 | ```sh 318 | $ cat testdata/triggerbinding.yaml 319 | apiVersion: tekton.dev/v1alpha1 320 | kind: TriggerBinding 321 | metadata: 322 | name: pipeline-binding 323 | spec: 324 | params: 325 | - name: foo 326 | value: $(body.test) 327 | - name: bar 328 | value: $(header.X-Header) 329 | 330 | $ cat testdata/http.txt 331 | POST /foo HTTP/1.1 332 | Content-Length: 16 333 | Content-Type: application/json 334 | X-Header: tacocat 335 | 336 | {"test": "body"} 337 | 338 | $ binding-eval -b testdata/triggerbinding.yaml -r testdata/http.txt 339 | [ 340 | { 341 | "name": "foo", 342 | "value": "body" 343 | }, 344 | { 345 | "name": "bar", 346 | "value": "tacocat" 347 | } 348 | ] 349 | ``` 350 | -------------------------------------------------------------------------------- /docs/images/TriggerFlow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Trigger
Trigger
TriggerBinding
TriggerBinding
TriggerTemplate
TriggerTemplate
Parameters
Parame...
EventListener Pod
Event...
Event
Event
Create Tekton Resources
Create Tekton Res...
ClusterInterceptor
ClusterInterceptor
Process
Payload
Proces...
Viewer does not support full SVG 1.1
-------------------------------------------------------------------------------- /docs/cel_expressions.md: -------------------------------------------------------------------------------- 1 | 7 | # CEL expression extensions 8 | 9 | The CEL expression is configured to expose parts of the request, and some custom 10 | functions to make matching easier. 11 | 12 | In addition to the custom function extension listed below, you can craft any 13 | valid CEL expression as defined by the 14 | [cel-spec language definition](https://github.com/google/cel-spec/blob/master/doc/langdef.md) 15 | 16 | ## String functions 17 | 18 | The [upstream CEL implementation](https://github.com/google/cel-go/) provides 19 | extensions to the CEL specification for manipulating strings. 20 | 21 | For example: 22 | 23 | ```javascript 24 | 'refs/heads/main'.split('/') // result = list ['refs', 'heads', 'main'] 25 | ['refs', 'heads', 'main'].join('/') // result = string 'refs/heads/main' 26 | 'my place'.replace('my ',' ') // result = string 'place' 27 | 'this that another'.replace('th ',' ', 2) // result = 'is at another' 28 | ``` 29 | 30 | The `replace` overload allows an optional limit on replacements. 31 | 32 | ## Notes on numbers in CEL expressions 33 | 34 | One thing to be aware of is how numeric values are treated in CEL expressions, 35 | JSON numbers are decoded to 36 | [CEL double](https://github.com/google/cel-spec/blob/master/doc/langdef.md#values) 37 | values. 38 | 39 | For example: 40 | 41 | ```json 42 | { 43 | "count": 2, 44 | "measure": 1.7 45 | } 46 | ``` 47 | 48 | In the JSON above, both numbers are parsed as CEL double (Go `float64`) values. 49 | 50 | This means that if you want to do integer arithmetic, you'll need to 51 | [use explicit conversion functions](https://github.com/google/cel-spec/blob/master/doc/langdef.md#numeric-values). 52 | 53 | From the CEL specification: 54 | 55 | > Note that currently there are no automatic arithmetic conversions for the 56 | > numeric types (int, uint, and double). 57 | 58 | You can either explicitly convert the number, or add another double value e.g. 59 | 60 | ```yaml 61 | interceptors: 62 | - cel: 63 | overlays: 64 | - key: count_plus_1 65 | expression: "body.count + 1.0" 66 | - key: count_plus_2 67 | expression: "int(body.count) + 2" 68 | - key: measure_times_3 69 | expression: "body.measure * 3.0" 70 | ``` 71 | 72 | These will be serialised back to JSON appropriately: 73 | 74 | ```json 75 | { 76 | "count_plus_1": 2, 77 | "count_plus_2": 3, 78 | "measure_times_3": 5.1 79 | } 80 | ``` 81 | 82 | ### Error messages in conversions 83 | 84 | The following example will generate an error with the JSON example. 85 | 86 | ```yaml 87 | interceptors: 88 | - cel: 89 | overlays: 90 | - key: bad_measure_times_3 91 | expression: "body.measure * 3" 92 | ``` 93 | 94 | **bad_measure_times_3** will fail with 95 | `failed to evaluate overlay expression 'body.measure * 3': no such overload` 96 | because there's no automatic conversion. 97 | 98 | ## CEL expression examples 99 | 100 | ### Matching on an element in an array. 101 | 102 | CEL provides several [macros](https://github.com/google/cel-spec/blob/master/doc/langdef.md#macros) which can operate on JSON objects. 103 | 104 | If you have a JSON body like this: 105 | 106 | ```json 107 | { 108 | "labels": [ 109 | { 110 | "name": "test-a" 111 | }, 112 | { 113 | "name":"test-b" 114 | } 115 | ] 116 | } 117 | ``` 118 | 119 | You can use this in filters in the following ways: 120 | 121 | * `filter: body.labels.exists(x, x.name == 'test-b')` is _true_ 122 | * `filter: body.labels.exists(x, x.name == 'test-c')` is _false_ 123 | * `filter: body.labels.exists_one(x, x.name.endsWith('-b'))` is _true_ 124 | * `filter: body.labels.exists_one(x, x.name.startsWith('test-'))` is _false_ 125 | * `filter: body.labels.all(x, x.name.startsWith('test-'))` is _true_ 126 | * `filter: body.labels.all(x, x.name.endsWith('-b'))` is _false_ 127 | 128 | You can also parse additional data from each of the labels: 129 | ```yaml 130 | overlays: 131 | - key: suffixes 132 | expression: "body.labels.map(x, x.name.substring(x.name.lastIndexOf('-')+1))" 133 | ``` 134 | This yields an array of `["a", "b"]` in the `suffixes` extension key. 135 | ```yaml 136 | overlays: 137 | - key: filtered 138 | expression: "body.labels.filter(x, x.name.endsWith('-b'))" 139 | ``` 140 | This would add an extensions key `filtered` with only one of the labels. 141 | ```yaml 142 | [ 143 | { 144 | "name": "test-b" 145 | } 146 | ] 147 | ``` 148 | 149 | ## cel-go extensions 150 | 151 | All the functionality from the cel-go project's [CEL extension](https://github.com/google/cel-go/tree/master/ext) is available in 152 | your CEL expressions. 153 | 154 | ### cel-go Bytes 155 | 156 | The cel-go project function `base64.decode` returns a [CEL `Bytes`](https://github.com/google/cel-spec/blob/master/doc/langdef.md#string-and-bytes-values) value. 157 | 158 | To compare this to a string, you will need to convert it to a Bytes type: 159 | 160 | ``` 161 | base64.decode(body.b64value) == b'hello' # compare to Bytes literal 162 | base64.decode(body.b64value) == bytes('hello') # convert to bytes. 163 | ``` 164 | 165 | ### Returning Bytes 166 | 167 | If you decode a base64 string with the cel-go base64 decoder, the result will 168 | be a set of base64 decoded bytes. To ensure the result is encoded as a string 169 | you will need to explicitly convert it to a CEL string. 170 | 171 | ```yaml 172 | interceptors: 173 | - cel: 174 | overlays: 175 | - key: base64_decoded 176 | expression: "string(base64.decode(body.b64Value))" 177 | ``` 178 | This will correctly appear in the extension as the decoded version. 179 | 180 | ## List of extensions 181 | 182 | The body from the `http.Request` value is decoded to JSON and exposed, and the 183 | headers are also available. 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 196 | 199 | 202 | 205 | 206 | 207 | 210 | 213 | 216 | 219 | 220 | 221 | 224 | 227 | 230 | 233 | 234 |
SymbolTypeDescriptionExample
194 | body 195 | 197 | map(string, dynamic) 198 | 200 | This is the decoded JSON body from the incoming http.Request exposed as a map of string keys to any value types. 201 | 203 |
body.value == 'test'
204 |
208 | header 209 | 211 | map(string, list(string)) 212 | 214 | This is the request Header. 215 | 217 |
header['X-Test'][0] == 'test-value'
218 |
222 | requestURL 223 | 225 | string 226 | 228 | This is the URL for the incoming HTTP request. 229 | 231 |
requestURL.parseURL().path
232 |
235 | 236 | NOTE: The header value is a Go `http.Header`, which is 237 | [defined](https://golang.org/pkg/net/http/#Header) as: 238 | 239 | ```go 240 | type Header map[string][]string 241 | ``` 242 | 243 | i.e. the header is a mapping of strings, to arrays of strings, see the `match` 244 | function on headers below for an extension that makes looking up headers easier. 245 | 246 | ## List of extension functions 247 | 248 | This lists custom functions that can be used from CEL expressions in the CEL 249 | interceptor. 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 262 | 265 | 268 | 271 | 272 | 273 | 276 | 279 | 282 | 285 | 286 | 287 | 290 | 293 | 296 | 299 | 300 | 301 | 304 | 307 | 310 | 313 | 314 | 315 | 318 | 321 | 324 | 327 | 328 | 329 | 332 | 335 | 338 | 341 | 342 | 343 | 346 | 349 | 354 | 357 | 358 | 359 | 362 | 365 | 368 | 371 | 372 | 373 | 376 | 379 | 382 | 385 | 386 | 387 | 390 | 393 | 396 | 399 | 400 | 401 | 404 | 407 | 427 | 430 | 431 | 432 | 435 | 438 | 441 | 444 | 445 |
SymbolTypeDescriptionExample
260 | match 261 | 263 | header.match(string, string) -> bool 264 | 266 | Uses the canonical header matching from Go's http.Request to match the header against the value. 267 | 269 |
header.match('x-test', 'test-value')
270 |
274 | canonical 275 | 277 | header.canonical(string) -> string 278 | 280 | Uses the canonical header matching from Go's http.Request to get the provided header name. 281 | 283 |
header.canonical('x-test')
284 |
288 | truncate 289 | 291 |
<string>.truncate(uint) -> string
292 |
294 | Truncates a string to no more than the specified length. 295 | 297 |
body.commit.sha.truncate(5)
298 |
302 | split 303 | 305 |
<string>.split(string) -> list(string)
306 |
308 | Splits a string on the provided separator value. 309 | 311 |
body.ref.split('/')
312 |
316 | join 317 | 319 |
<list(string)>.join(string) -> string
320 |
322 | Joins a list of strings on the provided separator value. 323 | 325 |
['body', 'refs', 'main'].join('/')
326 |
330 | decodeb64 **deprecated: please use base64.decode** 331 | 333 |
<string>.decodeb64() -> string
334 |
336 | Decodes a base64 encoded string. 337 | 339 |
body.message.data.decodeb64()
340 |
344 | compareSecret 345 | 347 |
<string>.compareSecret(string, string, string) -> bool
348 |
350 | Constant-time comparison of strings against secrets, this will fetch the secret using the combination of namespace/name and compare the token key to the string using a cryptographic constant-time comparison..

351 | The event-listener service account must have access to the secret. 352 | The parameters to the function are 1. the key within the secret, 2. the secret name, and 3. the namespace for the secret (optional, defaults to the namespace of the EventListener). 353 |

355 |
header.canonical('X-Secret-Token').compareSecret('', 'secret-name', 'namespace')
356 |
360 | compareSecret 361 | 363 |
<string>.compareSecret(string, string) -> bool
364 |
366 | This is almost identical to the version above, but only requires two arguments, the namespace is assumed to be the namespace for the event-listener. 367 | 369 |
header.canonical('X-Secret-Token').compareSecret('key', 'secret-name')
370 |
374 | parseJSON() 375 | 377 |
<string>.parseJSON() -> map<string, dyn>
378 |
380 | This parses a string that contains a JSON body into a map which which can be subsequently used in other expressions. 381 | 383 |
'{"testing":"value"}'.parseJSON().testing == "value"
384 |
388 | parseYAML() 389 | 391 |
<string>.parseYAML() -> map<string, dyn>
392 |
394 | This parses a string that contains a YAML body into a map which which can be subsequently used in other expressions. 395 | 397 |
'key1: value1\nkey2: value2\n'.parseYAML().key1 == "value"
398 |
402 | parseURL() 403 | 405 |
<string>.parseURL() -> map<string, dyn>
406 |
408 | This parses a string that contains a URL into a map with keys for the elements of the URL.
409 | The resulting map will contain the following keys for this URL "https://user:pass@example.com/test/path?s=testing#first"
410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 |
FieldExample
schemehttps
hostexample.com
path/test/path
rawQuerys=testing
fragmentfirst
query{"s": "testing"}
queryStrings{"s": ["testing"]}
auth{"username": "user", "password": "pass"}
421 | Note the difference between query and queryStrings, in 422 | query, multiple query params with the same name would be comma separated, for 423 | the case where a single string is provided, this will just be the single string 424 | value. For queryString the query param values are provided as a list, 425 | which can be accessed by indexing. 426 |
428 |
'https://example.com/test?query=testing'.parseURL().query['query'] == "testing"
429 |
433 | marshalJSON() 434 | 436 |
<jsonObjectOrList>.marshalJSON() -> <string>
437 |
439 | Returns the JSON encoding of 'jsonObjectOrList' as a string. 440 | 442 |
{"testing":"value"}.marshalJSON() == "{\"testing\": \"value\"}"
443 |
446 | 447 | ## Troubleshooting CEL expressions 448 | 449 | You can use the `cel-eval` tool to evaluate your CEL expressions against a specific HTTP request. 450 | 451 | To install the `cel-eval` tool use the following command: 452 | 453 | ```sh 454 | $ go install github.com/tektoncd/triggers/cmd/cel-eval@latest 455 | ``` 456 | 457 | Below is an example of using the tool to evaluate a CEL expression: 458 | 459 | ```sh 460 | $ cat testdata/expression.txt 461 | body.test.nested == "value" 462 | 463 | $ cat testdata/http.txt 464 | POST /foo HTTP/1.1 465 | Content-Length: 29 466 | Content-Type: application/json 467 | X-Header: tacocat 468 | 469 | {"test": {"nested": "value"}} 470 | 471 | $ cel-eval -e testdata/expression.txt -r testdata/http.txt 472 | true 473 | ``` 474 | -------------------------------------------------------------------------------- /docs/interceptors.md: -------------------------------------------------------------------------------- 1 | 7 | # Configuring `Interceptors` 8 | 9 | - [Overview](#overview) 10 | - [Specifying an `Interceptor`](#specifying-an-interceptor) 11 | - [Webhook `Interceptors`](#webhook-interceptors) 12 | - [GitHub `Interceptors`](#github-interceptors) 13 | - [GitLab `Interceptors`](#gitlab-interceptors) 14 | - [Bitbucket `Interceptors`](#bitbucket-interceptors) 15 | - [Bitbucket Server](#bitbucket-server) 16 | - [Bitbucket Cloud](#bitbucket-cloud) 17 | - [CEL `Interceptors`](#cel-interceptors) 18 | - [Implementing custom `Interceptors`](#implementing-custom-interceptors) 19 | 20 | ## Overview 21 | 22 | An `Interceptor` is a "catch-all" event processor for a specific platform that runs before the `TriggerBinding.` It allows you to perform payload filtering, verification (using a secret), 23 | transformation, define and test trigger conditions, and implement other useful processing. Once the event data passes through an `Interceptor`, it then goes to the `Trigger` before you pass 24 | the payload data to the `TriggerBinding`. You can also use an `Interceptor` to modify the behavior of the associated `Trigger`. 25 | 26 | Tekton Triggers currently supports two distinct `Interceptor` implementations: 27 | - Standalone `Interceptors`, which are instances of the [`ClusterInterceptor`](./clusterinterceptors.md) Custom Resource Definition (CRD). You specify these `Interceptors` by referencing them, 28 | along with the desired parameters, within your `EventListener`. You can use the `ClusterInterceptor` CRD to implement your own custom `Interceptors`. 29 | - Legacy `Interceptors`, which you define entirely as part of the `EventListener` definition. This implementation will eventually be deprecated, so please consider 30 | transitioning to standalone `Interceptors` as soon as possible. See [TEP-0026](https://github.com/tektoncd/community/blob/main/teps/0026-interceptor-plugins.md) for more context on this change. 31 | 32 | Tekton Triggers ships with the following `Interceptors` to help you get started: 33 | - [Webhook `Interceptors`](#webhook-interceptors) 34 | - [GitHub `Interceptors`](#github-interceptors) 35 | - [GitLab `Interceptors`](#gitlab-interceptors) 36 | - [Bitbucket `Interceptors`](#bitbucket-interceptors) 37 | - [Bitbucket Server](#bitbucket-server) 38 | - [Bitbucket Cloud](#bitbucket-cloud) 39 | - [CEL `Interceptors`](#cel-interceptors) 40 | 41 | ## Specifying an `Interceptor` 42 | 43 | To specify an `Interceptor` within your `EventListener`, create an `interceptors:` field with the following sub-fields: 44 | - `name` - (optional) a name that uniquely identifies this `Interceptor` definition 45 | - `ref` - a reference to a [`ClusterInterceptor`](./clusterinterceptors.md) object with the following fields: 46 | - `name` - the name of the referenced `ClusterInterceptor` 47 | - `kind` - (optional) specifies that the referenced Kubernetes object is a `ClusterInterceptor` object 48 | - `apiVersion` - (optional) specifies the target API version, for example `triggers.tekton.dev/v1alpha1` 49 | - `params` - `name`/`value` pairs that specify the parameters you want to pass to the `ClusterInterceptor` 50 | - `params` - (optional) `name`/`value` pairs that specify the desired parameters for the `Interceptor`; 51 | the `name` field takes a string, while the `value` field takes a valid JSON object 52 | 53 | Below is an example standalone `Interceptor` reference within an `EventListener` definition: 54 | 55 | ```yaml 56 | interceptors: 57 | - name: "validate GitHub payload and filter on eventType" 58 | ref: 59 | name: "github" 60 | params: 61 | - name: "secretRef" 62 | value: 63 | secretName: github-secret 64 | secretKey: secretToken 65 | - name: "eventTypes" 66 | value: ["pull_request"] 67 | - name: "CEL filter: only when PRs are opened" 68 | ref: 69 | name: "cel" 70 | params: 71 | - name: "filter" 72 | value: "body.action in ['opened', 'reopened']" 73 | ``` 74 | 75 | ### Webhook `Interceptors` 76 | 77 | **Note:** Tekton Triggers ships with only a legacy Webhook `Interceptor`. If you want to implement it using 78 | the standalone model, see [Implementing custom `Interceptors`](#implementing-custom-interceptors). 79 | 80 | A Webhook `Interceptor` allows you to process your event payload by an external Kubernetes object containing 81 | custom business logic. The Kubernetes object, exposed via a Kubernetes Service, receives event payload 82 | data from your `EventListener` via HTTP, applies its business logic to it, and returns the processed payload 83 | (both headers plus body) via a HTTP 200 response to the `EventListener`. 84 | 85 | This payload can then continue on to the `TriggerBinding` specified in the `EventListener`. If processing 86 | is not successful, the `Interceptor` recognizes that the returned HTTP response code is not 200 and halts 87 | further processing of the event payload. 88 | 89 | You can optionally specify additional data to merge with the event payload before sending out for processing 90 | by adding the data as [canonical](https://golang.org/pkg/net/textproto/#CanonicalMIMEHeaderKey) key/value pairs 91 | in the `Interceptor's` `header` field. 92 | 93 | Your external Kubernetes object must meet the following criteria: 94 | 95 | - Fronted by a regular Kubernetes v1 Service running on HTTP port 80, 96 | - Accepts JSON payloads over HTTP, 97 | - Accepts HTTP POST requests with JSON payloads, 98 | - Returns an HTTP 200 OK response when you want the `EventListener` to continue processing the event, 99 | - Returns a JSON object in the HTTP response body. 100 | - Returns headers expected by subsequently chained `Interceptors` and the associated `TriggerBinding`. 101 | 102 | **Note:** If your business logic does not modify either the HTTP payload's header or body, simply return the same HTTP 103 | header or body that you received. 104 | 105 | Below is an example Webhook `Interceptor` definition: 106 | 107 | ```yaml 108 | --- 109 | apiVersion: triggers.tekton.dev/v1alpha1 110 | kind: EventListener 111 | metadata: 112 | name: listener-interceptor 113 | spec: 114 | serviceAccountName: tekton-triggers-example-sa 115 | triggers: 116 | - name: foo-trig 117 | interceptors: 118 | - webhook: 119 | header: 120 | - name: Foo-Trig-Header1 121 | value: string-value 122 | - name: Foo-Trig-Header2 123 | value: 124 | - array-val1 125 | - array-val2 126 | objectRef: 127 | kind: Service 128 | name: gh-validate 129 | apiVersion: v1 130 | namespace: default 131 | bindings: 132 | - ref: pipeline-binding 133 | template: 134 | ref: pipeline-template 135 | ``` 136 | 137 | ### GitHub Interceptors 138 | 139 | A GitHub `Interceptor` contains logic that validates and filters GitHub webhooks. 140 | It can validate the webhook's origin as described in [Securing your webhooks](https://developer.github.com/webhooks/securing/) 141 | as well as filter incoming events by the criteria you specify. The GitHub `Interceptor` 142 | always preserves the payload data (both header and body) in its responses. 143 | 144 | To use a GitHub `Interceptor` as a GitHub webhook validator, do the following: 145 | 146 | 1. Create a secret string value. 147 | 2. Configure the `GitHub` webhook with that value. 148 | 3. Create a Kubernetes secret containing your secret value. 149 | 4. Pass the Kubernetes secret as a reference to your GitHub `Interceptor`. 150 | 151 | To use a GitHub `Interceptor` as a filter for event data, specify the event types 152 | you want the `Interceptor` to accept in the `eventTypes` field. The `Interceptor` 153 | accepts data event types listed in [Event types and payloads](https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads). 154 | 155 | Below is an example GitHub `Interceptor` reference: 156 | 157 | ```yaml 158 | triggers: 159 | - name: github-listener 160 | interceptors: 161 | - ref: 162 | name: "github" 163 | kind: ClusterInterceptor 164 | apiVersion: triggers.tekton.dev 165 | params: 166 | - name: "secretRef" 167 | value: 168 | secretName: github-secret 169 | secretKey: secretToken 170 | - name: "eventTypes" 171 | value: ["pull_request"] 172 | ``` 173 | 174 | For reference, below is an example legacy GitHub `Interceptor` definition: 175 | 176 | ```yaml 177 | triggers: 178 | - name: github-listener 179 | interceptors: 180 | - github: 181 | secretRef: 182 | secretName: github-secret 183 | secretKey: secretToken 184 | eventTypes: 185 | 186 | ``` 187 | 188 | For more information, see our [example](../examples/v1beta1/github) of using this `Interceptor`. 189 | 190 | ### GitLab Interceptors 191 | 192 | A GitLab `Interceptor` contains logic that validates and filters GitLab webhooks. 193 | It can validate the webhook's origin as described in [Webhooks](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html) 194 | as well as filter incoming events by the criteria you specify. The GitLab `Interceptor` 195 | always preserves the payload data (both header and body) in its responses. 196 | 197 | To use a GitLab `Interceptor` as a GitLab webhook validator, do the following: 198 | 199 | 1. Create a secret string value. 200 | 2. Configure the GitLab webhook with that value. 201 | 3. Create a Kubernetes secret containing your secret value. 202 | 4. Pass the Kubernetes secret as a reference to your GitLab `Interceptor`. 203 | 204 | To use a GitLab `Interceptor` as a filter for event data, specify the event types 205 | you want the `Interceptor` to accept in the `eventTypes` field. The `Interceptor` 206 | accepts data event types listed in [Events](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#events). 207 | 208 | Below is an example GitLab `Interceptor` reference: 209 | 210 | ```yaml 211 | interceptors: 212 | - ref: 213 | name: "gitlab" 214 | params: 215 | - name: "secretRef" 216 | value: 217 | secretName: foo 218 | secretKey: bar 219 | - name: "eventTypes" 220 | value: ["Push Hook"] 221 | ``` 222 | 223 | For reference, below is an example legacy GitLab `Interceptor` definition: 224 | 225 | ```yaml 226 | apiVersion: triggers.tekton.dev/v1alpha1 227 | kind: EventListener 228 | metadata: 229 | name: gitlab-listener-interceptor 230 | spec: 231 | serviceAccountName: tekton-triggers-example-sa 232 | triggers: 233 | - name: foo-trig 234 | interceptors: 235 | - gitlab: 236 | secretRef: 237 | secretName: foo 238 | secretKey: bar 239 | eventTypes: 240 | - Push Hook 241 | bindings: 242 | - ref: pipeline-binding 243 | template: 244 | ref: pipeline-template 245 | ``` 246 | 247 | ### Bitbucket `Interceptors` 248 | 249 | Bitbucket `Interceptors` has support for both Bitbucket server (which does secret validation and event filtering) and Bitbucket cloud (which does event filtering). 250 | 251 | ### Bitbucket Server 252 | 253 | A Bitbucket server contains logic that validates and filters [Bitbucket server](https://confluence.atlassian.com/bitbucketserver) webhooks. 254 | It can validate the webhook's origin as described in [Webhooks](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html). The Bitbucket `Interceptor` always preserves the payload 255 | data (both header and body) in its responses. 256 | 257 | To use a Bitbucket `Interceptor` as a Bitbucket server webhook validator, do the following: 258 | 259 | 1. Create a secret string value. 260 | 2. Configure the Bitbucket server webhook with that value. 261 | 3. Create a Kubernetes secret containing your secret value. 262 | 4. Pass the Kubernetes secret as a reference to your Bitbucket `Interceptor`. 263 | 264 | To use a Bitbucket `Interceptor` as a filter for event data, specify the event types 265 | you want the `Interceptor` to accept in the `eventTypes` field. The `Interceptor` 266 | accepts data event types listed in [Event payload](https://confluence.atlassian.com/bitbucketserver/event-payload-938025882.html). 267 | 268 | Below is an example Bitbucket `Interceptor` reference for server: 269 | 270 | ```yaml 271 | interceptors: 272 | - ref: 273 | name: "bitbucket" 274 | params: 275 | - name: secretRef 276 | value: 277 | secretName: bitbucket-server-secret 278 | secretKey: secretToken 279 | - name: eventTypes 280 | value: 281 | - repo:refs_changed 282 | ``` 283 | 284 | For reference, below is an example legacy Bitbucket `Interceptor` definition: 285 | 286 | ```yaml 287 | --- 288 | apiVersion: triggers.tekton.dev/v1alpha1 289 | kind: EventListener 290 | metadata: 291 | name: bitbucket-server-listener 292 | spec: 293 | serviceAccountName: tekton-triggers-example-sa 294 | triggers: 295 | - name: bitbucket-server-triggers 296 | interceptors: 297 | - ref: 298 | name: "bitbucket" 299 | params: 300 | - name: secretRef 301 | value: 302 | secretName: bitbucket-server-secret 303 | secretKey: secretToken 304 | - name: eventTypes 305 | value: 306 | - repo:refs_changed 307 | bindings: 308 | - ref: bitbucket-server-binding 309 | template: 310 | ref: bitbucket-server-template 311 | ``` 312 | 313 | ### Bitbucket Cloud 314 | 315 | At the moment, Bitbucket cloud does not support validating webhook payloads using shared secrets. 316 | See the [Secure Webhooks section](https://support.atlassian.com/bitbucket-cloud/docs/manage-webhooks/) of Bitbucket cloud docs for more information on securing your Bitbucket webhooks. 317 | 318 | To use a Bitbucket `Interceptor` as a filter for event data, specify the event types 319 | you want the `Interceptor` to accept in the `eventTypes` field. The `Interceptor` 320 | accepts data event types listed in [Event payload](https://support.atlassian.com/bitbucket-cloud/docs/event-payloads/). 321 | 322 | Below is an example Bitbucket `Interceptor` reference for cloud: 323 | 324 | ```yaml 325 | interceptors: 326 | - ref: 327 | name: "bitbucket" 328 | params: 329 | - name: eventTypes 330 | value: 331 | - repo:push 332 | ``` 333 | 334 | For reference, below is an example legacy Bitbucket `Interceptor` definition: 335 | 336 | ```yaml 337 | --- 338 | apiVersion: triggers.tekton.dev/v1beta1 339 | kind: EventListener 340 | metadata: 341 | name: bitbucket-cloud-listener 342 | spec: 343 | serviceAccountName: tekton-triggers-example-sa 344 | triggers: 345 | - name: bitbucket-cloud-triggers 346 | interceptors: 347 | - ref: 348 | name: "bitbucket" 349 | params: 350 | - name: eventTypes 351 | value: 352 | - repo:push 353 | bindings: 354 | - ref: bitbucket-cloud-binding 355 | template: 356 | ref: bitbucket-cloud-template 357 | ``` 358 | 359 | ### CEL Interceptors 360 | 361 | A CEL `Interceptor` allows you to filter and modify the payloads of incoming events using 362 | the [CEL](https://github.com/google/cel-spec/blob/master/doc/langdef.md) expression language. 363 | 364 | CEL `Interceptors` support `overlays`, which are CEL expressions that Tekton Triggers adds 365 | to the event payload in the top-level `extensions` field. `overlays` are accessible from 366 | `TriggerBindings`. 367 | 368 | In the example `overlays` definition below, the `Interceptor` adds two new fields to the 369 | event payload that the corresponding `TriggerBinding` will receive in addition to the standard 370 | `header` and `body fields`: `extensions.truncated_sha` and `extensions.branch_name`: 371 | 372 | ```yaml 373 | triggers: 374 | - name: cel-trig 375 | interceptors: 376 | - ref: 377 | name: cel 378 | params: 379 | - name: "overlays" 380 | value: 381 | - key: truncated_sha 382 | expression: "body.pull_request.head.sha.truncate(7)" 383 | - key: branch_name 384 | expression: "body.ref.split('/')[2]" 385 | ``` 386 | 387 | Below is the same example as a legacy `Interceptor`: 388 | 389 | ```yaml 390 | triggers: 391 | - name: cel-trig 392 | interceptors: 393 | - cel: 394 | overlays: 395 | - key: truncated_sha 396 | expression: "body.pull_request.head.sha.truncate(7)" 397 | - key: branch_name 398 | expression: "body.ref.split('/')[2]" 399 | ``` 400 | 401 | You can use the `key` element within the `overlays` definition to create new or replace existing 402 | elements within the `extensions` field. This does not modify the body of the event payload, but instead 403 | adds the extra fields to the top-level `extensions` field. 404 | 405 | For example, the following expression: 406 | 407 | ```yaml 408 | - key: short_sha 409 | expression: "truncate(body.pull_request.head.sha, 7)" 410 | ``` 411 | 412 | can access the `short_sha` field and its value that have been created in `extensions` field: 413 | 414 | ```json 415 | { 416 | "body": { 417 | "ref": "refs/heads/master", 418 | "pull_request": { 419 | "head": { 420 | "sha": "6113728f27ae82c7b1a177c8d03f9e96e0adf246" 421 | } 422 | } 423 | }, 424 | "extensions": { 425 | "short_sha": "6113728" 426 | } 427 | } 428 | ``` 429 | 430 | **Note:** You can also replace existing fields by specifying a key that matches the path to an existing field/value pair. 431 | 432 | You can access the extra fields added by a CEL `Interceptor` from your `TriggerBinding` as follows: 433 | 434 | ```yaml 435 | apiVersion: triggers.tekton.dev/v1alpha1 436 | kind: TriggerBinding 437 | metadata: 438 | name: pipeline-binding-with-cel-extensions 439 | spec: 440 | params: 441 | - name: gitrevision 442 | value: $(extensions.short_sha) 443 | - name: branch 444 | value: $(extensions.branch_name) 445 | ``` 446 | 447 | In the example CEL `Interceptor` definition below, the `cel-trig-with-matches` `Trigger` 448 | filters events that don't have an `'X-GitHub-Event'` header matching `'pull_request'` and 449 | adds an extra key to the JSON body of the payload with a truncated string derived from the hook 450 | body: 451 | 452 | ```yaml 453 | triggers: 454 | - name: cel-trig-with-matches 455 | interceptors: 456 | - ref: 457 | name: "cel" 458 | params: 459 | - name: "filter" 460 | value: "header.match('X-GitHub-Event', 'pull_request')" 461 | - name: "overlays" 462 | value: 463 | - key: truncated_sha 464 | expression: "body.pull_request.head.sha.truncate(7)" 465 | bindings: 466 | - name: sha 467 | value: $(extensions.truncated_sha) 468 | ``` 469 | 470 | Below is the same example as a legacy `Interceptor`: 471 | 472 | ```yaml 473 | triggers: 474 | - name: cel-trig-with-matches 475 | interceptors: 476 | - cel: 477 | filter: "header.match('X-GitHub-Event', 'pull_request')" 478 | overlays: 479 | - key: truncated_sha 480 | expression: "body.pull_request.head.sha.truncate(7)" 481 | bindings: 482 | - name: sha 483 | value: $(extensions.truncated_sha) 484 | ``` 485 | 486 | In the example CEL `Interceptor` definition below, the `filter` expression must 487 | return a `true` value for this `Trigger` to execute and apply the specified `overlays`: 488 | 489 | You also have the option to omit the `filter` expression entirely, in which case the `Interceptor` 490 | applies the specified `overlays` to the payload's body: 491 | 492 | ```yaml 493 | apiVersion: triggers.tekton.dev/v1alpha1 494 | kind: EventListener 495 | metadata: 496 | name: cel-eventlistener-no-filter 497 | spec: 498 | serviceAccountName: tekton-triggers-example-sa 499 | triggers: 500 | - name: cel-trig 501 | interceptors: 502 | - ref: 503 | name: "cel" 504 | params: 505 | - name: "overlays" 506 | value: 507 | - key: extensions.truncated_sha 508 | expression: "body.pull_request.head.sha.truncate(7)" 509 | bindings: 510 | - ref: pipeline-binding 511 | template: 512 | ref: pipeline-template 513 | ``` 514 | 515 | ### Chaining `Interceptors` 516 | 517 | You can chain `Interceptors` with the following constraints: 518 | 519 | - `ClusterInterceptors` do not modify the body of the event payload; instead, they add extra fields to the top-level `extensions` field. 520 | 521 | - Webhook `Interceptors` can modify the body of the event payload, but cannot access the top-level `extensions` field. 522 | 523 | #### Chaining ClusterInterceptors 524 | 525 | Each ClusterInterceptor can return values in the InterceptorResponse within the `extensions` field. These values are then added to the `extensions` field of the InterceptorRequest that is sent to the next interceptor in the chain. 526 | 527 | If two interceptors return an extensions field with the same name, the latter one will overwrite the one from the previous one i.e. if interceptors A and B both return `foo` in the Extensions field of the InterceptorResponse, the values written by B will overwrite the ones written by A. To prevent this, it is recommended that each cluster interceptor write to its own top level field i.e A returns `A.foo` and B return `B.foo` in the InterceptorResponse. 528 | 529 | #### Chaining Webhook Interceptors 530 | 531 | **Note:** We are working on changing the behavior of Webhook `Interceptors` to match that of CEL `Interceptors` so that both `Interceptor` types can share data via the top-level `extensions` field. 532 | 533 | Since Webhook `Interceptors` cannot access the `extensions` field, the `EventListener` adds the `extensions` field to the body of the event payload before passing it to any Webhook `Interceptor`. 534 | In the example `Interceptor` chain shown below, the first CEL `Interceptor` adds the `truncated_sha` field to the `extensions` field and the `EventListener` adds it to the body of the payload so that 535 | the Webhook `Interceptor` can access that data: 536 | 537 | ```yaml 538 | interceptors: 539 | - cel: 540 | overlays: 541 | - key: "truncated_sha" 542 | expression: "body.sha.truncate(5)" 543 | - webhook: 544 | objectRef: 545 | kind: Service 546 | name: some-interceptor 547 | apiVersion: v1 548 | - cel: 549 | filter: "body.extensions.truncated_sha == \"abcde\"" # Can also be extensions.truncated_sha == \"abcde\" 550 | ``` 551 | 552 | As a result, the body of the payload sent to the Webhook `Interceptor` is as follows: 553 | 554 | ``` 555 | { 556 | "sha": "abcdefghi", // Original field 557 | "extensions": { 558 | "truncated_sha": "abcde" 559 | } 560 | } 561 | ``` 562 | 563 | As long as the Webhook `Interceptor` does not modify the body of the payload, the last CEL interceptor in the chain and the target `TriggerBinding` can access the `truncated_sha` 564 | field both in the body of the payload as well as via the extra fields added to the top-level `extension` field, namely `$(body.extensions.truncated_sha)` as well as `$(extensions.truncated_sha)`. 565 | 566 | ## Implementing custom `Interceptors` 567 | 568 | Tekton Triggers ships with the `ClusterInterceptor` Custom Resource Definition (CRD), which you can use to implement custom `Interceptors`. See [`ClusterInterceptors`](./clusterinterceptors.md) for more information. 569 | -------------------------------------------------------------------------------- /docs/eventlisteners.md: -------------------------------------------------------------------------------- 1 | 7 | # `EventListeners` 8 | 9 | An `EventListener` is a Kubernetes object that listens for events at a specified port on your Kubernetes cluster. 10 | It exposes an addressable sink that receives incoming event and specifies one or more [`Triggers`](./triggers.md). 11 | The sink is a Kubernetes service running the sink logic inside a dedicated Pod. 12 | 13 | Each `Trigger`, in turn, allows you to specify one or more [`TriggerBindings`](./triggerbindings.md) that allow 14 | you to extract fields and their values from event payloads, and one or more [`TriggerTemplates`](./triggertemplates.md) 15 | that receive field values from the corresponding `TriggerBindings` and allow Tekton Triggers to instantiate resources 16 | such as `TaskRuns` and `PipelineRuns` with that data. 17 | 18 | If you need to modify, filter, or validate the event payload data before passing it to a `TriggerBinding`, you can optionally specify one 19 | or more [`Interceptors`](./interceptors.md). 20 | 21 | - [Structure of an `EventListener`](#structure-of-an-eventlistener) 22 | - [Specifying the Kubernetes service account](#specifying-the-kubernetes-service-account) 23 | - [Specifying `Triggers`](#specifying-triggers) 24 | - [Specifying `TriggerGroups`](#specifying-triggergroups) 25 | - [Specifying `Resources`](#specifying-resources) 26 | - [Specifying a `kubernetesResource` object](#specifying-a-kubernetesresource-object) 27 | - [Specifying `Service` configuration](#specifying-service-configuration) 28 | - [Specifying `Replicas`](#specifying-replicas) 29 | - [Specifying a `CustomResource` object](#specifying-a-customresource-object) 30 | - [Contract for the `CustomResource` object](#contract-for-the-customresource-object) 31 | - [Specifying `Interceptors`](#specifying-interceptors) 32 | - [Specifying `cloudEventURI`](#specifying-cloudeventuri) 33 | - [Constraining `EventListeners` to specific namespaces](#constraining-eventlisteners-to-specific-namespaces) 34 | - [Constraining `EventListeners` to specific labels](#constraining-eventlisteners-to-specific-labels) 35 | - [Disabling Payload Validation](#disabling-payload-validation) 36 | - [Labels in `EventListeners`](#labels-in-eventlisteners) 37 | - [Specifying `EventListener` timeouts](#specifying-eventlistener-timeouts) 38 | - [Annotations in `EventListeners`](#annotations-in-eventlisteners) 39 | - [Understanding `EventListener` response](#understanding-eventlistener-response) 40 | - [TLS HTTPS support in `EventListeners`](#tls-https-support-in-eventlisteners) 41 | - [Obtaining the status of deployed `EventListeners`](#obtaining-the-status-of-deployed-eventlisteners) 42 | - [Configuring logging for `EventListeners`](#configuring-logging-for-eventlisteners) 43 | - [Exposing an `EventListener` outside of the cluster](#exposing-an-eventlistener-outside-of-the-cluster) 44 | - [Exposing an `EventListener` using a Kubernetes `Ingress` object](#exposing-an-eventlistener-using-a-kubernetes-ingress-object) 45 | - [Exposing an `EventListener` using OpenShift Route](#exposing-an-eventlistener-using-openshift-route) 46 | - [Understanding the deployment of an `EventListener`](#understanding-the-deployment-of-an-eventlistener) 47 | - [Deploying `EventListeners` in multi-tenant scenarios](#deploying-eventlisteners-in-multi-tenant-scenarios) 48 | - [Deploying each `EventListener` in its own namespace](#deploying-each-eventlistener-in-its-own-namespace) 49 | - [Deploying multiple `EventListeners` in the same namespace](#deploying-multiple-eventlisteners-in-the-same-namespace) 50 | - [CloudEvents during Trigger Processing](#cloud-events-during-trigger-processing) 51 | 52 | 53 | ## Structure of an `EventListener` 54 | 55 | An `EventListener` definition consists of the following fields: 56 | 57 | - Required: 58 | - [`apiVersion`][kubernetes-overview] - specifies the target API version, for example `triggers.tekton.dev/v1alpha1` 59 | - [`kind`][kubernetes-overview] - specifies that this Kubernetes resource is an `EventListener` object 60 | - [`metadata`][kubernetes-overview] - specifies data that uniquely identifies this `EventListener` object, for example a `name` 61 | - [`spec`][kubernetes-overview] - specifies the configuration of your `EventListener`: 62 | - [`serviceAccountName`](#specifying-the-kubernetes-service-account) - Specifies the `ServiceAccount` the `EventListener` will use to instantiate Tekton resources 63 | - Optional: 64 | - [`triggers`](#specifying-triggers) - specifies a list of `Triggers` to execute upon event detection 65 | - [`cloudEventURI`](#specifying-cloudEventURI) - specifies the URI for cloudevent sink 66 | - [`resources`](#specifying-resources) - specifies the resources that will be available to the event listening service 67 | - [`namespaceSelector`](#constraining-eventlisteners-to-specific-namespaces) - specifies the namespace for the `EventListener`; this is where the `EventListener` looks for the specified `Triggers` and stores the Tekton objects it instantiates upon event detection 68 | - [`labelSelector`](#constraining-eventlisteners-to-specific-labels) - specifies the labels for which your `EventListener` recognizes `Triggers` and instantiates the specified Tekton objects 69 | 70 | [kubernetes-overview]: 71 | https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields 72 | 73 | See our [Tekton Triggers examples](https://github.com/tektoncd/triggers/tree/master/examples) for ready-to-use example `EventListener` definitions. 74 | 75 | ## Specifying the Kubernetes service account 76 | 77 | You must specify a Kubernetes service account in the `serviceAccountName` field that the `EventListener` will use to instantiate Tekton objects. 78 | 79 | Tekton Trigger creates 2 clusterroles while installing with necessary permissions required for an eventlistener. You can directly create bindings for your serviceaccount with the clusterroles. 80 | - A Kubernetes RoleBinding with `tekton-triggers-eventlistener-roles` clusterrole. 81 | - A Kubernetes ClusterRoleBinding with `tekton-triggers-eventlistener-clusterroles` clusterrole. 82 | 83 | You can checkout an example [here](../examples/rbac.yaml). 84 | - If you're using `namespaceSelectors` in your `EventListener`, you will have to create an additional `ClusterRoleBinding ` 85 | with `tekton-triggers-eventlistener-roles` clusterrole. 86 | 87 | ## Specifying `Triggers` 88 | 89 | You can optionally specify one or more `Triggers` that define the actions to take when the `EventListener` detects a qualifying event. You can specify *either* a reference to an 90 | external `Trigger` object *or* reference/define the `TriggerBindings`, `TriggerTemplates`, and `Interceptors` in the `Trigger` definition. A `Trigger` definition 91 | specifies the following fields: 92 | 93 | - `name` - (optional) a valid [Kubernetes name](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set) that uniquely identifies the `Trigger` 94 | - `interceptors` - (optional) a list of [`Interceptors`](#specifying-interceptors) that will process event payload data before passing it to the associated `TriggerBinding` 95 | - `bindings` - (optional) a list of `TriggerBindings` for this `Trigger`; you can either reference existing `TriggerBindings` or embed their definitions directly 96 | - `template` - (optional) a `TriggerTemplate` for this `Trigger`; you can either reference an existing `TriggerTemplate` or embed its definition directly 97 | - `triggerRef` - (optional) a reference to an external [`Trigger`](./triggers.md) 98 | 99 | Below is an example `Trigger` definition that references the desired `TriggerBindings`, `TriggerTemplates`, and `Interceptors`: 100 | 101 | ```yaml 102 | triggers: 103 | - name: trigger-1 104 | interceptors: 105 | - github: 106 | eventTypes: ["pull_request"] 107 | bindings: 108 | - ref: pipeline-binding # Reference to a TriggerBinding object 109 | - name: message # Embedded Binding 110 | value: Hello from the Triggers EventListener! 111 | template: 112 | ref: pipeline-template 113 | ``` 114 | 115 | Below is an example `Trigger` definition that specifies a reference to an external `Trigger` object: 116 | 117 | ```yaml 118 | triggers: 119 | - triggerRef: trigger 120 | ``` 121 | 122 | Below is an example `Trigger` definition that embeds a `triggerTemplate` definition directly: 123 | 124 | ```yaml 125 | triggers: 126 | - name: "my-trigger" 127 | template: 128 | spec: 129 | params: 130 | - name: "my-param-name" 131 | resourceTemplates: 132 | - apiVersion: "tekton.dev/v1beta1" 133 | kind: TaskRun 134 | metadata: 135 | generateName: "pr-run-" 136 | spec: 137 | taskSpec: 138 | steps: 139 | - image: ubuntu 140 | script: echo "hello there" 141 | ``` 142 | 143 | Below is an example `Trigger` definition tailored to a multi-tenant scenario in which you may not 144 | want all of your `Trigger` objects to have the same permissions as the `EventListener`. In such case, 145 | you can specify a different service account at the `Trigger` level. This service account overrides 146 | the service account specified in the `EventListener`. 147 | 148 | ```yaml 149 | triggers: 150 | - name: trigger-1 151 | serviceAccountName: trigger-1-sa 152 | interceptors: 153 | - github: 154 | eventTypes: ["pull_request"] 155 | bindings: 156 | - ref: pipeline-binding 157 | - ref: message-binding 158 | template: 159 | ref: pipeline-template 160 | ``` 161 | 162 | You must update the `Role` assigned to the service account specified in the `EventListener` as shown below 163 | to allow it to impersonate the service account specified in the `Trigger`: 164 | 165 | ```yaml 166 | rules: 167 | - apiGroups: [""] 168 | resources: ["serviceaccounts"] 169 | verbs: ["impersonate"] 170 | ``` 171 | 172 | ## Specifying `cloudEventURI` 173 | 174 | Specifying the URI for cloud event sink which receives [cloud events during Trigger Processing](#cloud-events-during-trigger-processing). 175 | 176 | ```yaml 177 | spec: 178 | cloudEventURI: http://eventlistener.free.beeceptor.com 179 | ``` 180 | 181 | ## Specifying `TriggerGroups` 182 | 183 | `TriggerGroups` is a feature that allows you to specify a set of interceptors that will process before a set of 184 | `Trigger` resources are processed by the eventlistener. The goal of this feature is described in 185 | [TEP-0053](https://github.com/tektoncd/community/blob/main/teps/0053-nested-triggers.md). `TriggerGroups` allow for 186 | a common set of interceptors to be defined inline in the `EventListenerSpec` before `Triggers` are invoked. 187 | 188 | You can optionally specify one or more `Triggers` that define the actions to take when the `EventListener` detects a qualifying event. You can specify *either* a reference to an 189 | external `Trigger` object *or* reference/define the `TriggerBindings`, `TriggerTemplates`, and `Interceptors` in the `Trigger` definition. A `TriggerGroup` definition specifies the following fields: 190 | 191 | - `name` - (optional) a valid [Kubernetes name](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set) that uniquely identifies the `TriggerGroup` 192 | - `interceptors` - a list of [`Interceptors`](#specifying-interceptors) that will process event payload data before passing it to the downstream `Triggers` 193 | - `triggerSelector` - a combination of a Kubernetes `labelSelector` and a `namespaceSelector` as defined later [in this document](#constraining-eventlisteners-to-specific-namespaces). These two fields work together to define the `Triggers` that will be processed once `Interceptors` processing completes. 194 | 195 | Below is an example EventListener that defines an inline `triggerGroup`: 196 | 197 | ```yaml 198 | apiVersion: triggers.tekton.dev/v1beta1 199 | kind: EventListener 200 | metadata: 201 | name: eventlistener 202 | spec: 203 | triggerGroups: 204 | - name: github-pr-group 205 | interceptors: 206 | - name: "validate GitHub payload and filter on eventType" 207 | ref: 208 | name: "github" 209 | params: 210 | - name: "secretRef" 211 | value: 212 | secretName: github-secret 213 | secretKey: secretToken 214 | - name: "eventTypes" 215 | value: ["pull_request"] 216 | triggerSelector: 217 | labelSelector: 218 | matchLabels: 219 | type: github-pr 220 | ``` 221 | 222 | This configuration would first process any event that is sent to the `EventListener` and determine if it matches 223 | the outlined conditions. If it passes these conditions, it will use the `triggerSelector` matching criteria to determine 224 | the target `Trigger` resources to continue processing. 225 | 226 | Any `extensions` fields added during `triggerGroup` processing are passed to the downstream `Trigger` execution. This allows 227 | for shared data across all Triggers that are processed after group execution completes. As an example, `extensions.myfield` would 228 | be available to all `Trigger` resources matched by this group: 229 | 230 | ```yaml 231 | apiVersion: triggers.tekton.dev/v1beta1 232 | kind: EventListener 233 | metadata: 234 | name: eventlistener 235 | spec: 236 | triggerGroups: 237 | - name: cel-filter-group 238 | interceptors: 239 | - name: "validate body and add field" 240 | ref: 241 | name: "cel" 242 | params: 243 | - name: "filter" 244 | value: "body.action in ['opened', 'reopened']" 245 | - name: "overlays" 246 | value: 247 | - key: myfield 248 | expression: "body.pull_request.head.sha.truncate(7)" 249 | triggerSelector: 250 | namespaceSelector: 251 | matchNames: 252 | - foo 253 | labelSelector: 254 | matchLabels: 255 | type: cel-preprocessed 256 | ``` 257 | 258 | At this time, each `TriggerGroup` determines its own downstream Triggers, so if two separate groups select the same 259 | downstream `Trigger` resources, it may be executed multiple times. If you use this feature, ensure that `Trigger` resources 260 | are labeled to be queried by the appropriate set of `TriggerGroups`. 261 | 262 | ## Specifying `Resources` 263 | 264 | You can optionally customize the sink deployment for your `EventListener` using the `resources` field. It accepts the following types of objects: 265 | - Kubernetes Resource using the `kubernetesResource` field 266 | - Custom Resource objects via the `CustomResource` field 267 | 268 | Legal values for the `PodSpec` and `Containers` sub-fields for both `kubernetesResource` and `CustomResource` fields are: 269 | ``` 270 | ServiceAccountName 271 | NodeSelector 272 | Tolerations 273 | Volumes 274 | Containers 275 | ``` 276 | 277 | Legal values for the `Containers` sub-field are: 278 | ``` 279 | Resources 280 | VolumeMounts 281 | Env 282 | ``` 283 | 284 | ### Specifying a `kubernetesResource` object 285 | 286 | Below is an example `resources:` field definition specifying a `kubernetesResource` object: 287 | 288 | ```yaml 289 | spec: 290 | resources: 291 | kubernetesResource: 292 | serviceType: NodePort 293 | servicePort: 80 294 | spec: 295 | template: 296 | metadata: 297 | labels: 298 | key: "value" 299 | annotations: 300 | key: "value" 301 | spec: 302 | serviceAccountName: tekton-triggers-github-sa 303 | nodeSelector: 304 | app: test 305 | tolerations: 306 | - key: key 307 | value: value 308 | operator: Equal 309 | effect: NoSchedule 310 | ``` 311 | 312 | #### Specifying `Service` configuration 313 | 314 | The type and port for the `Service` created for the `EventListener` can be configured via the `ServiceType` and `ServicePort` 315 | specifications respectively. By default, the `Service` type is set to `ClusterIP` and port is set to `8080`. 316 | ```yaml 317 | spec: 318 | resources: 319 | kubernetesResource: 320 | serviceType: LoadBalancer 321 | servicePort: 8128 322 | ``` 323 | 324 | #### Specifying `Replicas` 325 | 326 | You can optionally use the `replicas` field to instruct Tekton Triggers to deploy more than one instance of your `EventListener` in individual Kubernetes Pods. 327 | If you do not specify this value, the default number of instances (and thus, the number of respective Pods) per `EventListener` is 1. If you set a value for the `replicas` field 328 | while creating or upgrading the `EventListener's` YAML file, that value overrides any value you set manually later as well as a value set by any other deployment 329 | mechanism, such as HPA. 330 | 331 | ### Specifying a `CustomResource` object 332 | 333 | You can specify a Kubernetes Custom Resource object using the `CustomResource` field. This field has one sub-field, `runtime.RawExtension` that allows you to specify dynamic objects. 334 | 335 | #### Contract for the `CustomResource` object 336 | 337 | The `CustomResource` object must abide by the contract shown below. 338 | 339 | Contract-mandated CRD structure for the `spec` field: 340 | 341 | ```spec 342 | spec: 343 | template: 344 | metadata: 345 | spec: 346 | ``` 347 | 348 | Contract-mandated CRD structure for the `status` field: 349 | ```status 350 | type EventListenerStatus struct { 351 | duckv1beta1.Status `json:",inline"` 352 | 353 | // EventListener is addressable via the DNS address of the sink. 354 | duckv1alpha1.AddressStatus `json:",inline"` 355 | } 356 | ``` 357 | 358 | **Note:** The CRD must follow the [WithPod{}](https://github.com/knative/pkg/blob/master/apis/duck/v1/podspec_types.go#L41) spec. 359 | 360 | Below is an example `resources:` field definition specifying a `CustomResource` object using a [Knative Service](https://knative.dev/docs/): 361 | 362 | **Note:** This example assumes that [Knative is installed](https://github.com/tektoncd/community/blob/main/teps/0008-support-knative-service-for-triggers-eventlistener-pod.md#note) on your cluster. 363 | 364 | ```yaml 365 | spec: 366 | resources: 367 | customResource: 368 | apiVersion: serving.knative.dev/v1 369 | kind: Service 370 | # metadata: 371 | # name: knativeservice # name is optional; if not specified, Triggers substitutes the EventListener's name with an "el-" prefix, for example: el-github-knative-listener 372 | spec: 373 | template: 374 | spec: 375 | serviceAccountName: tekton-triggers-example-sa 376 | containers: 377 | - resources: 378 | requests: 379 | memory: "64Mi" 380 | cpu: "250m" 381 | limits: 382 | memory: "128Mi" 383 | cpu: "500m" 384 | ``` 385 | 386 | ## Specifying `Interceptors` 387 | 388 | An `Interceptor` is a "catch-all" event processor for a specific platform that runs before the `TriggerBinding`. It allows you to perform payload filtering, 389 | verification (using a secret), transformation, define and test trigger conditions, and implement other useful processing. Once the event data passes through 390 | an `Interceptor`, it then goes to the `Trigger` before you pass the payload data to the `TriggerBinding`. You can also use an `Interceptor` to modify the 391 | behavior of the associated `Trigger`. 392 | 393 | For more information, see [`Interceptors`](./interceptors.md). 394 | 395 | ## Constraining `EventListeners` to specific namespaces 396 | 397 | You can optionally specify a list of namespaces in which your `EventListener` will search for `Triggers` and instantiate the specified Tekton objects using the `namespaceSelector` field. 398 | 399 | If you omit this field, your `EventListener` will only recognize `Triggers` specified in its definition or found under one or more specified target labels. 400 | 401 | Below is an example `namespaceSelector` field that configures the `EventListener` to use the `foo` and `bar` namespaces: 402 | 403 | ```yaml 404 | namespaceSelector: 405 | matchNames: 406 | - foo 407 | - bar 408 | ``` 409 | 410 | If you want your `EventListener` to recognize `Triggers` across your entire cluster, use a wildcard between quote as the only namespace: 411 | 412 | ```yaml 413 | namespaceSelector: 414 | matchNames: 415 | - "*" 416 | ``` 417 | At present, if an EventListeners has `Triggers` inside its own spec as well as `namespace-selector`, `Triggers` in spec as well as in selected namespaces will be processed for a request. `Triggers` inside EventListener spec when using `namespace-selector` mode is deprecated and ability to specify both will be removed. 418 | 419 | ## Constraining `EventListeners` to specific labels 420 | 421 | You can optionally specify the labels for which your `EventListener` recognizes `Triggers` and instantiates the specified Tekton objects using the `labelSelector` field. 422 | This field uses the standard Kubernetes `labelSelector` mechanism and supports the `matchExpressions` sub-field. If you omit the `labelSelector` field, the `EventListener` 423 | accepts all resource labels. 424 | 425 | Below is an example `labelSelector` field definition that constrains your `EventListener` to only recognize `Triggers` within its own namespace that are labeled `foo=bar`: 426 | 427 | ```yaml 428 | labelSelector: 429 | matchLabels: 430 | foo: bar 431 | ``` 432 | 433 | Below is an example `labelSelector` field definition that uses the `matchExpression` sub-field to specify expressions that allow the `EventListener` to recognize `Triggers` 434 | across all namespaces in the cluster: 435 | 436 | ```yaml 437 | namespaceSelector: 438 | matchNames: 439 | - * 440 | labelSelector: 441 | matchExpressions: 442 | - {key: environment, operator: In, values: [dev,stage]} 443 | - {key: trigger-phase, operator: NotIn, values: [testing]} 444 | ``` 445 | 446 | ## Specifying `EventListener` timeouts 447 | 448 | An `EventListener` times out if it cannot process an event request within a timeout specified in [controller.yaml](../config/controller.yaml). The timeouts are as follows: 449 | - `-el-readtimeout`: Read timeout; default is 5 seconds. 450 | - `-el-writetimeout`: Write timeout; default is 40 seconds. 451 | - `-el-idletimeout`: Idle timeout; default is 120 seconds. 452 | - `-el-timeouthandler`: Server route handler timeout; default is 30 seconds. 453 | 454 | ## Disabling Payload Validation 455 | 456 | To disable incoming payload validation for an EventListener, you can define an annotation `tekton.dev/payload-validation: false` 457 | on EventListener. 458 | 459 | ``` 460 | apiVersion: triggers.tekton.dev/v1alpha1 461 | kind: EventListener 462 | metadata: 463 | name: eventlistener 464 | annotations: 465 | tekton.dev/payload-validation: "false" 466 | ``` 467 | 468 | By default, payload validation is enabled and will be disabled only if the annotation is defined. Removing the annotation will enable 469 | the payload validation. 470 | 471 | ## Labels in `EventListeners` 472 | 473 | By default, each `EventListener` automatically attaches the following labels to all resources it instantiates: 474 | 475 | | Name | Description | 476 | | --------------------------------- | ----------------------------------------------------------- | 477 | | triggers.tekton.dev/eventlistener | Name of the `EventListener` that instantiated the resource. | 478 | | triggers.tekton.dev/trigger | Name of the `Trigger` that instantiated the resource. | 479 | | triggers.tekton.dev/eventid | UID of the incoming event. | 480 | 481 | **Note:** Because they're used as labels, `EventListener` and `Trigger` names must conform to the [Kubernetes syntax and character set requirements](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set). 482 | 483 | ## Annotations in `EventListeners` 484 | 485 | Tekton Triggers propagates all annotations that you include in your `EventListener` to the Kubernetes service and deployment created by that `EventListener`. 486 | Keep in mind that annotations propagated from the `EventListener` override annotations already present in its Kubernetes service and deployment. 487 | 488 | Below is an example load balancer protocol annotation in an `EventListener` definition that automatically propagates to the `EventListener's` service: 489 | 490 | ``` 491 | apiVersion: triggers.tekton.dev/v1alpha1 492 | kind: EventListener 493 | metadata: 494 | name: eventlistener 495 | annotations: 496 | service.beta.kubernetes.io/aws-load-balancer-backend-protocol: https 497 | ``` 498 | 499 | ## Understanding `EventListener` response 500 | 501 | An `EventListener` responds with a `202 ACCEPTED` HTTP response when the `EventListener` 502 | has been able to process the request and selected the appropriate triggers to process 503 | based off the `EventListener` configuration. 504 | 505 | After detecting an event, the `EventListener` responds with the following message: 506 | 507 | ```json 508 | { 509 | "eventListener": "listener", 510 | "namespace": "default", 511 | "eventListenerUID": "ea71a6e4-9531-43a1-94fe-6136515d938c", 512 | "eventID": "14a657c3-6816-45bf-b214-4afdaefc4ebd" 513 | } 514 | ``` 515 | 516 | - `eventListenerUID` - [UID](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids) of the target EventListener. 517 | - `eventID` - UID assigned to this event request 518 | 519 | ### Deprecated Fields 520 | 521 | These fields are included in `EventListener` responses, but will be removed in a future release. 522 | 523 | - `eventListener` - name of the target EventListener. Use `eventListenerUID` instead. 524 | - `namespace` - namespace of the target EventListener. Use `eventListenerUID` instead. 525 | 526 | ## TLS HTTPS support in `EventListeners` 527 | 528 | Tekton Triggers supports both HTTP and TLS-based HTTPS connections. To configure your `EventListener` for TLS, 529 | add the `TLS_CERT` and `TLS_KEY` reserved environment variables using the `secretKeyRef` variable type, then 530 | specify a `secret` containing the `cert` and `key` files. See [TEP-0027](https://github.com/tektoncd/community/blob/master/teps/0027-https-connection-to-triggers-eventlistener.md) 531 | and our [TLS configuration example](../examples/v1beta1/eventlistener-tls-connection/README.md) for more information. 532 | 533 | ## Obtaining the status of deployed `EventListeners` 534 | 535 | Use the following command to get a list of `EventListeners` deployed on your cluster along with their statuses: 536 | ``` 537 | kubectl get el 538 | ``` 539 | 540 | You will get a response similar to the following: 541 | ``` 542 | NAME ADDRESS AVAILABLE REASON READY REASON 543 | tls-listener-interceptor http://el-tls-listener-interceptor.default.svc.cluster.local True MinimumReplicasAvailable 544 | ``` 545 | 546 | Where for each returned line, the column values are, from left to right: 547 | - `NAME` - name of the `EventListener` 548 | - `ADDRESS` - IP address or URL of the `EventListener` 549 | - `AVAILABLE` - readiness state of the associated `Deployment` and `Service` 550 | - `REASON` - reason for the value displayed in the `AVAILABLE` column 551 | - `READY` - readiness state of the Kubernetes Custom Resource object specified in the `EventListener` 552 | - `REASON` - reason for the value displayed in the `READY` column 553 | 554 | **Note:** The status messaging described above is being refactored. For more information, see [Issue 932](https://github.com/tektoncd/triggers/issues/932). 555 | 556 | ## Configuring logging for `EventListeners` 557 | 558 | You can configure logging for your `EventListener`s using the `config-logging-triggers` 559 | `ConfigMap` located in the `tekton-pipelines` namespace ([config-logging.yaml](../config/config-logging.yaml)). 560 | Tekton Triggers automatically reconciles this configmap into environment variables on your 561 | event listener deployment. 562 | 563 | To access your `EventListener` logs, query your cluster for Pods whose `eventlistener` label matches the name of your `EventListener` object. For example: 564 | 565 | ```shell 566 | kubectl get pods --selector eventlistener=my-eventlistener 567 | ``` 568 | 569 | ## Configuring metrics for `EventListeners` 570 | 571 | The following pipeline metrics are available on the `eventlistener` Service on port `9000`. 572 | 573 | | Name | Type | Labels/Tags | Status | 574 | | ---------- | ----------- | :-: | ----------- | 575 | | `eventlistener_triggered_resources` | Counter | `kind`=<kind> | experimental | 576 | | `eventlistener_event_count` | Counter | `status`=<status> | experimental | 577 | | `eventlistener_http_duration_seconds_[bucket, sum, count]` | Histogram | - | experimental | 578 | 579 | Several kinds of exporters can be configured for an `EventListener`, including Prometheus, Google Stackdriver, and many others. 580 | You can configure metrics using the [`config-observability-triggers` config map](../config/config-observability.yaml) in the `EventListener` namespaces. 581 | There is a `config-observability-triggers` configmap in the `tekton-pipelines` namespace that can be configured for the operation of the Triggers 582 | webhook and controller components. 583 | 584 | See [the Knative documentation](https://github.com/knative/pkg/blob/main/metrics/README.md) for more information about available exporters and configuration values. 585 | 586 | ## Exposing an `EventListener` outside of the cluster 587 | 588 | `EventListeners` create an underlying Kubernetes service (unless a user specifies a `customResource` EventListener deployment). 589 | By convention, this service is the same name as the EventListener prefixed with `el`. So, an EventListener named `foo` 590 | will create a service called `el-foo`. 591 | 592 | This service, by default is of type `ClusterIP` which means it is only accessible within the cluster on which it is running. 593 | You can expose this service as you would with any regular Kubernetes service. A few ways are highlighted below: 594 | - Using a `LoadBalancer` Service type 595 | - Using a Kubernetes `Ingress` object 596 | - Using the NGINX Ingress Controller 597 | - Using OpenShift Route 598 | 599 | ### Exposing an `EventListener` using a `LoadBalancer` Service 600 | 601 | If your Kuberentes cluster supports [external load balancers](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer), 602 | you can set the `serviceType` field to `LoadBalancer` to switch the Kubernetes service type: 603 | 604 | ```yaml 605 | spec: 606 | resources: 607 | kubernetesResource: 608 | serviceType: LoadBalancer 609 | ``` 610 | 611 | **Note:** You can find the external IP of this service by running `kubectl get svc/el-${EVENTLISTENER-NAME} -o=jsonpath='{.status.loadBalancer.ingress[0].ip}'` 612 | 613 | ### Exposing an `EventListener` using a Kubernetes `Ingress` object 614 | 615 | You can expose the service created by the EventListener using a regular [Kubernetes Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/). 616 | To do this, you may first have to change the `serviceType` to `NodePort`: 617 | 618 | ```yaml 619 | spec: 620 | resources: 621 | kubernetesResource: 622 | serviceType: NodePort 623 | ``` 624 | 625 | You can also use the Tekton [`create-ingress`](./getting-started/create-ingress.yaml) task to configure an `Ingress` object using self-signed certificates. 626 | 627 | ## Exposing an `EventListener` using Openshift Route 628 | 629 | Below are instructions for configuring an OpenShift 4.2 cluster running API version `v1.14.6+32dc4a0`. For more information, 630 | see [Route Configuration](https://docs.openshift.com/container-platform/4.2/networking/routes/route-configuration.html). 631 | 632 | 1. Obtain the name of your `EventListener` service: 633 | ```sh 634 | oc get el -o=jsonpath='{.status.configuration.generatedName}' 635 | ``` 636 | 637 | 2. Expose the `EventListener` service: 638 | ```sh 639 | oc expose svc/[el-listener] # REPLACE el-listener WITH YOUR SERVICE NAME FROM STEP 1 640 | ``` 641 | 642 | 3. Obtain the IP address of the exposed route: 643 | ```sh 644 | oc get route el-listener -o=jsonpath='{.spec.host}' # REPLACE el-listener WITH YOUR SERVICE NAME FROM STEP 1 645 | ``` 646 | 647 | 4. Test the configuration with `curl` or set up a GitHub Webhook that sends events to it. 648 | 649 | ## Understanding the deployment of an `EventListener` 650 | 651 | Below is a high-level walkthrough through the deployment of an `EventListener` using a GitHub example provided by Tekton Triggers. 652 | 653 | 1. Instantiate the example `EventListener` on your cluster: 654 | ```bash 655 | kubectl create -f https://github.com/tektoncd/triggers/tree/master/examples/github 656 | ``` 657 | 658 | Tekton Triggers creates a new `Deployment` and `Service` for the `EventListener`. using the `EventListener` definition, 659 | [`metadata.labels`](https://github.com/tektoncd/triggers/blob/master/docs/eventlisteners.md#labels), and pre-existing values 660 | such as container `Image`, `Name`, and `Port`. Tekton Triggers uses the `EventListener` name prefixed with `el-` to name the 661 | `Deployment` and `Service` when instantiating them. For example, if the `EventListener` name is `foo`, the `Deployment` and 662 | `Service` names are named `el-foo`. 663 | 664 | 2. Use `kubectl` to verify the `Deployment` is running on your cluster: 665 | ```bash 666 | kubectl get deployment 667 | NAME READY UP-TO-DATE AVAILABLE AGE 668 | el-github-listener-interceptor 1/1 1 1 11s 669 | 670 | 3. Use `kubectl` to verify the `Service` is running on your cluster: 671 | ``` 672 | kubectl get svc 673 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 674 | el-github-listener-interceptor ClusterIP 10.99.188.140 8080/TCP 52s 675 | ``` 676 | 677 | 4. Obtain the URL on which the `EventListener` is listening for events: 678 | ```bash 679 | kubectl get eventlistener 680 | NAME ADDRESS AVAILABLE REASON 681 | github-listener-interceptor http://el-github-listener-interceptor.ptest.svc.cluster.local:8080 True MinimumReplicasAvailable 682 | ``` 683 | 684 | See our [GitHub `EventListener` example](https://github.com/tektoncd/triggers/blob/master/examples/github/README.md) to try instantiating an `EventListener` locally. 685 | 686 | ## Deploying `EventListeners` in multi-tenant scenarios 687 | 688 | `EventListeners` are effectively Tekton clients that use HTTP to bypass the normal Kubernetes authentication 689 | mechanism established through `kubeconfig` files and the `kubectl config` command tree. Because of this, 690 | you must be conscious of your configuration decisions, such as: 691 | - How to securely expose each `EventListener` to the outside of your cluster, 692 | - How to securely control how each `EventListener` and its associated objects, such as [`Interceptors`](./interceptors.md), 693 | interact with data on your cluster. 694 | 695 | At the minimum, each `EventListener` specifies its own Kubernetes Service account as explained earlier, and 696 | it acts on all events it receives with the permissions of that service account. If your business needs mandate 697 | more granular permission control across the `Triggers` and `Interceptors` specified in your `EventListeners`, 698 | you have the following options: 699 | - Deploy each `EventListener` in its own namespace 700 | - Deploy multiple `EventListeners` in the same namespace 701 | - Specify a separate service account for each `Trigger` 702 | 703 | ### Deploying each `EventListener` in its own namespace 704 | 705 | In this scenario, you create multiple `EventListeners` that in turn use a variety of `Triggers` and `Interceptors`, 706 | each `EventListener` gets its own namespace. This way, you can use a different service account for each namespace 707 | and tailor the permissions of those accounts to the functionality of their corresponding `EventListeners`. Because 708 | creating a namespace often instantiates the necessary service accounts based on pre-configured permissions, this 709 | also simplifies the deployment process as you simply need to update the permissions associated with those accounts. 710 | 711 | However, this approach has the following drawbacks: 712 | - Namespaces with separately associated `Secrets` and `ServiceAccounts` can be the most expensive items in the 713 | Kubernetes `etcd` store; on large clusters, the capacity of the `etcd` store can become a concern. 714 | - Since each `EventListener` requires its own HTTP port to listen for events, you must configure your network 715 | to allow access to each corresponding IP address and port combination unless you configure an ingress abstraction 716 | layer, such as the Kubernetes `Ingress` object,or OpenShift Route. 717 | 718 | ### Deploying multiple `EventListeners` in the same namespace 719 | 720 | In this scenario, you create multiple `EventListeners` in the same namespace. This will require customization of 721 | the associated service account(s), secret(s), and RBAC, since the automatically generated defaults are not always 722 | ideal, but you will not incur a significant `etcd` store cost as in the multiple namespace scenario. Network security 723 | and configuration overhead concerns, however, still apply as described earlier. You can also achieve a similar result 724 | by specifying a separate service account for each `Trigger` used across your `EventListener` pool at the cost of 725 | increased administration overhead. 726 | 727 | ## Cloud Events during Trigger Processing 728 | 729 | The [cloud event](https://github.com/cloudevents/spec) that is sent to a target `URI` during Trigger processing. The types of events send for now are: 730 | 731 | Cloud Events is currently an `alpha` feature. To use it, you use the v1beta1 API version with the `enable-api-fields` [feature flag set to `alpha`](./install.md#Customizing-the-Triggers-Controller-behavior). 732 | 733 | | Type | Description | 734 | | ---------- | ----------- | 735 | | dev.tekton.event.triggers.started.v1 | triggers processing started in eventlistener | 736 | | dev.tekton.event.triggers.successful.v1 | triggers processing successful and a resource created | 737 | | dev.tekton.event.triggers.failed.v1 | triggers failed in eventlistener | 738 | | dev.tekton.event.triggers.done.v1 | triggers processing done in eventlistener handle | 739 | 740 | 741 | 742 | -------------------------------------------------------------------------------- /ocean-api/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ocean-api", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "ocean-api", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.18.1", 13 | "mongodb": "^4.10.0" 14 | } 15 | }, 16 | "node_modules/@types/node": { 17 | "version": "18.8.3", 18 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.3.tgz", 19 | "integrity": "sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w==" 20 | }, 21 | "node_modules/@types/webidl-conversions": { 22 | "version": "7.0.0", 23 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 24 | "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" 25 | }, 26 | "node_modules/@types/whatwg-url": { 27 | "version": "8.2.2", 28 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", 29 | "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", 30 | "dependencies": { 31 | "@types/node": "*", 32 | "@types/webidl-conversions": "*" 33 | } 34 | }, 35 | "node_modules/accepts": { 36 | "version": "1.3.8", 37 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 38 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 39 | "dependencies": { 40 | "mime-types": "~2.1.34", 41 | "negotiator": "0.6.3" 42 | }, 43 | "engines": { 44 | "node": ">= 0.6" 45 | } 46 | }, 47 | "node_modules/array-flatten": { 48 | "version": "1.1.1", 49 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 50 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 51 | }, 52 | "node_modules/base64-js": { 53 | "version": "1.5.1", 54 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 55 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 56 | "funding": [ 57 | { 58 | "type": "github", 59 | "url": "https://github.com/sponsors/feross" 60 | }, 61 | { 62 | "type": "patreon", 63 | "url": "https://www.patreon.com/feross" 64 | }, 65 | { 66 | "type": "consulting", 67 | "url": "https://feross.org/support" 68 | } 69 | ] 70 | }, 71 | "node_modules/body-parser": { 72 | "version": "1.20.0", 73 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", 74 | "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", 75 | "dependencies": { 76 | "bytes": "3.1.2", 77 | "content-type": "~1.0.4", 78 | "debug": "2.6.9", 79 | "depd": "2.0.0", 80 | "destroy": "1.2.0", 81 | "http-errors": "2.0.0", 82 | "iconv-lite": "0.4.24", 83 | "on-finished": "2.4.1", 84 | "qs": "6.10.3", 85 | "raw-body": "2.5.1", 86 | "type-is": "~1.6.18", 87 | "unpipe": "1.0.0" 88 | }, 89 | "engines": { 90 | "node": ">= 0.8", 91 | "npm": "1.2.8000 || >= 1.4.16" 92 | } 93 | }, 94 | "node_modules/bson": { 95 | "version": "4.7.0", 96 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.0.tgz", 97 | "integrity": "sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA==", 98 | "dependencies": { 99 | "buffer": "^5.6.0" 100 | }, 101 | "engines": { 102 | "node": ">=6.9.0" 103 | } 104 | }, 105 | "node_modules/buffer": { 106 | "version": "5.7.1", 107 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 108 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 109 | "funding": [ 110 | { 111 | "type": "github", 112 | "url": "https://github.com/sponsors/feross" 113 | }, 114 | { 115 | "type": "patreon", 116 | "url": "https://www.patreon.com/feross" 117 | }, 118 | { 119 | "type": "consulting", 120 | "url": "https://feross.org/support" 121 | } 122 | ], 123 | "dependencies": { 124 | "base64-js": "^1.3.1", 125 | "ieee754": "^1.1.13" 126 | } 127 | }, 128 | "node_modules/bytes": { 129 | "version": "3.1.2", 130 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 131 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 132 | "engines": { 133 | "node": ">= 0.8" 134 | } 135 | }, 136 | "node_modules/call-bind": { 137 | "version": "1.0.2", 138 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 139 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 140 | "dependencies": { 141 | "function-bind": "^1.1.1", 142 | "get-intrinsic": "^1.0.2" 143 | }, 144 | "funding": { 145 | "url": "https://github.com/sponsors/ljharb" 146 | } 147 | }, 148 | "node_modules/content-disposition": { 149 | "version": "0.5.4", 150 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 151 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 152 | "dependencies": { 153 | "safe-buffer": "5.2.1" 154 | }, 155 | "engines": { 156 | "node": ">= 0.6" 157 | } 158 | }, 159 | "node_modules/content-type": { 160 | "version": "1.0.4", 161 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 162 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", 163 | "engines": { 164 | "node": ">= 0.6" 165 | } 166 | }, 167 | "node_modules/cookie": { 168 | "version": "0.5.0", 169 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 170 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", 171 | "engines": { 172 | "node": ">= 0.6" 173 | } 174 | }, 175 | "node_modules/cookie-signature": { 176 | "version": "1.0.6", 177 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 178 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 179 | }, 180 | "node_modules/debug": { 181 | "version": "2.6.9", 182 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 183 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 184 | "dependencies": { 185 | "ms": "2.0.0" 186 | } 187 | }, 188 | "node_modules/denque": { 189 | "version": "2.1.0", 190 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", 191 | "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", 192 | "engines": { 193 | "node": ">=0.10" 194 | } 195 | }, 196 | "node_modules/depd": { 197 | "version": "2.0.0", 198 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 199 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 200 | "engines": { 201 | "node": ">= 0.8" 202 | } 203 | }, 204 | "node_modules/destroy": { 205 | "version": "1.2.0", 206 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 207 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 208 | "engines": { 209 | "node": ">= 0.8", 210 | "npm": "1.2.8000 || >= 1.4.16" 211 | } 212 | }, 213 | "node_modules/ee-first": { 214 | "version": "1.1.1", 215 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 216 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 217 | }, 218 | "node_modules/encodeurl": { 219 | "version": "1.0.2", 220 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 221 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 222 | "engines": { 223 | "node": ">= 0.8" 224 | } 225 | }, 226 | "node_modules/escape-html": { 227 | "version": "1.0.3", 228 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 229 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 230 | }, 231 | "node_modules/etag": { 232 | "version": "1.8.1", 233 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 234 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 235 | "engines": { 236 | "node": ">= 0.6" 237 | } 238 | }, 239 | "node_modules/express": { 240 | "version": "4.18.1", 241 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", 242 | "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", 243 | "dependencies": { 244 | "accepts": "~1.3.8", 245 | "array-flatten": "1.1.1", 246 | "body-parser": "1.20.0", 247 | "content-disposition": "0.5.4", 248 | "content-type": "~1.0.4", 249 | "cookie": "0.5.0", 250 | "cookie-signature": "1.0.6", 251 | "debug": "2.6.9", 252 | "depd": "2.0.0", 253 | "encodeurl": "~1.0.2", 254 | "escape-html": "~1.0.3", 255 | "etag": "~1.8.1", 256 | "finalhandler": "1.2.0", 257 | "fresh": "0.5.2", 258 | "http-errors": "2.0.0", 259 | "merge-descriptors": "1.0.1", 260 | "methods": "~1.1.2", 261 | "on-finished": "2.4.1", 262 | "parseurl": "~1.3.3", 263 | "path-to-regexp": "0.1.7", 264 | "proxy-addr": "~2.0.7", 265 | "qs": "6.10.3", 266 | "range-parser": "~1.2.1", 267 | "safe-buffer": "5.2.1", 268 | "send": "0.18.0", 269 | "serve-static": "1.15.0", 270 | "setprototypeof": "1.2.0", 271 | "statuses": "2.0.1", 272 | "type-is": "~1.6.18", 273 | "utils-merge": "1.0.1", 274 | "vary": "~1.1.2" 275 | }, 276 | "engines": { 277 | "node": ">= 0.10.0" 278 | } 279 | }, 280 | "node_modules/finalhandler": { 281 | "version": "1.2.0", 282 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 283 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 284 | "dependencies": { 285 | "debug": "2.6.9", 286 | "encodeurl": "~1.0.2", 287 | "escape-html": "~1.0.3", 288 | "on-finished": "2.4.1", 289 | "parseurl": "~1.3.3", 290 | "statuses": "2.0.1", 291 | "unpipe": "~1.0.0" 292 | }, 293 | "engines": { 294 | "node": ">= 0.8" 295 | } 296 | }, 297 | "node_modules/forwarded": { 298 | "version": "0.2.0", 299 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 300 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 301 | "engines": { 302 | "node": ">= 0.6" 303 | } 304 | }, 305 | "node_modules/fresh": { 306 | "version": "0.5.2", 307 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 308 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 309 | "engines": { 310 | "node": ">= 0.6" 311 | } 312 | }, 313 | "node_modules/function-bind": { 314 | "version": "1.1.1", 315 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 316 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 317 | }, 318 | "node_modules/get-intrinsic": { 319 | "version": "1.1.3", 320 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", 321 | "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", 322 | "dependencies": { 323 | "function-bind": "^1.1.1", 324 | "has": "^1.0.3", 325 | "has-symbols": "^1.0.3" 326 | }, 327 | "funding": { 328 | "url": "https://github.com/sponsors/ljharb" 329 | } 330 | }, 331 | "node_modules/has": { 332 | "version": "1.0.3", 333 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 334 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 335 | "dependencies": { 336 | "function-bind": "^1.1.1" 337 | }, 338 | "engines": { 339 | "node": ">= 0.4.0" 340 | } 341 | }, 342 | "node_modules/has-symbols": { 343 | "version": "1.0.3", 344 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 345 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 346 | "engines": { 347 | "node": ">= 0.4" 348 | }, 349 | "funding": { 350 | "url": "https://github.com/sponsors/ljharb" 351 | } 352 | }, 353 | "node_modules/http-errors": { 354 | "version": "2.0.0", 355 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 356 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 357 | "dependencies": { 358 | "depd": "2.0.0", 359 | "inherits": "2.0.4", 360 | "setprototypeof": "1.2.0", 361 | "statuses": "2.0.1", 362 | "toidentifier": "1.0.1" 363 | }, 364 | "engines": { 365 | "node": ">= 0.8" 366 | } 367 | }, 368 | "node_modules/iconv-lite": { 369 | "version": "0.4.24", 370 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 371 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 372 | "dependencies": { 373 | "safer-buffer": ">= 2.1.2 < 3" 374 | }, 375 | "engines": { 376 | "node": ">=0.10.0" 377 | } 378 | }, 379 | "node_modules/ieee754": { 380 | "version": "1.2.1", 381 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 382 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 383 | "funding": [ 384 | { 385 | "type": "github", 386 | "url": "https://github.com/sponsors/feross" 387 | }, 388 | { 389 | "type": "patreon", 390 | "url": "https://www.patreon.com/feross" 391 | }, 392 | { 393 | "type": "consulting", 394 | "url": "https://feross.org/support" 395 | } 396 | ] 397 | }, 398 | "node_modules/inherits": { 399 | "version": "2.0.4", 400 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 401 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 402 | }, 403 | "node_modules/ip": { 404 | "version": "2.0.0", 405 | "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", 406 | "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" 407 | }, 408 | "node_modules/ipaddr.js": { 409 | "version": "1.9.1", 410 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 411 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 412 | "engines": { 413 | "node": ">= 0.10" 414 | } 415 | }, 416 | "node_modules/media-typer": { 417 | "version": "0.3.0", 418 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 419 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 420 | "engines": { 421 | "node": ">= 0.6" 422 | } 423 | }, 424 | "node_modules/memory-pager": { 425 | "version": "1.5.0", 426 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 427 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 428 | "optional": true 429 | }, 430 | "node_modules/merge-descriptors": { 431 | "version": "1.0.1", 432 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 433 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 434 | }, 435 | "node_modules/methods": { 436 | "version": "1.1.2", 437 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 438 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 439 | "engines": { 440 | "node": ">= 0.6" 441 | } 442 | }, 443 | "node_modules/mime": { 444 | "version": "1.6.0", 445 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 446 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 447 | "bin": { 448 | "mime": "cli.js" 449 | }, 450 | "engines": { 451 | "node": ">=4" 452 | } 453 | }, 454 | "node_modules/mime-db": { 455 | "version": "1.52.0", 456 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 457 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 458 | "engines": { 459 | "node": ">= 0.6" 460 | } 461 | }, 462 | "node_modules/mime-types": { 463 | "version": "2.1.35", 464 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 465 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 466 | "dependencies": { 467 | "mime-db": "1.52.0" 468 | }, 469 | "engines": { 470 | "node": ">= 0.6" 471 | } 472 | }, 473 | "node_modules/mongodb": { 474 | "version": "4.10.0", 475 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.10.0.tgz", 476 | "integrity": "sha512-My2QxLTw0Cc1O9gih0mz4mqo145Jq4rLAQx0Glk/Ha9iYBzYpt4I2QFNRIh35uNFNfe8KFQcdwY1/HKxXBkinw==", 477 | "dependencies": { 478 | "bson": "^4.7.0", 479 | "denque": "^2.1.0", 480 | "mongodb-connection-string-url": "^2.5.3", 481 | "socks": "^2.7.0" 482 | }, 483 | "engines": { 484 | "node": ">=12.9.0" 485 | }, 486 | "optionalDependencies": { 487 | "saslprep": "^1.0.3" 488 | } 489 | }, 490 | "node_modules/mongodb-connection-string-url": { 491 | "version": "2.5.4", 492 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.4.tgz", 493 | "integrity": "sha512-SeAxuWs0ez3iI3vvmLk/j2y+zHwigTDKQhtdxTgt5ZCOQQS5+HW4g45/Xw5vzzbn7oQXCNQ24Z40AkJsizEy7w==", 494 | "dependencies": { 495 | "@types/whatwg-url": "^8.2.1", 496 | "whatwg-url": "^11.0.0" 497 | } 498 | }, 499 | "node_modules/ms": { 500 | "version": "2.0.0", 501 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 502 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 503 | }, 504 | "node_modules/negotiator": { 505 | "version": "0.6.3", 506 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 507 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 508 | "engines": { 509 | "node": ">= 0.6" 510 | } 511 | }, 512 | "node_modules/object-inspect": { 513 | "version": "1.12.2", 514 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", 515 | "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", 516 | "funding": { 517 | "url": "https://github.com/sponsors/ljharb" 518 | } 519 | }, 520 | "node_modules/on-finished": { 521 | "version": "2.4.1", 522 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 523 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 524 | "dependencies": { 525 | "ee-first": "1.1.1" 526 | }, 527 | "engines": { 528 | "node": ">= 0.8" 529 | } 530 | }, 531 | "node_modules/parseurl": { 532 | "version": "1.3.3", 533 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 534 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 535 | "engines": { 536 | "node": ">= 0.8" 537 | } 538 | }, 539 | "node_modules/path-to-regexp": { 540 | "version": "0.1.7", 541 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 542 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 543 | }, 544 | "node_modules/proxy-addr": { 545 | "version": "2.0.7", 546 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 547 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 548 | "dependencies": { 549 | "forwarded": "0.2.0", 550 | "ipaddr.js": "1.9.1" 551 | }, 552 | "engines": { 553 | "node": ">= 0.10" 554 | } 555 | }, 556 | "node_modules/punycode": { 557 | "version": "2.1.1", 558 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 559 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 560 | "engines": { 561 | "node": ">=6" 562 | } 563 | }, 564 | "node_modules/qs": { 565 | "version": "6.10.3", 566 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", 567 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", 568 | "dependencies": { 569 | "side-channel": "^1.0.4" 570 | }, 571 | "engines": { 572 | "node": ">=0.6" 573 | }, 574 | "funding": { 575 | "url": "https://github.com/sponsors/ljharb" 576 | } 577 | }, 578 | "node_modules/range-parser": { 579 | "version": "1.2.1", 580 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 581 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 582 | "engines": { 583 | "node": ">= 0.6" 584 | } 585 | }, 586 | "node_modules/raw-body": { 587 | "version": "2.5.1", 588 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 589 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 590 | "dependencies": { 591 | "bytes": "3.1.2", 592 | "http-errors": "2.0.0", 593 | "iconv-lite": "0.4.24", 594 | "unpipe": "1.0.0" 595 | }, 596 | "engines": { 597 | "node": ">= 0.8" 598 | } 599 | }, 600 | "node_modules/safe-buffer": { 601 | "version": "5.2.1", 602 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 603 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 604 | "funding": [ 605 | { 606 | "type": "github", 607 | "url": "https://github.com/sponsors/feross" 608 | }, 609 | { 610 | "type": "patreon", 611 | "url": "https://www.patreon.com/feross" 612 | }, 613 | { 614 | "type": "consulting", 615 | "url": "https://feross.org/support" 616 | } 617 | ] 618 | }, 619 | "node_modules/safer-buffer": { 620 | "version": "2.1.2", 621 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 622 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 623 | }, 624 | "node_modules/saslprep": { 625 | "version": "1.0.3", 626 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 627 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 628 | "optional": true, 629 | "dependencies": { 630 | "sparse-bitfield": "^3.0.3" 631 | }, 632 | "engines": { 633 | "node": ">=6" 634 | } 635 | }, 636 | "node_modules/send": { 637 | "version": "0.18.0", 638 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 639 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 640 | "dependencies": { 641 | "debug": "2.6.9", 642 | "depd": "2.0.0", 643 | "destroy": "1.2.0", 644 | "encodeurl": "~1.0.2", 645 | "escape-html": "~1.0.3", 646 | "etag": "~1.8.1", 647 | "fresh": "0.5.2", 648 | "http-errors": "2.0.0", 649 | "mime": "1.6.0", 650 | "ms": "2.1.3", 651 | "on-finished": "2.4.1", 652 | "range-parser": "~1.2.1", 653 | "statuses": "2.0.1" 654 | }, 655 | "engines": { 656 | "node": ">= 0.8.0" 657 | } 658 | }, 659 | "node_modules/send/node_modules/ms": { 660 | "version": "2.1.3", 661 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 662 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 663 | }, 664 | "node_modules/serve-static": { 665 | "version": "1.15.0", 666 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 667 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 668 | "dependencies": { 669 | "encodeurl": "~1.0.2", 670 | "escape-html": "~1.0.3", 671 | "parseurl": "~1.3.3", 672 | "send": "0.18.0" 673 | }, 674 | "engines": { 675 | "node": ">= 0.8.0" 676 | } 677 | }, 678 | "node_modules/setprototypeof": { 679 | "version": "1.2.0", 680 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 681 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 682 | }, 683 | "node_modules/side-channel": { 684 | "version": "1.0.4", 685 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 686 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 687 | "dependencies": { 688 | "call-bind": "^1.0.0", 689 | "get-intrinsic": "^1.0.2", 690 | "object-inspect": "^1.9.0" 691 | }, 692 | "funding": { 693 | "url": "https://github.com/sponsors/ljharb" 694 | } 695 | }, 696 | "node_modules/smart-buffer": { 697 | "version": "4.2.0", 698 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", 699 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", 700 | "engines": { 701 | "node": ">= 6.0.0", 702 | "npm": ">= 3.0.0" 703 | } 704 | }, 705 | "node_modules/socks": { 706 | "version": "2.7.1", 707 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", 708 | "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", 709 | "dependencies": { 710 | "ip": "^2.0.0", 711 | "smart-buffer": "^4.2.0" 712 | }, 713 | "engines": { 714 | "node": ">= 10.13.0", 715 | "npm": ">= 3.0.0" 716 | } 717 | }, 718 | "node_modules/sparse-bitfield": { 719 | "version": "3.0.3", 720 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 721 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", 722 | "optional": true, 723 | "dependencies": { 724 | "memory-pager": "^1.0.2" 725 | } 726 | }, 727 | "node_modules/statuses": { 728 | "version": "2.0.1", 729 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 730 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 731 | "engines": { 732 | "node": ">= 0.8" 733 | } 734 | }, 735 | "node_modules/toidentifier": { 736 | "version": "1.0.1", 737 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 738 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 739 | "engines": { 740 | "node": ">=0.6" 741 | } 742 | }, 743 | "node_modules/tr46": { 744 | "version": "3.0.0", 745 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", 746 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", 747 | "dependencies": { 748 | "punycode": "^2.1.1" 749 | }, 750 | "engines": { 751 | "node": ">=12" 752 | } 753 | }, 754 | "node_modules/type-is": { 755 | "version": "1.6.18", 756 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 757 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 758 | "dependencies": { 759 | "media-typer": "0.3.0", 760 | "mime-types": "~2.1.24" 761 | }, 762 | "engines": { 763 | "node": ">= 0.6" 764 | } 765 | }, 766 | "node_modules/unpipe": { 767 | "version": "1.0.0", 768 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 769 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 770 | "engines": { 771 | "node": ">= 0.8" 772 | } 773 | }, 774 | "node_modules/utils-merge": { 775 | "version": "1.0.1", 776 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 777 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 778 | "engines": { 779 | "node": ">= 0.4.0" 780 | } 781 | }, 782 | "node_modules/vary": { 783 | "version": "1.1.2", 784 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 785 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 786 | "engines": { 787 | "node": ">= 0.8" 788 | } 789 | }, 790 | "node_modules/webidl-conversions": { 791 | "version": "7.0.0", 792 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 793 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", 794 | "engines": { 795 | "node": ">=12" 796 | } 797 | }, 798 | "node_modules/whatwg-url": { 799 | "version": "11.0.0", 800 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", 801 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", 802 | "dependencies": { 803 | "tr46": "^3.0.0", 804 | "webidl-conversions": "^7.0.0" 805 | }, 806 | "engines": { 807 | "node": ">=12" 808 | } 809 | } 810 | }, 811 | "dependencies": { 812 | "@types/node": { 813 | "version": "18.8.3", 814 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.3.tgz", 815 | "integrity": "sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w==" 816 | }, 817 | "@types/webidl-conversions": { 818 | "version": "7.0.0", 819 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 820 | "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" 821 | }, 822 | "@types/whatwg-url": { 823 | "version": "8.2.2", 824 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", 825 | "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", 826 | "requires": { 827 | "@types/node": "*", 828 | "@types/webidl-conversions": "*" 829 | } 830 | }, 831 | "accepts": { 832 | "version": "1.3.8", 833 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 834 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 835 | "requires": { 836 | "mime-types": "~2.1.34", 837 | "negotiator": "0.6.3" 838 | } 839 | }, 840 | "array-flatten": { 841 | "version": "1.1.1", 842 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 843 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 844 | }, 845 | "base64-js": { 846 | "version": "1.5.1", 847 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 848 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 849 | }, 850 | "body-parser": { 851 | "version": "1.20.0", 852 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", 853 | "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", 854 | "requires": { 855 | "bytes": "3.1.2", 856 | "content-type": "~1.0.4", 857 | "debug": "2.6.9", 858 | "depd": "2.0.0", 859 | "destroy": "1.2.0", 860 | "http-errors": "2.0.0", 861 | "iconv-lite": "0.4.24", 862 | "on-finished": "2.4.1", 863 | "qs": "6.10.3", 864 | "raw-body": "2.5.1", 865 | "type-is": "~1.6.18", 866 | "unpipe": "1.0.0" 867 | } 868 | }, 869 | "bson": { 870 | "version": "4.7.0", 871 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.0.tgz", 872 | "integrity": "sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA==", 873 | "requires": { 874 | "buffer": "^5.6.0" 875 | } 876 | }, 877 | "buffer": { 878 | "version": "5.7.1", 879 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 880 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 881 | "requires": { 882 | "base64-js": "^1.3.1", 883 | "ieee754": "^1.1.13" 884 | } 885 | }, 886 | "bytes": { 887 | "version": "3.1.2", 888 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 889 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" 890 | }, 891 | "call-bind": { 892 | "version": "1.0.2", 893 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 894 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 895 | "requires": { 896 | "function-bind": "^1.1.1", 897 | "get-intrinsic": "^1.0.2" 898 | } 899 | }, 900 | "content-disposition": { 901 | "version": "0.5.4", 902 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 903 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 904 | "requires": { 905 | "safe-buffer": "5.2.1" 906 | } 907 | }, 908 | "content-type": { 909 | "version": "1.0.4", 910 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 911 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 912 | }, 913 | "cookie": { 914 | "version": "0.5.0", 915 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 916 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" 917 | }, 918 | "cookie-signature": { 919 | "version": "1.0.6", 920 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 921 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 922 | }, 923 | "debug": { 924 | "version": "2.6.9", 925 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 926 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 927 | "requires": { 928 | "ms": "2.0.0" 929 | } 930 | }, 931 | "denque": { 932 | "version": "2.1.0", 933 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", 934 | "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" 935 | }, 936 | "depd": { 937 | "version": "2.0.0", 938 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 939 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 940 | }, 941 | "destroy": { 942 | "version": "1.2.0", 943 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 944 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" 945 | }, 946 | "ee-first": { 947 | "version": "1.1.1", 948 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 949 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 950 | }, 951 | "encodeurl": { 952 | "version": "1.0.2", 953 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 954 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" 955 | }, 956 | "escape-html": { 957 | "version": "1.0.3", 958 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 959 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 960 | }, 961 | "etag": { 962 | "version": "1.8.1", 963 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 964 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" 965 | }, 966 | "express": { 967 | "version": "4.18.1", 968 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", 969 | "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", 970 | "requires": { 971 | "accepts": "~1.3.8", 972 | "array-flatten": "1.1.1", 973 | "body-parser": "1.20.0", 974 | "content-disposition": "0.5.4", 975 | "content-type": "~1.0.4", 976 | "cookie": "0.5.0", 977 | "cookie-signature": "1.0.6", 978 | "debug": "2.6.9", 979 | "depd": "2.0.0", 980 | "encodeurl": "~1.0.2", 981 | "escape-html": "~1.0.3", 982 | "etag": "~1.8.1", 983 | "finalhandler": "1.2.0", 984 | "fresh": "0.5.2", 985 | "http-errors": "2.0.0", 986 | "merge-descriptors": "1.0.1", 987 | "methods": "~1.1.2", 988 | "on-finished": "2.4.1", 989 | "parseurl": "~1.3.3", 990 | "path-to-regexp": "0.1.7", 991 | "proxy-addr": "~2.0.7", 992 | "qs": "6.10.3", 993 | "range-parser": "~1.2.1", 994 | "safe-buffer": "5.2.1", 995 | "send": "0.18.0", 996 | "serve-static": "1.15.0", 997 | "setprototypeof": "1.2.0", 998 | "statuses": "2.0.1", 999 | "type-is": "~1.6.18", 1000 | "utils-merge": "1.0.1", 1001 | "vary": "~1.1.2" 1002 | } 1003 | }, 1004 | "finalhandler": { 1005 | "version": "1.2.0", 1006 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 1007 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 1008 | "requires": { 1009 | "debug": "2.6.9", 1010 | "encodeurl": "~1.0.2", 1011 | "escape-html": "~1.0.3", 1012 | "on-finished": "2.4.1", 1013 | "parseurl": "~1.3.3", 1014 | "statuses": "2.0.1", 1015 | "unpipe": "~1.0.0" 1016 | } 1017 | }, 1018 | "forwarded": { 1019 | "version": "0.2.0", 1020 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 1021 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 1022 | }, 1023 | "fresh": { 1024 | "version": "0.5.2", 1025 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 1026 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" 1027 | }, 1028 | "function-bind": { 1029 | "version": "1.1.1", 1030 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 1031 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 1032 | }, 1033 | "get-intrinsic": { 1034 | "version": "1.1.3", 1035 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", 1036 | "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", 1037 | "requires": { 1038 | "function-bind": "^1.1.1", 1039 | "has": "^1.0.3", 1040 | "has-symbols": "^1.0.3" 1041 | } 1042 | }, 1043 | "has": { 1044 | "version": "1.0.3", 1045 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 1046 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 1047 | "requires": { 1048 | "function-bind": "^1.1.1" 1049 | } 1050 | }, 1051 | "has-symbols": { 1052 | "version": "1.0.3", 1053 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 1054 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 1055 | }, 1056 | "http-errors": { 1057 | "version": "2.0.0", 1058 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 1059 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1060 | "requires": { 1061 | "depd": "2.0.0", 1062 | "inherits": "2.0.4", 1063 | "setprototypeof": "1.2.0", 1064 | "statuses": "2.0.1", 1065 | "toidentifier": "1.0.1" 1066 | } 1067 | }, 1068 | "iconv-lite": { 1069 | "version": "0.4.24", 1070 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1071 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1072 | "requires": { 1073 | "safer-buffer": ">= 2.1.2 < 3" 1074 | } 1075 | }, 1076 | "ieee754": { 1077 | "version": "1.2.1", 1078 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 1079 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 1080 | }, 1081 | "inherits": { 1082 | "version": "2.0.4", 1083 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1084 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 1085 | }, 1086 | "ip": { 1087 | "version": "2.0.0", 1088 | "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", 1089 | "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" 1090 | }, 1091 | "ipaddr.js": { 1092 | "version": "1.9.1", 1093 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1094 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 1095 | }, 1096 | "media-typer": { 1097 | "version": "0.3.0", 1098 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1099 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" 1100 | }, 1101 | "memory-pager": { 1102 | "version": "1.5.0", 1103 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 1104 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 1105 | "optional": true 1106 | }, 1107 | "merge-descriptors": { 1108 | "version": "1.0.1", 1109 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1110 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 1111 | }, 1112 | "methods": { 1113 | "version": "1.1.2", 1114 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1115 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" 1116 | }, 1117 | "mime": { 1118 | "version": "1.6.0", 1119 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1120 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 1121 | }, 1122 | "mime-db": { 1123 | "version": "1.52.0", 1124 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1125 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 1126 | }, 1127 | "mime-types": { 1128 | "version": "2.1.35", 1129 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1130 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1131 | "requires": { 1132 | "mime-db": "1.52.0" 1133 | } 1134 | }, 1135 | "mongodb": { 1136 | "version": "4.10.0", 1137 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.10.0.tgz", 1138 | "integrity": "sha512-My2QxLTw0Cc1O9gih0mz4mqo145Jq4rLAQx0Glk/Ha9iYBzYpt4I2QFNRIh35uNFNfe8KFQcdwY1/HKxXBkinw==", 1139 | "requires": { 1140 | "bson": "^4.7.0", 1141 | "denque": "^2.1.0", 1142 | "mongodb-connection-string-url": "^2.5.3", 1143 | "saslprep": "^1.0.3", 1144 | "socks": "^2.7.0" 1145 | } 1146 | }, 1147 | "mongodb-connection-string-url": { 1148 | "version": "2.5.4", 1149 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.4.tgz", 1150 | "integrity": "sha512-SeAxuWs0ez3iI3vvmLk/j2y+zHwigTDKQhtdxTgt5ZCOQQS5+HW4g45/Xw5vzzbn7oQXCNQ24Z40AkJsizEy7w==", 1151 | "requires": { 1152 | "@types/whatwg-url": "^8.2.1", 1153 | "whatwg-url": "^11.0.0" 1154 | } 1155 | }, 1156 | "ms": { 1157 | "version": "2.0.0", 1158 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1159 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 1160 | }, 1161 | "negotiator": { 1162 | "version": "0.6.3", 1163 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1164 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" 1165 | }, 1166 | "object-inspect": { 1167 | "version": "1.12.2", 1168 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", 1169 | "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" 1170 | }, 1171 | "on-finished": { 1172 | "version": "2.4.1", 1173 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1174 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1175 | "requires": { 1176 | "ee-first": "1.1.1" 1177 | } 1178 | }, 1179 | "parseurl": { 1180 | "version": "1.3.3", 1181 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1182 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 1183 | }, 1184 | "path-to-regexp": { 1185 | "version": "0.1.7", 1186 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1187 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 1188 | }, 1189 | "proxy-addr": { 1190 | "version": "2.0.7", 1191 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1192 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1193 | "requires": { 1194 | "forwarded": "0.2.0", 1195 | "ipaddr.js": "1.9.1" 1196 | } 1197 | }, 1198 | "punycode": { 1199 | "version": "2.1.1", 1200 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1201 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 1202 | }, 1203 | "qs": { 1204 | "version": "6.10.3", 1205 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", 1206 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", 1207 | "requires": { 1208 | "side-channel": "^1.0.4" 1209 | } 1210 | }, 1211 | "range-parser": { 1212 | "version": "1.2.1", 1213 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1214 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 1215 | }, 1216 | "raw-body": { 1217 | "version": "2.5.1", 1218 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 1219 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 1220 | "requires": { 1221 | "bytes": "3.1.2", 1222 | "http-errors": "2.0.0", 1223 | "iconv-lite": "0.4.24", 1224 | "unpipe": "1.0.0" 1225 | } 1226 | }, 1227 | "safe-buffer": { 1228 | "version": "5.2.1", 1229 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1230 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1231 | }, 1232 | "safer-buffer": { 1233 | "version": "2.1.2", 1234 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1235 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1236 | }, 1237 | "saslprep": { 1238 | "version": "1.0.3", 1239 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 1240 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 1241 | "optional": true, 1242 | "requires": { 1243 | "sparse-bitfield": "^3.0.3" 1244 | } 1245 | }, 1246 | "send": { 1247 | "version": "0.18.0", 1248 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 1249 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 1250 | "requires": { 1251 | "debug": "2.6.9", 1252 | "depd": "2.0.0", 1253 | "destroy": "1.2.0", 1254 | "encodeurl": "~1.0.2", 1255 | "escape-html": "~1.0.3", 1256 | "etag": "~1.8.1", 1257 | "fresh": "0.5.2", 1258 | "http-errors": "2.0.0", 1259 | "mime": "1.6.0", 1260 | "ms": "2.1.3", 1261 | "on-finished": "2.4.1", 1262 | "range-parser": "~1.2.1", 1263 | "statuses": "2.0.1" 1264 | }, 1265 | "dependencies": { 1266 | "ms": { 1267 | "version": "2.1.3", 1268 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1269 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1270 | } 1271 | } 1272 | }, 1273 | "serve-static": { 1274 | "version": "1.15.0", 1275 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 1276 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 1277 | "requires": { 1278 | "encodeurl": "~1.0.2", 1279 | "escape-html": "~1.0.3", 1280 | "parseurl": "~1.3.3", 1281 | "send": "0.18.0" 1282 | } 1283 | }, 1284 | "setprototypeof": { 1285 | "version": "1.2.0", 1286 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1287 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1288 | }, 1289 | "side-channel": { 1290 | "version": "1.0.4", 1291 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 1292 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 1293 | "requires": { 1294 | "call-bind": "^1.0.0", 1295 | "get-intrinsic": "^1.0.2", 1296 | "object-inspect": "^1.9.0" 1297 | } 1298 | }, 1299 | "smart-buffer": { 1300 | "version": "4.2.0", 1301 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", 1302 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" 1303 | }, 1304 | "socks": { 1305 | "version": "2.7.1", 1306 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", 1307 | "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", 1308 | "requires": { 1309 | "ip": "^2.0.0", 1310 | "smart-buffer": "^4.2.0" 1311 | } 1312 | }, 1313 | "sparse-bitfield": { 1314 | "version": "3.0.3", 1315 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 1316 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", 1317 | "optional": true, 1318 | "requires": { 1319 | "memory-pager": "^1.0.2" 1320 | } 1321 | }, 1322 | "statuses": { 1323 | "version": "2.0.1", 1324 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1325 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 1326 | }, 1327 | "toidentifier": { 1328 | "version": "1.0.1", 1329 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1330 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 1331 | }, 1332 | "tr46": { 1333 | "version": "3.0.0", 1334 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", 1335 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", 1336 | "requires": { 1337 | "punycode": "^2.1.1" 1338 | } 1339 | }, 1340 | "type-is": { 1341 | "version": "1.6.18", 1342 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1343 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1344 | "requires": { 1345 | "media-typer": "0.3.0", 1346 | "mime-types": "~2.1.24" 1347 | } 1348 | }, 1349 | "unpipe": { 1350 | "version": "1.0.0", 1351 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1352 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" 1353 | }, 1354 | "utils-merge": { 1355 | "version": "1.0.1", 1356 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1357 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" 1358 | }, 1359 | "vary": { 1360 | "version": "1.1.2", 1361 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1362 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" 1363 | }, 1364 | "webidl-conversions": { 1365 | "version": "7.0.0", 1366 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 1367 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" 1368 | }, 1369 | "whatwg-url": { 1370 | "version": "11.0.0", 1371 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", 1372 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", 1373 | "requires": { 1374 | "tr46": "^3.0.0", 1375 | "webidl-conversions": "^7.0.0" 1376 | } 1377 | } 1378 | } 1379 | } 1380 | --------------------------------------------------------------------------------