From 7f246ef348ae76c489ce3e612132c28a821dead0 Mon Sep 17 00:00:00 2001 From: William Lam Date: Thu, 22 Apr 2021 05:34:08 -0700 Subject: [PATCH 01/56] Add Windows specific Docker command to kn-ps-slack function Signed-off-by: William Lam --- examples/knative/powershell/kn-ps-slack/README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/examples/knative/powershell/kn-ps-slack/README.md b/examples/knative/powershell/kn-ps-slack/README.md index c1cd3d43..6904ed8d 100644 --- a/examples/knative/powershell/kn-ps-slack/README.md +++ b/examples/knative/powershell/kn-ps-slack/README.md @@ -17,13 +17,20 @@ docker push /kn-ps-slack:1.0 Verify the container image works by executing it locally. +**Windows PowerShell:** + +```powershell +docker run -e FUNCTION_DEBUG=true -e PORT=8080 -e SLACK_SECRET="{'SLACK_WEBHOOK_URL': 'YOUR-WEBHOOK-URL'}" -it --rm -p 8080:8080 /kn-ps-slack:1.0 +``` + +**Windows WSL/Linux/MacOS:** ```bash docker run -e FUNCTION_DEBUG=true -e PORT=8080 -e SLACK_SECRET='{"SLACK_WEBHOOK_URL": "YOUR-WEBHOOK-URL"}' -it --rm -p 8080:8080 /kn-ps-slack:1.0 +``` -# now in a separate window run the following +In a separate terminal, run either `test.ps1` (PowerShell Script) or `test.sh` (Bash Script) to simulate a CloudEvent payload being sent to the local container image -# Run either test.ps1 (PowerShell Script) or test.sh (Bash Script) to simulate a CloudEvent payload being sent to the container image -./test.ps1 +``` Testing Function ... See docker container console for output From e2344375a52b19d707f19b5212e18f642ef1cc88 Mon Sep 17 00:00:00 2001 From: William Lam Date: Thu, 22 Apr 2021 09:27:48 -0700 Subject: [PATCH 02/56] Remove * in front of Closes keyword + Fix Typo Signed-off-by: William Lam --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 16e91616..20195478 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,7 +8,7 @@ Please describe your changes and what you plan to introduce as part of this pull - [ ] Please ensure that you are making a pull request against the **Development** branch - [ ] Please use the `WIP` keyword in the title of your PR if you are not ready for review - [ ] Please ensure that you have opened a Github Issue if you are resolving/fixing a problem -- [ ] Please ensure that you have [signed](https://help.github.com/en/github/authenticating-to-github/signing-commits) all commits and that you have [squashed](https://medium.com/@slamflipstrom/a-beginners-guide-to-squashing-commits-with-git-rebase-8185cf6e62ec) all relevant commmits related to your change +- [ ] Please ensure that you have [signed](https://help.github.com/en/github/authenticating-to-github/signing-commits) all commits and that you have [squashed](https://medium.com/@slamflipstrom/a-beginners-guide-to-squashing-commits-with-git-rebase-8185cf6e62ec) all relevant commits related to your change - [ ] Please make sure that you have tested your change locally by successfully [building and deploying the VMware Event Broker Appliance](https://vmweventbroker.io/kb/contribute-appliance) and/or [building and deploying VMware Event Router](https://vmweventbroker.io/kb/contribute-eventrouter) - [ ] Please include any relevant screenshots and/or output as part of your testing - [ ] Please include any documentation updates that is applicable for your changes @@ -33,7 +33,7 @@ Please check the type of change your PR introduces: List of Issues closed or resolved by this PR. Add multiple `Closes` keyword followed by the issue number (e.g. Closes #ISSUE-NUMBER) -* Closes #ISSUE-NUMBER +Closes #ISSUE-NUMBER ## Testing Verification From 72bb1511b27e88806703e673b9ffa23c652d822a Mon Sep 17 00:00:00 2001 From: Ludovic Rivallain Date: Wed, 28 Apr 2021 10:53:50 +0200 Subject: [PATCH 03/56] Fix #355 by helm-ignoring releases folder Signed-off-by: Ludovic Rivallain --- vmware-event-router/chart/.helmignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vmware-event-router/chart/.helmignore b/vmware-event-router/chart/.helmignore index 0e8a0eb3..591b4835 100644 --- a/vmware-event-router/chart/.helmignore +++ b/vmware-event-router/chart/.helmignore @@ -21,3 +21,5 @@ .idea/ *.tmproj .vscode/ +# archives +releases/ From b567d658bf6c54dd3f2b9a95c64c811209d1d2b6 Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Thu, 29 Apr 2021 11:27:33 +0200 Subject: [PATCH 04/56] Update helm chart Bump helm chart version to v0.6.1 to include the fix in #355 Closes: #357 Signed-off-by: Michael Gasch --- vmware-event-router/chart/Chart.yaml | 2 +- .../chart/releases/event-router-v0.6.1.tgz | Bin 0 -> 2857 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 vmware-event-router/chart/releases/event-router-v0.6.1.tgz diff --git a/vmware-event-router/chart/Chart.yaml b/vmware-event-router/chart/Chart.yaml index df5d916b..564d58af 100644 --- a/vmware-event-router/chart/Chart.yaml +++ b/vmware-event-router/chart/Chart.yaml @@ -3,7 +3,7 @@ name: event-router description: The VMware Event Router is used to connect to various VMware event providers and forwards received events to supported event processors. home: https://vmweventbroker.io/ type: application -version: v0.6.0 +version: v0.6.1 appVersion: v0.6.0 maintainers: - name: Michael Gasch diff --git a/vmware-event-router/chart/releases/event-router-v0.6.1.tgz b/vmware-event-router/chart/releases/event-router-v0.6.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..a4220e8529f242f202603b56530e5eda75b98427 GIT binary patch literal 2857 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 H05|{u`mBxC literal 0 HcmV?d00001 From 7a24aefdf625a451fa5d68aa0a5929a33f7f0768 Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Thu, 29 Apr 2021 14:20:00 +0200 Subject: [PATCH 05/56] Verify Helm chart Runs helm chart deployment verification on every change in the chart/ folder. Closes: #359 Signed-off-by: Michael Gasch --- .github/workflows/router-verify-helm.yml | 101 +++++++++++++++++++++++ vmware-event-router/deploy/vcsim.yaml | 33 ++++++++ 2 files changed, 134 insertions(+) create mode 100644 .github/workflows/router-verify-helm.yml create mode 100644 vmware-event-router/deploy/vcsim.yaml diff --git a/.github/workflows/router-verify-helm.yml b/.github/workflows/router-verify-helm.yml new file mode 100644 index 00000000..2fa62474 --- /dev/null +++ b/.github/workflows/router-verify-helm.yml @@ -0,0 +1,101 @@ +name: VMware Event Router Helm Test + +# triggered on every PR and commit on changes inside the vmware-event-router +# (Helm) chart directory +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - "vmware-event-router/chart/**" + push: + paths: + - "vmware-event-router/chart/**" + +jobs: + helm: + name: Verify Helm chart (latest release) + runs-on: ubuntu-latest + env: + KO_DOCKER_REPO: kind.local + KO_VERSION: 0.8.2 + KIND_VERSION: v0.10.0 + NAMESPACE: vmware + CHART_REPO: "https://projects.registry.vmware.com/chartrepo/veba" + timeout-minutes: 15 + + steps: + - name: Install KinD + run: | + curl -L https://github.com/google/ko/releases/download/v${KO_VERSION}/ko_${KO_VERSION}_Linux_x86_64.tar.gz | tar xzf - ko + chmod +x ./ko + sudo mv ko /usr/local/bin + + - name: Check out code onto GOPATH + uses: actions/checkout@v2 + with: + fetch-depth: 1 + + - name: Setup KinD Cluster + run: | + set -x + curl -Lo ./kind https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-$(uname)-amd64 + chmod +x ./kind + sudo mv kind /usr/local/bin + + # create cluster with defaults + kind create cluster --wait 3m + + - name: Install OpenFaaS with Helm + run: | + kubectl create ns openfaas && kubectl create ns openfaas-fn + helm repo add openfaas https://openfaas.github.io/faas-netes + helm repo update + helm upgrade openfaas --install openfaas/openfaas \ + --namespace openfaas \ + --set functionNamespace=openfaas-fn \ + --set generateBasicAuth=true --wait + + kubectl wait --timeout=1m --for=condition=Available -n openfaas deploy/gateway + echo "OF_PASS=$(kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode)" >> $GITHUB_ENV + + - name: Install vCenter Simulator + working-directory: ./vmware-event-router + run: | + kubectl create ns ${NAMESPACE} + kubectl -n ${NAMESPACE} apply -f deploy/vcsim.yaml + kubectl wait --timeout=1m --for=condition=Available -n ${NAMESPACE} deploy/vcsim + + - name: Install VMware Event Router with Helm + run: | + echo "::group::Create override.yaml" + cat << EOF > override.yaml + eventrouter: + config: + logLevel: debug + vcenter: + address: https://vcsim.vmware.svc.cluster.local + username: user + password: pass + insecure: true # ignore TLS certs + openfaas: + address: http://gateway.openfaas.svc.cluster.local:8080 + basicAuth: true + username: admin + password: ${OF_PASS} + EOF + echo "::endgroup::" + + echo "::group::Deploy VMware Event Router" + helm repo add vmware-veba ${CHART_REPO} + helm install -n vmware --create-namespace veba vmware-veba/event-router -f override.yaml --wait + + # assert it deployed correctly + kubectl wait --timeout=1m --for=condition=Available -n vmware deploy/router + echo "::endgroup::" + + - name: "Debug" + if: ${{ always() }} + run: | + kubectl get pods --all-namespaces + kubectl -n ${NAMESPACE} describe pods + kubectl -n ${NAMESPACE} get events \ No newline at end of file diff --git a/vmware-event-router/deploy/vcsim.yaml b/vmware-event-router/deploy/vcsim.yaml new file mode 100644 index 00000000..1a21c57c --- /dev/null +++ b/vmware-event-router/deploy/vcsim.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vcsim +spec: + selector: + matchLabels: + app: vcsim + template: + metadata: + labels: + app: vcsim + spec: + containers: + - name: vcsim + image: vmware/vcsim:latest + args: ["/vcsim", "-l", ":8989"] + ports: + - name: https + containerPort: 8989 + +--- +apiVersion: v1 +kind: Service +metadata: + name: vcsim +spec: + selector: + app: vcsim + ports: + - name: https + port: 443 + targetPort: 8989 From 166f39630e60c8dc795f04027308e7474de3c0d2 Mon Sep 17 00:00:00 2001 From: William Lam Date: Thu, 29 Apr 2021 06:40:19 -0700 Subject: [PATCH 06/56] Document minimum vSphere Privileges for VEBA UI Signed-off-by: William Lam --- docs/kb/install-knative.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/kb/install-knative.md b/docs/kb/install-knative.md index 9815f414..09b4b047 100644 --- a/docs/kb/install-knative.md +++ b/docs/kb/install-knative.md @@ -61,6 +61,8 @@ Deploy the VMware Event Broker Appliance OVA to your vCenter Server using the vS * vCenter Password to register VEBA UI (Optional) - Password to register VMware Event Broker UI to vCenter Server for Knative Processor * Disable vCenter Server TLS Verification - If you have a self-signed SSL Certificate, you will need to check this box +> **Note:** The minimum vSphere Privileges that is required for proper VEBA UI functionality are: **Register Extension**, **Update Extension** (Installing Plugins) and **Manage Plugins** (Updating Plugins) + #### **Event Processor Configuration** (**Required**) * Event Processor - Choose Knative From 016bd9af33cd5037c5c3ff4aa8fe2a1b8e1d8650 Mon Sep 17 00:00:00 2001 From: William Lam Date: Thu, 29 Apr 2021 09:13:48 -0700 Subject: [PATCH 07/56] Document multiple Knative Triggers Signed-off-by: William Lam --- docs/kb/intro-functions.md | 46 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/docs/kb/intro-functions.md b/docs/kb/intro-functions.md index e1fa0896..1b9fbbaf 100644 --- a/docs/kb/intro-functions.md +++ b/docs/kb/intro-functions.md @@ -22,7 +22,7 @@ The VMware Event Broker Appliance can be deployed using either Knative or OpenFa - [Knative](#knative) - [Knative Naming and Version Control](#knative-naming-and-version-control) - [Knative Service](#knative-service) - - [Knative Trigger](#knative-service) + - [Knative Trigger](#knative-trigger) - [Knative Combined Service and Trigger](#knative-combined-service-and-trigger) - [Knative Secrets](#knative-secrets) - [Knative Environment Variables](#knative-environment-variables) @@ -154,7 +154,49 @@ The value of this field: `kn-ps-echo`: The name of the Knative Service -> **Note:** Today, a single Knative Trigger can only filter on one vCenter Server event. To associate multiple vCenter Server events to a given Knative Service, you simply create a Knative Trigger for each event. +Today, a single Knative Trigger can only filter on one vCenter Server event. To associate multiple vCenter Server events to a given Knative Service, you simply create a Knative Trigger for each event as shown in the two examples below, one for `VmPoweredOffEvent` and `DrsVmPoweredOnEvent` vCenter Events respectively: + +`kn-trigger-1.yaml` +``` +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-ps-echo-trigger-1 + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + subject: VmPoweredOffEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-ps-echo +``` + +`kn-trigger-2.yaml` +``` +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-ps-echo-trigger-2 + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + subject: DrsVmPoweredOnEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-ps-echo +``` ## Knative Combined Service and Trigger From 6f33d65a4eb37ca110638bc7ee321c88ac1dc5d3 Mon Sep 17 00:00:00 2001 From: William Lam Date: Thu, 29 Apr 2021 13:57:09 -0700 Subject: [PATCH 08/56] Update the correct cert name for TLS replacement Closes #367 Signed-off-by: William Lam --- docs/kb/advanced-certificates.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/kb/advanced-certificates.md b/docs/kb/advanced-certificates.md index e614ec29..1958093f 100644 --- a/docs/kb/advanced-certificates.md +++ b/docs/kb/advanced-certificates.md @@ -42,13 +42,13 @@ If you are using the Embedded Knative Broker, you will also need to reference th ```bash cd /folder/certs/location -KNATIVE_CERT_NAME=eventrouter-tls #DO NOT CHANGE THIS +KNATIVE_CERT_NAME=default-cert #DO NOT CHANGE THIS KEY_FILE=.pem CERT_FILE=.cer #recreate the tls secret kubectl -n contour-external delete secret ${KNATIVE_CERT_NAME} -kubectl -n contour-external create secret tls default-cert --key ${KEY_FILE} --cert ${CERT_FILE} +kubectl -n contour-external create secret tls ${KNATIVE_CERT_NAME} --key ${KEY_FILE} --cert ${CERT_FILE} #reapply the config to take the new certificate kubectl apply -f /root/config/ingressroute-gateway.yaml From c7da5cc25c3086227b0fadabc6b38a846d496a2f Mon Sep 17 00:00:00 2001 From: William Lam Date: Fri, 30 Apr 2021 13:16:00 -0700 Subject: [PATCH 09/56] Add correct AWS Event Bridge Type into Event Router Config Signed-off-by: William Lam --- files/setup-06-event-processor.sh | 2 +- vmware-event-router/deploy/event-router-config-aws.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/files/setup-06-event-processor.sh b/files/setup-06-event-processor.sh index 83e8e766..a4daf5ea 100755 --- a/files/setup-06-event-processor.sh +++ b/files/setup-06-event-processor.sh @@ -118,7 +118,7 @@ eventProcessor: region: ${AWS_EVENTBRIDGE_REGION} ruleARN: ${ESCAPED_AWS_EVENTBRIDGE_RULE_ARN} name: veba-aws - type: awsEventBridge + type: aws_event_bridge eventProvider: name: veba-vc-01 type: vcenter diff --git a/vmware-event-router/deploy/event-router-config-aws.yaml b/vmware-event-router/deploy/event-router-config-aws.yaml index ba2eb61e..5cafc5e5 100644 --- a/vmware-event-router/deploy/event-router-config-aws.yaml +++ b/vmware-event-router/deploy/event-router-config-aws.yaml @@ -17,7 +17,7 @@ eventProvider: username: administrator@vsphere.local password: ReplaceMe eventProcessor: - type: awsEventBridge + type: aws_event_bridge name: veba-demo-aws awsEventBridge: eventBus: default From 33ed68914a5832a6d9f5dee3b271a0bfcc9f2054 Mon Sep 17 00:00:00 2001 From: William Lam Date: Fri, 30 Apr 2021 14:25:58 -0700 Subject: [PATCH 10/56] Add dispatcher container image to VEBA BOM for RabbitMQ Broker deployment Signed-off-by: William Lam --- veba-bom.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/veba-bom.json b/veba-bom.json index c1aed015..583c6a31 100644 --- a/veba-bom.json +++ b/veba-bom.json @@ -140,6 +140,10 @@ { "name": "gcr.io/knative-releases/knative.dev/eventing-rabbitmq/cmd/ingress@sha256", "version": "94232a856453d0761bf0ffd1fb13cb2a957591fabac8821c1be796098acc6d62" + }, + { + "name": "gcr.io/knative-releases/knative.dev/eventing-rabbitmq/cmd/dispatcher@sha256", + "version": "2f99ca7c62a68c213d17c23c17ec92e3b4815cec982631c3050d9ccee38fabcb" } ] }, From c778a0aa1082282fa87dce94301fa3e405614099 Mon Sep 17 00:00:00 2001 From: William Lam Date: Fri, 30 Apr 2021 14:43:08 -0700 Subject: [PATCH 11/56] VEBA UI fix for incorrect TLS miss-match Signed-off-by: William Lam --- veba-bom.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veba-bom.json b/veba-bom.json index 583c6a31..cedc80b9 100644 --- a/veba-bom.json +++ b/veba-bom.json @@ -13,7 +13,7 @@ "gitRepoTag": "v0.6.0", "containers": [{ "name": "projects.registry.vmware.com/veba/veba-ui", - "version": "b5412f7d" + "version": "ff40f863" }] }, "antrea": { From db9399e40f7def11f9703254472615591bde9ad2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Apr 2021 22:08:00 +0000 Subject: [PATCH 12/56] Bump urllib3 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.6 to 1.25.8. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.25.6...1.25.8) Signed-off-by: dependabot[bot] --- .../python/trigger-pagerduty-incident/handler/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/openfaas/python/trigger-pagerduty-incident/handler/requirements.txt b/examples/openfaas/python/trigger-pagerduty-incident/handler/requirements.txt index 7c5a6ab8..2a9a5f36 100644 --- a/examples/openfaas/python/trigger-pagerduty-incident/handler/requirements.txt +++ b/examples/openfaas/python/trigger-pagerduty-incident/handler/requirements.txt @@ -1,2 +1,2 @@ -urllib3==1.25.6 +urllib3==1.25.8 requests==2.22.0 From 8fa4b40d833a76a43fb0a52839bdd13f64b7aebe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Apr 2021 22:10:41 +0000 Subject: [PATCH 13/56] Bump urllib3 in /examples/openfaas/python/invoke-rest-api/handler Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.6 to 1.25.8. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.25.6...1.25.8) Signed-off-by: dependabot[bot] --- .../openfaas/python/invoke-rest-api/handler/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/openfaas/python/invoke-rest-api/handler/requirements.txt b/examples/openfaas/python/invoke-rest-api/handler/requirements.txt index c195d28b..ca1d0a48 100644 --- a/examples/openfaas/python/invoke-rest-api/handler/requirements.txt +++ b/examples/openfaas/python/invoke-rest-api/handler/requirements.txt @@ -1,3 +1,3 @@ -urllib3==1.25.6 +urllib3==1.25.8 requests==2.22.0 dpath==2.0.1 From 46d7e5d36cd679a1636bb69cc3e22441d3eb5468 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Apr 2021 22:11:01 +0000 Subject: [PATCH 14/56] Bump urllib3 in /examples/openfaas/python/tagging/handler Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.6 to 1.25.8. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.25.6...1.25.8) Signed-off-by: dependabot[bot] --- examples/openfaas/python/tagging/handler/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/openfaas/python/tagging/handler/requirements.txt b/examples/openfaas/python/tagging/handler/requirements.txt index c0637ff5..06c09a70 100644 --- a/examples/openfaas/python/tagging/handler/requirements.txt +++ b/examples/openfaas/python/tagging/handler/requirements.txt @@ -1,3 +1,3 @@ -urllib3==1.25.6 +urllib3==1.25.8 requests==2.22.0 toml==0.10.0 From d288918da23eabc2345d98c3115464eea726f466 Mon Sep 17 00:00:00 2001 From: William Lam Date: Fri, 30 Apr 2021 13:13:07 -0700 Subject: [PATCH 15/56] Refactor Ingress Configuration based on Processor Type Signed-off-by: William Lam --- files/eventbridge-ingressroute-gateway.yaml | 39 ++++ ...knative-embedded-ingressroute-gateway.yaml | 75 ++++++ ...embedded-veba-ui-ingressroute-gateway.yaml | 80 +++++++ ...knative-external-ingressroute-gateway.yaml | 38 +++ files/openfaas-ingressroute-gateway.yaml | 58 +++++ files/setup-09-ingress.sh | 216 ++---------------- photon.json | 25 ++ 7 files changed, 337 insertions(+), 194 deletions(-) create mode 100644 files/eventbridge-ingressroute-gateway.yaml create mode 100644 files/knative-embedded-ingressroute-gateway.yaml create mode 100644 files/knative-embedded-veba-ui-ingressroute-gateway.yaml create mode 100644 files/knative-external-ingressroute-gateway.yaml create mode 100644 files/openfaas-ingressroute-gateway.yaml diff --git a/files/eventbridge-ingressroute-gateway.yaml b/files/eventbridge-ingressroute-gateway.yaml new file mode 100644 index 00000000..fa2646ac --- /dev/null +++ b/files/eventbridge-ingressroute-gateway.yaml @@ -0,0 +1,39 @@ +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + labels: + app: vmware + name: event-router + namespace: vmware-system +spec: + routes: + - conditions: + - prefix: /status + pathRewritePolicy: + replacePrefix: + - replacement: /status + services: + - name: tinywww + port: 8100 + - conditions: + - prefix: /bootstrap + pathRewritePolicy: + replacePrefix: + - replacement: /bootstrap + services: + - name: tinywww + port: 8100 + - conditions: + - prefix: /stats + pathRewritePolicy: + replacePrefix: + - replacement: /stats + services: + - name: vmware-event-router + port: 8082 + virtualhost: + fqdn: ##HOSTNAME## + tls: + minimumProtocolVersion: "1.2" + secretName: ##CERT_NAME## +status: {} \ No newline at end of file diff --git a/files/knative-embedded-ingressroute-gateway.yaml b/files/knative-embedded-ingressroute-gateway.yaml new file mode 100644 index 00000000..369b2ae6 --- /dev/null +++ b/files/knative-embedded-ingressroute-gateway.yaml @@ -0,0 +1,75 @@ +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + annotations: + kubernetes.io/ingress.class: contour-external + labels: + app: vmware + name: event-router + namespace: vmware-system +spec: + routes: + - conditions: + - prefix: /status + pathRewritePolicy: + replacePrefix: + - replacement: /status + services: + - name: tinywww + port: 8100 + - conditions: + - prefix: /bootstrap + pathRewritePolicy: + replacePrefix: + - replacement: /bootstrap + services: + - name: tinywww + port: 8100 + - conditions: + - prefix: /stats + pathRewritePolicy: + replacePrefix: + - replacement: /stats + services: + - name: vmware-event-router + port: 8082 + virtualhost: + fqdn: ##HOSTNAME## + tls: + minimumProtocolVersion: "1.2" + secretName: ##CERT_NAME## + includes: + - name: sockeye + namespace: vmware-functions +--- +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + annotations: + kubernetes.io/ingress.class: contour-external + name: sockeye + namespace: vmware-functions +spec: + routes: + - conditions: + - prefix: /events + pathRewritePolicy: + replacePrefix: + - replacement: / + services: + - name: sockeye + port: 80 + - conditions: + - prefix: /static + pathRewritePolicy: + replacePrefix: + - replacement: /static + services: + - name: sockeye + port: 80 + - conditions: + - prefix: /ws + enableWebsockets: true + services: + - name: sockeye + port: 80 \ No newline at end of file diff --git a/files/knative-embedded-veba-ui-ingressroute-gateway.yaml b/files/knative-embedded-veba-ui-ingressroute-gateway.yaml new file mode 100644 index 00000000..2cf9af1e --- /dev/null +++ b/files/knative-embedded-veba-ui-ingressroute-gateway.yaml @@ -0,0 +1,80 @@ +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + annotations: + kubernetes.io/ingress.class: contour-external + labels: + app: vmware + name: event-router + namespace: vmware-system +spec: + routes: + - conditions: + - prefix: /status + pathRewritePolicy: + replacePrefix: + - replacement: /status + services: + - name: tinywww + port: 8100 + - conditions: + - prefix: /bootstrap + pathRewritePolicy: + replacePrefix: + - replacement: /bootstrap + services: + - name: tinywww + port: 8100 + - conditions: + - prefix: /stats + pathRewritePolicy: + replacePrefix: + - replacement: /stats + services: + - name: vmware-event-router + port: 8082 + - conditions: + - prefix: /veba-ui + services: + - name: veba-ui + port: 80 + virtualhost: + fqdn: ##HOSTNAME## + tls: + minimumProtocolVersion: "1.2" + secretName: ##CERT_NAME## + includes: + - name: sockeye + namespace: vmware-functions +--- +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + annotations: + kubernetes.io/ingress.class: contour-external + name: sockeye + namespace: vmware-functions +spec: + routes: + - conditions: + - prefix: /events + pathRewritePolicy: + replacePrefix: + - replacement: / + services: + - name: sockeye + port: 80 + - conditions: + - prefix: /static + pathRewritePolicy: + replacePrefix: + - replacement: /static + services: + - name: sockeye + port: 80 + - conditions: + - prefix: /ws + enableWebsockets: true + services: + - name: sockeye + port: 80 \ No newline at end of file diff --git a/files/knative-external-ingressroute-gateway.yaml b/files/knative-external-ingressroute-gateway.yaml new file mode 100644 index 00000000..331c0907 --- /dev/null +++ b/files/knative-external-ingressroute-gateway.yaml @@ -0,0 +1,38 @@ +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + labels: + app: vmware + name: event-router + namespace: vmware-system +spec: + routes: + - conditions: + - prefix: /status + pathRewritePolicy: + replacePrefix: + - replacement: /status + services: + - name: tinywww + port: 8100 + - conditions: + - prefix: /bootstrap + pathRewritePolicy: + replacePrefix: + - replacement: /bootstrap + services: + - name: tinywww + port: 8100 + - conditions: + - prefix: /stats + pathRewritePolicy: + replacePrefix: + - replacement: /stats + services: + - name: vmware-event-router + port: 8082 + virtualhost: + fqdn: ##HOSTNAME## + tls: + minimumProtocolVersion: "1.2" + secretName: ##CERT_NAME## \ No newline at end of file diff --git a/files/openfaas-ingressroute-gateway.yaml b/files/openfaas-ingressroute-gateway.yaml new file mode 100644 index 00000000..cbc74b72 --- /dev/null +++ b/files/openfaas-ingressroute-gateway.yaml @@ -0,0 +1,58 @@ +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + labels: + app: vmware + name: event-router + namespace: vmware-system +spec: + includes: + - conditions: + - prefix: / + name: gateway + namespace: openfaas + routes: + - conditions: + - prefix: /status + pathRewritePolicy: + replacePrefix: + - replacement: /status + services: + - name: tinywww + port: 8100 + - conditions: + - prefix: /bootstrap + pathRewritePolicy: + replacePrefix: + - replacement: /bootstrap + services: + - name: tinywww + port: 8100 + - conditions: + - prefix: /stats + pathRewritePolicy: + replacePrefix: + - replacement: /stats + services: + - name: vmware-event-router + port: 8082 + virtualhost: + fqdn: ##HOSTNAME## + tls: + minimumProtocolVersion: "1.2" + secretName: ##CERT_NAME## +status: {} +--- +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: gateway + namespace: openfaas +spec: + routes: + - conditions: + - prefix: / + services: + - name: gateway + port: 8080 +status: {} \ No newline at end of file diff --git a/files/setup-09-ingress.sh b/files/setup-09-ingress.sh index 471426b9..fd3db0be 100755 --- a/files/setup-09-ingress.sh +++ b/files/setup-09-ingress.sh @@ -32,201 +32,29 @@ if [ "${KNATIVE_DEPLOYMENT_TYPE}" == "embedded" ]; then kubectl patch configmap -n knative-serving config-domain -p "{\"data\": {\"$CN_NAME\": \"\"}}" fi -# Deploy Ingress Route - +# Ingress Route Configuration for OpenFaaS if [ "${EVENT_PROCESSOR_TYPE}" == "OpenFaaS" ]; then - cat << EOF > /root/config/ingressroute-gateway.yaml ---- - -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - labels: - app: vmware - name: event-router - namespace: vmware-system -spec: - includes: - - conditions: - - prefix: / - name: gateway - namespace: openfaas - routes: - - conditions: - - prefix: /status - pathRewritePolicy: - replacePrefix: - - replacement: /status - services: - - name: tinywww - port: 8100 - - conditions: - - prefix: /bootstrap - pathRewritePolicy: - replacePrefix: - - replacement: /bootstrap - services: - - name: tinywww - port: 8100 - - conditions: - - prefix: /stats - pathRewritePolicy: - replacePrefix: - - replacement: /stats - services: - - name: vmware-event-router - port: 8082 - virtualhost: - fqdn: ${HOSTNAME} - tls: - minimumProtocolVersion: "1.2" - secretName: ${CERT_NAME} -status: {} ---- - -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - name: gateway - namespace: openfaas -spec: - routes: - - conditions: - - prefix: / - services: - - name: gateway - port: 8080 -status: {} -EOF + INGRESS_CONFIG_YAML=/root/config/openfaas-ingressroute-gateway.yaml +# Ingress Route Configuration for AWS EventBridge +elif [ "${EVENT_PROCESSOR_TYPE}" == "AWS EventBridge" ]; then + INGRESS_CONFIG_YAML=/root/config/eventbridge-ingressroute-gateway.yaml +# Ingress Route Configuration for Knative External +elif [[ "${EVENT_PROCESSOR_TYPE}" == "Knative" ]] && [[ "${KNATIVE_DEPLOYMENT_TYPE}" == "external" ]]; then + INGRESS_CONFIG_YAML=/root/config/knative-external-ingressroute-gateway.yaml +# Ingress Route Configuration for Knative Embedded w/VEBA UI +elif [[ "${EVENT_PROCESSOR_TYPE}" == "Knative" ]] && [[ "${KNATIVE_DEPLOYMENT_TYPE}" == "embedded" ]] && [[ ! -z ${VCENTER_USERNAME_FOR_VEBA_UI} ]] && [[ ! -z ${VCENTER_PASSWORD_FOR_VEBA_UI} ]]; then + INGRESS_CONFIG_YAML=/root/config/knative-embedded-veba-ui-ingressroute-gateway.yaml +# Ingress Route Configuration for Knative Embedded w/o VEBA UI elif [[ "${EVENT_PROCESSOR_TYPE}" == "Knative" ]] && [[ "${KNATIVE_DEPLOYMENT_TYPE}" == "embedded" ]]; then - cat << EOF > /root/config/ingressroute-gateway.yaml ---- - -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - annotations: - kubernetes.io/ingress.class: contour-external - labels: - app: vmware - name: event-router - namespace: vmware-system -spec: - routes: - - conditions: - - prefix: /status - pathRewritePolicy: - replacePrefix: - - replacement: /status - services: - - name: tinywww - port: 8100 - - conditions: - - prefix: /bootstrap - pathRewritePolicy: - replacePrefix: - - replacement: /bootstrap - services: - - name: tinywww - port: 8100 - - conditions: - - prefix: /stats - pathRewritePolicy: - replacePrefix: - - replacement: /stats - services: - - name: vmware-event-router - port: 8082 - - conditions: - - prefix: /veba-ui - services: - - name: veba-ui - port: 80 - virtualhost: - fqdn: ${HOSTNAME} - tls: - minimumProtocolVersion: "1.2" - secretName: ${CERT_NAME} - includes: - - name: sockeye - namespace: vmware-functions ---- -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - annotations: - kubernetes.io/ingress.class: contour-external - name: sockeye - namespace: vmware-functions -spec: - routes: - - conditions: - - prefix: /events - pathRewritePolicy: - replacePrefix: - - replacement: / - services: - - name: sockeye - port: 80 - - conditions: - - prefix: /static - pathRewritePolicy: - replacePrefix: - - replacement: /static - services: - - name: sockeye - port: 80 - - conditions: - - prefix: /ws - enableWebsockets: true - services: - - name: sockeye - port: 80 -EOF -else - cat << EOF > /root/config/ingressroute-gateway.yaml ---- - -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - labels: - app: vmware - name: event-router - namespace: vmware-system -spec: - routes: - - conditions: - - prefix: /status - pathRewritePolicy: - replacePrefix: - - replacement: /status - services: - - name: tinywww - port: 8100 - - conditions: - - prefix: /bootstrap - pathRewritePolicy: - replacePrefix: - - replacement: /bootstrap - services: - - name: tinywww - port: 8100 - - conditions: - - prefix: /stats - pathRewritePolicy: - replacePrefix: - - replacement: /stats - services: - - name: vmware-event-router - port: 8082 - virtualhost: - fqdn: ${HOSTNAME} - tls: - minimumProtocolVersion: "1.2" - secretName: ${CERT_NAME} -status: {} -EOF + INGRESS_CONFIG_YAML=/root/config/knative-embedded-ingressroute-gateway.yaml fi -kubectl create -f /root/config/ingressroute-gateway.yaml \ No newline at end of file +if [ ! -z ${INGRESS_CONFIG_YAML} ]; then + echo -e "\e[92mDeploying Ingress using configuration ${INGRESS_CONFIG_YAML} ..." > /dev/console + sed -i "s/##HOSTNAME##/${HOSTNAME}/s" ${INGRESS_CONFIG_YAML} + sed -i "s/##CERT_NAME##/${CERT_NAME}/s" ${INGRESS_CONFIG_YAML} + kubectl create -f ${INGRESS_CONFIG_YAML} +else + echo -e "\e[91mUnable to match a supported Ingress configuration ..." > /dev/console + exit 1 +fi \ No newline at end of file diff --git a/photon.json b/photon.json index 533c1d8e..2622940e 100644 --- a/photon.json +++ b/photon.json @@ -169,6 +169,31 @@ "type": "file", "source": "files/veba-dcui", "destination": "/usr/bin/veba-dcui" + }, + { + "type": "file", + "source": "files/knative-embedded-veba-ui-ingressroute-gateway.yaml", + "destination": "/root/config/knative-embedded-veba-ui-ingressroute-gateway.yaml" + }, + { + "type": "file", + "source": "files/knative-embedded-ingressroute-gateway.yaml", + "destination": "/root/config/knative-embedded-ingressroute-gateway.yaml" + }, + { + "type": "file", + "source": "files/knative-external-ingressroute-gateway.yaml", + "destination": "/root/config/knative-external-ingressroute-gateway.yaml" + }, + { + "type": "file", + "source": "files/eventbridge-ingressroute-gateway.yaml", + "destination": "/root/config/eventbridge-ingressroute-gateway.yaml" + }, + { + "type": "file", + "source": "files/openfaas-ingressroute-gateway.yaml", + "destination": "/root/config/openfaas-ingressroute-gateway.yaml" } ], "post-processors": [ From bf21bbd10f76810bba5d4903ebd2d53dc6d62650 Mon Sep 17 00:00:00 2001 From: William Lam Date: Mon, 3 May 2021 16:34:07 -0700 Subject: [PATCH 16/56] Add support for custom VEBA TLS Certificate Signed-off-by: William Lam --- files/setup-09-ingress.sh | 12 +++++++++--- files/setup.sh | 2 ++ manual/photon.xml.template | 9 +++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/files/setup-09-ingress.sh b/files/setup-09-ingress.sh index fd3db0be..faf36f10 100755 --- a/files/setup-09-ingress.sh +++ b/files/setup-09-ingress.sh @@ -12,13 +12,19 @@ if [[ "${KNATIVE_DEPLOYMENT_TYPE}" == "na" ]] || [[ "${KNATIVE_DEPLOYMENT_TYPE}" kubectl create -f /root/download/contour/examples/contour/ fi -## Create SSL Certificate & Secret KEY_FILE=/root/config/eventrouter.key CERT_FILE=/root/config/eventrouter.crt -CN_NAME=$(hostname -f) CERT_NAME=eventrouter-tls +CN_NAME=$(hostname -f) -openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ${KEY_FILE} -out ${CERT_FILE} -subj "/CN=${CN_NAME}/O=${CN_NAME}" +# Customer provided TLS Certificate +if [[ ! -z ${CUSTOM_VEBA_TLS_PRIVATE_KEY} ]] && [[ ! -z ${CUSTOM_VEBA_TLS_CA_CERT} ]]; then + echo ${CUSTOM_VEBA_TLS_PRIVATE_KEY} | /usr/bin/base64 -d > ${KEY_FILE} + echo ${CUSTOM_VEBA_TLS_CA_CERT} | /usr/bin/base64 -d > ${CERT_FILE} +else + # Create Self Sign TLS Certifcate + openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ${KEY_FILE} -out ${CERT_FILE} -subj "/CN=${CN_NAME}/O=${CN_NAME}" +fi kubectl -n vmware-system create secret tls ${CERT_NAME} --key ${KEY_FILE} --cert ${CERT_FILE} diff --git a/files/setup.sh b/files/setup.sh index 11e461f7..ac2f43a2 100755 --- a/files/setup.sh +++ b/files/setup.sh @@ -39,6 +39,8 @@ AWS_EVENTBRIDGE_EVENT_BUS=$(/root/setup/getOvfProperty.py "guestinfo.aws_eb_even AWS_EVENTBRIDGE_REGION=$(/root/setup/getOvfProperty.py "guestinfo.aws_eb_region") AWS_EVENTBRIDGE_RULE_ARN=$(/root/setup/getOvfProperty.py "guestinfo.aws_eb_arn") AWS_EVENTBRIDGE_ADV_OPTION=$(/root/setup/getOvfProperty.py "guestinfo.aws_eb_advanced_options") +CUSTOM_VEBA_TLS_PRIVATE_KEY=$(/root/setup/getOvfProperty.py "guestinfo.custom_tls_private_key") +CUSTOM_VEBA_TLS_CA_CERT=$(/root/setup/getOvfProperty.py "guestinfo.custom_tls_ca_cert") DOCKER_NETWORK_CIDR=$(/root/setup/getOvfProperty.py "guestinfo.docker_network_cidr") POD_NETWORK_CIDR=$(/root/setup/getOvfProperty.py "guestinfo.pod_network_cidr") LOCAL_STORAGE_DISK="/dev/sdb" diff --git a/manual/photon.xml.template b/manual/photon.xml.template index 2fd425f1..d6e4e1f7 100644 --- a/manual/photon.xml.template +++ b/manual/photon.xml.template @@ -145,6 +145,15 @@ Opaque string for applying advanced configurations for AWS EventBridge Processor. For advanced use cases only, please see documentation for more details + Custom TLS Certificate Configuration + + + Base64 encoded custom TLS certificate (.PEM) for the VMware Event Broker Appliance + + + + Base64 encoded custom TLS certificate (.CER) for the VMware Event Broker Appliance + zAdvanced From 5169b39c04cf269f04097884dfa8b8921354679b Mon Sep 17 00:00:00 2001 From: William Lam Date: Mon, 3 May 2021 16:38:54 -0700 Subject: [PATCH 17/56] Configure container log rotation Signed-off-by: William Lam --- files/setup-01-os.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/files/setup-01-os.sh b/files/setup-01-os.sh index 8995448b..59b35bbb 100755 --- a/files/setup-01-os.sh +++ b/files/setup-01-os.sh @@ -24,7 +24,11 @@ if [ "${DOCKER_NETWORK_CIDR}" != "172.17.0.1/16" ]; then echo -e "\e[92mConfiguring Docker Bridge Network ..." > /dev/console cat > /etc/docker/daemon.json << EOF { - "bip": "${DOCKER_NETWORK_CIDR}" + "bip": "${DOCKER_NETWORK_CIDR}", + "log-opts": { + "max-size": "10m", + "max-file": "5" + } } EOF fi From 7f8177f101de5d03fdbf4b1ee2bbeb8c2c4fb900 Mon Sep 17 00:00:00 2001 From: William Lam Date: Mon, 3 May 2021 19:54:44 -0700 Subject: [PATCH 18/56] Fix sed command Signed-off-by: William Lam --- files/setup-09-ingress.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/setup-09-ingress.sh b/files/setup-09-ingress.sh index fd3db0be..be5711b4 100755 --- a/files/setup-09-ingress.sh +++ b/files/setup-09-ingress.sh @@ -51,8 +51,8 @@ fi if [ ! -z ${INGRESS_CONFIG_YAML} ]; then echo -e "\e[92mDeploying Ingress using configuration ${INGRESS_CONFIG_YAML} ..." > /dev/console - sed -i "s/##HOSTNAME##/${HOSTNAME}/s" ${INGRESS_CONFIG_YAML} - sed -i "s/##CERT_NAME##/${CERT_NAME}/s" ${INGRESS_CONFIG_YAML} + sed -i "s/##HOSTNAME##/${HOSTNAME}/g" ${INGRESS_CONFIG_YAML} + sed -i "s/##CERT_NAME##/${CERT_NAME}/g" ${INGRESS_CONFIG_YAML} kubectl create -f ${INGRESS_CONFIG_YAML} else echo -e "\e[91mUnable to match a supported Ingress configuration ..." > /dev/console From 11a83cd5f64e0e5acad4b04399531c3965f19aa0 Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Mon, 3 May 2021 22:52:07 +0200 Subject: [PATCH 19/56] Update WIP Action Closes: #382 Signed-off-by: Michael Gasch --- .github/workflows/check-wip-pr.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-wip-pr.yml b/.github/workflows/check-wip-pr.yml index a2c1ae2f..d02e6e98 100644 --- a/.github/workflows/check-wip-pr.yml +++ b/.github/workflows/check-wip-pr.yml @@ -6,9 +6,8 @@ on: types: [opened, synchronize, reopened, edited] jobs: - WIP: + wip: runs-on: ubuntu-latest - if: startsWith(github.event.pull_request.title, 'WIP') steps: - - name: Must Exit - run: exit 1 + - name: Check WIP in PR Title + uses: embano1/wip@v1 From 168abebb6b7b7010e0999033b06d72c56711455c Mon Sep 17 00:00:00 2001 From: William Lam Date: Sat, 1 May 2021 08:41:51 -0700 Subject: [PATCH 20/56] Example Knative PowerShell Email Function Signed-off-by: William Lam --- .../knative/powershell/kn-ps-email/Dockerfile | 20 +++ .../knative/powershell/kn-ps-email/README.md | 140 ++++++++++++++++++ .../powershell/kn-ps-email/email_secret.json | 9 ++ .../powershell/kn-ps-email/function.yaml | 33 +++++ .../powershell/kn-ps-email/handler.ps1 | 95 ++++++++++++ .../kn-ps-email/screenshots/screenshot-1.png | Bin 0 -> 127526 bytes .../knative/powershell/kn-ps-email/server.ps1 | 124 ++++++++++++++++ .../kn-ps-email/test/docker-test-env-variable | 1 + .../kn-ps-email/test/send-cloudevent-test.ps1 | 16 ++ .../kn-ps-email/test/send-cloudevent-test.sh | 6 + .../kn-ps-email/test/test-payload.json | 40 +++++ 11 files changed, 484 insertions(+) create mode 100644 examples/knative/powershell/kn-ps-email/Dockerfile create mode 100644 examples/knative/powershell/kn-ps-email/README.md create mode 100644 examples/knative/powershell/kn-ps-email/email_secret.json create mode 100644 examples/knative/powershell/kn-ps-email/function.yaml create mode 100644 examples/knative/powershell/kn-ps-email/handler.ps1 create mode 100644 examples/knative/powershell/kn-ps-email/screenshots/screenshot-1.png create mode 100755 examples/knative/powershell/kn-ps-email/server.ps1 create mode 100644 examples/knative/powershell/kn-ps-email/test/docker-test-env-variable create mode 100644 examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.ps1 create mode 100755 examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.sh create mode 100644 examples/knative/powershell/kn-ps-email/test/test-payload.json diff --git a/examples/knative/powershell/kn-ps-email/Dockerfile b/examples/knative/powershell/kn-ps-email/Dockerfile new file mode 100644 index 00000000..79cf884d --- /dev/null +++ b/examples/knative/powershell/kn-ps-email/Dockerfile @@ -0,0 +1,20 @@ +FROM photon:3.0 +ENV TERM linux +ENV PORT 8080 + +# Set terminal. If we don't do this, weird readline things happen. +RUN echo "/usr/bin/pwsh" >> /etc/shells && \ + echo "/bin/pwsh" >> /etc/shells && \ + tdnf install -y powershell-7.0.3-2.ph3 unzip && \ + pwsh -c "Set-PSRepository -Name PSGallery -InstallationPolicy Trusted" && \ + find / -name "net45" | xargs rm -rf && \ + tdnf erase -y unzip && \ + tdnf clean all +RUN pwsh -c 'Install-Module ThreadJob -Force -Confirm:$false -RequiredVersion 2.0.3' && \ + pwsh -c 'Install-Module -Name CloudEvents.Sdk -RequiredVersion 0.2.0' && \ + pwsh -c 'Install-Module -Name Send-MailKitMessage -RequiredVersion 3.1.0' + +COPY server.ps1 ./ +COPY handler.ps1 handler.ps1 + +CMD ["pwsh","./server.ps1"] \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-email/README.md b/examples/knative/powershell/kn-ps-email/README.md new file mode 100644 index 00000000..4a3c57f0 --- /dev/null +++ b/examples/knative/powershell/kn-ps-email/README.md @@ -0,0 +1,140 @@ +# kn-ps-email +Example Knative PowerShell function that uses [Send-MailKitMessage](https://www.powershellgallery.com/packages/Send-MailKitMessage) to send email notification. + +![](screenshots/screenshot-1.png) + +# Step 1 - Build + +Create the container image locally to test your function logic. + +``` +docker build -t /kn-ps-email:1.0 . +``` + +# Step 2 - Test + +Verify the container image works by executing it locally. + +Change into the `test` directory +```console +cd test +``` + +Update the following variable names within the `docker-test-env-variable` file + +* SMTP_SERVER - Address of Email Server +* SMTP_PORT - Port of Email Server +* SMTP_USERNAME - Username to send email (use empty string for no username) +* SMTP_PASSWORD - Password to send email (use empty string for no password) +* EMAIL_SUBJECT - Subject to use for email +* EMAIL_TO - Recipient email address (comma separated) +* EMAIL_FROM - Sender email address to use + +Start the container image by running the following command: + +```console +docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 /kn-ps-email:1.0 +``` + +In a separate terminal, run either `send-cloudevent-test.ps1` (PowerShell Script) or `send-cloudevent-test.sh` (Bash Script) to simulate a CloudEvent payload being sent to the local container image + +``` + +Testing Function ... +See docker container console for output + +# Output from docker container console +Server start listening on 'http://*:8080/' +DEBUG: K8s Secrets: +.... +DEBUG: CloudEventData + { + "Vm": { + "Vm": { + "Type": "VirtualMachine", + "Value": "vm-10838" + }, + "Name": "FooBar" + }, + "Key": 2040013, + "Ds": null, + "Dvs": null, + "CreatedTime": "2021-04-23T20:21:02.277Z", + "Net": null, + "ChainId": 2040012, + "ComputeResource": { + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c8" + }, + "Name": "Supermicro-Cluster" + }, + "UserName": "VSPHERE.LOCAL\\william", + "ChangeTag": "", + "Datacenter": { + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-3" + }, + "Name": "Primp-Datacenter" + }, + "Host": { + "Host": { + "Type": "HostSystem", + "Value": "host-11" + }, + "Name": "192.168.30.5" + }, + "Template": false, + "FullFormattedMessage": "Removed FooBar on 192.168.30.5 from Primp-Datacenter" +} + +Sending Secure Email ... +``` + +# Step 3 - Deploy + +> **Note:** The following steps assume a working Knative environment using the +`default` Rabbit `broker`. The Knative `service` and `trigger` will be installed in the +`vmware-functions` Kubernetes namespace, assuming that the `broker` is also available there. + +Push your container image to an accessible registry such as Docker once you're done developing and testing your function logic. + +```console +docker push /kn-ps-email:1.0 +``` + +Update the `email_secret.json` file with your email configurations and then create the kubernetes secret which can then be accessed from within the function by using the environment variable named called `EMAIL_SECRET`. + +```console +# create secret + +kubectl -n vmware-functions create secret generic email-secret --from-file=EMAIL_SECRET=email_secret.json +``` + +Edit the `function.yaml` file with the name of the container image from Step 1 if you made any changes. If not, the default VMware container image will suffice. By default, the function deployment will filter on the `VmRemovedEvent` vCenter Server Event. If you wish to change this, update the `subject` field within `function.yaml` to the desired event type. + + +Deploy the function to the VMware Event Broker Appliance (VEBA). + +```console +# Deploy function + +kubectl -n vmware-functions apply -f function.yaml +``` + +For testing purposes, the `function.yaml` contains the following annotations, which will ensure the Knative Service Pod will always run **exactly** one instance for debugging purposes. Functions deployed through through the VMware Event Broker Appliance UI defaults to scale to 0, which means the pods will only run when it is triggered by an vCenter Event. + +```yaml +annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" +``` + +# Step 4 - Undeploy + +```console +# Undeploy function + +kubectl -n vmware-functions delete -f function.yaml +``` \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-email/email_secret.json b/examples/knative/powershell/kn-ps-email/email_secret.json new file mode 100644 index 00000000..61af3628 --- /dev/null +++ b/examples/knative/powershell/kn-ps-email/email_secret.json @@ -0,0 +1,9 @@ +{ + "SMTP_SERVER" : "smtp.primp-industries.com", + "SMTP_PORT" : "587", + "SMTP_USERNAME" : "email@primp-industries.com", + "SMTP_PASSWORD" : "FILE-ME-IN-PLEASE", + "EMAIL_SUBJECT" : "⚠️ [VM Delete Notification] ⚠️", + "EMAIL_TO": "admins@primp-industries.com,sre@primp-industries.com", + "EMAIL_FROM" : "notification-do-not-reply@primp-industries.com" +} \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-email/function.yaml b/examples/knative/powershell/kn-ps-email/function.yaml new file mode 100644 index 00000000..a32a0f3c --- /dev/null +++ b/examples/knative/powershell/kn-ps-email/function.yaml @@ -0,0 +1,33 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-ps-email + labels: + app: veba-ui +spec: + template: + metadata: + spec: + containers: + - image: projects.registry.vmware.com/veba/kn-ps-email:1.0 + envFrom: + - secretRef: + name: email-secret +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-ps-email-trigger + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + subject: VmRemovedEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-ps-email \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-email/handler.ps1 b/examples/knative/powershell/kn-ps-email/handler.ps1 new file mode 100644 index 00000000..a075ed6e --- /dev/null +++ b/examples/knative/powershell/kn-ps-email/handler.ps1 @@ -0,0 +1,95 @@ +Function Process-Handler { + param( + [Parameter(Position=0,Mandatory=$true)][CloudNative.CloudEvents.CloudEvent]$CloudEvent + ) + + # Decode CloudEvent + $cloudEventData = $cloudEvent | Read-CloudEventJsonData -ErrorAction SilentlyContinue -Depth 10 + if($cloudEventData -eq $null) { + $cloudEventData = $cloudEvent | Read-CloudEventData + } + + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "DEBUG: K8s Secrets:`n${env:EMAIL_SECRET}`n" + + Write-Host "DEBUG: CloudEventData`n $(${cloudEventData} | ConvertTo-Json)`n" + } + + if(${env:EMAIL_SECRET}) { + $jsonSecrets = ${env:EMAIL_SECRET} | ConvertFrom-Json + } else { + Write-Host "K8s secrets `$env:EMAIL_SECRET does not look to be defined" + break + } + + ### BEGIN BUSINESS LOGIC CODE ### + + Import-Module Send-MailKitMessage + + # Extract all Email secrets for ease of use in function + $EMAIL_SERVER=${jsonSecrets}.SMTP_SERVER + $EMAIL_SERVER_PORT=${jsonSecrets}.SMTP_PORT + $EMAIL_SERVER_USERNAME=${jsonSecrets}.SMTP_USERNAME + $EMAIL_SERVER_PASSWORD=${jsonSecrets}.SMTP_PASSWORD + $EMAIL_SUBJECT=${jsonSecrets}.EMAIL_SUBJECT + $EMAIL_TO=${jsonSecrets}.EMAIL_TO + $EMAIL_FROM=${jsonSecrets}.EMAIL_FROM + + # Extract VM Deleted Info from event for inclusion in email + $VmDeletedName = $cloudEventData.Vm.Name + $VmDeletedByUser = $cloudEventData.UserName + $VmDeletedTime = $cloudEventData.CreatedTime + + # Create Email Body + $EmailBody = "Virtual Machine ${VmDeletedName} was deleted by ${VmDeletedByUser} on ${VmDeletedTime}" + + if(${env:FUNCTION_DEBUG} -eq "true") { + $debugOutput = @" + EMAIL_SERVER=$EMAIL_SERVER + EMAIL_SERVER_PORT=$EMAIL_SERVER_PORT + EMAIL_SERVER_USERNAME=$EMAIL_SERVER_USERNAME + EMAIL_SERVER_PASSWORD=$EMAIL_SERVER_PASSWORD + EMAIL_SUBJECT=$EMAIL_SUBJECT + EMAIL_TO=$EMAIL_TO + EMAIL_FROM=$EMAIL_FROM + EmailBody=$EmailBody +"@ + + Write-Host "DEBUG: `n$debugOutput" + } + + # Secure Email + if($EMAIL_SERVER_USERNAME.length -gt 0 -and $EMAIL_SERVER_PASSWORD.length -gt 0) { + $SecurePasswordString = ConvertTo-SecureString "$EMAIL_SERVER_PASSWORD" -AsPlainText -Force + $Credential = New-Object System.Management.Automation.PSCredential($EMAIL_SERVER_USERNAME, $SecurePasswordString) + + $EmailParams = @{ + "RecipientList" = $EMAIL_TO + "From" = $EMAIL_FROM + "Subject" = $EMAIL_SUBJECT + "TextBody" = $EmailBody + "SmtpServer" = $EMAIL_SERVER + "Credential" = $Credential + "Port" = $EMAIL_SERVER_PORT + } + + Write-Host "Sending Secure Email ..." + Send-MailkitMessage @EmailParams + + } else { + # Non-Secure Email + $EmailParams = @{ + "RecipientList" = $EMAIL_TO + "From" = $EMAIL_FROM + "Subject" = $EMAIL_SUBJECT + "TextBody" = $EmailBody + "SmtpServer" = $EMAIL_SERVER + "Port" = $EMAIL_SERVER_PORT + } + + Write-Host "Sending Non-Secure Email ..." + Send-MailkitMessage @EmailParams + } + + ### END BUSINESS LOGIC CODE ### +} diff --git a/examples/knative/powershell/kn-ps-email/screenshots/screenshot-1.png b/examples/knative/powershell/kn-ps-email/screenshots/screenshot-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d9c82dc887464d32bafe6e1f6e201ce101f812 GIT binary patch literal 127526 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�b{M5_g6sC)Qp?p5M$a-|dq%ccdDDD~IF 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 Z*IqWUOdq5#T>q+Cz0vc0?o!=hp@JW7# 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Ν;(mp1*SKoY*;jZgo>ZXS4l>@ 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 Y;+Ahz&Rjc`NubX?O+$@pHHXOm2g^oM%m4rY literal 0 HcmV?d00001 diff --git a/examples/knative/powershell/kn-ps-email/server.ps1 b/examples/knative/powershell/kn-ps-email/server.ps1 new file mode 100755 index 00000000..a3fb666b --- /dev/null +++ b/examples/knative/powershell/kn-ps-email/server.ps1 @@ -0,0 +1,124 @@ +if(${env:PORT}) { + $url = "http://*:${env:PORT}/" + $localUrl = "http://localhost:${env:PORT}/" +} else { + $url = "http://*:8080/" + $localUrl = "http://localhost:8080/" +} + +$serverStopMessage = 'break-signal-e2db683c-b8ff-4c4f-8158-c44f734e2bf1' + +$backgroundServer = Start-ThreadJob { + param($url, $serverStopMessage) + + Import-Module 'Microsoft.PowerShell.Utility' + Import-Module CloudEvents.Sdk + + . ./handler.ps1 + + function Start-HttpCloudEventListener { + <# + .DESCRIPTION + Starts a HTTP Listener on specified Url + #> + + [CmdletBinding()] + param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $false, + ValueFromPipelineByPropertyName = $false)] + [ValidateNotNull()] + [string] + $Url + ) + + $listener = New-Object -Type 'System.Net.HttpListener' + $listener.AuthenticationSchemes = [System.Net.AuthenticationSchemes]::Anonymous + $listener.Prefixes.Add($Url) + + $cloudEvent = $null + + try { + $listener.Start() + $context = $listener.GetContext() + + try { + # Read Input Stream + $buffer = New-Object 'byte[]' -ArgumentList 1024 + $ms = New-Object 'IO.MemoryStream' + $read = 0 + while (($read = $context.Request.InputStream.Read($buffer, 0, 1024)) -gt 0) { + $ms.Write($buffer, 0, $read); + } + $bodyData = $ms.ToArray() + $ms.Dispose() + + # Read Headers + $headers = @{} + for($i =0; $i -lt $context.Request.Headers.Count; $i++) { + $headers[$context.Request.Headers.GetKey($i)] = $context.Request.Headers.GetValues($i) + } + + $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::OK) + } catch { + Write-Error "HTTP Request Processing Error: $($_.Exception.ToString())" + $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::InternalServerError) + } finally { + $context.Response.Close(); + } + + $cloudEvent = ConvertFrom-HttpMessage -Headers $headers -Body $bodyData + + # function result + ([System.Text.Encoding]::UTF8.GetString($bodyData) -eq $serverStopMessage) + + } catch { + Write-Error "CloudEvent Processing Error: $($_.Exception.ToString())" + } finally { + $listener.Stop() + } + + if ( $cloudEvent -ne $null ) { + try { + Process-Handler -CloudEvent $cloudEvent | Out-Null + }catch { + Write-Error "Handler Processing Error: $($_.Exception.ToString())" + } + } + } + + + + while($true) { + $breakSignal = Start-HttpCloudEventListener -Url $url + if ($breakSignal) { + Write-Host "Server stop requested" + break; + } + } +} -ArgumentList $url, $serverStopMessage + +$killEvent = new-object 'System.Threading.AutoResetEvent' -ArgumentList $false + +$serverTerminateJob = Start-ThreadJob { +param($killEvent, $url, $serverStopMessage) + $killEvent.WaitOne() | Out-Null + Invoke-WebRequest -Uri $url -Body $serverStopMessage | Out-Null +} -ArgumentList $killEvent, $localUrl, $serverStopMessage + +try { + Write-Host "Server start listening on '$url'" + $running = $true + while($running) { + Start-Sleep -Milliseconds 100 + $running = ($backgroundServer.State -eq 'Running') + $backgroundServer = $backgroundServer | Get-Job + $backgroundServer | Receive-Job + } +} finally { + Write-Host "PowerShell stop requested. Wait server to stop" + $killEvent.Set() | Out-Null + Get-Job | Wait-Job | Receive-Job + Write-Host "Server is stopped" +} \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-email/test/docker-test-env-variable b/examples/knative/powershell/kn-ps-email/test/docker-test-env-variable new file mode 100644 index 00000000..504f5b68 --- /dev/null +++ b/examples/knative/powershell/kn-ps-email/test/docker-test-env-variable @@ -0,0 +1 @@ +EMAIL_SECRET={"SMTP_SERVER":"smtp.primp-industries.com","SMTP_PORT":"587","SMTP_USERNAME":"email@primp-industries.com","SMTP_PASSWORD":"FILE-ME-IN-PLEASE","EMAIL_SUBJECT":"⚠️ [VM Delete Notification] ⚠️","EMAIL_TO":"admins@primp-industries.com,sre@primp-industries.com","EMAIL_FROM":"notification-do-not-reply@primp-industries.com"} \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.ps1 b/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.ps1 new file mode 100644 index 00000000..f381369b --- /dev/null +++ b/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.ps1 @@ -0,0 +1,16 @@ + +$headers = @{ + "Content-Type" = "application/json"; + "ce-specversion" = "1.0"; + "ce-id" = "id-123"; + "ce-source" = "source-123"; + "ce-type" = "com.vmware.event.router/event"; + "ce-subject" = "VmRemovedEvent"; +} + +$body = Get-Content -Raw -Path "./test-payload.json" + +Write-Host "Testing Function ..." +Invoke-WebRequest -Uri http://localhost:8080 -Method POST -Headers $headers -Body $body + +Write-host "See docker container console for output" \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.sh b/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.sh new file mode 100755 index 00000000..6cbfabc1 --- /dev/null +++ b/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +echo "Testing Function ..." +curl -d@test-payload.json -H "Content-Type: application/json" -H 'ce-specversion: 1.0' -H 'ce-id: id-123' -H 'ce-source: source-123' -H 'ce-type: com.vmware.event.router/event' -H 'ce-subject: VmRemovedEvent' -X POST localhost:8080 + +echo "See docker container console for output" diff --git a/examples/knative/powershell/kn-ps-email/test/test-payload.json b/examples/knative/powershell/kn-ps-email/test/test-payload.json new file mode 100644 index 00000000..e858795a --- /dev/null +++ b/examples/knative/powershell/kn-ps-email/test/test-payload.json @@ -0,0 +1,40 @@ +{ + "Key": 2040013, + "ChainId": 2040012, + "CreatedTime": "2021-04-23T20:21:02.277Z", + "UserName": "VSPHERE.LOCAL\\william", + "Datacenter": { + "Name": "Primp-Datacenter", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-3" + } + }, + "ComputeResource": { + "Name": "Supermicro-Cluster", + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c8" + } + }, + "Host": { + "Name": "192.168.30.5", + "Host": { + "Type": "HostSystem", + "Value": "host-11" + } + }, + "Vm": { + "Name": "FooBar", + "Vm": { + "Type": "VirtualMachine", + "Value": "vm-10838" + } + }, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "Removed FooBar on 192.168.30.5 from Primp-Datacenter", + "ChangeTag": "", + "Template": false +} \ No newline at end of file From d0d93c83742cd4186568c8ac7840cda8624477f9 Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Tue, 4 May 2021 21:29:07 +0200 Subject: [PATCH 21/56] chore: Add issue greeting Closes: #390 Signed-off-by: Michael Gasch --- .github/comment-template.md | 1 + .github/workflows/issue-greeting.yml | 32 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 .github/comment-template.md create mode 100644 .github/workflows/issue-greeting.yml diff --git a/.github/comment-template.md b/.github/comment-template.md new file mode 100644 index 00000000..bf8b87a6 --- /dev/null +++ b/.github/comment-template.md @@ -0,0 +1 @@ +Howdy 🖐   {{ .author }} ! Thank you for your interest in this project. We value your feedback and will respond soon. \ No newline at end of file diff --git a/.github/workflows/issue-greeting.yml b/.github/workflows/issue-greeting.yml new file mode 100644 index 00000000..a44cb041 --- /dev/null +++ b/.github/workflows/issue-greeting.yml @@ -0,0 +1,32 @@ +name: Greeting + +on: + issues: + types: ["opened"] + +jobs: + greeting: + name: Send Greeting + runs-on: ubuntu-latest + # only send message to first-time contributors + if: github.event.issue.author_association == 'FIRST_TIME_CONTRIBUTOR' + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 1 + + - name: Render template + id: template + uses: chuhlomin/render-template@v1.2 + with: + template: .github/comment-template.md + vars: | + author: ${{ github.actor }} + + - name: Create comment + uses: peter-evans/create-or-update-comment@v1 + with: + issue-number: ${{ github.event.issue.number }} + body: ${{ steps.template.outputs.result }} From 4d503c92e8020cb36b2a31eed45f6fee64d7e60b Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Fri, 30 Apr 2021 13:42:21 +0200 Subject: [PATCH 22/56] feat: Add Helm Option for Knative General: - Added Knative to Helm tests (Github Actions) - Update README with Knative instructions - Remove ToC from README because Github creates this automatically Chart changes: - Bumped Helm chart version - Renamed template files - Added new eventProcessor option in Values (default OpenFaaS) - ServiceAccount and ClusterRoleBindings will be created too All changes backwards-compatible, i.e. existing environments/configuration files for the Helm chart options are not affected. Signed-off-by: Michael Gasch --- .github/workflows/router-verify-helm.yml | 172 +++++++++++++++-- vmware-event-router/README.MD | 179 +++++++++--------- vmware-event-router/chart/Chart.yaml | 2 +- vmware-event-router/chart/config.yaml | 19 +- .../chart/releases/event-router-v0.6.2.tgz | Bin 0 -> 3144 bytes .../templates/100_clusterrolebinding.yaml | 14 ++ .../chart/templates/200_serviceaccount.yaml | 6 + .../{secret.yaml => 300_secret.yaml} | 0 .../{deployment.yaml => 400_deployment.yaml} | 1 + .../{service.yaml => 500_service.yaml} | 0 vmware-event-router/chart/values.yaml | 32 +++- 11 files changed, 298 insertions(+), 127 deletions(-) create mode 100644 vmware-event-router/chart/releases/event-router-v0.6.2.tgz create mode 100644 vmware-event-router/chart/templates/100_clusterrolebinding.yaml create mode 100644 vmware-event-router/chart/templates/200_serviceaccount.yaml rename vmware-event-router/chart/templates/{secret.yaml => 300_secret.yaml} (100%) rename vmware-event-router/chart/templates/{deployment.yaml => 400_deployment.yaml} (94%) rename vmware-event-router/chart/templates/{service.yaml => 500_service.yaml} (100%) diff --git a/.github/workflows/router-verify-helm.yml b/.github/workflows/router-verify-helm.yml index 2fa62474..1d121e0b 100644 --- a/.github/workflows/router-verify-helm.yml +++ b/.github/workflows/router-verify-helm.yml @@ -12,24 +12,16 @@ on: - "vmware-event-router/chart/**" jobs: - helm: - name: Verify Helm chart (latest release) + openfaas: + name: OpenFaaS Event Processor runs-on: ubuntu-latest env: KO_DOCKER_REPO: kind.local - KO_VERSION: 0.8.2 KIND_VERSION: v0.10.0 NAMESPACE: vmware - CHART_REPO: "https://projects.registry.vmware.com/chartrepo/veba" timeout-minutes: 15 steps: - - name: Install KinD - run: | - curl -L https://github.com/google/ko/releases/download/v${KO_VERSION}/ko_${KO_VERSION}_Linux_x86_64.tar.gz | tar xzf - ko - chmod +x ./ko - sudo mv ko /usr/local/bin - - name: Check out code onto GOPATH uses: actions/checkout@v2 with: @@ -42,8 +34,17 @@ jobs: chmod +x ./kind sudo mv kind /usr/local/bin - # create cluster with defaults - kind create cluster --wait 3m + # KinD configuration. + cat > kind.yaml <> $GITHUB_ENV - name: Install vCenter Simulator @@ -63,9 +64,10 @@ jobs: run: | kubectl create ns ${NAMESPACE} kubectl -n ${NAMESPACE} apply -f deploy/vcsim.yaml - kubectl wait --timeout=1m --for=condition=Available -n ${NAMESPACE} deploy/vcsim + kubectl wait --timeout=3m --for=condition=Available -n ${NAMESPACE} deploy/vcsim - name: Install VMware Event Router with Helm + working-directory: ./vmware-event-router run: | echo "::group::Create override.yaml" cat << EOF > override.yaml @@ -77,6 +79,7 @@ jobs: username: user password: pass insecure: true # ignore TLS certs + eventProcessor: openfaas openfaas: address: http://gateway.openfaas.svc.cluster.local:8080 basicAuth: true @@ -86,11 +89,144 @@ jobs: echo "::endgroup::" echo "::group::Deploy VMware Event Router" - helm repo add vmware-veba ${CHART_REPO} - helm install -n vmware --create-namespace veba vmware-veba/event-router -f override.yaml --wait + helm install veba-openfaas -n ${NAMESPACE} --wait -f override.yaml ./chart + + # assert it deployed correctly + kubectl wait --timeout=3m --for=condition=Available -n ${NAMESPACE} deploy/router + echo "::endgroup::" + + - name: "Debug" + if: ${{ always() }} + run: | + kubectl get pods --all-namespaces + kubectl -n ${NAMESPACE} describe pods + kubectl -n ${NAMESPACE} get events + + knative: + name: Knative Event Processor + runs-on: ubuntu-latest + env: + KO_DOCKER_REPO: kind.local + KIND_VERSION: v0.10.0 + NAMESPACE: vmware + KNATIVE_VERSION: v0.22.0 + BROKER_NAMESPACE: default + BROKER_NAME: default + timeout-minutes: 15 + + steps: + - name: Check out code onto GOPATH + uses: actions/checkout@v2 + with: + fetch-depth: 1 + + - name: Setup KinD Cluster + run: | + set -x + curl -Lo ./kind https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-$(uname)-amd64 + chmod +x ./kind + sudo mv kind /usr/local/bin + + # KinD configuration. + cat > kind.yaml < override.yaml + eventrouter: + config: + logLevel: debug + vcenter: + address: https://vcsim.vmware.svc.cluster.local + username: user + password: pass + insecure: true # ignore TLS certs + eventProcessor: knative + knative: + destination: + ref: + apiVersion: eventing.knative.dev/v1 + kind: Broker + name: ${BROKER_NAME} + namespace: ${BROKER_NAMESPACE} + EOF + echo "::endgroup::" + + echo "::group::Deploy VMware Event Router" + helm install veba-knative -n ${NAMESPACE} --wait -f override.yaml ./chart # assert it deployed correctly - kubectl wait --timeout=1m --for=condition=Available -n vmware deploy/router + kubectl wait --timeout=3m --for=condition=Available -n ${NAMESPACE} deploy/router echo "::endgroup::" - name: "Debug" @@ -98,4 +234,4 @@ jobs: run: | kubectl get pods --all-namespaces kubectl -n ${NAMESPACE} describe pods - kubectl -n ${NAMESPACE} get events \ No newline at end of file + kubectl -n ${NAMESPACE} get events diff --git a/vmware-event-router/README.MD b/vmware-event-router/README.MD index 7ae21015..1d09e8d2 100644 --- a/vmware-event-router/README.MD +++ b/vmware-event-router/README.MD @@ -1,4 +1,3 @@ - # VMware Event Router The VMware Event Router is used to connect to various VMware event `providers` @@ -50,40 +49,6 @@ to normalize events from the supported event `providers`. See > **Note:** Event Processors, like Knative, support Dead Letter Queues when > using `Broker` mode. - -## Table of Contents - -- [Configuration](#configuration) - - [Overview: Configuration File Structure (YAML)](#overview-configuration-file-structure-yaml) - - [JSON Schema Validation](#json-schema-validation) - - [API Version, Kind and Metadata](#api-version-kind-and-metadata) - - [The `eventProvider` section](#the-eventprovider-section) - - [Provider Type `vcenter`](#provider-type-vcenter) - - [Provider Type `vcsim`](#provider-type-vcsim) - - [The `eventProcessor` section](#the-eventprocessor-section) - - [Processor Type `knative`](#processor-type-knative) - - [Destination](#destination) - - [Processor Type `openfaas`](#processor-type-openfaas) - - [Processor Type `aws_event_bridge`](#processor-type-aws_event_bridge) - - [The `auth` section](#the-auth-section) - - [Type `basic_auth`](#type-basic_auth) - - [Type `aws_access_key`](#type-aws_access_key) - - [The `metricsProvider` section](#the-metricsprovider-section) - - [Provider Type `default`](#provider-type-default) -- [Deployment](#deployment) - - [Assisted Deployment](#assisted-deployment) - - [Helm Deployment](#helm-deployment) - - [OpenFaaS Helm Deployment (skip if already installed)](#openfaas-helm-deployment-skip-if-already-installed) - - [Example: Deploy VMware Event Router into a new namespace](#example-deploy-vmware-event-router-into-a-new-namespace) - - [Creating/Updating the Chart](#creatingupdating-the-chart) - - [Manual Deployment](#manual-deployment) - - [Create Knative ClusterRoleBinding (skip if not using Knative)](#create-knative-clusterrolebinding-skip-if-not-using-knative) - - [Create the VMware Event Router Deployment](#create-the-vmware-event-router-deployment) - - [CLI Flags](#cli-flags) -- [Build from Source](#build-from-source) -- [Contribute](#contribute) -- [Example Event Structure](#example-event-structure) - ## Configuration The VMware Event Router can be run standalone (statically linked binary) or @@ -497,8 +462,6 @@ and optional fields for configuring the `default` metrics server. VMware Event Router can be deployed and run as standalone binary (see [below](#build-from-source)). However, it is designed (and recommended) to be run in a Kubernetes cluster for increased availability and ease of scaling out. -The following steps describe the deployment of the VMware Event Router in **a -Kubernetes cluster** for an **existing** OpenFaaS ("faas-netes") environment. > **Note:** Docker images are available > [here](https://hub.docker.com/r/vmware/veba-event-router). @@ -506,11 +469,9 @@ Kubernetes cluster** for an **existing** OpenFaaS ("faas-netes") environment. ### Assisted Deployment For your convenience we provide a Helm Chart which can be used to easily install -the VMware Event Router into an **existing** OpenFaaS ("faas-netes") -environment. See [below](#openfaas-helm-deployment) for an example how to set up OpenFaaS in -a development environment. - -> **Note:** Currently we do not support other event processors for Helm-based deployments. +the VMware Event Router into an **existing** OpenFaaS ("faas-netes") or Knative +environment. See [below](#openfaas-helm-deployment) for an example how to set up +OpenFaaS in a development environment. #### Helm Deployment @@ -519,12 +480,13 @@ file contains the allowed parameters and parameter descriptions which map to the VMware Event Router [configuration](#overview-configuration-file-structure-yaml) file. -##### OpenFaaS Helm Deployment (skip if already installed) +##### Option 1: Configuration with OpenFaaS -The following steps can be used to quickly install OpenFaaS as a requirement for the -Helm installation instructions of the VMware Event Router below: +The following steps can be used to quickly install OpenFaaS as a requirement for +the Helm installation instructions of the VMware Event Router below. Skip this +part if you already have an OpenFaaS environment set up. -```bash +```console kubectl create ns openfaas && kubectl create ns openfaas-fn helm repo add openfaas https://openfaas.github.io/faas-netes && \ @@ -537,9 +499,7 @@ helm repo add openfaas https://openfaas.github.io/faas-netes && \ OF_PASS=$(echo $(kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode)) ``` -##### Example: Deploy VMware Event Router into a new namespace - -First create an `override.yaml` file with your environment specific settings, +Now create a Helm `override.yaml` file with your environment specific settings, e.g.: ```yaml @@ -547,45 +507,78 @@ eventrouter: config: logLevel: debug vcenter: - address: https://10.186.34.28 + address: https://vcenter.corp.local username: administrator@vsphere.local - password: - insecure: true # ignore TLS certs + password: replaceMe + insecure: true # if required ignore TLS certs + eventProcessor: openfaas openfaas: address: http://gateway.openfaas:8080 basicAuth: true username: admin - password: + password: ${OF_PASS} # variable from previous section + +``` + +> **Note:** Please ensure the correct formatting/indentation which follows the +> Helm `values.yaml` file. + +##### Option 2: Configuration with Knative +If you don't have a working Knative installation, follow the steps described in +the [official](https://knative.dev/docs/install/prerequisites/) documentation to +deploy Knative Serving **and** Eventing. + +Now create a Helm `override.yaml` file with your environment specific settings, +e.g.: + +```yaml +eventrouter: + config: + logLevel: debug + vcenter: + address: https://vcenter.corp.local + username: administrator@vsphere.local + password: replaceMe + insecure: true # if required ignore TLS certs + eventProcessor: knative + knative: + destination: # follows Knative convention for ref/uri + ref: + apiVersion: eventing.knative.dev/v1 + kind: Broker + name: default + namespace: default ``` > **Note:** Please ensure the correct formatting/indentation which follows the > Helm `values.yaml` file. +##### Deploy the VMware Event Router Helm Chart -Now add the VMware Event Router Helm release to your Helm repository: -```bash +Add the VMware Event Router Helm release to your Helm repository: + +```console # adds the veba chartrepo to the list of local registries with the repo name "veba" -helm repo add vmware-veba https://projects.registry.vmware.com/chartrepo/veba +$ helm repo add vmware-veba https://projects.registry.vmware.com/chartrepo/veba ``` To ensure new releases are pulled/updated and reflected locally update the repo index: -```bash -helm repo update +```console +$ helm repo update Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "vmware-veba" chart repository Update Complete. ⎈ Happy Helming!⎈ - ``` The chart should now show up in the search: -```bash -helm search repo event-router +```console +$ helm search repo event-router NAME CHART VERSION APP VERSION DESCRIPTION -vmware-veba/event-router v0.5.0 v0.5.0 The VMware Event Router is used to connect to v... +vmware-veba/event-router v0.6.2 v0.6.0 The VMware Event Router is used to connect to v... ``` > **Note:** To list/install development releases add the `--devel` flag to the Helm CLI. @@ -595,8 +588,8 @@ using the configuration override file created above. The following command will create a release from the chart in the namespace `vmware`, which will be automatically created if it does not exist: -```bash -helm install -n vmware --create-namespace veba vmware-veba/event-router -f override.yaml +```console +$ helm install -n vmware --create-namespace veba vmware-veba/event-router -f override.yaml NAME: veba LAST DEPLOYED: Mon Nov 9 16:27:27 2020 NAMESPACE: vmware @@ -607,8 +600,8 @@ TEST SUITE: None Check the logs of the VMware Event Router to validate it is operating correctly: -```bash -kubectl -n vmware logs deploy/router -f +```console +$ kubectl -n vmware logs deploy/router -f ``` If you run into issues, the logs should give you a hint, e.g.: @@ -622,8 +615,8 @@ If you run into issues, the logs should give you a hint, e.g.: To uninstall the release run: -```bash -helm -n vmware uninstall veba +```console +$ helm -n vmware uninstall veba ``` @@ -632,20 +625,19 @@ helm -n vmware uninstall veba Before running the following commands make the appropriate changes to the chart, e.g. bumping up `version` and/or `appVersion` in `Chart.yaml`. -```bash -cd chart -helm package -d releases . +```console +$ cd chart +$ helm package -d releases . ``` Then upload the created `.tgz` file inside `releases/` to your Helm chart repo. - #### Manual Deployment Create a namespace where the VMware Event Router will be deployed to: -```bash -kubectl create namespace vmware +```console +$ kubectl create namespace vmware ``` Use one of the configuration files provided [here](deploy/) to configure the @@ -667,8 +659,8 @@ Now, from your current Git working directory create a Kubernetes [secret](https://kubernetes.io/docs/concepts/configuration/secret/) with the configuration file as input: -```bash -kubectl -n vmware create secret generic event-router-config --from-file=event-router-config.yaml +```console +$ kubectl -n vmware create secret generic event-router-config --from-file=event-router-config.yaml ``` > **Note:** You might want to delete the (local) configuration file to not leave @@ -685,15 +677,15 @@ Event Router will be deployed into the `vmware` namespace using the predefined service account (deployment manifest). This requires a properly configured Knative environment with `Serving` and `Eventing` CRDs installed. -```bash +```console # only for Knative-based deployments -kubectl create clusterrolebinding veba-addressable-resolver --clusterrole=addressable-resolver --serviceaccount=vmware:vmware-event-router +$ kubectl create clusterrolebinding veba-addressable-resolver --clusterrole=addressable-resolver --serviceaccount=vmware:vmware-event-router ```
Addressable Resolver Permissions -```bash -kubectl describe clusterrole addressable-resolver +```console +$ kubectl describe clusterrole addressable-resolver Name: addressable-resolver Labels: eventing.knative.dev/release=v0.18.4 Annotations: PolicyRule: @@ -719,7 +711,6 @@ Annotations: PolicyRule: services.serving.knative.dev/status [] [] [get list watch] services.serving.knative.dev [] [] [get list watch] channels.messaging.knative.dev/finalizers [] [] [update] - ```
@@ -728,14 +719,14 @@ Annotations: PolicyRule: Now we can deploy the VMware Event Router: -```bash -kubectl -n vmware create -f deploy/event-router-k8s.yaml +```console +$ kubectl -n vmware create -f deploy/event-router-k8s.yaml ``` Check the logs of the VMware Event Router to validate it started correctly: -```bash -kubectl -n vmware logs deploy/vmware-event-router -f +```console +$ kubectl -n vmware logs deploy/vmware-event-router -f ``` If you run into issues, the logs should give you a hint, e.g.: @@ -750,8 +741,8 @@ If you run into issues, the logs should give you a hint, e.g.: To delete the deployment and secret simply delete the namespace we created earlier: -```bash -kubectl delete namespace vmware +```console +$ kubectl delete namespace vmware ``` ### CLI Flags @@ -762,8 +753,8 @@ and human readable colored console logs will be printed. This behavior can be overridden via `log-json` to generate JSON logs and `log-level` to change the log level. Stack traces are only generated in level `error` or higher. -``` -./vmware-event-router -h +```console +$ ./vmware-event-router -h _ ____ ___ ______ __ ____ __ | | / / |/ / ______ _________ / ____/ _____ ____ / /_ / __ \____ __ __/ /____ _____ @@ -789,15 +780,15 @@ Requirements: This project uses [Golang](https://golang.org/dl/) and Go and Dockerfile are provided requiring `make` and [Docker](https://www.docker.com/) to be installed as well. -```bash -git clone https://github.com/vmware-samples/vcenter-event-broker-appliance -cd vcenter-event-broker-appliance/vmware-event-router +```console +$ git clone https://github.com/vmware-samples/vcenter-event-broker-appliance +$ cd vcenter-event-broker-appliance/vmware-event-router # for Go versions before v1.13 -export GO111MODULE=on +$ export GO111MODULE=on # defaults to build with Docker (use "make binary" for local executable instead) -make +$ make ``` > **Note:** For `_test.go` files your editor (e.g. vscode) might show errors and diff --git a/vmware-event-router/chart/Chart.yaml b/vmware-event-router/chart/Chart.yaml index 564d58af..0def27cc 100644 --- a/vmware-event-router/chart/Chart.yaml +++ b/vmware-event-router/chart/Chart.yaml @@ -3,7 +3,7 @@ name: event-router description: The VMware Event Router is used to connect to various VMware event providers and forwards received events to supported event processors. home: https://vmweventbroker.io/ type: application -version: v0.6.1 +version: v0.6.2 appVersion: v0.6.0 maintainers: - name: Michael Gasch diff --git a/vmware-event-router/chart/config.yaml b/vmware-event-router/chart/config.yaml index d35eb03b..9b296ae6 100644 --- a/vmware-event-router/chart/config.yaml +++ b/vmware-event-router/chart/config.yaml @@ -5,8 +5,8 @@ metadata: labels: {{ include "router.labels" . | indent 4 }} eventProvider: - type: vcenter - name: {{ .Release.Name }}-vcenter + type: {{ .Values.eventrouter.eventProvider }} + name: {{ .Release.Name }}-{{ .Values.eventrouter.eventProvider }} vcenter: {{- with .Values.eventrouter.vcenter }} address: {{ .address }} @@ -19,8 +19,9 @@ eventProvider: password: {{ .password }} {{- end }} eventProcessor: - type: openfaas - name: {{ .Release.Name }}-openfaas + type: {{ .Values.eventrouter.eventProcessor }} + name: {{ .Release.Name }}-{{ .Values.eventrouter.eventProcessor }} + {{- if eq .Values.eventrouter.eventProcessor "openfaas" }} openfaas: {{- with .Values.eventrouter.openfaas }} address: {{ .address }} @@ -33,6 +34,16 @@ eventProcessor: password: {{ .password }} {{- end }} {{- end }} + {{- end }} + {{- if eq .Values.eventrouter.eventProcessor "knative" }} + knative: + {{- with .Values.eventrouter.knative }} + encoding: {{ .encoding }} + insecureSSL: {{ .insecureSSL }} + destination: +{{ toYaml .destination | indent 6 }} + {{- end }} + {{- end }} metricsProvider: type: default name: veba-demo-metrics diff --git a/vmware-event-router/chart/releases/event-router-v0.6.2.tgz b/vmware-event-router/chart/releases/event-router-v0.6.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..031678943c6ab0fc649994f9f8d682ce49f10765 GIT binary patch literal 3144 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 Date: Mon, 3 May 2021 12:16:30 +0200 Subject: [PATCH 23/56] Add v0.6.2 Chart Pre-Release Signed-off-by: Michael Gasch --- vmware-event-router/chart/Chart.yaml | 2 +- .../releases/event-router-v0.6.2-pre-release.tgz | Bin 0 -> 3155 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 vmware-event-router/chart/releases/event-router-v0.6.2-pre-release.tgz diff --git a/vmware-event-router/chart/Chart.yaml b/vmware-event-router/chart/Chart.yaml index 0def27cc..5036d9e3 100644 --- a/vmware-event-router/chart/Chart.yaml +++ b/vmware-event-router/chart/Chart.yaml @@ -3,7 +3,7 @@ name: event-router description: The VMware Event Router is used to connect to various VMware event providers and forwards received events to supported event processors. home: https://vmweventbroker.io/ type: application -version: v0.6.2 +version: v0.6.2-pre-release appVersion: v0.6.0 maintainers: - name: Michael Gasch diff --git a/vmware-event-router/chart/releases/event-router-v0.6.2-pre-release.tgz b/vmware-event-router/chart/releases/event-router-v0.6.2-pre-release.tgz new file mode 100644 index 0000000000000000000000000000000000000000..58f04dc9f4c82eeb5552519bd10fb935fae37763 GIT binary patch literal 3155 zcmV-Z46O4XiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PH(^ZyPuA`FlUb{N!KJB#2fkS$2X24yfz&dc8}WAj+jU6ooKk%_HJtC(u>=YFZ$h+hWIh^4Phr?N1AlH2d=4(I`3^KZu5h$D_js5Zx*jTS}=kkq@FT9&2@S zf09B9`VOU{g2%8J1*9mN*C;#=qaa0n1U9N z2+lFl=5awJ6{V^$>{^3DiUmzkDj+e|W?=w*R1kfLBTA%+{lqQj|CzmV9M$dD)u?pv-mEgk(|5LTkP zbpxj|L>w492@}6$qYfE)|AgwAki-IQ&Dmq!!QVJdmI2rEQF$3 z$Yl&pg$4_pl72@|0E&{a*MiYx8N&&?BunM{cwI6!!9=1~G5qqY)v{L$l#-?xL$4QP zC1Y!qfkL^U37#a0C8<`~o}-~Nd@l$Jk)Ciav{h0S!_~E|n_yB(s+XsNYy7BfJ-`8E z>PIQcB8E{EMP{(gU5q*4^sb01D#ys*y_N&1Sj9`U32p#ZtN*P~#xS&`NK~Sf396cipAkw`62_LsUWzF14#*c&3Z7freM$8k7}c8jnN_4b0aYe*GyF@+=|wrk5Pr&FDN0BMToT@y zNeivyZnoMImkGwiCFL{QJEPoyko0}2Y{3YhX&K@BtC0oK&63d&+ zY+v6tto}Cio#s^6f=-Ik3MKdNIp$UghNEXMsh{G964gyH0#a(B62d5-84ToP$}o)~ zBTQisaFOB!875jtw}7=^D619y%dbH|vW#-7m#*2UvD+k_8W`bbbQah;SLG<1AxBBr zCg8>bfUhwq2Fa)!h|F@YgHk$@ESS?tKkm)&AftALfPFY?bzt`(-^=A3C7N}S8})Z0 zTBpN>mCS=41&Xry2$-y>JZm zZH@Caj6(A_G$6h^V?C_!wJ^`ts(8Q92*Rv zz#A}_5=9drrJe(s5z3XWCS>7k^GRuR(eHW&w}n)#giXt09lDb4R#97NTyUc40(T0- z3eN*OX&_HTYAiHNDJOE-k@{rvqRoO7m8RS>${`zXDXSg<+UG1w3FWiUj~1r57%YZ0 z>;>g%4By+cOkL?X;fe0lx`j|T*-7W9B~4VN&uepiSemWeR}ZLB7*5y zflgVeG|Hb|JH894V|W%l8~tg{Jp0m=&OWFyFBs9N2E!;iPn?{kU^u1RD8e5+8Sae# zJU#B7|3<^;DB7R@?xM8qlAMwxG?rCJ`kOUn;l(o*QeljfGfH0f-w1}^yEPHys7XpR zF(!hMDKZrYS6Al1z{(V%=hjx}ntBkz??8EKZ1EVbuj|9y1{Bj^5U#HSvk2cfdrAuG z4{TfM4JsSjBDWsMZ-!>S>KnJ8El>ukoW94zx$k|CJn0-eTR!g96@>3xsHA%|wG_Ka zbxgBCc;f;?;aOE};O<-cTlIewMdyBQ#Lw-IM}>Cj|7du$qW{tG=ypA<3vxE)E*SFcOrtj|UI$hYBu(_r)w$C)X#Ez5ta=+;ppuU-_uI$$OYwq(JysN9e zyXM($f!l+)1+XFZ-UP_xOf})MKJ@yomtMQhkX8ru;riOVx9ofew$H?@&v1dPU2QO` zXnvbOGas4(et_`365Pfo+wE0{rm@$-LY}q(7lM^JzBEL3=F|AVUBN>4j$(C&EvV{V zu@yY6hHEW~ZR2bp)j4=Ew8n5%XScT5-?w?&mmOux{(sb*|G)eIurvO5yqf=Ud_3Ie zf89y>)cOBz_x(ahXE8sywNy70w)?)neK5AjUW9q$R!E^mB3KM>Pu(qrCUS=Q^#*8n zvM_b#$<=jiW#edk#{NI9aFt>0Nqtv;CivHt8@5IN&*#Vrlq%H4m#2d5^#7lZR`q{0 z8twJ}F3Q!_;BoN7$HL|a&9RfRVKaQf;Ab=r2e+Fi5y!u&aR7$ zB1;tzi7u7K<%ZhiqleR_?f&`M#K2NMvtII}FnBz;z7CA?`E-e{wdgmE_A=AA_G&xP z&K!f^9npqd^GNeanPv1N^!iOtM#s%_V7YKA(N3`*AS)SL!iSPDno&&6`N&T0YhX#0 zDc4Nq|Lq|0-x~gO^5QmgsY157EE)8^QT=a>C2QYS_?|zTq3{o6xlN3s5>vl{z_tlh zn}|UtMG;=q+zY7~P_8uLo9CW_D#~Y1Kq7MqR|V1YRz2ZqZ~z=s`41`x3n|`XqJxm8 zNCpmEiuXadP>D#<3_)Zvrwdep|1LxgzB0Sl(f`(s$Zd|X%m06Rxc>de;nThT-$hx= zOZ00YY(9}1EQW*?b5cbI+$UeBF46ldx+CmnO?Qs=8%FFvucj%i_()OlBdF?ZxJ*zv}*PrBcmDKIYX)?79 z?bX{(^_fgwR4|?VZFBMw{~?9d tr93^UmXZ6=B8s;xecweL+`R9-WncDXU%u}0zW@LL|Nr6r@go32003Jh9*O_} literal 0 HcmV?d00001 From 121c6886e0ad79190d5e416e46843aff6fc7c69d Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Wed, 5 May 2021 10:32:24 +0200 Subject: [PATCH 24/56] Bump Helm chart version to v0.6.2 Signed-off-by: Michael Gasch --- vmware-event-router/chart/Chart.yaml | 2 +- .../chart/releases/event-router-v0.6.2.tgz | Bin 3144 -> 3147 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/vmware-event-router/chart/Chart.yaml b/vmware-event-router/chart/Chart.yaml index 5036d9e3..0def27cc 100644 --- a/vmware-event-router/chart/Chart.yaml +++ b/vmware-event-router/chart/Chart.yaml @@ -3,7 +3,7 @@ name: event-router description: The VMware Event Router is used to connect to various VMware event providers and forwards received events to supported event processors. home: https://vmweventbroker.io/ type: application -version: v0.6.2-pre-release +version: v0.6.2 appVersion: v0.6.0 maintainers: - name: Michael Gasch diff --git a/vmware-event-router/chart/releases/event-router-v0.6.2.tgz b/vmware-event-router/chart/releases/event-router-v0.6.2.tgz index 031678943c6ab0fc649994f9f8d682ce49f10765..50b4639402699d1fb76460f52605cb68e94d4d0e 100644 GIT binary patch delta 1125 zcmV-r1e*KE7|R%tO#&w^kxxt?C!>-3pCrlZ|KxZ$x;GppqtWR2=<(>}UNSs786Dk` zxFZ24k=(fn9}m~`e>6-E_p_b>Wdwi6C*AYk;V?Nl*`NPzqqOaknh+XW%PJN7!>Yb*9mJ&55?U?Q`& zcm&ti&0%f>ifyn6*VmCHp#Ict!t{;p5Sh$_6HXe7q9>JsBPEI55-Y$aOsHXjT6oAC30@|E-j^{*$uQOIQDCBmUcQ@pXxRG+Z)qMv3k? z4DCB9-=(g@<37qu44pmOPFkzIL*I$-tX}w`v(|K7^!M}oeNvwS>RGwxj@NVk8D|L> zkZ*3YeNErr+jY8dHSzMA&e}fP>=Qdqo6G%WzW~k6TzzG?*5C3zZ;5wx)%Vvt+bwW= z5WfI6#NL+yGBwr9a9JOJdVSwZuU%(JYXbUkeQn=ccD@7K=W^QTI7i;DwitCZzfGW> z56c1HL;Oxl;o_6+_PRsc*y~`S$l8E&$*TgNTOvF2S^B_T!9w?rVs(ZssQO;96+Eqn zYb}ax<7^<+J9sIy#_(0=H@4Z|wRzi@9c9b@f4n^ZfBgYqXZ-JfWHtZeob2#VThWddAR}FpDb*h zee!i3Te);JK4bqM)wtTS_O!XHKbHc(DWkYW|KHA#m#B1X%CAoa+v)#5K3vuR!%?!= z|Jx{6SA&PqPajHumm@U8PRfSM@QI^e(K;N$-H#IkCr7S9!vra*G=i1|wr-q#7db+4q5?yQ2UpBhS%+T6v>_j_r z4F2>)TXO9qE5=oxvk%bgFMF~&Zl5E^g)@b2ip>Cd#rXn%-dBXPoMC3qM{aW80!!*l zx#eX3+YOTb&yqi#ytvI=s*znTOAfv7bpJbR$=bISq36$LDEuGt!X-vAg<04@VB3W1 zO~j&;vWzbp?!`{JEwTpmTzC~t?vJ^U86oT r>L$n7?f+9w)w*kdAScL2A$S%TH-ky?@g#+7GdZXHgw#aP;3Y($bulm;Q=W|q%NUkRDkb3vM zACSkLV`t09y}E+T*y-d7maG<|%$I-~^G*N$*ga=0J6fNcCT8tAnmO}z(h#L4jp1^ruDqAmJA zN|LwBpu<<%fF1gOI6PX_|C6Jmz5jnJrLF&@EcMdWf7*!uc3ga2B7Y5+Oq@}oI}Stp zPRe(w>+rab@)AR5&$g4+YVXi@>^rL$e(0<)~39 zV%sb%#Us@^$msVz8`KMtSxdEVZpeSN*YNePvP~MzojrlP0_sE0PRl}w$48J zx{j?}IvSs`|Bq^1ZCQKT+|{2;f!~x-+@k+)XUI!bIyU9kr-JSD|4$BA_5bkjc(4Dr zQLe5A52K$xlz%QqXoj7X4VU2)N57(VIE1?&CkBp>T!V%QQc`IIEemYjIQuSghCI_i z6#7&epBw6ukM2(vuKTCwV++f~)OpDd;^^Vv`Z}`87t$rV)}p^`beEZ-wb$5*cIFuT z>4~=F+DBH5t2}2Ppx0maWOdv=M~(|;3f&Z&0rHCT1%JG+2xmFN%$|?jkTye>!<_o4HgYyIhtWdf(~(ch-`%Z!1F2pUqJCKjei=jA9D2uz|p~3Duj3 zMJHt$Uo_l{nH(^o4H28}QeY!|ND#cbYL0drI2XGk!#Z+71es#ipDen=g>7;3h)pu^ zw)55uc{ma4*}!R48?2tiIs;p~%T~U1cA#&7c~lmq6exC1@vbf3zT8{g|6{vGeP+~6 oj Date: Tue, 4 May 2021 07:50:38 -0700 Subject: [PATCH 25/56] Document simplified steps for replacing TLS certifcate in VEBA Signed-off-by: William Lam --- docs/kb/advanced-certificates.md | 70 ++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/docs/kb/advanced-certificates.md b/docs/kb/advanced-certificates.md index 1958093f..18e2e2e7 100644 --- a/docs/kb/advanced-certificates.md +++ b/docs/kb/advanced-certificates.md @@ -5,53 +5,61 @@ title: VMware Event Broker Appliance - Certificates description: Updating Certificates permalink: /kb/advanced-certificates cta: - description: With your certificates updated, you can now skip using the `--tls-no-verify` flag while working with faas-cli. + description: Replacing the default self-signed TLS certificate in VMware Event Broke Appliance. --- ## Updating the TLS Certificate on VEBA - -The default certificate for OpenFaaS (/ui), Stats (/stats), Status (/status), Logs (/bootstrap) and the other web endpoints running on VEBA are self signed. This might cause browsers to show the certificate as untrusted and would require providing the `--no-tls-verify` flag when working with faas-cli. - -In order to update the certificates with a certificate from a trusted authority, please follow the steps outlined below - + +By default, the VMware Event Broker Appliance generates a self-signed TLS certificate that is used to support different web endpoints running on the appliance such as Stats (`/stats`), Status (`/status`), Logs (`/bootstrap`) and Events (`/events`). This will cause browsers to show the certificate as untrusted. + +For organizations that require the use of a TLS certificate from a trusted authority, the VMware Event Broker Appliance provides an option for users to provide their certificate information during the OVF property configuration when deploying the virtual appliance. + +In order to use a certificates from a trusted authority, please follow the steps outlined below. + ### Assumptions -* Access to VMware Event Broker Appliance terminal -* Certificates from a trusted authority pre-downloaded onto the Appliance +* Certificates from a trusted authority pre-downloaded onto your local desktop * The public/private key pair must exist before hand. The public key certificate must be .PEM encoded and match the given private key. ### Steps -Run the below commands to update the certificate on VEBA +In the example, the private key file is named `privateKey.key` and certificate file is named `certificate.crt` + +1. Encode both the private key and the certificate file using base64 encoding. + +Microsoft Windows (PowerShell) -```bash -cd /folder/certs/location -CERT_NAME=eventrouter-tls #DO NOT CHANGE THIS -KEY_FILE=.pem -CERT_FILE=.cer +```console +$privateKeyContent = Get-Content -Raw privateKey.key +$privateKeybase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($privateKeyContent)) +Write-Host "Encoded Private Key:`n$privateKeybase64`n" -#recreate the tls secret -kubectl -n vmware-system delete secret ${CERT_NAME} -kubectl -n vmware-system create secret tls ${CERT_NAME} --key ${KEY_FILE} --cert ${CERT_FILE} +$certContent = Get-Content -Raw certificate.crt +$certbase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($certContent)) +Write-Host "Encoded Certificate:`n$certbase64`n" -#reapply the config to take the new certificate -kubectl apply -f /root/config/ingressroute-gateway.yaml +Encoded Private Key: +LS0tLS1CRUd......== + + +Encoded Certificate Key: +LS0tLS1CRUe......== ``` -If you are using the Embedded Knative Broker, you will also need to reference the newly generated certificate as it is also used as part of the Knative Contour integration. +MacOS/Linux -```bash -cd /folder/certs/location -KNATIVE_CERT_NAME=default-cert #DO NOT CHANGE THIS -KEY_FILE=.pem -CERT_FILE=.cer +``` +cat privateKey.key | base64 -#recreate the tls secret -kubectl -n contour-external delete secret ${KNATIVE_CERT_NAME} -kubectl -n contour-external create secret tls ${KNATIVE_CERT_NAME} --key ${KEY_FILE} --cert ${CERT_FILE} +LS0tLS1CRUd......== -#reapply the config to take the new certificate -kubectl apply -f /root/config/ingressroute-gateway.yaml +cat certificate.crt | base64 + +LS0tLS1CRUd......== ``` -Watch this short video to see the steps being performed to successfully update the certs for VEBA configured for OpenFaaS - [here](https://youtu.be/7oMCvxvL2ns){:target="_blank"} +2. Using the output from the previous step, the base64 content can now be provided in `Custom TLS Certificate Configuration` section of the OVF property during the deployment of the VMware Event Broker Appliance. + * Custom VMware Event Broker Appliance TLS Certificate Private Key (Base64) + * Custom VMware Event Broker Appliance TLS Certificate Authority Certificate (Base64) + +3. Power on the VMware Event Broker Appliance and ensure that the provided TLS certificate is now used instead of the auto-generated self-sign TLS certificate by opening a browser to one of the VMware Event Broker Appliance endpoints such as `/status`. From b2507eb33c9c08113432cfb70a055918cbc2a962 Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Thu, 6 May 2021 11:26:31 +0200 Subject: [PATCH 26/56] feat: Add router support for custom certificates - DEBUG and WARN information about insecure connections and custom certificates (when used) - Update schema with new `certificates` section Note: currently only the vCenter event provider makes use of this new certificate section. The only certificate types supported are PEM-encoded root CAs. Documentation updates will follow as separate PR after more validation. Closes: #370 Signed-off-by: Michael Gasch --- vmware-event-router/cmd/router/main.go | 5 +- .../internal/config/v1alpha1/config.go | 9 +++ .../internal/provider/vcenter/options.go | 8 +++ .../internal/provider/vcenter/vcenter.go | 58 +++++++++++++++---- .../internal/provider/vcenter/vcenter_test.go | 2 +- vmware-event-router/routerconfig.schema.json | 2 +- 6 files changed, 69 insertions(+), 15 deletions(-) diff --git a/vmware-event-router/cmd/router/main.go b/vmware-event-router/cmd/router/main.go index fa58e197..55b6632a 100644 --- a/vmware-event-router/cmd/router/main.go +++ b/vmware-event-router/cmd/router/main.go @@ -80,7 +80,7 @@ func main() { if err != nil { panic(err.Error()) } - log := logger.Named("[MAIN]").Sugar() + log := logger.Named("[MAIN]").Sugar().With("commit", commit, "version", version) f, err := os.Open(configPath) if err != nil { @@ -103,7 +103,7 @@ func main() { // set up event provider switch cfg.EventProvider.Type { case config.ProviderVCenter: - prov, err = vcenter.NewEventStream(ctx, cfg.EventProvider.VCenter, ms, logger.Sugar()) + prov, err = vcenter.NewEventStream(ctx, cfg.EventProvider.VCenter, ms, logger.Sugar(), vcenter.WithRootCAs(cfg.Certificates.RootCAs)) if err != nil { log.Fatalf("could not connect to vCenter: %v", err) } @@ -111,6 +111,7 @@ func main() { log.Infow("connecting to vCenter", "address", cfg.EventProvider.VCenter.Address) case config.ProviderVCSIM: + log.Warn("%s is DEPRECATED and will be removed in future versions", config.ProviderVCSIM) prov, err = vcsim.NewEventStream(ctx, cfg.EventProvider.VCSIM, ms, logger.Sugar()) if err != nil { log.Fatalf("could not connect to vCenter simulator: %v", err) diff --git a/vmware-event-router/internal/config/v1alpha1/config.go b/vmware-event-router/internal/config/v1alpha1/config.go index 6125baad..39763479 100644 --- a/vmware-event-router/internal/config/v1alpha1/config.go +++ b/vmware-event-router/internal/config/v1alpha1/config.go @@ -26,6 +26,12 @@ type ObjectMeta struct { Labels map[string]string `yaml:"labels,omitempty" jsonschema:""` } +// Certificates defines custom certificate types to be used instead of the +// system (OS) defaults. +type Certificates struct { + RootCAs []string `yaml:"rootCAs,omitempty" json:"rootCAs,omitempty" jsonschema:""` +} + // RouterConfig sets configuration for the event router type RouterConfig struct { TypeMeta `yaml:",inline" jsonschema:"required"` @@ -36,6 +42,9 @@ type RouterConfig struct { EventProcessor Processor `yaml:"eventProcessor" json:"eventProcessor" jsonschema:"required"` // MetricsProvider contains configuration information for a supported metrics provider MetricsProvider MetricsProvider `yaml:"metricsProvider" json:"metricsProvider" jsonschema:"required"` + // Certificates contains configuration information to define certificates. This + // section is currently only used by the vCenter event provider. + Certificates Certificates `yaml:"certificates,omitempty" json:"certificates,omitempty" jsonschema:""` } // Parse parses a given configuration and returns a RouterConfig diff --git a/vmware-event-router/internal/provider/vcenter/options.go b/vmware-event-router/internal/provider/vcenter/options.go index 72ccc6fa..2aaeda80 100644 --- a/vmware-event-router/internal/provider/vcenter/options.go +++ b/vmware-event-router/internal/provider/vcenter/options.go @@ -3,3 +3,11 @@ package vcenter // Option allows for customization of the vCenter event provider // TODO: change signature to return errors type Option func(*EventStream) + +// WithRootCAs configures the TLS transport cert pool to use the specified root +// CA PEM-files instead of the host OS system default +func WithRootCAs(pemFiles []string) Option { + return func(stream *EventStream) { + stream.rootCAs = pemFiles + } +} diff --git a/vmware-event-router/internal/provider/vcenter/vcenter.go b/vmware-event-router/internal/provider/vcenter/vcenter.go index bfdde500..670fbac1 100644 --- a/vmware-event-router/internal/provider/vcenter/vcenter.go +++ b/vmware-event-router/internal/provider/vcenter/vcenter.go @@ -14,11 +14,13 @@ import ( "github.com/pkg/errors" "github.com/vmware/govmomi" "github.com/vmware/govmomi/event" + "github.com/vmware/govmomi/session" "github.com/vmware/govmomi/vim25" "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/types" "go.uber.org/zap" + "knative.dev/pkg/logging" "github.com/jpillora/backoff" @@ -40,10 +42,11 @@ const ( // EventStream handles the connection to the vCenter events API type EventStream struct { - client govmomi.Client + client *govmomi.Client logger.Logger checkpoint bool checkpointDir string + rootCAs []string // custom root CAs, TLS OS defaults if not specified wg waitgroup.WaitGroup // shutdown handling @@ -83,25 +86,30 @@ func NewEventStream(ctx context.Context, cfg *config.ProviderConfigVCenter, ms m password := cfg.Auth.BasicAuth.Password parsedURL.User = url.UserPassword(username, password) - client, err := govmomi.NewClient(ctx, parsedURL, cfg.InsecureSSL) - if err != nil { - return nil, errors.Wrap(err, "create vCenter client") + // apply options (use defaults otherwise) + for _, opt := range opts { + opt(&vc) } - vcLog := log + vc.Logger = log if zapSugared, ok := log.(*zap.SugaredLogger); ok { prov := strings.ToUpper(string(config.ProviderVCenter)) - vcLog = zapSugared.Named(fmt.Sprintf("[%s]", prov)) + vc.Logger = zapSugared.Named(fmt.Sprintf("[%s]", prov)) + ctx = logging.WithLogger(ctx, vc.Logger.(*zap.SugaredLogger)) + } + + if vc.client == nil { + vc.client, err = newClient(ctx, parsedURL, vc.rootCAs, cfg.InsecureSSL) + if err != nil { + return nil, errors.Wrap(err, "create client") + } } - vc.Logger = vcLog - vc.client = *client vc.checkpoint = cfg.Checkpoint vc.checkpointDir = cfg.CheckpointDir - // apply options (overwrite any defaults) - for _, opt := range opts { - opt(&vc) + if cfg.InsecureSSL { + vc.Logger.Warnw("using potentially insecure connection to vCenter", "address", cfg.Address, "insecure", cfg.InsecureSSL) } // seed the metrics stats @@ -119,6 +127,34 @@ func NewEventStream(ctx context.Context, cfg *config.ProviderConfigVCenter, ms m return &vc, nil } +func newClient(ctx context.Context, u *url.URL, rootCAs []string, insecure bool) (*govmomi.Client, error) { + log := logging.FromContext(ctx) + soapClient := soap.NewClient(u, insecure) + + if len(rootCAs) > 0 { + files := strings.Join(rootCAs, ":") + log.Debugw("setting custom root CAs", "certificates", files) + if err := soapClient.SetRootCAs(files); err != nil { + return nil, err + } + } + + vimClient, err := vim25.NewClient(ctx, soapClient) + if err != nil { + return nil, err + } + + c := &govmomi.Client{ + Client: vimClient, + SessionManager: session.NewManager(vimClient), + } + + if err = c.Login(ctx, u.User); err != nil { + return nil, err + } + return c, nil +} + // Stream is the main logic, blocking to receive and handle events from vCenter func (vc *EventStream) Stream(ctx context.Context, p processor.Processor) error { var ( diff --git a/vmware-event-router/internal/provider/vcenter/vcenter_test.go b/vmware-event-router/internal/provider/vcenter/vcenter_test.go index 38548be0..75d15867 100644 --- a/vmware-event-router/internal/provider/vcenter/vcenter_test.go +++ b/vmware-event-router/internal/provider/vcenter/vcenter_test.go @@ -72,7 +72,7 @@ func TestEventStream_stream(t *testing.T) { } vc := &EventStream{ - client: c, + client: &c, Logger: logger, checkpoint: tt.args.enableCheckpoint, stats: metrics.EventStats{ diff --git a/vmware-event-router/routerconfig.schema.json b/vmware-event-router/routerconfig.schema.json index a0afe48a..e64a2b6e 100644 --- a/vmware-event-router/routerconfig.schema.json +++ b/vmware-event-router/routerconfig.schema.json @@ -1 +1 @@ -{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/RouterConfig","definitions":{"AWSAccessKeyAuthMethod":{"required":["accessKey","secretKey"],"properties":{"accessKey":{"type":"string"},"secretKey":{"type":"string"}},"additionalProperties":false,"type":"object"},"AuthMethod":{"required":["type"],"properties":{"type":{"enum":["basic_auth","aws_access_key"],"type":"string","description":"The authentication method to use","default":"basic_auth"},"basicAuth":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/BasicAuthMethod","description":"Basic authentication with username and password"},"awsAccessKeyAuth":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/AWSAccessKeyAuthMethod","description":"AWS authentication with access and secret key"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["basicAuth"],"title":"basicAuth"},{"required":["awsAccessKeyAuth"],"title":"awsAccessKeyAuth"}]},"BasicAuthMethod":{"required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string"}},"additionalProperties":false,"type":"object"},"Destination":{"properties":{"ref":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/KReference"},"uri":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/URL"}},"additionalProperties":false,"type":"object"},"KReference":{"required":["kind","name","apiVersion"],"properties":{"kind":{"type":"string"},"namespace":{"type":"string"},"name":{"type":"string"},"apiVersion":{"type":"string"}},"additionalProperties":false,"type":"object"},"MetricsProvider":{"required":["type","name"],"properties":{"type":{"enum":["default"],"type":"string"},"name":{"type":"string"},"default":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/MetricsProviderConfigDefault"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["default"],"title":"default"}]},"MetricsProviderConfigDefault":{"required":["bindAddress"],"properties":{"bindAddress":{"type":"string"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object"},"ObjectMeta":{"required":["name"],"properties":{"name":{"type":"string"},"labels":{"patternProperties":{".*":{"type":"string"}},"type":"object"}},"additionalProperties":false,"type":"object"},"Processor":{"required":["type","name"],"properties":{"type":{"enum":["openfaas","aws_event_bridge","knative"],"type":"string"},"name":{"type":"string"},"openfaas":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProcessorConfigOpenFaaS"},"awsEventBridge":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProcessorConfigEventBridge"},"knative":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProcessorConfigKnative"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["openfaas"],"title":"openfaas"},{"required":["awsEventBridge"],"title":"awsEventBridge"},{"required":["knative"],"title":"knative"}]},"ProcessorConfigEventBridge":{"required":["region","eventBus","ruleARN"],"properties":{"region":{"type":"string","default":"us-west-1"},"eventBus":{"type":"string","default":"default"},"ruleARN":{"type":"string","default":"arn:aws:events:us-west-1:1234567890:rule/vmware-event-router"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["auth"],"title":"auth"}]},"ProcessorConfigKnative":{"required":["insecureSSL","encoding"],"properties":{"destination":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/Destination","description":"Destination sink where to send events"},"insecureSSL":{"type":"boolean"},"encoding":{"enum":["binary","structured"],"type":"string","default":"structured"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["destination"],"title":"destination"}]},"ProcessorConfigOpenFaaS":{"required":["address","async"],"properties":{"address":{"type":"string","description":"OpenFaaS gateway address","default":"http://gateway.openfaas:8080"},"async":{"type":"boolean","description":"Use async function invocation mode"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object"},"Provider":{"required":["type","name"],"properties":{"type":{"enum":["vcenter","vcsim"],"type":"string"},"name":{"type":"string"},"vcenter":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProviderConfigVCenter"},"vcsim":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProviderConfigVCSIM"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["vcenter"],"title":"vcenter"},{"required":["vcsim"],"title":"vcsim"}]},"ProviderConfigVCSIM":{"required":["address","insecureSSL"],"properties":{"address":{"type":"string","default":"https://my-vcenter01.domain.local/sdk"},"insecureSSL":{"type":"boolean"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["auth"],"title":"auth"}]},"ProviderConfigVCenter":{"required":["address","insecureSSL","checkpoint"],"properties":{"address":{"type":"string","default":"https://my-vcenter01.domain.local/sdk"},"insecureSSL":{"type":"boolean"},"checkpoint":{"type":"boolean","description":"Enable checkpointing via checkpoint file for event recovery and replay purposes"},"checkpointDir":{"type":"string","description":"Directory where to persist checkpoints if enabled","default":"./checkpoints"},"auth":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["auth"],"title":"auth"}]},"RouterConfig":{"required":["apiVersion","kind","metadata","eventProvider","eventProcessor","metricsProvider"],"properties":{"apiVersion":{"enum":["event-router.vmware.com/v1alpha1"],"type":"string"},"kind":{"enum":["RouterConfig"],"type":"string"},"metadata":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ObjectMeta"},"eventProvider":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/Provider"},"eventProcessor":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/Processor"},"metricsProvider":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/MetricsProvider"}},"additionalProperties":false,"type":"object"},"URL":{"required":["Scheme","Opaque","User","Host","Path","RawPath","ForceQuery","RawQuery","Fragment","RawFragment"],"properties":{"Scheme":{"type":"string"},"Opaque":{"type":"string"},"User":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/Userinfo"},"Host":{"type":"string"},"Path":{"type":"string"},"RawPath":{"type":"string"},"ForceQuery":{"type":"boolean"},"RawQuery":{"type":"string"},"Fragment":{"type":"string"},"RawFragment":{"type":"string"}},"additionalProperties":false,"type":"object"},"Userinfo":{"properties":{},"additionalProperties":false,"type":"object"}}} \ No newline at end of file +{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/RouterConfig","definitions":{"AWSAccessKeyAuthMethod":{"required":["accessKey","secretKey"],"properties":{"accessKey":{"type":"string"},"secretKey":{"type":"string"}},"additionalProperties":false,"type":"object"},"AuthMethod":{"required":["type"],"properties":{"type":{"enum":["basic_auth","aws_access_key"],"type":"string","description":"The authentication method to use","default":"basic_auth"},"basicAuth":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/BasicAuthMethod","description":"Basic authentication with username and password"},"awsAccessKeyAuth":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/AWSAccessKeyAuthMethod","description":"AWS authentication with access and secret key"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["basicAuth"],"title":"basicAuth"},{"required":["awsAccessKeyAuth"],"title":"awsAccessKeyAuth"}]},"BasicAuthMethod":{"required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string"}},"additionalProperties":false,"type":"object"},"Certificates":{"properties":{"rootCAs":{"items":{"type":"string"},"type":"array"}},"additionalProperties":false,"type":"object"},"Destination":{"properties":{"ref":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/KReference"},"uri":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/URL"}},"additionalProperties":false,"type":"object"},"KReference":{"required":["kind","name","apiVersion"],"properties":{"kind":{"type":"string"},"namespace":{"type":"string"},"name":{"type":"string"},"apiVersion":{"type":"string"}},"additionalProperties":false,"type":"object"},"MetricsProvider":{"required":["type","name"],"properties":{"type":{"enum":["default"],"type":"string"},"name":{"type":"string"},"default":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/MetricsProviderConfigDefault"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["default"],"title":"default"}]},"MetricsProviderConfigDefault":{"required":["bindAddress"],"properties":{"bindAddress":{"type":"string"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object"},"ObjectMeta":{"required":["name"],"properties":{"name":{"type":"string"},"labels":{"patternProperties":{".*":{"type":"string"}},"type":"object"}},"additionalProperties":false,"type":"object"},"Processor":{"required":["type","name"],"properties":{"type":{"enum":["openfaas","aws_event_bridge","knative"],"type":"string"},"name":{"type":"string"},"openfaas":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProcessorConfigOpenFaaS"},"awsEventBridge":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProcessorConfigEventBridge"},"knative":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProcessorConfigKnative"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["openfaas"],"title":"openfaas"},{"required":["awsEventBridge"],"title":"awsEventBridge"},{"required":["knative"],"title":"knative"}]},"ProcessorConfigEventBridge":{"required":["region","eventBus","ruleARN"],"properties":{"region":{"type":"string","default":"us-west-1"},"eventBus":{"type":"string","default":"default"},"ruleARN":{"type":"string","default":"arn:aws:events:us-west-1:1234567890:rule/vmware-event-router"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["auth"],"title":"auth"}]},"ProcessorConfigKnative":{"required":["insecureSSL","encoding"],"properties":{"destination":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/Destination","description":"Destination sink where to send events"},"insecureSSL":{"type":"boolean"},"encoding":{"enum":["binary","structured"],"type":"string","default":"structured"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["destination"],"title":"destination"}]},"ProcessorConfigOpenFaaS":{"required":["address","async"],"properties":{"address":{"type":"string","description":"OpenFaaS gateway address","default":"http://gateway.openfaas:8080"},"async":{"type":"boolean","description":"Use async function invocation mode"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object"},"Provider":{"required":["type","name"],"properties":{"type":{"enum":["vcenter","vcsim"],"type":"string"},"name":{"type":"string"},"vcenter":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProviderConfigVCenter"},"vcsim":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProviderConfigVCSIM"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["vcenter"],"title":"vcenter"},{"required":["vcsim"],"title":"vcsim"}]},"ProviderConfigVCSIM":{"required":["address","insecureSSL"],"properties":{"address":{"type":"string","default":"https://my-vcenter01.domain.local/sdk"},"insecureSSL":{"type":"boolean"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["auth"],"title":"auth"}]},"ProviderConfigVCenter":{"required":["address","insecureSSL","checkpoint"],"properties":{"address":{"type":"string","default":"https://my-vcenter01.domain.local/sdk"},"insecureSSL":{"type":"boolean"},"checkpoint":{"type":"boolean","description":"Enable checkpointing via checkpoint file for event recovery and replay purposes"},"checkpointDir":{"type":"string","description":"Directory where to persist checkpoints if enabled","default":"./checkpoints"},"auth":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["auth"],"title":"auth"}]},"RouterConfig":{"required":["apiVersion","kind","metadata","eventProvider","eventProcessor","metricsProvider"],"properties":{"apiVersion":{"enum":["event-router.vmware.com/v1alpha1"],"type":"string"},"kind":{"enum":["RouterConfig"],"type":"string"},"metadata":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ObjectMeta"},"eventProvider":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/Provider"},"eventProcessor":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/Processor"},"metricsProvider":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/MetricsProvider"},"certificates":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/Certificates"}},"additionalProperties":false,"type":"object"},"URL":{"required":["Scheme","Opaque","User","Host","Path","RawPath","ForceQuery","RawQuery","Fragment","RawFragment"],"properties":{"Scheme":{"type":"string"},"Opaque":{"type":"string"},"User":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/Userinfo"},"Host":{"type":"string"},"Path":{"type":"string"},"RawPath":{"type":"string"},"ForceQuery":{"type":"boolean"},"RawQuery":{"type":"string"},"Fragment":{"type":"string"},"RawFragment":{"type":"string"}},"additionalProperties":false,"type":"object"},"Userinfo":{"properties":{},"additionalProperties":false,"type":"object"}}} \ No newline at end of file From 2ef2aa12f7c7b91e88fc09c8aa1a9e7ad5a5da5b Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Thu, 6 May 2021 11:45:46 +0200 Subject: [PATCH 27/56] chore: Automate CHANGELOG Closes: #394 Signed-off-by: Michael Gasch --- .chglog/CHANGELOG.tpl.md | 45 ++++++++++++++++++++++++++++++++++++++++ .chglog/config.yml | 29 ++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100755 .chglog/CHANGELOG.tpl.md create mode 100755 .chglog/config.yml diff --git a/.chglog/CHANGELOG.tpl.md b/.chglog/CHANGELOG.tpl.md new file mode 100755 index 00000000..eda31bd8 --- /dev/null +++ b/.chglog/CHANGELOG.tpl.md @@ -0,0 +1,45 @@ +{{ range .Versions }} +
+## {{ if .Tag.Previous }}[Release {{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} + +> Release Date: {{ datetime "2006-01-02" .Tag.Date }} + +{{ range .CommitGroups -}} +### {{ .Title }} + +{{ range .Commits -}} +- {{ .Subject }} +{{ end }} +{{ end -}} + +{{- if .RevertCommits -}} +### ⏮ Reverts + +{{ range .RevertCommits -}} +- {{ .Revert.Header }} +{{ end }} +{{ end -}} + +{{- if .NoteGroups -}} +{{ range .NoteGroups -}} +### ⚠️ {{ .Title }} + +{{ range .Notes }} +{{ .Body }} +{{ end }} +{{ end -}} +{{ end -}} + +### 📖 Commits + +{{ range .Commits -}} +{{ if not .Merge -}} +{{ if not (contains .Header "Update CHANGELOG for" ) -}} +{{ if not (contains .Header "Merge branch" ) -}} +- {{ .Header }} [{{ .Hash.Short }}] +{{ end -}} +{{ end -}} +{{ end -}} +{{ end -}} + +{{ end -}} \ No newline at end of file diff --git a/.chglog/config.yml b/.chglog/config.yml new file mode 100755 index 00000000..478088d9 --- /dev/null +++ b/.chglog/config.yml @@ -0,0 +1,29 @@ +style: github +template: CHANGELOG.tpl.md +info: + title: CHANGELOG + repository_url: https://github.com/vmware-samples/vcenter-event-broker-appliance +options: + commits: + filters: + Type: + - fix + - feat + - chore + commit_groups: + title_maps: + fix: 🐞 Fix + feat: 💫 Feature + chore: 🧹 Chore + header: + pattern: "^(\\w*)\\:\\s(.*)$" + pattern_maps: + - Type + - Subject + refs: + actions: + - Closes + - Fixes + notes: + keywords: + - "BREAKING" \ No newline at end of file From 0356447eb66406d1846107c5437f435b029b8d25 Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Thu, 6 May 2021 12:08:00 +0200 Subject: [PATCH 28/56] fix: Wrong association in greeting Signed-off-by: Michael Gasch --- .github/workflows/issue-greeting.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issue-greeting.yml b/.github/workflows/issue-greeting.yml index a44cb041..6c95232b 100644 --- a/.github/workflows/issue-greeting.yml +++ b/.github/workflows/issue-greeting.yml @@ -8,8 +8,9 @@ jobs: greeting: name: Send Greeting runs-on: ubuntu-latest - # only send message to first-time contributors - if: github.event.issue.author_association == 'FIRST_TIME_CONTRIBUTOR' + # only send message to users not (yet) associated with repo + # https://docs.github.com/en/graphql/reference/enums#commentauthorassociation + if: github.event.issue.author_association == 'NONE' steps: - name: Checkout From eefe0d2f3795279146161a8096bd4d9acf54d208 Mon Sep 17 00:00:00 2001 From: William Lam Date: Thu, 6 May 2021 16:53:39 -0700 Subject: [PATCH 29/56] feat: Document Trusted Root Certificate support w/VMware Event Router Signed-off-by: William Lam --- docs/kb/advanced-certificates.md | 102 ++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/docs/kb/advanced-certificates.md b/docs/kb/advanced-certificates.md index 18e2e2e7..a60dbabe 100644 --- a/docs/kb/advanced-certificates.md +++ b/docs/kb/advanced-certificates.md @@ -48,7 +48,7 @@ LS0tLS1CRUe......== MacOS/Linux -``` +```console cat privateKey.key | base64 LS0tLS1CRUd......== @@ -63,3 +63,103 @@ LS0tLS1CRUd......== * Custom VMware Event Broker Appliance TLS Certificate Authority Certificate (Base64) 3. Power on the VMware Event Broker Appliance and ensure that the provided TLS certificate is now used instead of the auto-generated self-sign TLS certificate by opening a browser to one of the VMware Event Broker Appliance endpoints such as `/status`. + +## Adding a Trusted Root Certificate to VEBA + +For organizations that require the use of a Private or Enterprise Certificate Authority (CA) and wish to have full TLS trust when the VMware Event Broker Appliance is configured with a vCenter Server that has a TLS certificate generated by the internal CA, a root of trust must be established. + +To support this use case, a post-deployment reconfiguration of the VMware Event Broker Appliance is required to import the trusted root certificate of your internal certificate authority. If this is not performed, you may see the following warning in the VMware Event Router logs when connecting to the configured vCenter Server. + +```console +WARN [VCENTER] vcenter/vcenter.go:112 using potentially insecure connection to vCenter {"address": "https://vcsa.primp-industries.local", "insecure": true} +``` + + +In order to import your trusted root certificate, please follow the steps outlined below. + +### Assumptions + +* Pre-download your root certificates from your internal certificate, which must be .PEM encoded + +In the example, the downloaded root certificate file is named `ca-root.crt` + +### Steps + +Step 1 - Transfer or copy the contents of the root certificate to the VMware Event Broker Appliance and create a new kubernetes configMap from that file. + +```console +kubectl -n vmware-system create cm ca-root-cert --from-file ca-root.crt +``` + +Step 2 - Undeploy the VMware Event Router and delete the current configuration secret. + +```console +kubectl -n vmware-system delete -f /root/config/event-router-k8s.yaml +kubectl -n vmware-system delete secret event-router-config +``` + +Step 3 - Edit `/root/config/event-router-k8s.yaml` and append the additional `volumenMount` and `volume` entry as shown in the snippet below. + +```console +snip +... + volumeMounts: + - name: config + mountPath: /etc/vmware-event-router/ + readOnly: true + - name: ca-root-cert + mountPath: /etc/vmware-event-router/ssl + readOnly: true + volumes: + - name: config + secret: + secretName: event-router-config + - name: ca-root-cert + configMap: + name: ca-root-cert +``` + +Step 4 - Edit `/root/config/event-router-config.yaml` and append the additional `certificates` section to the end of the file as shown in the snippet below. + +```console +snip +... +metricsProvider: + default: + bindAddress: 0.0.0.0:8082 + name: veba-metrics + type: default +certificates: + rootCAs: + - /etc/ssl/certs/ca-certificates.crt + - /etc/vmware-event-router/ssl/ca-root.crt +``` + +> **Note:** If `insecureSSL` is set to `true`, please update that to `false` which will now ensure TLS verification is performed when connecting to your vCenter Server. + +Step 5 - Recreate the VMware Event Router configuration and redeploy the VMware Event Router deployment. + +```console +kubectl -n vmware-system create secret event-router-config +kubectl -n vmware-system apply -f /root/config/event-router-k8s.yaml +``` + +Step 6 - Verify the VMware Event Router pod is running successfully. + +```console +kubectl -n vmware-system get pods +NAME READY STATUS RESTARTS AGE +tinywww-dd88dc7db-q6pzw 1/1 Running 0 166m +veba-rabbit-server-0 1/1 Running 0 167m +veba-ui-8d6584f84-ckvwb 1/1 Running 0 166m +vmware-event-router-7759d8bffc-45c92 1/1 Running 0 37m +``` + +Lastly, you can also look at the VMware Event Router log and ensure that you no longer see the message `potentially insecure connection to vCenter`, which is what you would see if the connection was not trusted if you had TLS verified disabled. The following `DEBG` log entry should appear when the VMware Event Router is deployed with the debug flag. + +``` +kubectl -n vmware-system logs vmware-event-router-7759d8bffc-kt2jm + +DEBUG [VCENTER] vcenter/vcenter.go:136 setting custom root CAs {"certificates": "/etc/ssl/certs/ca-certificates.crt:/etc/vmware-event-router/ssl/ca-root.crt"} +``` + From e8592627bc7c873afc71cfd2ed8a25014f96b563 Mon Sep 17 00:00:00 2001 From: Patrick Kremer Date: Fri, 7 May 2021 17:09:27 -0500 Subject: [PATCH 30/56] Updated docs with new URL www.williamlam.com Signed-off-by: Patrick Kremer --- docs/_data/resources.yml | 8 ++++---- docs/_data/team.yml | 2 +- docs/kb/contribute-eventrouter.md | 2 +- docs/kb/intro-event-router.md | 2 +- docs/site/resources.md | 4 ++-- docs/zcleanup/DESIGN.md | 2 +- .../powershell/vmware-cloud-ngw-ms-teams/README.md | 2 +- .../openfaas/powershell/vmware-cloud-ngw-slack/README.md | 2 +- examples/openfaas/powershell/vro/README.md | 2 +- vmware-event-router/README.MD | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/_data/resources.yml b/docs/_data/resources.yml index 842a946c..b77ae81f 100644 --- a/docs/_data/resources.yml +++ b/docs/_data/resources.yml @@ -38,8 +38,8 @@ links: display: true details: url_text: blog post - external_url: https://www.virtuallyghetto.com/2020/07/integrating-vmware-cloud-notification-gateway-with-vmware-event-broker-appliance-veba.html - external_image: https://i2.wp.com/www.virtuallyghetto.com/wp-content/uploads/2020/07/vmware-cloud-notification-to-veba-diagram.png?ssl=1 + external_url: https://www.williamlam.com/2020/07/integrating-vmware-cloud-notification-gateway-with-vmware-event-broker-appliance-veba.html + external_image: https://i2.wp.com/www.williamlam.com/wp-content/uploads/2020/07/vmware-cloud-notification-to-veba-diagram.png?ssl=1 author_name: William Lam excerpt: advantage of the NGW webhook capability but instead of forwarding the NGW notification to a cloud service that supports an incoming webhook, we are sending it to VEBA for processing. Once the notification has been received by VEBA, customers can apply... @@ -107,7 +107,7 @@ otherlinks: url: https://rguske.github.io/post/monitoring-the-vmware-event-broker-appliance-with-vrealize-operations-manager/ - title: vCenter Event Broker Appliance Updates – VMworld, Fling, Community & Open Source display: true - url: https://www.virtuallyghetto.com/2019/11/vcenter-event-broker-appliance-updates-vmworld-fling-community-open-source.html + url: https://www.williamlam.com/2019/11/vcenter-event-broker-appliance-updates-vmworld-fling-community-open-source.html - title: If This Then That for vSphere display: true url: https://octo.vmware.com/vsphere-power-event-driven-automation/ @@ -139,7 +139,7 @@ otherlinks: url: https://www.vmworld.com/en/video-library/video-landing.html?sessionid=15614121705290019EX2®ion=EU display: true - title: Listing all Events for vCenter Server - url: https://www.virtuallyghetto.com/2019/12/listing-all-events-for-vcenter-server.html + url: https://www.williamlam.com/2019/12/listing-all-events-for-vcenter-server.html display: true - title: Video - Robert and Michael explore Event Driven Automation for vCenter url: https://www.youtube.com/watch?v=X3usMlmyEoA&feature=emb_logo diff --git a/docs/_data/team.yml b/docs/_data/team.yml index 3be54192..95b1cde0 100644 --- a/docs/_data/team.yml +++ b/docs/_data/team.yml @@ -10,7 +10,7 @@ members: github: embano1 - name: William Lam img: https://avatars0.githubusercontent.com/u/602199?v=4 - website: https://www.virtuallyghetto.com/ + website: https://www.williamlam.com/ twitter: lamw github: lamw - name: Frankie Gold diff --git a/docs/kb/contribute-eventrouter.md b/docs/kb/contribute-eventrouter.md index 448175ee..4241ddab 100644 --- a/docs/kb/contribute-eventrouter.md +++ b/docs/kb/contribute-eventrouter.md @@ -266,7 +266,7 @@ centralized platform for controlling your VMware vSphere environments, allowing you to automate and deliver a virtual infrastructure across the hybrid cloud with confidence. Since VMware vCenter Server event types are environment specific (vSphere version, extensions), a list of events for vCenter as an event source can be generated as -described in this [blog post](https://www.virtuallyghetto.com/2019/12/listing-all-events-for-vcenter-server.html). +described in this [blog post](https://www.williamlam.com/2019/12/listing-all-events-for-vcenter-server.html). The following table lists allowed and required fields for connecting to a vCenter Server and the respective type values and examples for these fields. diff --git a/docs/kb/intro-event-router.md b/docs/kb/intro-event-router.md index 9109a5e0..6da76072 100644 --- a/docs/kb/intro-event-router.md +++ b/docs/kb/intro-event-router.md @@ -40,7 +40,7 @@ As described in the [architecture section](intro-architecture.md), due to the mi ## Event Types supported -For the supported event stream source, e.g. VMware vCenter, all events provided by that source can be used. Since event types are environment specific (vSphere version, extensions), a list of events for vCenter as an event source can be generated as described in this [blog post](https://www.virtuallyghetto.com/2019/12/listing-all-events-for-vcenter-server.html){:target="_blank"}. +For the supported event stream source, e.g. VMware vCenter, all events provided by that source can be used. Since event types are environment specific (vSphere version, extensions), a list of events for vCenter as an event source can be generated as described in this [blog post](https://www.williamlam.com/2019/12/listing-all-events-for-vcenter-server.html){:target="_blank"}. ## Message Delivery Guarantees diff --git a/docs/site/resources.md b/docs/site/resources.md index c0c6708d..0e21fb8d 100644 --- a/docs/site/resources.md +++ b/docs/site/resources.md @@ -9,8 +9,8 @@ limit: 3 ## Blog posts - https://rguske.github.io/post/event-driven-interactions-with-vsphere-using-functions-as-a-service/ - https://octo.vmware.com/vsphere-power-event-driven-automation/ -- https://www.virtuallyghetto.com/2019/11/vcenter-event-broker-appliance-updates-vmworld-fling-community-open-source.html +- https://www.williamlam.com/2019/11/vcenter-event-broker-appliance-updates-vmworld-fling-community-open-source.html - https://www.opvizor.com/audit-vm-configuration-changes-using-the-vcenter-event-broker - https://doogleit.github.io/2019/11/automate-host-maintenance-with-the-vcenter-event-broker-appliance/ - https://www.patrickkremer.com/veba/ -- https://www.virtuallyghetto.com/2019/12/listing-all-events-for-vcenter-server.html \ No newline at end of file +- https://www.williamlam.com/2019/12/listing-all-events-for-vcenter-server.html \ No newline at end of file diff --git a/docs/zcleanup/DESIGN.md b/docs/zcleanup/DESIGN.md index 8d475fc5..e8400f92 100644 --- a/docs/zcleanup/DESIGN.md +++ b/docs/zcleanup/DESIGN.md @@ -72,7 +72,7 @@ As described in the architecture section [above](#architecture) due to the micro ## Event Types supported -For the supported event stream source, e.g. VMware vCenter, all events provided by that source can be used. Since event types are environment specific (vSphere version, extensions), a list of events for vCenter as an event source can be generated as described in this [blog post](https://www.virtuallyghetto.com/2019/12/listing-all-events-for-vcenter-server.html). +For the supported event stream source, e.g. VMware vCenter, all events provided by that source can be used. Since event types are environment specific (vSphere version, extensions), a list of events for vCenter as an event source can be generated as described in this [blog post](https://www.williamlam.com/2019/12/listing-all-events-for-vcenter-server.html). ## Message Delivery Guarantees diff --git a/examples/openfaas/powershell/vmware-cloud-ngw-ms-teams/README.md b/examples/openfaas/powershell/vmware-cloud-ngw-ms-teams/README.md index 4cde57ab..7f8eed7b 100644 --- a/examples/openfaas/powershell/vmware-cloud-ngw-ms-teams/README.md +++ b/examples/openfaas/powershell/vmware-cloud-ngw-ms-teams/README.md @@ -65,7 +65,7 @@ NotificationEvents = @("SDDC-PROVISION","SDDC-DELETE"); New-VmcNotificationWebhook @vmcVebaTeamsNotificationParams ``` -> **Note:** For list of all available VMware Cloud Notification Events, please see this [blog post](https://www.virtuallyghetto.com/2020/06/extending-vmware-cloud-on-aws-notifications-using-the-notification-gateway-api.html) for more details. +> **Note:** For list of all available VMware Cloud Notification Events, please see this [blog post](https://www.williamlam.com/2020/06/extending-vmware-cloud-on-aws-notifications-using-the-notification-gateway-api.html) for more details. ### Test VMware Cloud Notification diff --git a/examples/openfaas/powershell/vmware-cloud-ngw-slack/README.md b/examples/openfaas/powershell/vmware-cloud-ngw-slack/README.md index 61a48d19..1c47b2bb 100644 --- a/examples/openfaas/powershell/vmware-cloud-ngw-slack/README.md +++ b/examples/openfaas/powershell/vmware-cloud-ngw-slack/README.md @@ -65,7 +65,7 @@ NotificationEvents = @("SDDC-PROVISION","SDDC-DELETE"); New-VmcNotificationWebhook @vmcVebaSlackNotificationParams ``` -> **Note:** For list of all available VMware Cloud Notification Events, please see this [blog post](https://www.virtuallyghetto.com/2020/06/extending-vmware-cloud-on-aws-notifications-using-the-notification-gateway-api.html) for more details. +> **Note:** For list of all available VMware Cloud Notification Events, please see this [blog post](https://www.williamlam.com/2020/06/extending-vmware-cloud-on-aws-notifications-using-the-notification-gateway-api.html) for more details. ### Test VMware Cloud Notification diff --git a/examples/openfaas/powershell/vro/README.md b/examples/openfaas/powershell/vro/README.md index 7b5e00a8..83391414 100644 --- a/examples/openfaas/powershell/vro/README.md +++ b/examples/openfaas/powershell/vro/README.md @@ -7,7 +7,7 @@ This function demonstrates using PowerShell to trigger vRealize Orchestrator wor ## Prerequisites * You have deployed the example vSphere Tagging vRO Workflow package from https://github.com/kclinden/vro-vsphere-tagging -* You have retrieved the required vRO Workflow ID (please see this blog post [here](https://www.virtuallyghetto.com/2020/03/using-vro-rest-api-to-execute-a-workflow-with-sdk-objects.html) for more details) +* You have retrieved the required vRO Workflow ID (please see this blog post [here](https://www.williamlam.com/2020/03/using-vro-rest-api-to-execute-a-workflow-with-sdk-objects.html) for more details) ## Instruction Consuming Function diff --git a/vmware-event-router/README.MD b/vmware-event-router/README.MD index 1d09e8d2..a3c2b6a7 100644 --- a/vmware-event-router/README.MD +++ b/vmware-event-router/README.MD @@ -180,7 +180,7 @@ centralized platform for controlling your VMware vSphere environments, allowing you to automate and deliver a virtual infrastructure across the hybrid cloud with confidence. Since VMware vCenter Server event types are environment specific (vSphere version, extensions), a list of events for vCenter as an event source can be generated as -described in this [blog post](https://www.virtuallyghetto.com/2019/12/listing-all-events-for-vcenter-server.html). +described in this [blog post](https://www.williamlam.com/2019/12/listing-all-events-for-vcenter-server.html). The following table lists allowed and required fields for connecting to a vCenter Server and the respective type values and examples for these fields. From a8c1976f1074a1e9c7a3fdaa861e2be6920a9c1b Mon Sep 17 00:00:00 2001 From: William Lam Date: Tue, 11 May 2021 07:08:55 -0700 Subject: [PATCH 31/56] fix: Update documentation to reflect minimum of vCenter Server 7.0 for VEBA UI Signed-off-by: William Lam --- docs/kb/install-knative.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/kb/install-knative.md b/docs/kb/install-knative.md index 09b4b047..f988146b 100644 --- a/docs/kb/install-knative.md +++ b/docs/kb/install-knative.md @@ -20,6 +20,7 @@ Customers looking to seamlessly extend their vCenter by either deploying our pre * 4 vCPU and 8GB of memory for VMware Event Broker Appliance * vCenter Server 6.x or greater + * The VEBA UI requires vCenter Server 7.0 or greater * vCenter TCP/443 accessible from Appliance IP address * Account to login to vCenter Server (readOnly is sufficient) From 1c78537d8d75e39de433ed87c67824a20bdc8513 Mon Sep 17 00:00:00 2001 From: William Lam Date: Tue, 11 May 2021 07:13:03 -0700 Subject: [PATCH 32/56] fix: Fix command in adding trusted root CA cert documentation Signed-off-by: William Lam --- docs/kb/advanced-certificates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/kb/advanced-certificates.md b/docs/kb/advanced-certificates.md index a60dbabe..51a65cc3 100644 --- a/docs/kb/advanced-certificates.md +++ b/docs/kb/advanced-certificates.md @@ -140,7 +140,7 @@ certificates: Step 5 - Recreate the VMware Event Router configuration and redeploy the VMware Event Router deployment. ```console -kubectl -n vmware-system create secret event-router-config +kubectl -n vmware-system create secret generic event-router-config --from-file=/root/config/event-router-config.yaml kubectl -n vmware-system apply -f /root/config/event-router-k8s.yaml ``` From 761d41cb6212e00e5d6e7a83dbd03b76a6cd9717 Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Tue, 11 May 2021 16:18:42 +0200 Subject: [PATCH 33/56] Add docs section to CHANGELOG Closes: #404 Signed-off-by: Michael Gasch --- .chglog/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.chglog/config.yml b/.chglog/config.yml index 478088d9..bbe5b8ca 100755 --- a/.chglog/config.yml +++ b/.chglog/config.yml @@ -10,11 +10,13 @@ options: - fix - feat - chore + - docs commit_groups: title_maps: fix: 🐞 Fix feat: 💫 Feature chore: 🧹 Chore + docs: 📃 Documentation header: pattern: "^(\\w*)\\:\\s(.*)$" pattern_maps: From 8bb894986019d9a280f8d0a57ae801d8e768ce67 Mon Sep 17 00:00:00 2001 From: William Lam Date: Tue, 11 May 2021 07:42:30 -0700 Subject: [PATCH 34/56] docs: Update Knative Function list w/PowerShell Email Example Signed-off-by: William Lam --- docs/site/examples-knative.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/site/examples-knative.md b/docs/site/examples-knative.md index 552d0823..de70444e 100644 --- a/docs/site/examples-knative.md +++ b/docs/site/examples-knative.md @@ -28,6 +28,14 @@ examples: links: - language: powershell url: "/tree/master/examples/knative/powershell/kn-ps-slack" + - title: Email Notification + usecases: + - item: notification + id: kn-ps-email-function + description: Powershell function to send an Email. + links: + - language: powershell + url: "/tree/master/examples/knative/powershell/kn-ps-email" --- From c3b670de97a03b7617bb72c81ed234b06596df33 Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Mon, 17 May 2021 21:11:14 +0200 Subject: [PATCH 35/56] Clarify vcsim deprecation Closes: #408 Signed-off-by: Michael Gasch --- vmware-event-router/README.MD | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vmware-event-router/README.MD b/vmware-event-router/README.MD index a3c2b6a7..e08844df 100644 --- a/vmware-event-router/README.MD +++ b/vmware-event-router/README.MD @@ -9,7 +9,8 @@ events to configurable event `processors` (see below). **Supported event providers:** - [VMware vCenter Server](https://www.vmware.com/products/vcenter-server.html) -- vCenter Simulator [vcsim](https://github.com/vmware/govmomi/tree/master/vcsim) (DEPRECATED) +- vCenter Simulator [vcsim](https://github.com/vmware/govmomi/tree/master/vcsim) + (DEPRECATED, see note [below](#provider-type-vcsim)) **Supported event processors:** From a5a91d0f3776c492649127999869b01cf74ed68e Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Tue, 18 May 2021 11:01:35 +0200 Subject: [PATCH 36/56] chore: Daily build and helm verification Closes: #414 Signed-off-by: Michael Gasch --- .github/workflows/router-verify-build.yml | 3 +++ .github/workflows/router-verify-helm.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/router-verify-build.yml b/.github/workflows/router-verify-build.yml index b9e0e42f..db346dea 100644 --- a/.github/workflows/router-verify-build.yml +++ b/.github/workflows/router-verify-build.yml @@ -7,6 +7,9 @@ on: types: [opened, synchronize, reopened] paths: - 'vmware-event-router/**' + schedule: + # runs daily + - cron: '0 0 * * *' # run all jobs with these defaults, unless specified otherwise defaults: diff --git a/.github/workflows/router-verify-helm.yml b/.github/workflows/router-verify-helm.yml index 1d121e0b..cf468534 100644 --- a/.github/workflows/router-verify-helm.yml +++ b/.github/workflows/router-verify-helm.yml @@ -10,6 +10,9 @@ on: push: paths: - "vmware-event-router/chart/**" + schedule: + # runs daily + - cron: '0 0 * * *' jobs: openfaas: From ceaa9764cda16aea05be4e7799cc7ee29bf3955b Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Thu, 20 May 2021 17:25:38 +0200 Subject: [PATCH 37/56] Update stale action Fix typos and explain how to unmark as stale. Closes: #420 Signed-off-by: Michael Gasch --- .github/workflows/stale-issues.yml | 28 ------------------------ .github/workflows/stale.yml | 34 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 28 deletions(-) delete mode 100644 .github/workflows/stale-issues.yml create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml deleted file mode 100644 index 7a866eb2..00000000 --- a/.github/workflows/stale-issues.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: "Close stale issues" -on: - schedule: - # runs daily - - cron: '0 0 * * *' - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - # for issues and PRs - days-before-stale: 90 - days-before-close: 30 - exempt-issue-labels: 'awaiting-approval,work-in-progress,wip' - - # issues - stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.' - close-issue-message: 'Closing issue due to inactivity. Please reopen if needed' - stale-issue-label: 'stale' - - # PRs - stale-pr-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.' - close-pr-message: 'Closing issue due to inactivity. Please reopen if needed' - stale-pr-label: 'stale' \ No newline at end of file diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..836f5255 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,34 @@ +name: "Close stale issues" +on: + schedule: + # runs daily + - cron: '0 0 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + # for issues and PRs + days-before-stale: 90 + days-before-close: 30 + exempt-issue-labels: 'awaiting-approval,work-in-progress,wip' + + # issues + stale-issue-message: |- + This issue is stale because it has been open for 90 days with no + activity. It will automatically close after 30 more days of + inactivity. Mark as fresh by adding the comment `/remove-lifecycle stale`. + close-issue-message: 'Closing issue due to inactivity. Please reopen if needed' + stale-issue-label: 'stale' + + # PRs + stale-pr-message: |- + This Pull Request is stale because it has been open for 90 days with + no activity. It will automatically close after 30 more days of + inactivity. Mark as fresh by adding the comment `/remove-lifecycle stale`. + close-pr-message: 'Closing pull request due to inactivity. Please reopen if needed' + stale-pr-label: 'stale' From 0666a0a6340f8b6d3953221760961589d00d4da7 Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Fri, 21 May 2021 21:39:13 +0200 Subject: [PATCH 38/56] chore: Add CHANGELOG workflow Closes: 422 - keep a CHANGELOG - create PR on new release tag to update CHANGELOG - CHANGELOG template formatting Signed-off-by: Michael Gasch --- .chglog/CHANGELOG.tpl.md | 6 +- .github/workflows/release-notes.yml | 48 +++++ CHANGELOG.md | 306 ++++++++++++++++++++++++++++ 3 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/release-notes.yml create mode 100644 CHANGELOG.md diff --git a/.chglog/CHANGELOG.tpl.md b/.chglog/CHANGELOG.tpl.md index eda31bd8..ddc9a6ab 100755 --- a/.chglog/CHANGELOG.tpl.md +++ b/.chglog/CHANGELOG.tpl.md @@ -8,7 +8,7 @@ ### {{ .Title }} {{ range .Commits -}} -- {{ .Subject }} +- [{{ .Hash.Short }}]{{"\t"}}{{ .Subject }} {{ end }} {{ end -}} @@ -16,7 +16,7 @@ ### ⏮ Reverts {{ range .RevertCommits -}} -- {{ .Revert.Header }} +- [{{ .Hash.Short }}]{{"\t"}}{{ .Revert.Header }} {{ end }} {{ end -}} @@ -36,7 +36,7 @@ {{ if not .Merge -}} {{ if not (contains .Header "Update CHANGELOG for" ) -}} {{ if not (contains .Header "Merge branch" ) -}} -- {{ .Header }} [{{ .Hash.Short }}] +- [{{ .Hash.Short }}]{{"\t"}}{{ .Header }} {{ end -}} {{ end -}} {{ end -}} diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml new file mode 100644 index 00000000..3e6b5998 --- /dev/null +++ b/.github/workflows/release-notes.yml @@ -0,0 +1,48 @@ +name: Release Notes + +on: + push: + tags: + - "v*" + +jobs: + changelog: + name: Create CHANGELOG PR + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # for changelog + fetch-depth: 0 + ref: master + + - name: Create CHANGELOG commit + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + IMAGE: quay.io/git-chglog/git-chglog + # https://quay.io/repository/git-chglog/git-chglog from tag v0.14.2 + IMAGE_SHA: 998e89dab8dd8284cfff5f8cfb9e9af41fe3fcd4671f2e86a180e453c20959e3 + run: | + # update CHANGELOG + docker run --rm -v $PWD:/workdir ${IMAGE}@sha256:${IMAGE_SHA} -o CHANGELOG.md + git config user.email "${{ github.actor }}@users.noreply.github.com" + git config user.name "${{ github.actor }}" + git add CHANGELOG.md + git commit -s -m "Update CHANGELOG for $(basename ${{ github.ref }})" + + - name: Create Pull Request + id: cpr + uses: peter-evans/create-pull-request@v3 + with: + delete-branch: true + title: "Update CHANGELOG" + reviewers: embano1 + body: | + Update CHANGELOG.md for new release + + - name: Check outputs + run: | + echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" + echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..029f4504 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,306 @@ + + +## [Release v0.6.0](https://github.com/vmware-samples/vcenter-event-broker-appliance/compare/v0.5.0...v0.6.0) + +> Release Date: 2021-04-20 + +### 📖 Commits + +- [2088553] Bump version to v0.6.0 for release +- [831193f] Clarifying Knative OVF description + Docs +- [8246c90] Adding missing vmware-functions NS to secret example +- [27b6553] Re-enable Registry Digest Check +- [77f830e] Fix typo for Docker push command +- [f53f20c] Fixed VEBA UI image + Documentation Updates +- [7701edb] Add `/events` API endpoint +- [c710557] Adding code to include external contributors +- [bc15599] Bump version to v0.6.0 for release +- [df1b12a] Integrate Sockeye into VEBA +- [1abc331] Disable digest checking for Harbor Registry +- [fa226c5] Add Knative Documentation + Reorganize example folders +- [d9def65] Use mod=vendor +- [ddba4b6] Integrate VEBA UI +- [2e0f556] Add Knative to architecture +- [5df7dab] Update stale workflow +- [e6baaca] Updating VMware container image URLs to VMware Harbor +- [403b586] Removing symlink to /etc/resolv.conf +- [9d92de5] Updating the new PhotonOS ISO URL +- [c2f3993] Renaming namespace vmware to vmware-system +- [891cd96] Fix namespace bug in prometheus YAML + use apply +- [ca00305] Deploy local storage provisioner only for embedded Knative +- [625aa14] Updating files to help users consume knative vs openfaas functions +- [6ef86a3] vSphere tag synchronization to NSX-T +- [811f65d] Integrate Embedded Knative Broker +- [b12f536] Deprecate vcsim provider +- [548208b] Update to K8s 1.20.2 +- [c18f911] Configure log rotation for contrackd +- [73c772a] Update to OpenFaaS 0.12.15 +- [78a5470] Fix metadata label +- [2ed89a4] Added firewall requirement for access to vCenter API +- [9119e38] Fixed handling of xml predefined entities passed via ovfEnv + + +## [Release v0.5.0](https://github.com/vmware-samples/vcenter-event-broker-appliance/compare/v0.4.1...v0.5.0) + +> Release Date: 2020-12-11 + +### 📖 Commits + +- [d2876b0] Remove test from output target +- [7666144] Update VEBA BOM to final release +- [0a38742] Update Helm chart to final release +- [0201aea] Update Resources with the most current articles and blog posts +- [a8afbda] Create kn-echo example +- [6273def] Bump OpenFaaS SDK version +- [86d11ce] Fixing escaping credentials +- [b090158] Update Ingress deployment to handle Knative processor +- [476b96b] Bump version to v0.5.0 for release +- [e9f14b7] Fix metadata in Helm Chart +- [7475285] Add OVF dropdown for Knative processor selection +- [1773a80] Integrate Knative Processor +- [a8bb076] Use Waitgroup to track in-flight requests +- [f1d7bae] Standardize graceful shutdown handling +- [358e322] Add Knative Processor +- [5951bf4] Optimize Router Workflows +- [3278b74] Add Status Badges for Router Actions +- [da5f3a3] Increase timeout +- [317570c] Make linter standalone action +- [26aaad5] Update README +- [c8bf2d5] Remove verbose flag from YAML +- [7547f6c] Add Helm chart +- [9e1c15e] Update README +- [e9f6e88] Remove unused color package +- [d27c7ed] Update integration tests +- [388c454] Use structured logging +- [fd164df] Refactored to enable filtering against all data fields Can specify if all defined filters must match Handles recursing in to dicts/lists in the event data Added functionality to use 'n' in numeric indicies Added faasstack support Unit tests improved Documentation updates and improved logging +- [45e418b] Add details about vCenter events to README +- [0dd4cdc] Optimize pattern map locking +- [4a7ffde] Fix AWS enum in schema +- [61ecab2] Update docs +- [01b6db0] Reflect processor package changes in main +- [ec3fb41] Add retries to OpenFaaS processor +- [925255f] Move AWS processor to own package +- [ae0d836] Add invocation details to metrics +- [67e4747] Update processor interface +- [b8fb290] Always use latest certificates +- [3f013da] Fix lock in vcenter +- [7799c9f] Include all files in gofmt +- [406e29d] Fix BOM version change in integration tests +- [9fde8e7] Address review Frankie +- [bafa586] Add vcsim as event provider +- [d95529b] Function for plugin auto-refresh +- [0dbb217] Changing development branch to use development container and simplify build script +- [9ba03c6] Remove set-env references +- [1c41453] Proposed fix for issue [#211](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/211) +- [dde2464] Update create-docker-dev-image.yml +- [2b8b852] Moving VMware Event Router section to top for ease of edit +- [0a22792] Renaming version in BOM to represent Github Repo Tag +- [fa832c1] Removing VERSION file, no longer being used +- [4bb5315] Add event UUID to checkpoint +- [36c0d15] Removing Packer vnc_disable_password +- [96aea76] Migrate VEBA configuration to YAML +- [c75f0f0] Fixing Contour envoy.yaml due to changes introduced in v1.4.0 + use latest Contour v1.9.0 +- [4b20765] Implement at-least-once delivery via checkpointing +- [265daf6] Build development image on push +- [e201fab] fixed bug in build.sh on CentOS Added support for Packer 1.6.x to support ESXi 7.0+ added min packer version Updates to docs +- [9dcc80f] Add workflow to build image on dev branch +- [61a2088] Update echo function to Python3 +- [d793958] VM backup function via VEEAM +- [3870e6a] Update build and README +- [b27af5c] Update documentation and deployment files +- [e46b128] Add JSON schemagen +- [75fefdc] Reflect changes in build files +- [a719289] Move router cmd to sub-folder +- [120beec] Migrate to v1alpha1 config API using YAML +- [88b878c] Add Pagerduty trigger example in go ([#201](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/201)) +- [01fb656] Add vm-reconfigure-via-tag go handle function example +- [fd2b58a] Initial commit of pre-filter function +- [3980efb] Shorten workflow names +- [93def7c] Add Github Action to close stale issues +- [edceb28] Bump action/checkout to v2 +- [af7ec3b] Update go mods +- [4d4296b] Add integration tests +- [62e69ab] VMware Cloud Notification Slack and Microsoft Teams Functions +- [30dc878] New example HA Restarted VMs Email Notification +- [b907174] Github Template to standardized Pull Requests +- [0f3c606] Global search and replace on flings URL - vcenter->vmware +- [f7fcc4d] Search functionality added to Documentation +- [7b5be22] VEBA Issue and Feature Enhancement Templates +- [88bd3f2] Update K8s, Contour, OpenFaaS to latest stable release +- [8895230] Refactor VEBA components to reference BOM file +- [5fc30e2] Add faas-cli version to BOM +- [7fbf2cb] Update Docker images used in VMware Event Router +- [312d726] Decouple from types.BaseEvent +- [977d96f] Update Linter and Unit Test Action + + +## [Release v0.4.1](https://github.com/vmware-samples/vcenter-event-broker-appliance/compare/v0.4.0...v0.4.1) + +> Release Date: 2020-06-10 + +### 📖 Commits + +- [4bf45d7] Updating docs to reflect changes with Proxy and SSH +- [a886138] Fixing VEBA_VERSION reference for VM Notes +- [d126374] Fix Pre-Release Image Build +- [d1c4496] Bump version to v0.4.1 for release +- [9025804] Add VEBA build-of-materials (BOM) file +- [f7d1687] Change print and add comment to test +- [122e365] Allow use of "http://" prefix in HTTPS Proxy config Updated OVF properties to suit previous commit +- [1404680] Add unit test and implement error interface +- [37b2fec] point at note from bundler docs +- [b4658a9] add FQDN hint to hostname description +- [5e0e6cf] Standarding on the relative path for files within docs +- [b9fd0f4] Updated DCUI loads default values if veba-release unreadable +- [2fa6384] install gems only for the user, and set a vendor path for bundle use +- [4164f74] fix broken link +- [7f66cca] update docs faqs and resources with monitoring and harbor blog posts +- [2140296] Added enable SSH option to OVA install +- [ae29aed] Add Action to block PR merge when title is WIP +- [acf040f] Introduce Error struct in processor +- [f0fd438] Fixed typo in processing ESCAPED_AWS_EVENTBRIDGE_EVENT_BUS variable +- [5e63b34] Added recommendation to add appliance IP to NO_PROXY +- [dd29cf5] Updated docs to reflect new v0.4 path to event-router-config.json +- [967c641] Updating the container image name for consistency +- [e4c4aec] Added proxy support for deployment scripts +- [d5e8f7e] Add Action for Event Router Unit Tests on PRs +- [fc83d68] Add Action to reject PRs against master +- [783fd0e] Add Github Action for Docker Pre-Release +- [832642f] Support datastore custom attribute as To: address + + +## [Release v0.4.0](https://github.com/vmware-samples/vcenter-event-broker-appliance/compare/v0.3.0...v0.4.0) + +> Release Date: 2020-05-11 + +### 📖 Commits + +- [70d47df] Fixing minor typo in README +- [2659ced] Use linux-esx as its optmized on VMware-based Hypervisor +- [e521d4a] tndf upgrade isn't needed due to newer Photon OS image +- [a3ebd26] Use VEBA_VERSION defined in build.sh so filename in OVA matches +- [3518ca5] Update build.sh script to handle release vs master build +- [80cdc3e] Update to latest PhotonOS image to 3.0 Rev2 +- [1294430] Fix Makefile timeout messed up +- [346323e] Bump version to v0.4.0 for release +- [3939aa2] Update READMEs in VMware Event Router +- [3d9d107] Remove unnecessary vmw:ExtraConfig from OVA +- [0cec03a] Initializing user focused and friendly website content +- [e2201f1] Adding a Fx for Rest API Integrations with basic, anonymous or token based auth +- [13bfab4] Add DCUI binary to files +- [7c12dc7] Add licenses for libraries used in DCUI +- [4943a23] Move release workflow to right folder +- [b48d053] Add Github Action to push Docker Images +- [c2d75cb] Document deploying VEBA as K8s App +- [1e5fcf3] Replace Weave with Antrea CNI + required configuration changes +- [459e589] Delete greetings workflow +- [db43ef7] Add input entry batching +- [65c71b8] Add PagerDuty Python Example (tested with VEBA v0.3, vCenter 6.7 with VMPowerOn/Off Events) +- [c0caac1] Consistent use of Notes in markdown files +- [10e14a8] Fix VMware Event Router image pull to support air-gap scenario +- [7af6632] Add Docker image for :VERSION tag +- [53293b0] Handling special character which must be escaped in Event Router JSON configuration +- [9bf3702] Updated to pull Weave YAML from Github rather than from dynamic URL + updated Weave version +- [6bf1e8b] Add Otto to start page +- [b6421db] Add official VEBA mascot ([#98](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/98)) +- [7addae5] Updating Troubleshooting docs with correct path to config file +- [4cc3083] Cleaning up dev template to simplify contributions +- [7a2c0d7] Reorganize all VEBA config files into /root/config +- [73c9377] Add example function using Go and govmomi that attaches tag to VM +- [54adab3] Add initial release of troubleshooting guide +- [c73bf9b] Adding /etc/veba-release to include VEBA version, commit ID & event processor type +- [7d3b92f] Remove User Stories ([#84](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/84)) +- [902e376] Add greeting action on pull requests ([#88](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/88)) +- [10a536f] Revert Github action for stale issues/PRs +- [015fa43] Add Github Action for stale issues/PRs +- [55a0d0d] Fix rule processing switch statements +- [db01ee1] Make EventBridge client interface +- [4d30278] Support customization of Docker Bridge Network ([#76](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/76)) +- [beb7637] vRO Function +- [11500b7] Spruce up README with a few badges + + +## [Release v0.3.0](https://github.com/vmware-samples/vcenter-event-broker-appliance/compare/v0.2.0...v0.3.0) + +> Release Date: 2020-03-10 + +### 📖 Commits + +- [976adc5] Clarify resync period of AWS EventBridge Processor +- [d5dc652] Ensure we pull latest vmware-event-router image +- [61f98e2] Fixed OpenFaaS admin password +- [d083a45] Added unauthenticated SMTP and green status emails ([#72](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/72)) +- [993eea3] Fixing FQDN in /etc/issue +- [1c97a8b] Fixing syntax issue w/creating tools.conf +- [f019970] Bump version to v0.3.0 for release +- [dd1330e] Fixed MoRef procesisng for v0.3 +- [81587c7] Ensuring eth0 interface is shown first in vSphere UI +- [8c2327d] Colorized log output +- [75414da] Fix linter errors +- [dd3ca14] Updated Getting Started User Guide +- [0e193ee] Stricter linting on VMware Event Router +- [84eb278] Add branch information to Python examples +- [ac6ddbe] Test automation script samples to deploy VEBA using either OpenFaaS or EventBridge Processor +- [6e597d7] Added drop-down menu for Network CIDR selection + clear OVF properties for security +- [3a56753] Updated functions to support v0.3 +- [75e56d1] Fix disabling SSH +- [c9be69e] Pull Event Router Image 1683830 + updated Event Router to include stats deployment +- [865c402] Refactored setup.sh to just process OVF properties and introduce sub-setup scripts for configurations +- [0a7b24f] Run TinyWWW in VMware namespace +- [a47145b] Reorganize OVF properties to incoroprate flexible Event Processors + Added support for Event Bridge +- [f37ee3c] Updated Packer build files to incoroprate refactored setup scripts, new OVF params, invalid Packer option + local test env +- [ec7977e] Updating VEBA Version in OVA build +- [f871128] Refactored Installation scripts, Pull Event Router Image + Remove vc-connector +- [3d90bb3] Update Python examples for v0.3 release +- [55b287c] Consolidate docs on architecture +- [be766b4] Use ErrorGroup Context to return on first error +- [7b52f9e] Update docs for new VMware Event Router +- [7cb1c84] Update .gitignore for VMware Event Router +- [6c34f73] Update Python Examples for CloudEvents +- [811f75d] Implement VMware Event Router +- [9a5be39] Add commit best practices link to CONTRIBUTING +- [168b2bf] replace references of [IP] to [hostname] + + +## [Release v0.2.0](https://github.com/vmware-samples/vcenter-event-broker-appliance/compare/v0.1.0...v0.2.0) + +> Release Date: 2020-01-23 + +### 📖 Commits + +- [0b2c33a] Bump version to v0.2.0 for release +- [9cbe137] Proxy settings for docker adjusted +- [94dc2c6] Added proxy settings to ova +- [fc17e8f] Add NTP to photon-dev.xml.template photon-dev.json +- [3889768] Add AWS EventBridge sample to example README +- [8a603d4] Update default POD CIDR 10.99.0.0/20 & made it configurable +- [2ac99cd] AWS EventBridge Sample +- [c00a4a1] Added ntp settings to ova +- [f582274] Fixed provider name, read_debug, and faas-cli typo +- [d3497b5] Updated DNS desc + vCenter Event Mapping Details +- [03b38d2] Add esx mtu fixer function to examples +- [8d894b4] The default provider name in stack.yml is 'faas', but the function fails to deploy with error "['openfaas'] is the only valid "provider.name" for the OpenFaaS CLI, but you gave: faas". Changing the default provider name to 'openfaas' fixes the problem. +- [3770282] Update FAQ with current WIP/TODO items +- [74f4480] Fixed typo 'read_debuge -> read_debug' +- [df02bdd] WIP: FAQ +- [9ae58a7] Compliance and Documentation changes +- [8926cac] Add host maintenance example to README +- [a5a8ce9] New example for host maintenance +- [a84a772] Pre-Download k8s Docker Images for non-internet require setup +- [b35a3e2] new example VM reconfigure to Slack + + +## v0.1.0 + +> Release Date: 2019-11-25 + +### 📖 Commits + +- [25fdc60] Fix dead link to contributing guide +- [cd125e6] Add README to Python tagging example +- [c41dabc] Add DCO info +- [2019596] Add DCO info +- [6d41d0b] Initial Commit +- [ac3a2d7] Adding .gitignore template +- [fd4f8bf] Add CONTRIBUTING template +- [e950c2a] Add README template From 02bd23c0e5b4339ce25bbdff9268638aa3bf178e Mon Sep 17 00:00:00 2001 From: "F. Gold" Date: Fri, 2 Apr 2021 14:26:51 -0700 Subject: [PATCH 39/56] Example Knative echo service written in Go Signed-off-by: F. Gold --- examples/knative/go/kn-go-echo/README.md | 146 ++++++++++++++++++ examples/knative/go/kn-go-echo/function.yaml | 29 ++++ examples/knative/go/kn-go-echo/go.mod | 5 + examples/knative/go/kn-go-echo/go.sum | 48 ++++++ examples/knative/go/kn-go-echo/server.go | 44 ++++++ examples/knative/go/kn-go-echo/server_test.go | 23 +++ .../go/kn-go-echo/testdata/cloudevent.json | 49 ++++++ 7 files changed, 344 insertions(+) create mode 100644 examples/knative/go/kn-go-echo/README.md create mode 100644 examples/knative/go/kn-go-echo/function.yaml create mode 100644 examples/knative/go/kn-go-echo/go.mod create mode 100644 examples/knative/go/kn-go-echo/go.sum create mode 100644 examples/knative/go/kn-go-echo/server.go create mode 100644 examples/knative/go/kn-go-echo/server_test.go create mode 100644 examples/knative/go/kn-go-echo/testdata/cloudevent.json diff --git a/examples/knative/go/kn-go-echo/README.md b/examples/knative/go/kn-go-echo/README.md new file mode 100644 index 00000000..29ce0ac9 --- /dev/null +++ b/examples/knative/go/kn-go-echo/README.md @@ -0,0 +1,146 @@ +# kn-go-echo + +Simple Go function running in Knative to echo [CloudEvents](https://github.com/cloudevents/sdk-go). +In addition, [ko](https://github.com/google/ko) is used to create the artifacts. + +Valid events will be printed out JSON-encoded, separated by their CloudEvent +`attributes` and `data` for better readability. This example service also +returns the received event, enabling it to be used as part of a pipeline of +events. + +## Step 1: Build with `ko` + +`ko` was created to help Knative developers build images and binaries without +the need for Dockerfiles nor Makefiles. See more information in [this Knative +blog](https://knative.dev/blog/2018/12/18/ko-fast-kubernetes-microservice-development-in-go/). + +Follow setup instructions at https://github.com/google/ko. + +After installing ko, set the destination for images with an environment variable. + +``` bash +KO_DOCKER_REPO=my-dockerhub-user +``` + +Run the following command from the `kn-go-echo` directory. It will build and +push the image to your local Docker daemon. To have `ko` publish and use a +container image registry, remove the `--local` flag. + +```bash +ko publish --local . +``` + +## Step 2: Test + +To run the container using a locally stored image, use + +```bash +docker run -p 8080:8080 $(ko publish --local .) +``` + +In a separate window, send a POST request. You can send fake cloud event for +testing purposes. + +```bash +$ curl -X POST -i -d@testdata/cloudevent.json \ + --header 'Content-Type: application/cloudevents+json' \ + localhost:8080 +HTTP/1.1 100 Continue + +HTTP/1.1 200 OK +Ce-Id: 08179137-b8e0-4973-b05f-8f212bf5003b +Ce-Source: https://10.0.0.1:443/sdk +Ce-Specversion: 1.0 +Ce-Subject: VmPoweredOffEvent +Ce-Time: 2020-02-11T21:29:54.9052539Z +Ce-Type: com.vmware.event.router/event +Content-Length: 1042 +Content-Type: application/json +Date: Fri, 07 May 2021 21:29:27 GMT + + { "Key": 9902, "ChainId": 9895, "CreatedTime": "2020-02-11T21:28:23.677595Z", "UserName": "VSPHERE.LOCAL\\Administrator", "Datacenter": { "Name": "testDC", "Datacenter": { "Type": "Datacenter", "Value": "datacenter-2" } }, "ComputeResource": { "Name": "cls", "ComputeResource": { "Type": "ClusterComputeResource", "Value": "domain-c7" } }, "Host": { "Name": "10.185.22.74", "Host": { "Type": "HostSystem", "Value": "host-21" } }, "Vm": { "Name": "test-01", "Vm": { "Type": "VirtualMachine", "Value": "vm-56" } }, "Ds": null, "Net": null, "Dvs": null, "FullFormattedMessage": "test-01 on 10.0.0.1 in testDC is powered off", "ChangeTag": "", "Template": false } +``` + +The following lines should appear in the docker container: + +``` +2021/05/06 21:09:03 listening on :8080 +***cloud event*** +Context Attributes, + specversion: 1.0 + type: com.vmware.event.router/event + source: https://10.0.0.1:443/sdk + subject: VmPoweredOffEvent + id: 08179137-b8e0-4973-b05f-8f212bf5003b + time: 2020-02-11T21:29:54.9052539Z + datacontenttype: application/json +Data, + { + "Key": 9902, + "ChainId": 9895, + "CreatedTime": "2020-02-11T21:28:23.677595Z", + "UserName": "VSPHERE.LOCAL\\Administrator", + "Datacenter": { + "Name": "testDC", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-2" + } + }, + "ComputeResource": { + "Name": "cls", + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c7" + } + }, + "Host": { + "Name": "10.185.22.74", + "Host": { + "Type": "HostSystem", + "Value": "host-21" + } + }, + "Vm": { + "Name": "test-01", + "Vm": { + "Type": "VirtualMachine", + "Value": "vm-56" + } + }, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "test-01 on 10.0.0.1 in testDC is powered off", + "ChangeTag": "", + "Template": false + } +``` + +## Step 3: Deploy + +**Note:** The following steps assume a working Knative environment using the +`default` Rabbit broker. The Knative `service` and `trigger` will be installed in the +`vmware-functions` Kubernetes namespace, assuming that the broker is also available there. + +Edit the function.yaml file with the name of the container image from Step 1 if you made any changes. If not, the default VMware container image will suffice and then deploy the function to VMware Event Broker Appliance (VEBA): + +```bash +# Deploy function +kubectl -n vmware-functions apply -f function.yaml +``` + +For testing purposes, the function.yaml contains the following annotations, which will ensure the Knative Service Pod will always run exactly one instance for debugging purposes. Functions deployed through through the VMware Event Broker Appliance UI defaults to scale to 0, which means the pods will only run when it is triggered by an vCenter Event. + +```yaml +annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" +``` + +## Step 4: Undeploy + +```bash +# Undeploy function +kubectl -n vmware-functions delete -f function.yaml +``` diff --git a/examples/knative/go/kn-go-echo/function.yaml b/examples/knative/go/kn-go-echo/function.yaml new file mode 100644 index 00000000..3fe208b7 --- /dev/null +++ b/examples/knative/go/kn-go-echo/function.yaml @@ -0,0 +1,29 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-go-echo + labels: + app: veba-ui +spec: + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" + spec: + containers: + - image: projects.registry.vmware.com/veba/kn-go-echo:1.0 +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: kn-go-echo-trigger + labels: + app: veba-ui +spec: + broker: default + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-go-echo diff --git a/examples/knative/go/kn-go-echo/go.mod b/examples/knative/go/kn-go-echo/go.mod new file mode 100644 index 00000000..d83d0fcc --- /dev/null +++ b/examples/knative/go/kn-go-echo/go.mod @@ -0,0 +1,5 @@ +module github.com/vmware-samples/vcenter-event-broker-appliance/examples/go/kn-echo + +go 1.15 + +require github.com/cloudevents/sdk-go/v2 v2.4.1 diff --git a/examples/knative/go/kn-go-echo/go.sum b/examples/knative/go/kn-go-echo/go.sum new file mode 100644 index 00000000..f2742691 --- /dev/null +++ b/examples/knative/go/kn-go-echo/go.sum @@ -0,0 +1,48 @@ +github.com/cloudevents/sdk-go/v2 v2.4.1 h1:rZJoz9QVLbWQmnvLPDFEmv17Czu+CfSPwMO6lhJ72xQ= +github.com/cloudevents/sdk-go/v2 v2.4.1/go.mod h1:MZiMwmAh5tGj+fPFvtHv9hKurKqXtdB9haJYMJ/7GJY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/examples/knative/go/kn-go-echo/server.go b/examples/knative/go/kn-go-echo/server.go new file mode 100644 index 00000000..147ddb08 --- /dev/null +++ b/examples/knative/go/kn-go-echo/server.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + "fmt" + "log" + "strings" + + cloudevents "github.com/cloudevents/sdk-go/v2" +) + +// echoSuffix indicates the event is an echo. +const echoSuffix = ".echo" + +func main() { + ctx := context.Background() + + client, err := cloudevents.NewClientHTTP() + if err != nil { + log.Fatalf("creating HTTP client, %v", err) + } + + log.Println("listening on :8080") + if err := client.StartReceiver(ctx, receive); err != nil { + log.Fatalf("start receiver: %s", err.Error()) + } +} + +func receive(ctx context.Context, event cloudevents.Event) *cloudevents.Event { + fmt.Printf("***cloud event***\n%s", event) + + // Don't return the event if it is an echo. Prevent an infinite event echo loop. + if isEcho(event.Type()) { + return nil + } + + event.SetType(event.Type() + echoSuffix) + return &event +} + +func isEcho(s string) bool { + trimmed := strings.TrimSuffix(s, echoSuffix) + return trimmed != s +} diff --git a/examples/knative/go/kn-go-echo/server_test.go b/examples/knative/go/kn-go-echo/server_test.go new file mode 100644 index 00000000..a84a4cac --- /dev/null +++ b/examples/knative/go/kn-go-echo/server_test.go @@ -0,0 +1,23 @@ +package main + +import ( + "testing" +) + +func TestIsEcho(t *testing.T) { + tests := []struct { + input string + want bool + }{ + {"blah blah blah.echo", true}, + {"blah blah blah", false}, + {"echo echo .echo blah blah blah", false}, + {"echo echo .echo blah blah blah.echo", true}, + } + + for _, tt := range tests { + if got := isEcho(tt.input); got != tt.want { + t.Errorf("got isEcho(%q) %t, want %t", tt.input, got, tt.want) + } + } +} diff --git a/examples/knative/go/kn-go-echo/testdata/cloudevent.json b/examples/knative/go/kn-go-echo/testdata/cloudevent.json new file mode 100644 index 00000000..da789ce0 --- /dev/null +++ b/examples/knative/go/kn-go-echo/testdata/cloudevent.json @@ -0,0 +1,49 @@ +{ + "id": "08179137-b8e0-4973-b05f-8f212bf5003b", + "source": "https://10.0.0.1:443/sdk", + "specversion": "1.0", + "type": "com.vmware.event.router/event", + "subject": "VmPoweredOffEvent", + "time": "2020-02-11T21:29:54.9052539Z", + "data": { + "Key": 9902, + "ChainId": 9895, + "CreatedTime": "2020-02-11T21:28:23.677595Z", + "UserName": "VSPHERE.LOCAL\\Administrator", + "Datacenter": { + "Name": "testDC", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-2" + } + }, + "ComputeResource": { + "Name": "cls", + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c7" + } + }, + "Host": { + "Name": "10.185.22.74", + "Host": { + "Type": "HostSystem", + "Value": "host-21" + } + }, + "Vm": { + "Name": "test-01", + "Vm": { + "Type": "VirtualMachine", + "Value": "vm-56" + } + }, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "test-01 on 10.0.0.1 in testDC is powered off", + "ChangeTag": "", + "Template": false + }, + "datacontenttype": "application/json" +} \ No newline at end of file From e3b02a5ac2c1c9bf5241c6e135d80ae0fec51e67 Mon Sep 17 00:00:00 2001 From: Scottie Ray Date: Tue, 25 May 2021 14:19:05 -0500 Subject: [PATCH 40/56] feat: Add kn-py-slack Python example Signed-off-by: Scottie Ray --- examples/knative/python/kn-py-slack/Procfile | 1 + examples/knative/python/kn-py-slack/README.md | 115 ++++++++++++++++++ .../knative/python/kn-py-slack/function.yaml | 40 ++++++ .../knative/python/kn-py-slack/handler.py | 56 +++++++++ .../knative/python/kn-py-slack/pyvenv.cfg | 2 + .../python/kn-py-slack/requirements.txt | 3 + .../kn-py-slack/screenshots/Test_Event.png | Bin 0 -> 41653 bytes .../python/kn-py-slack/slack_secret.txt | 1 + .../kn-py-slack/test/docker-test-env-variable | 1 + .../python/kn-py-slack/test/testevent.json | 49 ++++++++ 10 files changed, 268 insertions(+) create mode 100644 examples/knative/python/kn-py-slack/Procfile create mode 100644 examples/knative/python/kn-py-slack/README.md create mode 100644 examples/knative/python/kn-py-slack/function.yaml create mode 100644 examples/knative/python/kn-py-slack/handler.py create mode 100644 examples/knative/python/kn-py-slack/pyvenv.cfg create mode 100644 examples/knative/python/kn-py-slack/requirements.txt create mode 100644 examples/knative/python/kn-py-slack/screenshots/Test_Event.png create mode 100644 examples/knative/python/kn-py-slack/slack_secret.txt create mode 100644 examples/knative/python/kn-py-slack/test/docker-test-env-variable create mode 100644 examples/knative/python/kn-py-slack/test/testevent.json diff --git a/examples/knative/python/kn-py-slack/Procfile b/examples/knative/python/kn-py-slack/Procfile new file mode 100644 index 00000000..760d3af1 --- /dev/null +++ b/examples/knative/python/kn-py-slack/Procfile @@ -0,0 +1 @@ +web: FLASK_ENV=development FLASK_APP=handler.py python3 -m flask run --host=0.0.0.0 --port=$PORT \ No newline at end of file diff --git a/examples/knative/python/kn-py-slack/README.md b/examples/knative/python/kn-py-slack/README.md new file mode 100644 index 00000000..fbeefe69 --- /dev/null +++ b/examples/knative/python/kn-py-slack/README.md @@ -0,0 +1,115 @@ +# kn-py-slack +Example Knative Python function for sending to a Slack webhook when a Virtual Machine is powered off. + +# Step 1 - Build with `pack` + +[Buildpacks](https://buildpacks.io) are used to create the container image. + +```bash +IMAGE=/kn-py-slack:1.0 +pack build -B gcr.io/buildpacks/builder:v1 ${IMAGE} +``` + + +# Step 2 - Test + +Verify the container image works by executing it locally. + +Change into the `test` directory +```console +cd test +``` + +Update the `docker-test-env-variable` file with your Slack webook URL. + +Start the container image by running the following command: + +```console +docker run -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 /kn-py-slack:1.0 + +Serving Flask app "handler.py" (lazy loading) + * Environment: development + * Debug mode: on + * Running on all addresses. + WARNING: This is a development server. Do not use it in a production deployment. + * Running on http://172.17.0.2:8080/ (Press CTRL+C to quit) + * Restarting with stat + * Debugger is active! + ``` + +In a separate terminal window, go to the test directory and use the `testevent.json` file to validate the function is working. You should see output similar to this below. + +``` +curl -i -d@testevent.json localhost:8080 + +HTTP/1.1 100 Continue + +HTTP/1.0 200 OK +Content-Type: application/json +Content-Length: 3 +Server: Werkzeug/1.0.1 Python/3.9.0 +Date: Tue, 25 May 2021 13:59:31 GMT +``` +Return to the previous terminal window where you started the docker image, and you should see something similar to the following: + +``` +2021-05-25 08:59:28,400 INFO werkzeug MainThread : * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) +2021-05-25 08:59:31,423 DEBUG urllib3.connectionpool Thread-1 : Starting new HTTPS connection (1): hooks.slack.com:443 +2021-05-25 08:59:31,614 DEBUG urllib3.connectionpool Thread-1 : https://hooks.slack.com:443 "POST /services/ HTTP/1.1" 200 22 +2021-05-25 08:59:31,618 INFO werkzeug Thread-1 : 127.0.0.1 - - [25/May/2021 08:59:31] "POST / HTTP/1.1" 200 - +``` + + +Finally, check your Slack channel to see if the test event posted. + +![](screenshots/Test_Event.png) + +# Step 3 - Deploy + +> **Note:** The following steps assume a working Knative environment using the +`default` Rabbit `broker`. The Knative `service` and `trigger` will be installed in the +`vmware-functions` Kubernetes namespace, assuming that the `broker` is also available there. + +Push your container image to an accessible registry such as Docker once you're done developing and testing your function logic. + +```console +docker push /kn-py-slack:1.0 +``` + +Update the `slack_secret.json` file with your Slack webhook configurations and then create the kubernetes secret which can then be accessed from within the function by using the environment variable named called `SLACK_SECRET`. + +```console +# create secret +kubectl -n vmware-functions create secret generic slack-secret --from-file=SLACK_SECRET=slack_secret.txt + +# update label for secret to show up in VEBA UI +kubectl -n vmware-functions label secret slack-secret app=veba-ui +``` + +Edit the `function.yaml` file with the name of the container image from Step 1 if you made any changes. If not, the default VMware container image will suffice. By default, the function deployment will filter on the `VmPoweredOffEvent` vCenter Server Event. If you wish to change this, update the `subject` field within `function.yaml` to the desired event type. + + +Deploy the function to the VMware Event Broker Appliance (VEBA). + +```console +# deploy function +kubectl -n vmware-functions apply -f function.yaml +``` + +For testing purposes, the `function.yaml` contains the following annotations, which will ensure the Knative Service Pod will always run **exactly** one instance for debugging purposes. Functions deployed through through the VMware Event Broker Appliance UI defaults to scale to 0, which means the pods will only run when it is triggered by an vCenter Event. + +```yaml +annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" +``` + +# Step 4 - Undeploy + +```console +# undeploy function +kubectl -n vmware-functions delete -f function.yaml + +# delete secret +kubectl -n vmware-functions delete secret slack-secret +``` diff --git a/examples/knative/python/kn-py-slack/function.yaml b/examples/knative/python/kn-py-slack/function.yaml new file mode 100644 index 00000000..2363714d --- /dev/null +++ b/examples/knative/python/kn-py-slack/function.yaml @@ -0,0 +1,40 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-py-slack + labels: + app: veba-ui + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" +spec: + template: + metadata: + spec: + containers: + - image: projects.registry.vmware.com/veba/kn-py-slack:1.0 + envFrom: + - secretRef: + name: slack-secret + +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-py-slack-trigger + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + subject: VmPoweredOffEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-py-slack + diff --git a/examples/knative/python/kn-py-slack/handler.py b/examples/knative/python/kn-py-slack/handler.py new file mode 100644 index 00000000..16ac1ddb --- /dev/null +++ b/examples/knative/python/kn-py-slack/handler.py @@ -0,0 +1,56 @@ +from flask import Flask, request, jsonify +import os +import requests +from cloudevents.http import from_http +import logging,json + +logging.basicConfig(level=logging.DEBUG,format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s') + +app = Flask(__name__) +#Change the value to match the secret key in the VEBA appliance where you enter the Slack webook url information +#url = os.environ.get('SLACK_SECRET') + +@app.route('/', methods=['POST']) +def slack(): + + try: + event = from_http(request.headers, request.get_data(),None) + + data = event.data + attrs = event._attributes + + #this section uses the Slack formatting to present the events in the format you would like. You can modify as needed to add, remove or re-order the elements in a message + payload = { "text": f"*CLOUDEVENT_ID*:\n {attrs['id']}\n\n Source: {attrs['source']}\n Type: {attrs['type']}\n *Subject*: *{attrs['subject']}*\n Time: {attrs['time']}\n\n *EVENT DATA*:\n key: {data['Key']}\n user: {data['UserName']}\n datacenter: {data['Datacenter']['Name']}\n Host: {data['Host']['Name']}\n VM: {data['Vm']['Name']}\n\n Message: {data['FullFormattedMessage']}" } + + requests.post(url=url, json=payload) + + # app.logger.info(f'"***cloud event*** {json.dumps(e)}') + return {}, 200 + + except KeyError as e: + sc = 400 + msg = f'could not decode cloud event: {e}' + app.logger.error(msg) + message = { + 'status': sc, + 'error': msg, + } + resp = jsonify(message) + resp.status_code = sc + return resp + + except Exception as e: + sc = 500 + msg = f'could not send message: {e}' + app.logger.error(msg) + message = { + 'status': sc, + 'error': msg, + } + resp = jsonify(message) + resp.status_code = sc + return resp + +# hint: run with FLASK_ENV=development FLASK_APP=handler.py flask run +if __name__ == "__main__": + app.run() diff --git a/examples/knative/python/kn-py-slack/pyvenv.cfg b/examples/knative/python/kn-py-slack/pyvenv.cfg new file mode 100644 index 00000000..44b2a58c --- /dev/null +++ b/examples/knative/python/kn-py-slack/pyvenv.cfg @@ -0,0 +1,2 @@ +home = /usr/local/bin +include-system-site-packages = false \ No newline at end of file diff --git a/examples/knative/python/kn-py-slack/requirements.txt b/examples/knative/python/kn-py-slack/requirements.txt new file mode 100644 index 00000000..d4ac1ed0 --- /dev/null +++ b/examples/knative/python/kn-py-slack/requirements.txt @@ -0,0 +1,3 @@ +flask==1.1.2 +cloudevents==1.2.0 +requests==2.7.0 \ No newline at end of file diff --git a/examples/knative/python/kn-py-slack/screenshots/Test_Event.png b/examples/knative/python/kn-py-slack/screenshots/Test_Event.png new file mode 100644 index 0000000000000000000000000000000000000000..224ec1d13cd032a041b628efa1e723e22e5353f2 GIT binary patch literal 41653 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 W6?(SMtEbn%pPG{9qY4Gf@c#kt$gb@G literal 0 HcmV?d00001 diff --git a/examples/knative/python/kn-py-slack/slack_secret.txt b/examples/knative/python/kn-py-slack/slack_secret.txt new file mode 100644 index 00000000..6983a0a5 --- /dev/null +++ b/examples/knative/python/kn-py-slack/slack_secret.txt @@ -0,0 +1 @@ + diff --git a/examples/knative/python/kn-py-slack/test/docker-test-env-variable b/examples/knative/python/kn-py-slack/test/docker-test-env-variable new file mode 100644 index 00000000..4c429654 --- /dev/null +++ b/examples/knative/python/kn-py-slack/test/docker-test-env-variable @@ -0,0 +1 @@ +SLACK_SECRET= diff --git a/examples/knative/python/kn-py-slack/test/testevent.json b/examples/knative/python/kn-py-slack/test/testevent.json new file mode 100644 index 00000000..8507c32f --- /dev/null +++ b/examples/knative/python/kn-py-slack/test/testevent.json @@ -0,0 +1,49 @@ +{ + "id": "08179137-b8e0-4973-b05f-8f212bf5003b", + "source": "https://10.0.0.1:443/sdk", + "specversion": "1.0", + "type": "com.vmware.event.router/event", + "subject": "VmPoweredOffEvent", + "time": "2020-02-11T21:29:54.9052539Z", + "data": { + "Key": 9902, + "ChainId": 9895, + "CreatedTime": "2020-02-11T21:28:23.677595Z", + "UserName": "VSPHERE.LOCAL\\Administrator", + "Datacenter": { + "Name": "testDC", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-2" + } + }, + "ComputeResource": { + "Name": "cls", + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c7" + } + }, + "Host": { + "Name": "10.185.22.74", + "Host": { + "Type": "HostSystem", + "Value": "host-21" + } + }, + "Vm": { + "Name": "test-01", + "Vm": { + "Type": "VirtualMachine", + "Value": "vm-56" + } + }, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "test-01 on 10.0.0.1 in testDC is powered off", + "ChangeTag": "", + "Template": false + }, + "datacontenttype": "application/json" +} From 6367ee0b7a5c4d9961e5f20481b83798efabb5d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 May 2021 20:10:22 +0000 Subject: [PATCH 41/56] Bump requests in /examples/knative/python/kn-py-slack Bumps [requests](https://github.com/psf/requests) from 2.7.0 to 2.20.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/master/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.7.0...v2.20.0) Signed-off-by: dependabot[bot] --- examples/knative/python/kn-py-slack/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/knative/python/kn-py-slack/requirements.txt b/examples/knative/python/kn-py-slack/requirements.txt index d4ac1ed0..65f72562 100644 --- a/examples/knative/python/kn-py-slack/requirements.txt +++ b/examples/knative/python/kn-py-slack/requirements.txt @@ -1,3 +1,3 @@ flask==1.1.2 cloudevents==1.2.0 -requests==2.7.0 \ No newline at end of file +requests==2.20.0 \ No newline at end of file From 12ac7ac2667e9899f421f0d628e35ca492bbbc4e Mon Sep 17 00:00:00 2001 From: William Lam Date: Mon, 17 May 2021 16:12:14 -0700 Subject: [PATCH 42/56] feat: Add Knative PowerCLI vSphere Tagging Example Signed-off-by: William Lam --- docs/site/examples-knative.md | 8 + .../knative/powercli/kn-pcli-tag/Dockerfile | 7 + .../knative/powercli/kn-pcli-tag/README.md | 141 ++++++++++++++++++ .../powercli/kn-pcli-tag/function.yaml | 41 +++++ .../knative/powercli/kn-pcli-tag/handler.ps1 | 80 ++++++++++ .../powercli/kn-pcli-tag/tag_secret.json | 7 + .../kn-pcli-tag/test/docker-test-env-variable | 1 + .../kn-pcli-tag/test/send-cloudevent-test.ps1 | 16 ++ .../kn-pcli-tag/test/send-cloudevent-test.sh | 13 ++ .../kn-pcli-tag/test/test-payload.json | 40 +++++ 10 files changed, 354 insertions(+) create mode 100644 examples/knative/powercli/kn-pcli-tag/Dockerfile create mode 100644 examples/knative/powercli/kn-pcli-tag/README.md create mode 100644 examples/knative/powercli/kn-pcli-tag/function.yaml create mode 100644 examples/knative/powercli/kn-pcli-tag/handler.ps1 create mode 100644 examples/knative/powercli/kn-pcli-tag/tag_secret.json create mode 100644 examples/knative/powercli/kn-pcli-tag/test/docker-test-env-variable create mode 100644 examples/knative/powercli/kn-pcli-tag/test/send-cloudevent-test.ps1 create mode 100755 examples/knative/powercli/kn-pcli-tag/test/send-cloudevent-test.sh create mode 100644 examples/knative/powercli/kn-pcli-tag/test/test-payload.json diff --git a/docs/site/examples-knative.md b/docs/site/examples-knative.md index de70444e..fabbf483 100644 --- a/docs/site/examples-knative.md +++ b/docs/site/examples-knative.md @@ -36,6 +36,14 @@ examples: links: - language: powershell url: "/tree/master/examples/knative/powershell/kn-ps-email" + - title: vSphere Tagging + usecases: + - item: automation + id: kn-pcli-tag-function + description: Automatically tag a VM upon a vCenter event (ex. a VM can be tagged during a poweron event). + links: + - language: powercli + url: "/tree/master/examples/knative/powercli/kn-pcli-tag" --- diff --git a/examples/knative/powercli/kn-pcli-tag/Dockerfile b/examples/knative/powercli/kn-pcli-tag/Dockerfile new file mode 100644 index 00000000..da3242a6 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-tag/Dockerfile @@ -0,0 +1,7 @@ +FROM projects.registry.vmware.com/veba/ce-pcli-base:1.0 +ENV TERM linux +ENV PORT 8080 + +COPY handler.ps1 handler.ps1 + +CMD ["pwsh","./server.ps1"] \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-tag/README.md b/examples/knative/powercli/kn-pcli-tag/README.md new file mode 100644 index 00000000..814ceaad --- /dev/null +++ b/examples/knative/powercli/kn-pcli-tag/README.md @@ -0,0 +1,141 @@ +# kn-pcli-tag +Example Knative PowerCLI function for applying a vSphere Tag when a Virtual Machine powers on. + +# Step 1 - Build + +Create the container image locally to test your function logic. + +``` +docker build -t /kn-pcli-tag:1.0 . +``` + +# Step 2 - Test + +Verify the container image works by executing it locally. + +Change into the `test` directory +```console +cd test +``` + +Update the following variable names within the `docker-test-env-variable` file + +* VCENTER_SERVER - IP Address or FQDN of the vCenter Server to connect to for vSphere Tagging +* VCENTER_USERNAME - vCenter account with permission to apply vSphere Tagging +* VCENTER_PASSWORD - vCenter credentials to account with permission to apply vSphere Tagging +* VCENTER_TAG_NAME - Name of the vSphere Tag +* VCENTER_CERTIFCATE_ACTION - Set-PowerCLIConfiguration Action to configure when connection fails due to certificate error, default is Fail. (Possible values: Fail, Ignore or Warn) + + +Start the container image by running the following command: + +```console +docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 /kn-pcli-tag:1.0 +``` + +In a separate terminal, run either `send-cloudevent-test.ps1` (PowerShell Script) or `send-cloudevent-test.sh` (Bash Script) to simulate a CloudEvent payload being sent to the local container image + +```console +Testing Function ... +See docker container console for output + +# Output from docker container console +05/26/2021 13:46:44 - PowerShell HTTP server start listening on 'http://*:8080/' +05/26/2021 13:46:44 - Processing Init + +05/26/2021 13:46:44 - Configuring PowerCLI Configuration Settings + +05/26/2021 13:46:44 - Connecting to vCenter Server vcsa.primp-industries.local + +05/26/2021 13:47:19 - Successfully connected to vcsa.primp-industries.local + +05/26/2021 13:47:19 - Init Processing Completed + +05/26/2021 13:47:32 - Processing Handler + +05/26/2021 13:47:32 - Start CloudEvent Decode + +05/26/2021 13:47:32 - CloudEvent Decode Complete + +DEBUG: K8s Secrets: +{"VCENTER_SERVER":"vcsa.primp-industries.local","VCENTER_USERNAME":"administrator@vsphere.local","VCENTER_PASSWORD":"****","VCENTER_TAG_NAME":"Demo"} + +DEBUG: CloudEventData + +Name Value +---- ----- +Key 2816789 +Vm {Vm, Name} +Host {Host, Name} +Template False +CreatedTime 05/17/2021 21:39:03 +Net +Ds +Datacenter {Datacenter, Name} +ChainId 2816787 +UserName VSPHERE.LOCAL\Administrator +FullFormattedMessage DRS powered on K8s-User-Group-Test on 192.168.30.5 in Primp-Datacenter +ChangeTag +ComputeResource {ComputeResource, Name} +Dvs + + +05/26/2021 13:47:32 - Applying vSphere Tag "Demo" to K8s-User-Group-Test ... + +05/26/2021 13:47:42 - vSphere Tag Operation complete ... + +05/26/2021 13:47:42 - Handler Processing Completed +``` + +# Step 3 - Deploy + +> **Note:** The following steps assume a working Knative environment using the +`default` Rabbit `broker`. The Knative `service` and `trigger` will be installed in the +`vmware-functions` Kubernetes namespace, assuming that the `broker` is also available there. + +Push your container image to an accessible registry such as Docker once you're done developing and testing your function logic. + +```console +docker push /kn-pcli-tag:1.0 +``` + +Update the `tag_secret.json` file with your vCenter Server credentials and configurations and then create the kubernetes secret which can then be accessed from within the function by using the environment variable named called `TAG_SECRET`. + +```console +# create secret + +kubectl -n vmware-functions create secret generic tag-secret --from-file=TAG_SECRET=tag_secret.json + +# update label for secret to show up in VEBA UI +kubectl -n vmware-functions label secret tag-secret app=veba-ui +``` + +Edit the `function.yaml` file with the name of the container image from Step 1 if you made any changes. If not, the default VMware container image will suffice. By default, the function deployment will filter on the `DrsVmPoweredOnEvent` vCenter Server Event. If you wish to change this, update the `subject` field within `function.yaml` to the desired event type. + + +Deploy the function to the VMware Event Broker Appliance (VEBA). + +```console +# deploy function + +kubectl -n vmware-functions apply -f function.yaml +``` + +For testing purposes, the `function.yaml` contains the following annotations, which will ensure the Knative Service Pod will always run **exactly** one instance for debugging purposes. Functions deployed through through the VMware Event Broker Appliance UI defaults to scale to 0, which means the pods will only run when it is triggered by an vCenter Event. + +```yaml +annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" +``` + +# Step 4 - Undeploy + +```console +# undeploy function + +kubectl -n vmware-functions delete -f function.yaml + +# delete secret +kubectl -n vmware-functions delete secret tag-secret +``` \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-tag/function.yaml b/examples/knative/powercli/kn-pcli-tag/function.yaml new file mode 100644 index 00000000..b33f90bf --- /dev/null +++ b/examples/knative/powercli/kn-pcli-tag/function.yaml @@ -0,0 +1,41 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-pcli-tag + labels: + app: veba-ui + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" +spec: + template: + metadata: + spec: + containers: + - image: projects.registry.vmware.com/veba/kn-pcli-tag:1.0 + envFrom: + - secretRef: + name: tag-secret + env: + - name: FUNCTION_DEBUG + value: "false" +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-pcli-tag-trigger + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + subject: DrsVmPoweredOnEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-pcli-tag \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-tag/handler.ps1 b/examples/knative/powercli/kn-pcli-tag/handler.ps1 new file mode 100644 index 00000000..b8a6afdb --- /dev/null +++ b/examples/knative/powercli/kn-pcli-tag/handler.ps1 @@ -0,0 +1,80 @@ +Function Process-Init { + Write-Host "$(Get-Date) - Processing Init`n" + + try { + $jsonSecrets = ${env:TAG_SECRET} | ConvertFrom-Json + } catch { + throw "`nK8s secrets `$env:TAG does not look to be defined" + } + + # Extract all tag secrets for ease of use in function + $VCENTER_SERVER = ${jsonSecrets}.VCENTER_SERVER + $VCENTER_USERNAME = ${jsonSecrets}.VCENTER_USERNAME + $VCENTER_PASSWORD = ${jsonSecrets}.VCENTER_PASSWORD + $VCENTER_CERTIFCATE_ACTION = ${jsonSecrets}.VCENTER_CERTIFCATE_ACTION + + # Configure TLS 1.2/1.3 support as this is required for latest vSphere release + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls13 + + Write-Host "$(Get-Date) - Configuring PowerCLI Configuration Settings`n" + Set-PowerCLIConfiguration -InvalidCertificateAction:${VCENTER_CERTIFCATE_ACTION} -ParticipateInCeip:$true -Confirm:$false | Out-Null + + Write-Host "$(Get-Date) - Connecting to vCenter Server $VCENTER_SERVER`n" + Connect-VIServer -Server $VCENTER_SERVER -User $VCENTER_USERNAME -Password $VCENTER_PASSWORD | Out-Null + + Write-Host "$(Get-Date) - Successfully connected to $VCENTER_SERVER`n" + + Write-Host "$(Get-Date) - Init Processing Completed`n" +} + +Function Process-Shutdown { + Write-Host "$(Get-Date) - Processing Shutdown`n" + + Write-Host "$(Get-Date) - Disconnecting from to vCenter Server`n" + + try { + Disconnect-VIServer * -Confirm:$false | Out-Null + } catch { + Write-Error "$(Get-Date) - Failed to Disconnect from vCenter Server" + } + + Write-Host "$(Get-Date) - Shutdown Processing Completed`n" +} + +Function Process-Handler { + param( + [Parameter(Position=0,Mandatory=$true)][CloudNative.CloudEvents.CloudEvent]$CloudEvent + ) + + # Decode CloudEvent + try { + $cloudEventData = $cloudEvent | Read-CloudEventJsonData -Depth 10 + } catch { + throw "`nPayload must be JSON encoded" + } + + try { + $jsonSecrets = ${env:TAG_SECRET} | ConvertFrom-Json + } catch { + throw "`nK8s secrets `$env:TAG does not look to be defined" + } + + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: K8s Secrets:`n${env:TAG_SECRET}`n" + + Write-Host "$(Get-Date) - DEBUG: CloudEventData`n $(${cloudEventData} | Out-String)`n" + } + + # Extract all tag secrets for ease of use in function + $VCENTER_TAG_NAME = ${jsonSecrets}.VCENTER_TAG_NAME + + # Extract VM Name from event + $vmName = $cloudEventData.Vm.Name + + Write-Host "$(Get-Date) - Applying vSphere Tag `"$VCENTER_TAG_NAME`" to $vmName ...`n" + try { + Get-VM $vmName | New-TagAssignment -Tag (Get-Tag -Name $VCENTER_TAG_NAME) -Confirm:$false + } catch { + throw "`nFailed to assign vSphere Tag" + } +} diff --git a/examples/knative/powercli/kn-pcli-tag/tag_secret.json b/examples/knative/powercli/kn-pcli-tag/tag_secret.json new file mode 100644 index 00000000..b3f47cbc --- /dev/null +++ b/examples/knative/powercli/kn-pcli-tag/tag_secret.json @@ -0,0 +1,7 @@ +{ + "VCENTER_SERVER": "FILL-ME-IN", + "VCENTER_USRNAME" : "FILL-ME-IN", + "VCENTER_PASSWWORD" : "FILL-ME-IN", + "VCENTER_TAG_NAME" : "FILL-ME-IN", + "VCENTER_CERTIFCATE_ACTION" : "FILL-ME-IN" +} \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-tag/test/docker-test-env-variable b/examples/knative/powercli/kn-pcli-tag/test/docker-test-env-variable new file mode 100644 index 00000000..3e928ad7 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-tag/test/docker-test-env-variable @@ -0,0 +1 @@ +TAG_SECRET={"VCENTER_SERVER":"FILL-ME-IN","VCENTER_USERNAME":"FILL-ME-IN","VCENTER_PASSWORD":"FILL-ME-IN","VCENTER_TAG_NAME":"FILL-ME-IN","VCENTER_CERTIFCATE_ACTION":"Ignore"} \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-tag/test/send-cloudevent-test.ps1 b/examples/knative/powercli/kn-pcli-tag/test/send-cloudevent-test.ps1 new file mode 100644 index 00000000..870a8261 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-tag/test/send-cloudevent-test.ps1 @@ -0,0 +1,16 @@ + +$headers = @{ + "Content-Type" = "application/json"; + "ce-specversion" = "1.0"; + "ce-id" = "id-123"; + "ce-source" = "source-123"; + "ce-type" = "com.vmware.event.router/event"; + "ce-subject" = "DrsVmPoweredOnEvent"; +} + +$body = Get-Content -Raw -Path "./test-payload.json" + +Write-Host "Testing Function ..." +Invoke-WebRequest -Uri http://localhost:8080 -Method POST -Headers $headers -Body $body + +Write-host "See docker container console for output" \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-tag/test/send-cloudevent-test.sh b/examples/knative/powercli/kn-pcli-tag/test/send-cloudevent-test.sh new file mode 100755 index 00000000..57e71c05 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-tag/test/send-cloudevent-test.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "Testing Function ..." +curl -d@test-payload.json \ + -H "Content-Type: application/json" \ + -H 'ce-specversion: 1.0' \ + -H 'ce-id: d70079f9-fddd-4b7f-aa76-1193f28b0611' \ + -H 'ce-source: https://vcenter.local/sdk' \ + -H 'ce-type: com.vmware.event.router/event' \ + -H 'ce-subject: DrsVmPoweredOnEvent' \ + -X POST localhost:8080 + +echo "See docker container console for output" diff --git a/examples/knative/powercli/kn-pcli-tag/test/test-payload.json b/examples/knative/powercli/kn-pcli-tag/test/test-payload.json new file mode 100644 index 00000000..7517522e --- /dev/null +++ b/examples/knative/powercli/kn-pcli-tag/test/test-payload.json @@ -0,0 +1,40 @@ +{ + "Key": 2816789, + "ChainId": 2816787, + "CreatedTime": "2021-05-17T21:39:03.974Z", + "UserName": "VSPHERE.LOCAL\\Administrator", + "Datacenter": { + "Name": "Primp-Datacenter", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-3" + } + }, + "ComputeResource": { + "Name": "Supermicro-Cluster", + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c8" + } + }, + "Host": { + "Name": "192.168.30.5", + "Host": { + "Type": "HostSystem", + "Value": "host-11" + } + }, + "Vm": { + "Name": "K8s-User-Group-Test", + "Vm": { + "Type": "VirtualMachine", + "Value": "vm-11099" + } + }, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "DRS powered on K8s-User-Group-Test on 192.168.30.5 in Primp-Datacenter", + "ChangeTag": "", + "Template": false + } \ No newline at end of file From 2fcac13e9d2a1a15c1aed8af0660ca143d077797 Mon Sep 17 00:00:00 2001 From: William Lam Date: Mon, 17 May 2021 16:03:41 -0700 Subject: [PATCH 43/56] fix: Update Knative PS examples for consistency Signed-off-by: William Lam --- .../knative/powershell/kn-ps-echo/Dockerfile | 14 +- .../knative/powershell/kn-ps-echo/handler.ps1 | 33 +++-- .../knative/powershell/kn-ps-echo/server.ps1 | 124 ------------------ .../knative/powershell/kn-ps-echo/test.sh | 6 - .../test/send-cloudevent-test.ps1} | 10 +- .../kn-ps-echo/test/send-cloudevent-test.sh | 13 ++ .../kn-ps-echo/test/test-payload.json | 40 ++++++ .../knative/powershell/kn-ps-email/Dockerfile | 17 +-- .../knative/powershell/kn-ps-email/README.md | 12 +- .../powershell/kn-ps-email/function.yaml | 8 ++ .../powershell/kn-ps-email/handler.ps1 | 42 +++--- .../knative/powershell/kn-ps-email/server.ps1 | 124 ------------------ .../kn-ps-email/test/send-cloudevent-test.ps1 | 4 +- .../kn-ps-email/test/send-cloudevent-test.sh | 11 +- .../knative/powershell/kn-ps-slack/Dockerfile | 14 +- .../knative/powershell/kn-ps-slack/README.md | 60 +++++---- .../powershell/kn-ps-slack/binary-payload | 40 ------ .../powershell/kn-ps-slack/function.yaml | 8 ++ .../powershell/kn-ps-slack/handler.ps1 | 44 ++++--- .../knative/powershell/kn-ps-slack/server.ps1 | 124 ------------------ .../kn-ps-slack/{secret => slack_secret.json} | 0 .../knative/powershell/kn-ps-slack/test.sh | 6 - .../kn-ps-slack/test/docker-test-env-variable | 1 + .../test/send-cloudevent-test.ps1} | 10 +- .../kn-ps-slack/test/send-cloudevent-test.sh | 13 ++ .../test/test-payload.json} | 0 26 files changed, 233 insertions(+), 545 deletions(-) delete mode 100644 examples/knative/powershell/kn-ps-echo/server.ps1 delete mode 100755 examples/knative/powershell/kn-ps-echo/test.sh rename examples/knative/powershell/{kn-ps-slack/test.ps1 => kn-ps-echo/test/send-cloudevent-test.ps1} (52%) create mode 100755 examples/knative/powershell/kn-ps-echo/test/send-cloudevent-test.sh create mode 100644 examples/knative/powershell/kn-ps-echo/test/test-payload.json delete mode 100755 examples/knative/powershell/kn-ps-email/server.ps1 delete mode 100644 examples/knative/powershell/kn-ps-slack/binary-payload delete mode 100755 examples/knative/powershell/kn-ps-slack/server.ps1 rename examples/knative/powershell/kn-ps-slack/{secret => slack_secret.json} (100%) delete mode 100755 examples/knative/powershell/kn-ps-slack/test.sh create mode 100644 examples/knative/powershell/kn-ps-slack/test/docker-test-env-variable rename examples/knative/powershell/{kn-ps-echo/test.ps1 => kn-ps-slack/test/send-cloudevent-test.ps1} (52%) create mode 100755 examples/knative/powershell/kn-ps-slack/test/send-cloudevent-test.sh rename examples/knative/powershell/{kn-ps-echo/binary-payload => kn-ps-slack/test/test-payload.json} (100%) diff --git a/examples/knative/powershell/kn-ps-echo/Dockerfile b/examples/knative/powershell/kn-ps-echo/Dockerfile index 119c43d7..5b1f663d 100644 --- a/examples/knative/powershell/kn-ps-echo/Dockerfile +++ b/examples/knative/powershell/kn-ps-echo/Dockerfile @@ -1,19 +1,7 @@ -FROM photon:3.0 +FROM projects.registry.vmware.com/veba/ce-ps-base:1.0 ENV TERM linux ENV PORT 8080 -# Set terminal. If we don't do this, weird readline things happen. -RUN echo "/usr/bin/pwsh" >> /etc/shells && \ - echo "/bin/pwsh" >> /etc/shells && \ - tdnf install -y powershell-7.0.3-2.ph3 unzip && \ - pwsh -c "Set-PSRepository -Name PSGallery -InstallationPolicy Trusted" && \ - find / -name "net45" | xargs rm -rf && \ - tdnf erase -y unzip && \ - tdnf clean all -RUN pwsh -Command 'Install-Module ThreadJob -Force -Confirm:$false' -RUN pwsh -Command 'Install-Module -Name CloudEvents.Sdk' - -COPY server.ps1 ./ COPY handler.ps1 handler.ps1 CMD ["pwsh","./server.ps1"] diff --git a/examples/knative/powershell/kn-ps-echo/handler.ps1 b/examples/knative/powershell/kn-ps-echo/handler.ps1 index 80ac7a37..47343ead 100644 --- a/examples/knative/powershell/kn-ps-echo/handler.ps1 +++ b/examples/knative/powershell/kn-ps-echo/handler.ps1 @@ -1,20 +1,33 @@ +Function Process-Init { + Write-Host "$(Get-Date) - Processing Init`n" + + Write-Host "$(Get-Date) - Init Processing Completed`n" +} + +Function Process-Shutdown { + Write-Host "$(Get-Date) - Processing Shutdown`n" + + Write-Host "$(Get-Date) - Shutdown Processing Completed`n" +} + Function Process-Handler { param( [Parameter(Position=0,Mandatory=$true)][CloudNative.CloudEvents.CloudEvent]$CloudEvent ) - Write-Host "Cloud Event" - Write-Host " Source: $($cloudEvent.Source)" - Write-Host " Type: $($cloudEvent.Type)" - Write-Host " Subject: $($cloudEvent.Subject)" - Write-Host " Id: $($cloudEvent.Id)" + Write-Host $(Get-Date) - "Cloud Event" + Write-Host $(Get-Date) - " Source: $($cloudEvent.Source)" + Write-Host $(Get-Date) - " Type: $($cloudEvent.Type)" + Write-Host $(Get-Date) - " Subject: $($cloudEvent.Subject)" + Write-Host $(Get-Date) - " Id: $($cloudEvent.Id)" # Decode CloudEvent - $cloudEventData = $cloudEvent | Read-CloudEventJsonData -ErrorAction SilentlyContinue -Depth 10 - if($cloudEventData -eq $null) { - $cloudEventData = $cloudEvent | Read-CloudEventData + try { + $cloudEventData = $cloudEvent | Read-CloudEventJsonData -Depth 10 + } catch { + throw "`nPayload must be JSON encoded" } - Write-Host "CloudEvent Data:" - Write-Host "$($cloudEventData | Out-String)" + Write-Host $(Get-Date) - "CloudEvent Data:" + Write-Host $(Get-Date) - "$($cloudEventData | Out-String)" } diff --git a/examples/knative/powershell/kn-ps-echo/server.ps1 b/examples/knative/powershell/kn-ps-echo/server.ps1 deleted file mode 100644 index a3fb666b..00000000 --- a/examples/knative/powershell/kn-ps-echo/server.ps1 +++ /dev/null @@ -1,124 +0,0 @@ -if(${env:PORT}) { - $url = "http://*:${env:PORT}/" - $localUrl = "http://localhost:${env:PORT}/" -} else { - $url = "http://*:8080/" - $localUrl = "http://localhost:8080/" -} - -$serverStopMessage = 'break-signal-e2db683c-b8ff-4c4f-8158-c44f734e2bf1' - -$backgroundServer = Start-ThreadJob { - param($url, $serverStopMessage) - - Import-Module 'Microsoft.PowerShell.Utility' - Import-Module CloudEvents.Sdk - - . ./handler.ps1 - - function Start-HttpCloudEventListener { - <# - .DESCRIPTION - Starts a HTTP Listener on specified Url - #> - - [CmdletBinding()] - param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $false, - ValueFromPipelineByPropertyName = $false)] - [ValidateNotNull()] - [string] - $Url - ) - - $listener = New-Object -Type 'System.Net.HttpListener' - $listener.AuthenticationSchemes = [System.Net.AuthenticationSchemes]::Anonymous - $listener.Prefixes.Add($Url) - - $cloudEvent = $null - - try { - $listener.Start() - $context = $listener.GetContext() - - try { - # Read Input Stream - $buffer = New-Object 'byte[]' -ArgumentList 1024 - $ms = New-Object 'IO.MemoryStream' - $read = 0 - while (($read = $context.Request.InputStream.Read($buffer, 0, 1024)) -gt 0) { - $ms.Write($buffer, 0, $read); - } - $bodyData = $ms.ToArray() - $ms.Dispose() - - # Read Headers - $headers = @{} - for($i =0; $i -lt $context.Request.Headers.Count; $i++) { - $headers[$context.Request.Headers.GetKey($i)] = $context.Request.Headers.GetValues($i) - } - - $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::OK) - } catch { - Write-Error "HTTP Request Processing Error: $($_.Exception.ToString())" - $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::InternalServerError) - } finally { - $context.Response.Close(); - } - - $cloudEvent = ConvertFrom-HttpMessage -Headers $headers -Body $bodyData - - # function result - ([System.Text.Encoding]::UTF8.GetString($bodyData) -eq $serverStopMessage) - - } catch { - Write-Error "CloudEvent Processing Error: $($_.Exception.ToString())" - } finally { - $listener.Stop() - } - - if ( $cloudEvent -ne $null ) { - try { - Process-Handler -CloudEvent $cloudEvent | Out-Null - }catch { - Write-Error "Handler Processing Error: $($_.Exception.ToString())" - } - } - } - - - - while($true) { - $breakSignal = Start-HttpCloudEventListener -Url $url - if ($breakSignal) { - Write-Host "Server stop requested" - break; - } - } -} -ArgumentList $url, $serverStopMessage - -$killEvent = new-object 'System.Threading.AutoResetEvent' -ArgumentList $false - -$serverTerminateJob = Start-ThreadJob { -param($killEvent, $url, $serverStopMessage) - $killEvent.WaitOne() | Out-Null - Invoke-WebRequest -Uri $url -Body $serverStopMessage | Out-Null -} -ArgumentList $killEvent, $localUrl, $serverStopMessage - -try { - Write-Host "Server start listening on '$url'" - $running = $true - while($running) { - Start-Sleep -Milliseconds 100 - $running = ($backgroundServer.State -eq 'Running') - $backgroundServer = $backgroundServer | Get-Job - $backgroundServer | Receive-Job - } -} finally { - Write-Host "PowerShell stop requested. Wait server to stop" - $killEvent.Set() | Out-Null - Get-Job | Wait-Job | Receive-Job - Write-Host "Server is stopped" -} \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-echo/test.sh b/examples/knative/powershell/kn-ps-echo/test.sh deleted file mode 100755 index 6aa16a74..00000000 --- a/examples/knative/powershell/kn-ps-echo/test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -echo "Testing Function ..." -curl -d@binary-payload -H "Content-Type: application/json" -H 'ce-specversion: 1.0' -H 'ce-id: id-123' -H 'ce-source: source-123' -H 'ce-type: binary' -H 'ce-subject: subject-123' -X POST localhost:8080 - -echo "See docker container console for output" diff --git a/examples/knative/powershell/kn-ps-slack/test.ps1 b/examples/knative/powershell/kn-ps-echo/test/send-cloudevent-test.ps1 similarity index 52% rename from examples/knative/powershell/kn-ps-slack/test.ps1 rename to examples/knative/powershell/kn-ps-echo/test/send-cloudevent-test.ps1 index 1dbc977d..22224d8d 100644 --- a/examples/knative/powershell/kn-ps-slack/test.ps1 +++ b/examples/knative/powershell/kn-ps-echo/test/send-cloudevent-test.ps1 @@ -2,13 +2,13 @@ $headers = @{ "Content-Type" = "application/json"; "ce-specversion" = "1.0"; - "ce-id" = "id-123"; - "ce-source" = "source-123"; - "ce-type" = "binary"; - "ce-subject" = "subject-123"; + "ce-id" = "d70079f9-fddd-4b7f-aa76-1193f28b0611"; + "ce-source" = "https://vcenter.local/sdk"; + "ce-type" = "com.vmware.event.router/event"; + "ce-subject" = "VmPoweredOffEvent"; } -$body = Get-Content -Raw -Path "./binary-payload" +$body = Get-Content -Raw -Path "./test-payload.json" Write-Host "Testing Function ..." Invoke-WebRequest -Uri http://localhost:8080 -Method POST -Headers $headers -Body $body diff --git a/examples/knative/powershell/kn-ps-echo/test/send-cloudevent-test.sh b/examples/knative/powershell/kn-ps-echo/test/send-cloudevent-test.sh new file mode 100755 index 00000000..8ad485bc --- /dev/null +++ b/examples/knative/powershell/kn-ps-echo/test/send-cloudevent-test.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "Testing Function ..." +curl -d@test-payload.json \ + -H "Content-Type: application/json" \ + -H 'ce-specversion: 1.0' \ + -H 'ce-id: d70079f9-fddd-4b7f-aa76-1193f28b0611' \ + -H 'ce-source: https://vcenter.local/sdk' \ + -H 'ce-type: com.vmware.event.router/event' \ + -H 'ce-subject: VmPoweredOffEvent' \ + -X POST localhost:8080 + +echo "See docker container console for output" diff --git a/examples/knative/powershell/kn-ps-echo/test/test-payload.json b/examples/knative/powershell/kn-ps-echo/test/test-payload.json new file mode 100644 index 00000000..06592223 --- /dev/null +++ b/examples/knative/powershell/kn-ps-echo/test/test-payload.json @@ -0,0 +1,40 @@ +{ + "Key": 2817273, + "ChainId": 2817260, + "CreatedTime": "2021-05-17T22:01:36.496Z", + "UserName": "VSPHERE.LOCAL\\Administrator", + "Datacenter": { + "Name": "Primp-Datacenter", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-3" + } + }, + "ComputeResource": { + "Name": "Supermicro-Cluster", + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c8" + } + }, + "Host": { + "Name": "192.168.30.5", + "Host": { + "Type": "HostSystem", + "Value": "host-11" + } + }, + "Vm": { + "Name": "K8s-User-Group-Test", + "Vm": { + "Type": "VirtualMachine", + "Value": "vm-11099" + } + }, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "K8s-User-Group-Test on 192.168.30.5 in Primp-Datacenter is powered off", + "ChangeTag": "", + "Template": false + } \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-email/Dockerfile b/examples/knative/powershell/kn-ps-email/Dockerfile index 79cf884d..a82b5709 100644 --- a/examples/knative/powershell/kn-ps-email/Dockerfile +++ b/examples/knative/powershell/kn-ps-email/Dockerfile @@ -1,20 +1,11 @@ -FROM photon:3.0 +FROM projects.registry.vmware.com/veba/ce-ps-base:1.0 ENV TERM linux ENV PORT 8080 -# Set terminal. If we don't do this, weird readline things happen. -RUN echo "/usr/bin/pwsh" >> /etc/shells && \ - echo "/bin/pwsh" >> /etc/shells && \ - tdnf install -y powershell-7.0.3-2.ph3 unzip && \ - pwsh -c "Set-PSRepository -Name PSGallery -InstallationPolicy Trusted" && \ - find / -name "net45" | xargs rm -rf && \ - tdnf erase -y unzip && \ - tdnf clean all -RUN pwsh -c 'Install-Module ThreadJob -Force -Confirm:$false -RequiredVersion 2.0.3' && \ - pwsh -c 'Install-Module -Name CloudEvents.Sdk -RequiredVersion 0.2.0' && \ - pwsh -c 'Install-Module -Name Send-MailKitMessage -RequiredVersion 3.1.0' +ARG MAILKIT_VERSION="3.1.0" + +RUN pwsh -c "Install-Module -Name Send-MailKitMessage -RequiredVersion ${MAILKIT_VERSION}" -COPY server.ps1 ./ COPY handler.ps1 handler.ps1 CMD ["pwsh","./server.ps1"] \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-email/README.md b/examples/knative/powershell/kn-ps-email/README.md index 4a3c57f0..60576e3a 100644 --- a/examples/knative/powershell/kn-ps-email/README.md +++ b/examples/knative/powershell/kn-ps-email/README.md @@ -1,5 +1,5 @@ # kn-ps-email -Example Knative PowerShell function that uses [Send-MailKitMessage](https://www.powershellgallery.com/packages/Send-MailKitMessage) to send email notification. +Example Knative PowerShell function that uses [Send-MailKitMessage](https://www.powershellgallery.com/packages/Send-MailKitMessage) to send email notification when a Virtual Machine is deleted. ![](screenshots/screenshot-1.png) @@ -110,6 +110,9 @@ Update the `email_secret.json` file with your email configurations and then crea # create secret kubectl -n vmware-functions create secret generic email-secret --from-file=EMAIL_SECRET=email_secret.json + +# update label for secret to show up in VEBA UI +kubectl -n vmware-functions label secret email-secret app=veba-ui ``` Edit the `function.yaml` file with the name of the container image from Step 1 if you made any changes. If not, the default VMware container image will suffice. By default, the function deployment will filter on the `VmRemovedEvent` vCenter Server Event. If you wish to change this, update the `subject` field within `function.yaml` to the desired event type. @@ -118,7 +121,7 @@ Edit the `function.yaml` file with the name of the container image from Step 1 i Deploy the function to the VMware Event Broker Appliance (VEBA). ```console -# Deploy function +# deploy function kubectl -n vmware-functions apply -f function.yaml ``` @@ -134,7 +137,10 @@ annotations: # Step 4 - Undeploy ```console -# Undeploy function +# undeploy function kubectl -n vmware-functions delete -f function.yaml + +# delete secret +kubectl -n vmware-functions delete secret email-secret ``` \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-email/function.yaml b/examples/knative/powershell/kn-ps-email/function.yaml index a32a0f3c..1b638fec 100644 --- a/examples/knative/powershell/kn-ps-email/function.yaml +++ b/examples/knative/powershell/kn-ps-email/function.yaml @@ -4,6 +4,11 @@ metadata: name: kn-ps-email labels: app: veba-ui + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" spec: template: metadata: @@ -13,6 +18,9 @@ spec: envFrom: - secretRef: name: email-secret + env: + - name: FUNCTION_DEBUG + value: "false" --- apiVersion: eventing.knative.dev/v1 kind: Trigger diff --git a/examples/knative/powershell/kn-ps-email/handler.ps1 b/examples/knative/powershell/kn-ps-email/handler.ps1 index a075ed6e..fac53350 100644 --- a/examples/knative/powershell/kn-ps-email/handler.ps1 +++ b/examples/knative/powershell/kn-ps-email/handler.ps1 @@ -1,25 +1,37 @@ +Function Process-Init { + Write-Host "$(Get-Date) - Processing Init`n" + + Write-Host "$(Get-Date) - Init Processing Completed`n" +} + +Function Process-Shutdown { + Write-Host "$(Get-Date) - Processing Shutdown`n" + + Write-Host "$(Get-Date) - Shutdown Processing Completed`n" +} + Function Process-Handler { param( [Parameter(Position=0,Mandatory=$true)][CloudNative.CloudEvents.CloudEvent]$CloudEvent ) # Decode CloudEvent - $cloudEventData = $cloudEvent | Read-CloudEventJsonData -ErrorAction SilentlyContinue -Depth 10 - if($cloudEventData -eq $null) { - $cloudEventData = $cloudEvent | Read-CloudEventData + try { + $cloudEventData = $cloudEvent | Read-CloudEventJsonData -Depth 10 + } catch { + throw "`nPayload must be JSON encoded" } - if(${env:FUNCTION_DEBUG} -eq "true") { - Write-Host "DEBUG: K8s Secrets:`n${env:EMAIL_SECRET}`n" - - Write-Host "DEBUG: CloudEventData`n $(${cloudEventData} | ConvertTo-Json)`n" + try { + $jsonSecrets = ${env:EMAIL_SECRET} | ConvertFrom-Json + } catch { + throw "`nK8s secrets `$env:EMAIL_SECRET does not look to be defined" } - if(${env:EMAIL_SECRET}) { - $jsonSecrets = ${env:EMAIL_SECRET} | ConvertFrom-Json - } else { - Write-Host "K8s secrets `$env:EMAIL_SECRET does not look to be defined" - break + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: K8s Secrets:`n${env:EMAIL_SECRET}`n" + + Write-Host "$(Get-Date) - DEBUG: CloudEventData`n $(${cloudEventData} | ConvertTo-Json)`n" } ### BEGIN BUSINESS LOGIC CODE ### @@ -55,7 +67,7 @@ Function Process-Handler { EmailBody=$EmailBody "@ - Write-Host "DEBUG: `n$debugOutput" + Write-Host "$(Get-Date) - DEBUG: `n$debugOutput" } # Secure Email @@ -73,7 +85,7 @@ Function Process-Handler { "Port" = $EMAIL_SERVER_PORT } - Write-Host "Sending Secure Email ..." + Write-Host "$(Get-Date) - Sending Secure Email ..." Send-MailkitMessage @EmailParams } else { @@ -87,7 +99,7 @@ Function Process-Handler { "Port" = $EMAIL_SERVER_PORT } - Write-Host "Sending Non-Secure Email ..." + Write-Host "$(Get-Date) - Sending Non-Secure Email ..." Send-MailkitMessage @EmailParams } diff --git a/examples/knative/powershell/kn-ps-email/server.ps1 b/examples/knative/powershell/kn-ps-email/server.ps1 deleted file mode 100755 index a3fb666b..00000000 --- a/examples/knative/powershell/kn-ps-email/server.ps1 +++ /dev/null @@ -1,124 +0,0 @@ -if(${env:PORT}) { - $url = "http://*:${env:PORT}/" - $localUrl = "http://localhost:${env:PORT}/" -} else { - $url = "http://*:8080/" - $localUrl = "http://localhost:8080/" -} - -$serverStopMessage = 'break-signal-e2db683c-b8ff-4c4f-8158-c44f734e2bf1' - -$backgroundServer = Start-ThreadJob { - param($url, $serverStopMessage) - - Import-Module 'Microsoft.PowerShell.Utility' - Import-Module CloudEvents.Sdk - - . ./handler.ps1 - - function Start-HttpCloudEventListener { - <# - .DESCRIPTION - Starts a HTTP Listener on specified Url - #> - - [CmdletBinding()] - param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $false, - ValueFromPipelineByPropertyName = $false)] - [ValidateNotNull()] - [string] - $Url - ) - - $listener = New-Object -Type 'System.Net.HttpListener' - $listener.AuthenticationSchemes = [System.Net.AuthenticationSchemes]::Anonymous - $listener.Prefixes.Add($Url) - - $cloudEvent = $null - - try { - $listener.Start() - $context = $listener.GetContext() - - try { - # Read Input Stream - $buffer = New-Object 'byte[]' -ArgumentList 1024 - $ms = New-Object 'IO.MemoryStream' - $read = 0 - while (($read = $context.Request.InputStream.Read($buffer, 0, 1024)) -gt 0) { - $ms.Write($buffer, 0, $read); - } - $bodyData = $ms.ToArray() - $ms.Dispose() - - # Read Headers - $headers = @{} - for($i =0; $i -lt $context.Request.Headers.Count; $i++) { - $headers[$context.Request.Headers.GetKey($i)] = $context.Request.Headers.GetValues($i) - } - - $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::OK) - } catch { - Write-Error "HTTP Request Processing Error: $($_.Exception.ToString())" - $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::InternalServerError) - } finally { - $context.Response.Close(); - } - - $cloudEvent = ConvertFrom-HttpMessage -Headers $headers -Body $bodyData - - # function result - ([System.Text.Encoding]::UTF8.GetString($bodyData) -eq $serverStopMessage) - - } catch { - Write-Error "CloudEvent Processing Error: $($_.Exception.ToString())" - } finally { - $listener.Stop() - } - - if ( $cloudEvent -ne $null ) { - try { - Process-Handler -CloudEvent $cloudEvent | Out-Null - }catch { - Write-Error "Handler Processing Error: $($_.Exception.ToString())" - } - } - } - - - - while($true) { - $breakSignal = Start-HttpCloudEventListener -Url $url - if ($breakSignal) { - Write-Host "Server stop requested" - break; - } - } -} -ArgumentList $url, $serverStopMessage - -$killEvent = new-object 'System.Threading.AutoResetEvent' -ArgumentList $false - -$serverTerminateJob = Start-ThreadJob { -param($killEvent, $url, $serverStopMessage) - $killEvent.WaitOne() | Out-Null - Invoke-WebRequest -Uri $url -Body $serverStopMessage | Out-Null -} -ArgumentList $killEvent, $localUrl, $serverStopMessage - -try { - Write-Host "Server start listening on '$url'" - $running = $true - while($running) { - Start-Sleep -Milliseconds 100 - $running = ($backgroundServer.State -eq 'Running') - $backgroundServer = $backgroundServer | Get-Job - $backgroundServer | Receive-Job - } -} finally { - Write-Host "PowerShell stop requested. Wait server to stop" - $killEvent.Set() | Out-Null - Get-Job | Wait-Job | Receive-Job - Write-Host "Server is stopped" -} \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.ps1 b/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.ps1 index f381369b..bf99fe78 100644 --- a/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.ps1 +++ b/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.ps1 @@ -2,8 +2,8 @@ $headers = @{ "Content-Type" = "application/json"; "ce-specversion" = "1.0"; - "ce-id" = "id-123"; - "ce-source" = "source-123"; + "ce-id" = "d70079f9-fddd-4b7f-aa76-1193f28b0611"; + "ce-source" = "https://vcenter.local/sdk"; "ce-type" = "com.vmware.event.router/event"; "ce-subject" = "VmRemovedEvent"; } diff --git a/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.sh b/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.sh index 6cbfabc1..b8ea838d 100755 --- a/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.sh +++ b/examples/knative/powershell/kn-ps-email/test/send-cloudevent-test.sh @@ -1,6 +1,13 @@ #!/bin/bash echo "Testing Function ..." -curl -d@test-payload.json -H "Content-Type: application/json" -H 'ce-specversion: 1.0' -H 'ce-id: id-123' -H 'ce-source: source-123' -H 'ce-type: com.vmware.event.router/event' -H 'ce-subject: VmRemovedEvent' -X POST localhost:8080 +curl -d@test-payload.json \ + -H "Content-Type: application/json" \ + -H 'ce-specversion: 1.0' \ + -H 'ce-id: d70079f9-fddd-4b7f-aa76-1193f28b0611' \ + -H 'ce-source: https://vcenter.local/sdk' \ + -H 'ce-type: com.vmware.event.router/event' \ + -H 'ce-subject: VmRemovedEvent' \ + -X POST localhost:8080 -echo "See docker container console for output" +echo "See docker container console for output" \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-slack/Dockerfile b/examples/knative/powershell/kn-ps-slack/Dockerfile index 4317ca91..07a2338d 100644 --- a/examples/knative/powershell/kn-ps-slack/Dockerfile +++ b/examples/knative/powershell/kn-ps-slack/Dockerfile @@ -1,19 +1,7 @@ -FROM photon:3.0 +FROM projects.registry.vmware.com/veba/ce-ps-base:1.0 ENV TERM linux ENV PORT 8080 -# Set terminal. If we don't do this, weird readline things happen. -RUN echo "/usr/bin/pwsh" >> /etc/shells && \ - echo "/bin/pwsh" >> /etc/shells && \ - tdnf install -y powershell-7.0.3-2.ph3 unzip && \ - pwsh -c "Set-PSRepository -Name PSGallery -InstallationPolicy Trusted" && \ - find / -name "net45" | xargs rm -rf && \ - tdnf erase -y unzip && \ - tdnf clean all -RUN pwsh -Command 'Install-Module ThreadJob -Force -Confirm:$false' -RUN pwsh -Command 'Install-Module -Name CloudEvents.Sdk' - -COPY server.ps1 ./ COPY handler.ps1 handler.ps1 CMD ["pwsh","./server.ps1"] \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-slack/README.md b/examples/knative/powershell/kn-ps-slack/README.md index 6904ed8d..72fa6568 100644 --- a/examples/knative/powershell/kn-ps-slack/README.md +++ b/examples/knative/powershell/kn-ps-slack/README.md @@ -1,37 +1,37 @@ # kn-ps-slack -Simple Knative powershell function for sending Slack webhook -[CloudEvents](https://github.com/cloudevents/). - -> **Note:** CloudEvents using structured or binary mode are supported. +Example Knative PowerShell function for sending to a Slack webhook when a Virtual Machine is powered off. # Step 1 - Build -Create the container image and optionally push to an external registry such as Docker. + +Create the container image locally to test your function logic. ``` docker build -t /kn-ps-slack:1.0 . -docker push /kn-ps-slack:1.0 ``` # Step 2 - Test Verify the container image works by executing it locally. -**Windows PowerShell:** - -```powershell -docker run -e FUNCTION_DEBUG=true -e PORT=8080 -e SLACK_SECRET="{'SLACK_WEBHOOK_URL': 'YOUR-WEBHOOK-URL'}" -it --rm -p 8080:8080 /kn-ps-slack:1.0 +Change into the `test` directory +```console +cd test ``` -**Windows WSL/Linux/MacOS:** -```bash -docker run -e FUNCTION_DEBUG=true -e PORT=8080 -e SLACK_SECRET='{"SLACK_WEBHOOK_URL": "YOUR-WEBHOOK-URL"}' -it --rm -p 8080:8080 /kn-ps-slack:1.0 -``` +Update the following variable names within the `docker-test-env-variable` file + +* SLACK_WEBHOOK_URL - Slack webhook URL -In a separate terminal, run either `test.ps1` (PowerShell Script) or `test.sh` (Bash Script) to simulate a CloudEvent payload being sent to the local container image +Start the container image by running the following command: +```console +docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 /kn-ps-slack:1.0 ``` +In a separate terminal, run either `send-cloudevent-test.ps1` (PowerShell Script) or `send-cloudevent-test.sh` (Bash Script) to simulate a CloudEvent payload being sent to the local container image + +```console Testing Function ... See docker container console for output @@ -57,15 +57,24 @@ RelationLink : {} # Step 3 - Deploy > **Note:** The following steps assume a working Knative environment using the -`default` Rabbit broker. The Knative `service` and `trigger` will be installed in the -`vmware-functions` Kubernetes namespace, assuming that the broker is also available there. +`default` Rabbit `broker`. The Knative `service` and `trigger` will be installed in the +`vmware-functions` Kubernetes namespace, assuming that the `broker` is also available there. -Update the `secret` file with your Slack webhook URL and then create the kubernetes secret which can then accessed from within the function by using the environmental variable named called `SLACK_SECRET`. +Push your container image to an accessible registry such as Docker once you're done developing and testing your function logic. -```bash +```console +docker push /kn-ps-slack:1.0 +``` + +Update the `slack_secret.json` file with your Slack webhook configurations and then create the kubernetes secret which can then be accessed from within the function by using the environment variable named called `SLACK_SECRET`. + +```console # create secret -kubectl -n vmware-functions create secret generic slack-secret --from-file=SLACK_SECRET=secret +kubectl -n vmware-functions create secret generic slack-secret --from-file=SLACK_SECRET=slack_secret.json + +# update label for secret to show up in VEBA UI +kubectl -n vmware-functions label secret slack-secret app=veba-ui ``` Edit the `function.yaml` file with the name of the container image from Step 1 if you made any changes. If not, the default VMware container image will suffice. By default, the function deployment will filter on the `VmPoweredOffEvent` vCenter Server Event. If you wish to change this, update the `subject` field within `function.yaml` to the desired event type. @@ -73,8 +82,8 @@ Edit the `function.yaml` file with the name of the container image from Step 1 i Deploy the function to the VMware Event Broker Appliance (VEBA). -```bash -# Deploy function +```console +# deploy function kubectl -n vmware-functions apply -f function.yaml ``` @@ -89,8 +98,11 @@ annotations: # Step 4 - Undeploy -```bash -# Undeploy function +```console +# undeploy function kubectl -n vmware-functions delete -f function.yaml + +# delete secret +kubectl -n vmware-functions delete secret slack-secret ``` \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-slack/binary-payload b/examples/knative/powershell/kn-ps-slack/binary-payload deleted file mode 100644 index a6aad5d3..00000000 --- a/examples/knative/powershell/kn-ps-slack/binary-payload +++ /dev/null @@ -1,40 +0,0 @@ -{ - "Key": 607954, - "ChainId": 607952, - "CreatedTime": "2021-02-16T20:27:31.711999Z", - "UserName": "VSPHERE.LOCAL\\Administrator", - "Datacenter": { - "Name": "Primp-Datacenter", - "Datacenter": { - "Type": "Datacenter", - "Value": "datacenter-3" - } - }, - "ComputeResource": { - "Name": "Supermicro-Cluster", - "ComputeResource": { - "Type": "ClusterComputeResource", - "Value": "domain-c8" - } - }, - "Host": { - "Name": "192.168.30.5", - "Host": { - "Type": "HostSystem", - "Value": "host-11" - } - }, - "Vm": { - "Name": "Test", - "Vm": { - "Type": "VirtualMachine", - "Value": "vm-7114" - } - }, - "Ds": "None", - "Net": "None", - "Dvs": "None", - "FullFormattedMessage": "Test on 192.168.30.5 in Primp-Datacenter is powered off", - "ChangeTag": "", - "Template": "False" -} \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-slack/function.yaml b/examples/knative/powershell/kn-ps-slack/function.yaml index 85e172a6..77e085ee 100644 --- a/examples/knative/powershell/kn-ps-slack/function.yaml +++ b/examples/knative/powershell/kn-ps-slack/function.yaml @@ -4,6 +4,11 @@ metadata: name: kn-ps-slack labels: app: veba-ui + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" spec: template: metadata: @@ -13,6 +18,9 @@ spec: envFrom: - secretRef: name: slack-secret + env: + - name: FUNCTION_DEBUG + value: "false" --- apiVersion: eventing.knative.dev/v1 kind: Trigger diff --git a/examples/knative/powershell/kn-ps-slack/handler.ps1 b/examples/knative/powershell/kn-ps-slack/handler.ps1 index a8d8ccda..5c8e46f8 100644 --- a/examples/knative/powershell/kn-ps-slack/handler.ps1 +++ b/examples/knative/powershell/kn-ps-slack/handler.ps1 @@ -1,29 +1,41 @@ +Function Process-Init { + Write-Host "$(Get-Date) - Processing Init`n" + + Write-Host "$(Get-Date) - Init Processing Completed`n" +} + +Function Process-Shutdown { + Write-Host "$(Get-Date) - Processing Shutdown`n" + + Write-Host "$(Get-Date) - Shutdown Processing Completed`n" +} + Function Process-Handler { param( [Parameter(Position=0,Mandatory=$true)][CloudNative.CloudEvents.CloudEvent]$CloudEvent ) # Decode CloudEvent - $cloudEventData = $cloudEvent | Read-CloudEventJsonData -ErrorAction SilentlyContinue -Depth 10 - if($cloudEventData -eq $null) { - $cloudEventData = $cloudEvent | Read-CloudEventData + try { + $cloudEventData = $cloudEvent | Read-CloudEventJsonData -Depth 10 + } catch { + throw "`nPayload must be JSON encoded" } - if(${env:FUNCTION_DEBUG} -eq "true") { - Write-Host "DEBUG: K8s Secrets:`n${env:SLACK_SECRET}`n" - - Write-Host "DEBUG: CloudEventData`n $(${cloudEventData} | Out-String)`n" + try { + $jsonSecrets = ${env:SLACK_SECRET} | ConvertFrom-Json + } catch { + throw "`nK8s secrets `$env:SLACK_SECRET does not look to be defined" } - if(${env:SLACK_SECRET}) { - $jsonSecrets = ${env:SLACK_SECRET} | ConvertFrom-Json - } else { - Write-Host "K8s secrets `$env:SLACK_SECRET does not look to be defined" - break + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: K8s Secrets:`n${env:SLACK_SECRET}`n" + + Write-Host "$(Get-Date) - DEBUG: CloudEventData`n $(${cloudEventData} | Out-String)`n" } # Send VM changes - Write-Host "Detected change to $($cloudEvent.Subject) ..." + Write-Host "$(Get-Date) - Detected change to $($cloudEvent.Subject) ..." $payload = @{ attachments = @( @@ -53,11 +65,11 @@ Function Process-Handler { $body = $payload | ConvertTo-Json -Depth 5 if(${env:FUNCTION_DEBUG} -eq "true") { - Write-Host "DEBUG: `"$body`"" + Write-Host "$(Get-Date) - DEBUG: `"$body`"" } - Write-Host "Sending Webhook payload to Slack ..." + Write-Host "$(Get-Date) - Sending Webhook payload to Slack ..." $ProgressPreference = "SilentlyContinue" Invoke-WebRequest -Uri $(${jsonSecrets}.SLACK_WEBHOOK_URL) -Method POST -ContentType "application/json" -Body $body - Write-Host "Successfully sent Webhook ..." + Write-Host "$(Get-Date) - Successfully sent Webhook ..." } diff --git a/examples/knative/powershell/kn-ps-slack/server.ps1 b/examples/knative/powershell/kn-ps-slack/server.ps1 deleted file mode 100755 index a3fb666b..00000000 --- a/examples/knative/powershell/kn-ps-slack/server.ps1 +++ /dev/null @@ -1,124 +0,0 @@ -if(${env:PORT}) { - $url = "http://*:${env:PORT}/" - $localUrl = "http://localhost:${env:PORT}/" -} else { - $url = "http://*:8080/" - $localUrl = "http://localhost:8080/" -} - -$serverStopMessage = 'break-signal-e2db683c-b8ff-4c4f-8158-c44f734e2bf1' - -$backgroundServer = Start-ThreadJob { - param($url, $serverStopMessage) - - Import-Module 'Microsoft.PowerShell.Utility' - Import-Module CloudEvents.Sdk - - . ./handler.ps1 - - function Start-HttpCloudEventListener { - <# - .DESCRIPTION - Starts a HTTP Listener on specified Url - #> - - [CmdletBinding()] - param( - [Parameter( - Mandatory = $true, - ValueFromPipeline = $false, - ValueFromPipelineByPropertyName = $false)] - [ValidateNotNull()] - [string] - $Url - ) - - $listener = New-Object -Type 'System.Net.HttpListener' - $listener.AuthenticationSchemes = [System.Net.AuthenticationSchemes]::Anonymous - $listener.Prefixes.Add($Url) - - $cloudEvent = $null - - try { - $listener.Start() - $context = $listener.GetContext() - - try { - # Read Input Stream - $buffer = New-Object 'byte[]' -ArgumentList 1024 - $ms = New-Object 'IO.MemoryStream' - $read = 0 - while (($read = $context.Request.InputStream.Read($buffer, 0, 1024)) -gt 0) { - $ms.Write($buffer, 0, $read); - } - $bodyData = $ms.ToArray() - $ms.Dispose() - - # Read Headers - $headers = @{} - for($i =0; $i -lt $context.Request.Headers.Count; $i++) { - $headers[$context.Request.Headers.GetKey($i)] = $context.Request.Headers.GetValues($i) - } - - $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::OK) - } catch { - Write-Error "HTTP Request Processing Error: $($_.Exception.ToString())" - $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::InternalServerError) - } finally { - $context.Response.Close(); - } - - $cloudEvent = ConvertFrom-HttpMessage -Headers $headers -Body $bodyData - - # function result - ([System.Text.Encoding]::UTF8.GetString($bodyData) -eq $serverStopMessage) - - } catch { - Write-Error "CloudEvent Processing Error: $($_.Exception.ToString())" - } finally { - $listener.Stop() - } - - if ( $cloudEvent -ne $null ) { - try { - Process-Handler -CloudEvent $cloudEvent | Out-Null - }catch { - Write-Error "Handler Processing Error: $($_.Exception.ToString())" - } - } - } - - - - while($true) { - $breakSignal = Start-HttpCloudEventListener -Url $url - if ($breakSignal) { - Write-Host "Server stop requested" - break; - } - } -} -ArgumentList $url, $serverStopMessage - -$killEvent = new-object 'System.Threading.AutoResetEvent' -ArgumentList $false - -$serverTerminateJob = Start-ThreadJob { -param($killEvent, $url, $serverStopMessage) - $killEvent.WaitOne() | Out-Null - Invoke-WebRequest -Uri $url -Body $serverStopMessage | Out-Null -} -ArgumentList $killEvent, $localUrl, $serverStopMessage - -try { - Write-Host "Server start listening on '$url'" - $running = $true - while($running) { - Start-Sleep -Milliseconds 100 - $running = ($backgroundServer.State -eq 'Running') - $backgroundServer = $backgroundServer | Get-Job - $backgroundServer | Receive-Job - } -} finally { - Write-Host "PowerShell stop requested. Wait server to stop" - $killEvent.Set() | Out-Null - Get-Job | Wait-Job | Receive-Job - Write-Host "Server is stopped" -} \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-slack/secret b/examples/knative/powershell/kn-ps-slack/slack_secret.json similarity index 100% rename from examples/knative/powershell/kn-ps-slack/secret rename to examples/knative/powershell/kn-ps-slack/slack_secret.json diff --git a/examples/knative/powershell/kn-ps-slack/test.sh b/examples/knative/powershell/kn-ps-slack/test.sh deleted file mode 100755 index 6aa16a74..00000000 --- a/examples/knative/powershell/kn-ps-slack/test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -echo "Testing Function ..." -curl -d@binary-payload -H "Content-Type: application/json" -H 'ce-specversion: 1.0' -H 'ce-id: id-123' -H 'ce-source: source-123' -H 'ce-type: binary' -H 'ce-subject: subject-123' -X POST localhost:8080 - -echo "See docker container console for output" diff --git a/examples/knative/powershell/kn-ps-slack/test/docker-test-env-variable b/examples/knative/powershell/kn-ps-slack/test/docker-test-env-variable new file mode 100644 index 00000000..d2a495a7 --- /dev/null +++ b/examples/knative/powershell/kn-ps-slack/test/docker-test-env-variable @@ -0,0 +1 @@ +SLACK_SECRET={"SLACK_WEBHOOK_URL":"YOUR-WEBHOOK-URL"} \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-echo/test.ps1 b/examples/knative/powershell/kn-ps-slack/test/send-cloudevent-test.ps1 similarity index 52% rename from examples/knative/powershell/kn-ps-echo/test.ps1 rename to examples/knative/powershell/kn-ps-slack/test/send-cloudevent-test.ps1 index 1dbc977d..22224d8d 100644 --- a/examples/knative/powershell/kn-ps-echo/test.ps1 +++ b/examples/knative/powershell/kn-ps-slack/test/send-cloudevent-test.ps1 @@ -2,13 +2,13 @@ $headers = @{ "Content-Type" = "application/json"; "ce-specversion" = "1.0"; - "ce-id" = "id-123"; - "ce-source" = "source-123"; - "ce-type" = "binary"; - "ce-subject" = "subject-123"; + "ce-id" = "d70079f9-fddd-4b7f-aa76-1193f28b0611"; + "ce-source" = "https://vcenter.local/sdk"; + "ce-type" = "com.vmware.event.router/event"; + "ce-subject" = "VmPoweredOffEvent"; } -$body = Get-Content -Raw -Path "./binary-payload" +$body = Get-Content -Raw -Path "./test-payload.json" Write-Host "Testing Function ..." Invoke-WebRequest -Uri http://localhost:8080 -Method POST -Headers $headers -Body $body diff --git a/examples/knative/powershell/kn-ps-slack/test/send-cloudevent-test.sh b/examples/knative/powershell/kn-ps-slack/test/send-cloudevent-test.sh new file mode 100755 index 00000000..8ad485bc --- /dev/null +++ b/examples/knative/powershell/kn-ps-slack/test/send-cloudevent-test.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "Testing Function ..." +curl -d@test-payload.json \ + -H "Content-Type: application/json" \ + -H 'ce-specversion: 1.0' \ + -H 'ce-id: d70079f9-fddd-4b7f-aa76-1193f28b0611' \ + -H 'ce-source: https://vcenter.local/sdk' \ + -H 'ce-type: com.vmware.event.router/event' \ + -H 'ce-subject: VmPoweredOffEvent' \ + -X POST localhost:8080 + +echo "See docker container console for output" diff --git a/examples/knative/powershell/kn-ps-echo/binary-payload b/examples/knative/powershell/kn-ps-slack/test/test-payload.json similarity index 100% rename from examples/knative/powershell/kn-ps-echo/binary-payload rename to examples/knative/powershell/kn-ps-slack/test/test-payload.json From 509e763b0292099f955cda0faeecbbf579752d7a Mon Sep 17 00:00:00 2001 From: William Lam Date: Tue, 18 May 2021 16:10:42 -0700 Subject: [PATCH 44/56] feat: Add Knative Base PowerShell/PowerCLI Container Images Signed-off-by: William Lam --- examples/knative/templates/Dockerfile.pcli | 8 ++ examples/knative/templates/Dockerfile.ps | Bin 0 -> 1051 bytes examples/knative/templates/README.md | 27 ++++ examples/knative/templates/server.ps1 | 141 +++++++++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 examples/knative/templates/Dockerfile.pcli create mode 100644 examples/knative/templates/Dockerfile.ps create mode 100644 examples/knative/templates/README.md create mode 100755 examples/knative/templates/server.ps1 diff --git a/examples/knative/templates/Dockerfile.pcli b/examples/knative/templates/Dockerfile.pcli new file mode 100644 index 00000000..203ec340 --- /dev/null +++ b/examples/knative/templates/Dockerfile.pcli @@ -0,0 +1,8 @@ +FROM projects.registry.vmware.com/veba/ce-ps-base:1.0 + +ARG POWERCLI_VERSION="12.3.0.17860403" + +RUN pwsh -c "\$ProgressPreference = \"SilentlyContinue\"; Install-Module VMware.PowerCLI -RequiredVersion ${POWERCLI_VERSION}" && \ + pwsh -c 'Set-PowerCLIConfiguration -ParticipateInCEIP $true -confirm:$false' + +CMD ["pwsh","./server.ps1"] \ No newline at end of file diff --git a/examples/knative/templates/Dockerfile.ps b/examples/knative/templates/Dockerfile.ps new file mode 100644 index 0000000000000000000000000000000000000000..33391dfe20ba80364ce082d2255c7ac2d8f5ce31 GIT binary patch literal 1051 zcma)5+fLg+5PkPoj7$VCczq!^kU*u0fl!iIc1Tr~3OVaBUg7MzyBi>){Cn3Sl92$l zez3fAj_1srncab^5L)n~1L8S>7x-SsZ(C>abi4(=s3HwI-6N!(MF)F}+zWb#-2>{k zyJ;2#*!F$u_p_ki&r^Sq(!Ag9<(mBsF?acGgZ_~pLwZh39W??*rlJV8k<`_-m z8T7rrXIZE5IYiU%VVqoqS68=l^D&xEj_n@tN#OXTDh})oX*9TQ(5``B!?TMxJRM$6 zzcye%JhJ_1a5cRihVyVTOKy|lw}#^p!?G=F2NKkvQ7#!zO9G=D9uYFZ_q2I~E|}Uk z5hgQ`m}aI0tb_bcL6KG!a$?2T6F^!Bu-%oC?t*c*dQ^oCCnw;drmiZmEEVkT!i@#y z*^>WF)EUo#ai!C;1ZNGEctp9$=Cq#QoAv^Xt_!mOx`x)cj=&V-~0&Oq>- z$>q^aD^E*h0x|wxF^SpSbYmo-_55y%FaM$RTKAw7t1Ns#t`$kL`;W4JvITE7a>g@o z!Qr(PHb>p=*zgCQQfVeFm*B{}X?=O0F$qout`tJMgw_q-xy^VQ>Soq^tw@SHiAqHh zbCk%9EyrdOtTbhpYh#c$cLVJ`Fe~#(PIt_x6iVcpMD-pd^;X?hQsgEJsg&P{ZuW+j km~t>9wXDH3`T+{%1Ii7NxF$9p!cTj%sQWf?8`S>w7kRWnqyPW_ literal 0 HcmV?d00001 diff --git a/examples/knative/templates/README.md b/examples/knative/templates/README.md new file mode 100644 index 00000000..d3f392ba --- /dev/null +++ b/examples/knative/templates/README.md @@ -0,0 +1,27 @@ +# CloudEvent PowerShell and PowerCLI Base Container Images + +* [server.ps1](server.ps1) - PowerShell HTTP Listener for handling function invocation +* [Dockerfile.ps](Dockerfile.ps) - Dockerfile for Base PowerShell Image +* [Dockerfile.pcli](Dockerfile.pcli) - Dockerfile Base PowerCLI Image which builds on top of `Dockerfile.ps` + +# Run + +Pre-built base PowerShell Image: + +* projects.registry.vmware.com/veba/ce-ps-base:1.0 + +Pre-built base PowerCLI Image: + +* projects.registry.vmware.com/veba/ce-pcli-base:1.0 +# Build + +Build Base PowerShell Image +```console +docker build -t /ce-ps-base:1.0 -f Dockerfile.ps . +``` + +Build Base PowerCLI Image + +```console +docker build -t /ce-pcli-base:1.0 -f Dockerfile.pcli . +``` \ No newline at end of file diff --git a/examples/knative/templates/server.ps1 b/examples/knative/templates/server.ps1 new file mode 100755 index 00000000..e0f185ba --- /dev/null +++ b/examples/knative/templates/server.ps1 @@ -0,0 +1,141 @@ +if(${env:PORT}) { + $url = "http://*:${env:PORT}/" + $localUrl = "http://localhost:${env:PORT}/" +} else { + $url = "http://*:8080/" + $localUrl = "http://localhost:8080/" +} + +$serverStopMessage = 'break-signal-e2db683c-b8ff-4c4f-8158-c44f734e2bf1' + +. ./handler.ps1 + +$backgroundServer = Start-ThreadJob { + param($url, $serverStopMessage) + + Import-Module 'Microsoft.PowerShell.Utility' + Import-Module CloudEvents.Sdk + + . ./handler.ps1 + + function Start-HttpCloudEventListener { + <# + .DESCRIPTION + Starts a HTTP Listener on specified Url + #> + + [CmdletBinding()] + param( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $false, + ValueFromPipelineByPropertyName = $false)] + [ValidateNotNull()] + [string] + $Url + ) + + $listener = New-Object -Type 'System.Net.HttpListener' + $listener.AuthenticationSchemes = [System.Net.AuthenticationSchemes]::Anonymous + $listener.Prefixes.Add($Url) + + $cloudEvent = $null + + try { + $listener.Start() + $context = $listener.GetContext() + + try { + # Read Input Stream + $buffer = New-Object 'byte[]' -ArgumentList 1024 + $ms = New-Object 'IO.MemoryStream' + $read = 0 + while (($read = $context.Request.InputStream.Read($buffer, 0, 1024)) -gt 0) { + $ms.Write($buffer, 0, $read); + } + $bodyData = $ms.ToArray() + $ms.Dispose() + + # Read Headers + $headers = @{} + for($i =0; $i -lt $context.Request.Headers.Count; $i++) { + $headers[$context.Request.Headers.GetKey($i)] = $context.Request.Headers.GetValues($i) + } + + $cloudEvent = ConvertFrom-HttpMessage -Headers $headers -Body $bodyData + + # function result + ([System.Text.Encoding]::UTF8.GetString($bodyData) -eq $serverStopMessage) + + if ( $cloudEvent -ne $null ) { + try { + Process-Handler -CloudEvent $cloudEvent | Out-Null + $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::OK) + } catch { + Write-Error "$(Get-Date) - Handler Processing Error: $($_.Exception.ToString())" + $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::InternalServerError) + } + } + + } catch { + Write-Error "`n$(Get-Date) - HTTP Request Processing Error: $($_.Exception.ToString())" + $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::InternalServerError) + } finally { + $context.Response.Close(); + } + + } catch { + Write-Error "$(Get-Date) - Listener Processing Error: $($_.Exception.ToString())" + exit 1 + } finally { + $listener.Stop() + } + } + + # Runs Init function (defined in handler.ps1) which can be used to enable warm startup + try { + Process-Init + } catch { + Write-Error "$(Get-Date) - Init Processing Error: $($_.Exception.ToString())" + exit 1 + } + + while($true) { + $breakSignal = Start-HttpCloudEventListener -Url $url + if ($breakSignal) { + Write-Host "$(Get-Date) - PowerShell HTTP server stop requested" + break; + } + } +} -ArgumentList $url, $serverStopMessage + +$killEvent = new-object 'System.Threading.AutoResetEvent' -ArgumentList $false + +$serverTerminateJob = Start-ThreadJob { +param($killEvent, $url, $serverStopMessage) + $killEvent.WaitOne() | Out-Null + Invoke-WebRequest -Uri $url -Body $serverStopMessage | Out-Null +} -ArgumentList $killEvent, $localUrl, $serverStopMessage + +try { + Write-Host "$(Get-Date) - PowerShell HTTP server start listening on '$url'" + $running = $true + while($running) { + Start-Sleep -Milliseconds 100 + $running = ($backgroundServer.State -eq 'Running') + $backgroundServer = $backgroundServer | Get-Job + $backgroundServer | Receive-Job + } +} finally { + Write-Host "$(Get-Date) - PowerShell HTTP Server stop requested. Waiting for server to stop" + $killEvent.Set() | Out-Null + Get-Job | Wait-Job | Receive-Job + Write-Host "$(Get-Date) - PowerShell HTTP server is stopped" + + # Runs Shutdown function (defined in handler.ps1) to clean up connections from warm startup + try { + Process-Shutdown + } catch { + Write-Error "`n$(Get-Date) - Shutdown Processing Error: $($_.Exception.ToString())" + } +} \ No newline at end of file From 59590e8ea66b79fed78c942af0a6a84480b9791e Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Mon, 31 May 2021 12:12:22 +0200 Subject: [PATCH 45/56] chore: Update CHANGELOG template Closes: #429 Signed-off-by: Michael Gasch --- .chglog/CHANGELOG.tpl.md | 23 +++++++++--------- CHANGELOG.md | 48 ++++++++++++++++++++++++------------- docs/kb/contribute-start.md | 48 ++++++++++++++++++++++++++++++++++--- 3 files changed, 88 insertions(+), 31 deletions(-) diff --git a/.chglog/CHANGELOG.tpl.md b/.chglog/CHANGELOG.tpl.md index ddc9a6ab..0f5c3012 100755 --- a/.chglog/CHANGELOG.tpl.md +++ b/.chglog/CHANGELOG.tpl.md @@ -8,7 +8,7 @@ ### {{ .Title }} {{ range .Commits -}} -- [{{ .Hash.Short }}]{{"\t"}}{{ .Subject }} +- [{{ .Hash.Short }}]{{"\t"}}{{ .Subject }}{{ range .Refs }} (#{{ .Ref }}) {{ end }} {{ end }} {{ end -}} @@ -16,28 +16,29 @@ ### ⏮ Reverts {{ range .RevertCommits -}} -- [{{ .Hash.Short }}]{{"\t"}}{{ .Revert.Header }} +- [{{ .Hash.Short }}]{{"\t"}}{{ .Revert.Header }}{{ range .Refs }} (#{{ .Ref }}) {{ end }} {{ end }} {{ end -}} -{{- if .NoteGroups -}} -{{ range .NoteGroups -}} -### ⚠️ {{ .Title }} +### ⚠️ BREAKING -{{ range .Notes }} -{{ .Body }} +{{ range .Commits -}} +{{ if .Notes -}} +{{ if not .Merge -}} +{{ if not (contains .Header "Update CHANGELOG for" ) -}} +{{ range .Notes }}{{ .Body }} {{ end }} {{ end -}} {{ end -}} +{{ end -}} +{{ end -}} ### 📖 Commits {{ range .Commits -}} {{ if not .Merge -}} -{{ if not (contains .Header "Update CHANGELOG for" ) -}} -{{ if not (contains .Header "Merge branch" ) -}} -- [{{ .Hash.Short }}]{{"\t"}}{{ .Header }} -{{ end -}} +{{ if not (or (contains .Header "Update CHANGELOG for") (contains .Header "Merge branch" )) -}} +- [{{ .Hash.Short }}]{{"\t"}}{{ .Header }}{{ range .Refs }} (#{{ .Ref }}){{ end }} {{ end -}} {{ end -}} {{ end -}} diff --git a/CHANGELOG.md b/CHANGELOG.md index 029f4504..44d92a35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ > Release Date: 2021-04-20 +### ⚠️ BREAKING + ### 📖 Commits - [2088553] Bump version to v0.6.0 for release @@ -11,16 +13,16 @@ - [8246c90] Adding missing vmware-functions NS to secret example - [27b6553] Re-enable Registry Digest Check - [77f830e] Fix typo for Docker push command -- [f53f20c] Fixed VEBA UI image + Documentation Updates -- [7701edb] Add `/events` API endpoint +- [f53f20c] Fixed VEBA UI image + Documentation Updates (#337) +- [7701edb] Add `/events` API endpoint (#335) - [c710557] Adding code to include external contributors - [bc15599] Bump version to v0.6.0 for release - [df1b12a] Integrate Sockeye into VEBA - [1abc331] Disable digest checking for Harbor Registry - [fa226c5] Add Knative Documentation + Reorganize example folders -- [d9def65] Use mod=vendor +- [d9def65] Use mod=vendor (#329) - [ddba4b6] Integrate VEBA UI -- [2e0f556] Add Knative to architecture +- [2e0f556] Add Knative to architecture (#323) - [5df7dab] Update stale workflow - [e6baaca] Updating VMware container image URLs to VMware Harbor - [403b586] Removing symlink to /etc/resolv.conf @@ -44,6 +46,8 @@ > Release Date: 2020-12-11 +### ⚠️ BREAKING + ### 📖 Commits - [d2876b0] Remove test from output target @@ -64,7 +68,7 @@ - [5951bf4] Optimize Router Workflows - [3278b74] Add Status Badges for Router Actions - [da5f3a3] Increase timeout -- [317570c] Make linter standalone action +- [317570c] Make linter standalone action (#253) - [26aaad5] Update README - [c8bf2d5] Remove verbose flag from YAML - [7547f6c] Add Helm chart @@ -87,11 +91,11 @@ - [7799c9f] Include all files in gofmt - [406e29d] Fix BOM version change in integration tests - [9fde8e7] Address review Frankie -- [bafa586] Add vcsim as event provider +- [bafa586] Add vcsim as event provider (#2134) - [d95529b] Function for plugin auto-refresh - [0dbb217] Changing development branch to use development container and simplify build script - [9ba03c6] Remove set-env references -- [1c41453] Proposed fix for issue [#211](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/211) +- [1c41453] Proposed fix for issue [#211](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/211) (#211) - [dde2464] Update create-docker-dev-image.yml - [2b8b852] Moving VMware Event Router section to top for ease of edit - [0a22792] Renaming version in BOM to represent Github Repo Tag @@ -112,7 +116,7 @@ - [75fefdc] Reflect changes in build files - [a719289] Move router cmd to sub-folder - [120beec] Migrate to v1alpha1 config API using YAML -- [88b878c] Add Pagerduty trigger example in go ([#201](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/201)) +- [88b878c] Add Pagerduty trigger example in go ([#201](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/201)) (#201) - [01fb656] Add vm-reconfigure-via-tag go handle function example - [fd2b58a] Initial commit of pre-filter function - [3980efb] Shorten workflow names @@ -138,6 +142,8 @@ > Release Date: 2020-06-10 +### ⚠️ BREAKING + ### 📖 Commits - [4bf45d7] Updating docs to reflect changes with Proxy and SSH @@ -173,6 +179,8 @@ > Release Date: 2020-05-11 +### ⚠️ BREAKING + ### 📖 Commits - [70d47df] Fixing minor typo in README @@ -198,24 +206,24 @@ - [65c71b8] Add PagerDuty Python Example (tested with VEBA v0.3, vCenter 6.7 with VMPowerOn/Off Events) - [c0caac1] Consistent use of Notes in markdown files - [10e14a8] Fix VMware Event Router image pull to support air-gap scenario -- [7af6632] Add Docker image for :VERSION tag +- [7af6632] Add Docker image for :VERSION tag (#102) - [53293b0] Handling special character which must be escaped in Event Router JSON configuration - [9bf3702] Updated to pull Weave YAML from Github rather than from dynamic URL + updated Weave version - [6bf1e8b] Add Otto to start page -- [b6421db] Add official VEBA mascot ([#98](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/98)) +- [b6421db] Add official VEBA mascot ([#98](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/98)) (#98) - [7addae5] Updating Troubleshooting docs with correct path to config file - [4cc3083] Cleaning up dev template to simplify contributions - [7a2c0d7] Reorganize all VEBA config files into /root/config - [73c9377] Add example function using Go and govmomi that attaches tag to VM - [54adab3] Add initial release of troubleshooting guide - [c73bf9b] Adding /etc/veba-release to include VEBA version, commit ID & event processor type -- [7d3b92f] Remove User Stories ([#84](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/84)) -- [902e376] Add greeting action on pull requests ([#88](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/88)) +- [7d3b92f] Remove User Stories ([#84](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/84)) (#84) +- [902e376] Add greeting action on pull requests ([#88](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/88)) (#88) - [10a536f] Revert Github action for stale issues/PRs - [015fa43] Add Github Action for stale issues/PRs - [55a0d0d] Fix rule processing switch statements -- [db01ee1] Make EventBridge client interface -- [4d30278] Support customization of Docker Bridge Network ([#76](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/76)) +- [db01ee1] Make EventBridge client interface (#69) +- [4d30278] Support customization of Docker Bridge Network ([#76](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/76)) (#76) - [beb7637] vRO Function - [11500b7] Spruce up README with a few badges @@ -224,19 +232,21 @@ > Release Date: 2020-03-10 +### ⚠️ BREAKING + ### 📖 Commits -- [976adc5] Clarify resync period of AWS EventBridge Processor +- [976adc5] Clarify resync period of AWS EventBridge Processor (#68) - [d5dc652] Ensure we pull latest vmware-event-router image - [61f98e2] Fixed OpenFaaS admin password -- [d083a45] Added unauthenticated SMTP and green status emails ([#72](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/72)) +- [d083a45] Added unauthenticated SMTP and green status emails ([#72](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues/72)) (#72) - [993eea3] Fixing FQDN in /etc/issue - [1c97a8b] Fixing syntax issue w/creating tools.conf - [f019970] Bump version to v0.3.0 for release - [dd1330e] Fixed MoRef procesisng for v0.3 - [81587c7] Ensuring eth0 interface is shown first in vSphere UI - [8c2327d] Colorized log output -- [75414da] Fix linter errors +- [75414da] Fix linter errors (#58) - [dd3ca14] Updated Getting Started User Guide - [0e193ee] Stricter linting on VMware Event Router - [84eb278] Add branch information to Python examples @@ -266,6 +276,8 @@ > Release Date: 2020-01-23 +### ⚠️ BREAKING + ### 📖 Commits - [0b2c33a] Bump version to v0.2.0 for release @@ -294,6 +306,8 @@ > Release Date: 2019-11-25 +### ⚠️ BREAKING + ### 📖 Commits - [25fdc60] Fix dead link to contributing guide diff --git a/docs/kb/contribute-start.md b/docs/kb/contribute-start.md index a13c1fb1..c771115e 100644 --- a/docs/kb/contribute-start.md +++ b/docs/kb/contribute-start.md @@ -80,7 +80,7 @@ Commit the code and push your commit. -a commits all changed files, -s signs you ```bash -git commit -a -s -m "Added prereq and git diff output to contribution page." +git commit -a -s -m "Add prereq and git diff output to contribution page." git push ``` @@ -130,10 +130,52 @@ Before you submit a bug report about the code in the repository, please check th Feature requests should fall within the scope of the project. -## Pull Requests +## Pull Requests (PRs) Before submitting a pull request, please make sure that your change satisfies the following requirements: - VMware Event Broker Appliance can be built and deployed. See the getting started build guide [here](contribute-eventrouter.md). - The change is signed as described by the [Developer Certificate of Origin](https://cla.vmware.com/dco){:target="_blank"} doc. - The change is clearly documented and follows Git commit [best practices](https://chris.beams.io/posts/git-commit/){:target="_blank"} -- Contributions to the [examples](/examples) contain a titled readme. \ No newline at end of file +- Contributions to the [examples](/examples) contain a titled readme. + +### Commits + +Please follow the guidance provided +[here](https://chris.beams.io/posts/git-commit/) how to write good commit +messages. + +Please also include the issue number(s) this PR fixes. The +[`CHANGELOG.md`](./../../CHANGELOG.md) generated by this project uses this +information and specific commit title prefixes (see below) to render the +`CHANGELOG` with details and highlights. + +Currently, the following commit title prefixes are recognized: + +- `fix:` will be highlighted as `🐞 Fix` +- `feat:` will be highlighted as `💫 Feature` +- `chore:` will be highlighted as `🧹 Chore` +- `docs:` will be highlighted as `📃 Documentation` + +For example, to fix a bug in the VMware Event Router: + +```bash +# make changes, then +git add +git commit -s -m "fix: Race in VMware Event Router" -m "Closes: #issue-number" +``` + +### Breaking Changes + +If this PR introduces a **breaking** change, please indicate this in the +corresponding commit: + +```bash +cat << EOF | git commit -s -F - +Remove vCenter Simulator + +This commit removes support for the vCenter Simulator event provider +Closes: #1234 + +BREAKING: Remove deprecated vCenter Simulator +EOF +``` \ No newline at end of file From cbeb5bdb03229d6198b26d458bc4defca96dda02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jun 2021 04:27:57 +0000 Subject: [PATCH 46/56] chore(deps): Bump urllib3 in /examples/openfaas/python/tagging/handler Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.8 to 1.26.5. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.25.8...1.26.5) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- examples/openfaas/python/tagging/handler/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/openfaas/python/tagging/handler/requirements.txt b/examples/openfaas/python/tagging/handler/requirements.txt index 06c09a70..79e12fb4 100644 --- a/examples/openfaas/python/tagging/handler/requirements.txt +++ b/examples/openfaas/python/tagging/handler/requirements.txt @@ -1,3 +1,3 @@ -urllib3==1.25.8 +urllib3==1.26.5 requests==2.22.0 toml==0.10.0 From c37b8e2636b6431066ddc66182c8cc046ba60c20 Mon Sep 17 00:00:00 2001 From: Ludovic Rivallain Date: Fri, 28 May 2021 11:56:52 +0200 Subject: [PATCH 47/56] feat: Add Knative Python VM Attribute Example Closes: #434 Signed-off-by: Ludovic Rivallain --- .../python/kn-py-vm-attr/.dockerignore | 4 + .../knative/python/kn-py-vm-attr/Procfile | 1 + .../knative/python/kn-py-vm-attr/README.md | 168 ++++++++++++++++++ .../python/kn-py-vm-attr/function.yaml | 94 ++++++++++ .../knative/python/kn-py-vm-attr/handler.py | 101 +++++++++++ .../kn-py-vm-attr/kn-py-vm-attr_secret.json | 9 + .../knative/python/kn-py-vm-attr/pyvenv.cfg | 2 + .../python/kn-py-vm-attr/requirements.txt | 4 + .../screenshots/custom_attributes.png | Bin 0 -> 50676 bytes .../python/kn-py-vm-attr/test/testevent.json | 50 ++++++ .../knative/python/kn-py-vm-attr/vcenter.py | 117 ++++++++++++ 11 files changed, 550 insertions(+) create mode 100644 examples/knative/python/kn-py-vm-attr/.dockerignore create mode 100644 examples/knative/python/kn-py-vm-attr/Procfile create mode 100644 examples/knative/python/kn-py-vm-attr/README.md create mode 100644 examples/knative/python/kn-py-vm-attr/function.yaml create mode 100644 examples/knative/python/kn-py-vm-attr/handler.py create mode 100644 examples/knative/python/kn-py-vm-attr/kn-py-vm-attr_secret.json create mode 100644 examples/knative/python/kn-py-vm-attr/pyvenv.cfg create mode 100644 examples/knative/python/kn-py-vm-attr/requirements.txt create mode 100644 examples/knative/python/kn-py-vm-attr/screenshots/custom_attributes.png create mode 100644 examples/knative/python/kn-py-vm-attr/test/testevent.json create mode 100644 examples/knative/python/kn-py-vm-attr/vcenter.py diff --git a/examples/knative/python/kn-py-vm-attr/.dockerignore b/examples/knative/python/kn-py-vm-attr/.dockerignore new file mode 100644 index 00000000..dcfe9d3b --- /dev/null +++ b/examples/knative/python/kn-py-vm-attr/.dockerignore @@ -0,0 +1,4 @@ +README.md +.env +test/ +screenshots/ \ No newline at end of file diff --git a/examples/knative/python/kn-py-vm-attr/Procfile b/examples/knative/python/kn-py-vm-attr/Procfile new file mode 100644 index 00000000..760d3af1 --- /dev/null +++ b/examples/knative/python/kn-py-vm-attr/Procfile @@ -0,0 +1 @@ +web: FLASK_ENV=development FLASK_APP=handler.py python3 -m flask run --host=0.0.0.0 --port=$PORT \ No newline at end of file diff --git a/examples/knative/python/kn-py-vm-attr/README.md b/examples/knative/python/kn-py-vm-attr/README.md new file mode 100644 index 00000000..82799f8b --- /dev/null +++ b/examples/knative/python/kn-py-vm-attr/README.md @@ -0,0 +1,168 @@ +# kn-vm-creation-attr-fn + +> This function is greatly inspired by the content from [embano1/kn-echo](https://github.com/embano1/kn-echo) and +> [lrivallain/openfaas-fn/.../vm-creation-attr-fn](https://github.com/lrivallain/openfaas-fn/tree/master/vm-creation-attr-fn). + +A sample python function with `Flask` REST API running in Knative to apply _custom attributes_ + to a VM object based on an event submitted by the [VEBA](https://vmweventbroker.io/) + (VMware Event Broker) services: + +* `event-creation_date`: To store the creation date +* `event-last_poweredon`: To store the last powered on date +* `event-owner`: To store the user that created the VM + +![Custom attributes](./screenshots/custom_attributes.png) + +> **Note:** Only cloud event payload ("*data*") in JSON format is supported. + + +# Step 1 - Build with `pack` + +> **Note:** If you not plan to edit the function behavior, you can directly go to **Step 3 - Deploy**part of this +> document. + +[Buildpacks](https://buildpacks.io) are used to create the container image. + +Requirements: + +- `pack` (see: https://buildpacks.io/docs/app-developer-guide/) +- Docker + +```bash +export IMAGE=/kn-py-vm-attr +pack build --builder gcr.io/buildpacks/builder:v1 ${IMAGE} +``` + + +# Step 2 - Test + +Edit the content of `kn-py-vm-attr_secret.json` to configure the following settings: + +```json +{ + "VC_SERVER": "vcsa.local", + "VC_USER": "test@vsphere.local", + "VC_PASSWORD": "ReplaceMe!", + "VC_SSLVERIFY": "True", + "VC_ATTR_OWNER": "event-owner", + "VC_ATTR_CREATION_DATE": "event-creation_date", + "VC_ATTR_LAST_POWEREDON": "event-last_poweredon" +} +``` + +Run a local container based on the `${IMAGE}` image or the one you just built. + +```bash +docker run -e PORT=8080 -it --rm -p 8080:8080 --env VCCONFIG_SECRET="$(cat kn-py-vm-attr_secret.json)" ${IMAGE} + +# now in a separate window or use -d in the docker cmd above to detach +curl -i -d@test/testevent.json localhost:8080 + +HTTP/1.0 204 NO CONTENT +Content-Type: application/json +Server: Werkzeug/1.0.1 Python/3.8.6 +Date: Wed, 17 Feb 2021 09:16:09 GMT + +# you should see the following lines printed in the docker container + * Serving Flask app "handler.py" (lazy loading) + * Environment: development + * Debug mode: on + * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit) + * Restarting with stat + * Debugger is active! + * Debugger PIN: 138-375-841 +2021-05-04 14:09:18,464 INFO vcenter Thread-4 : Initializing vCenter connection... +2021-05-04 14:09:18,992 INFO vcenter Thread-4 : Connected to vCenter vcsa.local +2021-05-04 14:09:19,483 INFO handler Thread-4 : Apply attribute > event-last_poweredon +172.17.0.1 - - [04/May/2021 14:09:19] "POST / HTTP/1.1" 204 - +2021-05-04 14:09:19,777 INFO werkzeug Thread-4 : 172.17.0.1 - - [04/May/2021 14:09:19] "POST / HTTP/1.1" 204 - +``` + + +# Step 3 - Deploy + +## Pre-requisites + +You need: + +* A deployed VEBA instance (K8S or appliance based) +* A deployed knative setup +* A set of custom attributes applicable to VMs like the previous ones. +* This repository + +> * VMware Event Broker Appliance v0.6 ships with embedded Knative deployment. + +## Push container image to registry + +Push your container image to an accessible registry such as Docker once you're done developing and testing your +function logic. + +```console +docker push ${IMAGE} +``` + +## Configuration + +**Note:** The following steps assume a working Knative environment using a broker named +`broker: default`. The Knative `service` and `trigger` will be installed in the +Kubernetes namespace specified in apply command, assuming that the broker is also available +there. You can customize this in the `function.yaml` file if needed. + +If not already done in the *Test* step, edit the content of `kn-py-vm-attr_secret.json` to configure the following +settings: + +```json +{ + "VC_SERVER": "vcsa.local", + "VC_USER": "test@vsphere.local", + "VC_PASSWORD": "ReplaceMe!", + "VC_SSLVERIFY": "True", + "VC_ATTR_OWNER": "event-owner", + "VC_ATTR_CREATION_DATE": "event-creation_date", + "VC_ATTR_LAST_POWEREDON": "event-last_poweredon" +} +``` + +## Deploy + +```bash +export VEBA_NS=vmware-functions + +# Create secret +kubectl -n ${VEBA_NS} create secret generic vcconfig-secret --from-file=VCCONFIG_SECRET=kn-py-vm-attr_secret.json + +# update label for secret to show up in VEBA UI +kubectl -n ${VEBA_NS} label secret vcconfig-secret app=veba-ui + +Edit the `function.yaml` file with the name of the container image from *Step 1* if you made any changes. + +```bash +# deploy function +kubectl apply -n ${VEBA_NS} -f function.yaml +``` + +For testing purposes, the `function.yaml` contains the following annotations, which will ensure the Knative Service +Pod will always run **exactly** one instance for debugging purposes. Functions deployed through through the +VMware Event Broker Appliance UI defaults to scale to 0, which means the pods will only run when it is triggered by +an vCenter Event. + +```yaml +annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" +``` + +# Step 4 - Undeploy + +```console +# undeploy function +kubectl delete -n ${VEBA_NS} -f function.yaml +``` + +# Limitations + +**Note:** As this function is not using any session or content caching, it may lead to an increase of the API workload in large +environments. Some extra modifications may be required to safely manage a large environment: + +* Move session initialization outside the handler to reduce the number of tokens managed by vCenter system +* Cache the data from inventory requests \ No newline at end of file diff --git a/examples/knative/python/kn-py-vm-attr/function.yaml b/examples/knative/python/kn-py-vm-attr/function.yaml new file mode 100644 index 00000000..7406da01 --- /dev/null +++ b/examples/knative/python/kn-py-vm-attr/function.yaml @@ -0,0 +1,94 @@ +--- +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-py-vm-attr-service + labels: + app: kn-py-vm-attr +spec: + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" + spec: + containers: + - image: projects.registry.vmware.com/veba/kn-py-vm-attr:latest + envFrom: + - secretRef: + name: vcconfig-secret +--- +# Here is a list of triggers with event type filtering to match multiple kind of VM events +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: kn-py-vm-attr-trigger-vmcreated + labels: + app: kn-py-vm-attr +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + subject: VmCreatedEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-py-vm-attr-service +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: kn-py-vm-attr-trigger-vmcloned + labels: + app: kn-py-vm-attr +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + subject: VmClonedEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-py-vm-attr-service + +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: kn-py-vm-attr-trigger-vmregistered + labels: + app: kn-py-vm-attr +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + subject: VmRegisteredEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-py-vm-attr-service + +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: kn-py-vm-attr-trigger-drsvmpoweredon + labels: + app: kn-py-vm-attr +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + subject: DrsVmPoweredOnEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-py-vm-attr-service diff --git a/examples/knative/python/kn-py-vm-attr/handler.py b/examples/knative/python/kn-py-vm-attr/handler.py new file mode 100644 index 00000000..fddd4e16 --- /dev/null +++ b/examples/knative/python/kn-py-vm-attr/handler.py @@ -0,0 +1,101 @@ +from flask import Flask, request, jsonify +from cloudevents.http import from_http +import logging, json +from vcenter import Session +from datetime import date + +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s' +) + +app = Flask(__name__) +@app.route('/', methods=['POST']) +def handler(): + try: + event = from_http(request.headers, request.get_data(),None) + + if event._attributes.get("datacontenttype").lower() != "application/json": + sc = 400 + msg = f'invalid datacontenttype for cloud event: {event._attributes.get("datacontenttype")}' + app.logger.error(msg) + message = { + 'status': sc, + 'error': msg, + } + resp = jsonify(message) + resp.status_code = sc + return resp + + # CloudEvent - simple validation + ref_vm = event.data['Vm']['Vm'] + ref_user = event.data['UserName'] + subject = event._attributes['subject'] + + vc_s = Session() + attr_owner, attr_creation_date, attr_last_poweredon = vc_s.get_field_attributes() + vm_obj = vc_s.get_vm(ref_vm['Value']) + if not vm_obj: + sc = 404 + msg = f"could not find vm with moRef: {ref_vm['Value']}" + app.logger.error(msg) + message = { + 'status': sc, + 'error': msg, + } + resp = jsonify(message) + resp.status_code = sc + return resp + + if subject in ["DrsVmPoweredOnEvent", "VmPoweredOnEvent"]: + app.logger.info(f"Apply attribute > {attr_last_poweredon.name}") + vc_s.set_custom_attr( + entity=vm_obj, + key=attr_last_poweredon.key, + value=date.today().strftime("%d/%m/%Y") + ) + + if subject in ["VmCreatedEvent", "VmClonedEvent", "VmRegisteredEvent"]: + app.logger.info(f"Apply attribute > {attr_owner.name}") + vc_s.set_custom_attr( + entity=vm_obj, + key=attr_owner.key, + value=ref_user + ) + + app.logger.info(f"Apply attribute > {attr_creation_date.name}") + vc_s.set_custom_attr( + entity=vm_obj, + key=attr_creation_date.key, + value=date.today().strftime("%d/%m/%Y") + ) + vc_s.close() + return {}, 204 + except KeyError as e: + sc = 400 + msg = f'could not decode cloud event: {e}' + app.logger.error(msg) + message = { + 'status': sc, + 'error': msg, + } + resp = jsonify(message) + resp.status_code = sc + return resp + + except Exception as e: + sc = 500 + msg = f'could not apply attributes value: {e}' + app.logger.error(msg) + message = { + 'status': sc, + 'error': msg, + } + resp = jsonify(message) + resp.status_code = sc + return resp + + +# hint: run with FLASK_ENV=development FLASK_APP=handler.py flask run +if __name__ == "__main__": + app.run() diff --git a/examples/knative/python/kn-py-vm-attr/kn-py-vm-attr_secret.json b/examples/knative/python/kn-py-vm-attr/kn-py-vm-attr_secret.json new file mode 100644 index 00000000..126b6b63 --- /dev/null +++ b/examples/knative/python/kn-py-vm-attr/kn-py-vm-attr_secret.json @@ -0,0 +1,9 @@ +{ + "VC_SERVER": "vcsa.local", + "VC_USER": "test@vsphere.local", + "VC_PASSWORD": "ReplaceMe!", + "VC_SSLVERIFY": "True", + "VC_ATTR_OWNER": "event-owner", + "VC_ATTR_CREATION_DATE": "event-creation_date", + "VC_ATTR_LAST_POWEREDON": "event-last_poweredon" +} \ No newline at end of file diff --git a/examples/knative/python/kn-py-vm-attr/pyvenv.cfg b/examples/knative/python/kn-py-vm-attr/pyvenv.cfg new file mode 100644 index 00000000..ff445273 --- /dev/null +++ b/examples/knative/python/kn-py-vm-attr/pyvenv.cfg @@ -0,0 +1,2 @@ +home = /usr/local/bin +include-system-site-packages = false diff --git a/examples/knative/python/kn-py-vm-attr/requirements.txt b/examples/knative/python/kn-py-vm-attr/requirements.txt new file mode 100644 index 00000000..d0ace9b9 --- /dev/null +++ b/examples/knative/python/kn-py-vm-attr/requirements.txt @@ -0,0 +1,4 @@ +flask==1.1.4 +cloudevents==1.2.0 +pyvmomi==7.0.2 +python-dotenv==0.17.1 \ No newline at end of file diff --git a/examples/knative/python/kn-py-vm-attr/screenshots/custom_attributes.png b/examples/knative/python/kn-py-vm-attr/screenshots/custom_attributes.png new file mode 100644 index 0000000000000000000000000000000000000000..85cd92f700fd19cf48a509e44853b4c34e6cd82e GIT binary patch literal 50676 zcmd43XH-*d*Dk6E(m}dNlMd1Zq&G!+RX}=?4$^xMMw%!^I!KeQ(t8VpA~guog4BR? z0wk1BLpagrectE&_WAZ1V~>6I82d-UxL4M?+no0-*EJWh&vaBtiS7~IxN(D2T}?^v z#*JIZ8#i#632tMrFgmQMVgKCp(Nk5tQ8miAjcwpMKGA-1<3?>FG1?jr+a~l>Gx51` zgRJ-W=jMRNXFKdgN?&DTUwsb;-vDcG`x~m>_V(^RP9DCNF9@)Y^tq@jJuwKh+`W45 zV_=krAif}P;oeXgS;+eMHnzo2H$U?W)#j4Vg}JsC-D!H_mr~QR?FVg(hT`_cP1>|> zUAOBr`f(z<$j@21oA`+tNZx*X?Mie|XYiVnd8qfT(Xbl92Z5pc))kLvIS3|j`1#Sf zsQJW^T$uoQ4vr+=FfdKN_rBwPhkk{5eh1X@{6~21@!0*yJBkFX*e^AQr$Fw%8+A8` zWPe}${Yx_5vt`16H9qja#?k$AJNC=&CUxLn&l0e55P#~T`nzFr126Aiy%G%J@F4&0 zRgL!Xe;tC}2tAoHCOyhc&I}NbydxrReO}cIe>}J)oPOQ$e0iwlja6P2u>yl40eyB& z=*)`WeY<1qI3g;`8IBu0W6^gM`?>gGet}E`^m07N-mYs*_J9gw*(v8YBCxC|-GA-# z2@p(;rOxLSLM4G9tT zS#f+yz-mjCj*teCMaD5xpRpcZUtWfLrS2xJfv+aP(#*uHIRD+?ENs+)$8KW;`j60i%JSvUf zZ(4>@UCV}5tzO8SGIrVS2PHsm_d2B_^wJ?ggXcq1+Sy_(V8E{WfFBEWMZ_JE;J9_~ z7#!Vx8ua0axeB~~C|}O1yur||a{58p8&G;s@AK!Jksq|44vSOx*N>E=orst8Cwrja zDb?X8rz~^>uG~kW7R&2r73-}*me3%*TK_r!25Y9s597A{88Xy?B2~XdA~KO5zrdMh z$jxg&2Y#NQ@cG@>1{K0X(+y|Hl(cIacVryRNS$`Eh5JkCN_GL2>h#JfDAl+Gc`q_>Pv40?T~NC&HP1U+ZjcK!@vVtX4qO0OH;m21Mq( zTDj?FNq%cp&ZWMs9FZRiUN%vZHvMu+2RdY2u6zz83pf|tO#iHvk$vY->+9tAIlKAz z+kb7mLu*Q05>EgRavS;W!S3#sGAy^fCB$ev1kVv}xZ+jjm-68Y&UIkl&73Pk^|qXB zf54)op+z+89>f+GpoCm8_7FdI1yUT36)C(^*>``fhV zZ8lPpE?tWcrG9FB_6HuokmD3u|-f!9W6m8$)1ht07 zuZr+_$9Wr_vBYeD919;R-}88P5h7&OXemQhhKf!(7wU+ecsCumHqOnM;rJkNELhOH zGAEr%gi&pl(>{}=Vy$5zAvKY$gR3srH9r<7AG>;I=j~+s2@S66MtQQAY>>6mD!(Ns zVaAy!!=3SFuRff{oQ?H~ttOgYDMog8@-r3hv&};~*X7naa-gC?=ar{5!MzS>wNkM=1M=UtmpA|hlXWuy26%*lOIc4Be?gWIJa+CHkKnD#0U#Yj3dhhm>GPN(8piM`ly#?NDVs9;}U`Je51&6!j z9!UsF$fJ+QTt8!!LfjTDm#h+cr9JpX?yc*zkp#4ge#KFEarHd!ywKvZsCkr5eyLO6 zj0Mtu!ZY75319 z)PgKAK(2#B`Jtvpn=l0#QH(*{_xxQ$RCHrf_M~^bT^_c7>~JQ&kUKO z2gvYe+^GN->4HQ3|W#60VpHt}@)liwmbN~3cLqTvC2pIWy(mc?q-)|nGV^xv)Kl~$ju2i| zsIUp*t;S708qRuTI*w^)=Gy+31?4N`*nDF+a?Gt^vSS4JUtnnN4RV@m%h@pn+3}? zE0upXuHK~dBdG(7^Fi41#~?efo?jA6;~0UVi^KV8k&jJ`MU3UUM(%~^?=W5M3xM~+ z#5>l@G~3j@>q^hquhi!aa1f}D=zL7JdhcmfOemL#6*TSLWRdRoSjI6vkLn+o4hZJE zy?2D_39m$qq1%}#rue~)!_Gs@hU59iV1XHplc$5vk`VOtIg`$_5ieVY1Dv&W4G8^r zggIv(o38I2t;4LpBJhI9>8GZgv+fJ@M_jqny8|<3L@a@+VP>!34c@Bhv65=D-0L~5 zi(q-P;>L|(k@M*^HyLwj$kjAmvR624brB2r252>Ze97+u~Aj8q_rVAHurr7OTr_@J+ zwo_%cMRqKLCORG!4IWP1= z_LK~6C@mwFYAaT$=sTnI<$V)QV0j(t#l@>NqVVMaCw)fueHXrL@IbPQj!@k(}oF;-t=O6USb zJNWj~X-+R!^`IkbIt1gM*XwP|V%vykNuNNSTrZ`^ zOlhT~G585zRLik+5-c@U=-IhQ>Ko(qSz@a)AzB&HP4MRzc54p6ly4Ba%E7E-#yhb| ztF_gl$-fU-8sQfwDFMxB19b7kasxjFo6}+Idrt-7fG0|K@qz?8-s2{Tufzu!*%mxl zwQE)LSz?i$gDY33IZtrY=zZZCaullLnc#saFO}F^Dd}#|d>xKJlV-s`%H1dy;jm)% zXH7*_;N*{rD;C;naU#ErS~R6G5FF;cLFdv53QSu{YX)z$(>(z(tql zz1?*nIUnkJzA)-;-mWxPJ;(6ynkBt7vCw*NE*%%y1@Tfd;YZp>h zcev`;g{-xYRTjN=_HRQoAh0hc1hL`G6hLVoRAB@oQJSUOAB0-_ii5Ex_3>S887RnD#)Xqa}2Ok<9IAWjiz7EsW(P zJv7M-{{1FJAjK=Vbom(1fyX2Hk;2>NpfAwo#*bj%U81U7V|y*QBBPuo)9l##({TmL zcy9k+u2zM#dnG@Q;-$yheqKo?886$jG<*7|xUo#svApVm&owY21aeG0Oyr$Kbru_l zE`L%7@{W`!3gP929t<{6w|T3YV^%c#tT23d6|K1oe(6DaAF}7OA#0k0pj)qmLUTK@ z4h5%A{a59oAkM36Jw{TMo%$rGj!W~fZxRvY**?#E{&$SSmm11XziEHlwZiNrYRD^m zLpZ90OsNq!Rq)xoT8tt1Y~XEYQSJ&6nN1x0JbU4$3{A_%XG;|k7;Dk&(b2$!4B|t-rXW0WZQjJ z(?aD!AK=qHG;bk0mK%4?GD#<8AwI{g^I=e@2Dv5`m}}yf^h9Am$!(Xh6UBn6)jd zadw&hBtFDr$7?k)&c18_1pOqox}?(-*`>@UJQVz6EZ-IwG$_^Go^P{JHZ>?|Z>PRs z^F1_`XaC}AoHW2uoXk_XQ)m9oswpX3jxB^OtrSf-A!hzSsPu+uZ8aZ!FsPXF%oqFP zo!A87v@UL$fLWC#SX=z=7`69#0NaG%8-9tWBk8W~U66*>4q3gIxwI9VIa^m1@WIe_ z*X+qd4>i+`y-oJ(1Ush7gF(P|7gt!B+9jy3`js5PTJ%^?ax?GYTIXE|*5Jm!_`(4> zhXi;OW;eGjWTU>WWnNOnf?0y-R1}vhVOJt!50(yd1=kDI>ebTGMxCqnFD177EzLG( zf^QGkPv{{Y!nvVW#r2(_491xA&Nuui!Eqxg2)*;_poKc~dqQjHz7$%{-IjK;RUf{h ze!d&Y@SdTz4^pTXxQk^lt3fy`&<(j_XWT@l|Q&9;!XG0Ah1X+2wki1 zO}g3o;rtI@@#LB4o8P@^o9B>G(J!?(;|A*b?<=wuq|bP3YqMskYK=Sz897az@we6~ z!&}Tdw7!UB!eo0LfYXlZ{Q|o4(v(%;2 z_PPxRwPWd`A0T?1h(S|A#d)j}EK0uid1{{)JX66`S?LTFY+bRq^0_E`02?p^K}7GQ zwr;HW93IchBBEccUIahE_)J)`7o^25N? zR@vDm!wQ6>@=na0C^C(qaR!PFksr*+9+H59bGJei<$YRbZ7>}l2fyPV9VPt^-f}HLt!8=+LhCblRPq;0`JeBjO$S+x% zfr?rDEU}ZB&^E_?)FF7RjwkPc1Sv@phF*v;KdpPRo0my5Yh(kQsh(fAy>LQ}K3o3Y zKBTwx5i){3yY=eLqDNJq(FSkPxe%Aj3?K^fv`uk{?rUi5U=tg{I{<1f!Gsm7;@K0d zV(I0_^R7lXYiIb`XLlRER;DZ|!Db)!rg_!0=a08BzYoZjt#T$D?AGlp5U@^A`=Sub}|6JOxU1$5q-n7Y&GLUt=6;h|9=%xete(QW9 zbDdFL-SF-5x2O#cf~fyP9U`OwpH-E-<6=K$io#T_JRtw)zleR0EAzqdzOm z$Z)<)UJ9@4sNh4%rC2_Wjv#u9ECSU?1oS_}inp$_n;d zY*|NI(OgK4re?q8s^mcz;fQix`DZ;}<9WbTLe94!Ux+e=pP3o-D?j!qsFpz8_gBXQ zOz>NzKOMgb+o*8v>iZBU?cu)4kG%G){Rk^dIQoDI#dze0kY|6aUiP-7$!n-~>E3o+ z+o+IwP3E_oK`w=k%L%(9vv+Zj|Fmv# zcWA8NH7S`WbJCm7LGwNJs178BRgo)=?SpUX&JZi4G^jqspY7!KM7D-DKodh+H0Z=&G(fY#3QJwC4PtAaUR$6a)Z-Eg8 z${}3K2RK+KCb2EWKJyrhPCc!PU)W6w%buqcp}rk5IYG#Kt7Lu1dG;m!=%K~%=B1S2 zQR%*C?)=NUZa})VXz*bABd)=QP!^3D59g$`wwTmJy;HTnRj#J9x{;X!Apo8cw9h{e z<3X`dJgX;eqq%Bz+k`#LkWLN-=v@oLwKuQ;CS`6FHulAn(&d{V?gmui9o%t#oFr{f z=eBMo?wYiHIujPc3n41i?Amf&QsE-Q&YprXT@ zl67%i8_2Tny$K{ls|gzbXYdVu+_eFqLgoR%;|Jqnc?iRvdzVyR(H}}tqTbj|;Q4m= z)Z)VDq%MDnPtweh>81j(iw|(Qqmy$Xeu0@0%=s2mT?**GI(T$v9U~y}7&BI`euA!?Z zz%g@`)%qk_&ibx2LExeJr~B27L9eoA4+Sfmp-C}>tQ=+^GztZmU6?Y;dO$1A@^#)| z5-hN6?^o!PQq@1f-tU|>%izKzR}p>d50I_Wwvgm)xbsf0)Ho?Upd6H_^c&cwa(o)g9cd?8 z8mC$QT)J{wZzvUoa4h!YRw)e4cvpKY@`2ev&rqh4UCrJU5EEL{qd@upLi2~F#-b^8 z35FbsNm7W)3^+;{Q<9P@MTD^Ftdj;`QU`p~Q&?}k-B2A+WG*AIEiSalddqlNN~6TY z-F&c>c<8}=5gzcie(uUEWvV0AJxv&oPKa(d+YOQ$qQ;0 zomg*A9S`oCEz2!RojG*_N!vf4F#&?sqW z%Z^iN?MGNLS>h|IB-Wx4@qbe)XR~({2Y*Jcx06-hVlBeQ1H45ez(FJ_WpaBwbL@SQ z;e&DRdK{uF{~d32+6_Zr8DPQrgll=f{rm zmJkx~XO`qxA`iTDpWN)k* z8C*n2QnU;HMS~{GanJwP#&{Wse>`&17ovEFfjw5$krV6oIBfL8(t(va&nc^>WKfGQd$tI*hL8KLDP z_Dkf_`*5Wp77X6zSYEr2483p zhH?j2TXhdT&Hpvsig)IF6l3brVfPt_<<)Vws1#k5EZu@;`8cIG?eSEXXf=^on){k= z-rLwQ!vX=uNU(wS<~o~EsRup$D=xgl69S;#*Hd_Ns~;!Y`qzKRiU&Dxvad8tT>qjE z6Wxz;G4lLUuJ00dy+7~qV?G4WsM>e6BZIiscJXqnJ?QCfm>Lc&+yU>9*oCc*$-VlG zW3ZUCy(zBzK*WybMcuQo+GI54)ZNLIUdxsTC`aUFVsH|;;wG!DLf!su<&&^C)|oB; zXdopK`a66EdGf}D00xR?kf{1)GjFnceRO;GGLUIB?~G)oD(pR?^YR|V>VnENTgPc^ zMDrKQ0#_biN=oN#SIml4PU4QBtv$P2nqaC#Ih`qSD5>IwtNkv3T1E!~c=v3^Wn^X| zb|*;QGcGjR>mTL*Ew|3;7<*;W#`F`H0*GXoox<0=5ZZa#kv@zz%dg=y52RZS6^#&v zg2}P3fr>kbIF$SA;jjEC^6B8^FE2j2(*@3sdbQzos>ixhJ|tQF51ph0UNd{hOWb1R zL>Q%2NU+RBxQ)q0gb{C8BUI%>1J2pnz~@8=!Q3+3&Y_>fQbTxv4+2OEYYAH&nZ_{QP3dNpNhyEPKg+3|MlH2mF;$>ej1`S=Uc^6|y6n^xhA(TAKq2>oTI zJfOmA#QX(Gs3}f0!qH#(hPAE27U8FYMp)*{yZy}noEk+S7!`I`NI{0Q+hcS!yL#R$ zR98Sg_}A%~CIJ#XAQ17JR6OJPkOCQzQa65Nn@DLkF#x}7ulG8^m zp+MxomQ(w?{UraK2WMjRoA4`q?O#?oC?VtHHR_S;&DcQ9_;vJV*;N2Prnks=4a1E- zu)>Jj6U+8K%%aCm>*_ zugaeqgY9S!M{Y`TuUkWtPix9iZL1f(+Txm zFw&AtWV@Iox5NwYyq;2>O_bJO)h6_OfBNPw!o`gG`7-{DoY*`>zJ>MyaJ;W;2;fwH z`d}aW0eSPtGA`X`aE*+UvS)3pj8(94Wp16)wnS@fJ6Pg<1brjo7Sr5?Fr4%8iPYX| z!q4-8>7eh6@;pGWxePfKv;K_DoeuIxQd-CIbL_-TE_^c%(`dCBv?_CUlrjsmVoXr) z<4f6Nd&A*-0+&Cwdi!p{YX<{e?{6Bl2EG~!W`s9Y({*gj+1}QbE8Nq)){;KXX5KlH zYc85P1Fh8y^qn*npcgF$stOrp8_x@a7h`Nabh=VsY`Vg?oMygz1Eh~?ED{vf#hetg zTKCs&jlpZYYnb6oL&%;86U~{t@2+4kdZ2LT<#^11=PV|mG|YYcs?VE#ep4lpb34em zseP%9*k1S=FeRpglh zXdkAIGPooAqA)l+azQpmEIjiyVInwAJD#p+am?tu;yz2W?WLK9Mg>l|y%6oy4#miI z&Ct(N<(&D-Z^s9OnT;=$e$*jw&0%s_p4LqJ%cSCh*Zc! z9$OvIx?r+Q{^fk~VWpk^@tlNv4`c`7`sSiNE{MbC4o?2DY@C}Qbaa3n{EoQ{gK#8u z&`ySXTrx~o%|1%qO8`_2_@_F<)0tRS>VEGw>nR@Am-c(r6%dEPtc%(klnqBKzw0Zu z4;|1Syq8h3v%#}8?eMWwpuVnXa7EORt15M}=!-No5^85_9!wRhJ`ArPZ_5$X$SEB( zzM)f>75Rh^y>9VJx;y0OsMy>qMj0#9q-?`MG0Q}u_u_W1Tg-M@y(rBj70V422<$W2xWv8m1H&@Cjwn4z8ZBy8WG?RiNU;52u^-hBYq7Fj*Mj`S{T`ELP(={b=@XH=p-Nd0wvma<}*0&1jg8kj_1u^r(NWX zfV-`!&ejqs2o6n5qAYL$J@A+bKNC5W%@>T|6|6H5%!1)+k)cia}`) zg=5?da4lWr@}mi*5??}P=EtY9&g!)}s^`2ga4z4U8w1E`F0J~8V{DA|G?EGHTP6J0 z%2spCc7<#v^{JlCEr(V;r90yl?jWgk?(|}ZUw+|qcaWQF) z_|4XEeoe$Fv~e>k&B>(gxs^ekc(-)5ejda!vV+jniakz+F8J1!#76$a?MErF3ZeN* za-^dLcR^QK^7C%4PwaxoxwD(GsySdwFaZ_698Lv^#WEG4KMpYb$aqA$_GeY^Ln6?w zg#T+9{9QSweRGw`1d)+R_e{BDPfx|)RfTr6)Pd3T14DO#Mi6dES%pelDwcZA`o6XV z8Vf*9$;k4CGUKJE{9)?gSh*jq4AtJ|>aUaOvJHhO!o-fa4l66@?f8)4?JOIaB9XVb z@iJj8KWmteSoGOEE{EEc4j+i*)Zs1pi`o?9S%#!9Jt$6)B*uIu3>~HogcRNH)yi1A=Z;oudorI{Hu}YE)1O&W1QxwV z%ZK@1D&@?drLNX57jYpYf^J(fl)p551G~szklQ8#VGyj93~IbT3t7XM7QJ1M>E z{byKv068q|lu7>TSbuk+m8$#YkLSM-QuOG@cQASq=0SpMI>S-z*f6R2SXqFEdFptp z8X+SmhBRonOZ9ZDhMB6@&QnBrxx`OwKzsre3$<7jd~HNlV4#8mILnM$h$mQF<{o4S z?;VJwc2M)Bge}rB;qUIqAia0%yUCm?#|a}ZFTCjEEjngwOC)`z0IlpBWA#_oq#r5e z2CAX6mU^^{WxnG9)I0Kw!4_?g&8qgkLt3^PVA(OqI?4%)^i?hX>ZgoRfMVQEd(u*l zv&*+nLQ+{V-0LdZ%i&@-C#O0m?nPO)^CqqGEID{Y9OGJACp^mEI(WlZoA}NgGk0$4 z>zWfy$oD5`P~%ET@5(;xbek9Fx9Vi1p`rN+3KH!+-^iAXW$|=M^DxUGA4~Woj2B6k z(OX}IceNviwFS)PBBjI~Csb}yfooY}Nw7O$^t7<+gRIiX&swo%5jQM7dM*?;Oxs>4 zyeYFZQmkGRQ{lQCwZ76n483Bj(A|{S*n6urpFf6h?Kuf>SV^*yixShO^V((NY>!Aj zgGe5yH!SJ)7Lo_7n7WPoiDj^PI z84Wu#SOC~Bo~{KPS^Z=~>jATgeG92-p`_9GkrC%ifl)#1YwO>&FJL8JVLsPq%)^XrKe%NQAsQErKw5V2 zEg@UA-HCxG&o-MOvel4~S~W~(9&hi-?Ny%+Zwl>ftAuu5ETR_qVTAolSN7tD@)9|t zD8wRf5)9eO_0igb0F{AQ`E zAnnrOS$;kdjQHa8*t5&HcqsTrcW|R@^<7LgnkTFD!BL9&V0a-e=0m6H=W+bR+OXnv za4VyfaG}tEF|@2T$(q{QgHq1tA!MOc8u{7L=I8N&v)3%C znalz1v2jmtqxEd$;KKAbblntq$&X;q-?%pU!deq-`8HP87wPa1vNr0NK3b)~=r zU5I5JQ)`g_gc(^#(4Qg5`{OD7LSt{sFbD%@+K$Zfm~??lwmneO@J+UpPG+Vb9{SlR zgxt8;-FID(^V0&`0QwDvaK6~;j!_hJk4LP!8f{UT?3bGl)CDlV+qeB#ql)RD1{X|x zkNFjT_zaWo4~|hDTf=e^*;YY22=|Pz@ZZjFNHUoS^!7ZB&*+(nN$lxT2)Uo1pJebc z4;i!UTe*;v<-KNDe>(Eti-oA5~Dm7YWesif=Gj>nc%_GtIW{SHT{2Rjd=xYpVDQxJtI?pgBW;ubJU9)iZ4)U=-AB zF8p+FzN`@hIWDj6d^ckiZUHwGDy(k&DI99l;*bC}=B5i`NKzJu41ow2p5DkD)rauhJxK>yQK7L!gh<|)po^W+%iq5A zj>w7H1Efe`u5S@ydm9Kt3|L_f=|CrQEVq6!@2Z z(aqodCGA-;PJS_7=VA=6@VtNFMJxx7hr|81AA4erz4|j9GWm7I0}7fIHgk8+UH=TF z*pH)*w-m>FAaaP-6zYg9(ad;Nd8kwLW8gdlOnE@B{JF{Z33>iOcSns5ufGT|G5ofC zOS|L=eIPebh*-(}?l*;7Rx!7?tE&Cp3#oQ(^|QDez~x+N0*lPRu5`ym7d5lwpICq* zba;96qV!lo?=Kw?5m(n-t}Gf4KOEvVg}0;0Kn)V=cN1Kx_9W=$IAs zHS#e5aG1>vH+b)^jmnT?=I(!>FkiZ^i!lL+vJmOhediv)YCowt7$6rGB?*=n{7<+_B=y;VID|CKx$ zyzL2||D)P*6n`>u(O&{c?X=9cjfSJT3a+>@%PTQ}oMVyl&t8o!tL~!uS6-a%SI0pQ zaadlTf@R+g5Cd+XLixNPF&s$4e<@L~A{I2mTnT_Mn{o-}oq$QN&L0!>k50NCYL#wc z6UXV&#DjjH$>&Hr;4j_4)5j0n(4#MK<@x)M-{5G4UpS+`pzq)`_VFy)R~%W~fM@7n zm+7Vok^p}jW4Vw5 zoHK}3%YLdXQ^!2H3JX8T2fdPmh&{wbYuVy4&y1csp7kL*&UVd?!jjFaH5++y&WO>w zj^JmAYZq#Ox4IRe{g&BU2WDsURXDh7wIpBJBZs48&c*0Yc|p3dmoV9gX{)UH>i(i|eW2*!ToPFY z<`Nv;{M^#2JH2dV=J|v(FvAZZRePsA`+GLAIPh8s{H>^3h4XT_+i1$`-PSx*tab_c z)&&TT6u<-b7lQAKRBxRjy<{(d0PPeDtKTO6gDY1_mqe4*H?#SZ)!G^8D=qUD#zb%{ zGr7XGyWJ96Or`;aRoN7ap;W=+N`}&+v&r%ul(PXX2>5iyCIjDvP6@XY)w_L`LW#y! z0Qsq;#0MqX*k&AwcN_Mg=b2E|*V8?n_i@z=BZW;T%$7QjR)vi2?o1E-Kq%iMW%{z}?vlb?1nAv&Y{ zz-3;e)UVXdl0)@NU6_7&PcIV0C;G z2{@40&lEhUt;f~Dho;Hcd$dB_cNMsEpK#T(T zbk0=oVbe_0$`~{WD2>mtVRMG>BWl~2f?1Im{Sw#mWR<6fe5G0wx#jd&c)U z*N;U2>GO(0ye!4Kz4I@}Hu4D*%{hA4=l253%Flz&Ju@ySTzzTJkexOyQ$c+ljY*%oVxEbP zMSNs-Yhs3I-z@VX+~a$KStHE+oWOc51_o&3Pfb6Y;SQ*)-H&b>3~4s_eAQg;^5S!P zWs1w5uRzv8CO#X|s$$^SKBjQ2#R}d9Eunn^_PJHo+yPYh{v-U&{yOV-2k1UiGn`Bnz}4eN5~4Zxo{q*Rl|(ad56GQP+71BN|`LYO|-1Gq+39%jcjPGFDrb73k>MsR3cW$I~AV7cfPZ zTIRGrPPuWeYbcjA=h&7*)4qO~*?u$6@;V^d-b9yaxU7{CSDxhhSExY%eF_;iHcQEQ z-+|ZZbL1K*o;{;P7`*NpsC(DwvklQ9ol!ww!*S$HGQCI8LyIksPkt$WsplO8p*HmP zOE)XAsdgMFIIQPxHy}Dr$tn&Ne}Tq-G64f#pNIyA3mt_6t%l#i!!4;PCiuRaA&2$B zQ|VW~6tq>Wr?Z?~4vox~4KFYIU_4L6Ptv@pFA4}i+7o-<20MC%{m`?OOk+Y=aPa*j zTuUbXU(dH_)T=Urde`W#w=)9Kw~kCLx*barSLx=&(0E*9U*GNuZKF8TEE+3*C*au11WCJR zIN>XYFWlyeQNNmdXK+|fioRL5qD&4vLzsq%eT2R0@HgniGH-3M3x1u;l$<;L)l?B_}U z^q1gd;Qd!b+%{1MuAv+gQJ@M|>6V4ZH+w z=Ua=7CrM39S41GSS0g;fX~4l7?rdPO68ErFl&l2ka^!SdRkOtG zJ0#}xbMgf64o;f#oZ9Z3JbzKGY9Tb~`3AR+H*5IE-SXS(*z!${U7lk;=C`-)+ zL>FDr@K%*OO<I`<{0t`iIDxkVQZ zGM z=f$jpRv%Z(bbR4OP4M)a!yL9L)XtIZ#0$~@omNxZ<4L`hC)$a^6zarZ^9EHr9}3V> zF^^7L@oAhy#rdg7eqSH_q86sC*vOnJ!9Yb~`%h-XDgYFk2Xtk6kA7^k&AeK2SQ9jf z9UIM+*qEF6ZCUAn0Nc5d#|)uC8=(|uj9Rf&R{-2}?Z-MQgdk2VNIO#A&Ep*Mh&pgX z{+~_1OAAyuP^Q~qFgxgp30LPwK5$FZUzWS)g1q7eW!_DC#)+(d_v}$0lqmemz5Q)> zP2y06Iq|^dVIS6jI)*+5)BAg;wcD!5$5QX6q^m&YYqS?XZ1(jd+bhUOY1~`5%IzmR zkofWTui8gVg%)0Qx_o3<3VVVZ{Q#M zn^|~d?2%K{V{tD`VIg_-Vb$!hje*#Jm5IY{sf2Xu>a*I5DI=5?FKt{}eyEg^Xv#ea3z|4|y(*8;>^; z75=O4b!Q&IX|G0{kn{W3M=z3AC)|0cp-PeB23A$yf(UTJ%Wp0@TRzGZed@=P_k#5M ziZZAymHnmp+a?Xp5z*94hRIx(>_qCfUAjPc+N%b7NKgE_r*`x10QDL0{+wxi+HL9Q za*Oe=R?)ixNN<6|46*yvQUf+4OJ89YPG+0^??mS`8*RAf+H$J+MGgNcc)6S01-9rQ z7czh<@tjPTaw9d8;It}zlpTINBK8LF1(5{+6bxi@R?=s_W?VTbb$<~~3Y;-rOS_Tn zpQm#hhbKHRsrvsTmw$*fc!LQ+N0VFRUKZzHp}7j}Ww79BMK zVIHT}8gI7K-!Yn1M*$wC-Wza&C31E%)vwG}vOKTof=JB`&_COh9K&z5XIrIc;loK{ zU>W)O4@zQfjEjlaMo9(;XGV9y1r|15`j28zlEuwyvi`$7Ub~zZJp|^T@Kd$M* zJCI8^Zm()AZs?zbzi+k-J2PIpGBaF7=dPz%-<67^P$z&)!rH<~Y^Jq(@;PW6^~TJR`%YjPZQ!QU zB8nJpGy0w$`?B?4uvM~LF(=Z)<8*bYBmbc%f*EkJUG}!?J*fHKVO~>Lc|&D3j&8xg zisL#zPBk0l*XB;mLL3;(JmRCjE#~nv7qLXDGf}=}k^B@R!e*oH6uo5>GY%B%1!Xj% zPe?+;i4A2L!bX{HvEq_7Z;vgLYb=+&9RBr{dJG%=8htKakiCJj#ORq3hZc>@aE(`2 zecyfe_;^D__XcUDx80S~m&#ZW-J2ar9;B`GKLtJ5BD(Qp4fGx{!W#3D>0<-<XaQ40dW%Y_U5e2Vg;`2GD zL$UF_6atW%E7mWfPKAj&d|f0|eaHCTAR=q~KjB(-oYK$g9zNf?95X61ZtKw1htn7q z?Az)pEsyJ$#rNl5yNXGpqzJ?=mXjzh)@AdJE+35wEm+<{kVsiJ@;mqf9QdREZ5Ob1 z|2xRchu{U~hG|UX~JV2p97h4VNzZPL2k`+z<4>@5`tKPVd ze-!0^sq*lD3yAt%wg~5&@j^ocr@}CJ@>0+@OsZxzM{81y(nmC%Eyqmmr!X<7-CeHi z|A)4>j;eBPzecwRf~2&xlF|aw0tyJy-6hg3-5>@?3rLrUbayRUN>aL6bh8%SaPEcs zeck&vzH`2BjC0Ny4F7PfdYr;I=S7;SXE;+D(Mv)<&1)iE> zkS0}|Jkr~*{5wacvzj)o2~~`@*m_gAJn5QrMraJ)Iztso_iGyTBgbry(zajU)4@q? zsq{BBl6r@H3GaK;932}R7&QYQb@?`@`5}eWjY&J?uD_uK4fujUWT6oUL~#J(V3-(D z8ZHN`%y-sWesOK{`BdJu{P)KQ>&tzAsfEhdZQJKHDOF-q0?ODG^ z`tvC~nS&q&2t?CUWN}aEQiZ-r)ex!-l)yyHFNXQ#66G1;-Zq-55UM=(iIwYtyD( z7*~Xy!z)fqct`~sLep#V80b9vT(XwB?L1W+PJ(y7B&}5^MY1EVrDLF=FNX0 zzszNz>lo(9c(H2#{n`0ViGM<$)6`gD)5wvLb=^U= zd$}!GJ>iP#dgd#B&MPDhZyY)qpNrFlz8F{?Y1(Bx{Y*tSACcAuse+w{2 zS2s(ko)ynxxayZw`drhA{7j7<^(J;;@Pu0NXsCDhjWr2+Q`4_3(=-kQJoDyvVBy6m zzwA7JG@aptj2eQ1$<-dkh{P^>)sx~I&Kq`V^Z{cmp~B=n(Mg@igJKi*D-Rj7t&7p9 z6Dt^_lji8=xa(gcMSLHhtyS_+>|7xQkMg81qI%bCneD~wS5psFMT^@BhXk<~@c!i_ z;IiFe&{#_>5GT(@bL;oWaUGx?`Y>TTODK+pXY3-u*BernWre9#2K3^cT5h!3g7^K$sBJe!x zarYiCYoH9@DJw6u+9YqL%p!6QrL@k;?%?XM6JRO(TCoM|*pSa6x^jC2FZu5kZn-3V zmfv~|1>s;{YgoYkjtGq`J}eAA&Qeu zs+X6TUe#F6gfHI6{cQfwMM(z@O!32MZRDdG(QE%6Q7KLQbD1@=VukGP7rCY&u8kLt z{PbS0HWTK|E1SB{3DOERQVX*1NA|tUxiasYvM}JITy-#G-9O3IUF>+HFwe^OvFZ-4|mu4oiZovfh0W|J44h?{gw!ShP^NTNBz8?}6O z6J`C@Gr#b*e-7M#Kh{B8x--{IM>;RE>zZ#5sdx7bfJN-c8A0%YMi(QTxUtT(Cor55 z-fN)OQhR2UgED(%eR|4NEn7orjo}sYrK*{?o*XMC=AemF40^6p{5k=3WV@Yfcb-R; zJt^)xLeZp9`T}ZFldOuAT545S{*z%_luF^`1ox?`{hinz_jagr=2x8;GYtiIQG|(Z z=GMP|;@=Zbs*W(LkxFGDb$U?L9kDsIKLphHa=1v_)r!lO;K-fEvm&Pqktss){ZqSv z@89d$27!TD zN%afIbpzr>=7HC<4-_Nyx%=^&Zl6u|%joH9-hjgcfltYh{;i+I;>q)u5 z)AatwMvqA*J_=7~1lG~Rst!kOwb*Dzg3B>h{&LNEDO`JAR@>zTFDtK~^$MMZmqtA5-^13xXtkV1TQ?J}h{&xcsEX=s#ld0LZX%Iwt zxA^%x68r5Tsloue=MqUGGbBGE9EqPqh)lTPqAIvT#jVUH_144~auuJ_5ZAb$b|!C$ zDw#asbW73>s{P1To!J{!4(SLHHPC+Fr*r0&#deUEzWhc(`JFJU0hv@oW)C&|=t+#x z&)PMWoyqg;!2iM?^Z;j+_0b&KxpKAOI68ksX%1Q%_xfZ{eqBrLO zb(05~w^NN^s|J=uh>QADu|@(?>TEHa-=#e+$aI27WcIwswn~F|B66LWVOMX%r?G(H}4hqy?}YzE3L{ zGid&{_Z`1hG&I&);1CtfWyn!Io$*WV=(i|}k|aL48x=GU<2JSE!UHWbj{E=N?hpnT zU;9?u{2W358s@5bc80z*8n+i>)CDlT*rNFpV!ed5tgufW{G_Da^Gx|FDyG&*RJPC6 z4A~s^b-|o@u9i3Znrc$x-9;DvV-9vI1iyfOuJp8szBW~6~Xcx~Ia?RWoy=T`@`2jkS1L99O(LEpZ*J)Bu{KlY17CYWZP~0UG)SkX zk0I8(+m@o=XPDqPI!Epe=(dZd&)?)ldf@s@NYJtHvN$|~%bNZR6V)|f%yQQ+8OoI($bGK8FxridLF55gw49V!Q|Vkt2=JQ z=eU&S&rhA|1-)1AXk0k%6OoM74(KdvBVR>3d|6$76?AC&P7lgDvz5MTT0?>h=(w)w ze@+^Rcx{KT#~!&hyH8$46T9v)@pinM`yJs%r`rwa`rMWk zFd^%JDWE`}{o-iFd-eoHzZT{?>M@^^-)B`yHhcD0?xOZG2|d?8=D7^Cmqk-2xJyQT z-fOa8XOs^OB2GIoDe9ev59sXba-}sBg5%20w{O;R#3B+>uk%gH-RY+BWlLf{>5Amf zc2J*|qz`y18DU3eMXEy8%88u0XHqMhrfBrm(%)}$$kP9VI#z_juK4bCWcfL2*6gFz z7R`RrX}`a2@ho}SY{Qt~%*&l^4ycc0|K(zH4e%(j-n=Rw|Mg~aJSZYHCyEjoh=1P5 zC7`)lU;0g_TgsqEqKA+}deLnemTN(9Syr8;j8xI25_s>TgD)u9Y z?8-lH{r^$20>FO5$_a@z|6mdxSm~Mnxzb;Em2kNoU0OowdyAARg@_!l1O+eJiNL-g zBOwcIv}sQd#(rgVj8n4zGr?Xeai_Vx6rj{cF&f&#r48(g)oW-+2P9K7_5PDM8@ppu z0_o>xd$d;qr9}~9h20S}l8OcfRL!vCMWAM`c3j0KB_pHR#{o*e!a}x#<73H65zix* zhVz}eYG*m)6qd4v)i+TaeE1ZJ?$f=!y_2Vt$5Rs%BvqkFcDasn<;mXHHybT&$`>Sq zu@jW5qmRe%aQ32hk?4GT@tDijU@GWEl}2a*Am05$@|&0$H6mwr-^pvIY$EhjwExPYw+lO02m!JDvG`E~?X;CYO&!3v~Fr&z>|~>~(hv zWHu!Zy+g}?mCPIYT|i!?db`7dO@3O=)P4a-aY59$VOoz z@t^sDibHQ(C6fVBSuV+sGUj4Fc=C#c%+kXWnq$sEzr(=#C{^SAXAZ(dAX{};95RyC z9-P~1-TFy%kT=Fk*ta?Dd-hA2pRB>uo!FlwB_z8kmZF21@X*a`&ZHaSgnY%uwk>$i z#@>!UEY;TuoiGEncvnY@ZEEM^ard~5#JgrR$O8u!PA?!Jvk|~Y(J!WC_}!zq@uE_B z+OUpp3mNv<%bQ%5d;$K&_J9jOJ!>NKI*nuW*Ye2u1SFxdz(+%pfCUzAW{jfWbyj5t zKN-9|PQ4(6=GQpoj^4C*~5@xEP5&iQkc) zx;35&{?0w=_sILaWvGbMxc@^d|MVc+|A*J5dq>7FlE}av1AX+G`jNl{<*HFmnDS-} z6*eh`6Gx$dZs@{Oi<&@{_?1dWzr-c~GWOz;uYR9KSjTMx$8k-q#`po3OmxbMo~*DegfL@8)6dWyr^45$ zE_ZZ|^)pm>qH!`yHw#@vV8Xngw(}DP=qq>mB6x^&V_|)$QHozU3%4xP>=BDd{+u4>}DZSwyt?@^$GkJ|K<+}he4@8?e5 zzTu=kaRL30k%5yX9{bClvF#b5oErgU29}ieq*%7fpw6s0+SW6)D`jGc%te${6gG5CG zSAlwDklM7fwIbRIMgB)S4oU81O8Qjw9DcL%EW4aL39PKWKB0$zh+xSz}ux*K^YCQJP*_$5J4rH18;ZQvaM0cl^e(m_>$4}<- zvSsOnWE4b4fxok>BpoIRMufuXRAs%dU$_AS^(+Sr@N!;Q5H1Ifhi25e{Y2^JwG(o} zSDHPSA2x!?``9}igpjheH`;$l*}7Q?Mjd#(YWD=%X4uu+FmF^iS7K9#Q<}yPwE81Q zhx3wJE&bRYRPBByhkUJ$Jb+_kaS+9vMGxhF3$@86#{7jmj)oVvs`9udb2~mp9x?Sz#qr$ z=fec@PqYt6=!I$M|4#J&Y-olCVENGJHSn(tjKnSa>WHa&Q6f!)xD=GKU#UOfp>@qUjo0X! z;v`7GX_hl)LL%*$TjG#Ar^!k*uQHxT$8J4_KUCP}z=b4m26%mM8BHw#>{YR&4ok*? zDV=jIQNwp0vPiOfKIMrLg=Yf~wU3;Z08-IwU1skCk9DRw zQ%pFq=-1MhB6bxloZ2F-eE;Yke0aWP46P-bN5m?~2>9>u-@@DhyS&=U%}>0Op@w#FzdNBjLgm+MSz0Lt ztf%YwX5rSnMRy(=aV8Lb5$vNnEmw6@G)lStLE?We#@aKZsPJ2&(C;e?nrQseP(Y<9jNX~$P& zpRZUA5CS|B8u;Z}{$Yy{BAp->uk#L|w_hYE-no~r(VZn_)j&2o+4qv!;f5l2wU}C# zKWZ8yA0_@y122`=q9A3O*2eLhUe`Z)Z`t5-v24x}{(Nw!^~*=+fg_(B?Adk8%ctEu zk>ckn789Rw#=hU}OpXb^z-o7&+Ypz_(V?g!Z$w=V=%rx9DpDI_c$clxtrvV*t!aRK zJi6u}rIUd%i+^-<095$(E6*DRP~8ojlRhrPU2`r!0B1~0@ZI0e7@wM#Rg;Qp=J~w! z_tjPoO-kAR+@EtxKi}mt(VnSeb`FTslGb$Xd3_17jJS&oS)}VoWv4Mh6(&8{>i;+Z z+ijk+ikPv_(Wvc~SR3lQY{sPfgkv{b%UBgDGiS zHk%<9?l|jOuXgxU0~WMK28IQ?u4qeG;SYxiSJENBGzIRxYgDc8P`VU)0oCh|N$;E1 z0G5iURFgf4=nheawI%|QPB-c?>f!j+fhm7^M{j;qpi7t%aNh2}kAJzL9lXY+^+zh- z>MeA;@hO}Xz96GGInaaJ>0L4rRD#>6EW zi({Ibq|Q>N6fKV*j;(VmtI&hBZLQRZdnZ#>cIoNqw^Mzu_DBy)gv+fFqL z&Vvb0;C_*O&&R@gtDDonf@(4&yOjA3?ZjF&v0$G=N)oDeM-)Eyh^!ATzA$D~zcbt1 zfRUQX4qxGdmxNpA@u6LKS2W4+?m0X;Al+9(>h_m`jwlTir@5u#jZ0U}wy^D!YeQ*B zJ~U+wAyF~FC!HBZ^@?xlcQ{6UXN1QfyShEzy=~+(%`c8`%My?`aR0;Su3AKFY_dew z>E9%uq<@*u(={`w!PBMYW08XwwY}E_(_x;W*SQai@X;aCzMh2BM@nltdGqn*EgBN;0& z_%Y1{-@E^|GM|dIg4>*Hjwu_kAZ0xNnWhpkA@|(MM)ZDMo*h@cDZ8$1?FzkZ4J$LAxudJ{k7) z`HJ=TZcgs+kT!2800{(FdeYTjKdJhfKUmPg1)amq;s{7Kfuykor2{-%PRds#FVP;x zH)r3yIP+#$bZgiN&$cUSy$Tb_SOiQimM&yJ;)giMNQeL=@>p=$Szzy79gX+vJb(ngas;<)yK) zC-!q*wSuNTCr3DLBiDqKd6q%(zKxiFpsG6l8M}b2OKR=JgX8 zB1*9>+=<<AhyYv8LZwofZE+UzG3@2irytrFSl)}`@%aE-2R^wiX6w&l+U0Z+z? zf4*_dE$g)FrE6j{hS$A1VuiNU%B}V@5)`-NIJS8XRu$?+IS{SLB)6fEu3WFLRYzvm z^Qe92t8BL~mLI!z;Nh~bX!9_Vtic(Fwq2*^i?N8~Pty-RO;u^TK=m%xrMUGTQ`mV8 z`jJH}(tR+-7_`yXo+V?~>$nPI4*N9SKWR@C&}ZO0-SLVJ=?-6r>1lMtpqL_hwXr8wZ7(KOpCDYWtEO9(BWG< zc@d4K3-Qx&wMG&-w19Y?P}?HwlsM-^x)pf(Ao*3n&Vvu#JdW!4z8`tg+<&&bm9=YM z*MRkq*7NhI>uC|%#r1OZrJK2uHzsHn-qL8Pvj5KYVb#SOx9U>yKBZq{Fd6FXrz6^p z=^%b>9J)*YRn>Gabe|H!HtEr^RJG0GY3dIlkE!7Z^W??{^-5rd&Oe--e+QHiJ;fi8 zsJhz}t_Y%C{sY5$hpUw@Y>Eaf5B>1`9;R&*GMKDQBo?-Be;wP(U1Vwvcnv1+l5SLV+;&~~|RswwJMMLi>G#GKSZhI-JvG>Cg*{?RoK z<*K(H&9XmpFJ4u&l#s+`R)7`HF4lFvF0tzK@h>>0DX%q>?=evr8@uB2pwjSDv+~j` zJU7JiQ+h@#JhZI$djbM+D_L2FJetPxxKPw;Vha1){I7+C9oGZ+m5~iS|1L-{X{i zS+D4m(P^QT`Jd|!r|yR+u=3XSCw7*lfCjFsUAFW#xte=&8D&-m-Fo7T+i6$MBR6mM zcL37fDPc}D0xncG+b0EMcifrccJDCF8Qim(51*)-3@~||y?_%&ZB9awdM}cq69(y4 zXwzEdzk6Xfdgsl&;YHKn2MY4K+s^MjzTsn$y=<_X2TU=3zNP7mjay7na`%UN< ziO`bkEHAgD?`{31nMv=f(;0(>g4R|ITP&TM39pCsYB^+G^ZFyd0kNC#lsuaIz$XZf znVt*nD@0lnm?fb64q@99gzc%=b524qH5vLLGF9#t?o{%CI?Ka^)UKKop#Dm#7%hY& z_CqP+o7oj9d+!9SwHI))EyuL3&(0=fStc%EW&SKH?z5`r!z3?}7<+4TDgJnVgU9ag zWgkd~4gKljAHaccS$izYiB^9!0`bOW7fpA%4>wZFkFtl$C>g!7yp+nKbapIKW*jwd{30~_TSSVabbG@{LKHOy`)Q%_;2h) z2~ZT`2miMUAo}yc2!#g?0&2w>{#P*^@$v6j`r8KD*(rK6T{xBhR=uGGE~wg3qTdAq z4L?tF9DR}}fGbvzkOu3$;eGID%CfZ5n|Ry{Hc=XOmX3=GZj1fhhw9TL^ofX1x_MFD z4|}}5X3kusRNE`B6N0}n>+kpbOOZ1?q+-EQh2A!BAt`ztr47|lyn4g7m)bcN{}i|8 zqcE~)`+~6ro22}UV2@wnF+V5Z+{c^y6j6NwI1fNm&V2P%a8K$|kS_7q_rN!?qhI%9 zO1wU=+ZDBJb=ACK%@=!&XgO{)mOgO6BB3Wm+zm{`A8lCFXPg)%;uG&w-?U4OCGXxs znzhR-BJ3wsZ9sP3pZ7eZ$Uu2dpf{lMmXUx``#Udse7C8KH%9z>pdZAOi5jI?fVv#o z{(+mot^Fb5rr%ub@5=#)!W{dY-A%l>rMfMM8{yhDHm4VC_!6lI4?#DX9gL+(P(2;= zNLqd8X~C^6`@W*nOy&jl&%}yYrW@dBW4)MzAdhv-9)!eTQ7*~5G-r(NTbrxstl!;H znabTRKPB{W5_mFsY_OMp;a(A@&F%`!7^eGQm%MTZiYqi(yGLTwDzshhp*Lw{udD*e zRWZ8F>JYJ;vvGF(^7G_cO+++DdDYikMT%Adqx#N<5y*O5ws1O?in+nvJqCp)argeQ z3$(6=UMpMtlUMcjGdH<;D{^1HQq8VAG+16EFr$g4!W~o1C@}2Lk8HLk%21=DqczJ+ z<&=#nv@|Mz-x0&Oh~Kx`&hL7DC!EizR)`w5J)`m1vyJFtdw~$wr{WbqJ(~xWOs??A zXrKbMUn-J89o{k*^Ov^uYknb~{9nhqXT09D@|k^S>0PFh2;$BNe4m@80W~MSiZz zd^e2E%FiwhLj{;|fTgWh=cXH~c3>(2l&`WX%D!E3*nm(2Q7&?G(Stj}F6y%}%m~DZ{c%xzH-+8rare0A zWrtKJYIinpveeJbwdWsKnJZr>(lP%U9HE$hx#`Yw0%t$D$g>NnC!bb; z4iV}!(@CainXETiY$Eb@%x|Ki(n}D71#1l_kkFS44NiDm&$HO|2R5k@dAlh9X?KN(-%{w%2oXkW$4W z9_Q95htpa0lj1kc#Dr?Xe`!r~XG+qTjj=uVCT)C{u;T%0V2;}}45rPaQ z7xAIyRBL{7I_TFXMh!xhoF?Y$&%I0b8(kTBXUHZ)8LiIVlz#h5X2XAW<8!sFynP=~ z&z|z#t%=UfW$HLpm(YXOv29MWvcjY5!d~_LESN0+1c~-#)C<3;$^|>_2{5H$JIGS@ zRX9b##MSg`v}t&#(^Iq$XXS64OldC+@U$3I1E@DW6XK>0|5%t33Jyxv{|SNH9wpe$ zl7DJ^nC9#n3&ZZ-m2k)*wLO0t__FeFoW4d;_1C@iz zCqeN`6&RVP7dw_GkWUs6MXbO;$t3UNU8E<+&hUJ zleR8^yV~zvA`$eZk}+n%T&S9JHNLa_>JA8HjZv;F!Z;-SXxYG`(VYNjGnm@B!fUlO zeSKne^5@~|D_Y@_Kj(V+ly!1rRgOvnmhoCcy1<}*6@9FVC$hl`U#)%TX4mmtZk8^y z2_|xAf*f(026!9Yb1t}<*BhK8l9N_v^EwlQEVLJb-za8Ux3SPVjR(RvT33D@&?v4r z+&dx4@6_=lDjUodvPJYk3)f8lgx{vAg#08O+foR*m_qwC6w>c5BNiT#;YW!D& z--8RC^#1l^-k8=GepExCo~53VBxY%Lk&9G2n=G+!c1VJx>&s-gT=kh}@OU9-v^yPF zqG*;Dbbme)F83|}t6O1XWT4C{>vd)>^r)+mk?u^A>03*3X~k+-?xQ}fcgZV>xj}|Q z{O-iuzU{hS)Kf=}nb!pammP;o5>O%|_Xknhcd-w?=Cx)+EcJ{r(!uEuFV0keUC>t+1EiaQX8if5&f?|vuiuSmrXzgkc>H`#Z>du}!9a?pH3^~&Zen0b zjKqKxXB)TK)%L6#@R?wdRXsiVt|gr9-N>i0-TFvy_?*YP zkg9QON?X`jjriNrS4f~7G^;Pfwx|K)KoVovDndY+A7xOc5I`T$P_({V&m`<+m@e|0!1W8 zL1ye1MOsWd%!vmR+8)J@eoBlA_tz^P6HoOfhHI;S99z^?>Uf5)-sMgj#Q|~-%U<7K zY1mtq=qaCmYBl$m%Rxo?ssQwH?CTSZx?UmA#+Xlh5rwMNFkUC0Mtr&Dsy6X84Ca=~ z!(N%C`b3Ty11gxM-`V>qXst$zW~wM?m_m&W_5*2q+y<%fm01rzZ^4t865ZEv9Atmj zml=nbPOH4t=ZkOYx9M~020+HFMOu^OP+cRkN|t;m%^= zqo>8Aw;O2Ort~>9A}CQNg}0|HI#Q*f$;``gu*ChIox=@Xu%e;N;t|L@6P>8061F}* z#$V^O`(hu_ym>&!aUHh!^Hwy-2x=+qs>%N>{!?x}?Vtxtz^}9Ye8PE6$2q6nt92FL z{a$GVbRRt?M#kVSEe_O`vg(0J0Ol`r8hD2f(O-K1O-(-4c|_!NbUb_(^vV&#TLR>I5f4pxB8o0VT_^PcZ4&QQ^J}y>+gezPZyYn z;2GuxKb2`=@J<@6#gruw*KN@r*=B0^=1Jm`tQz^_EmWsQmvvfG`&Gp-YGYh6Jt3*@ zJWueB%M&7v%C{~>ceH*@t8*m8;BlLq37-4ZOQJr&9dn+|nvDm9AlLveap!aIH^0t_ z+_b{~-XKn2kr3YKNdu7Vf;j~2ja8Rw3)i060SSX79a&F=cpuE6f}(OwQ<759Yt z%Gq$4-Ky+FEPF2bSrRRy3McG{dkTOyH0` zp(>@ic1I0TUR-5wb|QmvBhz4sq%*ENhF9lPQX z|K4C5dizpoDD2V+XUtAd>~M8+enR_gmPo?46N;28zLa<>g=VW%wjUXrcRN%}?<0g` z{{_TdOn!}mZ-^P#DVw=;=odr#uQaUJ&s>A9ANp^|C$lDBV>`NDXiHsC8(D!YNTEOQ zGASSLgvtl<{{6OKiBrFCYe1H7V01_3*M&|3$oT}p7pjzZt^_6lnb2*qMzK?~##TYg ztB}xe5*@JIfvLmw`!>if7)LkZOrPTt7go-P*7sWuvVCj#dgSU>VS7iN+~*dvh|c#C zr`5$rc5cWf0SwH3*JegmS;$I4La3*Sf}!gN{}J}8ie|_2;ubROMHIb}Q!Yw9PMcS5 zEV=NZK0o@J*%4 z>ecO^S<88sGetjCpz&KcI{)LrJb7zX;# z-A6JQxe{5}?x&x}kwR`L7({^uUUmA?tmpWd>4z#Wm}@gR@nioT>k$_leld2@F7T(b zOZ=1&3r%45-X~%=)_NZT9Xsj|I*Mv>!MYVi(_NIR6$u@q?;MI6Xzqy3L0-el zLG|^pc+)sk!ao#?{3zPRNJIDd@B#Ku`5?%(Y}E0dnB$w;bJ`v3i?=aQseIcc|8V*G zPm#6j2~CaeV5rZQrP|Wuzj>OTgTi2E1b5Z3AL*B z(7W;+T4+6FM*3S$9AQrf=dV28uzPz98nW6b`rx#(xo4kaT=$c{x7D}s1>LGpsEJ5Z z;OM#i?|bVb*UCu;yQcINzV>b1!*Be-d2LO%eQUesBnEod>)snfx$f(bG3<*}w+7Qv zdbk}(V#hrKetps?Ch7wfS$j&LI|6xo1K|SMPk(EF zXT{m$w&#(KlvwE6@jgm5q~$Jrl^O z3vr>GyS~?6#oN$aQYd78mU(MlkT*mq22QO) zj`Y)9F|YPxy(g`6Anody=Wmvg6V&uaIL=H5%l5Xc56d}2o!G5iuMd2seD@YthLuLK++X{xBPLV-Yj>~ z$DS}uw>EM1iZxR6<@3EuF$D#9TAnRG`iN&| zg?8x~T5V`zoie{c#Wk&Zjul<%$I61mP+;5aSU*hvQASacI~$t3FPB!?SwX)kwA?uZv-)rHcqRDq|%4ADQjp%*ZAe$xxO z!`*ubdZGA+Uhr^#qQYu58cWAUli%S;)UfcIQK*T5-DSPoAO^HnJI0|+$FtaJM_V3{ z;&a$;!gW>9b=Vlr#T^WnD++Z=cdWSspHM2;<)>7iGpY0XlPf=};RC(x)zx-+f8hAT z1x$5Pz`l~hD@|Jz(8v3TWEbc}rB{qR#eSc#u))Pzx0sv(mOZANlibLqor6X7JcnbZ z-sV<}^(Nhz4YB^hQ6cT)_}&*(}dnK0ATq$Vbf3Qm55{kN7gx)>!Y zg7<486yJH2nXlVO#wx8-$XX3fR7ktc&4#fpo~8)XwQ=?C*QYb(JAN@vWT4tRZ zj_n9*nEoWHQj2OVq-GzkIy!RbDWNtzQp3BMHgT0;FWt${ zoyBq@D#oa0p@Zo##WbVdtRL_2s{Jr#y|tU8Y~lM+gM$#dzUnzg%nhJ_m8(Mr<^we+ zIcrwAll6yl?tO``a?n(*w`!=pzMq+nh)#@=Qj2wE*x(JOj5^ZxKQ3083t;iRsJFdVLD=;k z|0IWlb#>B*dBrkv*0q|zu}X_+4UcYzYk6FJQmsu(C%14eaxyB{Azwz2%|3NU+&-014TF)sw>#aA!F*`P8Xjem-m*1%B;Lbnplb}~cKYJEv6 z>`SKNFAk>jrF11U!zH8LFdEI%`7 zo!T||6!F94gB;XmIYi4p;|$yIKE0BRND_AIHz<`mOjs7hAr z&<&!|t~zPfqXCg4T*p&LFkOB#aCDC2K}jPOK%xKR_& zyU+V8sw7iL43oPa8AV7zkvpL&IC*IutoN$}Gj`VU`y?l2xenG(Zx_jRyZ!Z16ECMa z_ow5oZcpg+()}gw$Yamu0Q&uSw{^pTSC}4VfD!d%n3dnd^g2!%j5C8W0!rR=Y3>k{ z?uWeybF6<;k#$gv;BOTF(eDeZBL=waFIE=lkV+W;uf+GIvUB>{tD%zaw3F2o{!QHt zK>tO>pB>8{*!>B3qDEzQDOw)Oy7&VYU$*_jKhXOpOv{MoNc61&NPFk1B^imK-e$XP zrx~YT?5%O>VID_=@h~=Td24DU_Le#owakpf9K1zh-c|#OZKlcnQ;7;H@8QLGORYL3 zcDWi~nI%5gl&FZ6A(D?i{2RT`6UtvMS3byGVD7Jw>{ID}P#7WZDMIp4d$tUjOeB1V z*@eJEq*g=$z-|A`&hfthz$B41_6kLk4Yew$?95+mOrtz8P`P$lWodFrhpwMHa1nJj zcJb%R3Qd1V292X1%L!_6Mn>1JEOUCM-;i()C=D`%)Sswq>qtb+)eI82MboAa%%wKv zeZLu`%>NmrX0%~=y}#?)8!(*&DisZN^@Z8`179EI(P*2v1DgJ7L99aBm%)U8W1x0^ z1peiu2ZgEU(N;5<--D5ET9V+!zY)@X)9+mL$iiOds9v7inH+78Wh?3Nk*|)nrxHHP zm?M{3O6$-VAWtz_*U1Vh>+tsIu1nr@s7V_AVE`{@~pA(x_X=ja^rZ4P+;T@`f z03&sD2-=xQLn%G=hPq!By9_z*iGyZznaKFtJ&V&vc9ahT`;q-%c76)U>Bq&(k32}B ztOse^KJtl3{g`41?&9VaM)7Gez~$e8^foCEJSuj$w#Vh8J{3;ESF#4V?_E8By_Eow ztaUg64hEw;JF^n9fy)5_WRV96aKENmFN*FM+LWJO#zs)=R`zW>SbS90> zisYPkl3kVMk)BMq(c(M4*pz>*let7A?M8%;rJ{W+9L5@Z?K|E3uF{#!%`$3Q z^gdd;$$LqPDjDB7EStym4H=@z-Sd{t-b)acZc(2vvfXVTq}Y~3l#m)r&eJGqxO@;J z<8PS&U_}&YS6uvJzsH;)-!#iXC)mUOcSM$WNF8=tS#BD?R~}BGiQlK`Z!>X}YvPA2 zIJx~~GPaB*smnl1%=+JR*_6p_HA;m+_{L;8-U%by7i(KF&CF<3RHiRWb|WxK5W|LO z;)<^!nz%MB`1~$HI$^akliyh#qMj!!EPY)Zs&VIe3fPYQ4M`nD*5sNBDg@S%5C>=GdG<08~ zhmPn3s$Ml(OF63%^6l(pS#?ZbFQwj2bh}34>q0z>2n}l34Wh-3XPwPD!pA*%d=b2_ zG-Hzgigq%$2%IiQKY57-7fwSqm>M--5C4ux(2(wT=9iytDLfKvt-dvXpYCLcwH}9d zuOawyL+=IDEWN$f-pKu+{x$NIPgn#=zac_Eb2EHs0Xl~muXZ_h8dN@ya<{^}d(llb zX2Hkc3=aES7Dt$24PBCne;re*#R%OaoKy6USma44mOyU6S99NmQ4s3XV|eE;1Pyod zfL!UIIp45K**Qt;F&`^H4hL)~5>y#{v}YOo*w8+imc}2^G}q6;xp*^^ZyvW4V6xl- zqkah9^>T2J{qA!II7Wo&>!sZ?oNGr=j|I|4X3|iEBXO<#!s(;QL6;Nua`+Bx0Lcz) zYQDZ5ogs7oq~YYDbS{Ba7+#j{u|F`prXL+#vTE*H^CM-pcJ~)igkf)(23hVdOk{^0 zY~iHY0%u6iy5HkTY}5m@85*P_7aQL@S0Q-UVD-=Xda{Vz-;`%#me~JI#skUx@tvPC zj2)LY*$KD+kGiMfuv!u$q8m%me5&hj-7@0LXo^J{F4>4Wb`I&O{wKA-jG|ACXD3~! z23>ZAs&vrsRo42t}WCJOo$A`EXTvWH@`|2NKLn=s!9Mos;aL3x;KGa>P95`w`RpqLO_cdj+aGr z@*};^OeHA=xa&(&!NQ2U2RbA3vml-!>Bs+k11sD#A4!A`8~()J)Yxvo_G1n!Hk*jb$!q4{GQwA z^AQ1nE3#&CD4N7wm1RxfD0LxCBGi1N5xY0S*+;6Za`yEa>c9Is>R`t65$VSfwmB_K zNd|JxZB%=^mUsgw3%@xDOg^n&)%uDKI4uCU(2!6Kls!dq^Blv(v#w7R*bt84eT!r< zeV(FXx<*-M_r}mrCtsQh47XJ0toe?YXoF)q`Tfr`ma7##5+;J$kKrEH&CT=Uoi5HZ;0zO9k|> zP@q4o)3AzJiLXF;;&g19MrW*rKb=yySqT0!*c^m@TPJ6aRg9cAyKHHf@%IL`|0vtg z$9(a{+3y{ixGwxZTI3F){IaT{7d%Fgr|rXd94nz<#ZCLFjg^kq-4e)H~?zb0YMw4S&qmo~Uq_TN941V7M^1NXu zAkUL;(+{o}9A-URSi7ZeswNsBmevtv1z5s{F5@4=GfPUoy?6 z0b%P!?u7J!UXk|a8?QY;)3|)H$*eiSis<$_hAi6IsUivPYbYB~x|ZZzBL!neD?_fY4lWdv-$R?XaA z%`hC(!mEPhepu@CYgw(%2?9&10iHgf<7nKwh z3YUW@3*}Ee;Q2zMlFno8EKFG{OX)>Q)6*3UI7n?zRt}qlYC$`tSXnMvp|oy6C4|K7 z$cR!~Gau|@o)q#yN@rT%*ex-~CfeyLtJ;zaYTt@A0ZCD~66=VUje%{sf@R`KBYtdM zX4?k|sxK2ahWBd6L&!D1bv&|In&Mo!)s|9ACG|-1UQJh5^EfFTv!|>gWkP}pGq845 zfRtjy#gh)G9cZ8>beYt==T)>&;K&d;A0M)!$R(*aPvY`JqTYk{7Sbr5Ly~{Z-gX^D zWH$qWeUz)&5D2NMM=wJB#nq)hekkYP3@Hb?jwEo!QKAI>roHr!lKiJP_h-9?-=Qq*dtls|B(c<=qh);`{?8QXIqeJHoEgK*qQ0>)u3A50uf}In)Lbm- zFeoaqC@gl~qZsfwq{2}@BcS?e=51xgMZ&j@YZ-t<7Tv~8Ef9pqs&UBE7#iGp=dyjd zQ5DzxSAcABoYAPijEM#jW&_cXEjl%K`R19y&u-Q%ufJVyOjkJyLPW2{>Nqc6Nk03} z1de3E7X2q{w{ReZ8N&3SsXa2l*r=u5H|Y$mo@F8L0gIhHs@>w2rHIP=6Lh;bkglwn zK+-hz@K~7OnfaSOfST;(=cm}Gd8KN=mc9qtD#xjXYUt6lubZ`n_~!R8Dz^oK$c6m- zoS3OSHaDAsT*kLP>z8m479i%zZ&>*%Y4|X@yV4HxhieH{VXI1#co}2CCgIH08%mFo zJ__$?97p9g=q||COU{v=ow@TDs0YHCKn)pzC z$?j8=Wv0{fIxmYIw9xbMnMkFuYT;M!x%Vl$l)?r8LRJa-a=S$4ItX^!v}%@lpk}RC z@}&)Lo^yKT*T(A6YVB5~9<3AM3Jdo_W!=h*$ONilb<#LqfaJr9D5>=as=qr2OH6u~ z<(Ng;zuH|_wZ2a?PC#qS4Z7PcYv?Qqc5T^)UpHI=6$OHih+ z63i9oW{G8LQFtB>*NVaVc^mRSQKKomcsy&V?>5Q+B!xoH_3ju0c}>A07!4|5=CABu z_I;?`wUM$1FHX}}`DW#Q;U;HVM8dhZMr(~4eO8Ojgxjssn? zBLE;*^)B%zPq1Acp+ZR?FC`9R$M-0V_mVkYz!61w-y1*nODQ(2LQr0HMo5)gmm7vr z0Tb5=Zvb@J=4Z8-ay*JyzfgU%JN(BE-t&WR>%!ksf=_Qezeu0=^;w*875eF*0GTDV zL9Iw5N|PT|Vmy%S^m~AVN^N&48cQe|!3n+qhRNI-QJF&hSa? zN2}}{Wm}5`;^@2av+vMfS>-yVId%rkiWL!A-VfQn0}L9Sx?#H%9NpYMvNBBN_B&76 z=F40JkUX2AZvhtP)zR7km(ma8wocn8+oZE2xYz{1jWP)4-!}`-h-CiD_C#io6-jTu zVOZ--Tml0!0KpnoFI^dYy8w1#VQaBA8YU0M+McVeq}+az<+jLECrdIK|Ju5|6IO_BV*wlSA^UJI?WA0#jv9 z7+b#VtIVt;=gse`rvc_qX<+~>qUNKPhBqrxyVa- z28=PCf8TmYaXh5cleGDe6GNgUm}?v=?5vPij1iOz?Liph z*O2X1a&JDX^pi;>ti_x2w9UVOxD4U@k{co~giR8a;eqlo2=|{o0B;lY+uqdh?>~Pg z`RXnqHK!Ea)2btTz_P-$Q0mbpp;%S)9rSzzUW%WhM8)|^*K4+v_g`> zp(f|YkYIG>j=Bvj`nE@-WE~xAR}_I!+VEzTndU#fNSU*@c-10*Tr)7*@BOSk1I-ik z2-CMMYG%j|psW#W+!f!KZ2)Jw+hXwu0^(EbRG7C2Jcf`RuKaxnM-@}FpWSNMGp=_Z zhj^b$GJ_5@cF7Q;UQ@8!TcI6wK?n==v=oz?j|W1Kf@OUQ)@E1{x*42hasOXF$O529 z_}qlJ>}1K1mPybV-x#E((ikx>>z%wE+UCF9~W$*~{~=v{}by zYE$ttfvw7CFLz(xr7^NPngcuDZ^m{LeH8lS^DV#F&}2{jSJ6t$HN#TpOu4RKz~w>L zU$ml57m570OhQiazXqH-Ug5v9NB~@(Ea}|#T=o}SKVmAMRqIgFJ{^Mc*$m6bS&oc%N06{HjEsO5iIhHJ0nXfO#A=IpH6%7*wgVl&Rl15lX&AjyX0R|A zlel`;v>?(QXigp?{76?<5|B>f3}>Yqqq(A`f~s3+>Id|M0IG0ytFGys5+2XdVH-Yj z74cOeqm1tvHdRg=K&exe>w%Mmbon0jo0oqPbo`)5b+8s%di~A(HHpImAkI1~20*_^ zioyWJ{3mK-r;RkzxSqc;-dRxX2Yqb3J(i(gIPYga44}?N>lS-3$!ql`1^99cuDX@p zn&GUd3TG`8_f|b1$I)>=^4gjGXO=jiNF*$HjGiMgkqD;Ndgz{j`yhiQ(2jR4CLR?$ zKy*0Pc85-9>Gup1MP>7)!W=z3LJK0z0rn4RL9)t_gV8JL_%49%T86~RdMF)ri=0G{ z3HabWR8kCZtXih!iiV(Mi$1yl8X2lgQd+&qX75VRxD8C%T$*o{BGfE+=)67kfCF-dDIWmT0<+Qx8_QIvQ5buNiSj{uvRn8-nc zW|RUYe;l)UI8y?}>l=fJUOfNg^3;KxD70koBGcpOG zRz1D(aCMIZ3di(W8Ir(>M|-_1FpkE>odXPWVf#NDw7P4foKPb+Dl znbd-T+W|oAsqUI4`C=a+2+=v5bDNYFh?h+%d|>UzdBBFbNNLN~i`*?UjR%saHwX5^3BMcEy7Ucy zO#)~&At!Z{{F9BSwRVBXKBBI=FRr(*S^|_wF3ZygP&IPJgVdMzXi?YIzA(!^UL*CpJMR4k&5=Y{uGV zoG!YeKf?C4_RGVZT*in&ZpGj#fx_oquw)G9c{iAWex|+QLcy|pa!%!H5m$<{fAZ~C zYR7^eY9o$^sQH0xetrsa6cp(nDA7Amd#2f6D|P<8GO!IFhOud;0z)Tddk2jCyLv^~ zfuW+m7KkPg#bj~qR-?-o?HSfREkbu7>4uQWgUz(Qa%+*gy&;K@O$U24n>}6wyLz~a zHg1yOhS}?DS$;r)KXwyO>n3u@7S%-QUTervUB6sF95C$3+(HeG0DXZ(dN)8zf$})m zIMlpgU2(XxzL!|#zI?7?O(HvR&(TV-J0|`@aqO-?I^T)jZFP4CcXyf1Z?x4D0MEqQ zXwU)}nEYon3H7H5*|K&v?jSM?f#7;3Lrl5Dri(Al=fW>%oaaJ(&+lhc&;tZ6#RV(8 z>xaW=LS6O1d}+_0;mQi2cvJ^Q(6Hi}*2h1xxEZ&?L!<@iyf=5T2&L`QbfdXx2DRxc`;DtE0wGMoy}~^pBM90c5EJ z0SDqUa;`eUNk``7*LvFrB9`b~Y|sI@h419_%dV?i6RXYj>L45bZ2%zsXV_~_17w}% z`$>^6tldF_2yhg(8czhdZ8m|X(*~Q}winE$VOUT-^rz4*FKkYQ(Lp_n*)lM8{6Yh1 zr}{YZsrS+w4Ur;d{%_hp%ThdzLLpELsWOvM(ivBh$x5AZ5tYNzX2i%@dK`BERF5R= z@s;kH7S>hdYuzivb4gwY7wD2ffu*`MDcAt?vnAMs8)4Y7d~HimeE(F->#_4|;S6JT zbhu9>8LBHH9R|5?rMQ6TJtXj$WTo{gE!F-VR_{Hb5Ew!Cx#)OMSYANS1)KU=v=qh< z!Z|UE-p3&#cm3R^alkED7i8Y!7);!aD)xJ-Nd*@eC5=GG$It*>aW|lqO#L)3$Bg2d z#^T*IbB2v9ca4EjYoON&krj9=)sRnOZDyXrG(OCc2{v=~XZ zta&zZb)g9=pYAxv;4_{*U)l6|JwascoL^;RKx~*JI!8pM#{n>ajr-U*>Ph%?gooBb zXT%T=#$X5N?6wo((}a62Sgc{4xIMrA>gNnK8mo4P%8umozJTdHK&WhIgojo{BrNrONpLbE!WI&ukmc9 zWuFwh3n6N)fEZw6&oj+~>0^V`RD+*GaZ^>Pd2Br6KP#8l`2BxHr0H_AV+&8P6*UVCS6(sBl(@;*z~eTJ^ML2seq zKc0u~e{nql+xFqzDGaZ390HxP)ne53?GkC06pFJGF{ec5Q^tGbZ&>RM&d$ckO&aD@ zQ|0>g%9hl(*s5s5d4=TGxGJiZXYCuJ%G}72pArb-8zn6Mtitn5j}~jMIqDU1Z}rec zm5nkH#EQIpX5X=%PUY=Y?Cpz^wt`yUiO2=g2>s~HGAO=0I`8Im&HiVv44#}~bSoUW zIw$fO9!2qnj0V`BDt1?25WbchEgnzn6TFL6T%3Sq^r4j-=Q~uOS?-e-S_=u#$^IqR z`Gr(Bcea%vMv~;-XHoOoOmvn|m>v*2XPs8{Z%X zI%~+rWA^Z|GMAE#jg7+htK+ofM)&3}_dNjn+0Zr&!ZR1(&ECvB*`2w)bRx6TqY;IvxCA zqmu?F!-;iug{tJ0^H+V9(UG#F z++L&H&Idgo@T?D@bsXjU5Xrtd27Jd^QS0pvHgFmFto!Z4!OqNV53N3X)!k2GY8*^0 zuGF4eSf{9=`mwM2a1pRR9{VAT*;ifFtP(%E>6W6}BOg%7KIL4Xa$x^Q#1>i6=WG?L z%tB*zIyWcyVxCst;B>+0O`jDq(4GVhw+=Y>YfcofIiFQr6~Gb9(P-W6ucqLnEwL?7 zOHFnSVkoOVDlp(r%cvZxF&4AfkIboLc=#ZT$R|qwk@f)WBVSvs>@gppIU$}gn2`yu zOptR&t~hbuzN5I8#^duHDKWvL-5Rr%P_R2&x@fYCTa%j{|5!sssE*^IpS9iSC-a#o zq~=lKZB(;W+f6n~>Gis6TRC4`j}%2BTM>_E@uj-R3*<=i1%uqVEK0aLyhznwi<8&% z2ke=90grBo86_cm{u85=f319ZAS+8pZFKu8bOY4Vo7>chxq&hN`MLfCuoG+F4N+0m zD-7;ZqP2BDl64%50xQL<^6+m#&B!n~jgL)5samfQ6R1Z+HcA3qv@|ELAM47n_gbM` zh5y>jsQ=MzFk>c1HihMmU2umo?Daib#7O(h5FW=L%+D3(!pAgFQGt~n>?&?jJCNe<-**8t8o&zbS*CdbP8LCn8yMiXS5iO;>CNq5uK;0&9H3E8vY2x~_<8Nz zkxw~Ip7j3wuO09sQ&)a>&-XO&p7FcBT-Td^T~|u|McSVlsNaFVy8`DL#qd2K3s7dTh%>JwlC?;`#+8?g9kIVBf1z+ z=Owf_=^f>N}Wx87}J(dX=INIzzG9{P~m%@?lPsoRqaX@1-BHG~Xgp44SX2lgD6UMvJJ+ClR@ zX9aI)tv-jH0O;0^KUM&v1j1>=3K)yc65bePdpz)BO0?c#;w; zI&-qc_yqcAxczwwg5rZ5OsV$MEO-)d{Z#Z__ z6vih&o{N!UXo+mI2}DDr?IV;vSQe(beWF1tYC%g+aWMb}t~MzF2|H5luPWq9UlnPm z>?m0os&;kc?VXu)DwUEeId&T3jS`7#$VwqIRPHxcYv_u;V}R#cXGX{-6qI69D}0LFB9?D z3Ysk%RBX9{QR9Kh7?YP$DnfMfh$g|dAGM-+AOn|xYJfS_FB6U`!Pj^Fc4$Z`j9yj$M7H+*Yi1H6M@AvD2+mRCVxl z5yC%F#;Z4D{6p_&OVugw8@atBbeTyH)(x8*ls%%_vJCY0HaFTjKBm;42L0xCUvO2g z&^NpioM(5pmQ$TybGPA>m7Ma^V}qHpMcH1_FE80BFn^Ma-M6)o9*p-SB9xM7Dmprb z#fxnX^|Lp`q!NKx1F1?14s%q?>bWk4=h}HhJ&jG9PQWiqSN$-|O3ei=6|t1o7rj$y@V*zo10JC_ zUt`gTW>%(_lQ82I& zG*Sf{4u6O^FsTm8RD#30m9FyWZfSq+*NPwO~OnV%VcF< zW*>V~1yojmS5aYI1}J-C*}*&AnbjNnR8mNtu0{3NqJKS_&%uC$`3aEJ0q_3*ArxxS zm6Vh``NyYU3%dM2A7K4E|Ml}7`RYp5?E0~uZGE(d0T#3uq=VRdn=}b9>xyaT zp>ze8pjf^>HIb{^l*D(0!RAZY+^^ArtRpfn#=4pn?Im51J`GcwHtuF-G4{QD<2fu` zKWv{3PbyfQF7v_b%vz1PFek~H8pG7}OY`vtc5D49GU&}jh^Xm=!z#!&##JQHFCoCpoX`Ssc&E0vpGG z$S%yZTt!zuRq+frYOP4zPUkLT=$Ok~aAmd@njbDar|+TWTc96hfq#xW5WwUiMbJXZ z?K@k}$p!UethdAl`fZZDRDBOLi;sPPu5aCwZ)tmT`7L0$X(>`-G$##l&t{EjFlJAT zUvHBOeD$4mLm$(2d9nbL^A*CNz^18{nTMpkUPFy#`POOn8P0XHM|FGLNAu{cXbqoW74C3kT6*@Mq|EP+@HAHFYR9xZ28TztYI zaMp9_4HrW_Zm$lr%OIpN(TYjD zy*JnuQ`PJ_X}A+`E~)|PQUHnDxnd1Ef87WEJn<{1+oQMc#tvjQTvb4`y zhF178GwadkePNEaRzvHu&1%)Fwxfw=l5o%!nc)iYc1hbZvbq7#kco@07wbBE`CK_R zx3E3zU2@Np(*5QE%Q`Oo8E0#!+8nIsBNkDB$WL4s(50hM3JCQj0s8TnWeR+Fp?IF69am8jRYyu|Ah3FsKENw5mf#GEMk=>ls z1wBE0>7_L~A2$RQ3bHlEDu{Z%9C>eL+-0#UO748m5N0W}pI{mkkn|3X@F)UEctJPI6K@-y41Eq~<|!w$0r% z+4l!;re2r(nBabZxNiJVg+kv7Y>asG*`t6;^GmqAE52Qy=zSisP!r2=9c1X$zP@~A z%(#kulwL_Gs0)JfW0gJ?F1|EnRe2@UrkH*F#46-d1-gpTb5Bh75>A_<%K#KXhAwK4 zyy$XJT&Hg!1E2&CkH$~r-n}av9-j6Yqxc`TRx7DU9IC|1Dy3uIWrj*^o2uRH9OL!1Oyp0Qhn6{aDt78$9C%Xn2bT&;Wfq#& zWLxH@-@#k7o|)M2+)H0Q&OdMn`Yg`{Xv;J)Pd?mzIOzfhjDV8p;WHx*(DZF#Ta8Pk z+*spte*R}|G^lo~*T%MkNUHI%wy+Q2faCJ?6?Z!a(2Zn&U-%8sJk z^)pRocES+0P{{`!0ur69O+Kx*9-6QzbwLE3rVxN9M6OM&B6$lL2A+RWNH7xR@R1&> zSFfrc+S6L?+Hp%xLp0W9H5}}pn#|j-*p&5wCVJoN|Kv2f-Z@{FR(LnKMGqA`6Jio4#Z^`YQ< zDsd0?Qjl?paMk?;W6n)ov1|eNF;^{3=$N18#~*bG?xs90<47SwD`b`RCV81a%eyte zijYXU$)w~~6Gv5kf(yb6WLuVBsN@ycCqyp3i=DHtuJr5V^g2x#V%`+ky2yvw1`5)lOGvY*L6y7#(Qw=zaE(zn&!q-qvaeYZ4Ha~*Euue&{*$J z${n)9*#%tt&VDUvJU82gK>>yA)E>i0^HsI_2K>kNKQ3OJu>Q^=zFK|~U&JMfUL4wj zO|W=acHNiR*tklVe+}N7GYntMlp|6mP~QR6ytituwcaSyx8`!bIk9nX$V4sZvrF1ZE8jIvH+W)=TK^abSiZ!R4L zAwX<(hWqFN2L^1;YaRv3{6@ea*PG+2#s8ZkUo$y@_2g9lkB#U?O~C*Bc3;DDCbRiS~ literal 0 HcmV?d00001 diff --git a/examples/knative/python/kn-py-vm-attr/test/testevent.json b/examples/knative/python/kn-py-vm-attr/test/testevent.json new file mode 100644 index 00000000..e8bcc5b9 --- /dev/null +++ b/examples/knative/python/kn-py-vm-attr/test/testevent.json @@ -0,0 +1,50 @@ +{ + "specversion": "1.0", + "id": "42516969-218a-406f-9ccc-db387befc4bf", + "source": "https://vcsa.local/sdk", + "type": "com.vmware.event.router/event", + "datacontenttype": "application/json", + "subject": "DrsVmPoweredOnEvent", + "time": "2021-05-04T07:33:33.773581268Z", + "knativearrivaltime": "2021-05-04T07:33:33.772937393Z", + "data": { + "Key": 992270, + "ChainId": 992267, + "CreatedTime": "2021-05-04T07:33:32.759Z", + "UserName": "VSPHERE.LOCAL\\test-user", + "Datacenter": { + "Name": "Datacenter", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-21" + } + }, + "ComputeResource": { + "Name": "Cluster01", + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c84" + } + }, + "Host": { + "Name": "esxi1.local", + "Host": { + "Type": "HostSystem", + "Value": "host-34" + } + }, + "Vm": { + "Name": "TestVM", + "Vm": { + "Type": "VirtualMachine", + "Value": "vm-596" + } + }, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "DRS powered On TestVM on esxi1.local in Datacenter", + "ChangeTag": "", + "Template": false + } +} \ No newline at end of file diff --git a/examples/knative/python/kn-py-vm-attr/vcenter.py b/examples/knative/python/kn-py-vm-attr/vcenter.py new file mode 100644 index 00000000..15672192 --- /dev/null +++ b/examples/knative/python/kn-py-vm-attr/vcenter.py @@ -0,0 +1,117 @@ +import os +import atexit +import ssl +import logging +from pyVim.connect import SmartConnect, Disconnect +from pyVmomi import vim +import json + +logger = logging.getLogger(__name__) + +class Session: + """Session is a helper object to connect to a vcenter server and set custom attributes to + VM objects. + """ + + def __init__(self): + """Session is a helper object to connect to a vcenter server and set custom attributes to + VM objects. + """ + try: + config = json.loads(os.environ['VCCONFIG_SECRET']) + except json.JSONDecodeError as err: + raise Exception(f'Invalid JSON configuration: {err}') + except KeyError as err: + raise Exception(f'Missing environment variable `VCCONFIG_SECRET`') + except Exception as err: + raise Exception(f'Unknown error when reading configuration: {err}') + self.ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + if config.get('VC_SSLVERIFY', True) == 'False': + self.ssl_context.verify_mode = ssl.CERT_NONE + try: + self.host = config['VC_SERVER'] + self.user = config['VC_USER'] + self.pwd = config['VC_PASSWORD'] + self.attr_owner = config['VC_ATTR_OWNER'] + self.attr_creation_date = config['VC_ATTR_CREATION_DATE'] + self.attr_last_poweredon = config['VC_ATTR_LAST_POWEREDON'] + except KeyError as err: + raise Exception(f'Missing mandatory configuration key: {err}') + logger.info(f'Initializing vCenter connection...') + try: + self.service_instance = SmartConnect( + host = self.host, + user = self.user, + pwd = self.pwd, + port = 443, + sslContext = self.ssl_context + ) + atexit.register(Disconnect, self.service_instance) + self.content = self.service_instance.RetrieveContent() + except IOError as err: + raise Exception(f'Error connecting to vCenter: {err}') + except Exception as err: + raise Exception(f'Unknown error when creating vsphere session: {err}') + logger.info(f"Connected to vCenter {self.host}") + + + def close(self): + """Disconnect the current session from vCenter. + """ + Disconnect(self.service_instance) + + + def get_field_attributes(self): + """Get attributes used in the function based on configuration. + + Returns: + tuple: 3 VM attributes + """ + attr_owner, attr_creation_date, attr_last_poweredon = None, None, None + cfmgr = self.content.customFieldsManager + for field in cfmgr.field: + if field.name == self.attr_owner: + attr_owner = field + if field.name == self.attr_creation_date: + attr_creation_date = field + if field.name == self.attr_last_poweredon: + attr_last_poweredon = field + if not (attr_owner and attr_creation_date and attr_last_poweredon): + raise Exception(f'Missing attribute for owner, last_poweredon or creation_date') + return attr_owner, attr_creation_date, attr_last_poweredon + + + def get_vm(self, moref: str): + """Get a VM object based on its MoRef identifier + + Args: + moref (str): Identifier of the VM to return + """ + # List and iter on VMs objects + objView = self.content.viewManager.CreateContainerView( + self.content.rootFolder, + [vim.VirtualMachine], + True + ) + vmList = objView.view + objView.Destroy() + for vm in vmList: + if vm._moId == moref: + return vm + return None + + + def set_custom_attr(self, entity, key, value): + """Set a custom attribute to entity + + Args: + entity (obj): The entity object to set attribute on + key (str): Key of the custom attribute + value (str): Value of the custom attribute + """ + cfmgr = self.content.customFieldsManager + cfmgr.SetField( + entity=entity, + key=key, + value=value + ) \ No newline at end of file From 4e4c8f7f775baa3a49feeb1851325f8ad55805c9 Mon Sep 17 00:00:00 2001 From: William Lam Date: Wed, 2 Jun 2021 05:50:42 -0700 Subject: [PATCH 48/56] docs: Add new Python/Go examples to Docs Signed-off-by: William Lam --- docs/site/examples-knative.md | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/docs/site/examples-knative.md b/docs/site/examples-knative.md index fabbf483..ce4f2ebb 100644 --- a/docs/site/examples-knative.md +++ b/docs/site/examples-knative.md @@ -11,23 +11,29 @@ images: go: /assets/img/languages/go.png powershell: /assets/img/languages/powershell.png examples: - - title: Powershell echo Cloud Event for Knative - usecases: + - title: Echo Cloud Event for Knative + usecases: - item: other id: kn-ps-echo-function - description: Powershell function that helps users understand the structure and data of a given vCenter Event using the Knative event processor which will be useful when creating brand new Functions. - links: + description: Function that helps users understand the structure and data of a given vCenter Event using the Knative event processor which will be useful when creating brand new Functions. + links: - language: powershell url: "/tree/master/examples/knative/powershell/kn-ps-echo" + - language: python + url: "/tree/master/examples/knative/python/kn-py-echo" + - language: go + url: "/tree/master/examples/knative/go/kn-go-echo" - title: Slack Notification - usecases: + usecases: - item: integration - item: notification id: kn-ps-slack-function - description: Powershell function to send a Slack notification. - links: + description: Function to send a Slack notification. + links: - language: powershell url: "/tree/master/examples/knative/powershell/kn-ps-slack" + - language: python + url: "/tree/master/examples/knative/python/kn-py-slack" - title: Email Notification usecases: - item: notification @@ -44,6 +50,14 @@ examples: links: - language: powercli url: "/tree/master/examples/knative/powercli/kn-pcli-tag" + - title: vSphere Custom Attributes + usecases: + - item: automation + id: kn-py-vm-attr-function + description: Add Custom Attribute to VM upon a vCenter event. + links: + - language: python + url: "/tree/master/examples/knative/python/kn-py-vm-attr" --- @@ -51,7 +65,7 @@ A complete and updated list of ready to use functions curated by the VMware Even # Get started with our prebuilt functions -These functions are prebuilt, available in ready to deploy container and `stack.yml` files for you to deploy as is. Should you need to modify the functions to fit your needs, the `README.md` files provided within each function folder will provide all the information you need to customize, build and deploy the function on your VMware Event Broker appliance. +These functions are prebuilt, available in ready to deploy container and `function.yaml` files for you to deploy as is. Should you need to modify the functions to fit your needs, the `README.md` files provided within each function folder will provide all the information you need to customize, build and deploy the function on your VMware Event Broker appliance. > **Note:** These functions are provided and tested to be used with the VMware Event Broker Appliance deployed with [Knative](/kb/install-knative) as the event stream processor. From e6371a636064817c8678176dcc75d1178f3f0aa5 Mon Sep 17 00:00:00 2001 From: Scottie Ray Date: Wed, 2 Jun 2021 09:23:36 -0500 Subject: [PATCH 49/56] feat: add kn-py-echo example closes: #426 Signed-off-by: Scottie Ray --- examples/knative/python/kn-py-echo/Procfile | 1 + examples/knative/python/kn-py-echo/README.md | 98 +++++++++++++++++++ .../knative/python/kn-py-echo/function.yaml | 36 +++++++ examples/knative/python/kn-py-echo/handler.py | 38 +++++++ examples/knative/python/kn-py-echo/pyvenv.cfg | 2 + .../python/kn-py-echo/requirements.txt | 2 + .../python/kn-py-echo/test/testevent.json | 49 ++++++++++ 7 files changed, 226 insertions(+) create mode 100644 examples/knative/python/kn-py-echo/Procfile create mode 100644 examples/knative/python/kn-py-echo/README.md create mode 100644 examples/knative/python/kn-py-echo/function.yaml create mode 100644 examples/knative/python/kn-py-echo/handler.py create mode 100644 examples/knative/python/kn-py-echo/pyvenv.cfg create mode 100644 examples/knative/python/kn-py-echo/requirements.txt create mode 100644 examples/knative/python/kn-py-echo/test/testevent.json diff --git a/examples/knative/python/kn-py-echo/Procfile b/examples/knative/python/kn-py-echo/Procfile new file mode 100644 index 00000000..7397456d --- /dev/null +++ b/examples/knative/python/kn-py-echo/Procfile @@ -0,0 +1 @@ +web: FLASK_ENV=development FLASK_APP=handler.py python3 -m flask run --host=0.0.0.0 --port=$PORT diff --git a/examples/knative/python/kn-py-echo/README.md b/examples/knative/python/kn-py-echo/README.md new file mode 100644 index 00000000..05822251 --- /dev/null +++ b/examples/knative/python/kn-py-echo/README.md @@ -0,0 +1,98 @@ +# kn-py-echo +Example Python function with `Flask` REST API running in Knative to echo +[CloudEvents](https://github.com/cloudevents/sdk-python). + +# Step 1 - Build with `pack` + +[Buildpacks](https://buildpacks.io) are used to create the container image. + +```bash +IMAGE=/kn-py-echo:1.0 +pack build -B gcr.io/buildpacks/builder:v1 ${IMAGE} +``` + +# Step 2 - Test + +Verify the container image works by executing it locally. + +```bash +docker run -e PORT=8080 -it --rm -p 8080:8080 /kn-py-echo:1.0 +``` +You should see output similar to the following: +``` +* Serving Flask app "handler.py" (lazy loading) + * Environment: development + * Debug mode: on + * Running on all addresses. + WARNING: This is a development server. Do not use it in a production deployment. + * Running on http://172.17.0.2:8080/ (Press CTRL+C to quit) + * Restarting with stat + * Debugger is active! + * Debugger PIN: 994-125-687 + ``` + + +In a separate terminal window, go to the test directory and use the `testevent.json` file to validate the function is working. + +```console +cd test +curl -i -d@testevent.json localhost:8080 +``` +You should see output similar to this below. +``` +HTTP/1.1 100 Continue + +HTTP/1.0 204 NO CONTENT +Content-Type: application/json +Server: Werkzeug/2.0.1 Python/3.8.6 +Date: Wed, 26 May 2021 18:56:27 GMT +``` +Return to the previous terminal window where you started the docker image, and you should see output similar to the following: +``` +* Serving Flask app "handler.py" (lazy loading) + * Environment: development + * Debug mode: on + * Running on all addresses. + WARNING: This is a development server. Do not use it in a production deployment. + * Running on http://172.17.0.2:8080/ (Press CTRL+C to quit) + * Restarting with stat + * Debugger is active! + * Debugger PIN: 994-125-687 +2021-05-26 18:56:27,719 INFO handler Thread-3 : "***cloud event*** {"attributes": {"specversion": "1.0", "id": "08179137-b8e0-4973-b05f-8f212bf5003b", "source": "https://10.0.0.1:443/sdk", "type": "com.vmware.event.router/event", "datacontenttype": "application/json", "subject": "VmPoweredOffEvent", "time": "2020-02-11T21:29:54.9052539Z"}, "data": {"Key": 9902, "ChainId": 9895, "CreatedTime": "2020-02-11T21:28:23.677595Z", "UserName": "VSPHERE.LOCAL\\Administrator", "Datacenter": {"Name": "testDC", "Datacenter": {"Type": "Datacenter", "Value": "datacenter-2"}}, "ComputeResource": {"Name": "cls", "ComputeResource": {"Type": "ClusterComputeResource", "Value": "domain-c7"}}, "Host": {"Name": "10.185.22.74", "Host": {"Type": "HostSystem", "Value": "host-21"}}, "Vm": {"Name": "test-01", "Vm": {"Type": "VirtualMachine", "Value": "vm-56"}}, "Ds": null, "Net": null, "Dvs": null, "FullFormattedMessage": "test-01 on 10.0.0.1 in testDC is powered off", "ChangeTag": "", "Template": false}} +172.17.0.1 - - [26/May/2021 18:56:27] "POST / HTTP/1.1" 204 - +2021-05-26 18:56:27,720 INFO werkzeug Thread-3 : 172.17.0.1 - - [26/May/2021 18:56:27] "POST / HTTP/1.1" 204 - +``` + +# Step 3 - Deploy + +> **Note:** The following steps assume a working Knative environment using the +`default` Rabbit `broker`. The Knative `service` and `trigger` will be installed in the +`vmware-functions` Kubernetes namespace, assuming that the `broker` is also available there. + +Push your container image to an accessible registry such as Docker once you're done developing and testing your function logic. + +```console +docker push /kn-py-echo:1.0 +``` +Edit the `function.yaml` file with the name of the container image from Step 1 if you made any changes. If not, the default VMware container image will suffice. By default, the function deployment will filter on the `VmPoweredOffEvent` vCenter Server Event. If you wish to change this, update the `subject` field within `function.yaml` to the desired event type. + +Deploy the function to the VMware Event Broker Appliance (VEBA). + +```console +# deploy function +kubectl -n vmware-functions apply -f function.yaml +``` + +For testing purposes, the `function.yaml` contains the following annotations, which will ensure the Knative Service Pod will always run **exactly** one instance for debugging purposes. Functions deployed through through the VMware Event Broker Appliance UI defaults to scale to 0, which means the pods will only run when it is triggered by an vCenter Event. + +```yaml +annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" +``` +# Step 4 - Undeploy + +```console +# undeploy function +kubectl -n vmware-functions delete -f function.yaml +``` \ No newline at end of file diff --git a/examples/knative/python/kn-py-echo/function.yaml b/examples/knative/python/kn-py-echo/function.yaml new file mode 100644 index 00000000..c4dc3ae8 --- /dev/null +++ b/examples/knative/python/kn-py-echo/function.yaml @@ -0,0 +1,36 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-py-echo + labels: + app: veba-ui + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" +spec: + template: + metadata: + spec: + containers: + - image: projects.registry.vmware.com/veba/kn-py-echo:1.0 + +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-py-echo-trigger + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + subject: VmPoweredOffEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-py-echo \ No newline at end of file diff --git a/examples/knative/python/kn-py-echo/handler.py b/examples/knative/python/kn-py-echo/handler.py new file mode 100644 index 00000000..80bb126d --- /dev/null +++ b/examples/knative/python/kn-py-echo/handler.py @@ -0,0 +1,38 @@ +from flask import Flask, request, jsonify +from cloudevents.http import from_http +import logging,json + +logging.basicConfig(level=logging.DEBUG,format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s') + +app = Flask(__name__) +@app.route('/', methods=['POST']) +def echo(): + try: + event = from_http(request.headers, request.get_data(),None) + + data = event.data + # hack to handle non JSON payload, e.g. xml + if not isinstance(data,dict): + data = str(event.data) + + e = { + "attributes": event._attributes, + "data": data + } + app.logger.info(f'"***cloud event*** {json.dumps(e)}') + return {}, 204 + except Exception as e: + sc = 400 + msg = f'could not decode cloud event: {e}' + app.logger.error(msg) + message = { + 'status': sc, + 'error': msg, + } + resp = jsonify(message) + resp.status_code = sc + return resp + +# hint: run with FLASK_ENV=development FLASK_APP=handler.py flask run +if __name__ == "__main__": + app.run() diff --git a/examples/knative/python/kn-py-echo/pyvenv.cfg b/examples/knative/python/kn-py-echo/pyvenv.cfg new file mode 100644 index 00000000..ff445273 --- /dev/null +++ b/examples/knative/python/kn-py-echo/pyvenv.cfg @@ -0,0 +1,2 @@ +home = /usr/local/bin +include-system-site-packages = false diff --git a/examples/knative/python/kn-py-echo/requirements.txt b/examples/knative/python/kn-py-echo/requirements.txt new file mode 100644 index 00000000..c2e97b25 --- /dev/null +++ b/examples/knative/python/kn-py-echo/requirements.txt @@ -0,0 +1,2 @@ +flask==1.1.2 +cloudevents==1.2.0 diff --git a/examples/knative/python/kn-py-echo/test/testevent.json b/examples/knative/python/kn-py-echo/test/testevent.json new file mode 100644 index 00000000..8507c32f --- /dev/null +++ b/examples/knative/python/kn-py-echo/test/testevent.json @@ -0,0 +1,49 @@ +{ + "id": "08179137-b8e0-4973-b05f-8f212bf5003b", + "source": "https://10.0.0.1:443/sdk", + "specversion": "1.0", + "type": "com.vmware.event.router/event", + "subject": "VmPoweredOffEvent", + "time": "2020-02-11T21:29:54.9052539Z", + "data": { + "Key": 9902, + "ChainId": 9895, + "CreatedTime": "2020-02-11T21:28:23.677595Z", + "UserName": "VSPHERE.LOCAL\\Administrator", + "Datacenter": { + "Name": "testDC", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-2" + } + }, + "ComputeResource": { + "Name": "cls", + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c7" + } + }, + "Host": { + "Name": "10.185.22.74", + "Host": { + "Type": "HostSystem", + "Value": "host-21" + } + }, + "Vm": { + "Name": "test-01", + "Vm": { + "Type": "VirtualMachine", + "Value": "vm-56" + } + }, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "test-01 on 10.0.0.1 in testDC is powered off", + "ChangeTag": "", + "Template": false + }, + "datacontenttype": "application/json" +} From 8d0dcb919d65ff7cc2b95c121d3f6acfa0a05cee Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Thu, 3 Jun 2021 12:43:33 +0200 Subject: [PATCH 50/56] Use 1.0 tag for image Closes: #437 Signed-off-by: Michael Gasch --- examples/knative/python/kn-py-vm-attr/function.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/knative/python/kn-py-vm-attr/function.yaml b/examples/knative/python/kn-py-vm-attr/function.yaml index 7406da01..c916c503 100644 --- a/examples/knative/python/kn-py-vm-attr/function.yaml +++ b/examples/knative/python/kn-py-vm-attr/function.yaml @@ -13,7 +13,7 @@ spec: autoscaling.knative.dev/minScale: "1" spec: containers: - - image: projects.registry.vmware.com/veba/kn-py-vm-attr:latest + - image: projects.registry.vmware.com/veba/kn-py-vm-attr:1.0 envFrom: - secretRef: name: vcconfig-secret From 2eb6ae9fa2670c0b959113008eccaa6941ce3e48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:46:19 +0000 Subject: [PATCH 51/56] chore(deps): Bump urllib3 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.8 to 1.26.5. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.25.8...1.26.5) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .../python/trigger-pagerduty-incident/handler/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/openfaas/python/trigger-pagerduty-incident/handler/requirements.txt b/examples/openfaas/python/trigger-pagerduty-incident/handler/requirements.txt index 2a9a5f36..58490813 100644 --- a/examples/openfaas/python/trigger-pagerduty-incident/handler/requirements.txt +++ b/examples/openfaas/python/trigger-pagerduty-incident/handler/requirements.txt @@ -1,2 +1,2 @@ -urllib3==1.25.8 +urllib3==1.26.5 requests==2.22.0 From 4dd58702dc593d9457363f327d5bd6bff6af172e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:46:22 +0000 Subject: [PATCH 52/56] chore(deps): Bump urllib3 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.8 to 1.26.5. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.25.8...1.26.5) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .../openfaas/python/invoke-rest-api/handler/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/openfaas/python/invoke-rest-api/handler/requirements.txt b/examples/openfaas/python/invoke-rest-api/handler/requirements.txt index ca1d0a48..66202922 100644 --- a/examples/openfaas/python/invoke-rest-api/handler/requirements.txt +++ b/examples/openfaas/python/invoke-rest-api/handler/requirements.txt @@ -1,3 +1,3 @@ -urllib3==1.25.8 +urllib3==1.26.5 requests==2.22.0 dpath==2.0.1 From b894ab60512040b5f99798abe888cd228dc16d5a Mon Sep 17 00:00:00 2001 From: Michael Gasch Date: Thu, 3 Jun 2021 18:00:19 +0200 Subject: [PATCH 53/56] Bump requests lib in OpenFaaS Python fns Closes: #440 Signed-off-by: Michael Gasch --- .../openfaas/python/invoke-rest-api/handler/requirements.txt | 2 +- examples/openfaas/python/tagging/handler/requirements.txt | 2 +- .../python/trigger-pagerduty-incident/handler/requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/openfaas/python/invoke-rest-api/handler/requirements.txt b/examples/openfaas/python/invoke-rest-api/handler/requirements.txt index 66202922..2c2e3026 100644 --- a/examples/openfaas/python/invoke-rest-api/handler/requirements.txt +++ b/examples/openfaas/python/invoke-rest-api/handler/requirements.txt @@ -1,3 +1,3 @@ urllib3==1.26.5 -requests==2.22.0 +requests==2.25.1 dpath==2.0.1 diff --git a/examples/openfaas/python/tagging/handler/requirements.txt b/examples/openfaas/python/tagging/handler/requirements.txt index 79e12fb4..18b08d37 100644 --- a/examples/openfaas/python/tagging/handler/requirements.txt +++ b/examples/openfaas/python/tagging/handler/requirements.txt @@ -1,3 +1,3 @@ urllib3==1.26.5 -requests==2.22.0 +requests==2.25.1 toml==0.10.0 diff --git a/examples/openfaas/python/trigger-pagerduty-incident/handler/requirements.txt b/examples/openfaas/python/trigger-pagerduty-incident/handler/requirements.txt index 58490813..ddb1967f 100644 --- a/examples/openfaas/python/trigger-pagerduty-incident/handler/requirements.txt +++ b/examples/openfaas/python/trigger-pagerduty-incident/handler/requirements.txt @@ -1,2 +1,2 @@ urllib3==1.26.5 -requests==2.22.0 +requests==2.25.1 From 0e3cfb8c1df82d8ea2f72f447086339d3b4d5315 Mon Sep 17 00:00:00 2001 From: William Lam Date: Thu, 3 Jun 2021 11:30:16 -0700 Subject: [PATCH 54/56] Bump version to v0.6.1 for release Signed-off-by: William Lam --- veba-bom.json | 6 +++--- vmware-event-router/chart/Chart.yaml | 4 ++-- .../chart/releases/event-router-v0.6.3.tgz | Bin 0 -> 3157 bytes 3 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 vmware-event-router/chart/releases/event-router-v0.6.3.tgz diff --git a/veba-bom.json b/veba-bom.json index cedc80b9..46564cf8 100644 --- a/veba-bom.json +++ b/veba-bom.json @@ -1,12 +1,12 @@ { "veba": { - "version": "v0.6.0" + "version": "release-0.6.1" }, "vmware-event-router": { - "gitRepoTag": "v0.6.0", + "gitRepoTag": "release-0.6.1", "containers": [{ "name": "vmware/veba-event-router", - "version": "v0.6.0" + "version": "release-0.6.1" }] }, "veba-ui": { diff --git a/vmware-event-router/chart/Chart.yaml b/vmware-event-router/chart/Chart.yaml index 0def27cc..70d2e466 100644 --- a/vmware-event-router/chart/Chart.yaml +++ b/vmware-event-router/chart/Chart.yaml @@ -3,8 +3,8 @@ name: event-router description: The VMware Event Router is used to connect to various VMware event providers and forwards received events to supported event processors. home: https://vmweventbroker.io/ type: application -version: v0.6.2 -appVersion: v0.6.0 +version: v0.6.3 +appVersion: release-0.6.1 maintainers: - name: Michael Gasch email: mgasch@vmware.com diff --git a/vmware-event-router/chart/releases/event-router-v0.6.3.tgz b/vmware-event-router/chart/releases/event-router-v0.6.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..da0b79f9b3f7e3d1e92e4c0059bc9523d044ed76 GIT binary patch literal 3157 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 literal 0 HcmV?d00001 From fd738b33545f814126df83608aeaaa129d86efee Mon Sep 17 00:00:00 2001 From: William Lam Date: Fri, 4 Jun 2021 09:54:29 -0700 Subject: [PATCH 55/56] fix: Resolve CVE-2021{22901,22898,20266} for PS Base Image Closes: #443 Signed-off-by: William Lam --- .../knative/powercli/kn-pcli-tag/Dockerfile | 4 ++-- .../powercli/kn-pcli-tag/function.yaml | 4 ++-- .../knative/powershell/kn-ps-echo/Dockerfile | 2 +- .../powershell/kn-ps-echo/function.yaml | 2 +- .../knative/powershell/kn-ps-email/Dockerfile | 4 ++-- .../powershell/kn-ps-email/function.yaml | 4 ++-- .../knative/powershell/kn-ps-slack/Dockerfile | 4 ++-- .../powershell/kn-ps-slack/function.yaml | 4 ++-- examples/knative/templates/Dockerfile.pcli | 4 ++-- examples/knative/templates/Dockerfile.ps | Bin 1051 -> 1154 bytes 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/examples/knative/powercli/kn-pcli-tag/Dockerfile b/examples/knative/powercli/kn-pcli-tag/Dockerfile index da3242a6..c93fc634 100644 --- a/examples/knative/powercli/kn-pcli-tag/Dockerfile +++ b/examples/knative/powercli/kn-pcli-tag/Dockerfile @@ -1,7 +1,7 @@ -FROM projects.registry.vmware.com/veba/ce-pcli-base:1.0 +FROM projects.registry.vmware.com/veba/ce-pcli-base:1.1 ENV TERM linux ENV PORT 8080 COPY handler.ps1 handler.ps1 -CMD ["pwsh","./server.ps1"] \ No newline at end of file +CMD ["pwsh","./server.ps1"] diff --git a/examples/knative/powercli/kn-pcli-tag/function.yaml b/examples/knative/powercli/kn-pcli-tag/function.yaml index b33f90bf..21e4f6e9 100644 --- a/examples/knative/powercli/kn-pcli-tag/function.yaml +++ b/examples/knative/powercli/kn-pcli-tag/function.yaml @@ -14,7 +14,7 @@ spec: metadata: spec: containers: - - image: projects.registry.vmware.com/veba/kn-pcli-tag:1.0 + - image: projects.registry.vmware.com/veba/kn-pcli-tag:1.1 envFrom: - secretRef: name: tag-secret @@ -38,4 +38,4 @@ spec: ref: apiVersion: serving.knative.dev/v1 kind: Service - name: kn-pcli-tag \ No newline at end of file + name: kn-pcli-tag diff --git a/examples/knative/powershell/kn-ps-echo/Dockerfile b/examples/knative/powershell/kn-ps-echo/Dockerfile index 5b1f663d..fbe27019 100644 --- a/examples/knative/powershell/kn-ps-echo/Dockerfile +++ b/examples/knative/powershell/kn-ps-echo/Dockerfile @@ -1,4 +1,4 @@ -FROM projects.registry.vmware.com/veba/ce-ps-base:1.0 +FROM projects.registry.vmware.com/veba/ce-ps-base:1.1 ENV TERM linux ENV PORT 8080 diff --git a/examples/knative/powershell/kn-ps-echo/function.yaml b/examples/knative/powershell/kn-ps-echo/function.yaml index 8127a6f3..616ced47 100644 --- a/examples/knative/powershell/kn-ps-echo/function.yaml +++ b/examples/knative/powershell/kn-ps-echo/function.yaml @@ -12,7 +12,7 @@ spec: autoscaling.knative.dev/minScale: "1" spec: containers: - - image: projects.registry.vmware.com/veba/kn-ps-echo:1.0 + - image: projects.registry.vmware.com/veba/kn-ps-echo:1.1 --- apiVersion: eventing.knative.dev/v1 kind: Trigger diff --git a/examples/knative/powershell/kn-ps-email/Dockerfile b/examples/knative/powershell/kn-ps-email/Dockerfile index a82b5709..4d32a5cb 100644 --- a/examples/knative/powershell/kn-ps-email/Dockerfile +++ b/examples/knative/powershell/kn-ps-email/Dockerfile @@ -1,4 +1,4 @@ -FROM projects.registry.vmware.com/veba/ce-ps-base:1.0 +FROM projects.registry.vmware.com/veba/ce-ps-base:1.1 ENV TERM linux ENV PORT 8080 @@ -8,4 +8,4 @@ RUN pwsh -c "Install-Module -Name Send-MailKitMessage -RequiredVersion ${MAILKIT COPY handler.ps1 handler.ps1 -CMD ["pwsh","./server.ps1"] \ No newline at end of file +CMD ["pwsh","./server.ps1"] diff --git a/examples/knative/powershell/kn-ps-email/function.yaml b/examples/knative/powershell/kn-ps-email/function.yaml index 1b638fec..9ac93426 100644 --- a/examples/knative/powershell/kn-ps-email/function.yaml +++ b/examples/knative/powershell/kn-ps-email/function.yaml @@ -14,7 +14,7 @@ spec: metadata: spec: containers: - - image: projects.registry.vmware.com/veba/kn-ps-email:1.0 + - image: projects.registry.vmware.com/veba/kn-ps-email:1.1 envFrom: - secretRef: name: email-secret @@ -38,4 +38,4 @@ spec: ref: apiVersion: serving.knative.dev/v1 kind: Service - name: kn-ps-email \ No newline at end of file + name: kn-ps-email diff --git a/examples/knative/powershell/kn-ps-slack/Dockerfile b/examples/knative/powershell/kn-ps-slack/Dockerfile index 07a2338d..fbe27019 100644 --- a/examples/knative/powershell/kn-ps-slack/Dockerfile +++ b/examples/knative/powershell/kn-ps-slack/Dockerfile @@ -1,7 +1,7 @@ -FROM projects.registry.vmware.com/veba/ce-ps-base:1.0 +FROM projects.registry.vmware.com/veba/ce-ps-base:1.1 ENV TERM linux ENV PORT 8080 COPY handler.ps1 handler.ps1 -CMD ["pwsh","./server.ps1"] \ No newline at end of file +CMD ["pwsh","./server.ps1"] diff --git a/examples/knative/powershell/kn-ps-slack/function.yaml b/examples/knative/powershell/kn-ps-slack/function.yaml index 77e085ee..e11de9e5 100644 --- a/examples/knative/powershell/kn-ps-slack/function.yaml +++ b/examples/knative/powershell/kn-ps-slack/function.yaml @@ -14,7 +14,7 @@ spec: metadata: spec: containers: - - image: projects.registry.vmware.com/veba/kn-ps-slack:1.0 + - image: projects.registry.vmware.com/veba/kn-ps-slack:1.1 envFrom: - secretRef: name: slack-secret @@ -38,4 +38,4 @@ spec: ref: apiVersion: serving.knative.dev/v1 kind: Service - name: kn-ps-slack \ No newline at end of file + name: kn-ps-slack diff --git a/examples/knative/templates/Dockerfile.pcli b/examples/knative/templates/Dockerfile.pcli index 203ec340..ee67b6af 100644 --- a/examples/knative/templates/Dockerfile.pcli +++ b/examples/knative/templates/Dockerfile.pcli @@ -1,8 +1,8 @@ -FROM projects.registry.vmware.com/veba/ce-ps-base:1.0 +FROM projects.registry.vmware.com/veba/ce-ps-base:1.1 ARG POWERCLI_VERSION="12.3.0.17860403" RUN pwsh -c "\$ProgressPreference = \"SilentlyContinue\"; Install-Module VMware.PowerCLI -RequiredVersion ${POWERCLI_VERSION}" && \ pwsh -c 'Set-PowerCLIConfiguration -ParticipateInCEIP $true -confirm:$false' -CMD ["pwsh","./server.ps1"] \ No newline at end of file +CMD ["pwsh","./server.ps1"] diff --git a/examples/knative/templates/Dockerfile.ps b/examples/knative/templates/Dockerfile.ps index 33391dfe20ba80364ce082d2255c7ac2d8f5ce31..d52ff6659f9402988a018e69978a0ff04b5ceea9 100644 GIT binary patch delta 117 zcmbQu(Zo67gt>EQkWYM=Yf!MKzn`s=xt_VHo`J5BUO|Sj5|?9;yFyTaZ@iDEQ?RX) ziJqZ}o{_F0SaRbbHAcbulZt^7Fuk>t7cvQN{>7-s1OU%m BBPsv@ delta 16 XcmZqToXs)e#Aa4T1;)+(OvX$AE;9t0 From 992f8fb4a126292de7b7981fb2faa980d9771e72 Mon Sep 17 00:00:00 2001 From: William Lam Date: Thu, 10 Jun 2021 07:56:59 -0700 Subject: [PATCH 56/56] Bump version to v0.6.1 for release Signed-off-by: William Lam --- veba-bom.json | 6 +++--- vmware-event-router/chart/Chart.yaml | 4 ++-- .../chart/releases/event-router-v0.6.4.tgz | Bin 0 -> 3149 bytes 3 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 vmware-event-router/chart/releases/event-router-v0.6.4.tgz diff --git a/veba-bom.json b/veba-bom.json index 46564cf8..9b4662c4 100644 --- a/veba-bom.json +++ b/veba-bom.json @@ -1,12 +1,12 @@ { "veba": { - "version": "release-0.6.1" + "version": "v0.6.1" }, "vmware-event-router": { - "gitRepoTag": "release-0.6.1", + "gitRepoTag": "v0.6.1", "containers": [{ "name": "vmware/veba-event-router", - "version": "release-0.6.1" + "version": "v0.6.1" }] }, "veba-ui": { diff --git a/vmware-event-router/chart/Chart.yaml b/vmware-event-router/chart/Chart.yaml index 70d2e466..b544feeb 100644 --- a/vmware-event-router/chart/Chart.yaml +++ b/vmware-event-router/chart/Chart.yaml @@ -3,8 +3,8 @@ name: event-router description: The VMware Event Router is used to connect to various VMware event providers and forwards received events to supported event processors. home: https://vmweventbroker.io/ type: application -version: v0.6.3 -appVersion: release-0.6.1 +version: v0.6.4 +appVersion: v0.6.1 maintainers: - name: Michael Gasch email: mgasch@vmware.com diff --git a/vmware-event-router/chart/releases/event-router-v0.6.4.tgz b/vmware-event-router/chart/releases/event-router-v0.6.4.tgz new file mode 100644 index 0000000000000000000000000000000000000000..4a669025379dd9f1f9b5e3ef07fb0b2c748ecae4 GIT binary patch literal 3149 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 literal 0 HcmV?d00001