diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..0d72e79a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: +- package-ecosystem: gomod + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..4f401f0f --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [main] + pull_request: + # The branches below must be a subset of the branches above + branches: [main] + schedule: + - cron: "34 21 * * 5" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ["go", "python"] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/gcr-dev-image.yml b/.github/workflows/gcr-dev-image.yml index d620fa62..2547bfd7 100644 --- a/.github/workflows/gcr-dev-image.yml +++ b/.github/workflows/gcr-dev-image.yml @@ -19,7 +19,7 @@ jobs: image: strategy: matrix: - go-version: ["1.17"] + go-version: ["1.18"] platform: ["ubuntu-latest"] runs-on: ${{ matrix.platform }} @@ -32,15 +32,15 @@ jobs: uses: imjasonh/setup-ko@2c3450ca27f6e6f2b02e72a40f2163c281a1f675 # v0.4 - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2.1.5 + uses: actions/setup-go@b22fbbc2921299758641fab08929b4ac52b32923 with: go-version: ${{ matrix.go-version }} id: go - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - name: Login to GCP - uses: google-github-actions/setup-gcloud@37a9333538a8350a13fe9d8fa03e0d4742a1ad2e # 0.5.0 + uses: google-github-actions/setup-gcloud@877d4953d2c70a0ba7ef3290ae968eb24af233bb with: project_id: ${{ secrets.GCP_PROJECT_ID }} service_account_email: ${{ secrets.GCP_EMAIL }} diff --git a/.github/workflows/gcr-prerelease-image.yml b/.github/workflows/gcr-prerelease-image.yml index b227881f..d6b8925b 100644 --- a/.github/workflows/gcr-prerelease-image.yml +++ b/.github/workflows/gcr-prerelease-image.yml @@ -14,7 +14,7 @@ jobs: image: strategy: matrix: - go-version: ["1.17"] + go-version: ["1.18"] platform: ["ubuntu-latest"] runs-on: ${{ matrix.platform }} @@ -27,15 +27,15 @@ jobs: uses: imjasonh/setup-ko@2c3450ca27f6e6f2b02e72a40f2163c281a1f675 # v0.4 - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2.1.5 + uses: actions/setup-go@b22fbbc2921299758641fab08929b4ac52b32923 with: go-version: ${{ matrix.go-version }} id: go - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - name: Login to GCP - uses: google-github-actions/setup-gcloud@37a9333538a8350a13fe9d8fa03e0d4742a1ad2e # 0.5.0 + uses: google-github-actions/setup-gcloud@877d4953d2c70a0ba7ef3290ae968eb24af233bb with: project_id: ${{ secrets.GCP_PROJECT_ID }} service_account_email: ${{ secrets.GCP_EMAIL }} diff --git a/.github/workflows/issue-greeting.yml b/.github/workflows/issue-greeting.yml index 7e7edb07..3d67acc5 100644 --- a/.github/workflows/issue-greeting.yml +++ b/.github/workflows/issue-greeting.yml @@ -14,20 +14,20 @@ jobs: steps: - name: Checkout - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b with: fetch-depth: 1 - name: Render template id: template - uses: chuhlomin/render-template@f828bb5c72a3e3af89cb79808cea490166c6f1ce # v1.4 + uses: chuhlomin/render-template@69462090a6315efa50069855670b3a4abab20512 with: template: .github/comment-template.md vars: | author: ${{ github.actor }} - name: Create comment - uses: peter-evans/create-or-update-comment@a35cf36e5301d70b76f316e867e7788a55a31dae # v1.4.5 + uses: peter-evans/create-or-update-comment@c9fcb64660bc90ec1cc535646af190c992007c32 with: issue-number: ${{ github.event.issue.number }} body: ${{ steps.template.outputs.result }} diff --git a/.github/workflows/protect-master.yml b/.github/workflows/protect-master.yml index 56a1f521..4a2762cf 100644 --- a/.github/workflows/protect-master.yml +++ b/.github/workflows/protect-master.yml @@ -16,4 +16,6 @@ jobs: if: github.event_name == 'pull_request' && github.base_ref == 'master' steps: - name: Must reject PR - run: exit 1 + run: | + echo "::error:: pull requests must not be made against master branch" + exit 1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9f2adc87..e8e4427e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,19 +17,19 @@ jobs: timeout-minutes: 30 strategy: matrix: - go-version: ["1.17"] + go-version: ["1.18"] platform: ["ubuntu-latest"] runs-on: ${{ matrix.platform }} steps: - name: Checkout - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b with: fetch-depth: 0 ref: "master" - name: Set up Go - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2.1.5 + uses: actions/setup-go@b22fbbc2921299758641fab08929b4ac52b32923 with: go-version: ${{ matrix.go-version }} @@ -44,7 +44,7 @@ jobs: docker run --rm -v $PWD:/workdir ${IMAGE}@sha256:${IMAGE_SHA} -o vmware-event-router/RELEASE_CHANGELOG.md $(basename "${{ github.ref }}" ) - name: GoReleaser - uses: goreleaser/goreleaser-action@79d4afbba1b4eff8b9a98e3d2e58c4dbaf094e2b # v2.8.1 + uses: goreleaser/goreleaser-action@68acf3b1adf004ac9c2f0a4259e85c5f66e99bef with: args: release --rm-dist --release-notes RELEASE_CHANGELOG.md workdir: vmware-event-router @@ -61,7 +61,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b with: # for changelog fetch-depth: 0 @@ -85,7 +85,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@f22a7da129c901513876a2380e2dae9f8e145330 # v3.12.1 + uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27 with: delete-branch: true reviewers: embano1 @@ -106,7 +106,7 @@ jobs: KO_DOCKER_REPO: us.gcr.io/daisy-284300/veba # .../router@sha256: strategy: matrix: - go-version: ["1.17"] + go-version: ["1.18"] platform: ["ubuntu-latest"] runs-on: ${{ matrix.platform }} @@ -115,15 +115,15 @@ jobs: uses: imjasonh/setup-ko@2c3450ca27f6e6f2b02e72a40f2163c281a1f675 # v0.4 - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2.1.5 + uses: actions/setup-go@b22fbbc2921299758641fab08929b4ac52b32923 with: go-version: ${{ matrix.go-version }} id: go - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - name: Login to GCP - uses: google-github-actions/setup-gcloud@37a9333538a8350a13fe9d8fa03e0d4742a1ad2e # 0.5.0 + uses: google-github-actions/setup-gcloud@877d4953d2c70a0ba7ef3290ae968eb24af233bb with: project_id: ${{ secrets.GCP_PROJECT_ID }} service_account_email: ${{ secrets.GCP_EMAIL }} diff --git a/.github/workflows/router-build.yml b/.github/workflows/router-build.yml index da44e293..4fa794ac 100644 --- a/.github/workflows/router-build.yml +++ b/.github/workflows/router-build.yml @@ -22,7 +22,7 @@ jobs: name: Build binaries strategy: matrix: - go-version: ["1.17"] + go-version: ["1.18"] platform: ["ubuntu-latest"] runs-on: ${{ matrix.platform }} @@ -30,16 +30,16 @@ jobs: steps: - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2.1.5 + uses: actions/setup-go@b22fbbc2921299758641fab08929b4ac52b32923 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - name: GoReleaser Snapshot - uses: goreleaser/goreleaser-action@79d4afbba1b4eff8b9a98e3d2e58c4dbaf094e2b # v2.8.1 + uses: goreleaser/goreleaser-action@68acf3b1adf004ac9c2f0a4259e85c5f66e99bef with: version: latest args: release --rm-dist --snapshot @@ -48,7 +48,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Archive run artifacts - uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # v2.3.1 + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 with: name: dist path: | @@ -60,7 +60,7 @@ jobs: name: Verify Release ko artifact (no upload) strategy: matrix: - go-version: ["1.17"] + go-version: ["1.18"] platform: ["ubuntu-latest"] runs-on: ${{ matrix.platform }} @@ -73,13 +73,13 @@ jobs: uses: imjasonh/setup-ko@2c3450ca27f6e6f2b02e72a40f2163c281a1f675 # v0.4 - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2.1.5 + uses: actions/setup-go@b22fbbc2921299758641fab08929b4ac52b32923 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - name: Get short COMMIT and TAG run: | @@ -92,7 +92,7 @@ jobs: ko resolve --platform=linux/arm64,linux/amd64 --push=false --tags ${KO_TAG},${KO_COMMIT},latest -BRf deploy/event-router-k8s.yaml > release.yaml - name: Archive run artifacts - uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # v2.3.1 + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 with: name: release path: | diff --git a/.github/workflows/router-helm.yml b/.github/workflows/router-helm.yml index 10a3d9f9..a6315b92 100644 --- a/.github/workflows/router-helm.yml +++ b/.github/workflows/router-helm.yml @@ -23,16 +23,16 @@ jobs: timeout-minutes: 15 steps: - - name: Set up Go 1.17.x - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2.1.5 + - name: Set up Go 1.18.x + uses: actions/setup-go@b22fbbc2921299758641fab08929b4ac52b32923 with: - go-version: 1.17 + go-version: 1.18 - name: Setup ko uses: imjasonh/setup-ko@2c3450ca27f6e6f2b02e72a40f2163c281a1f675 # v0.4 - name: Check out code onto GOPATH - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b with: fetch-depth: 1 @@ -135,16 +135,16 @@ jobs: timeout-minutes: 15 steps: - - name: Set up Go 1.17.x - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2.1.5 + - name: Set up Go 1.18.x + uses: actions/setup-go@b22fbbc2921299758641fab08929b4ac52b32923 with: - go-version: 1.17 + go-version: 1.18 - name: Setup ko uses: imjasonh/setup-ko@2c3450ca27f6e6f2b02e72a40f2163c281a1f675 # v0.4 - name: Check out code onto GOPATH - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b with: fetch-depth: 1 diff --git a/.github/workflows/router-integration-tests.yml b/.github/workflows/router-integration-tests.yml index 042b8b06..f3e22dd4 100644 --- a/.github/workflows/router-integration-tests.yml +++ b/.github/workflows/router-integration-tests.yml @@ -21,7 +21,7 @@ jobs: integration-tests: strategy: matrix: - go-version: ["1.17"] + go-version: ["1.18"] platform: ["ubuntu-latest"] runs-on: ${{ matrix.platform }} @@ -29,13 +29,13 @@ jobs: steps: - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2.1.5 + uses: actions/setup-go@b22fbbc2921299758641fab08929b4ac52b32923 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - name: Run OpenFaaS integration tests run: hack/run_integration_tests.sh diff --git a/.github/workflows/router-lint.yml b/.github/workflows/router-lint.yml index a2260e0d..9d40df1e 100644 --- a/.github/workflows/router-lint.yml +++ b/.github/workflows/router-lint.yml @@ -16,7 +16,7 @@ jobs: name: lint strategy: matrix: - go-version: ["1.17"] + go-version: ["1.18"] platform: ["ubuntu-latest"] runs-on: ${{ matrix.platform }} @@ -24,15 +24,15 @@ jobs: steps: - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2.1.5 + uses: actions/setup-go@b22fbbc2921299758641fab08929b4ac52b32923 with: go-version: ${{ matrix.go-version }} id: go - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - name: golangci-lint - uses: golangci/golangci-lint-action@5c56cd6c9dc07901af25baab6f2b0d9f3b7c3018 # v2.5.2 + uses: golangci/golangci-lint-action@537aa1903e5d359d0b27dbc19ddd22c5087f3fbc with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. version: latest diff --git a/.github/workflows/router-unit-tests.yml b/.github/workflows/router-unit-tests.yml index 4302509c..2dfed812 100644 --- a/.github/workflows/router-unit-tests.yml +++ b/.github/workflows/router-unit-tests.yml @@ -22,7 +22,7 @@ jobs: name: Unit Tests strategy: matrix: - go-version: ["1.17"] + go-version: ["1.18"] platform: ["ubuntu-latest"] runs-on: ${{ matrix.platform }} @@ -30,13 +30,13 @@ jobs: steps: - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2.1.5 + uses: actions/setup-go@b22fbbc2921299758641fab08929b4ac52b32923 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - name: run unit tests run: make unit-test diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 9f3ab307..5be71b5e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@7fb802b3079a276cf3c7e6ba9aa003c665b3f838 # v4.1.0 + - uses: actions/stale@3cc123766321e9f15a6676375c154ccffb12a358 with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/verify-gcr-login.yaml b/.github/workflows/verify-gcr-login.yaml index e43166ea..326ae589 100644 --- a/.github/workflows/verify-gcr-login.yaml +++ b/.github/workflows/verify-gcr-login.yaml @@ -14,10 +14,10 @@ jobs: steps: - name: Check out code - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - name: Login to GCP - uses: google-github-actions/setup-gcloud@37a9333538a8350a13fe9d8fa03e0d4742a1ad2e # 0.5.0 + uses: google-github-actions/setup-gcloud@877d4953d2c70a0ba7ef3290ae968eb24af233bb with: project_id: ${{ secrets.GCP_PROJECT_ID }} service_account_email: ${{ secrets.GCP_EMAIL }} diff --git a/.gitignore b/.gitignore index c6782593..850302c3 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ checkpoints/ # ignore RELEASE-specific CHANGELOG RELEASE_CHANGELOG.md + +# ignore python bytecode artifacts +*.pyc +*.pyo +__pycache__ \ No newline at end of file diff --git a/README.md b/README.md index 94054d6d..857eaa47 100644 --- a/README.md +++ b/README.md @@ -128,9 +128,10 @@ page](https://vmweventbroker.io/kb/architecture). Public VEBA community meetings are held every **last Tuesday** in the month at **8AM Pacific Time (US)**. -- **Zoom:** https://via.vmw.com/veba-ama - - **Note:** The meeting is **password protected** to mitigate abuse. Please join the VEBA Slack [channel](#other-channels) to receive the Zoom password or contact us in case of issues. -- **Notes**: https://via.vmw.com/veba-notes +- **Zoom:** + - **Note:** The meeting is **password protected** to mitigate abuse. Please join the VEBA Slack [channel](https://vmwarecode.slack.com/archives/CQLT9B5AA) to receive the Zoom password or contact us in case of issues. +- **Notes**: +- **Recording Playlist**: [VEBA Community Calls](https://youtube.com/playlist?list=PLnopqt07fPn3hspeQvarWuFH3IiwkMpDJ) ### Other Channels diff --git a/build.sh b/build.sh index 8de6aa63..7becb27a 100755 --- a/build.sh +++ b/build.sh @@ -16,7 +16,7 @@ if ! hash jq 2>/dev/null; then exit 1 fi -if [[ ! -z $(git status -s) ]]; then +if [[ ! -z $(git status -s | grep -vE 'photon-builder.json|test/.*\.sh') ]]; then echo "Dirty Git repository, please clean up any untracked files or commit them before building" exit fi diff --git a/docs/Gemfile b/docs/Gemfile index d8b8f2f5..7e99e3a1 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -14,4 +14,3 @@ gem 'jekyll-titles-from-headings', '~> 0.5.3' gem 'jekyll-seo-tag', '~> 2.6', '>= 2.6.1' gem 'redcarpet', '~> 3.5' gem "jekyll-github-metadata", '~> 2.13.0' -gem "jekyll-simple-tab", '~> 0.1.6' diff --git a/docs/_config.yml b/docs/_config.yml index b8f0577f..ca0a5d2d 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -90,8 +90,7 @@ plugins: - jekyll-redirect-from - jekyll-seo-tag - jekyll-github-metadata - - jekyll-simple-tab - + # Include these subdirectories include: - CONTRIBUTING.md diff --git a/docs/_data/default.yml b/docs/_data/default.yml index 7ba0d12a..66c5c98a 100644 --- a/docs/_data/default.yml +++ b/docs/_data/default.yml @@ -67,9 +67,9 @@ toc: - page: Replace TLS Certificates on VEBA id: advanced-certificates url: /kb/advanced-certificates - - page: Using Harbor with VEBA - id: site-resources - external_url: https://rguske.github.io/post/using-harbor-with-the-vcenter-event-broker-appliance/ + - page: Using Private Container Registry with VEBA + id: private-registry + url: /kb/private-registry - page: Monitoring VEBA with vROps id: site-resources external_url: https://rguske.github.io/post/monitoring-the-vmware-event-broker-appliance-with-vrealize-operations-manager/ diff --git a/docs/_data/resources.yml b/docs/_data/resources.yml index 21aada15..4a5bc609 100644 --- a/docs/_data/resources.yml +++ b/docs/_data/resources.yml @@ -3,53 +3,80 @@ # Featured links with the below details populated. Current max limit of links set to 5 # use the flag display: true/false to control if the link needs to show up on the site. Videos show up in the same order as below # -linklimit: 7 #Avoid high number to reduce vertical scroll +linklimit: 10 #Avoid high number to reduce vertical scroll videolimit: 3 #Optimal number, DONT CHANGE links: - - title: VMware Event Broker (aka VEBA) on Kubernetes – First steps + - title: Achieve Event-Driven Automation with VMware Event Broker Appliance (VEBA) display: true details: url_text: blog post - external_url: https://vuptime.io/2020/11/02/vmware-event-broker-on-k8s-first-steps/ - external_image: https://vuptime.io/images/veba-first-steps/veba_otto_the_orca_md.png - author_name: Ludovic Rivallain - excerpt: In the following post, we will discover how to deploy the VMware Event Broker services (VEBA) within an existing Kubernetes (K8S) cluster and use it to add/edit custom attributes information to virtual machines... + external_url: https://www.altaro.com/vmware/vmware-event-broker-appliance/ + external_image: https://prodwewpstorageaccount.s3.eu-central-1.amazonaws.com/wp-content/uploads/sites/7/2022/03/14090840/VMware-Event-Broker-Appliance-841x281-1.jpg + author_name: Brandon Lee + excerpt: Organizations worldwide are in the middle of a paradigm shift in provisioning, managing, monitoring, and configuring their infrastructure. The cloud revolution has prompted a change in the way businesses think about infrastructure ... - - title: Self Service for your DataCenter + Auto Remediation + - title: vSphere Event-Driven Automation using Tanzu Application Platform (TAP) on Tanzu Community Edition display: true - details: + details: url_text: blog post - external_url: https://bit.ly/vmselfservepk - external_image: https://miro.medium.com/max/1506/1*l2CTz_sUAjRmWugTV_952A.png - author_name: Partheeban Kandasamy (PK) & Frankie Gold - excerpt: For the Command/User driven use case, What if IT enables Business Stakeholders to interact with vSphere SDDC through a Slack command...For the event-driven use case, what if certain crises are handled automatically thus increasing operational efficiency... + external_url: https://williamlam.com/2022/01/vsphere-event-driven-automation-using-tanzu-application-platform-tap-on-tanzu-community-edition.html + external_image: https://i0.wp.com/williamlam.com/wp-content/uploads/2022/01/vsphere-event-driven-automation-using-tanzu-application-platform-on-tanzu-kubernetes-grid-5.png + author_name: William Lam + excerpt: One of the core components of TAP is the Cloud Native Runtime (CNR), which is VMware's commercial offering of the popular open source project Knative. The VMware Event Broker Appliance (VEBA) project also makes use of Knative as our backend to provide customers with an event-driven automation solution. - - title: Integrating VMware Cloud Notification Gateway with VMware Event Broker Appliance + - title: vSphere Event-Driven Automation using VMware Event Router on VMware Cloud on AWS with Knative or AWS EventBridge display: true - details: + details: url_text: blog post - 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 + external_url: https://williamlam.com/2022/05/vsphere-event-driven-automation-using-vmware-event-router-on-vmware-cloud-on-aws-with-knative-or-aws-eventbridge.html + external_image: https://i0.wp.com/williamlam.com/wp-content/uploads/2022/05/vmware-event-router-vmc-on-aws-event-bridge-0.png 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... + excerpt: I wanted to spend some time covering the native Kubernetes deployment model, as it there are actually a couple of options and most recently, this came up in a customer discussions as they were interested in forwarding vSphere Events from VEBA to AWS EventBridge. - - title: Bursting to the Cloud with VEBA, AWS Lambda & VMC on AWS + - title: How to modernize your vSphere Alarm actions using the VMware Event Broker Appliance (VEBA)? display: true - details: + details: url_text: blog post - external_url: https://nicovibert.com/2020/05/20/cloudbursting-vmwarecloud-veba-lambda-eventbridge/ - external_image: https://nicovmc.files.wordpress.com/2020/05/screenshot-2020-05-20-at-12.17.07.png?w=1024 - author_name: Nico Vibert - excerpt: We need an engine to trigger an action based on an event. For VMware events, the best way to do that would be to leverage the VMware Event Broker Appliance (VEBA). I will leverage EventBridge as it integrates easily with AWS Lambda... + external_url: https://williamlam.com/2021/07/how-to-modernize-your-vsphere-alarm-actions-using-the-vmware-event-broker-appliance-veba.html + external_image: https://i0.wp.com/williamlam.com/wp-content/uploads/2021/07/modernizing-vsphere-alarm-actions-0.png + author_name: William Lam + excerpt: The benefits of VEBA can extend beyond just vSphere Events and can also be used with both new and existing vSphere Alarms. In fact, vSphere Alarms is just another a type of vSphere Event, which then makes it super easy to work with if you are already familiar with VEBA. - - title: Deploying the VMware Event Broker Appliance (VEBA) using Cloud Assembly + - title: VMware Event Broker Appliance – Part VIII – Building a New PowerCLI Function – Preparation display: true - details: + details: url_text: blog post - external_url: https://blogs.vmware.com/services-education-insights/2020/07/deploying-the-vmware-event-broker-appliance-veba-using-cloud-assembly.html - external_image: https://blogs.vmware.com/services-education-insights/files/2020/07/ovfScreenshot.png - author_name: VMware Blogs - James Wirth + external_url: http://www.patrickkremer.com/vmware-event-broker-appliance-part-viii-building-a-new-powercli-function-preparation/ + external_image: http://www.patrickkremer.com/wp-content/uploads/2022/03/kn-pcli-template.png + author_name: Patrick Kremer + excerpt: A Knative PowerCLI function template was committed to the repo in February 2022, and was released in 0.7.2. This template makes it easy to build a brand new function complete with a full set of documentation. This post walks through the process of using the template to build the kn-pcli-pg-check function, which was published in March 2022. + + - title: Custom webhook function to publish events into VMware Event Broker Appliance (VEBA) + display: true + details: + url_text: blog post + external_url: https://williamlam.com/2021/09/custom-webhook-function-to-publish-events-into-vmware-event-broker-appliance-veba.html + external_image: https://i0.wp.com/williamlam.com/wp-content/uploads/2021/09/vmware-event-broker-appliance-webhook-support-0.png + author_name: William Lam + excerpt: In my previous article, I demonstrated how you can leverage the upcoming v0.7 release of the VMware Event Broker Appliance (VEBA) to publish and consume custom events to easily extend your event-driven automation to other event sources. Once you have setup a wildcard DNS for your VEBA deployment, you can refer to this sample PowerShell function which demonstrates how to create and test a custom webhook function. + + - title: Managing VM snapshot retention policies using the VMware Event Broker Appliance (VEBA) + display: true + details: + url_text: blog post + external_url: https://williamlam.com/2021/10/managing-vm-snapshot-retention-policies-using-the-vmware-event-broker-appliance-veba.html + external_image: https://i0.wp.com/williamlam.com/wp-content/uploads/2021/10/snapshot-policy-retention-using-veba.png + author_name: William Lam + excerpt: Imagine if you could implement a snapshot retention policy for your VM(s) based on the size of a given snapshot or maybe the number of days the snapshot has existed? I realized we can easily do so with a bit of event-driven automation using our VMware Event Broker Appliance (VEBA) solution to run scheduled job for managing snapshot policies for a set of VM(s) + + - title: Bursting to the Cloud with VEBA, AWS Lambda & VMC on AWS + display: true + details: + url_text: blog post + external_url: https://nicovibert.com/2020/05/20/cloudbursting-vmwarecloud-veba-lambda-eventbridge/ + external_image: https://nicovmc.files.wordpress.com/2020/05/screenshot-2020-05-20-at-12.17.07.png?w=1024 + author_name: Nico Vibert excerpt: We need an engine to trigger an action based on an event. For VMware events, the best way to do that would be to leverage the VMware Event Broker Appliance (VEBA). I will leverage EventBridge as it integrates easily with AWS Lambda... - title: Using the VEBA to Automatically Expand a Pure Storage FlashArray Datastore @@ -59,25 +86,46 @@ links: external_url: https://davidstamen.com/using-veba-to-expand-pure-datastore/ external_image: https://davidstamen.com/images/veba05.png author_name: David Stamen - excerpt: can we automatically expand a datastore when it gets full? The answer is yes! With all of the integrations with automation platformsm, Pure Storage Arrays have many options. This blog will cover how to handle this with the VMware Event Broker Appliance (VEBA)... + excerpt: ...can we automatically expand a datastore when it gets full? The answer is yes! With all of the integrations with automation platformsm, Pure Storage Arrays have many options. This blog will cover how to handle this with the VMware Event Broker Appliance (VEBA)... - title: VMware Event Broker Appliance - vSphere HA Event Notification Function display: true - details: + details: url_text: blog post external_url: https://rguske.github.io/post/vsphere-ha-event-notification-function/ external_image: https://rguske.github.io/img/posts/202006_harestartfunction/CapturFiles-20200701_022720.jpg author_name: Robert Guske - excerpt: to send out a notification via Email after a vSphere HA event occurred. This Email will have the affected host mentioned as well as all affected VMs which has been restarted through vSphere HA. The problem was, that the execution of the script was a manual task every time a ESXi host outage took place…at least until he became aware of VEBA... - + excerpt: This post is about a great function contribution from a VEBA community member. His function example is about sending an email which notifies a recipient about the affected host(s) and VM(s) after an outage. + # # All other links with the below details populated. # use the flag display: true/false to control if the link needs to show up on the site. Videos show up in the same order as below # otherlinks: + - title: Quick Tip - Enabling vCenter Events for NTP (Network Time Protocol) or PTP (Precision Time Protocol) operations + display: true + url: https://williamlam.com/2022/05/quick-tip-enabling-vcenter-events-for-ntp-network-time-protocol-or-ptp-precision-time-protocol-operations.html + - title: Integrating VMware Event Broker Appliance (VEBA) with Zapier + display: true + url: https://williamlam.com/2022/04/integrating-vmware-event-broker-appliance-veba-with-zapier.html + - title: Publishing and consuming custom events with VMware Event Broker Appliance (VEBA) + display: true + url: https://williamlam.com/2021/09/publishing-and-consuming-custom-events-with-vmware-event-broker-appliance-veba.html + - title: Heads Up - No healthy upstream error with VEBA vSphere UI plugin with vSphere 7.0 Update 3 + display: true + url: https://williamlam.com/2021/10/heads-up-no-healthy-upstream-error-with-veba-vsphere-ui-plugin-with-vsphere-7-0-update-3.html + - title: Leveraging Fluent Bit on Tanzu Kubernetes Grid to send VEBA logs to vRealize Log Insight + display: true + url: https://rguske.github.io/post/leveraging-fluent-bit-on-tkg-to-send-veba-logs-to-vrli/ + - title: (German) VMware Event Broker Appliance - das Mega Release 0.6 bringt Simplifizierung und Optimierung + display: true + url: https://youtu.be/Zqp3Z5uV1n4 - title: VMworld 2020 - VEBA and the Power of Event-Driven Automation – Reloaded display: true url: https://www.vmworld.com/en/video-library/video-landing.html?sessionid=1586353214997001Abo2 + - title: Deploying the VMware Event Broker Appliance (VEBA) using Cloud Assembly + display: true + url: https://blogs.vmware.com/services-education-insights/2020/07/deploying-the-vmware-event-broker-appliance-veba-using-cloud-assembly.html - title: VMworld 2020 - Arm Yourself with Event-Driven Functions and Reimagine SDDC Capabilities display: true url: https://www.vmworld.com/en/video-library/video-landing.html?sessionid=15863800295950014HrA @@ -148,14 +196,14 @@ tweet_collection: # Current max limit of videos set to 3. Videos require the video_id # videos: -- title: VEBA team talk about Event Driven Automation, usecases and showcase a demo - video_id: tOjp5_qn-Fg - url: https://www.youtube.com/watch?v=tOjp5_qn-Fg&feature=emb_logo +- title: VEBA Revolutions - Unleashing the Power of Event-Driven Automation + video_id: jwgJpZM68mA + url: https://www.youtube.com/watch?v=jwgJpZM68mA&feature=emb_logo -- title: Learn how to deploy VEBA and deploy your own functions - video_id: /videoseries?list=PLQvw_QGg8w6qlcCxXnighoK780ElUrOFj - url: https://www.youtube.com/playlist?list=PLQvw_QGg8w6qlcCxXnighoK780ElUrOFj +- title: Optimize Kubernetes on vSphere with Event-Driven Automation + video_id: NJYBwJemdoY + url: https://youtu.be/NJYBwJemdoY -- title: DEMO - Veeam VM Backup FaaS Function - video_id: OtAG9C8FM4U - url: https://www.youtube.com/watch?v=OtAG9C8FM4U&feature=emb_logo \ No newline at end of file +- title: DIY Deployment of Event-Driven Automation in vSphere Environments + video_id: ieUqfir5Oag + url: https://youtu.be/ieUqfir5Oag&feature=emb_logo \ No newline at end of file diff --git a/docs/_functions/sample1.md b/docs/_functions/sample1.md index fd269455..01c94186 100644 --- a/docs/_functions/sample1.md +++ b/docs/_functions/sample1.md @@ -3,6 +3,6 @@ title: vSphere Alarm Function icon: case-study-2.svg #subtitle: Subheading goes here links: - Deploy Function: https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/development/examples/knative/powershell/kn-ps-slack-vsphere-alarm + Deploy Function: https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/examples/knative/powershell/kn-ps-slack-vsphere-alarm --- vSphere alarm events do not provide metadata about defined triggers and thresholds. This example demonstrates how Knative flows can be used to enrich events and send it to other functions for consumption. \ No newline at end of file diff --git a/docs/_functions/sample3.md b/docs/_functions/sample3.md index 87bd80c2..428a1dc3 100644 --- a/docs/_functions/sample3.md +++ b/docs/_functions/sample3.md @@ -3,6 +3,6 @@ title: Slack Notification Function icon: case-study-3.svg #subtitle: Subheading goes here links: - Deploy Function: https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/development/examples/knative/python/kn-py-slack + Deploy Function: https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/examples/knative/python/kn-py-slack --- Based on a Virtual Machine powered off event, this function will send a message to Slack with details about the virtual machine, e.g. when and by whom was this event triggered. \ No newline at end of file diff --git a/docs/_includes/head.html b/docs/_includes/head.html index 6a77fca6..9117d1d0 100644 --- a/docs/_includes/head.html +++ b/docs/_includes/head.html @@ -5,9 +5,6 @@ - - - {{ site.title }} {{page.title}} {% seo %} {% include google-analytics.html %} diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 1e889cfe..94addefe 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -13,5 +13,4 @@ - diff --git a/docs/assets/css/custom-tabs.css b/docs/assets/css/custom-tabs.css deleted file mode 100644 index e9d72f2f..00000000 --- a/docs/assets/css/custom-tabs.css +++ /dev/null @@ -1,48 +0,0 @@ -.tab { - display: flex; - flex-wrap: wrap; - margin-left: -20px; - padding: 0; - list-style: none; - position: relative; -} - -.tab > * { - flex: none; - padding-left: 20px; - position: relative; -} - -.tab > * > a { - display: block; - text-align: center; - padding: 9px 20px; - color: #999; - border-bottom: 2px solid transparent; - border-bottom-color: transparent; - font-size: 12px; - text-transform: uppercase; - transition: color .1s ease-in-out; - line-height: 20px; -} - -.tab > .active > a { - color:#222; - border-color: #1e87f0; -} - -.tab li a { - text-decoration: none; - cursor: pointer; -} - -.tab-content{ - padding: 0; -} - -.tab-content li { - display: none; -} -.tab-content li.active { - display: initial; -} diff --git a/docs/assets/img/icons/github.svg b/docs/assets/img/icons/github.svg new file mode 100644 index 00000000..36e758fb --- /dev/null +++ b/docs/assets/img/icons/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/kb/advanced-certificates.md b/docs/kb/advanced-certificates.md index 8dfee9c4..c066ac57 100644 --- a/docs/kb/advanced-certificates.md +++ b/docs/kb/advanced-certificates.md @@ -209,12 +209,18 @@ This section demonstrates installation of the Let's Encrypt Certbot Docker image ### Steps -Step 1 - Pull the Certbot Docker image +Step 1 - Start the Docker daemon. VEBA uses containerd for its container runtime - the Docker daemon is disabled by default. + +```console +systemctl start docker +``` + +Step 2 - Pull the Certbot Docker image ```console docker pull certbot/certbot ``` -Step 2 - Run certbot. For the `-d` (domain) switch, use your VEBA FQDN. You will be prompted for an e-mail address as well as some yes/no questions. +Step 3 - Run certbot. For the `-d` (domain) switch, use your VEBA FQDN. You will be prompted for an e-mail address as well as some yes/no questions. ```console docker run -it --rm --name certbot -v "/etc/letsencrypt:/etc/letsencrypt" ` -v "/var/lib/letsencrypt:/var/lib/letsencrypt" ` @@ -263,9 +269,9 @@ value(s) you've just added. Press Enter to Continue ``` -Step 3 - Using your public DNS provider's tools, configure the required TXT record as prompted in Step 2. +Step 4 - Using your public DNS provider's tools, configure the required TXT record as prompted in Step 2. -Step 4 - Press Enter to continue. If you have configured DNS properly, the certificate PEM files will be saved in the location specified. +Step 5 - Press Enter to continue. If you have configured DNS properly, the certificate PEM files will be saved in the location specified. ``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -281,6 +287,12 @@ NEXT STEPS: - This certificate will not be renewed automatically. Autorenewal of --manual certificates requires the use of an authentication hook script (--manual-auth-hook) but one was not provided. To renew this certificate, repeat this same certbot command before the certificate's expiry date. ``` -Step 5 - Install the certificate - follow the instructions starting with step 2 of [Replacing an Existing Cert on VEBA](#replacestep2). Note from the output above that the public key file is named `fullchain.pem` - you will need to pass this value for the `--cert` argument when creating the Kubernetes TLS certificates. +Step 6 - Install the certificate - follow the instructions starting with step 2 of [Replacing an Existing Cert on VEBA](#replacestep2). Note from the output above that the public key file is named `fullchain.pem` - you will need to pass this value for the `--cert` argument when creating the Kubernetes TLS certificates. + +Step 7 - Stop the Docker daemon + +```console +systemctl stop docker +``` -Step 6 (optional) - If you want to automate renewals, this is an excellent blog on configuring [automated certificate renewals](https://chariotsolutions.com/blog/post/automating-lets-encrypt-certificate-renewal-using-dns-challenge-type/) using DNS validation. \ No newline at end of file +Step 8 (optional) - If you want to automate renewals, this is an excellent blog on configuring [automated certificate renewals](https://chariotsolutions.com/blog/post/automating-lets-encrypt-certificate-renewal-using-dns-challenge-type/) using DNS validation. \ No newline at end of file diff --git a/docs/kb/contribute-appliance.md b/docs/kb/contribute-appliance.md index 2d8a2517..6eee0110 100644 --- a/docs/kb/contribute-appliance.md +++ b/docs/kb/contribute-appliance.md @@ -13,8 +13,9 @@ cta: ## Requirements -* 4 vCPU and 8GB of memory for VMware Event Broker Appliance +* 6 vCPU and 8GB of memory for VMware Event Broker Appliance * ESXi host v6.7 or greater + * Datastore with at least 60GB of free space * SSH must be enabled on the host * Enable GuestIPHack on the host by running `esxcli system settings advanced set -o /Net/GuestIPHack -i 1` * The following must be installed on your development machine: @@ -23,6 +24,7 @@ cta: * [Packer](https://www.packer.io/intro/getting-started/install.html){:target="_blank"} (v1.6.3 or greater) * [jq](https://stedolan.github.io/jq/){:target="_blank"} * Development machine must have the firewall disabled for the duration of the build +> **Note:** It has been seen that Packer can bind to an IPv6 on the development machine - you may wish to disable IPv6! * Development machine must be on the same L2 subnet as the target VM portgroup defined in `builder_host_portgroup` below diff --git a/docs/kb/contribute-functions.md b/docs/kb/contribute-functions.md index 14e89047..15301f02 100644 --- a/docs/kb/contribute-functions.md +++ b/docs/kb/contribute-functions.md @@ -5,7 +5,7 @@ title: VMware Event Broker Appliance - Building Functions description: Building Functions permalink: /kb/contribute-functions cta: - title: Have a question? + title: Have a question? description: Please check our [Frequently Asked Questions](/faq) first. --- @@ -16,7 +16,7 @@ The VMware Event Broker Appliance (VEBA) uses Knative as a Function-as-a-Service [here](functions). You can also get started quickly with these quickstart [templates](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/examples/knative){:target="_blank"}. -## Instructions +## Intro This guide describes how to create a function with PowerCLI (PowerShell) to apply a vSphere tag when a Virtual Machine is powered on. @@ -26,6 +26,11 @@ apply a vSphere tag when a Virtual Machine is powered on. > correctly. Access to the Kubernetes environment in VEBA via `kubectl` is also > assumed to be working. +A template for Knative PowerCLI functions is available in [kn-pcli-template](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/examples/knative/powercli/kn-pcli-template). If you do not want to build all of the required files from scratch, you can copy all of the files from this template directory. Follow the instructions in the [README](https://github.com/vmware-samples/vcenter-event-broker-appliance/blob/master/examples/knative/powercli/kn-pcli-template/README.md) instead of the instructions on this page. + +To create a function from scratch, continue with the instructions on this page. + +## Instructions First, create a directory for your function code, credentials (implemented via Kubernetes [secrets](https://kubernetes.io/docs/concepts/configuration/secret/)) @@ -35,8 +40,6 @@ and test files. mkdir tag-fn && cd tag-fn ``` -A template for Knative PowerCLI functions is available in [kn-pcli-template](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/development/examples/knative/powercli/kn-pcli-template). If you do not want to build all of the required files from scratch, you can copy all of the files from this template directory, then follow the instructions in the README. - Before we start looking at the actual function business logic (inside `handler.ps1`), let's discuss how `secrets`, such as credentials, are injected and used inside a function. diff --git a/docs/kb/deploy-event-router-kind.md b/docs/kb/deploy-event-router-kind.md index 00185a7c..465326ce 100644 --- a/docs/kb/deploy-event-router-kind.md +++ b/docs/kb/deploy-event-router-kind.md @@ -9,7 +9,7 @@ permalink: /kb/deploy-event-router-kind # Installation of VMware Event Router with `kind` The following steps describe the installation of the [VMware Event -Router](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/development/vmware-event-router) +Router](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/vmware-event-router) in a local [kind](https://kind.sigs.k8s.io/) cluster and [Knative](https://knative.dev/) environment. The steps assume a Mac OSX environment but the links provide resources to diff --git a/docs/kb/function-tutorial-deploy.md b/docs/kb/function-tutorial-deploy.md index f8dc6abb..3b8b6d25 100644 --- a/docs/kb/function-tutorial-deploy.md +++ b/docs/kb/function-tutorial-deploy.md @@ -85,25 +85,18 @@ docker.io//: ## Introduction to the Kubernetes vmware-functions namespace With the Docker image pushed to the registry, we are now ready to deploy the function to the VEBA appliance. Remember, you will need to copy the Kubernetes config file to your workstation and export the `KUBECONFIG` environment variable so that the `kubectl` command can access the Kubernetes cluster on the VEBA appliance. We will use `kubectl` to deploy the function. Below is a reminder of the steps we used to copy and use the VEBA appliance config file. Getting the Kubernetes config file was covered in the intro [Function Tutorial - Function Intro](function-tutorial-intro). If you have opened a new terminal window, you may need to set the `KUBECONFIG` environment variable once more for the current session. -{% tabs export KUBECONFIG %} - -{% tab export KUBECONFIG#macOS%} +**Hint:** KUBECONFIG export for macOS: ``` export KUBECONFIG=$HOME/veba/config ``` -{% endtab %} - -{% tab export KUBECONFIG#Windows %} +**Hint:** KUBECONFIG export for Windows: ``` Env:KUBECONFIG="$HOME\veba\config" ``` -{% endtab %} - -{% endtabs %} Kubernetes namespaces are resource boundaries within the cluster. Function related resources in the VEBA appliance are segregated into the "vmware-functions" namespace. Use `kubectl` to list out the resources in the vmware-functions namespace: @@ -306,7 +299,7 @@ We have used `kubectl get` and `kubectl apply` in the above examples. The follo ### describe ### -The `kubectl describe` command is very useful and shows details about Kubernetes objects like pods. Remember, we need to explicitely set the namespace if not "default". +The `kubectl describe` command is very useful and shows details about Kubernetes objects like pods. Remember, we need to explicitly set the namespace if not "default". ``` kubectl -n vmware-functions get pods diff --git a/docs/kb/function-tutorial-intro.md b/docs/kb/function-tutorial-intro.md index df416f44..bb3086de 100644 --- a/docs/kb/function-tutorial-intro.md +++ b/docs/kb/function-tutorial-intro.md @@ -22,14 +22,24 @@ This tutorial will go over: - How to deploy the function to the Kubernetes cluster in your VEBA appliance ## Table of Contents -- [The big picture](#the-big-picture) -- [The anatomy of a VEBA function](#the-anatomy-of-a-veba-function) -- [Installing required tools on your workstation](#installing-required-tools-on-your-workstation) +- [In-depth Function Tutorial - Intro](#in-depth-function-tutorial---intro) + - [Table of Contents](#table-of-contents) + - [The big picture](#the-big-picture) + - [The anatomy of a VEBA function](#the-anatomy-of-a-veba-function) + - [Installing required tools on your workstation](#installing-required-tools-on-your-workstation) + - [macOS Instructions](#macos-instructions) + - [Install `git` and clone the repo to your workstation](#install-git-and-clone-the-repo-to-your-workstation) + - [Install Docker](#install-docker) + - [Install and Configure `kubectl`](#install-and-configure-kubectl) + - [Windows Instructions](#windows-instructions) + - [Install `git` and clone the repo to your workstation](#install-git-and-clone-the-repo-to-your-workstation-1) + - [Install Docker](#install-docker-1) + - [Install and Configure `kubectl`](#install-and-configure-kubectl-1) ## The big picture VEBA functions provide the "custom" logic to the VEBA appliance to fulfil your business requirements. Functions are packaged as Docker images. The functions can be written in PowerShell, Python, Go, or just about any language, and are packaged and distributed as Docker images. This is because the VEBA appliance runs Kubernetes (on top of the Photon OS) and functions are deployed as containers on the Kubernetes system. Kubernetes provides an abstraction layer to allow containers to run seamlessly on disparate hardware/OSes. -The VEBA team has provided a library of sample functions for you to get started with: [VEBA Functions](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/development/examples/knative). As you can see, they are assembled under the "knative" folder. Knative is a framework of building blocks for Kubernetes that provides basic services to the VEBA application. So the first step would be to review the provided functions and determine if there are any functions that match your use case requirements. If your use case was to send an email message when a specific vCenter event occurred, the function: [kn-ps-email](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/development/examples/knative/powershell/kn-ps-email) would be a good solution to begin with. In looking at the function, you can see that it is currently configured to fire/trigger for a VM deletion event. Don't worry if your use case is a different vCenter event - this is easily modified in the function regardless of if you are adept with PowerShell programming. +The VEBA team has provided a library of sample functions for you to get started with: [VEBA Functions](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/examples/knative). As you can see, they are assembled under the "knative" folder. Knative is a framework of building blocks for Kubernetes that provides basic services to the VEBA application. So the first step would be to review the provided functions and determine if there are any functions that match your use case requirements. If your use case was to send an email message when a specific vCenter event occurred, the function: [kn-ps-email](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/examples/knative/powershell/kn-ps-email) would be a good solution to begin with. In looking at the function, you can see that it is currently configured to fire/trigger for a VM deletion event. Don't worry if your use case is a different vCenter event - this is easily modified in the function regardless of if you are adept with PowerShell programming. **A word about programming languages:** If you find a function that meets your requirements but is written in a language you are not comfortable with, don't worry, you will still be able to deploy that function. There are two parts to the VEBA functions: the programming logic and the input variables. Variables contain things like: IP addresses, Event types, authentication parameters, email addresses, or Slack keys. Variables are easily input and modified without knowledge of the specific programming language. You will find examples of this later in the tutorial. @@ -62,12 +72,10 @@ You will need to install and configure/use three tools: Note: even if the function's code is not modified, Docker will still be necessary to run local tests of the function from your workstation. -Install instructions for the required tools follow: +Install instructions for the required tools on macOS are below. Install instructions for Windows follow after the macOS instructions. -{% tabs install %} - -{% tab install#macOS%} +## macOS Instructions ### Install `git` and clone the repo to your workstation @@ -195,9 +203,7 @@ vmware-system Active 14d ``` -{% endtab %} - -{% tab install#Windows %} +## Windows Instructions ### Install `git` and clone the repo to your workstation @@ -326,8 +332,3 @@ vmware-functions Active 14d vmware-system Active 14d ``` - - -{% endtab %} - -{% endtabs %} diff --git a/docs/kb/install-knative.md b/docs/kb/install-knative.md index 0dd094ac..596084a8 100644 --- a/docs/kb/install-knative.md +++ b/docs/kb/install-knative.md @@ -6,7 +6,7 @@ description: Deploying VMware Event Broker Appliance with Knative permalink: /kb/install-knative cta: title: Deploy a Function - description: At this point, you have successfully deployed the VMware Event Broker Appliance and you are ready to start deploying your functions! + description: At this point, you have successfully deployed the VMware Event Broker Appliance and you are ready to start deploying your functions! actions: - text: Check the [Knative Echo Function](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/examples/knative/powershell/kn-ps-echo){:target="_blank"} to quickly get started --- @@ -18,9 +18,9 @@ Customers looking to seamlessly extend their vCenter by either deploying our pre ### Requirements -* 4 vCPU and 8GB of memory for VMware Event Broker Appliance +* 6 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 + * **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) @@ -121,4 +121,3 @@ Webhook: https://[hostname]/stats/webhook ### Step 4 You can verify that everything was deployed correctly by opening a web browser and accessing one of the endpoints along with the associated admin password you had specified as part of the OVA deployment. - diff --git a/docs/kb/intro-about.md b/docs/kb/intro-about.md index 247b0b57..79c4da64 100644 --- a/docs/kb/intro-about.md +++ b/docs/kb/intro-about.md @@ -52,4 +52,4 @@ VMware Event Broker Appliance enables customers to quickly get started with pre- ### Community Use Cases -Please see [this list here](https://github.com/vmware-samples/vcenter-event-broker-appliance/USECASES.md) for a collection of use cases from the VMware Event Broker Appliance community. \ No newline at end of file +Please see [this list here](https://github.com/vmware-samples/vcenter-event-broker-appliance/blob/master/USECASES.md) for a collection of use cases from the VMware Event Broker Appliance community. \ No newline at end of file diff --git a/docs/kb/intro-event-router.md b/docs/kb/intro-event-router.md index 73eee07a..33509fee 100644 --- a/docs/kb/intro-event-router.md +++ b/docs/kb/intro-event-router.md @@ -50,7 +50,7 @@ to normalize events from the supported event `providers`. See - with the [Horizon event provider](#provider-type-horizon) - with the [vcsim event provider](#provider-type-vcsim) -**Note:** All implemented event `processors` use built-in retry mechanisms so +> **Note:** All implemented event `processors` use built-in retry mechanisms so your function might still be involved multiple times depending on its response code. However, if an event `provider` crashes before sending an event to the configured `processor` or when the `processor` returns an error, the event is @@ -64,8 +64,8 @@ not retried and discarded. router crashes **within seconds** right after startup and having received *n* events but before creating the first valid checkpoint (current checkpoint interval is 5s) - If an event cannot be successfully delivered (retried) by an event `processor` it is - logged and discarded, i.e. there is currently no support for dead letter - queues (see note below) + logged and discarded, i.e. there is currently no support for [dead letter + queues](https://en.wikipedia.org/wiki/Dead_letter_queue) (see note below) - Retries in the [OpenFaaS event processor](#processor-type-openfaas) are only supported when running in synchronous mode, i.e. `async: false` (see this OpenFaaS [issue](https://github.com/openfaas/nats-queue-worker/issues/84)) @@ -97,6 +97,7 @@ not retried and discarded. - [The `auth` section](#the-auth-section) - [Type `basic_auth`](#type-basic_auth) - [Type `aws_access_key`](#type-aws_access_key) + - [Type `aws_iam_role`](#type-aws_iam_role) - [Type `active_directory`](#type-active_directory) - [The `metricsProvider` section](#the-metricsprovider-section) - [Provider Type `default`](#provider-type-default) @@ -143,7 +144,7 @@ version: The following sections describe the layout of the configuration file (YAML) and specific options for the supported event `providers`, `processors` and `metrics` -endpoint. Configuration examples are provided [here](deploy/). +endpoint. Configuration examples are provided [here](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/vmware-event-router/deploy). > **Note:** Currently only one event `provider` and one event `processor` can be > configured at a time, e.g. one vCenter Server instance streaming events to @@ -197,7 +198,7 @@ metricsProvider: ## JSON Schema Validation In order to simplify the configuration and validation of the YAML configuration -file a JSON schema [file](README.MD) is provided. Many editors/IDEs offer +file a JSON schema [file](https://github.com/vmware-samples/vcenter-event-broker-appliance/blob/master/vmware-event-router/routerconfig.schema.json) is provided. Many editors/IDEs offer support for registering a schema file, e.g. [Jetbrains](https://www.jetbrains.com/help/rider/Settings_Languages_JSON_Schema.html) and [VS @@ -428,15 +429,21 @@ only forwards events configured in the associated `rule` of an event bus. Rules in AWS EventBridge use pattern matching ([docs](https://docs.aws.amazon.com/eventbridge/latest/userguide/filtering-examples-structure.html)). Upon start, VMware Event Router contacts EventBridge (using the given IAM role) -to parse and extract event categories from the configured rule ARN (see -configuration option below). +to parse the configured rule ARN (see configuration option below). -The VMware Event Router uses the `"subject"` field in the event payload to store -the event category, e.g. `"VmPoweredOnEvent"`. Thus it is required that you use -a **specific pattern match** (`"detail->subject"`) that the VMware Event Router -can parse to retrieve the desired event (forwarding) categories. For example, -the following AWS EventBridge event pattern rule matches power on/off events -(including DRS-enabled clusters): +The VMware Event Router uses the pattern match library which supports a subset +of the EventBridge pattern rules. You may only use these supported patterns in +your specified EventBridge rule. Refer to [this +page](https://github.com/timbray/quamina/blob/v0.2.0/PATTERNS.md) for the +currently supported patterns in `Quamina`. + +> **Note:** EventBridge wraps each VMware Event Router event (CloudEvent) into +> an EventBridge message envelop. The `detail` field contains the JSON +> representation of the full CloudEvent as produced by the VMware Event Router. + +The following examples show supported and useful patterns. + +Example: Forward all CloudEvents containing one of the specified `subjects`: ```json { @@ -446,10 +453,34 @@ the following AWS EventBridge event pattern rule matches power on/off events } ``` -`"subject"` can contain one or more event categories. Wildcards (`"*"`) are not -supported. If one wants to modify the event pattern match rule **after** -deploying the VMware Event Router, its internal rules cache is periodically -synchronized with AWS EventBridge at a fixed interval of 5 minutes. +Example: Forward all CloudEvents containing a `subject` with the prefix `Vm`: + +```json +{ + "detail": { + "subject": [{ + "shellstyle": "Vm*" + }] + } +} +``` + +Example: Forward all CloudEvents containing virtual machines with the prefix +`Linux`: + +```json +{ + "detail": { + "data": { + "Vm": { + "Name": [{ + "shellstyle": "Linux*" + }] + } + } + } +} +``` > **Note:** A list of event names (categories) and how to retrieve them can be > found @@ -463,7 +494,7 @@ as an event `processor`. | `region` | String | AWS region to use, see [regions doc](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html). | true | `us-west-1` | | `eventBus` | String | Name of the event bus to use | true | `default` or `arn:aws:events:us-west-1:1234567890:event-bus/customBus` | | `ruleARN` | String | Rule ARN to use for event pattern matching | true | `arn:aws:events:us-west-1:1234567890:rule/vmware-event-router` | -| `` | Object | AWS IAM role credentials | true | (see `aws_access_key` example below) | +| `` | Object | AWS IAM role credentials | true | (see `aws_access_key` and `aws_iam_role` examples below) | ## The `auth` section @@ -490,6 +521,9 @@ Supported providers/processors: ### Type `aws_access_key` +Use an AWS IAM role with the provided access key ID and secret access key for +authentication. + Supported providers/processors: - `aws_event_bridge` @@ -501,8 +535,48 @@ Supported providers/processors: | `awsAccessKeyAuth.accessKey` | String | Access Key ID for the IAM role used | true | `ABCDEFGHIJK` | | `awsAccessKeyAuth.secretKey` | String | Secret Access Key for the IAM role used | true | `ZYXWVUTSRQPO` | -> **Note:** Currently only IAM user accounts with access key/secret are -> supported to authenticate against AWS EventBridge. Please follow the [user +> **Note:** Please follow the EventBridge IAM [user +> guide](https://docs.aws.amazon.com/eventbridge/latest/userguide/getting-set-up-eventbridge.html) +> before deploying the event router. Further information can also be found in +> the +> [authentication](https://docs.aws.amazon.com/eventbridge/latest/userguide/auth-and-access-control-eventbridge.html#authentication-eventbridge) +> section. + +In addition to the recommendation in the AWS EventBridge user guide you might +want to lock down the IAM role for the VMware Event Router and scope it to these +permissions ("Action"): + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "events:PutEvents", + "events:ListRules", + "events:TestEventPattern" + ], + "Resource": "*" + } + ] +} +``` + +### Type `aws_iam_role` + +Use an AWS IAM role configured from the shared credentials file. + +Supported providers/processors: + +- `aws_event_bridge` + +| Field | Type | Description | Required | Example | +|--------|--------|------------------------------|----------|----------------| +| `type` | String | Authentication method to use | true | `aws_iam_role` | + +> **Note:** Please follow the EventBridge IAM [user > guide](https://docs.aws.amazon.com/eventbridge/latest/userguide/getting-set-up-eventbridge.html) > before deploying the event router. Further information can also be found in > the @@ -592,7 +666,7 @@ using the Knative backend. ### Helm Deployment -The Helm files are located in the [chart](chart/) directory. The `values.yaml` +The Helm files are located in the [chart](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/vmware-event-router/chart) directory. The `values.yaml` file contains the allowed parameters and parameter descriptions which map to the VMware Event Router [configuration](#overview-configuration-file-structure-yaml) file. @@ -756,7 +830,7 @@ Create a namespace where the VMware Event Router will be deployed to: $ kubectl create namespace vmware ``` -Use one of the configuration files provided [here](deploy/) to configure the +Use one of the configuration files provided [here](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/vmware-event-router/deploy) to configure the router with **one** VMware vCenter Server `eventProvider` and **one** OpenFaaS **or** AWS EventBridge `eventProcessor`. Change the values to match your environment. The following example will use the OpenFaaS config sample. @@ -848,7 +922,6 @@ Deploy the VMware Event Router: ```console $ kubectl -n vmware create -f release.yaml ``` -``` Check the logs of the VMware Event Router to validate it started correctly: diff --git a/docs/kb/intro-functions.md b/docs/kb/intro-functions.md index a807daf6..211ed829 100644 --- a/docs/kb/intro-functions.md +++ b/docs/kb/intro-functions.md @@ -28,7 +28,7 @@ The VMware Event Broker Appliance can be deployed using the Knative event proces ## Knative -Users who directly want to jump into VMware vSphere-related function code might want to check out the examples we provide [here](/examples/knative). +Users who directly want to jump into VMware vSphere-related function code might want to check out the examples we provide [here](/examples). ### Knative Naming and Version Control diff --git a/docs/kb/private-registry.md b/docs/kb/private-registry.md new file mode 100644 index 00000000..36cd4335 --- /dev/null +++ b/docs/kb/private-registry.md @@ -0,0 +1,136 @@ +--- +layout: docs +toc_id: private-container-registry +title: VMware Event Broker Appliance - Private Registry +description: Private Registry +permalink: /kb/private-registry +cta: + description: Using private container registry with the VMware Event Broker Appliance. +--- + +## Using private container registry with VEBA + +By default, the VMware Event Broker Appliance can integrate with any Open Container Initiative (OCI) compliant container registry for hosting and deploying container images that uses a TLS certificate from a trusted authority such as [Docker Hub](https://hub.docker.com/) or [Amazon Elastic Container Registry (ECR)](https://aws.amazon.com/ecr/) as an example. + +For organizations that require the use of a private container registry and uses a self-signed TLS certificate, an additional post-deployment configuration is required within the VMware Event Broker Appliance. Please follow the steps outlined below. + +### Assumptions + +* VMware Event Broker Appliance v0.7.2 or later +* Root CA Certificates from a trusted authority has been pre-downloaded onto your local desktop +* Un-deploy any functions that has been attempted using private registry prior to instructions below + +> **Note:** For those using the Harbor registry, the root CA certificate is located in : **/etc/docker/certs.d/[FQDN]/ca.crt** + +### Steps + +In this example, the root CA certificate key file is named `ca.crt` and is located in `/root` + +1. Copy the root CA certificate from your private registry to VMware Event Broker Appliance Appliance. If SSH has not been enabled, go ahead and start it up by logging into the VM Console and running the following command: + +```console +systemctl start sshd +``` + +1. SSH to the VMware Event Broker Appliance and make a backup of the original containerd configuration file + +```console +cp /etc/containerd/config.toml /etc/containerd/config.toml.bak +``` + +1. Edit the '/etc/containerd/config.toml' file using VI and locate the following section `[plugins."io.containerd.grpc.v1.cri".registry.mirrors]` within the configuration file. + +Append the following two lines below this section and replace the **REPLACE_ME_FQDN** value with FQDN of the private registry and **REPLACE_ME_PATH_TO_ROOT_CA_CERT** value the full path to the root CA certificate located on the VMware Event Broker Appliance + +```yaml +[plugins."io.containerd.grpc.v1.cri".registry.mirrors] + [plugins."io.containerd.grpc.v1.cri".registry.configs."REPLACE_ME_FQDN".tls] + ca_file = "REPLACE_ME_PATH_TO_ROOT_CA_CERT" +``` + +1. Restart the containerd service for the change to go into effect/. + +```console +systemctl restart containderd +``` + +1. Verify containerd is successfully running before proceeding to the next step + +```console +systemctl status containerd + +● containerd.service - containerd container runtime + Loaded: loaded (/usr/lib/systemd/system/containerd.service; enabled; vendor preset: disabled) + Active: active (running) since Mon 2022-03-28 19:35:06 UTC; 1 day 3h ago + Docs: https://containerd.io + Process: 30072 ExecStartPre=/sbin/modprobe overlay (code=exited, status=0/SUCCESS) + Main PID: 30073 (containerd) + Tasks: 545 + Memory: 1.2G + CGroup: /system.slice/containerd.service +``` + +1. Create a kubernetes secret in the `knative-serving` namespace that points to full path of the root CA certificate of private registry which should reside within the VMware Event Broker Appliance + +```console +kubectl -n knative-serving create secret generic customca --from-file=ca.crt=/root/ca.crt +``` + +1. Retrieve the current Knative Serving controller deployment and save it to a file named `knative-serving-controller.yaml` + +```console +kubectl -n knative-serving get deploy/controller -o yaml > knative-serving-controller.yaml +``` + +1. Create the following YTT overlay which will be used to patch the Knative Serving controller to reference the root CA certificate from the private registry + +```console +cat > overlay.yaml < new-knative-serving-controller.yaml +``` + +1. Apply the new Knative Serving controller configuration + +```console +kubectl apply -f new-knative-serving-controller.yaml +``` + +It can take a couple of minutes for the previous Knative Serving controller to terminate and spawn the new configuration. You can monitor the progress using the following commmand and ensure the `READY` state shows 1/1 + +```console +kubectl -n knative-serving get deployment/controller -w + +NAME READY UP-TO-DATE AVAILABLE AGE +controller 1/1 1 1 29h +``` + +> **Note:** If for some reason the deployment is not re-deploying, you can run `kubectl -n knative-serving delete deployment/controller` and then perform the `apply` operation with the new Knative Serving YAML. \ No newline at end of file diff --git a/docs/kb/troubleshoot-appliance.md b/docs/kb/troubleshoot-appliance.md index 9eea24b3..17194da9 100644 --- a/docs/kb/troubleshoot-appliance.md +++ b/docs/kb/troubleshoot-appliance.md @@ -5,7 +5,7 @@ title: VMware Event Broker Appliance Troubleshooting description: Troubleshooting guide for general appliance issues permalink: /kb/troubleshoot-appliance cta: - title: Still having trouble? + title: Still having trouble? description: Please submit bug reports and feature requests by using our GitHub [Issues](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues){:target="_blank"} page or Join us on slack [#vcenter-event-broker-appliance](https://vmwarecode.slack.com/archives/CQLT9B5AA){:target="_blank"} on vmwarecode.slack.com. --- # VMware Event Broker Appliance - Troubleshooting @@ -74,7 +74,10 @@ vmware-system vmware-event-router-5dd9c8f858-5c9mh 0/1 > **Note:** The status ```Completed``` of the container ```contour-certgen-v1.10.0-btmlp``` is expected after successful appliance deployment. -One of the first things to look for is whether a pod is in a crash state. In this case, the vmware-event-router pod is crashing. We need to look at the logs with this command: +One of the first things to look for is whether a pod is in a crash state. + +### Recovering from a crashing event router pod +In the above case, the vmware-event-router pod is crashing. We need to look at the logs with this command: ```bash kubectl -n vmware-system logs vmware-event-router-5dd9c8f858-5c9mh @@ -167,6 +170,17 @@ Here is the command output: We now see that the Event Router came online, connected to vCenter, and successfully received an event. +### Check for completed installation + +If the pods appear to be up without a crash status, check to make sure the installation completed. The file `/root/ran_customzation` gets created when installation completes successfully. If this file is missing, you can turn to the installation logs to find out why. +```bash +root@veba [ ~ ]# ls -al /root/ran_customization +-rw-r--r-- 1 root root 0 Oct 1 2021 /root/ran_customization +``` + +### Examine log files + +The appliance installation log file is found in `/var/log/bootstrap.log`. If enabled at install time, a debug log is available in `/var/log/bootstrap-debug.log`. The logs should point you toward the source of the issue. Don't hesitate to [reach out](#bottom) to the team if you need help. ## Changing the vCenter service account If you need to change the account the appliance uses to connect to vCenter, the procedure above can be used. @@ -292,4 +306,5 @@ Here is the command output: } } } -``` \ No newline at end of file +``` + \ No newline at end of file diff --git a/docs/kb/use-vcenter-events.md b/docs/kb/use-vcenter-events.md index 2b3bfddb..e5aeda87 100644 --- a/docs/kb/use-vcenter-events.md +++ b/docs/kb/use-vcenter-events.md @@ -15,19 +15,19 @@ cta: # vCenter Events -vCenter produces events that get generated in response to actions taken on an entity such as VM, Host, Datastore, etc. These events contain immutable facts documenting the entity state changes such as who initiated the change, what action was performed, which object was modified, and when was the change initiated. +vCenter produces events that get generated in response to actions taken on an entity such as VM, Host, Datastore, etc. These events contain immutable facts documenting the entity state changes such as who initiated the change, what action was performed, which object was modified, and when was the change initiated. Events naturally serve as auditing and troubleshooting tools, allowing an administrator to retrieve details on a specific change. Event Driven Automation builds on the construct of events and enables advanced distributed design patterns driven through Events. VMware Event Broker Appliance aims to enable this for VMware SDDC by enabling VI Administrators to write lean functions (script or code) that are triggered by vCenter Events. ## Overview of the vCenter events -vCenter Events are categorized by the Objects and the actions that are allowed on these objects and are documented under the vSphere API [7.0U2 reference](https://vdc-download.vmware.com/vmwb-repository/dcr-public/8946c1b6-2861-4c12-a45f-f14ae0d3b1b9/a5b8094c-c222-4307-9399-3b606a04af55/vim.event.Event.html){:target="_blank"}. +vCenter Events are categorized by the Objects and the actions that are allowed on these objects and are documented under the vSphere API [7.0U3 reference](https://vdc-download.vmware.com/vmwb-repository/dcr-public/bf660c0a-f060-46e8-a94d-4b5e6ffc77ad/208bc706-e281-49b6-a0ce-b402ec19ef82/SDK/vsphere-ws/docs/ReferenceGuide/vim.event.Event.html){:target="_blank"}. * Event * ClusterEvent * ClusterCreatedEvent, ClusterDestroyedEvent, ClusterOvercommittedEvent... * DatastoreEvent - * DatastoreCapacityIncreasedEvent, DatastoreDestroyedEvent, DatastoreDuplicatedEvent... + * DatastoreCapacityIncreasedEvent, DatastoreDestroyedEvent, DatastoreDuplicatedEvent... * DatacenterEvent * DatacenterCreatedEvent, DatacenterRenamedEvent * HostEvent @@ -36,7 +36,7 @@ vCenter Events are categorized by the Objects and the actions that are allowed o * VmNoNetworkAccessEvent, VmOrphanedEvent, VmPoweredOffEvent... * ... -There are over 1650+ events available on an out of the box install of vCenter that are provided [here](https://github.com/lamw/vcenter-event-mapping/){:target="_blank"}. You can also get the complete list of events for your specific vCenter using the following powershell script below. +There are over 1900+ events available on an out of the box install of vCenter that are provided [here](https://github.com/lamw/vcenter-event-mapping/){:target="_blank"}. You can also get the complete list of events for your specific vCenter using the following powershell script below. ```powershell $vcNames = "hostname" diff --git a/docs/site/community.md b/docs/site/community.md index d50681b2..6ddf00f9 100644 --- a/docs/site/community.md +++ b/docs/site/community.md @@ -11,9 +11,15 @@ links: - description: "Follow us at " url: "https://twitter.com/VMWEventBroker" label: "@VMWEventBroker" +- title: Github + image: /assets/img/icons/github.svg + items: + - description: "Follow us at " + url: "https://github.com/vmware-samples/vcenter-event-broker-appliance" + label: vcenter-event-broker-appliance - title: Slack image: /assets/img/icons/slack.svg - items: + items: - description: "Join us at" url: "https://vmwarecode.slack.com/archives/CQLT9B5AA" label: "#vcenter-event-broker-appliance" @@ -25,42 +31,136 @@ links: label: dl-veba@vmware.com --- +# Get in touch + +
+
+ {% for link in page.links %} +
+
+ {{ link.title}} +
+

{{link.title}}

+ {% for item in link.items %} + + {% endfor %} +
+ {% endfor %} +
+
The VMware Event Broker Appliance team welcomes contributions from the community and this page presents the guidelines for contributing to VMware Event Broker Appliance. -# Guidelines +## Community Calls + +Public VEBA community meetings are held every **last Tuesday** in the month at +**8AM Pacific Time (US)**. -Following the guidelines helps to make the contribution process easy, collaborative, and productive. +- **Zoom:** {:target="_blank"} + - **Note:** The meeting is **password protected** to mitigate abuse. Please join the VEBA Slack [channel](https://vmwarecode.slack.com/archives/CQLT9B5AA){:target="_blank"} to receive the Zoom password or contact us in case of issues. +- **Notes**: {:target="_blank"} +- **Recording Playlist**: [VEBA Community Calls](https://youtube.com/playlist?list=PLnopqt07fPn3hspeQvarWuFH3IiwkMpDJ){:target="_blank"} -Before you start working with the VMware Event Broker Appliance, please read our [Developer Certificate of Origin](https://cla.vmware.com/dco){:target="_blank"}. All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. +## Contributing -## Submitting Bug Reports and Feature Requests +Following the guidelines helps to make the contribution process easy, +collaborative, and productive. -Please submit bug reports and feature requests by using our GitHub [Issues](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues){:target="_blank"} page. +Before you start working with the VMware Event Broker Appliance, please read our +[Developer Certificate of Origin](https://cla.vmware.com/dco){:target="_blank"}. +All contributions to this repository must be signed as described on that page. +Your signature certifies that you wrote the patch or have the right to pass it +on as an open-source patch. -Before you submit a bug report about the code in the repository, please check the Issues page to see whether someone has already reported the problem. In the bug report, be as specific as possible about the error and the conditions under which it occurred. On what version and build did it occur? What are the steps to reproduce the bug? +This is a rough outline of what a contributor's workflow looks like: + +- Create an issue describing the feature/fix. +- Create a topic branch from where you want to base your work. +- Make commits of logical units. +- Make sure your commit messages are in the proper format (see below). +- Push your changes to a topic branch in your fork of the repository. +- Submit a pull request to `vmware-samples/vcenter-event-broker-appliance`. + +See [below](#format-of-the-commit-message) for details on commit best practices +and **supported prefixes**, e.g. `fix: `. + +> **Note:** If you are new to Git(hub) check out [Git rebase, squash...oh +> my!](https://www.mgasch.com/2021/05/git-basics/) for more details on how to +> successfully contribute to an open source project. + +### Submitting Bug Reports and Feature Requests + +Please submit bug reports and feature requests by using our GitHub +[Issues](https://github.com/vmware-samples/vcenter-event-broker-appliance/issues){:target="_blank"} +page. + +Before you submit a bug report about the code in the repository, please check +the Issues page to see whether someone has already reported the problem. In the +bug report, be as specific as possible about the error and the conditions under +which it occurred. On what version and build did it occur? What are the steps to +reproduce the bug? Feature requests should fall within the scope of the project. -## Pull Requests +### Pull Requests + +Before submitting a pull request, please make sure that your change satisfies +the following requirements: -Before submitting a pull request, please make sure that your change satisfies the following requirements: -- 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"} +- 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 (see + below) -### Contributions to the Appliance - - See the Build Appliance document [here](/kb/contribute-appliance) - - See the Build Event Router document [here](/kb/contribute-eventrouter) - - Requestor must verify that the VMware Event Broker Appliance can be built and deployed. + +### Format of the Commit Message + +We follow the conventions described in [How to Write a Git Commit +Message](http://chris.beams.io/posts/git-commit/). + +Be sure to include any related GitHub issue references in the commit message, +e.g. `Closes: #`. + +The `CHANGELOG.md` and release page use **commit message prefixes** for grouping +and highlighting. A commit message that starts with `[prefix:] ` will place this +commit under the respective section in the `CHANGELOG`. + +The following example creates a commit referencing the `issue: 1234` and puts +the commit message in the `Feature` `CHANGELOG` section: + +```bash +git commit -s -m "feat: Add Slack Example" -m "Closes: #1234" +``` + +Currently the following prefixes are used: + +- `fix:` - Use for bug fixes +- `feat:` - A new feature +- `chore:` - Use for repository related activities +- `docs:` - Use for changes to the documentation + +If your contribution falls into multiple categories, e.g. `feat` and `fix` it is +recommended to break up your commits using distinct prefixes. + +### Contributions to the Appliance + +- See the Build Appliance document [here](/kb/contribute-appliance) +- See the Build Event Router document [here](/kb/contribute-eventrouter) +- Requestor must verify that the VMware Event Broker Appliance can be built and deployed. ### Contributions to the Functions - - See the Build Functions document [here](/kb/contribute-functions) - - PR should contain information on how the function was tested (environment, version etc) - - PR should contain a titled readme and the title is listed in the [Functions](/examples) page + +- See the Build Functions document [here](/kb/contribute-functions) +- PR should contain information on how the function was tested (environment, version etc) +- PR should contain a titled readme and the title is listed in the [Functions](/examples) page ### Contributions to the Website - - See the Build Website document [here](/kb/contribute-functions) - - Requestor must verify that the website change was built and tested locally + +- See the Build Website document [here](/kb/contribute-functions) +- Requestor must verify that the website change was built and tested locally Get started quickly with your contributions with our [getting started](/kb/contribute-start) guide @@ -69,23 +169,3 @@ Get started quickly with your contributions with our [getting started](/kb/contr
{% include contributors.html %}
- -## Get in touch -
-
- {% for link in page.links %} -
-
- {{ link.title}} -
-

{{link.title}}

- {% for item in link.items %} - - {% endfor %} -
- {% endfor %} -
-
\ No newline at end of file diff --git a/docs/site/evolutions.md b/docs/site/evolutions.md index 8d40a80e..856e7474 100644 --- a/docs/site/evolutions.md +++ b/docs/site/evolutions.md @@ -6,6 +6,18 @@ permalink: /evolution limit: 3 entry: +- title: VEBA [v0.7.3](https://github.com/vmware-samples/vcenter-event-broker-appliance/releases/tag/v0.7.3) + type: feature + date: Jul 2022 + id: vzeroseventhree + detail: + subtitle: Features + text: + - New VMware Harbor, Zapier, NSX Tag Sync & Unapproved Portgroup Usage Functions + - Ported Datastore Usage Email, vSphere HA Restart & Host Maint. Alarm functions to Knative + - Enhanced pattern matching for EventBridge processor + - Various Documentation Updates & Bug Fixes + - title: VEBA [v0.7.2](https://williamlam.com/2022/03/vmware-event-broker-appliance-veba-v0-7-2.html) type: feature date: Mar 2022 diff --git a/docs/site/examples.md b/docs/site/examples.md index 83ab326f..1a188e82 100644 --- a/docs/site/examples.md +++ b/docs/site/examples.md @@ -11,6 +11,15 @@ images: go: /assets/img/languages/go.png powershell: /assets/img/languages/powershell.png examples: + - title: Datastore Usage Alarm Email Notification + usecases: + - item: notification + id: kn-pcli-datastore-usage-email + description: Sends email notifications to a specified email address for datastore usage on disk alarms. Optional configuration for a per-datastore email, enabling different recipients for different datastores. + links: + - language: powercli + url: "/tree/master/examples/knative/powercli/kn-pcli-datastore-usage-email" + - title: Echo Cloud Event for Knative usecases: - item: other @@ -33,6 +42,15 @@ examples: - language: powershell url: "/tree/master/examples/knative/powershell/kn-ps-email" + - title: Host Maintenance Alarm Actions + usecases: + - item: notification + id: kn-pcli-hostmaint-alarms + description: Automatically disables alarm actions when a host enters maintenance mode, enables alarm action when a host exits maintenance mode. + links: + - language: powercli + url: "/tree/master/examples/knative/powercli/kn-pcli-hostmaint-alarms" + - title: Slack Notification usecases: - item: integration @@ -86,6 +104,15 @@ examples: - language: powershell url: "/tree/master/examples/knative/powershell/kn-ps-ngw-slack" + - title: VMware HA Notification + usecases: + - item: notification + id: kn-pcli-ha-restarted-vms + description: Function to send an e-mail notification after an HA event. The email includes a list of VMs and timestamps showing when each VM was restarted by HA. + links: + - language: powercli + url: "/tree/master/examples/knative/powercli/kn-pcli-ha-restarted-vms" + - title: VMware Horizon Notification usecases: - item: integration @@ -112,7 +139,16 @@ examples: description: Function to enforce the `Notify Switches` value on a distributed virtual switch portgroup. Any changes to the `Notify Switches` value will be intercepted by the function and reset to the desired value. links: - language: powercli - url: "/tree/master/knative/powercli/kn-pcli-vds-pg-config" + url: "/tree/master/knative/powercli/kn-pcli-vds-pg-config" + + - title: Portgroup Compliance Check + usecases: + - item: notification + id: kn-pcli-pg-check + description: Creates a Slack notification when VM network portgroups are out of compliance. Tag VMs as `PCI`, tag portgroups as `PCI`, receive a Slack notification any time a tagged VM is moved off of a `PCI` portgroup. + links: + - language: powercli + url: "/tree/master/knative/powercli/kn-pcli-pg-check" - title: Enhancing vSphere Alarm Actions usecases: @@ -141,6 +177,9 @@ examples: links: - language: powercli url: "/tree/master/examples/knative/powercli/kn-pcli-nsx-tag-sync" + - language: go + url: "/tree/master/examples/knative/go/kn-go-nsx-tag-sync" + - title: vSphere Tagging usecases: @@ -157,7 +196,7 @@ examples: usecases: - item: integration id: kn-ps-webhook-function - description: Function to ingest a non-CloudEvent using a custom incoming webhook + description: Function to ingest a non-CloudEvent using a custom incoming webhook. links: - language: powershell url: "/tree/master/examples/knative/powershell/kn-ps-webhook" @@ -166,7 +205,7 @@ examples: usecases: - item: notification id: kn-ps-vrni-databus-function - description: Function that accepts an incoming webhook from the vRealize Network Insight Databus, constructs a CloudEvent and sends it to the VMware Event Router + description: Function that accepts an incoming webhook from the vRealize Network Insight Databus, constructs a CloudEvent and sends it to the VMware Event Router. links: - language: powershell url: "/tree/master/examples/knative/powershell/kn-ps-vrni-databus" @@ -175,7 +214,7 @@ examples: usecases: - item: notification id: kn-ps-vsphere-inv-slack-function - description: Function to send a Slack notification when a specific vSphere inventory resource has been deleted + description: Function to send a Slack notification when a specific vSphere inventory resource has been deleted. links: - language: powershell url: "/tree/master/examples/knative/powershell/kn-ps-vsphere-inv-slack" @@ -185,7 +224,7 @@ examples: - item: automation - item: remediation id: kn-pcli-snapshot-cron-function - description: Function to manage VM snapshots on a scheduled job (cron) + description: Function to manage VM snapshots on a scheduled job (cron). links: - language: powercli url: "/tree/master/examples/knative/powercli/kn-pcli-snapshot-cron" @@ -194,20 +233,47 @@ examples: usecases: - item: integration id: kn-go-preemption-function - description: Function for triggering vSphere virtual machine preemption (power off) using a workflow engine and the vsphere-preemption prototype + description: Function for triggering vSphere virtual machine preemption (power off) using a workflow engine and the vsphere-preemption prototype. links: - language: go url: "/tree/master/examples/knative/go/kn-go-preemption" - title: vRealize Orchestrator - usecases: + usecases: - item: integration - item: remediation id: kn-py-vro-function description: Trigger a vRealize Orchestrator workflow, passing all CloudEvent data as native vRO datatypes, using the vRO REST API. - links: + links: - language: python url: "/tree/master/examples/knative/python/kn-py-vro" + + - title: Zapier workflow integration + usecases: + - item: integration + id: kn-ps-zapier-function + description: Trigger a Zapier workflow, passing select CloudEvent data to a Zapier webhook. + links: + - language: powershell + url: "/tree/master/examples/knative/powershell/kn-ps-zapier" + + - title: Transform Harbor webhook event notifications to CloudEvents + usecases: + - item: integration + id: kn-go-harbor-webhook-function + description: Function for receiving Project Harbor webhook notifications (events). + links: + - language: go + url: "/tree/master/examples/knative/go/kn-go-harbor-webhook" + + - title: Creates a Slack notification when a Harbor webhook notification event got triggered + usecases: + - item: notification + id: kn-ps-harbor-slack-function + description: Function to send a Slack notification triggered by a Harbor webhook notification. + links: + - language: powershell + url: "/tree/master/examples/knative/powershell/kn-ps-harbor-slack" --- A complete and updated list of ready to use functions curated by the VMware Event Broker community is listed below. diff --git a/docs/site/faq.md b/docs/site/faq.md index 4478536b..5e37eb89 100644 --- a/docs/site/faq.md +++ b/docs/site/faq.md @@ -17,7 +17,7 @@ faqs: A: Yes! Follow the steps provided [here](/kb/advanced-certificates). - Q: What happens if vCenter Server and VMware Event Broker connectivity is lost? A: > - VMware [Event Router](https://vmweventbroker.io/kb/contribute-eventrouter) streams vCenter events as they get generated and being stateless, does not persist any event information. To provide a certain level of reliability, the following Event Delivery Guarantees exists:
+ VMware [Event Router](https://vmweventbroker.io/kb/event-router) streams vCenter events as they get generated and being stateless, does not persist any event information. To provide a certain level of reliability, the following Event Delivery Guarantees exists:
- At-least-once event delivery semantics for the vCenter event provider by checkpointing the event stream into a file. In case of disconnection, the Event Router will replay all vCenter events of the last 10 minutes (10m reiteration) after a successful reconnection.
- At-least-once event delivery semantics are not guaranteed if the event router crashes within seconds right after startup and having received *n* events but before creating the first valid checkpoint (current checkpoint interval is 5s).
- Q: How long does it take for the functions to be invoked upon an event being generated? @@ -25,7 +25,7 @@ faqs: - Q: Can I setup the VMware Event Broker Appliance components on Kubernetes? A: Yes! Follow the steps provided [here](/kb/event-router#deployment). - Q: Can I use a private registry like e.g. [Harbor](https://goharbor.io/) to have a source of truth for my functions (images)? - A: Yes! Follow the steps provided [here](https://rguske.github.io/post/using-harbor-with-the-vcenter-event-broker-appliance/). + A: Yes! Follow the steps provided [here](https://vmweventbroker.io/kb/private-registry). - Q: How can I monitor the Appliance, the Kubernetes components as well as the functions (pods) in terms of utilization, performance and state? A: vRealize Operations Manager provides these capabilities as described [here](https://rguske.github.io/post/monitoring-the-vmware-event-broker-appliance-with-vrealize-operations-manager/). - title: Common Questions - Functions diff --git a/examples/knative/go/kn-go-harbor-webhook/.ko.yaml b/examples/knative/go/kn-go-harbor-webhook/.ko.yaml new file mode 100644 index 00000000..d6710a53 --- /dev/null +++ b/examples/knative/go/kn-go-harbor-webhook/.ko.yaml @@ -0,0 +1,14 @@ +builds: + - id: function + # dir: . + # main: . + env: + - GOPRIVATE=*.vmware.com + flags: + - -tags + - netgo + ldflags: + - -s -w + - -extldflags "-static" + - -X main.buildCommit={{.Env.KO_COMMIT}} + - -X main.buildTag={{.Env.KO_TAG}} diff --git a/examples/knative/go/kn-go-harbor-webhook/README.md b/examples/knative/go/kn-go-harbor-webhook/README.md new file mode 100644 index 00000000..7179b9d6 --- /dev/null +++ b/examples/knative/go/kn-go-harbor-webhook/README.md @@ -0,0 +1,217 @@ +# kn-go-harbor-webhook + +Example Knative Go function for receiving [Project Harbor +webhook](https://goharbor.io/docs/latest/working-with-projects/project-configuration/configure-webhooks/) +notifications (events). + +⚠️ This guide assumes that you have stood up a working Knative environment using +the vCenter Event Broker Appliance (VEBA). Since the function needs to be +exposed to the outside (so Harbor can send webhook notifications to it), **your +DNS server needs to be set up with wildcard DNS support for the VEBA host**. + +# How the function works + +The function starts an HTTP server, transforms Harbor webhook event +notifications to [CloudEvents](https://cloudevents.io/) and sends them to the +configured `K_SINK` (injected via a Knative +[`SinkBinding`](https://knative.dev/docs/eventing/custom-event-source/sinkbinding/)). +By default, `K_SINK` is set as the VEBA `default` broker in the +`vmware-functions` namespace. + +The function can be set up with +[`basic_auth`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) +for the HTTP endpoint. This is highly recommended (and the default in this guide +using a Kubernetes secret). + +The webhook endpoint in the function, i.e. the HTTP `POST` target, by default is +`/webhook` (configurable.) + +See the [deployment](#step-3---deploy) section for configuration options and +details. + +The event transformation is done as follows: + +| CloudEvent Field | Harbor Event Field | Comment | Example | +|------------------|--------------------|-------------------------------------------------------------------------------------------------------------------------|----------------------------------------| +| `ID` | n/a | An `ID` is autogenerated using UUIDv4 | `35f42638-4627-4a8e-8862-360cae64dc44` | +| `Source` | n/a | The Source is generated using a fixed format (`/%s`) using the Knative `service` Name | `/kn-go-harbor-webhook` | +| `Type` | `type` | Harbor event `type` field is lower-cased and injected into a fixed CloudEvent `Type` format (`com.vmware.harbor.%s.v0`) | `com.vmware.harbor.pull_artifact.v0` | +| `Subject` | `operator` | This field might be empty | `admin` | +| `Time` | `occur_at` | Converted Harbor time to RFC3339 (UTC) | `2022-06-22T08:49:48Z` | +| `Data` | n/a | JSON-encoded full Harbor event | | + +A full example of a structured CloudEvent (JSON): + +```json +{ + "specversion": "1.0", + "id": "", + "source": "/kn-go-harbor-webhook", + "type": "com.vmware.harbor.pull_artifact.v0", + "subject": "admin", + "datacontenttype": "application/json", + "time": "2022-06-22T08:49:48Z", + "data": { + "type": "PULL_ARTIFACT", + "occur_at": 1655887788, + "operator": "admin", + "event_data": { + "resources": [{ + "digest": "sha256:3b465cbcadf7d437fc70c3b6aa2c93603a7eef0a3f5f1e861d91f303e4aabdee", + "tag": "sha256:3b465cbcadf7d437fc70c3b6aa2c93603a7eef0a3f5f1e861d91f303e4aabdee", + "resource_url": "harbor-app.jarvis.tanzu/veba-test/csi-test@sha256:3b465cbcadf7d437fc70c3b6aa2c93603a7eef0a3f5f1e861d91f303e4aabdee" + }], + "repository": { + "date_created": 1655887764, + "name": "csi-test", + "namespace": "veba-test", + "repo_full_name": "veba-test/csi-test", + "repo_type": "public" + } + } + } +} +``` + +# Step 1 - Build + +⚠️ This step is only required if you made code changes to any of the \*.go +files. To directly deploy the function jump to [Step 3](#step-3---deploy). + +Requirement: If you make changes to the Go code, the +[ko](https://github.com/google/ko) tool is required to create the artifacts. + +Set the destination to push the function container image with an environment +variable. + +```bash +export KO_DOCKER_REPO=docker.io/my-user +export KO_COMMIT=$(git rev-parse --short=8 HEAD) +export KO_TAG=1.0 +``` + +The following command will build and push the image to the specified +`KO_DOCKER_REPO` repository. + +```bash +# for docker.io +ko publish --bare -t $KO_TAG . + +# for GCR +ko publish -B -t $KO_TAG . +``` + +⚠️ Using the above example, the resulting image would be +`docker.io/myuser/kn-go-harbor-webhook:1.0`. + +# Step 2 - Test + +Run unit tests using the following command: + +```bash +go test -v -race -count 1 ./... +``` + +# Step 3 - Deploy + +⚠️ The following steps assume a working Knative environment using the `default` + Rabbit `broker`. The Knative `service` and `sinkbinding` will be installed in + the `vmware-functions` Kubernetes namespace, assuming that the `broker` is also + available there. + +## Create Basic Auth Credentials Secrets + +Create a secret holding the username and password enforcing basic authentication +on the HTTP endpoint of the function which receives Harbor webhook +notifications. + +```bash +kubectl create secret generic webhook-auth \ +--type=kubernetes.io/basic-auth \ +--from-literal=username='webhookuser' \ +--from-literal=password='replaceme' \ +--namespace vmware-functions + +# update label for secret to show up in VEBA UI +kubectl -n vmware-functions label secret webhook-auth app=veba-ui +``` + +## Update Environment Settings + +The `function.yaml` comes with sane defaults, incl. basic auth for the HTTP +endpoint. Users may update environment specific settings under `env:` in the +`function.yaml` file. + +Please see the table below for a description of the available (and **required**) +settings. + +| Configuration | Description | Example Values | Required | +|-----------------------|------------------------------------------------------------------------------|---------------------------|----------| +| `ADDRESS` | HTTP listen (bind) address of the function | `"0.0.0.0"` (default) | **Yes** | +| `WEBHOOK_PATH` | Endpoint where the function HTTP server accepts Harbor webhook notifications | `"/webhook"` (default) | **Yes** | +| `WEBHOOK_SECRET_PATH` | The path where the basic auth credentials secret will be mounted | `"/var/bindings/webhook"` | No | +| `DEBUG` | Enable debug logging | `"true"` | No | + +## Deploy the Function + +Create the `SinkBinding` which will automatically inject the VEBA `default` +broker into the function. + +```bash +kubectl create -f sinkbinding.yaml -n vmware-functions +``` + +⚠️ If you made changes to the Go code/container image in [Step +1](#step-1---build) edit the `function.yaml` file with the custom name of the +container image used to build and push. + +Deploy the function to the VMware Event Broker Appliance (VEBA): + +```bash +kubectl create -f function.yaml -n vmware-functions +``` + +For testing purposes, the [Knative manifest](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 the +VMware Event Broker Appliance UI defaults to scale to 0, which means the pods +will only run when it is triggered by a vCenter Event. + +```yaml +annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" +``` + +## Configure the Harbor Webhook + +Configure the webhook notifications in the Harbor UI. For example, the following +screenshot shows how to send all Harbor events to the `kn-go-harbor-webhook` +function with basic auth enabled. + +Change the "Endpoint URL" and "Auth Header" fields accordingly: + +The "Endpoint URL" is +`https://kn-go-harbor-webhook.vmware-functions./webhook` if you used +the default values provided in the `function.yaml`. + +The value in "Auth Header" needs to start with `Basic` followed by a whitespace +and the base64-encoded value of the `:` string combination +defined in the `webhook-auth` secret created earlier. You can use [this online +tool](https://www.debugbear.com/basic-auth-header-generator) to create the +required value. + +![Harbor Webhook Configuration](static/screenshot-1.png) + +# Step 4 - Undeploy + +```bash +# undeploy function +kubectl delete -f function.yaml -n vmware-functions + +# undeploy sinkbinding +kubectl delete -f sinkbinding.yaml -n vmware-functions + +# delete secret +kubectl delete secret webhook-auth -n vmware-functions +``` diff --git a/examples/knative/go/kn-go-harbor-webhook/function.yaml b/examples/knative/go/kn-go-harbor-webhook/function.yaml new file mode 100644 index 00000000..2f932c40 --- /dev/null +++ b/examples/knative/go/kn-go-harbor-webhook/function.yaml @@ -0,0 +1,33 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-go-harbor-webhook + labels: + app: veba-ui +spec: + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" + spec: + containers: + - image: us.gcr.io/daisy-284300/veba/kn-go-harbor-webhook:1.0 + imagePullPolicy: IfNotPresent + env: + - name: ADDRESS + value: "0.0.0.0" + - name: WEBHOOK_PATH + value: "/webhook" # default + - name: DEBUG + value: "true" + - name: WEBHOOK_SECRET_PATH # remove this env to disable basic auth + value: "/var/bindings/webhook" + volumeMounts: # remove this when not using basic auth + - name: webhook-auth + mountPath: "/var/bindings/webhook" + readOnly: true + volumes: # remove this when not using basic auth + - name: webhook-auth + secret: + secretName: webhook-auth diff --git a/examples/knative/go/kn-go-harbor-webhook/go.mod b/examples/knative/go/kn-go-harbor-webhook/go.mod new file mode 100644 index 00000000..ee96c0c4 --- /dev/null +++ b/examples/knative/go/kn-go-harbor-webhook/go.mod @@ -0,0 +1,28 @@ +module github.com/vmware-samples/vcenter-event-broker-appliance/examples/knative/go/kn-go-harbor-webhook + +go 1.18 + +require ( + github.com/cloudevents/sdk-go/v2 v2.10.1 + github.com/embano1/vsphere v0.2.2 + github.com/goharbor/harbor/src v0.0.0-20220622063440-0cf036e73a36 + github.com/google/uuid v1.3.0 + github.com/kelseyhightower/envconfig v1.4.0 + go.uber.org/zap v1.21.0 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + gotest.tools/v3 v3.0.3 +) + +require ( + github.com/beego/beego v1.12.9 // indirect + github.com/benbjohnson/clock v1.1.0 // indirect + github.com/google/go-cmp v0.5.6 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/json-iterator/go v1.1.11 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.8.0 // indirect +) diff --git a/examples/knative/go/kn-go-harbor-webhook/go.sum b/examples/knative/go/kn-go-harbor-webhook/go.sum new file mode 100644 index 00000000..39bb1c51 --- /dev/null +++ b/examples/knative/go/kn-go-harbor-webhook/go.sum @@ -0,0 +1,234 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= +github.com/beego/beego v1.12.9 h1:knN+7lL7BSVFm6McUVu58QVrh2UUPn0C9ioq83W5seo= +github.com/beego/beego v1.12.9/go.mod h1:QURFL1HldOcCZAxnc1cZ7wrplsYR5dKPHFjmk6WkLAs= +github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= +github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= +github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudevents/sdk-go/v2 v2.10.1 h1:qNFovJ18fWOd8Q9ydWJPk1oiFudXyv1GxJIP7MwPjuM= +github.com/cloudevents/sdk-go/v2 v2.10.1/go.mod h1:GpCBmUj7DIRiDhVvsK5d6WCbgTWs8DxAWTRtAwQmIXs= +github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= +github.com/couchbase/go-couchbase v0.0.0-20201216133707-c04035124b17/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A= +github.com/couchbase/gomemcached v0.1.2-0.20201224031647-c432ccf49f32/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo= +github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= +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/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= +github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/embano1/vsphere v0.2.2 h1:QIsdBSL5BbwmkKqj8/MqAkGXvn8EJKo6eHBneUR3H/g= +github.com/embano1/vsphere v0.2.2/go.mod h1:R9spY3upDbJete+1Jp9r4xZ+AuI4GkS/KAHl9IwDQuw= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/goharbor/harbor/src v0.0.0-20220622063440-0cf036e73a36 h1:ydLuq3CRqlk8VGgwGNYejyyn4eikCd4vMncsZuOknfg= +github.com/goharbor/harbor/src v0.0.0-20220622063440-0cf036e73a36/go.mod h1:P75O0oYa4pNwMqprSvbl9TUyTqEfI6cn4jLnTPx5UFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ= +github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo= +github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= +github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= +github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s= +github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= +github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/wendal/errors v0.0.0-20181209125328-7f31f4b264ec/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= diff --git a/examples/knative/go/kn-go-harbor-webhook/main.go b/examples/knative/go/kn-go-harbor-webhook/main.go new file mode 100644 index 00000000..892a0fa5 --- /dev/null +++ b/examples/knative/go/kn-go-harbor-webhook/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "context" + "os" + "os/signal" + "syscall" + + "github.com/embano1/vsphere/logger" + "github.com/kelseyhightower/envconfig" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var ( + buildCommit = "unknown" + buildTag = "unknown" +) + +type config struct { + // http settings + Address string `envconfig:"ADDRESS" default:"0.0.0.0" required:"true"` + Path string `envconfig:"WEBHOOK_PATH" default:"/webhook" required:"true"` + + // knative injected + Port int `envconfig:"PORT" default:"8080" required:"true"` + Service string `envconfig:"K_SERVICE" required:"true"` + Sink string `envconfig:"K_SINK" required:"true"` + + Debug bool `envconfig:"DEBUG" default:"false"` + + SecretPath string `envconfig:"WEBHOOK_SECRET_PATH"` +} + +func main() { + var cfg config + if err := envconfig.Process("", &cfg); err != nil { + panic("process environment variables: " + err.Error()) + } + + log, err := getLogger(cfg.Debug) + if err != nil { + panic("create logger: " + err.Error()) + } + log = log.Named("harbor-webhook") + + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer cancel() + ctx = logger.Set(ctx, log) + + if err = run(ctx, cfg); err != nil { + log.Panic("could not run server", zap.Error(err)) + } + + log.Info("graceful shutdown complete") +} + +func getLogger(debug bool) (*zap.Logger, error) { + fields := []zap.Field{ + zap.String("commit", buildCommit), + zap.String("tag", buildTag), + } + + var cfg zap.Config + if debug { + cfg = zap.NewDevelopmentConfig() + } else { + cfg = zap.NewProductionConfig() + cfg.EncoderConfig.EncodeTime = zapcore.RFC3339NanoTimeEncoder + } + + log, err := cfg.Build(zap.Fields(fields...)) + if err != nil { + return nil, err + } + + return log, nil +} diff --git a/examples/knative/go/kn-go-harbor-webhook/server.go b/examples/knative/go/kn-go-harbor-webhook/server.go new file mode 100644 index 00000000..a1a31526 --- /dev/null +++ b/examples/knative/go/kn-go-harbor-webhook/server.go @@ -0,0 +1,211 @@ +package main + +import ( + "context" + "crypto/sha256" + "crypto/subtle" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + ce "github.com/cloudevents/sdk-go/v2" + cectx "github.com/cloudevents/sdk-go/v2/context" + "github.com/embano1/vsphere/logger" + "github.com/goharbor/harbor/src/pkg/notifier/model" + "github.com/google/uuid" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" +) + +const ( + httpTimeout = time.Second * 5 + sourceFormat = "/%s" // /K_SERVICE + eventTypeFormat = "com.vmware.harbor.%s.v0" // com.vmware.harbor.pull_artifact.v0 + + // secrets + userFileKey = "username" + passwordFileKey = "password" +) + +var ( + retries = 3 + retryDelay = time.Millisecond * 200 +) + +func run(ctx context.Context, cfg config) error { + log := logger.Get(ctx) + + client, err := ce.NewClientHTTP(ce.WithTarget(cfg.Sink)) + if err != nil { + return fmt.Errorf("create cloudevent client: %w", err) + } + + var auth bool + if path := os.Getenv("WEBHOOK_SECRET_PATH"); path != "" { + auth = true + } + + handler := eventHandler(ctx, client) + if auth { + user, err := readKey(userFileKey, cfg.SecretPath) + if err != nil { + return fmt.Errorf("read secret key %q: %w", userFileKey, err) + } + + pass, err := readKey(passwordFileKey, cfg.SecretPath) + if err != nil { + return fmt.Errorf("read secret key %q: %w", passwordFileKey, err) + } + + handler = withBasicAuth(ctx, eventHandler(ctx, client), user, pass) + } + + mux := http.NewServeMux() + mux.Handle(cfg.Path, handler) + + address := fmt.Sprintf("%s:%d", cfg.Address, cfg.Port) + srv := http.Server{ + Addr: address, + Handler: mux, + ReadTimeout: httpTimeout, + WriteTimeout: httpTimeout, + } + + eg, egCtx := errgroup.WithContext(ctx) + + eg.Go(func() error { + <-egCtx.Done() + log.Info("shutting down http server") + shutdownCtx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + if err := srv.Shutdown(shutdownCtx); err != nil { + log.Warn("could not gracefully shutdown http server") + } + return nil + }) + + log.Info("starting http server", + zap.String("address", address), + zap.String("path", cfg.Path), + zap.String("sink", cfg.Sink), + zap.Bool("basic_auth", auth), + ) + + eg.Go(func() error { + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + return fmt.Errorf("run http server: %w", err) + } + return nil + }) + + return eg.Wait() +} + +// harbor webhook event handler +func eventHandler(ctx context.Context, client ce.Client) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + + // TODO (@mgasch): support inbound rate limiting + + log := logger.Get(ctx) + b, err := io.ReadAll(r.Body) + if err != nil { + log.Error("read body", zap.Error(err)) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + + var event model.Payload + if err = json.Unmarshal(b, &event); err != nil { + log.Error("could not decode harbor notification event", zap.Error(err)) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + + id := uuid.New().String() + log = log.With(zap.String("eventID", id)) + + log.Debug("received request", zap.String("request", string(b))) + + e := ce.NewEvent() + e.SetID(id) + e.SetSource(fmt.Sprintf(sourceFormat, os.Getenv("K_SERVICE"))) + e.SetSubject(event.Operator) // might be empty + + // sanity check + if event.Type == "" { + log.Error("harbor event type must not be empty", zap.String("type", event.Type)) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + + t := strings.ToLower(event.Type) + e.SetType(fmt.Sprintf(eventTypeFormat, t)) + + ts := time.Unix(event.OccurAt, 0) + e.SetTime(ts) + + if err = e.SetData(ce.ApplicationJSON, event); err != nil { + log.Error("could not set cloudevent data", zap.Error(err)) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + ctx = cectx.WithRetriesExponentialBackoff(ctx, retryDelay, retries) + if err = client.Send(ctx, e); ce.IsUndelivered(err) || ce.IsNACK(err) { + log.Error("could not send cloudevent", zap.Error(err), zap.String("event", e.String())) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + log.Debug("successfully sent cloudevent", zap.Any("event", e)) + }) +} + +// withBasicAuth enforces basic auth as a middleware for the given username and +// password +func withBasicAuth(ctx context.Context, next http.Handler, u, p string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + username, password, ok := r.BasicAuth() + if ok { + // reduce brute-force guessing attacks with constant-time comparisons + usernameHash := sha256.Sum256([]byte(username)) + passwordHash := sha256.Sum256([]byte(password)) + expectedUsernameHash := sha256.Sum256([]byte(u)) + expectedPasswordHash := sha256.Sum256([]byte(p)) + + usernameMatch := subtle.ConstantTimeCompare(usernameHash[:], expectedUsernameHash[:]) == 1 + passwordMatch := subtle.ConstantTimeCompare(passwordHash[:], expectedPasswordHash[:]) == 1 + + if usernameMatch && passwordMatch { + next.ServeHTTP(w, r) + return + } + } + + logger.Get(ctx).Debug("rejecting incoming request: user not authenticated") + w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`) + http.Error(w, "Unauthorized", http.StatusUnauthorized) + }) +} + +// readKey reads the file from the secret path +func readKey(key string, path string) (string, error) { + data, err := ioutil.ReadFile(filepath.Join(path, key)) + if err != nil { + return "", err + } + return string(data), nil +} diff --git a/examples/knative/go/kn-go-harbor-webhook/server_test.go b/examples/knative/go/kn-go-harbor-webhook/server_test.go new file mode 100644 index 00000000..1365faf7 --- /dev/null +++ b/examples/knative/go/kn-go-harbor-webhook/server_test.go @@ -0,0 +1,201 @@ +package main + +import ( + "context" + "encoding/json" + "io/fs" + "io/ioutil" + nethttp "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "sync/atomic" + "testing" + "time" + + ce "github.com/cloudevents/sdk-go/v2" + "github.com/cloudevents/sdk-go/v2/protocol" + "github.com/cloudevents/sdk-go/v2/protocol/http" + "github.com/embano1/vsphere/logger" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" + "gotest.tools/v3/assert" +) + +type mockClient struct { + ce.Client + + t *testing.T + requests int32 + code int +} + +func (m *mockClient) Send(_ context.Context, event ce.Event) protocol.Result { + assert.NilError(m.t, event.Validate()) + + var inputEvent map[string]interface{} + err := json.Unmarshal([]byte(harborEvent), &inputEvent) + assert.NilError(m.t, err) + + assert.Equal(m.t, inputEvent["occur_at"], float64(event.Time().Unix())) + + eventType := strings.ToLower(inputEvent["type"].(string)) + assert.Assert(m.t, strings.Contains(event.Type(), eventType)) + + // send mock response + atomic.AddInt32(&m.requests, 1) + if m.code != 200 { + return http.NewResult(m.code, "") + } + return nil // ACK +} + +func Test_run(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + ctx = logger.Set(ctx, zaptest.NewLogger(t)) + dir, err := ioutil.TempDir("", "secret") + assert.NilError(t, err) + + err = ioutil.WriteFile(filepath.Join(dir, userFileKey), []byte("user"), fs.ModePerm) + assert.NilError(t, err) + + err = ioutil.WriteFile(filepath.Join(dir, passwordFileKey), []byte("pass"), fs.ModePerm) + assert.NilError(t, err) + + t.Cleanup(func() { + if err := os.RemoveAll(dir); err != nil { + logger.Get(ctx).Error("could not clean up temp directory", zap.Error(err)) + } + }) + + t.Setenv("WEBHOOK_SECRET_PATH", dir) + + cfg := config{ + Address: "127.0.0.1", + Path: "/webhook", + Port: 8080, + Service: "testservice", + Sink: "somesink", + Debug: true, + SecretPath: dir, + } + + err = run(ctx, cfg) + assert.NilError(t, err) +} + +func Test_eventhandler(t *testing.T) { + type basicAuth struct { + username string + password string + } + + basicAuthCredentials := basicAuth{ + username: "user", + password: "pass", + } + + tests := []struct { + name string + auth *basicAuth + code int + method string + wantCount int32 + wantCode int + }{ + { + name: "successfully sends event (no auth)", + code: 200, + method: nethttp.MethodPost, + wantCount: 1, + wantCode: 200, + }, + { + name: "fails with 405 if method is not POST", + code: 0, + method: nethttp.MethodGet, + wantCount: 0, + wantCode: 405, + }, + { + name: "fails with 401 not authorized", + auth: &basicAuth{ + username: "userrrrr", + password: "passssssss", + }, + code: 0, + method: nethttp.MethodPost, + wantCount: 0, + wantCode: 401, + }, + { + name: "successfully sends event (basic auth)", + auth: &basicAuth{ + username: "user", + password: "pass", + }, + code: 200, + method: nethttp.MethodPost, + wantCount: 1, + wantCode: 200, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := logger.Set(context.Background(), zaptest.NewLogger(t)) + t.Setenv("K_SERVICE", "testservice") + + // overwrite retry config + retries = 3 + retryDelay = time.Millisecond + + mc := mockClient{ + t: t, + code: tc.code, + } + + testHandler := eventHandler(ctx, &mc) + req := httptest.NewRequest(tc.method, "/webhook", strings.NewReader(harborEvent)) + + if tc.auth != nil { + t.Setenv("WEBHOOK_SECRET_PATH", "/somesecret") + testHandler = withBasicAuth(ctx, eventHandler(ctx, &mc), basicAuthCredentials.username, basicAuthCredentials.password) + req.SetBasicAuth(tc.auth.username, tc.auth.password) + } + + rec := httptest.NewRecorder() + testHandler.ServeHTTP(rec, req) + + count := atomic.LoadInt32(&mc.requests) + assert.Equal(t, tc.wantCount, count) + assert.Equal(t, tc.wantCode, rec.Code) + }) + } +} + +const harborEvent = ` +{ + "type": "PULL_ARTIFACT", + "occur_at": 1655887788, + "operator": "admin", + "event_data": { + "resources": [ + { + "digest": "sha256:3b465cbcadf7d437fc70c3b6aa2c93603a7eef0a3f5f1e861d91f303e4aabdee", + "tag": "sha256:3b465cbcadf7d437fc70c3b6aa2c93603a7eef0a3f5f1e861d91f303e4aabdee", + "resource_url": "harbor-app.jarvis.tanzu/veba-test/csi-test@sha256:3b465cbcadf7d437fc70c3b6aa2c93603a7eef0a3f5f1e861d91f303e4aabdee" + } + ], + "repository": { + "date_created": 1655887764, + "name": "csi-test", + "namespace": "veba-test", + "repo_full_name": "veba-test/csi-test", + "repo_type": "public" + } + } +}` diff --git a/examples/knative/go/kn-go-harbor-webhook/sinkbinding.yaml b/examples/knative/go/kn-go-harbor-webhook/sinkbinding.yaml new file mode 100644 index 00000000..350ff842 --- /dev/null +++ b/examples/knative/go/kn-go-harbor-webhook/sinkbinding.yaml @@ -0,0 +1,14 @@ +apiVersion: sources.knative.dev/v1 +kind: SinkBinding +metadata: + name: kn-go-harbor-webhook-binding +spec: + subject: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-go-harbor-webhook + sink: + ref: + apiVersion: eventing.knative.dev/v1 + kind: Broker + name: default \ No newline at end of file diff --git a/examples/knative/go/kn-go-harbor-webhook/static/screenshot-1.png b/examples/knative/go/kn-go-harbor-webhook/static/screenshot-1.png new file mode 100644 index 00000000..08085cd5 Binary files /dev/null and b/examples/knative/go/kn-go-harbor-webhook/static/screenshot-1.png differ diff --git a/examples/knative/go/kn-go-nsx-tag-sync/.ko.yaml b/examples/knative/go/kn-go-nsx-tag-sync/.ko.yaml new file mode 100644 index 00000000..d6710a53 --- /dev/null +++ b/examples/knative/go/kn-go-nsx-tag-sync/.ko.yaml @@ -0,0 +1,14 @@ +builds: + - id: function + # dir: . + # main: . + env: + - GOPRIVATE=*.vmware.com + flags: + - -tags + - netgo + ldflags: + - -s -w + - -extldflags "-static" + - -X main.buildCommit={{.Env.KO_COMMIT}} + - -X main.buildTag={{.Env.KO_TAG}} diff --git a/examples/knative/go/kn-go-nsx-tag-sync/README.md b/examples/knative/go/kn-go-nsx-tag-sync/README.md new file mode 100644 index 00000000..efddd7f9 --- /dev/null +++ b/examples/knative/go/kn-go-nsx-tag-sync/README.md @@ -0,0 +1,277 @@ +# kn-go-nsx-tag-sync + +Example Knative Go function for synchronizing vSphere virtual machine tags to +NSX-T based on vSphere tagging events. + +⚠️ This guide assumes that you have stood up a working Knative environment using +the vCenter Event Broker Appliance (VEBA). + +# How the Synchronization works + +When a vSphere tag is `attached` or `detached` to/from a virtual machine, a +corresponding vSphere event is generated. This is also true when batched tagging +operations are performed, i.e. every tag generates one event. + +The `kn-go-nsx-tag-sync` function reacts to these events and performs the +following steps: + +1) Validate that the received event is a valid `CloudEvent` +1) Validate that the received `CloudEvent` payload (`data`) contains a valid + tagging event +1) Retrieve the virtual machine `ManagedObjectReference` for the `Value` in the `Arguments[].Key == "Object"` field in the payload +1) If the `Object` does not resolve to a virtual machine, e.g. when a tag is attached to a folder or cluster, a warning is logged and the event is discarded +1) Retrieve the instance ID of the virtual machine +1) Retrieve all attached `category:tag` associations for the virtual machine +1) Update the tags in NSX-T for the virtual machine via +`api/v1/fabric/virtual-machines?action=update_tags` +[API](https://vdc-download.vmware.com/vmwb-repository/dcr-public/ce4128ae-8334-4f91-871b-ecce254cf69e/488f1280-204c-441d-8520-8279ac33d54b/api_includes/method_TagVirtualMachine.html) + +In case of temporary errors, e.g. HTTP timeouts or any HTTP 5xx code from NSX-T, +the VEBA `default` broker retries to deliver the event to the function. The +function will also log any errors during execution. + +## Performance and Scalability + +The function is configured to process incoming tagging events serially in a FIFO +(first-in, first-out) order (one event in-flight, maximum one function instance +running). + +This guarantees that concurrent or batched tagging operations on the same object +are **not interleaved, producing determinstic synchronization** results (safety +guarantee), eventually converging to the desired state. + +However, in larger environments with lots of objects and tags, the function can +fall behind processing from the VEBA `default` broker, causing delays and stale +tag states (views) in NSX-T. + +⚠️ FIFO execution can impact your network security, e.g. when a virtual machine is +supposed to be removed from a security group or firewall rule. + +To increase throughput and reduce latency (staleness) several tuning knobs exist +to **parallize** the processing of tagging events (see the [advanced +settings](#advanced-settings) section). In this case, when a function instance +(`pod`) receives multiple events **for the same object** (virtual machine), +events are deduplicated and only one operation to synchronize the tag(s) is +performed. + +⚠️ With parallel processing, **FIFO order is not guaranteed**. Depending on the +environment (size, tagging activities, etc.), there is a chance of interleaving +tagging operations **for the same object** which can lead to inconsistent +synchronisation results. The function will log cases were deduplication was +performed so an administrator can manually inspect the outcome. + +⚠️ When function autoscaling is also enabled (`maxScale > 1`), concurrent +operations without FIFO order can only be detected by inspecting the logs of all +active instances of the `kn-go-nsx-tag-sync` function. + +# Step 1 - Build + +⚠️ This step is only required if you made code changes to any of the \*.go +files. To directly deploy the function jump to [Step 3](#step-3---deploy). + +Requirement: If you make changes to the Go code, the +[ko](https://github.com/google/ko) tool is required to create the artifacts. + +Set the destination to push the function container image with an environment +variable. + +```bash +export KO_DOCKER_REPO=docker.io/my-user +export KO_COMMIT=$(git rev-parse --short=8 HEAD) +export KO_TAG=1.0 +``` + +The following command will build and push the image to the specified +`KO_DOCKER_REPO` repository. + +```bash +# for docker.io +ko publish --bare -t $KO_TAG . + +# for GCR +ko publish -B -t $KO_TAG . +``` + +⚠️ Using the above example, the resulting image would be +`docker.io/myuser/kn-go-nsx-tag-sync:1.0`. + + +# Step 2 - Test + +Run unit tests using the following command: + +```bash +go test -v -race -count 1 ./... +``` + +# Step 3 - Deploy + +⚠️ The following steps assume a working Knative environment using the `default` + Rabbit `broker`. The Knative `service` and `triggers` will be installed in the + `vmware-functions` Kubernetes namespace, assuming that the `broker` is also + available there. + +## Create vSphere and NSX Credentials Secrets + +Create a secret holding the username (role) and password needed to access +vCenter Server. The role must have at least **read-only** access to (the desired +subset of) virtual machines, e.g. cluster/datacenter, and tags in the inventory. + +```bash +kubectl create secret generic vsphere-credentials \ +--type=kubernetes.io/basic-auth \ +--from-literal=username='ro-user@vsphere.local' \ +--from-literal=password='ReplaceMe' \ +--namespace vmware-functions + +# update label for secret to show up in VEBA UI +kubectl -n vmware-functions label secret vsphere-credentials app=veba-ui +``` + +Create a secret holding the username (role) and password needed to access NSX +Manager. The role must have **write** permissions to manage virtual machine tags +(`Inventory > VM > Create & Assign Tags`). + +```bash +kubectl create secret generic nsx-credentials \ +--type=kubernetes.io/basic-auth \ +--from-literal=username='tag-admin@nsx.local' \ +--from-literal=password='ReplaceMe' \ +--namespace vmware-functions + +# update label for secret to show up in VEBA UI +kubectl -n vmware-functions label secret nsx-credentials app=veba-ui +``` + +## Update Environment Settings + +Update environment specific settings under `env:` in the `function.yaml` file. + +Please see the table below for a description of the available (and **required**) +settings. + + +| Configuration | Description | Example Values | Required | +|-----------------------|---------------------------------------------------------------------------------------------------|-----------------------------------|----------| +| `VCENTER_URL` | URL of the vCenter Server | `"https://vcenter-01.corp.local"` | **Yes** | +| `VCENTER_INSECURE` | When set to `false` require strict TLS (certificate) validation when connecting to vCenter Server | `"true"` | No | +| `VCENTER_SECRET_PATH` | The path where the vSphere credentials secret will be mounted | `"/var/bindings/vsphere"` | No | +| `NSX_URL` | URL of the NSX Manager Server | `"https://nsx-01.corp.local"` | **Yes** | +| `NSX_INSECURE` | When set to `false` require strict TLS (certificate) validation when connecting to NSX Manager | `"true"` | No | +| `NSX_SECRET_PATH` | The path where the NSX credentials secret will be mounted | `"/var/bindings/nsx"` | No | +| `DEBUG` | Enable debug logging | `"true"` | No | + +## Deploy the Function + +⚠️ If you made changes to the Go code/container image in [Step +1](#step-1---build) edit the `function.yaml` file with the custom name of the +container image used to build and push. + +Deploy the function to the VMware Event Broker Appliance (VEBA): + +```bash +kubectl apply -f function.yaml -n vmware-functions +``` + +For testing purposes, the [Knative manifest](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" +``` + +## Advanced Settings + +The following sections describe advanced settings for the `function.yaml` file. + +⚠️ Only change the default values once you understand the implications outlined +in the [performance and scalability](#performance-and-scalability) section. + +### `containerConcurrency` (Function) + +The field `containerConcurrency` influences how many events the function will +receive concurrently (i.e. "in-flight") from the attached trigger. + +In the default `containerConcurrency: 1` setting, the function will process +exactly one event at the same time (no concurrency). + +When changing this field to a higher value, +`rabbitmq.eventing.knative.dev/prefetchCount` must also be changed accordingly. + +### `rabbitmq.eventing.knative.dev/prefetchCount` (Trigger) + +The field `rabbitmq.eventing.knative.dev/prefetchCount` influences how many +events the corresponding trigger will pull (prefetch) from the event queue +(broker) and process them in parallel, i.e. send to the function. + +In order for this setting to be effective, the function must be configured with +`containerConcurrency >= prefetchCount` (recommended) or +`autoscaling.knative.dev/maxScale >= prefetchCount`. + +### `autoscaling.knative.dev/[min|max]Scale` (Function) + +The fields `autoscaling.knative.dev/minScale` and +`autoscaling.knative.dev/maxScale` influence how many instances of the function +are allowed to run. + +In the default setting (below) the function is a singleton and will not scale to +zero: + +```yaml +autoscaling.knative.dev/maxScale: "1" +autoscaling.knative.dev/minScale: "1" +``` + +To enable [scale to +zero](https://knative.dev/docs/serving/autoscaling/scale-to-zero/), set +`autoscaling.knative.dev/minScale: "0"`. + +In busy vSphere environments with lots of tagging operations and many objects, +it might be required to allow multiple instances of the function to share the +event queue ("worker pattern"). This should be the last resort if changing +`containerConcurrency` is not sufficient. + +Depending on the number of events per second and settings in +`containerConcurrency` and `rabbitmq.eventing.knative.dev/prefetchCount`, the +autoscaler will then create multiple instances of the function. + +### Example + +To handle up to **200 concurrent** tagging events if the default values (FIFO +semantics) are not appropriate: + +*Trigger settings:* + +```yaml +# pull up to 200 events (batch) from broker +rabbitmq.eventing.knative.dev/prefetchCount: "200" +``` + +*Function settings:* + +```yaml +# each function instance will process up to 20 events concurrently +containerConcurrency: 20 +``` + +```yaml +# scale to zero and max 10 function instances +autoscaling.knative.dev/maxScale: "10" +autoscaling.knative.dev/minScale: "0" +``` + +# Step 4 - Undeploy + +```bash +# undeploy function +kubectl delete -f function.yaml -n vmware-functions + +# delete secret +kubectl delete secret vsphere-credentials -n vmware-functions +kubectl delete secret nsx-credentials -n vmware-functions +``` diff --git a/examples/knative/go/kn-go-nsx-tag-sync/function.yaml b/examples/knative/go/kn-go-nsx-tag-sync/function.yaml new file mode 100644 index 00000000..b8c93f56 --- /dev/null +++ b/examples/knative/go/kn-go-nsx-tag-sync/function.yaml @@ -0,0 +1,88 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-go-nsx-tag-sync + labels: + app: veba-ui +spec: + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" + spec: + # max events in-flight - if this setting and prefetchCount in trigger is + # greater than 1 FIFO order is not respected + containerConcurrency: 1 + containers: + - image: us.gcr.io/daisy-284300/veba/kn-go-nsx-tag-sync:1.0 + imagePullPolicy: IfNotPresent + env: + - name: VCENTER_URL + value: "https://replace.me" + - name: VCENTER_INSECURE + value: "false" + - name: VCENTER_SECRET_PATH + value: "/var/bindings/vsphere" # default + - name: NSX_URL + value: "https://replace.me" + - name: NSX_INSECURE + value: "false" + - name: NSX_SECRET_PATH + value: "/var/bindings/nsx" # default + - name: DEBUG + value: "true" + volumeMounts: + - name: vsphere-credentials + mountPath: /var/bindings/vsphere + readOnly: true + - name: nsx-credentials + mountPath: /var/bindings/nsx + readOnly: true + volumes: + - name: vsphere-credentials + secret: + secretName: vsphere-credentials + - name: nsx-credentials + secret: + secretName: nsx-credentials +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-go-nsx-tag-sync-trigger-attach + annotations: + # Value must be between 1 and 1000 + # A value of 1 RabbitMQ Trigger behaves as a FIFO queue when function maxScale=1 + # Values above 1 break message ordering guarantees but can be seen as more performance oriented. + rabbitmq.eventing.knative.dev/prefetchCount: "1" +spec: + broker: default + filter: + attributes: + subject: com.vmware.cis.tagging.attach + subscriber: + ref: + apiVersion: v1 + kind: Service + name: kn-go-nsx-tag-sync +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-go-nsx-tag-sync-trigger-detach + annotations: + # Value must be between 1 and 1000 + # A value of 1 RabbitMQ Trigger behaves as a FIFO queue when function maxScale=1 + # Values above 1 break message ordering guarantees but can be seen as more performance oriented. + rabbitmq.eventing.knative.dev/prefetchCount: "1" +spec: + broker: default + filter: + attributes: + subject: com.vmware.cis.tagging.detach + subscriber: + ref: + apiVersion: v1 + kind: Service + name: kn-go-nsx-tag-sync diff --git a/examples/knative/go/kn-go-nsx-tag-sync/go.mod b/examples/knative/go/kn-go-nsx-tag-sync/go.mod new file mode 100644 index 00000000..91a3defd --- /dev/null +++ b/examples/knative/go/kn-go-nsx-tag-sync/go.mod @@ -0,0 +1,39 @@ +module github.com/vmware-samples/vcenter-event-broker-appliance/examples/knative/go/kn-go-nsx-tag-sync + +go 1.17 + +require ( + github.com/cloudevents/sdk-go/v2 v2.8.0 + github.com/embano1/vsphere v0.2.1 + github.com/google/uuid v1.3.0 + github.com/kelseyhightower/envconfig v1.4.0 + github.com/vmware/go-vmware-nsxt v0.0.0-20220328155605-f49a14c1ef5f + github.com/vmware/govmomi v0.27.2 + go.uber.org/zap v1.20.0 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + gotest.tools/v3 v3.0.3 + +) + +require ( + github.com/antihax/optional v1.0.0 // indirect + github.com/benbjohnson/clock v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.6 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.0.0 // indirect + github.com/json-iterator/go v1.1.11 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.7.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.26.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/examples/knative/go/kn-go-nsx-tag-sync/go.sum b/examples/knative/go/kn-go-nsx-tag-sync/go.sum new file mode 100644 index 00000000..44b1ce80 --- /dev/null +++ b/examples/knative/go/kn-go-nsx-tag-sync/go.sum @@ -0,0 +1,733 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d/go.mod h1:FSdwKX97koS5efgm8WevNf7XS3PqtyFkKDDXrz778cg= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudevents/sdk-go/v2 v2.8.0 h1:kmRaLbsafZmidZ0rZ6h7WOMqCkRMcVTLV5lxV/HKQ9Y= +github.com/cloudevents/sdk-go/v2 v2.8.0/go.mod h1:GpCBmUj7DIRiDhVvsK5d6WCbgTWs8DxAWTRtAwQmIXs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/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/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/embano1/vsphere v0.2.1 h1:2iXK1QzX45aVprbYB5qaR/lG5/ic10iaKTtivoR51og= +github.com/embano1/vsphere v0.2.1/go.mod h1:1G9PR75ds28DW/s4SOndi1htxKScpPCtii/Zq4nhvT0= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +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/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +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/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/vladimirvivien/gexe v0.1.1/go.mod h1:LHQL00w/7gDUKIak24n801ABp8C+ni6eBht9vGVst8w= +github.com/vmware/go-vmware-nsxt v0.0.0-20220328155605-f49a14c1ef5f h1:NbC9yOr5At92seXK+kOr2TzU3mIWzcJOVzZasGSuwoU= +github.com/vmware/go-vmware-nsxt v0.0.0-20220328155605-f49a14c1ef5f/go.mod h1:VEqcmf4Sp7gPB7z05QGyKVmn6xWppr7Nz8cVNvyC80o= +github.com/vmware/govmomi v0.27.2 h1:Ecooqg069gUbl5EuWYwcrvzRqMkah9J8BXaf9HCEGVM= +github.com/vmware/govmomi v0.27.2/go.mod h1:daTuJEcQosNMXYJOeku0qdBJP9SOLLWB3Mqz8THtv6o= +github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.20.0 h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc= +go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s= +k8s.io/apiextensions-apiserver v0.21.1/go.mod h1:KESQFCGjqVcVsZ9g0xX5bacMjyX5emuWcS2arzdEouA= +k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= +k8s.io/apiserver v0.21.1/go.mod h1:nLLYZvMWn35glJ4/FZRhzLG/3MPxAaZTgV4FJZdr+tY= +k8s.io/client-go v0.21.1/go.mod h1:/kEw4RgW+3xnBGzvp9IWxKSNA+lXn3A7AuH3gdOAzLs= +k8s.io/code-generator v0.21.1/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= +k8s.io/component-base v0.21.1/go.mod h1:NgzFZ2qu4m1juby4TnrmpR8adRk6ka62YdH5DkIIyKA= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210527160623-6fdb442a123b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/controller-runtime v0.9.0/go.mod h1:TgkfvrhhEw3PlI0BRL/5xM+89y3/yc0ZDfdbTl84si8= +sigs.k8s.io/e2e-framework v0.0.5/go.mod h1:ckH1mQj5eeRTxndiTLuVF+oq5fDIJsfvIVjtNM4npcI= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/examples/knative/go/kn-go-nsx-tag-sync/main.go b/examples/knative/go/kn-go-nsx-tag-sync/main.go new file mode 100644 index 00000000..d71bb4e2 --- /dev/null +++ b/examples/knative/go/kn-go-nsx-tag-sync/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "context" + "errors" + "os" + "os/signal" + "syscall" + + "github.com/embano1/vsphere/logger" + "github.com/kelseyhightower/envconfig" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/vmware-samples/vcenter-event-broker-appliance/examples/knative/go/kn-go-nsx-tag-sync/tags" +) + +var ( + buildCommit = "unknown" + buildTag = "unknown" +) + +func main() { + var cfg tags.Config + if err := envconfig.Process("", &cfg); err != nil { + panic("process environment variables: " + err.Error()) + } + + log, err := getLogger(cfg.Debug) + if err != nil { + panic("create logger: " + err.Error()) + } + log = log.Named("nsx-tag-sync") + + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer cancel() + ctx = logger.Set(ctx, log) + + syncer, err := tags.NewSyncer(ctx) + if err != nil { + log.Fatal("could not create vsphere to nsx tag synchronizer", zap.Error(err)) + } + + log.Info("starting vsphere to nsx tag synchronizer", + zap.Int("listenPort", cfg.Port), + zap.Bool("debug", cfg.Debug), + ) + + if err = syncer.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { + log.Fatal("could not run vsphere to nsx tag synchronizer", zap.Error(err)) + } + log.Info("shutdown complete") +} + +func getLogger(debug bool) (*zap.Logger, error) { + fields := []zap.Field{ + zap.String("commit", buildCommit), + zap.String("tag", buildTag), + } + + var config zap.Config + if debug { + config = zap.NewDevelopmentConfig() + } else { + config = zap.NewProductionConfig() + config.EncoderConfig.EncodeTime = zapcore.RFC3339NanoTimeEncoder + } + + log, err := config.Build(zap.Fields(fields...)) + if err != nil { + return nil, err + } + + return log, nil +} diff --git a/examples/knative/go/kn-go-nsx-tag-sync/tags/nsx.go b/examples/knative/go/kn-go-nsx-tag-sync/tags/nsx.go new file mode 100644 index 00000000..b8cdcab8 --- /dev/null +++ b/examples/knative/go/kn-go-nsx-tag-sync/tags/nsx.go @@ -0,0 +1,112 @@ +package tags + +import ( + "context" + "fmt" + "io/ioutil" + "net/url" + "path/filepath" + "time" + + "github.com/embano1/vsphere/logger" + "github.com/kelseyhightower/envconfig" + nsxt "github.com/vmware/go-vmware-nsxt" + "go.uber.org/zap" +) + +const ( + nsxApiBase = "/api/v1" + nsxAPITimeout = time.Second * 5 // http client transport timeout per request + nsxSessionRefresh = time.Minute +) + +var defaultRetryOnStatusCodes = []int{429, 500, 503} // nsx client + +func newNSXClient(ctx context.Context) (*nsxt.APIClient, error) { + var cfg Config + if err := envconfig.Process("", &cfg); err != nil { + return nil, fmt.Errorf("process environment variables: %w", err) + } + + log := logger.Get(ctx) + + nsxMgr, err := url.Parse(cfg.NSXAddress) + if err != nil { + return nil, fmt.Errorf("parse NSX_URL server value: %w", err) + } + + user, err := readKey("username") + if err != nil { + return nil, fmt.Errorf("read nsx username secret value: %w", err) + } + if user == "" { + return nil, fmt.Errorf("nsx username secret value must not be empty") + } + + pass, err := readKey("password") + if err != nil { + return nil, fmt.Errorf("read nsx password secret value: %w", err) + } + if pass == "" { + return nil, fmt.Errorf("nsx password secret value must not be empty") + } + + nsxCfg := nsxt.Configuration{ + // TODO (mgasch): CA/certs + BasePath: nsxApiBase, + Host: nsxMgr.Host, + Scheme: nsxMgr.Scheme, + UserName: user, + Password: pass, + SkipSessionAuth: true, // https://github.com/vmware/go-vmware-nsxt/issues/51 + UserAgent: "veba-nsx-tag-sync/1.0", + Insecure: cfg.NSXInsecure, + DefaultHeader: map[string]string{}, + RetriesConfiguration: nsxt.ClientRetriesConfiguration{ + MaxRetries: 3, + RetryMinDelay: 500, + RetryMaxDelay: 30000, + RetryOnStatuses: defaultRetryOnStatusCodes, + }, + } + + // we need to do this trick to use init logic for http client but customize + // request timeouts to not block on requests forever + if err = nsxt.InitHttpClient(&nsxCfg); err != nil { + return nil, fmt.Errorf("initialize nsx http client: %w", err) + } + nsxCfg.HTTPClient.Timeout = nsxAPITimeout + + // creates unauthenticated client + nsxClient, err := nsxt.NewAPIClient(&nsxCfg) + if err != nil { + return nil, fmt.Errorf("create nsx client: %w", err) + } + + log.Info("connecting to nsx manager", zap.String("host", cfg.NSXAddress)) + if cfg.NSXInsecure { + log.Warn("using potentially insecure connection to nsx manager", zap.Bool("insecure", cfg.NSXInsecure)) + } + + // create session + err = nsxt.GetDefaultHeaders(nsxClient) + if err != nil { + return nil, fmt.Errorf("connect to nsx manager: %w", err) + } + + return nsxClient, nil +} + +// readKey reads the file from the secret path +func readKey(key string) (string, error) { + var env Config + if err := envconfig.Process("", &env); err != nil { + return "", err + } + + data, err := ioutil.ReadFile(filepath.Join(env.NSXSecretPath, key)) + if err != nil { + return "", err + } + return string(data), nil +} diff --git a/examples/knative/go/kn-go-nsx-tag-sync/tags/tags.go b/examples/knative/go/kn-go-nsx-tag-sync/tags/tags.go new file mode 100644 index 00000000..5e68cc5f --- /dev/null +++ b/examples/knative/go/kn-go-nsx-tag-sync/tags/tags.go @@ -0,0 +1,281 @@ +package tags + +import ( + "context" + "errors" + "fmt" + nethttp "net/http" + "reflect" + "sync" + "time" + + ce "github.com/cloudevents/sdk-go/v2" + "github.com/cloudevents/sdk-go/v2/protocol/http" + vsphere "github.com/embano1/vsphere/client" + "github.com/embano1/vsphere/logger" + "github.com/kelseyhightower/envconfig" + nsxt "github.com/vmware/go-vmware-nsxt" + "github.com/vmware/go-vmware-nsxt/common" + "github.com/vmware/go-vmware-nsxt/manager" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/vim25/types" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" + "golang.org/x/sync/singleflight" +) + +// Config configures the syncer +type Config struct { + // cloudevents listener + Port int `envconfig:"PORT" default:"8080"` // knative injected + Debug bool `envconfig:"DEBUG" default:"false"` + + // NSX + NSXAddress string `envconfig:"NSX_URL" required:"true"` + NSXInsecure bool `envconfig:"NSX_INSECURE" default:"false"` + NSXSecretPath string `envconfig:"NSX_SECRET_PATH" default:"/var/bindings/nsx"` + + // vCenter + vsphere.Config +} + +// Syncer synchronizes vSphere tags to nsx +type Syncer struct { + vsphere *vsphere.Client + ce ce.Client + + sessionLock sync.RWMutex // periodically recreate nsx session + nsx *nsxt.APIClient + + serializer *singleflight.Group // dedupe concurrent operations for same vm object +} + +// NewSyncer returns an initialized syncer configured via environment variables +func NewSyncer(ctx context.Context) (*Syncer, error) { + var cfg Config + if err := envconfig.Process("", &cfg); err != nil { + return nil, fmt.Errorf("process environment variables: %w", err) + } + + log := logger.Get(ctx) + log.Info("connecting to vcenter", zap.String("host", cfg.Address)) + if cfg.Insecure { + log.Warn("using potentially insecure connection to vcenter", zap.Bool("insecure", cfg.Insecure)) + } + + var s Syncer + vc, err := vsphere.New(ctx) + if err != nil { + return nil, fmt.Errorf("create vsphere client: %w", err) + } + s.vsphere = vc + + nsx, err := newNSXClient(ctx) + if err != nil { + return nil, fmt.Errorf("create nsx client: %w", err) + } + + s.nsx = nsx + + s.serializer = &singleflight.Group{} + ceClient, err := ce.NewClientHTTP(http.WithPort(cfg.Port)) + if err != nil { + return nil, fmt.Errorf("create cloudevents http client: %w", err) + } + s.ce = ceClient + + return &s, nil +} + +// Run runs the syncer +func (s *Syncer) Run(ctx context.Context) error { + eg, egCtx := errgroup.WithContext(ctx) + + eg.Go(func() error { + return s.ce.StartReceiver(egCtx, s.handler) + }) + + // nsx session handling + eg.Go(func() error { + ticker := time.NewTicker(nsxSessionRefresh) + defer ticker.Stop() + + for { + select { + case <-egCtx.Done(): + return egCtx.Err() + case <-ticker.C: + reauth := func() error { + logger.Get(egCtx).Debug("attempting to reauthenticate nsx session") + s.sessionLock.Lock() + defer s.sessionLock.Unlock() + + // note: could block up to nsxAPITimeout + if err := nsxt.GetDefaultHeaders(s.nsx); err != nil { + return fmt.Errorf("reauthenticate nsx session: %w", err) + } + logger.Get(egCtx).Debug("successfully reauthenticated nsx session") + + return nil + }() + + if err := reauth; err != nil { + return err + } + } + } + }) + + eg.Go(func() error { + <-egCtx.Done() + logger.Get(egCtx).Info("received shutdown signal", zap.String("signal", egCtx.Err().Error())) + return nil + }) + + return eg.Wait() +} + +func (s *Syncer) handler(ctx context.Context, event ce.Event) error { + log := logger.Get(ctx).With(zap.String("eventID", event.ID())) + log.Debug("received event", zap.Any("event", event)) + + var vevent types.EventEx + if err := event.DataAs(&vevent); err != nil { + log.Error("could not marshal event to eventex", zap.Error(err)) + return http.NewResult(nethttp.StatusBadRequest, + "could not marshal cloudevent event data to vsphere event (eventID: %d)", + event.ID(), + ) + } + + var object string + for _, arg := range vevent.Arguments { + if arg.Key == "Object" { + if o, ok := arg.Value.(string); ok { + object = o + break + } else { + valueType := reflect.TypeOf(arg.Value).String() + log.Error("could not convert eventex argument value to string", + zap.Any("argumentObject", arg), + zap.String("valueType", valueType), + ) + return http.NewResult(nethttp.StatusBadRequest, + "could not read object value from event arguments (eventID: %d)", + event.ID(), + ) + } + } + } + + if object == "" { + log.Error("event did not contain object key", zap.Any("event", event)) + return http.NewResult(nethttp.StatusBadRequest, + "could not read object value from event arguments (eventID: %d)", + event.ID(), + ) + } + + ctx = logger.Set(ctx, log) + nsxTimeout := time.Second * 3 + _, err, shared := s.serializer.Do(object, s.syncVmTags(ctx, object, nsxTimeout)) + if shared { + log.Info("serialized and deduplicated concurrent calls to object ", zap.String("object", object)) + } + + if err != nil { + // either tag not on a vm object or vm already removed from inventory + var nfe *find.NotFoundError + if errors.As(err, &nfe) { + log.Warn("ignoring object", zap.String("object", object), zap.Error(err)) + + // return 400 instead of 404 because some brokers retry on 404 + return http.NewResult(nethttp.StatusBadRequest, + "could not synchronize tags for object %q (eventID: %d)", + object, + event.ID(), + ) + } + + log.Error("could not synchronize vm tags", zap.String("object", object), zap.Error(err)) + return http.NewResult(nethttp.StatusInternalServerError, + "could not synchronize tags for vm %q (eventID: %d)", + object, + event.ID(), + ) + } + + return nil +} + +func (s *Syncer) syncVmTags(ctx context.Context, vm string, nsxTimeout time.Duration) func() (interface{}, error) { + return func() (interface{}, error) { + log := logger.Get(ctx) + log.Debug("retrieving vm managed object reference", zap.String("object", vm)) + + ref, err := getVmRef(ctx, s.vsphere.SOAP.Client, vm) + if err != nil { + return nil, fmt.Errorf("find vm reference for object %q: %w", vm, err) + } + + log.Debug("retrieving vm instance id", zap.Any("ref", ref)) + id, err := getInstancedID(ctx, s.vsphere.SOAP.Client, ref) + if err != nil { + return nil, fmt.Errorf("retrieve vm instance id for ref %q: %w", ref, err) + } + + log.Debug("retrieving vm tags", zap.Any("ref", ref)) + attachedTags, err := s.vsphere.Tags.ListAttachedTags(ctx, ref) + if err != nil { + return nil, fmt.Errorf("list attached tags: %w", err) + } + + req := manager.VirtualMachineTagUpdate{ + ExternalId: id, + Tags: make([]common.Tag, len(attachedTags)), + } + + for idx, t := range attachedTags { + details, err := s.vsphere.Tags.GetTag(ctx, t) + if err != nil { + return nil, fmt.Errorf("get tag %q: %w", t, err) + } + + categoryDetails, err := s.vsphere.Tags.GetCategory(ctx, details.CategoryID) + if err != nil { + return nil, fmt.Errorf("get category %q: %w", details.CategoryID, err) + } + + req.Tags[idx] = common.Tag{ + Scope: categoryDetails.Name, + Tag: details.Name, + } + } + + // use explicitly provided timeout here to return fast from this singleflight + // routine and reduce likelihood of stale tag information during concurrent + // (blocked) operations on the same object (key) + // + // note: in case timeout fires before nsx ack, singleflight caller returns HTTP + // 500 due to ctx.Error() in the event handler, causing event sender (broker) + // typically to retry + ctx, cancel := context.WithTimeout(ctx, nsxTimeout) + defer cancel() + log.Info("updating virtual machine tags in nsx", zap.Any("request", req)) + + updateTags := func() error { + s.sessionLock.RLock() + defer s.sessionLock.RUnlock() + if _, err = s.nsx.FabricApi.UpdateVirtualMachineTagsUpdateTags(ctx, req); err != nil { + return err + } + return nil + } + + if err = updateTags(); err != nil { + return nil, fmt.Errorf("update virtual machine tags in nsx: %w", err) + } + + return nil, nil + } +} diff --git a/examples/knative/go/kn-go-nsx-tag-sync/tags/tags_test.go b/examples/knative/go/kn-go-nsx-tag-sync/tags/tags_test.go new file mode 100644 index 00000000..dcab7296 --- /dev/null +++ b/examples/knative/go/kn-go-nsx-tag-sync/tags/tags_test.go @@ -0,0 +1,278 @@ +package tags + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + nethttp "net/http" + "net/url" + "sync" + "testing" + "time" + + ce "github.com/cloudevents/sdk-go/v2" + "github.com/cloudevents/sdk-go/v2/client/test" + "github.com/cloudevents/sdk-go/v2/protocol/http" + vsphere "github.com/embano1/vsphere/client" + "github.com/embano1/vsphere/logger" + "github.com/google/uuid" + nsxt "github.com/vmware/go-vmware-nsxt" + "github.com/vmware/go-vmware-nsxt/manager" + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/session" + "github.com/vmware/govmomi/simulator" + "github.com/vmware/govmomi/vapi/rest" + _ "github.com/vmware/govmomi/vapi/simulator" + "github.com/vmware/govmomi/vapi/tags" + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/types" + "go.uber.org/zap/zaptest" + "golang.org/x/sync/singleflight" + "gotest.tools/v3/assert" +) + +func newVmPoweredOnEvent() types.BaseEvent { + return &types.VmPoweredOnEvent{ + VmEvent: types.VmEvent{ + Event: types.Event{ + Key: 1, + ChainId: 1, + CreatedTime: time.Now(), + UserName: "administrator", + Vm: &types.VmEventArgument{ + Vm: types.ManagedObjectReference{ + Type: "VirtualMachine", + Value: "vm-1", + }, + }, + }, + }, + } +} + +func newCloudEvent(t *testing.T, data interface{}) ce.Event { + t.Helper() + e := ce.NewEvent() + e.SetID(uuid.New().String()) + e.SetType("test.event.v0") + e.SetSource("test.source") + + err := e.SetData(ce.ApplicationJSON, data) + assert.NilError(t, err) + + return e +} + +func TestSyncer_Run(t *testing.T) { + t.Run("returns when context cancelled", func(t *testing.T) { + ceClient, _ := test.NewMockReceiverClient(t, 1) + s := &Syncer{ + ce: ceClient, + serializer: &singleflight.Group{}, + } + + ctx := logger.Set(context.Background(), zaptest.NewLogger(t)) + ctx, cancel := context.WithTimeout(ctx, time.Millisecond*500) + defer cancel() + + err := s.Run(ctx) + assert.ErrorType(t, err, context.DeadlineExceeded) + }) +} + +func TestSyncer_handler(t *testing.T) { + type wantError struct { + message string + code int + } + + testCases := []struct { + name string + event ce.Event + wantError wantError + }{ + { + name: "cloudevent data is not vsphere event", + event: newCloudEvent(t, `{"hello":"world"}`), + wantError: wantError{message: "could not marshal", code: 400}, + }, { + name: "cloudevent data is not tagging event", + event: newCloudEvent(t, newVmPoweredOnEvent()), + wantError: wantError{message: "could not read object", code: 400}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := logger.Set(context.Background(), zaptest.NewLogger(t)) + + s := &Syncer{} + err := s.handler(ctx, tc.event) + assert.ErrorType(t, err, &http.Result{}) + assert.Equal(t, err.(*http.Result).StatusCode, tc.wantError.code) + }) + } +} + +func TestSyncer_syncTags(t *testing.T) { + testCases := []struct { + name string + object string // vm + tagMap map[string]string // category/tag mappings + nsxMock *nsxAPIMock // mock NSX API responses + wantErr string + }{ + { + name: "fails when vm object cannot be found", + object: "notexist", + nsxMock: &nsxAPIMock{}, + wantErr: "not found", + }, + { + name: "fails on nsx http 500 error", + object: "DC0_H0_VM0", + nsxMock: &nsxAPIMock{codes: []int{500}}, + wantErr: "500", + }, + { + name: "successfully synchronizes tags", + object: "DC0_H0_VM0", + tagMap: map[string]string{ + "category1": "tag1", + "category2": "tag2", + }, + nsxMock: &nsxAPIMock{codes: []int{200}}, + wantErr: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + simulator.Run(func(ctx context.Context, client *vim25.Client) error { + ctx = logger.Set(ctx, zaptest.NewLogger(t)) + + tc.nsxMock.t = t + mockHttp := nethttp.DefaultClient + mockHttp.Transport = tc.nsxMock + + nsxCfg := nsxt.Configuration{ + SkipSessionAuth: true, + RetriesConfiguration: nsxt.ClientRetriesConfiguration{}, // disable retries + HTTPClient: mockHttp, + } + + nsxClient, err := nsxt.NewAPIClient(&nsxCfg) + assert.NilError(t, err) + + govm := govmomi.Client{ + Client: client, + SessionManager: session.NewManager(client), + } + + rc := rest.NewClient(client) + err = rc.Login(ctx, url.UserPassword("user", "pass")) + assert.NilError(t, err) + tm := tags.NewManager(rc) + + s := &Syncer{ + vsphere: &vsphere.Client{ + SOAP: &govm, + REST: rc, + Tags: tm, + }, + nsx: nsxClient, + } + + var wantID string + + // only required when we don't expect error + if tc.wantErr == "" { + ref, err := getVmRef(ctx, s.vsphere.SOAP.Client, tc.object) + assert.NilError(t, err) + wantID, err = getInstancedID(ctx, s.vsphere.SOAP.Client, ref) + assert.NilError(t, err) + attachTags(t, ctx, s.vsphere.Tags, ref, tc.tagMap) + } + + _, err = s.syncVmTags(ctx, tc.object, time.Second)() + + if tc.wantErr != "" { + assert.ErrorContains(t, err, tc.wantErr) + } else { + assert.NilError(t, err) + assert.Equal(t, tc.nsxMock.tagReq.ExternalId, wantID) + assert.Equal(t, len(tc.tagMap), len(tc.nsxMock.tagReq.Tags)) + } + + return nil + }) + }) + } +} + +type nsxAPIMock struct { + t *testing.T + codes []int // response codes/count + + sync.Mutex + counter int + + tagReq manager.VirtualMachineTagUpdate // stores last syncer nsx tag request +} + +func (nsx *nsxAPIMock) RoundTrip(req *nethttp.Request) (*nethttp.Response, error) { + var buf bytes.Buffer + + _, err := io.Copy(&buf, req.Body) + assert.NilError(nsx.t, err) + + var tagReq manager.VirtualMachineTagUpdate + err = json.Unmarshal(buf.Bytes(), &tagReq) + assert.NilError(nsx.t, err) + + nsx.Lock() + defer func() { + nsx.counter++ + _ = req.Body.Close() + nsx.Unlock() + }() + + nsx.tagReq = tagReq + + code := nsx.codes[nsx.counter] + return &nethttp.Response{ + StatusCode: code, + Status: fmt.Sprintf("%d", code), + Request: req.Clone(context.TODO()), + }, nil +} + +func attachTags(t *testing.T, ctx context.Context, tm *tags.Manager, ref types.ManagedObjectReference, mappings map[string]string) { + t.Helper() + + var tagIDs []string + for cat, tag := range mappings { + newCat := tags.Category{ + Name: cat, + Description: cat, + // Cardinality: "", + } + catID, err := tm.CreateCategory(ctx, &newCat) + assert.NilError(t, err) + + newTag := tags.Tag{ + Description: tag, + Name: tag, + CategoryID: catID, + } + tagID, err := tm.CreateTag(ctx, &newTag) + assert.NilError(t, err) + + tagIDs = append(tagIDs, tagID) + + } + err := tm.AttachMultipleTagsToObject(ctx, tagIDs, ref) + assert.NilError(t, err) +} diff --git a/examples/knative/go/kn-go-nsx-tag-sync/tags/vsphere.go b/examples/knative/go/kn-go-nsx-tag-sync/tags/vsphere.go new file mode 100644 index 00000000..1b283b40 --- /dev/null +++ b/examples/knative/go/kn-go-nsx-tag-sync/tags/vsphere.go @@ -0,0 +1,32 @@ +package tags + +import ( + "context" + "fmt" + + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/property" + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +func getInstancedID(ctx context.Context, client *vim25.Client, ref types.ManagedObjectReference) (string, error) { + var info mo.VirtualMachine + pc := property.DefaultCollector(client) + if err := pc.RetrieveOne(ctx, ref, []string{"config.instanceUuid"}, &info); err != nil { + return "", fmt.Errorf("retrieve instanceUuid property: %w", err) + } + + return info.Config.InstanceUuid, nil +} + +func getVmRef(ctx context.Context, client *vim25.Client, name string) (types.ManagedObjectReference, error) { + f := find.NewFinder(client) + vm, err := f.VirtualMachine(ctx, name) + if err != nil { + return types.ManagedObjectReference{}, err + } + + return vm.Reference(), nil +} diff --git a/examples/knative/go/kn-go-nsx-tag-sync/tags/vsphere_test.go b/examples/knative/go/kn-go-nsx-tag-sync/tags/vsphere_test.go new file mode 100644 index 00000000..cd0f46e5 --- /dev/null +++ b/examples/knative/go/kn-go-nsx-tag-sync/tags/vsphere_test.go @@ -0,0 +1,97 @@ +package tags + +import ( + "context" + "testing" + + "github.com/vmware/govmomi/simulator" + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/types" + "gotest.tools/v3/assert" +) + +func Test_getVmRef(t *testing.T) { + tests := []struct { + name string + vm string + want types.ManagedObjectReference + wantErr string + }{ + { + name: "retrieves ref for vm", + vm: "DC0_H0_VM0", + want: types.ManagedObjectReference{ + Type: "VirtualMachine", + Value: "vm-54", + }, + wantErr: "", + }, + { + name: "vm does not exist", + vm: "vm-not-exist", + want: types.ManagedObjectReference{ + Type: "", + Value: "", + }, + wantErr: "not found", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + simulator.Run(func(ctx context.Context, client *vim25.Client) error { + ref, err := getVmRef(ctx, client, tt.vm) + assert.Equal(t, tt.want, ref) + + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + } else { + assert.NilError(t, err) + } + + return nil + }) + }) + } +} + +func Test_getInstancedID(t *testing.T) { + tests := []struct { + name string + vm types.ManagedObjectReference + wantErr string + }{ + { + name: "retrieves id for vm", + vm: types.ManagedObjectReference{ + Type: "VirtualMachine", + Value: "vm-54", + }, + wantErr: "", + }, + { + name: "vm does not exist", + vm: types.ManagedObjectReference{ + Type: "VirtualMachine", + Value: "invalid", + }, + wantErr: "has already been deleted", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + simulator.Run(func(ctx context.Context, client *vim25.Client) error { + id, err := getInstancedID(ctx, client, tt.vm) + + if tt.wantErr != "" { + assert.Equal(t, id, "") + assert.ErrorContains(t, err, tt.wantErr) + } else { + assert.NilError(t, err) + assert.Assert(t, len(id) > 0) + } + + return nil + }) + }) + } +} diff --git a/examples/knative/powercli/kn-pcli-datastore-usage-email/Dockerfile b/examples/knative/powercli/kn-pcli-datastore-usage-email/Dockerfile new file mode 100644 index 00000000..49b2c980 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-datastore-usage-email/Dockerfile @@ -0,0 +1,5 @@ +FROM us.gcr.io/daisy-284300/veba/ce-pcli-base:1.4 + +COPY handler.ps1 handler.ps1 + +CMD ["pwsh","./server.ps1"] diff --git a/examples/knative/powercli/kn-pcli-datastore-usage-email/README.md b/examples/knative/powercli/kn-pcli-datastore-usage-email/README.md new file mode 100644 index 00000000..4697bfe0 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-datastore-usage-email/README.md @@ -0,0 +1,180 @@ +# kn-pcli-datastore-usage-email +Example Knative PowerCLI function kn-pcli-datastore-usage-email. Sends email notifications to a specified email address for datastore usage on disk alarms. Optional configuration for a per-datastore email, enabling different recipients for different datastores. + +# Step 1 - Build + +> **Note:** This step is only required if you made code changes to `handler.ps1` +> or `Dockerfile`. + +Create the container image locally to test your function logic. + +Mac/Linux +``` +# change the IMAGE name accordingly, example below for Docker +export IMAGE=/kn-pcli-datastore-usage-email:1.0 +docker build -t ${IMAGE} . +``` + +Windows +``` +# change the IMAGE name accordingly, example below for Docker +$IMAGE="/kn-pcli-datastore-usage-email:1.0" +docker build -t ${IMAGE} . +``` +# 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 +* `VCENTER_USERNAME` - vCenter account with permission to reconfigure distributed virtual switches +* `VCENTER_PASSWORD` - vCenter password associated with the username +* `VCENTER_CERTIFCATE_ACTION` - Set-PowerCLIConfiguration Action to configure when connection fails due to certificate error, default is `Fail`. (Possible values: `Fail`, `Ignore` or `Warn`) +* `VC_ALARM_NAME` - The alarm to trigger alerts for. The default is the default datastore usage alarm for all vCenter installations. If you have a custom +* `DATASTORE_NAMES` - A list of datastore names that you want monitored by the function +* `SMTP_SERVER` - SMTP server IP or FQDN +* `SMTP_PORT` - SMTP port, typically 25 for unauthenticated and 587 for authenticated +* `SMTP_USERNAME` - Optional. Username for authenticated SMTP +* `SMTP_PASSWORD` - Optional. Password for authenticated SMTP +* `EMAIL_SUBJECT` - The subject line of the notification email +* `EMAIL_TO` - A list of recipients for the notification +* `EMAIL_FROM` - The email address the notification email comes from +* `DATASTORE_CUSTOM_PROP_EMAIL_TO` - Optional. The name of a custom attribute containing datastore-specific notification email addresses. See the `Custom Recipients` section for details. +## Custom recipients + +The function always sends notification emails to `EMAIL_TO`. Some customers want a specific group notified based on the datastore. For example, you might want a group of database administrators notified if a datastore dedicated to MySQL databses begins to fill. + +You can accomplish per-datastore notifications by configuring any datastore with a custom attribute. For example, you might add a custom attribute named `notify_email` to `datastore1`. Another datastore, `datastore2`, does not have the custom attribute. You then update `DATASTORE_CUSTOM_PROP_EMAIL_TO` with a value of `notify_email`. When the function runs, it will check the alarming datastore for the presence of a custom attribute named `notify_email`. If the custom attribute is found, notifications for `datastore1` will be sent to `EMAIL_TO` and the email address found in custom attribute `notify_email`. Notifications for `datastore2` will be sent only to `EMAIL_TO`. + +## Run Container + +If you built a custom image in Step 1, comment out the default `IMAGE` command below - the `docker run` command will then use use the value previously stored in the `IMAGE` variable. Otherwise, use the default image as shown below. Start the container image by running the following commands: + +Mac/Linux +```console +export IMAGE=us.gcr.io/daisy-284300/veba/kn-pcli-datastore-usage-email:1.0 +docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} +``` +Windows +```console +$IMAGE="us.gcr.io/daisy-284300/veba/kn-pcli-datastore-usage-email:1.0" +docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} +``` +If you are not using the custom attribute functionality, you can test the function without making any changes. You can skip the next section. + +## Payload changes for custom attribute functionality + +In the `test` directory, edit `test-payload.json`. Locate the `Ds` section of the JSON file. Change the `Name:` property from `ProdDatastore1` to the name of the datastore in your vCenter inventory that contains the custom attribute. If you do not make this change, the function will still be invoked, but no notifications will be sent because the datastore will not be found. + +```json + "Ds": { + "Name": "ProdDatastore1", + "Datastore": { + "Type": "Datastore", + "Value": "datastore-60" + } + }, +``` + +You must also edit `docker-test-env-variable`, replace one of the existing values with the same datastore name you used in `test-payload.json` +```json +"DATASTORE_NAMES":["ProdDatastore1","ProdDatastore2"] +``` +>Note : If you make a change to `docker-test-env-variable`, you must run the `docker build` command again. +## Using the test scripts + +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. When run with no arguments, the scripts will send the contents of `test-payload.json` as the payload. If you pass the scripts a different filename as an argument, they will send the contents of the specified file instead. Example: `send-cloudevent-test.ps1 test-payload2.json`. This technique can be useful if you want to test notifications for multiple datastores. + +```console +Testing Function ... +See docker container console for output + +# Output from docker container console +04/28/2022 22:01:44 - DEBUG: Alarm Name: Datastore usage on disk +04/28/2022 22:01:44 - DEBUG: DS Name: VEBA-DS-01 +04/28/2022 22:01:44 - DEBUG: Alarm Status: yellow +04/28/2022 22:01:44 - DEBUG: vCenter: source-123 +04/28/2022 22:01:44 - DEBUG: Data Center: DataCenter1 +04/28/2022 22:01:44 - DEBUG: Alarm to Monitor: Datastore usage on disk +04/28/2022 22:01:44 - DEBUG: Datastores to Monitor: VEBA-DS-01 VEBA-DS-02 +04/28/2022 22:01:44 - DEBUG: Message Subject: ⚠️ [VMC Datastore Notification Alarm] ⚠️ +04/28/2022 22:01:44 - DEBUG: Message Body: Datastore usage on disk VEBA-DS-01 has reached warning threshold. +Please log in to source-123 and ensure that everything is operating as expected. + vCenter Server: source-123 + Datacenter: DataCenter1 + Datastore: VEBA-DS-01 +04/28/2022 22:01:44 - DEBUG: custom prop: notify_email +04/28/2022 22:01:44 - DEBUG: email Key: 101 +04/28/2022 22:01:44 - INFO: Datastore VEBA-DS-01 has Custom Field: notify_email with value: notify2@vmweventbroker.io + +04/28/2022 22:01:44 - DEBUG: Found key 101 with value notify2@vmweventbroker.io +04/28/2022 22:01:44 - Sending notification to notify1@vmweventbroker.io notify2@vmweventbroker.io ... + +04/28/2022 22:01:45 - datastore-usage-email operation complete ... + +04/28/2022 22:01:45 - Handler Processing Completed ... +``` +--- +> Pro Tip - If you are rapidly iterating on the code and want to easily rebuild and launch the container, +> you can chain all of the commands together with ampersands. This will allow you to re-run +> the commands by simply pressing the `up` arrow and `Enter`. + +```console +cd .. && docker build -t ${IMAGE} . && cd test && docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} +``` + +# 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. + +If you built a custom image, push it to an accessible registry such as Docker once you're done developing and testing your function logic. + +```console +docker push ${IMAGE} +``` + +Update the `datastore_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 `DATASTORE_SECRET`. + +```console +# create secret +kubectl -n vmware-functions create secret generic datastore-secret --from-file=DATASTORE_SECRET=datastore_secret.json + +# update label for secret to show up in VEBA UI +kubectl -n vmware-functions label secret function-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 `AlarmStatusChangedEventt` 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 function-secret +``` \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-datastore-usage-email/datastore_secret.json b/examples/knative/powercli/kn-pcli-datastore-usage-email/datastore_secret.json new file mode 100644 index 00000000..5e11f3a0 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-datastore-usage-email/datastore_secret.json @@ -0,0 +1,16 @@ +{ + "VCENTER_SERVER": "FILL-ME-IN", + "VCENTER_USERNAME" : "FILL-ME-IN", + "VCENTER_PASSWORD" : "FILL-ME-IN", + "VCENTER_CERTIFICATE_ACTION" : "Fail", + "VC_ALARM_NAME" : "Datastore usage on disk", + "DATASTORE_NAMES" : ["ProdDatastore1","ProdDatastore2"], + "SMTP_SERVER" : "FILL-ME-IN", + "SMTP_PORT" : "FILL-ME-IN", + "SMTP_USERNAME" : "", + "SMTP_PASSWORD" : "", + "EMAIL_SUBJECT" : "[VMC Datastore Notification Alarm]", + "EMAIL_TO": ["FILL-ME-IN"], + "EMAIL_FROM" : "FILL-ME-IN", + "DATASTORE_CUSTOM_PROP_EMAIL_TO" : "" +} diff --git a/examples/knative/powercli/kn-pcli-datastore-usage-email/function.yaml b/examples/knative/powercli/kn-pcli-datastore-usage-email/function.yaml new file mode 100644 index 00000000..7f953771 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-datastore-usage-email/function.yaml @@ -0,0 +1,41 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-pcli-datastore-usage-email + labels: + app: veba-ui +spec: + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" + spec: + containers: + - image: us.gcr.io/daisy-284300/veba/kn-pcli-datastore-usage-email:1.0 + envFrom: + - secretRef: + name: datastore-secret + env: + - name: FUNCTION_DEBUG + value: "true" +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: kn-pcli-datastore-usage-email-trigger + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + # Replace this subject with the event you need to trigger on + # Then, edit send-cloudevent-test.ps1 and send-cloudevent-test.sh in the /test folder + subject: AlarmStatusChangedEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-pcli-datastore-usage-email diff --git a/examples/knative/powercli/kn-pcli-datastore-usage-email/handler.ps1 b/examples/knative/powercli/kn-pcli-datastore-usage-email/handler.ps1 new file mode 100644 index 00000000..f9d69b2f --- /dev/null +++ b/examples/knative/powercli/kn-pcli-datastore-usage-email/handler.ps1 @@ -0,0 +1,216 @@ +Function Process-Init { + [CmdletBinding()] + param() + Write-Host "$(Get-Date) - Processing Init`n" + + try { + $jsonSecrets = ${env:DATASTORE_SECRET} | ConvertFrom-Json + } + catch { + throw "`nK8s secret `$env:DATASTORE_SECRET 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_CERTIFICATE_ACTION = ${jsonSecrets}.VCENTER_CERTIFICATE_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_CERTIFICATE_ACTION} -ParticipateInCeip:$true -Confirm:$false + + Write-Host "$(Get-Date) - Connecting to vCenter Server $VCENTER_SERVER`n" + + try { + Connect-VIServer -Server $VCENTER_SERVER -User $VCENTER_USERNAME -Password $VCENTER_PASSWORD + } + catch { + Write-Error "$(Get-Date) - ERROR: Failed to connect to vCenter Server" + throw $_ + } + + Write-Host "$(Get-Date) - Successfully connected to $VCENTER_SERVER`n" + + Write-Host "$(Get-Date) - Init Processing Completed`n" +} + +Function Process-Shutdown { + [CmdletBinding()] + param() + Write-Host "$(Get-Date) - Processing Shutdown`n" + + Write-Host "$(Get-Date) - Disconnecting from vCenter Server`n" + + try { + Disconnect-VIServer * -Confirm:$false + } + catch { + Write-Error "$(Get-Date) - Error: Failed to Disconnect from vCenter Server" + } + + Write-Host "$(Get-Date) - Shutdown Processing Completed`n" +} + +Function Process-Handler { + [CmdletBinding()] + 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:DATASTORE_SECRET} | ConvertFrom-Json + } + catch { + throw "`nK8s secret `$env:DATASTORE_SECRET does not look to be defined" + } + + $alarmName = $($cloudEventData.Alarm.Name -replace "\n"," ") + $datastoreName = $($cloudEventData.Ds.Name) + $alarmStatus = $($cloudEventData.To) + $vcenter = $($cloudEvent.source -replace "/sdk","") + $datacenter = $($cloudEventData.Datacenter.Name) + $alarmToMonitor = ${jsonSecrets}.VC_ALARM_NAME + $datastoresToMonitor = ${jsonSecrets}.DATASTORE_NAMES + + if (${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: Alarm Name: $alarmName" + Write-Host "$(Get-Date) - DEBUG: DS Name: $datastoreName" + Write-Host "$(Get-Date) - DEBUG: Alarm Status: $alarmStatus" + Write-Host "$(Get-Date) - DEBUG: vCenter: $vcenter" + Write-Host "$(Get-Date) - DEBUG: Data Center: $datacenter" + Write-Host "$(Get-Date) - DEBUG: Alarm to Monitor: $alarmToMonitor" + Write-Host "$(Get-Date) - DEBUG: Datastores to Monitor: $datastoresToMonitor" + } + + if( ("$alarmName" -match $($alarmToMonitor)) -and ([bool]($datastoresToMonitor -match "$datastoreName")) -and ($alarmStatus -eq "yellow" -or $alarmStatus -eq "red" -or $alarmStatus -eq "green") ) { + # Warning Email Body + if($alarmStatus -eq "yellow") { + $subject = "⚠️ $(${jsonSecrets}.EMAIL_SUBJECT) ⚠️ " + $threshold = "warning" + } elseif($alarmStatus -eq "red") { + $subject = "☢️ $(${jsonSecrets}.EMAIL_SUBJECT) ☢️ " + $threshold = "error" + } elseif($alarmStatus -eq "green") { + $subject = "$(${jsonSecrets}.EMAIL_SUBJECT)" + $threshold = "normal" + } + + $Body = "$alarmName $datastoreName has reached $threshold threshold.`r`n" + + if ( $threshold -ne "normal") { + $Body = $Body + "Please log in to $($vcenter) and ensure that everything is operating as expected.`r`n" + } + + $Body = $Body + @" + vCenter Server: $vCenter + Datacenter: $datacenter + Datastore: $datastoreName +"@ + + if (${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: Message Subject: $($Subject)" + Write-Host "$(Get-Date) - DEBUG: Message Body: $($Body)" + } + + $emailTo = ${jsonSecrets}.EMAIL_TO + + # If the JSON file has a custom property email field defined, find the value + # This is used to allow admins to add an email address as a custom property on a datastore for storage alarms independent of the EMAIL_TO value + if (${jsonSecrets}.DATASTORE_CUSTOM_PROP_EMAIL_TO.length -gt 0) + { + # This object has all defined custom fields in the vCenter + try { + $customFieldMgr = Get-View ($global:DefaultVIServer.ExtensionData.Content.CustomFieldsManager) + } + catch { + Write-Host "$(Get-Date) - ERROR: unable to retrieve CustomFieldsManager view`n" + throw $_ + } + + try { + $datastoreView = Get-View -ViewType Datastore -Property Name, Value -Filter @{"name"=$datastoreName} + } + catch { + Write-Host "$(Get-Date) - ERROR: unable to retrieve Datastore view view`n" + throw $_ + } + + # Build 2 hash tables for the key-value pairs in the Custom Fields Manager, one to search by Custom Field ID and one to search by Name + $customKeyLookup = @{} + $customNameLookup = @{} + $customFieldMgr.Field | ForEach-Object { + $customKeyLookup.Add($_.Key, $_.Name) + $customNameLookup.Add($_.Name, $_.Key) + } + + # This is the custom field that we're looking to pull an email address out of + $emailKey = $customNameLookup[$(${jsonSecrets}.DATASTORE_CUSTOM_PROP_EMAIL_TO)] + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: custom prop: $(${jsonSecrets}.DATASTORE_CUSTOM_PROP_EMAIL_TO)" + Write-Host "$(Get-Date) - DEBUG: email Key: $emailKey" + } + + #If we find one, this is the email address we will add to the "To" field in the email + $addEmailAddress = "" + foreach ($row in $datastoreView.Value) { + Write-Host "$(Get-Date) - INFO: Datastore" $datastoreName "has Custom Field:" $customKeyLookup[$row.Key] "with value:" $row.Value "`n" + if ($row.Key -eq $emailKey) + { + if(${env:FUNCTION_DEBUG} -eq "true") { + write-host "$(Get-Date) - DEBUG: Found key" $emailKey "with value" $row.value + } + $addEmailAddress = $row.value + break + } + } + + if ($addEmailAddress.length -gt 0){ + $emailTo = $emailTo + $addEmailAddress + } + else { + Write-Host "$(Get-Date) - WARN: DATASTORE_CUSTOM_PROP_EMAIL_TO value '"${jsonSecrets}.DATASTORE_CUSTOM_PROP_EMAIL_TO "' found in JSON config but not found on datastore" + } + + } + + Write-Host "$(Get-Date) - Sending notification to $($emailTo) ...`n" + # If defined in the config file, send via authenticated SMTP, otherwise use standard SMTP + if (${jsonSecrets}.SMTP_PASSWORD.length -gt 0 -and ${jsonSecrets}.SMTP_USERNAME.length -gt 0) + { + $password = ConvertTo-SecureString "$(${jsonSecrets}.SMTP_PASSWORD)" -AsPlainText -Force + $credential = New-Object System.Management.Automation.PSCredential($(${jsonSecrets}.SMTP_USERNAME), $password) + try { + Send-MailMessage -From $(${jsonSecrets}.EMAIL_FROM) -to $($emailTo) -Subject $Subject -Body $Body -SmtpServer $(${jsonSecrets}.SMTP_SERVER) -port $(${jsonSecrets}.SMTP_PORT) -UseSsl -Credential $credential -Encoding UTF32 + } + catch { + Write-Host "$(Get-Date) - ERROR: Unable to send email message`n" + throw $_ + } + } + else + { + try { + Send-MailMessage -From $(${jsonSecrets}.EMAIL_FROM) -to $($emailTo) -Subject $Subject -Body $Body -SmtpServer $(${jsonSecrets}.SMTP_SERVER) -port $(${jsonSecrets}.SMTP_PORT) -Encoding UTF32 + } + catch { + Write-Host "$(Get-Date) - ERROR: Unable to send email message`n" + throw $_ + } + } + } + + Write-Host "$(Get-Date) - datastore-usage-email operation complete ...`n" + + Write-Host "$(Get-Date) - Handler Processing Completed ...`n" +} diff --git a/examples/knative/powercli/kn-pcli-datastore-usage-email/test/docker-test-env-variable b/examples/knative/powercli/kn-pcli-datastore-usage-email/test/docker-test-env-variable new file mode 100644 index 00000000..ef9d9dd7 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-datastore-usage-email/test/docker-test-env-variable @@ -0,0 +1 @@ +DATASTORE_SECRET={"VCENTER_SERVER":"FILL-ME-IN","VCENTER_USERNAME":"FILL-ME-IN","VCENTER_PASSWORD":"FILL-ME-IN","VCENTER_CERTIFICATE_ACTION":"Fail","VC_ALARM_NAME":"Datastore usage on disk","DATASTORE_NAMES":["Datastore1","Datastore2"],"SMTP_SERVER":"FILL-ME-IN","SMTP_PORT":"FILL-ME-IN","SMTP_USERNAME":"","SMTP_PASSWORD":"","EMAIL_SUBJECT":"[VMC Datastore Notification Alarm]","EMAIL_TO":["FILL-ME-IN"],"EMAIL_FROM":"FILL-ME-IN","DATASTORE_CUSTOM_PROP_EMAIL_TO":""} \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-datastore-usage-email/test/send-cloudevent-test.ps1 b/examples/knative/powercli/kn-pcli-datastore-usage-email/test/send-cloudevent-test.ps1 new file mode 100644 index 00000000..5f51466a --- /dev/null +++ b/examples/knative/powercli/kn-pcli-datastore-usage-email/test/send-cloudevent-test.ps1 @@ -0,0 +1,32 @@ +# The ce-subject value should match the event router subject in function.yaml +$subject = "AlarmStatusChangedEvent" +$payloadPath = "./test-payload.json" + +if ( $args.Count -gt 0 ) { + if ( Test-Path $args[0] ) { + $payloadPath = $args[0] + } + else { + Write-Host "$(Get-Date) - ERROR: Invalid path"$args[0]"`n" + exit + } + + if ( $args.Count -gt 1 ) { + $subject = $args[1] + } +} + +$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" = $($subject); +} +$body = Get-Content -Raw -Path $payloadPath + +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-datastore-usage-email/test/send-cloudevent-test.sh b/examples/knative/powercli/kn-pcli-datastore-usage-email/test/send-cloudevent-test.sh new file mode 100644 index 00000000..591f9251 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-datastore-usage-email/test/send-cloudevent-test.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# The ce-subject value should match the event router subject in function.yaml +echo "Testing Function ..." +PAYLOAD_PATH="test-payload.json" +SUBJECT="AlarmStatusChangedEvent" + +if [ $# -gt 0 ]; then + if test -f "$1"; then + PAYLOAD_PATH=$1 + else + echo "$1 not found" + exit 1 + fi + + if [ $# -gt 1 ]; then + SUBJECT=$2 + fi +fi +curl -d@$PAYLOAD_PATH \ + -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: '$SUBJECT \ + -X POST localhost:8080 + +echo "See docker container console for output" diff --git a/examples/knative/powercli/kn-pcli-datastore-usage-email/test/test-payload.json b/examples/knative/powercli/kn-pcli-datastore-usage-email/test/test-payload.json new file mode 100644 index 00000000..63a790e4 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-datastore-usage-email/test/test-payload.json @@ -0,0 +1,50 @@ +{ + "Key": 952175, + "ChainId": 952175, + "CreatedTime": "2022-04-27T14:14:13.089999Z", + "UserName": "", + "Datacenter": { + "Name": "DataCenter1", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-47" + } + }, + "ComputeResource": null, + "Host": null, + "Vm": null, + "Ds": { + "Name": "ProdDatastore1", + "Datastore": { + "Type": "Datastore", + "Value": "datastore-60" + } + }, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "Alarm 'Datastore usage on disk' on ProdDatstore1 changed from Gray to Yellow", + "ChangeTag": "", + "Alarm": { + "Name": "Datastore usage on disk", + "Alarm": { + "Type": "Alarm", + "Value": "alarm-7" + } + }, + "Source": { + "Name": "Datacenters", + "Entity": { + "Type": "Folder", + "Value": "group-d1" + } + }, + "Entity": { + "Name": "ProdDatastore1", + "Entity": { + "Type": "Datastore", + "Value": "datastore-60" + } + }, + "From": "gray", + "To": "yellow" + } \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-ha-restarted-vms/Dockerfile b/examples/knative/powercli/kn-pcli-ha-restarted-vms/Dockerfile new file mode 100644 index 00000000..49b2c980 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-ha-restarted-vms/Dockerfile @@ -0,0 +1,5 @@ +FROM us.gcr.io/daisy-284300/veba/ce-pcli-base:1.4 + +COPY handler.ps1 handler.ps1 + +CMD ["pwsh","./server.ps1"] diff --git a/examples/knative/powercli/kn-pcli-ha-restarted-vms/README.md b/examples/knative/powercli/kn-pcli-ha-restarted-vms/README.md new file mode 100644 index 00000000..4ed112c8 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-ha-restarted-vms/README.md @@ -0,0 +1,149 @@ +# kn-pcli-ha-restarted-vms +Example Knative PowerCLI function kn-pcli-ha-restarted-vms. This function emails an administrator a list of virtual machines that were restarted by vSphere High Availability (HA) after an HA event. The VM list contains all VMs that were restarted on the date the cluster failover event completes. Example: A failover event happens on Feb. 10th at 8:50AM. The function will email a timestamped list of all VMs that were failed over during the 24 hour period between midnight on Feb. 10th and midnight on Feb. 11th + +# Step 1 - Build + +> **Note:** This step is only required if you made code changes to `handler.ps1` +> or `Dockerfile`. + +Create the container image locally to test your function logic. + +Mac/Linux +``` +# change the IMAGE name accordingly, example below for Docker +export IMAGE=/kn-pcli-ha-restarted-vms:1.0 +docker build -t ${IMAGE} . +``` + +Windows +``` +# change the IMAGE name accordingly, example below for Docker +$IMAGE="/kn-pcli-ha-restarted-vms:1.0" +docker build -t ${IMAGE} . +``` +# 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. +> Any changes to variables must also be updated in `ha_secret.yaml` and `test/docker-test-env-variable` + +* `VCENTER_SERVER` - IP Address or FQDN of the vCenter Server to connect to +* `VCENTER_USERNAME` - vCenter account with permission to reconfigure distributed virtual switches +* `VCENTER_PASSWORD` - vCenter password associated with the username +* `VCENTER_CERTIFCATE_ACTION` - Set-PowerCLIConfiguration Action to configure when connection fails due to certificate error, default is Fail. (Possible values: `Fail`, `Ignore` or `Warn`) +* `SMTP_SERVER` - The SMTP server responsible for relaying the e-mail notification +* `SMTP_PORT` - The port `SMTP_SERVER` is listening on +* `SMTP_USERNAME` - Optional. Username for SMTP authentication +* `SMTP_PASSWORD` - Optional. Username for SMTP authentication +* `EMAIL_TO` - Email address to receive notifications. At least one is required, multiple are allowed +* `EMAIL_FROM` - Email address to send notifications +* `DISPLAY_HOST_FQDN` - `true` or `false` - Set to true if you want the host's fully qualified domain name included in the report + +If you built a custom image in Step 1, comment out the default `IMAGE` command below - the `docker run` command will then use use the value previously stored in the `IMAGE` variable. Otherwise, use the default image as shown below. Start the container image by running the following commands: + +Mac/Linux +```console +export IMAGE=us.gcr.io/daisy-284300/veba/kn-pcli-ha-restarted-vms:1.0 +docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} +``` +Windows +```console +$IMAGE="us.gcr.io/daisy-284300/veba/kn-pcli-ha-restarted-vms:1.0" +docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} +``` + +Unlike many other sample functions, you do not need to edit `test-payload.json` to test your function. However, your vCenter must have logged a recent HA event with VM restart for this function to work. + +One way to simulate an HA event is by forcing a PSOD. You can do this at the command line of a vSphere host. Make sure to have at least one VM running on the host for HA to restart. +> Warning: This command will immediately cause a kernel panic, crashing your host! Do not run this in Production. +```console +vsish -e set /reliability/crashMe/Panic 1 +``` +Wait for HA to restart your VM(s) before continuing. + +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. The scripts will send the contents of `test-payload.json` as the payload with a subject of `com.vmware.vc.HA.ClusterFailoverActionCompletedEvent` + +```console +Testing Function ... +See docker container console for output + +# Output from docker container console +04/25/2022 21:35:20 - PowerShell HTTP server start listening on 'http://*:8080/' +04/25/2022 21:35:20 - Processing Init + +04/25/2022 21:35:20 - Configuring PowerCLI Configuration Settings + +04/25/2022 21:35:21 - Connecting to vCenter Server vcsa.primp-industries.local + +04/25/2022 21:35:25 - Successfully connected to vcsa.primp-industries.local + +04/25/2022 21:35:25 - Init Processing Completed + +04/25/2022 21:35:25 - Starting HTTP CloudEvent listener + +04/25/2022 21:35:29 - DEBUG: From - noreply@vmweventbroker.io +04/25/2022 21:35:29 - DEBUG: To - notifications@vmweventbroker.io +04/25/2022 21:35:29 - Handler Processing Completed ... +``` + +> Pro Tip - If you are rapidly iterating on the code and want to easily rebuild and launch the container, +> you can chain all of the commands together with ampersands. This will allow you to re-run +> the commands by simply pressing the `up` arrow and `Enter`. + +```console +cd .. && docker build -t ${IMAGE} . && cd test && docker run -e HA_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} +``` +# 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. + +If you built a custom image, push it to an accessible registry such as Docker once you're done developing and testing your function logic. + +```console +docker push ${IMAGE} +``` + +Update the `ha_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 `HA_SECRET`. + +```console +# create secret +kubectl -n vmware-functions create secret generic ha-secret --from-file=HA_SECRET=ha_secret.json + +# update label for secret to show up in VEBA UI +kubectl -n vmware-functions label secret ha-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 `com.vmware.vc.HA.ClusterFailoverActionCompletedEvent` vCenter Server Event. + +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 ha-secret +``` diff --git a/examples/knative/powercli/kn-pcli-ha-restarted-vms/function.yaml b/examples/knative/powercli/kn-pcli-ha-restarted-vms/function.yaml new file mode 100644 index 00000000..3b248bf7 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-ha-restarted-vms/function.yaml @@ -0,0 +1,41 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-pcli-ha-restarted-vms + labels: + app: veba-ui +spec: + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" + spec: + containers: + - image: us.gcr.io/daisy-284300/veba/kn-pcli-ha-restarted-vms:1.0 + envFrom: + - secretRef: + name: ha-secret + env: + - name: HA_DEBUG + value: "true" +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-pcli-ha-restarted-vms-trigger + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/eventex + # Replace this subject with the event you need to trigger on + # Then, edit send-cloudevent-test.ps1 and send-cloudevent-test.sh in the /test folder + subject: com.vmware.vc.HA.ClusterFailoverActionCompletedEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-pcli-ha-restarted-vms diff --git a/examples/knative/powercli/kn-pcli-ha-restarted-vms/ha_secret.json b/examples/knative/powercli/kn-pcli-ha-restarted-vms/ha_secret.json new file mode 100644 index 00000000..5a59088d --- /dev/null +++ b/examples/knative/powercli/kn-pcli-ha-restarted-vms/ha_secret.json @@ -0,0 +1,13 @@ +{ + "VCENTER_SERVER": "FILL-ME-IN", + "VCENTER_USERNAME" : "FILL-ME-IN", + "VCENTER_PASSWORD" : "FILL-ME-IN", + "VCENTER_CERTIFICATE_ACTION" : "Fail", + "SMTP_SERVER" : "FILL-ME-IN", + "SMTP_PORT" : "FILL-ME-IN", + "SMTP_USERNAME" : "", + "SMTP_PASSWORD" : "", + "EMAIL_TO": ["FILL-ME-IN"], + "EMAIL_FROM" : "FILL-ME-IN", + "DISPLAY_HOST_FQDN":"true" +} diff --git a/examples/knative/powercli/kn-pcli-ha-restarted-vms/handler.ps1 b/examples/knative/powercli/kn-pcli-ha-restarted-vms/handler.ps1 new file mode 100644 index 00000000..87cb4ffb --- /dev/null +++ b/examples/knative/powercli/kn-pcli-ha-restarted-vms/handler.ps1 @@ -0,0 +1,199 @@ +Function Process-Init { + [CmdletBinding()] + param() + Write-Host "$(Get-Date) - Processing Init`n" + + try { + $jsonSecrets = ${env:HA_SECRET} | ConvertFrom-Json + } + catch { + throw "`nK8s secret `$env:HA_SECRET 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_CERTIFICATE_ACTION = ${jsonSecrets}.VCENTER_CERTIFICATE_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_CERTIFICATE_ACTION} -ParticipateInCeip:$true -Confirm:$false + + Write-Host "$(Get-Date) - Connecting to vCenter Server $VCENTER_SERVER`n" + + try { + Connect-VIServer -Server $VCENTER_SERVER -User $VCENTER_USERNAME -Password $VCENTER_PASSWORD + } + catch { + Write-Error "$(Get-Date) - ERROR: Failed to connect to vCenter Server" + throw $_ + } + + Write-Host "$(Get-Date) - Successfully connected to $VCENTER_SERVER`n" + + Write-Host "$(Get-Date) - Init Processing Completed`n" +} + +Function Process-Shutdown { + [CmdletBinding()] + param() + Write-Host "$(Get-Date) - Processing Shutdown`n" + + Write-Host "$(Get-Date) - Disconnecting from vCenter Server`n" + + try { + Disconnect-VIServer * -Confirm:$false + } + catch { + Write-Error "$(Get-Date) - Error: Failed to Disconnect from vCenter Server" + } + + Write-Host "$(Get-Date) - Shutdown Processing Completed`n" +} + +Function Process-Handler { + [CmdletBinding()] + 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:HA_SECRET} | ConvertFrom-Json + } + catch { + throw "`nK8s secret `$env:HA_SECRET does not look to be defined" + } + + # Main processing of gathering specific events + $report = @() + $eventnumber = 1000 + # get vCenter EventManager + try { + $si = get-view ServiceInstance + } + catch { + Write-Host "$(Get-Date) - ERROR: Could not retrieve Service Instance" + throw $_ + } + + try { + $em = get-view $si.Content.EventManager + } + catch { + Write-Host "$(Get-Date) - ERROR: Could not retrieve Event Manager" + throw $_ + } + + # Create Event Filter Spec + $EventFilterSpec = New-Object VMware.Vim.EventFilterSpec + # Get specific Events based on EventTypeIDs for EventFilterSpec + $tgtEvtTypeIDs = "com.vmware.vc.ha.VmRestartedByHAEvent", "com.vmware.vc.HA.DasHostFailedEvent" + $EventFilterSpec.EventTypeId = $tgtEvtTypeIDs + # Time range to look for specific IDs (current day specified) for EventFilterSpec + $EventFilterSpecByTime = New-Object VMware.Vim.EventFilterSpecByTime + # [datetime]::Today is midnight on the date the ClusterFailoverActionCompletedEvent fired + $EventFilterSpecByTime.BeginTime = [datetime]::Today + # Searches the entire 24 hour period on the date the ClusterFailoverActionCompletedEvent fired + $EventFilterSpecByTime.EndTime = ([datetime]::Today).AddDays(+1) + $EventFilterSpec.Time = $EventFilterSpecByTime + + # Create an Event Collector and loop through events storing them into an array + try { + $eCollector = Get-View ($em.CreateCollectorForEvents($EventFilterSpec)) + } + catch { + Write-Host "$(Get-Date) - ERROR: Could not create event collector" + throw $_ + } + + try { + $events = $eCollector.ReadNextEvents($eventnumber) + } + catch { + Write-Host "$(Get-Date) - ERROR: Could not retrieve events with event collector" + throw $_ + } + + While ($events) { + $events | %{ + $report += $_ + } + try { + $events = $eCollector.ReadNextEvents($eventnumber) + } + catch { + Write-Host "$(Get-Date) - ERROR: Could not retrieve events with event collector" + throw $_ + } + } + + $eCollector.DestroyCollector() + + $SMTP_SERVER = ${jsonSecrets}.SMTP_SERVER + $SMTP_PORT = ${jsonSecrets}.SMTP_PORT + $SMTP_USERNAME = ${jsonSecrets}.SMTP_USERNAME + $SMTP_PASSWORD = ${jsonSecrets}.SMTP_PASSWORD + $EMAIL_TO = ${jsonSecrets}.EMAIL_TO + $EMAIL_FROM = ${jsonSecrets}.EMAIL_FROM + $DISPLAY_HOST_FQDN = ${jsonSecrets}.DISPLAY_HOST_FQDN + + # Retrieve Host name of ESXi host which crashed - used for email report + if ($report | Where-Object{$_.EventTypeId -eq "com.vmware.vc.HA.DasHostFailedEvent"}) { + if ($DISPLAY_HOST_FQDN -eq $false){ + # Displays just hostname - strips FQDN + $vmHost = ($report | Where-Object {$_.EventTypeId -eq "com.vmware.vc.HA.DasHostFailedEvent"} | Select-Object ObjectName -ExpandProperty ObjectName -First 1).split(".")[0] + } else { + # Displays host FQDN + $vmHost = $report | Where-Object {$_.EventTypeId -eq "com.vmware.vc.HA.DasHostFailedEvent"} | Select-Object ObjectName -ExpandProperty ObjectName -First 1 + } + } + + # Set up fields for email body and send email - only sending VMname, time VM restarted on another host, and VM description + $output = $report | Where-Object {$_.ObjectType -eq "VirtualMachine" } | Select-Object ObjectName, @{N = "Date"; E = { $_.CreatedTime } }, @{N = "Description"; E = { (" - " + (Get-view -id $_.vm.vm).config.annotation | Out-String) } } | Sort-Object ObjectName + $msgBody = $output | ConvertTo-Html | Out-String + $subject = "** $vmHost Failure - VMs Restarted **" + + if (${env:HA_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: From - $($EMAIL_FROM)" + Write-Host "$(Get-Date) - DEBUG: To - $($EMAIL_TO)" + } + + Write-Host "$(Get-Date) - Sending notification for host $($vmHost)" + + # If defined in the config file, send via authenticated SMTP, otherwise use standard SMTP + if ($SMTP_PASSWORD.length -gt 0 -and $SMTP_USERNAME.length -gt 0) + { + $password = ConvertTo-SecureString "$($SMTP_PASSWORD)" -AsPlainText -Force + $credential = New-Object System.Management.Automation.PSCredential($($SMTP_USERNAME), $password) + try { + Send-MailMessage -From $($EMAIL_FROM) -to $($EMAIL_TO) -Subject $subject -Body $msgBody -BodyAsHtml -SmtpServer $($SMTP_SERVER) -port $($SMTP_PORT) -UseSsl -Credential $credential #-Encoding UTF32 + } + catch { + Write-Host "$(Get-Date) - ERROR: Could not send authenticated email" + throw $_ + } + } + else + { + try { + Send-MailMessage -From $($EMAIL_FROM) -to $($EMAIL_TO) -Subject $subject -Body $msgBody -BodyAsHtml -SmtpServer $($SMTP_SERVER) -port $($SMTP_PORT) -Encoding UTF32 + } + catch { + Write-Host "$(Get-Date) - ERROR: Could not send email" + throw $_ + } + } + + Write-Host "$(Get-Date) - Handler Processing Completed ...`n" +} \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-ha-restarted-vms/test/docker-test-env-variable b/examples/knative/powercli/kn-pcli-ha-restarted-vms/test/docker-test-env-variable new file mode 100644 index 00000000..e3d584e7 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-ha-restarted-vms/test/docker-test-env-variable @@ -0,0 +1 @@ +HA_SECRET={"VCENTER_SERVER":"FILL-ME-IN","VCENTER_USERNAME":"FILL-ME-IN","VCENTER_PASSWORD":"FILL-ME-IN","VCENTER_CERTIFICATE_ACTION":"Fail","SMTP_SERVER":"FILL-ME-IN","SMTP_PORT":"25","SMTP_USERNAME":"","SMTP_PASSWORD":"","EMAIL_TO":["email_recipients@abc.com"],"EMAIL_FROM":"email_sender@abc.com","DISPLAY_HOST_FQDN":"false"} \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-ha-restarted-vms/test/send-cloudevent-test.ps1 b/examples/knative/powercli/kn-pcli-ha-restarted-vms/test/send-cloudevent-test.ps1 new file mode 100644 index 00000000..bce26745 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-ha-restarted-vms/test/send-cloudevent-test.ps1 @@ -0,0 +1,32 @@ +# The ce-subject value should match the event router subject in function.yaml +$subject = "com.vmware.vc.HA.ClusterFailoverActionCompletedEvent" +$payloadPath = "./test-payload.json" + +if ( $args.Count -gt 0 ) { + if ( Test-Path $args[0] ) { + $payloadPath = $args[0] + } + else { + Write-Host "$(Get-Date) - ERROR: Invalid path"$args[0]"`n" + exit + } + + if ( $args.Count -gt 1 ) { + $subject = $args[1] + } +} + +$headers = @{ + "Content-Type" = "application/json"; + "ce-specversion" = "1.0"; + "ce-id" = "id-123"; + "ce-source" = "source-123"; + "ce-type" = "com.vmware.event.router/eventex"; + "ce-subject" = $($subject); +} +$body = Get-Content -Raw -Path $payloadPath + +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-ha-restarted-vms/test/send-cloudevent-test.sh b/examples/knative/powercli/kn-pcli-ha-restarted-vms/test/send-cloudevent-test.sh new file mode 100644 index 00000000..09a183d2 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-ha-restarted-vms/test/send-cloudevent-test.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# The ce-subject value should match the event router subject in function.yaml +echo "Testing Function ..." +PAYLOAD_PATH="test-payload.json" +SUBJECT="com.vmware.vc.HA.ClusterFailoverActionCompletedEvent" + +if [ $# -gt 0 ]; then + if test -f "$1"; then + PAYLOAD_PATH=$1 + else + echo "$1 not found" + exit 1 + fi + + if [ $# -gt 1 ]; then + SUBJECT=$2 + fi +fi +curl -d@$PAYLOAD_PATH \ + -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/eventex' \ + -H 'ce-subject: '$SUBJECT \ + -X POST localhost:8080 + +echo "See docker container console for output" diff --git a/examples/knative/powercli/kn-pcli-ha-restarted-vms/test/test-payload.json b/examples/knative/powercli/kn-pcli-ha-restarted-vms/test/test-payload.json new file mode 100644 index 00000000..dc1a028d --- /dev/null +++ b/examples/knative/powercli/kn-pcli-ha-restarted-vms/test/test-payload.json @@ -0,0 +1,35 @@ +{ + "Key": 929015, + "ChainId": 929015, + "CreatedTime": "2022-04-25T17:29:48.171999Z", + "UserName": "", + "Datacenter": { + "Name": "DC1", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-47" + } + }, + "ComputeResource": { + "Name": "CL1", + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c52" + } + }, + "Host": null, + "Vm": null, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "vSphere HA completed a virtual machine failover action in cluster CL1 in datacenter DC1", + "ChangeTag": "", + "EventTypeId": "com.vmware.vc.HA.ClusterFailoverActionCompletedEvent", + "Severity": "info", + "Message": "", + "Arguments": null, + "ObjectId": "domain-c52", + "ObjectType": "ClusterComputeResource", + "ObjectName": "CL1", + "Fault": null + } \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-hostmaint-alarms/Dockerfile b/examples/knative/powercli/kn-pcli-hostmaint-alarms/Dockerfile new file mode 100644 index 00000000..49b2c980 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-hostmaint-alarms/Dockerfile @@ -0,0 +1,5 @@ +FROM us.gcr.io/daisy-284300/veba/ce-pcli-base:1.4 + +COPY handler.ps1 handler.ps1 + +CMD ["pwsh","./server.ps1"] diff --git a/examples/knative/powercli/kn-pcli-hostmaint-alarms/README.md b/examples/knative/powercli/kn-pcli-hostmaint-alarms/README.md new file mode 100644 index 00000000..7f696071 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-hostmaint-alarms/README.md @@ -0,0 +1,175 @@ +# kn-pcli-hostmaint-alarms +Example Knative PowerCLI function kn-pcli-hostmaint-alarms. This function will disable host alarm actions when a host is placed in maintenance mode, and enable host alarm actions when a host is removed from maintenance mode. + +# Step 1 - Build + +> **Note:** This step is only required if you made code changes to `handler.ps1` +> or `Dockerfile`. + +Create the container image locally to test your function logic. + +Mac/Linux +``` +# change the IMAGE name accordingly, example below for Docker +export IMAGE=/kn-pcli-hostmaint-alarms:1.0 +docker build -t ${IMAGE} . +``` + +Windows +``` +# change the IMAGE name accordingly, example below for Docker +$IMAGE="/kn-pcli-hostmaint-alarms:1.0" +docker build -t ${IMAGE} . +``` +# 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. +> Note - The sample variables are built for a vCenter function. You will need to replace them if you are authoring a function for a different purpose. +> Any changes to variables must also be updated in `hostmaint_secret.yaml` and `test/docker-test-env-variable` + +* `VCENTER_SERVER` - IP Address or FQDN of the vCenter Server to connect to +* `VCENTER_USERNAME` - vCenter account with permission to reconfigure distributed virtual switches +* `VCENTER_PASSWORD` - vCenter password associated with the username +* `VCENTER_CERTIFCATE_ACTION` - Set-PowerCLIConfiguration Action to configure when connection fails due to certificate error, default is Fail. (Possible values: Fail, Ignore or Warn) + +If you built a custom image in Step 1, comment out the default `IMAGE` command below - the `docker run` command will then use use the value previously stored in the `IMAGE` variable. Otherwise, use the default image as shown below. Start the container image by running the following commands: + +Mac/Linux +```console +export IMAGE=us.gcr.io/daisy-284300/veba/kn-pcli-hostmaint-alarms:1.0 +docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} +``` +Windows +```console +$IMAGE="us.gcr.io/daisy-284300/veba/kn-pcli-hostmaint-alarms:1.0" +docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} +``` + +--- +This function has two sample payload files: `enter-maint-payload.json` and `exit-maint-payload.json`. Locate the `Host` section of each file: +```json + "Host": { + "Name": "esx02.cl.vmweventbroker.io", + "Host": { + "Type": "HostSystem", + "Value": "REPLACE-ME" + } + }, +``` +Change `Value:` from `REPLACE-ME` to the ID of a host currently in your vCenter inventory. If you do not make this change, the function will still be invoked, but the configuration operation will fail because the host will not be found. The function output will also make more sense if you change `Name:` to match the name of your host. The function will still work without changing the name. + +One way to figure out a host's ID is with this PowerCLI command: +```powershell +(Get-VMHost "esx02.cl.vmweventbroker.io").id +HostSystem-host-58 +``` + +Based on the previous example host ID, the JSON should look like this: + +```json + "Host": { + "Name": "esx02.cl.vmweventbroker.io", + "Host": { + "Type": "HostSystem", + "Value": "host-58" + } + }, +``` + +**WARNING** - This function will reconfigure alarm actions on the host you specify. +--- + +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. When run with no arguments, the scripts will send the contents of `enter-maint-payload.json` as the payload and an event subject of `EnteredMaintenanceModeEvent`. + +This is an example of running a test for entering maintenance mode. +```console +> .\send-cloudevent-test.ps1 +Testing Function ... +See docker container console for output +``` +```console +# Output from docker container console +04/21/2022 13:52:51 - DEBUG: Event - EnteredMaintenanceModeEvent +04/21/2022 13:52:52 - Disabling alarm actions on host: esx02.cl.vmweventbroker.io +04/21/2022 13:52:52 - kn-pcli-hostmaint-alarms operation complete ... +``` + +To simulate an exit maintenance mode event, pass the JSON file name along with the exit event name . Example: `./send-cloudevent-test.ps1 ./exit-maint-payload.json ExitMaintenanceModeEvent`. + +```console +> ./send-cloudevent-test.ps1 ./exit-maint-payload.json ExitMaintenanceModeEvent +Testing Function ... +See docker container console for output +``` + +```console +# Output from docker container console +04/21/2022 14:01:31 - DEBUG: Event - ExitMaintenanceModeEvent +04/21/2022 14:01:31 - Enabling alarm actions on host: esx02.cl.vmweventbroker.io +04/21/2022 14:01:31 - kn-pcli-hostmaint-alarms operation complete ... +``` + +--- + +> Pro Tip - If you are rapidly iterating on the code and want to easily rebuild and launch the container, +> you can chain all of the commands together with ampersands. This will allow you to re-run +> the commands by simply pressing the `up` arrow and `Enter`. + +```console +cd .. && docker build -t ${IMAGE} . && cd test && docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} +``` +# 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. + +If you built a custom image, push it to an accessible registry such as Docker once you're done developing and testing your function logic. + +```console +docker push ${IMAGE} +``` + +Update the `hostmaint_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 `HOSTMAINT_SECRET`. + +```console +# create secret +kubectl -n vmware-functions create secret generic hostmaint-secret --from-file=HOSTMAINT_SECRET=hostmaint_secret.json + +# update label for secret to show up in VEBA UI +kubectl -n vmware-functions label secret hostmaint-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 `EnteredMaintenanceModeEvent` and `ExitMaintenanceModeEvent` vCenter Server events. 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 hostmaint-secret +``` diff --git a/examples/knative/powercli/kn-pcli-hostmaint-alarms/function.yaml b/examples/knative/powercli/kn-pcli-hostmaint-alarms/function.yaml new file mode 100644 index 00000000..5b946227 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-hostmaint-alarms/function.yaml @@ -0,0 +1,61 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-pcli-hostmaint-alarms + labels: + app: veba-ui +spec: + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" + spec: + containers: + - image: us.gcr.io/daisy-284300/veba/kn-pcli-hostmaint-alarms:1.0 + envFrom: + - secretRef: + name: hostmaint-secret + env: + - name: FUNCTION_DEBUG + value: "true" +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-pcli-hostmaint-alarms-trigger-exit + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + # Replace this subject with the event you need to trigger on + # Then, edit send-cloudevent-test.ps1 and send-cloudevent-test.sh in the /test folder + subject: ExitMaintenanceModeEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-pcli-hostmaint-alarms +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-pcli-hostmaint-alarms-trigger-enter + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + # Replace this subject with the event you need to trigger on + # Then, edit send-cloudevent-test.ps1 and send-cloudevent-test.sh in the /test folder + subject: EnteredMaintenanceModeEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-pcli-hostmaint-alarms \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-hostmaint-alarms/handler.ps1 b/examples/knative/powercli/kn-pcli-hostmaint-alarms/handler.ps1 new file mode 100644 index 00000000..96b6a43e --- /dev/null +++ b/examples/knative/powercli/kn-pcli-hostmaint-alarms/handler.ps1 @@ -0,0 +1,129 @@ +Function Process-Init { + [CmdletBinding()] + param() + Write-Host "$(Get-Date) - Processing Init`n" + + try { + $jsonSecrets = ${env:HOSTMAINT_SECRET} | ConvertFrom-Json + } + catch { + throw "`nK8s secret `$env:HOSTMAINT_SECRET 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_CERTIFICATE_ACTION = ${jsonSecrets}.VCENTER_CERTIFICATE_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_CERTIFICATE_ACTION} -ParticipateInCeip:$true -Confirm:$false + + Write-Host "$(Get-Date) - Connecting to vCenter Server $VCENTER_SERVER`n" + + try { + Connect-VIServer -Server $VCENTER_SERVER -User $VCENTER_USERNAME -Password $VCENTER_PASSWORD + } + catch { + Write-Error "$(Get-Date) - ERROR: Failed to connect to vCenter Server" + throw $_ + } + + Write-Host "$(Get-Date) - Successfully connected to $VCENTER_SERVER`n" + + Write-Host "$(Get-Date) - Init Processing Completed`n" +} + +Function Process-Shutdown { + [CmdletBinding()] + param() + Write-Host "$(Get-Date) - Processing Shutdown`n" + + Write-Host "$(Get-Date) - Disconnecting from vCenter Server`n" + + try { + Disconnect-VIServer * -Confirm:$false + } + catch { + Write-Error "$(Get-Date) - Error: Failed to Disconnect from vCenter Server" + } + + Write-Host "$(Get-Date) - Shutdown Processing Completed`n" +} + +Function Process-Handler { + [CmdletBinding()] + 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:HOSTMAINT_SECRET} | ConvertFrom-Json + } + catch { + throw "`nK8s secret `$env:HOSTMAINT_SECRET does not look to be defined" + } + + if (${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: Event - $($cloudEvent.subject)" + } + $hostName = $cloudEventData.Host.Name + $moRef = New-Object VMware.Vim.ManagedObjectReference + $moRef.Type = $cloudEventData.Host.Host.Type + $moRef.Value = $cloudEventData.Host.Host.Value + + try { + $hostMoRef = Get-View $moRef + } + catch { + Write-Host "$(Get-Date) - ERROR: Could not retrieve host view by moRef" + throw $_ + } + + try { + $alarmManager = Get-View AlarmManager + } + catch { + Write-Host "$(Get-Date) - ERROR: Could not retrieve AlarmManager object" + throw $_ + } + + if ($cloudEvent.subject -eq "EnteredMaintenanceModeEvent") { + # Disable alarm actions on the host + Write-Host "$(Get-Date) - Disabling alarm actions on host: $hostName" + try { + $alarmManager.EnableAlarmActions($hostMoRef.MoRef, $false) + } + catch { + Write-Host "$(Get-Date) - ERROR: Could not disable alarm actions" + throw $_ + } + } + + if ($cloudEvent.subject -eq "ExitMaintenanceModeEvent") { + # Enable alarm actions on the host + Write-Host "$(Get-Date) - Enabling alarm actions on host: $hostName" + try { + $alarmManager.EnableAlarmActions($hostMoRef.MoRef, $true) + } + catch { + Write-Host "$(Get-Date) - ERROR: Could not enable alarm actions" + throw $_ + } + } + + Write-Host "$(Get-Date) - kn-pcli-hostmaint-alarms operation complete ...`n" + + Write-Host "$(Get-Date) - Handler Processing Completed ...`n" +} diff --git a/examples/knative/powercli/kn-pcli-hostmaint-alarms/hostmaint_secret.json b/examples/knative/powercli/kn-pcli-hostmaint-alarms/hostmaint_secret.json new file mode 100644 index 00000000..b45d68aa --- /dev/null +++ b/examples/knative/powercli/kn-pcli-hostmaint-alarms/hostmaint_secret.json @@ -0,0 +1,6 @@ +{ + "VCENTER_SERVER": "FILL-ME-IN", + "VCENTER_USERNAME" : "FILL-ME-IN", + "VCENTER_PASSWORD" : "FILL-ME-IN", + "VCENTER_CERTIFICATE_ACTION" : "Ignore" +} diff --git a/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/docker-test-env-variable b/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/docker-test-env-variable new file mode 100644 index 00000000..d181a22f --- /dev/null +++ b/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/docker-test-env-variable @@ -0,0 +1 @@ +HOSTMAINT_SECRET={"VCENTER_SERVER":"FILL-ME-IN","VCENTER_USERNAME":"FILL-ME-IN","VCENTER_PASSWORD":"FILL-ME-IN","VCENTER_CERTIFICATE_ACTION":"Ignore"} \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/enter-maint-payload.json b/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/enter-maint-payload.json new file mode 100644 index 00000000..1c9152a7 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/enter-maint-payload.json @@ -0,0 +1,33 @@ +{ + "Key": 910093, + "ChainId": 910066, + "CreatedTime": "2022-04-20T15:47:32.365Z", + "UserName": "VSPHERE.LOCAL\\Administrator", + "Datacenter": { + "Name": "HomeLab", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-47" + } + }, + "ComputeResource": { + "Name": "CL1", + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c52" + } + }, + "Host": { + "Name": "esx02.cl.vmweventbroker.io", + "Host": { + "Type": "HostSystem", + "Value": "REPLACE-ME" + } + }, + "Vm": null, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "Host esx02.cl.vmweventbroker.io in HomeLab has entered maintenance mode", + "ChangeTag": "" + } \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/exit-maint-payload.json b/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/exit-maint-payload.json new file mode 100644 index 00000000..6801c44e --- /dev/null +++ b/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/exit-maint-payload.json @@ -0,0 +1,33 @@ +{ + "Key": 910100, + "ChainId": 910098, + "CreatedTime": "2022-04-20T15:48:39.140999Z", + "UserName": "VSPHERE.LOCAL\\Administrator", + "Datacenter": { + "Name": "HomeLab", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-47" + } + }, + "ComputeResource": { + "Name": "CL1", + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c52" + } + }, + "Host": { + "Name": "esx02.cl.vmweventbroker.io", + "Host": { + "Type": "HostSystem", + "Value": "REPLACE-ME" + } + }, + "Vm": null, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "Host esx02.cl.vmweventbroker.io in HomeLab has exited maintenance mode", + "ChangeTag": "" + } \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/send-cloudevent-test.ps1 b/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/send-cloudevent-test.ps1 new file mode 100644 index 00000000..2b594ef5 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/send-cloudevent-test.ps1 @@ -0,0 +1,32 @@ +# The ce-subject value should match the event router subject in function.yaml +$subject = "EnteredMaintenanceModeEvent" +$payloadPath = "./enter-maint-payload.json" + +if ( $args.Count -gt 0 ) { + if ( Test-Path $args[0] ) { + $payloadPath = $args[0] + } + else { + Write-Host "$(Get-Date) - ERROR: Invalid path"$args[0]"`n" + exit + } + + if ( $args.Count -gt 1 ) { + $subject = $args[1] + } +} + +$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" = $($subject); +} +$body = Get-Content -Raw -Path $payloadPath + +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-hostmaint-alarms/test/send-cloudevent-test.sh b/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/send-cloudevent-test.sh new file mode 100644 index 00000000..d0944fe8 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-hostmaint-alarms/test/send-cloudevent-test.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# The ce-subject value should match the event router subject in function.yaml +echo "Testing Function ..." +PAYLOAD_PATH="enter-maint-payload.json" +SUBJECT="EnteredMaintenanceModeEvent" + +if [ $# -gt 0 ]; then + if test -f "$1"; then + PAYLOAD_PATH=$1 + else + echo "$1 not found" + exit 1 + fi + + if [ $# -gt 1 ]; then + SUBJECT=$2 + fi +fi +curl -d@$PAYLOAD_PATH \ + -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: '$SUBJECT \ + -X POST localhost:8080 + +echo "See docker container console for output" diff --git a/examples/knative/powercli/kn-pcli-nsx-tag-sync/README.md b/examples/knative/powercli/kn-pcli-nsx-tag-sync/README.md index 575cf2c7..91df477d 100644 --- a/examples/knative/powercli/kn-pcli-nsx-tag-sync/README.md +++ b/examples/knative/powercli/kn-pcli-nsx-tag-sync/README.md @@ -11,7 +11,7 @@ Create the container image locally to test your function logic. ``` # change the IMAGE name accordingly -export IMAGE=us.gcr.io/daisy-284300/veba/kn-pcli-nsx-tag-sync:1.0 +export IMAGE=us.gcr.io/daisy-284300/veba/kn-pcli-nsx-tag-sync:1.2 docker build -t ${IMAGE} . ``` @@ -42,7 +42,7 @@ Update the following variable names within the `docker-test-env-variable` file Start the container image by running the following command: ```console -export IMAGE=us.gcr.io/daisy-284300/veba/kn-pcli-nsx-tag-sync:1.0 +export IMAGE=us.gcr.io/daisy-284300/veba/kn-pcli-nsx-tag-sync:1.2 docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} ``` @@ -160,7 +160,7 @@ done developing and testing your function logic. > or `Dockerfile`. ```console -export IMAGE=us.gcr.io/daisy-284300/veba/kn-pcli-nsx-tag-sync:1.0 +export IMAGE=us.gcr.io/daisy-284300/veba/kn-pcli-nsx-tag-sync:1.2 docker push ${IMAGE} ``` diff --git a/examples/knative/powercli/kn-pcli-nsx-tag-sync/function.yaml b/examples/knative/powercli/kn-pcli-nsx-tag-sync/function.yaml index 174b8c94..bfe0e466 100644 --- a/examples/knative/powercli/kn-pcli-nsx-tag-sync/function.yaml +++ b/examples/knative/powercli/kn-pcli-nsx-tag-sync/function.yaml @@ -12,7 +12,7 @@ spec: autoscaling.knative.dev/minScale: "1" spec: containers: - - image: us.gcr.io/daisy-284300/veba/kn-pcli-nsx-tag-sync:1.0 + - image: us.gcr.io/daisy-284300/veba/kn-pcli-nsx-tag-sync:1.2 envFrom: - secretRef: name: tag-secret diff --git a/examples/knative/powercli/kn-pcli-nsx-tag-sync/handler.ps1 b/examples/knative/powercli/kn-pcli-nsx-tag-sync/handler.ps1 index e0c1d513..f5141d3b 100644 --- a/examples/knative/powercli/kn-pcli-nsx-tag-sync/handler.ps1 +++ b/examples/knative/powercli/kn-pcli-nsx-tag-sync/handler.ps1 @@ -98,15 +98,22 @@ Function Process-Handler { } $arguments = $cloudEventData.Arguments | Out-String - Write-Host "$(Get-Date) - DEBUG: CloudEventDataArguments:`n $arguments" - Write-Host "$(Get-Date) - DEBUG: VM name: $vmname" + if (${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: CloudEventDataArguments:`n $arguments" + Write-Host "$(Get-Date) - DEBUG: VM name: $vmname" + } # Get VM object from vCenter try { $vm = Get-VM -name $vmname | Select-Object Name, PersistentId } catch { - Write-Host "$(Get-Date) - ERROR: unable to retrieve VM object" + Write-Host "$(Get-Date) - WARNING: unable to retrieve VM object. Error category: $($error.exception[0].errorcategory)" + # This error will occur if an object other than a VM was tagged. We only care about VM tags, so gracefully return + if ($error.exception[0].errorcategory -eq "ObjectNotFound") { + Write-Host "$(Get-Date) - WARNING: $($vmname) was not found via Get-VM" + return + } throw $_ } @@ -179,18 +186,23 @@ Function Process-Handler { # POST to NSX try { - $response = "" + if (${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: NSX Body=$($nsxBody)" + } + + $encodednsxbody = [System.Text.Encoding]::UTF8.GetBytes($nsxbody) + if ($NSX_SKIP_CERT_CHECK -eq "true") { - $response = Invoke-Webrequest -Uri $nsxUrl -Method POST -Headers $headers -SkipHeaderValidation -Body $nsxbody -SkipCertificateCheck + $response = Invoke-Webrequest -Uri $nsxUrl -Method POST -Headers $headers -SkipHeaderValidation -Body $encodednsxbody -ContentType "application/json; charset=utf-8" -SkipCertificateCheck } else { - $response = Invoke-Webrequest -Uri $nsxUrl -Method POST -Headers $headers -SkipHeaderValidation -Body $nsxbody + $response = Invoke-Webrequest -Uri $nsxUrl -Method POST -Headers $headers -SkipHeaderValidation -Body $encodednsxbody -ContentType "application/json; charset=utf-8" } - + if (${env:FUNCTION_DEBUG} -eq "true") { - Write-Host "$(Get-Date) - DEBUG: Invoke-WebRequest response=$($response)" + Write-Host "$(Get-Date) - DEBUG: Invoke-WebRequest response code=$($response.StatusCode)" } - + Write-Host "$(Get-Date) - vSphere Tag to NSX Operation complete" Write-Host "$(Get-Date) - Handler Processing complete" } diff --git a/examples/knative/powercli/kn-pcli-pg-check/Dockerfile b/examples/knative/powercli/kn-pcli-pg-check/Dockerfile new file mode 100644 index 00000000..49b2c980 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-pg-check/Dockerfile @@ -0,0 +1,5 @@ +FROM us.gcr.io/daisy-284300/veba/ce-pcli-base:1.4 + +COPY handler.ps1 handler.ps1 + +CMD ["pwsh","./server.ps1"] diff --git a/examples/knative/powercli/kn-pcli-pg-check/README.md b/examples/knative/powercli/kn-pcli-pg-check/README.md new file mode 100644 index 00000000..26ea34fa --- /dev/null +++ b/examples/knative/powercli/kn-pcli-pg-check/README.md @@ -0,0 +1,297 @@ +# kn-pcli-pg-check +Example Knative PowerCLI function kn-pcli-pg-check + +This function creates a Slack notification when VM network portgroups are out of compliance. The original intent was to ensure that VMs tagged as PCI VMs remain on portgroups tagged as PCI portgroups. Any time the VM is edited and a portgroup is changed to a non-PCI portgroup, the function alerts via Slack. + +# Step 1 - Build + +> **Note:** This step is only required if you made code changes to `handler.ps1` +> or `Dockerfile`. + +Create the container image locally to test your function logic. + +Mac/Linux +``` +# change the IMAGE name accordingly, example below for Docker +export IMAGE=/kn-pcli-pg-check:1.0 +docker build -t ${IMAGE} . +``` + +Windows +``` +# change the IMAGE name accordingly, example below for Docker +$IMAGE="/kn-pcli-pg-check:1.0" +docker build -t ${IMAGE} . +``` +# Step 2 - Test + +## Configure your vCenter + +In order for this function to work, you must assign at least one vCenter tag to a virtual machine, +and one vCenter tag to a virtual network. +This function supports multiple tags. You can configure your tags in any way suitable for your +environment. You do not have to configure multiple portgroups, nor a mix of standard and +distributed portgroups. +This function was tested with the folowing configuration: + +Tags +- New Tag Category named `PCI` +- New Tag named `PCI-VM` in Category `PCI` +- New Tag named `PCI-VM2` in Category `PCI` +- New Tag named `PCI-Network` in Category `PCI` + +Port Groups +- New Distributed Virtual Portgroup named `DVS_PG_PCI_01`, tagged with `PCI/PCI-Network` +- New Distributed Virtual Portgroup named `DVS_PG_PCI_02`, tagged with `PCI/PCI-Network` +- New Distributed Virtual Portgroup named `DVS_PG_NOT_PCI`, no vSphere tag +- Existing Standard Virtual Portgroup named `VSS_VM_VLAN_203`, no vSphere tag + +Virtual Machine +- New Virtual Machine named `tinycore-2`, tagged with `PCI/PCI-VM` +- Network Adapter 1 assigned to `DVS_PG_PCI_01` +- Network Adapter 2 assigned to `VSS_VM_VLAN_203` +- Network Adapter 3 assigned to `DVS_PG_NOT_PCI` +## Test your container image + +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 +* `VCENTER_USERNAME` - vCenter account with permission to reconfigure distributed virtual switches +* `VCENTER_PASSWORD` - vCenter password associated with the username +* `VCENTER_CERTIFCATE_ACTION` - Set-PowerCLIConfiguration Action to configure when connection fails due to certificate error, default is Fail. (Possible values: Fail, Ignore or Warn) +* `VM_WATCH_TAGS` - vCenter tags indicating a VM belongs on the PCI segment. In this example, `PCI/PCI-VM` and `PCI/PCI-VM2`. +* `PG_WATCH_TAGS` - vCenter tags indicating a portgroup is a valid PCI segment. In this example, `PCI/PCI-Network` and `PCI/PCI-Network2` +* `SLACK_WEBHOOK_URL` - Slack webhook URL + +If you built a custom image in Step 1, comment out the default `IMAGE` command below - the `docker run` command will then use use the value previously stored in the `IMAGE` variable. Otherwise, use the default image as shown below. Start the container image by running the following commands: + +Mac/Linux +```console +export IMAGE=us.gcr.io/daisy-284300/veba/kn-pg-check:1.0 +docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} +``` +Windows +```console +$IMAGE="us.gcr.io/daisy-284300/veba/kn-pcli-pg-check:1.0" +docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} +``` +# Configure the payload file + +In the `test` directory, edit `test-payload.json`. Locate the `Vm` section of the JSON file. Change the `Name:` property from `REPLACE-ME` to the name of a the VM you configured above. Locate the `Vm.Vm` section of the JSON file. Change the `Value` property to the object ID of the VM. One way to retrieve the object ID is using the PowerCLI `Get-VM` cmdlet + +```powershell +C:\> (Get-VM "tinycore-2").id +VirtualMachine-vm-6001 +``` + +```json + "Vm": { + "Name": "REPLACE-ME", + "Vm": { + "Type": "VirtualMachine", + "Value": "REPLACE-ME" + } +``` +For our example VM named `tinycore-2`, the relevant JSON looks like this: +```json + "Vm": { + "Name": "tinycore-2", + "Vm": { + "Type": "VirtualMachine", + "Value": "vm-6001" + } +``` + +If you do not make this change, the function will fail because it will be unable to locate the virtual machine in inventory. + +--- + +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. When run with no arguments, the scripts will send the contents of `test-payload.json` as the payload. If you pass the scripts a different filename as an argument, they will send the contents of the specified file instead. Example: `send-cloudevent-test.ps1 test-payload2.json`. This testing technique is useful when writing complex functions with varying payloads. + +```console +Testing Function ... +See docker container console for output + +# Output from docker container console +03/03/2022 22:23:05 - PowerShell HTTP server start listening on 'http://*:8080/' +03/03/2022 22:23:05 - Processing Init + +03/03/2022 22:23:05 - Configuring PowerCLI Configuration Settings +03/03/2022 22:23:06 - Connecting to vCenter Server vc02.lab.local + +IsConnected : True +Id : /VIServer=vsphere.local\administrator@vc02.lab.local:443/ +ServiceUri : https://vc02.lab.local/sdk +SessionSecret : "58d1ce3755ad6f35af5135c1139e03ea52898d5a" +Name : vc02.lab.local +Port : 443 +SessionId : "58d1ce3755ad6f35af5135c1139e03ea52898d5a" +User : VSPHERE.LOCAL\Administrator +Uid : /VIServer=vsphere.local\administrator@vc02.lab.local:443/ +Version : 7.0.2 +Build : 17920168 +ProductLine : vpx +InstanceUuid : 9db92255-bc00-4e91-9777-0e37928c5771 +RefCount : 1 +ExtensionData : VMware.Vim.ServiceInstance + +03/03/2022 22:23:08 - Successfully connected to vc02.lab.local + +03/03/2022 22:23:08 - Init Processing Completed + +03/03/2022 22:23:08 - Starting HTTP CloudEvent listener +03/03/2022 22:23:10 - DEBUG: ConfigSpec.DeviceChange Is Null? False + +03/03/2022 22:23:10 - DEBUG: Vm.Vm.Type Is Null? False + +03/03/2022 22:23:10 - DEBUG: Vm.Vm.Value Is Null? False + +03/03/2022 22:23:10 - DEBUG: vmID is VirtualMachine-vm-6001 + +03/03/2022 22:23:10 - Retrieving tags on tinycore-2 + +03/03/2022 22:23:12 - DEBUG: Tags found on VM: PCI/PCI-VM TestBedVMs/TestBed1 + +03/03/2022 22:23:12 - DEBUG: Tags to monitor: PCI/PCI-VM PCI/PCI-VM2 + +03/03/2022 22:23:12 - DEBUG: Comparing VM tag: PCI/PCI-VM on VM tinycore-2 + +03/03/2022 22:23:12 - DEBUG: Comparing watch tag: PCI/PCI-VM + +03/03/2022 22:23:12 - DEBUG: Match found for: PCI/PCI-VM, breaking out of loop + +03/03/2022 22:23:12 - Match found for tinycore-2, checking portgroups + +03/03/2022 22:23:12 - DEBUG: VM tinycore-2 - NIC Network adapter 1 - PortGroup DVS_PG_PCI_01 + +03/03/2022 22:23:12 - DEBUG: DVS Port Group Key is dvportgroup-8004 + +03/03/2022 22:23:12 - DEBUG: Virtual Network ID: DistributedVirtualPortgroup-dvportgroup-8004 + +03/03/2022 22:23:12 - DEBUG: PortGroup Tag: PCI/PCI-Network + +03/03/2022 22:23:12 - INFO: Found a match on PCI/PCI-Network + +03/03/2022 22:23:12 - DEBUG: VM tinycore-2 - NIC Network adapter 2 - PortGroup VSS_VM_VLAN_203 + +03/03/2022 22:23:12 - DEBUG: VSS Backing network is Network-network-63 + +03/03/2022 22:23:12 - DEBUG: Virtual Network ID: Network-network-63 + +03/03/2022 22:23:12 - DEBUG: VM tinycore-2 - NIC Network adapter 3 - PortGroup DVS_PG_NOT_PCI + +03/03/2022 22:23:12 - DEBUG: DVS Port Group Key is dvportgroup-9002 + +03/03/2022 22:23:12 - DEBUG: Virtual Network ID: DistributedVirtualPortgroup-dvportgroup-9002 + +03/03/2022 22:23:12 - DEBUG: PortGroup Tag: PCI/Non-PCI-Networks + +03/03/2022 22:23:12 - INFO: No permitted tags were found on the portgroup + +03/03/2022 22:23:12 - NICs using unapproved portgroups: + +Network adapter 2 VSS_VM_VLAN_203 +Network adapter 3 DVS_PG_NOT_PCI +03/03/2022 22:23:12 - DEBUG: "{ + "attachments": [ + { + "pretext": "Virtual Machine - Portgroup Alert", + "fields": [ + { + "short": "false", + "title": "EventType", + "value": "VmReconfiguredEvent" + }, + { + "short": "false", + "title": "Username", + "value": "VSPHERE.LOCAL\\Administrator" + }, + { + "short": "false", + "title": "DateTime", + "value": "2022-02-24T20:01:17.280999Z" + }, + { + "short": "false", + "title": "Full Message", + "value": "NICs using unapproved portgroups:\nNetwork adapter 2 - VSS_VM_VLAN_203\nNetwork adapter 3 - DVS_PG_NOT_PCI\n\n\nReconfigured tinycore-2 on esx02.lab.local in HomeLab. \n \nModified: \n \nconfig.hardware.device(4000).backing.port.portgroupKey: \"dvportgroup-1006\" -> \"dvportgroup-8004\"; \n\nconfig.hardware.device(4000).backing.port.portKey: \"4\" -> \"65\"; \n\nconfig.hardware.device(4000).backing.port.connectionCookie: 174899823 -> 193390024; \n\n Added: \n \n Deleted: \n \n" + } + ] + } + ] +}" +03/03/2022 22:23:12 - Sending Webhook payload to Slack ... +03/03/2022 22:23:13 - Successfully sent Webhook ... +03/03/2022 22:23:13 - PG Check operation complete ... + +03/03/2022 22:23:13 - Handler Processing Completed ... +``` + +You should see a Slack alert similar to this. In our test setup, Network Adapter 1 was connected to a portgroup tagged as PCI. It does not show up on the unapproved list in the Slack message below. The other two NICs do show up. + +![Img](kn-pcli-pg-slack.png) + +> Pro Tip - If you are rapidly iterating on the code and want to easily rebuild and launch the container, +> you can chain all of the commands together with ampersands. This will allow you to re-run +> the commands by simply pressing the `up` arrow and `Enter`. + +```console +cd .. && docker build -t ${IMAGE} . && cd test && docker run -e FUNCTION_DEBUG=true -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 ${IMAGE} +``` +# 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. + +If you built a custom image, push it to an accessible registry such as Docker once you're done developing and testing your function logic. + +```console +docker push ${IMAGE} +``` + +Update the `pg_check_secret.json` file with the same vCenter Server credentials and configurations that you used in `test/docker-test-env-variable` and then create the kubernetes secret which can then be accessed from within the function by using the environment variable named called `PG_CHECK_SECRET`. + +```console +# create secret +kubectl -n vmware-functions create secret generic pg-check-secret --from-file=PG_CHECK_SECRET=pg_check_secret.json + +# update label for secret to show up in VEBA UI +kubectl -n vmware-functions label secret pg-check-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 `VmReconfiguredEvent` 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 function-secret +``` diff --git a/examples/knative/powercli/kn-pcli-pg-check/function.yaml b/examples/knative/powercli/kn-pcli-pg-check/function.yaml new file mode 100644 index 00000000..0958e03f --- /dev/null +++ b/examples/knative/powercli/kn-pcli-pg-check/function.yaml @@ -0,0 +1,41 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-pcli-pg-check + labels: + app: veba-ui +spec: + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" + spec: + containers: + - image: us.gcr.io/daisy-284300/veba/kn-pcli-pg-check:1.0 + envFrom: + - secretRef: + name: pg-check-secret + env: + - name: FUNCTION_DEBUG + value: "true" +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-pcli-pg-check-trigger + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/event + # Replace this subject with the event you need to trigger on + # Then, edit send-cloudevent-test.ps1 and send-cloudevent-test.sh in the /test folder + subject: VmReconfiguredEvent + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-pcli-pg-check diff --git a/examples/knative/powercli/kn-pcli-pg-check/handler.ps1 b/examples/knative/powercli/kn-pcli-pg-check/handler.ps1 new file mode 100644 index 00000000..928ef913 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-pg-check/handler.ps1 @@ -0,0 +1,315 @@ +Function Process-Init { + [CmdletBinding()] + param() + Write-Host "$(Get-Date) - Processing Init`n" + + try { + $jsonSecrets = ${env:PG_CHECK_SECRET} | ConvertFrom-Json + } + catch { + throw "`nK8s secret `$env:PG_CHECK_SECRET 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_CERTIFICATE_ACTION = ${jsonSecrets}.VCENTER_CERTIFICATE_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_CERTIFICATE_ACTION} -ParticipateInCeip:$true -Confirm:$false + + Write-Host "$(Get-Date) - Connecting to vCenter Server $VCENTER_SERVER`n" + + try { + Connect-VIServer -Server $VCENTER_SERVER -User $VCENTER_USERNAME -Password $VCENTER_PASSWORD + } + catch { + Write-Error "$(Get-Date) - ERROR: Failed to connect to vCenter Server" + throw $_ + } + + Write-Host "$(Get-Date) - Successfully connected to $VCENTER_SERVER`n" + + Write-Host "$(Get-Date) - Init Processing Completed`n" +} + +Function Process-Shutdown { + [CmdletBinding()] + param() + Write-Host "$(Get-Date) - Processing Shutdown`n" + + Write-Host "$(Get-Date) - Disconnecting from vCenter Server`n" + + try { + Disconnect-VIServer * -Confirm:$false + } + catch { + Write-Error "$(Get-Date) - Error: Failed to Disconnect from vCenter Server" + } + + Write-Host "$(Get-Date) - Shutdown Processing Completed`n" +} + +Function Process-Handler { + [CmdletBinding()] + 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:PG_CHECK_SECRET} | ConvertFrom-Json + } + catch { + throw "`nK8s secret `$env:PG_CHECK_SECRET does not look to be defined" + } + + $VM_WATCH_TAGS = ${jsonSecrets}.VM_WATCH_TAGS + $PG_WATCH_TAGS = ${jsonSecrets}.PG_WATCH_TAGS + + $deviceUnchanged = ($NULL -eq $cloudEventData.ConfigSpec.DeviceChange ) + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: ConfigSpec.DeviceChange Is Null? $($deviceUnchanged)`n" + } + + # If no devices changed, then the NIC could not have been updated to a different portgroup + if ($deviceUnchanged) + { + Write-Host "$(Get-Date) - No devices changed.`n" + return + } + + # Build the MoRef ID + $vmID = $cloudEventData.Vm.Vm.Type + "-" + $cloudEventData.Vm.Vm.Value + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: Vm.Vm.Type Is Null? $($NULL -eq $cloudEventData.Vm.Vm.Type)`n" + Write-Host "$(Get-Date) - DEBUG: Vm.Vm.Value Is Null? $($NULL -eq $cloudEventData.Vm.Vm.Value)`n" + Write-Host "$(Get-Date) - DEBUG: vmID is $vmID`n" + } + + # Retrieve the VM object by MoRef ID + try { + $vm = Get-VM -id $vmID + } + catch { + Write-Host "$(Get-Date) - ERROR: unable to retrieve VM ID $vmID`n" + throw $_ + } + + # Retrieve all tags on the VM + Write-Host "$(Get-Date) - Retrieving tags on $($vm.Name)`n" + try { + $vmTags = $vm | Get-TagAssignment + } + catch { + Write-Host "$(Get-Date) - ERROR: unable to retrieve tags for $($vm.Name)`n" + throw $_ + } + + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: Tags found on VM: $($vmTags.tag)`n" + Write-Host "$(Get-Date) - DEBUG: Tags to monitor: $($VM_WATCH_TAGS)`n" + } + + # Search through the tags found on the VM and compare them to the VM tags specified in the secret. + # If any match is found, break out of the loop - finding any match means the VM is tagged for further inspection + $checkVM = $false + :outer foreach ($tag in $vmTags) { + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: Comparing VM tag: $($tag.Tag) on VM $($vm.Name)`n" + } + foreach ($watchTag in $VM_WATCH_TAGS) { + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: Comparing watch tag: $($watchTag)`n" + } + if ($watchTag -eq $tag.Tag) { + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: Match found for: $($watchTag), breaking outer loop`n" + } + $checkVM = $true + break outer + } + } + } + + # If the VM isn't tagged, no further inspection is necessary + if ($checkVM -eq $false) { + Write-Host "$(Get-Date) - VM not tagged for monitoring, no inspection required.`n" + return + } + + Write-Host "$(Get-Date) - Match found for $($vm.Name), checking portgroups`n" + + # Try retrieving the VM's network adapters + try { + $networkAdapters = $vm | Get-NetworkAdapter + } + catch { + Write-Host "$(Get-Date) - ERROR: unable to retrieve retrieve network adapters for $($vm.Name)`n" + throw $_ + } + + # This hash will store any network adapters that are attached to unapproved portgroups + $networkAdapterHash = @{} + + foreach ($nic in $networkAdapters) { + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: VM $($vm.Name) - NIC $($nic.Name) - PortGroup $($nic.NetworkName)`n" + } + # If the portgroup is a VSS portgroup, it will have a backing network property + $vssBackingNetwork = $nic.extensiondata.Backing.Network + # If the portgroup is a DVS portgroup, it will have a PortgroupKey property + $dvPortGroupKey = $nic.extensiondata.Backing.Port.PortgroupKey + $vNetworkID = $null + + if ($NULL -ne $vssBackingNetwork) { + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: VSS Backing network is $($vssBackingNetwork)`n" + } + # vssBackingNetwork is already in MoRef ID format + $vNetworkID = $vssBackingNetwork + } + elseif ($NULL -ne $dvPortGroupKey) { + if (${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: DVS Port Group Key is $($dvPortGroupKey)`n" + } + # Unlike the VSS above, We have to build the MoRef ID for VDS + $vNetworkID = "DistributedVirtualPortgroup-" + $dvPortGroupKey + } else { + Write-Host "$(Get-Date) - ERROR: Could not determine network type`n" + throw "$(Get-Date) - ERROR: Could not determine network type for $($nic.Name)`n" + } + + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: Virtual Network ID: $($vNetworkID)`n" + } + + # Now that we've determined the ID, we can try retrieving the portgroup virtual network information + try { + $pg = Get-VirtualNetwork -Id $vNetworkID + } + catch { + Write-Host "$(Get-Date) - ERROR: unable to retrieve retrieve virtual network information for $($vNetworkID)`n" + throw $_ + } + + # Retrieve the tag assignments for the port group + try { + $pgTags = $pg | Get-TagAssignment + } + catch { + Write-Host "$(Get-Date) - ERROR: unable to retrieve retrieve tag assignments for $($pg.Name)`n" + throw $_ + } + + # If $null, the NIC is on a pg with no tags - add it to the hash table + if ($null -eq $pgTags) { + $networkAdapterHash[$nic.id] = "$($pg.Name)" + } else { + $pgMatch = $false + # Search through all tags on the portgroup, looking for match on any of the watch tags + # provided in the secret. If there is a match on any tag, stop processing - we only need + # one tag match for the portgroup to be marked as OK + :outer foreach ($pgTag in $pgTags) { + $fullTag = $pgTag.Tag.Category.ToString() + "/" + $pgTag.Tag.Name.ToString() + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: PortGroup Tag: $($fullTag)`n" + } + + foreach ($watchTag in $PG_WATCH_TAGS) { + if ($fullTag -eq $watchTag) { + Write-Host "$(Get-Date) - INFO: Found a match on $($watchTag)`n" + $pgMatch = $true + break outer + } + } + } + + # If none of the portgroup's tags are a match, the vNIC is added to the hash table + if ($pgMatch -eq $false ) { + Write-Host "$(Get-Date) - INFO: No permitted tags were found on the portgroup`n" + $networkAdapterHash[$nic.id] = "$($pg.Name)" + } + } + } + + # Check to see if the hash is empty + if ( $networkAdapterHash.Count -eq 0 ) { + Write-Host "$(Get-Date) - INFO: All NICs are on approved portgroups" + return + } + + $msg = "NICs using unapproved portgroups:`n" + Write-Host "$(Get-Date) - $($msg)" + # Build a list of NICs and unapproved portgroups + foreach ($nic in $networkAdapters) { + if ($networkAdapterHash.ContainsKey($nic.Id)) { + Write-Host "$(Get-Date) - $($nic.Name): on unapproved portgroup $($networkAdapterHash[$nic.Id])" + $msg += $nic.Name + " - " + $networkAdapterHash[$nic.Id] + "`n" + } + } + + # Payload for Slack + $payload = @{ + attachments = @( + @{ + pretext = $(${jsonSecrets}.SLACK_MESSAGE_PRETEXT); + fields = @( + @{ + title = "EventType"; + value = $cloudEvent.Subject; + short = "false"; + } + @{ + title = "Username"; + value = $cloudEventData.UserName; + short = "false"; + } + @{ + title = "DateTime"; + value = $cloudEventData.CreatedTime; + short = "false"; + } + @{ + title = "Full Message"; + value = $msg + "`n`n" + $cloudEventData.FullFormattedMessage ; + short = "false"; + } + ) + } + ) + } + + # Convert Slack message object into JSON + $body = $payload | ConvertTo-Json -Depth 5 + + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: `"$body`"" + } + + Write-Host "$(Get-Date) - Sending Webhook payload to Slack ..." + $ProgressPreference = "SilentlyContinue" + + try { + Invoke-WebRequest -Uri $(${jsonSecrets}.SLACK_WEBHOOK_URL) -Method POST -ContentType "application/json" -Body $body + } catch { + throw "$(Get-Date) - Failed to send Slack Message: $($_)" + } + + Write-Host "$(Get-Date) - Successfully sent Webhook ..." + + Write-Host "$(Get-Date) - PG Check operation complete ...`n" + + Write-Host "$(Get-Date) - Handler Processing Completed ...`n" +} diff --git a/examples/knative/powercli/kn-pcli-pg-check/kn-pcli-pg-slack.png b/examples/knative/powercli/kn-pcli-pg-check/kn-pcli-pg-slack.png new file mode 100644 index 00000000..fd06399e Binary files /dev/null and b/examples/knative/powercli/kn-pcli-pg-check/kn-pcli-pg-slack.png differ diff --git a/examples/knative/powercli/kn-pcli-pg-check/pg_check_secret.json b/examples/knative/powercli/kn-pcli-pg-check/pg_check_secret.json new file mode 100644 index 00000000..a92ce566 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-pg-check/pg_check_secret.json @@ -0,0 +1,10 @@ +{ + "VCENTER_SERVER": "FILL-ME-IN", + "VCENTER_USERNAME" : "FILL-ME-IN", + "VCENTER_PASSWORD" : "FILL-ME-IN", + "VCENTER_CERTIFICATE_ACTION" : "Ignore", + "VM_WATCH_TAGS":["FILL-ME-IN","FILL-ME-IN"], + "PG_WATCH_TAGS":["FILL-ME-IN","FILL-ME-IN"], + "SLACK_WEBHOOK_URL" : "FILL-ME-IN", + "SLACK_MESSAGE_PRETEXT":"Virtual Machine - Portgroup Alert" +} \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-pg-check/test/docker-test-env-variable b/examples/knative/powercli/kn-pcli-pg-check/test/docker-test-env-variable new file mode 100644 index 00000000..c55ee64d --- /dev/null +++ b/examples/knative/powercli/kn-pcli-pg-check/test/docker-test-env-variable @@ -0,0 +1 @@ +PG_CHECK_SECRET={"VCENTER_SERVER":"FILL-ME-IN","VCENTER_USERNAME":"FILL-ME-IN","VCENTER_PASSWORD":"FILL-ME-IN","VCENTER_CERTIFICATE_ACTION":"Ignore","VM_WATCH_TAGS":["FILL-ME-IN","FILL-ME-IN"],"PG_WATCH_TAGS":["FILL-ME-IN","FILL-ME-IN"],"SLACK_WEBHOOK_URL":"FILL-ME-IN","SLACK_MESSAGE_PRETEXT":"Virtual Machine - Portgroup Alert"} \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-pg-check/test/send-cloudevent-test.ps1 b/examples/knative/powercli/kn-pcli-pg-check/test/send-cloudevent-test.ps1 new file mode 100644 index 00000000..afd37fe7 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-pg-check/test/send-cloudevent-test.ps1 @@ -0,0 +1,26 @@ +# The ce-subject value should match the event router subject in function.yaml +$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" = "VmReconfiguredEvent"; +} + +$payloadPath = "./test-payload.json" +if ( $args.Count -gt 0 ) { + if ( Test-Path $args[0] ) { + $payloadPath = $args[0] + } + else { + Write-Host "$(Get-Date) - ERROR: Invalid path"$args[0]"`n" + exit + } +} +$body = Get-Content -Raw -Path $payloadPath + +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-pg-check/test/send-cloudevent-test.sh b/examples/knative/powercli/kn-pcli-pg-check/test/send-cloudevent-test.sh new file mode 100644 index 00000000..c660e374 --- /dev/null +++ b/examples/knative/powercli/kn-pcli-pg-check/test/send-cloudevent-test.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# The ce-subject value should match the event router subject in function.yaml +echo "Testing Function ..." +PAYLOAD_PATH="test-payload.json" +if [ $# -gt 0 ]; then + if test -f "$1"; then + PAYLOAD_PATH=$1 + else + echo "$1 not found" + exit 1 + fi +fi +curl -d@$PAYLOAD_PATH \ + -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: VmReconfiguredEvent' \ + -X POST localhost:8080 + +echo "See docker container console for output" diff --git a/examples/knative/powercli/kn-pcli-pg-check/test/test-payload.json b/examples/knative/powercli/kn-pcli-pg-check/test/test-payload.json new file mode 100644 index 00000000..925c439b --- /dev/null +++ b/examples/knative/powercli/kn-pcli-pg-check/test/test-payload.json @@ -0,0 +1,158 @@ +{ + "Key": 689459, + "ChainId": 689456, + "CreatedTime": "2022-02-24T20:01:17.280999Z", + "UserName": "VSPHERE.LOCAL\\Administrator", + "Datacenter": { + "Name": "HomeLab", + "Datacenter": { + "Type": "Datacenter", + "Value": "datacenter-47" + } + }, + "ComputeResource": { + "Name": "CL1", + "ComputeResource": { + "Type": "ClusterComputeResource", + "Value": "domain-c52" + } + }, + "Host": { + "Name": "esx02.lab.local", + "Host": { + "Type": "HostSystem", + "Value": "host-58" + } + }, + "Vm": { + "Name": "REPLACE-ME", + "Vm": { + "Type": "VirtualMachine", + "Value": "REPLACE-ME" + } + }, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "Reconfigured tinycore-2 on esx02.lab.local in HomeLab. \n \nModified: \n \nconfig.hardware.device(4000).backing.port.portgroupKey: \"dvportgroup-1006\" -> \"dvportgroup-8004\"; \n\nconfig.hardware.device(4000).backing.port.portKey: \"4\" -> \"65\"; \n\nconfig.hardware.device(4000).backing.port.connectionCookie: 174899823 -> 193390024; \n\n Added: \n \n Deleted: \n \n", + "ChangeTag": "", + "Template": false, + "ConfigSpec": { + "ChangeVersion": "2022-02-24T20:00:58.726047Z", + "Name": "", + "Version": "", + "CreateDate": "2021-05-25T20:15:49.026445Z", + "Uuid": "", + "InstanceUuid": "", + "NpivNodeWorldWideName": null, + "NpivPortWorldWideName": null, + "NpivWorldWideNameType": "", + "NpivDesiredNodeWwns": 0, + "NpivDesiredPortWwns": 0, + "NpivTemporaryDisabled": null, + "NpivOnNonRdmDisks": null, + "NpivWorldWideNameOp": "", + "LocationId": "", + "GuestId": "", + "AlternateGuestName": "", + "Annotation": "", + "Files": { + "VmPathName": "ds:///vmfs/volumes/60883b4b-6596cf2d-0179-001b21a69dd8/tinycore-2/tinycore-2.vmx", + "SnapshotDirectory": "", + "SuspendDirectory": "", + "LogDirectory": "", + "FtMetadataDirectory": "" + }, + "Tools": null, + "Flags": null, + "ConsolePreferences": null, + "PowerOpInfo": null, + "NumCPUs": 0, + "VcpuConfig": null, + "NumCoresPerSocket": 0, + "MemoryMB": 0, + "MemoryHotAddEnabled": null, + "CpuHotAddEnabled": null, + "CpuHotRemoveEnabled": null, + "VirtualICH7MPresent": null, + "VirtualSMCPresent": null, + "DeviceChange": [ + { + "Operation": "edit", + "FileOperation": "", + "Device": { + "Key": 4000, + "DeviceInfo": { + "Label": "Network adapter 1", + "Summary": "DVSwitch: 50 17 d5 a1 f0 17 6c 1d-d7 f9 b4 a8 21 62 1e 6b" + }, + "Backing": { + "Port": { + "SwitchUuid": "50 17 d5 a1 f0 17 6c 1d-d7 f9 b4 a8 21 62 1e 6b", + "PortgroupKey": "dvportgroup-8004", + "PortKey": "65", + "ConnectionCookie": 193390024 + } + }, + "Connectable": null, + "SlotInfo": { + "PciSlotNumber": 32 + }, + "ControllerKey": 100, + "UnitNumber": 7, + "AddressType": "assigned", + "MacAddress": "00:50:56:97:6f:a8", + "WakeOnLanEnabled": true, + "ResourceAllocation": { + "Reservation": 0, + "Share": { + "Shares": 50, + "Level": "normal" + }, + "Limit": -1 + }, + "ExternalId": "", + "UptCompatibilityEnabled": false + }, + "Profile": null, + "Backing": null + } + ], + "CpuAllocation": null, + "MemoryAllocation": null, + "LatencySensitivity": null, + "CpuAffinity": null, + "MemoryAffinity": null, + "NetworkShaper": null, + "CpuFeatureMask": null, + "ExtraConfig": null, + "SwapPlacement": "", + "BootOptions": null, + "VAppConfig": null, + "FtInfo": null, + "RepConfig": null, + "VAppConfigRemoved": null, + "VAssertsEnabled": null, + "ChangeTrackingEnabled": null, + "Firmware": "", + "MaxMksConnections": 0, + "GuestAutoLockEnabled": null, + "ManagedBy": null, + "MemoryReservationLockedToMax": null, + "NestedHVEnabled": null, + "VPMCEnabled": null, + "ScheduledHardwareUpgradeInfo": null, + "VmProfile": null, + "MessageBusTunnelEnabled": null, + "Crypto": null, + "MigrateEncryption": "", + "SgxInfo": null, + "GuestMonitoringModeInfo": null, + "SevEnabled": null + }, + "ConfigChanges": { + "Modified": "config.hardware.device(4000).backing.port.portgroupKey: \"dvportgroup-1006\" -> \"dvportgroup-8004\"; \n\nconfig.hardware.device(4000).backing.port.portKey: \"4\" -> \"65\"; \n\nconfig.hardware.device(4000).backing.port.connectionCookie: 174899823 -> 193390024; \n\n", + "Added": "", + "Deleted": "" + } + } \ No newline at end of file diff --git a/examples/knative/powercli/kn-pcli-template/README.md b/examples/knative/powercli/kn-pcli-template/README.md index cefb6ed0..808b5a93 100644 --- a/examples/knative/powercli/kn-pcli-template/README.md +++ b/examples/knative/powercli/kn-pcli-template/README.md @@ -7,6 +7,7 @@ Delete this section before publishing your new function - handler.ps1 - README.md - function_secret.json (rename the file) + - function.yaml - Obtain a new payload file - The example `test/test-payload.json` is a sample event payload file for event type `DvsReconfiguredEvent`. This needs to be replaced with the payload for your specific event. This is easily obtained using the built-in Sockeye service. Browse to the `/events` endpoint of your VEBA deployment and cause your event to trigger. You can then can easily copy and paste the payload from the output in Sockeye. # kn-pcli-#REPLACE-FN-NAME# @@ -90,7 +91,11 @@ Next, locate the `ConfigSpec` section of the JSON file. Change the `MaxMTU` prop **WARNING** - This function will reconfigure your distributed virtual switch - if your MTU is not set to 1500, it will be reset by this function. --- -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. When run with no arguments, the scripts will send the contents of `test-payload.json` as the payload. If you pass the scripts a different filename as an argument, they will send the contents of the specified file instead. Example: `send-cloudevent-test.ps1 test-payload2.json`. This testing technique is useful when writing complex functions with varying payloads. +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. When run with no arguments, the scripts will send the contents of `test-payload.json` as the payload. If you pass the scripts a different filename as an argument, they will send the contents of the specified file instead. Example: `send-cloudevent-test.ps1 test-payload2.json`. + +You can also send a custom event subject as an argument. The default event is `DvsReconfiguredEvent`. Example: `send-cloudevent-test.ps1 test-payload2.json EnteredMaintenanceModeEvent` will send `test-payload2.json` with a subject of `EnteredMaintenanceModeEvent`. + +These testing technique are useful when writing complex functions with varying payloads, or when writing functions with separate triggers on different events. ```console Testing Function ... diff --git a/examples/knative/powercli/kn-pcli-template/function.yaml b/examples/knative/powercli/kn-pcli-template/function.yaml index cb940697..1291791c 100644 --- a/examples/knative/powercli/kn-pcli-template/function.yaml +++ b/examples/knative/powercli/kn-pcli-template/function.yaml @@ -38,4 +38,4 @@ spec: ref: apiVersion: serving.knative.dev/v1 kind: Service - name: kn-pcli-#REPLACE-ME# + name: kn-pcli-#REPLACE-FN-NAME# diff --git a/examples/knative/powercli/kn-pcli-template/function_secret.json b/examples/knative/powercli/kn-pcli-template/function_secret.json index b45d68aa..00eac43b 100644 --- a/examples/knative/powercli/kn-pcli-template/function_secret.json +++ b/examples/knative/powercli/kn-pcli-template/function_secret.json @@ -2,5 +2,5 @@ "VCENTER_SERVER": "FILL-ME-IN", "VCENTER_USERNAME" : "FILL-ME-IN", "VCENTER_PASSWORD" : "FILL-ME-IN", - "VCENTER_CERTIFICATE_ACTION" : "Ignore" + "VCENTER_CERTIFICATE_ACTION" : "Fail" } diff --git a/examples/knative/powercli/kn-pcli-template/test/send-cloudevent-test.ps1 b/examples/knative/powercli/kn-pcli-template/test/send-cloudevent-test.ps1 index 4970dbee..9d2ea0f4 100644 --- a/examples/knative/powercli/kn-pcli-template/test/send-cloudevent-test.ps1 +++ b/examples/knative/powercli/kn-pcli-template/test/send-cloudevent-test.ps1 @@ -1,14 +1,7 @@ # The ce-subject value should match the event router subject in function.yaml -$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" = "DvsReconfiguredEvent"; -} - +$subject = "DvsReconfiguredEvent" $payloadPath = "./test-payload.json" + if ( $args.Count -gt 0 ) { if ( Test-Path $args[0] ) { $payloadPath = $args[0] @@ -17,6 +10,19 @@ if ( $args.Count -gt 0 ) { Write-Host "$(Get-Date) - ERROR: Invalid path"$args[0]"`n" exit } + + if ( $args.Count -gt 1 ) { + $subject = $args[1] + } +} + +$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" = $($subject); } $body = Get-Content -Raw -Path $payloadPath diff --git a/examples/knative/powercli/kn-pcli-template/test/send-cloudevent-test.sh b/examples/knative/powercli/kn-pcli-template/test/send-cloudevent-test.sh index 3c7f114f..b2652b4b 100644 --- a/examples/knative/powercli/kn-pcli-template/test/send-cloudevent-test.sh +++ b/examples/knative/powercli/kn-pcli-template/test/send-cloudevent-test.sh @@ -3,6 +3,8 @@ # The ce-subject value should match the event router subject in function.yaml echo "Testing Function ..." PAYLOAD_PATH="test-payload.json" +SUBJECT="DvsReconfiguredEvent" + if [ $# -gt 0 ]; then if test -f "$1"; then PAYLOAD_PATH=$1 @@ -10,6 +12,10 @@ if [ $# -gt 0 ]; then echo "$1 not found" exit 1 fi + + if [ $# -gt 1 ]; then + SUBJECT=$2 + fi fi curl -d@$PAYLOAD_PATH \ -H "Content-Type: application/json" \ @@ -17,7 +23,7 @@ curl -d@$PAYLOAD_PATH \ -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: DvsReconfiguredEvent' \ + -H 'ce-subject: '$SUBJECT \ -X POST localhost:8080 echo "See docker container console for output" diff --git a/examples/knative/powershell/kn-ps-harbor-slack/Dockerfile b/examples/knative/powershell/kn-ps-harbor-slack/Dockerfile new file mode 100644 index 00000000..1b16e1ef --- /dev/null +++ b/examples/knative/powershell/kn-ps-harbor-slack/Dockerfile @@ -0,0 +1,5 @@ +FROM us.gcr.io/daisy-284300/veba/ce-ps-base:1.4 + +COPY handler.ps1 handler.ps1 + +CMD ["pwsh","./server.ps1"] diff --git a/examples/knative/powershell/kn-ps-harbor-slack/README.md b/examples/knative/powershell/kn-ps-harbor-slack/README.md new file mode 100644 index 00000000..7fcaac9b --- /dev/null +++ b/examples/knative/powershell/kn-ps-harbor-slack/README.md @@ -0,0 +1,157 @@ +# kn-ps-harbor-slack + +Example Knative PowerShell function for sending Harbor CloudEvents to a Slack webhook. This function relies on the Harbor webhook [function example](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/development/examples/knative/go/kn-go-harbor-webhook) which is a requirement for this example. + +# Step 1 - Build + +Create the container image locally to test your function logic. Change the IMAGE name accordingly, example below for Docker. + +```console +export IMAGE=/kn-ps-#REPLACE-FN-NAME#:1.0 +docker build -t ${IMAGE} +``` + +# 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 + +* SLACK_WEBHOOK_URL - Slack webhook URL +* SLACK_MESSAGE_PRETEXT - Text displayed for Slack notification + +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 ${IMAGE} +``` + +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 +06/27/2022 09:47:31 - DEBUG: K8s Secrets: +{"SLACK_WEBHOOK_URL":"**********","SLACK_MESSAGE_PRETEXT":":harbor: Harbor Slack Function :veba_official:"} + +06/27/2022 09:47:31 - DEBUG: CloudEventData + +Name Value +---- ----- +event_data {resources, repository} +occur_at 1656076946 +type PUSH_ARTIFACT +operator admin + + + +06/27/2022 09:47:31 - DEBUG: "{ + "attachments": [ + { + "footer_icon": "https://raw.githubusercontent.com/vmware-samples/vcenter-event-broker-appliance/development/logo/veba_icon_only.png", + "footer": "Powered by VEBA", + "pretext": ":harbor: Harbor Slack Function :veba_official:", + "fields": [ + { + "short": "false", + "value": "PUSH_ARTIFACT", + "title": "Event Type" + }, + { + "short": "false", + "value": "2022-06-25T11:42:42+00:00", + "title": "DateTime in UTC" + }, + { + "short": "false", + "value": "admin", + "title": "Username" + }, + { + "short": "false", + "value": "veba-webhook/bitnami-nginx", + "title": "Repository Name" + }, + { + "short": "false", + "value": "public", + "title": "Repository Type" + }, + { + "short": "false", + "value": "1.21.6-debian-10-r117", + "title": "Image Tag" + }, + { + "short": "false", + "value": "harbor.jarvis.tanzu/veba-webhook/bitnami-nginx:1.21.6-debian-10-r117", + "title": "Image Resource Data" + }, + { + "short": "false", + "value": "sha256:d3890814cc5a7cfc02403435281cdf51adfb6b67e223934d9d6137a4ad364286", + "title": "Image Digest" + } + ] + } + ] +}" +06/27/2022 09:47:31 - Sending Webhook payload to Slack ... +06/27/2022 09:47:31 - Successfully sent Webhook ... +``` + +# 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. +> +> **Note:** Also, in order to receive incoming Harbor events and to ultimately invoke the Harbor-Slack-Function, it's necessary to have the [kn-go-harbor-webhook function example](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/development/examples/knative/go/kn-go-harbor-webhook) running properly first. + +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 harbor-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 harbor-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 `com.vmware.harbor.push_artifact.v0` Harbor Event. If you wish to change this, update the `type` field within `function.yaml` to the desired event type. A list of supported notification events is available on the official Harbor documentation under [Configure Webhook Notifications](https://goharbor.io/docs/2.5.0/working-with-projects/project-configuration/configure-webhooks/). Furthermore, use the VEBA Event viewer endpoint (`https:///events`) to display all incoming events. + +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 harbor-slack-secret +``` \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-harbor-slack/function.yaml b/examples/knative/powershell/kn-ps-harbor-slack/function.yaml new file mode 100644 index 00000000..6fff81a3 --- /dev/null +++ b/examples/knative/powershell/kn-ps-harbor-slack/function.yaml @@ -0,0 +1,39 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-ps-harbor-slack + labels: + app: veba-ui +spec: + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" + spec: + containers: + - image: us.gcr.io/daisy-284300/veba/kn-ps-harbor-slack:1.0 + envFrom: + - secretRef: + name: harbor-slack-secret + env: + - name: FUNCTION_DEBUG + value: "false" +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: kn-ps-harbor-slack-trigger + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.harbor.push_artifact.v0 + subject: admin + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-ps-harbor-slack diff --git a/examples/knative/powershell/kn-ps-harbor-slack/handler.ps1 b/examples/knative/powershell/kn-ps-harbor-slack/handler.ps1 new file mode 100644 index 00000000..60b2e989 --- /dev/null +++ b/examples/knative/powershell/kn-ps-harbor-slack/handler.ps1 @@ -0,0 +1,112 @@ +Function Process-Init { + [CmdletBinding()] + param() + Write-Host "$(Get-Date) - Processing Init`n" + + Write-Host "$(Get-Date) - Init Processing Completed`n" +} + +Function Process-Shutdown { + [CmdletBinding()] + param() + Write-Host "$(Get-Date) - Processing Shutdown`n" + + Write-Host "$(Get-Date) - Shutdown Processing Completed`n" +} + +Function Process-Handler { + [CmdletBinding()] + 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:SLACK_SECRET} | ConvertFrom-Json + } catch { + throw "`nK8s secrets `$env:SLACK_SECRET does not look to be defined" + } + + 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" + } + + # Construct Slack message object + $payload = @{ + attachments = @( + @{ + pretext = $(${jsonSecrets}.SLACK_MESSAGE_PRETEXT); + fields = @( + @{ + title = "Event Type"; + value = $cloudEventData.type; + short = "false"; + } + @{ + title = "DateTime in UTC"; + value = $cloudEvent.time; + short = "false"; + } + @{ + title = "Username"; + value = $cloudEventData.operator; + short = "false"; + } + @{ + title = "Repository Name"; + value = $cloudEventData.event_data.repository.repo_full_name; + short = "false"; + } + @{ + title = "Repository Type"; + value = $cloudEventData.event_data.repository.repo_type; + short = "false"; + } + @{ + title = "Image Tag"; + value = $cloudEventData.event_data.resources[0].tag; + short = "false"; + } + @{ + title = "Image Resource Data"; + value = $cloudEventData.event_data.resources[0].resource_url; + short = "false"; + } + @{ + title = "Image Digest"; + value = $cloudEventData.event_data.resources[0].digest; + short = "false"; + } + ) + footer = "Powered by VEBA"; + footer_icon = "https://raw.githubusercontent.com/vmware-samples/vcenter-event-broker-appliance/development/logo/veba_icon_only.png"; + } + ) + } + + # Convert Slack message object into JSON + $body = $payload | ConvertTo-Json -Depth 5 + + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: `"$body`"" + } + + Write-Host "$(Get-Date) - Sending Webhook payload to Slack ..." + $ProgressPreference = "SilentlyContinue" + + try { + Invoke-WebRequest -Uri $(${jsonSecrets}.SLACK_WEBHOOK_URL) -Method POST -ContentType "application/json" -Body $body + } catch { + throw "$(Get-Date) - Failed to send Slack Message: $($_)" + } + + Write-Host "$(Get-Date) - Successfully sent Webhook ..." +} diff --git a/examples/knative/powershell/kn-ps-harbor-slack/slack_secret.json b/examples/knative/powershell/kn-ps-harbor-slack/slack_secret.json new file mode 100644 index 00000000..92afa898 --- /dev/null +++ b/examples/knative/powershell/kn-ps-harbor-slack/slack_secret.json @@ -0,0 +1,4 @@ +{ + "SLACK_WEBHOOK_URL": "YOUR-WEBHOOK-URL", + "SLACK_MESSAGE_PRETEXT": ":harbor: Harbor Slack Function :veba_official:" +} \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-harbor-slack/test/docker-test-env-variable b/examples/knative/powershell/kn-ps-harbor-slack/test/docker-test-env-variable new file mode 100644 index 00000000..d24a6e37 --- /dev/null +++ b/examples/knative/powershell/kn-ps-harbor-slack/test/docker-test-env-variable @@ -0,0 +1 @@ +SLACK_SECRET={"SLACK_WEBHOOK_URL":"YOUR-WEBHOOK-URL","SLACK_MESSAGE_PRETEXT":":harbor: Harbor Slack Function :veba_official:"} \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-harbor-slack/test/send-cloudevent-test.ps1 b/examples/knative/powershell/kn-ps-harbor-slack/test/send-cloudevent-test.ps1 new file mode 100644 index 00000000..4d1c4abf --- /dev/null +++ b/examples/knative/powershell/kn-ps-harbor-slack/test/send-cloudevent-test.ps1 @@ -0,0 +1,17 @@ + +$headers = @{ + "Content-Type" = "application/json"; + "ce-specversion" = "1.0"; + "ce-id" = "d70079f9-fddd-4b7f-aa76-1193f28b0611"; + "ce-source" = "/kn-go-harbor-webhook"; + "ce-type" = "com.vmware.harbor.push_artifact.v0"; + "ce-subject" = "admin"; + "ce-time" = "2022-06-25T11:42:42Z"; +} + +$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-harbor-slack/test/send-cloudevent-test.sh b/examples/knative/powershell/kn-ps-harbor-slack/test/send-cloudevent-test.sh new file mode 100755 index 00000000..0197ed8e --- /dev/null +++ b/examples/knative/powershell/kn-ps-harbor-slack/test/send-cloudevent-test.sh @@ -0,0 +1,14 @@ +#!/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: /kn-go-harbor-webhook' \ + -H 'ce-type: com.vmware.harbor.push_artifact.v0' \ + -H 'ce-subject: admin' \ + -H 'ce-time: 2022-06-25T11:42:42Z' \ + -X POST localhost:8080 + +echo "See docker container console for output" diff --git a/examples/knative/powershell/kn-ps-harbor-slack/test/test-payload.json b/examples/knative/powershell/kn-ps-harbor-slack/test/test-payload.json new file mode 100644 index 00000000..4c4898e5 --- /dev/null +++ b/examples/knative/powershell/kn-ps-harbor-slack/test/test-payload.json @@ -0,0 +1,21 @@ +{ + "type": "PUSH_ARTIFACT", + "occur_at": 1656076946, + "operator": "admin", + "event_data": { + "resources": [ + { + "digest": "sha256:d3890814cc5a7cfc02403435281cdf51adfb6b67e223934d9d6137a4ad364286", + "tag": "1.21.6-debian-10-r117", + "resource_url": "harbor.jarvis.tanzu/veba-webhook/bitnami-nginx:1.21.6-debian-10-r117" + } + ], + "repository": { + "date_created": 1656076946, + "name": "bitnami-nginx", + "namespace": "veba-webhook", + "repo_full_name": "veba-webhook/bitnami-nginx", + "repo_type": "public" + } + } + } \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-slack-vsphere-alarm/README.md b/examples/knative/powershell/kn-ps-slack-vsphere-alarm/README.md index 73c65561..947f3473 100644 --- a/examples/knative/powershell/kn-ps-slack-vsphere-alarm/README.md +++ b/examples/knative/powershell/kn-ps-slack-vsphere-alarm/README.md @@ -183,7 +183,7 @@ kubectl apply -f in-memory-channel.yaml ## Deploy the Function and its Components -Edit the `parallel.yaml` file with the name of the container image from Step 1 **if you made any changes to the function code**. If not, the default VMware container image will suffice and you can keep the defaults. +Edit the `function.yaml` file with the name of the container image from Step 1 **if you made any changes to the function code**. If not, the default VMware container image will suffice and you can keep the defaults. Deploy the functions to the VMware Event Broker Appliance (VEBA). diff --git a/examples/knative/powershell/kn-ps-slack-vsphere-alarm/in-memory-channel.yaml b/examples/knative/powershell/kn-ps-slack-vsphere-alarm/in-memory-channel.yaml index 0950e2b7..fda13b3d 100644 --- a/examples/knative/powershell/kn-ps-slack-vsphere-alarm/in-memory-channel.yaml +++ b/examples/knative/powershell/kn-ps-slack-vsphere-alarm/in-memory-channel.yaml @@ -1,3 +1,122 @@ +# Copyright 2021 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: imc-controller + namespace: knative-eventing + labels: + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: imc-controller + labels: + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +subjects: + - kind: ServiceAccount + name: imc-controller + namespace: knative-eventing +roleRef: + kind: ClusterRole + name: imc-controller + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + namespace: knative-eventing + name: imc-controller + labels: + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +subjects: + - kind: ServiceAccount + name: imc-controller + namespace: knative-eventing +roleRef: + kind: Role + name: knative-inmemorychannel-webhook + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: imc-controller-resolver + labels: + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +subjects: + - kind: ServiceAccount + name: imc-controller + namespace: knative-eventing +roleRef: + kind: ClusterRole + name: addressable-resolver + apiGroup: rbac.authorization.k8s.io + +--- +# Copyright 2021 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: imc-dispatcher + namespace: knative-eventing + labels: + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: imc-dispatcher + labels: + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +subjects: + - kind: ServiceAccount + name: imc-dispatcher + namespace: knative-eventing +roleRef: + kind: ClusterRole + name: imc-dispatcher + apiGroup: rbac.authorization.k8s.io + +--- # Copyright 2020 The Knative Authors # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +137,659 @@ metadata: name: config-imc-event-dispatcher namespace: knative-eventing labels: - eventing.knative.dev/release: "v0.20.0" + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/component: imc-controller + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing data: MaxIdleConnections: "1000" MaxIdleConnectionsPerHost: "100" +--- +# Copyright 2021 The Knative Authors + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-observability + namespace: knative-eventing + labels: + eventing.knative.dev/release: "v1.1.0" + knative.dev/config-propagation: original + knative.dev/config-category: eventing + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing + annotations: + knative.dev/example-checksum: "f46cf09d" +data: + _example: | + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + + # metrics.backend-destination field specifies the system metrics destination. + # It supports either prometheus (the default) or stackdriver. + # Note: Using stackdriver will incur additional charges + metrics.backend-destination: prometheus + + # metrics.request-metrics-backend-destination specifies the request metrics + # destination. If non-empty, it enables queue proxy to send request metrics. + # Currently supported values: prometheus, stackdriver. + metrics.request-metrics-backend-destination: prometheus + + # metrics.stackdriver-project-id field specifies the stackdriver project ID. This + # field is optional. When running on GCE, application default credentials will be + # used if this field is not provided. + metrics.stackdriver-project-id: "" + + # metrics.allow-stackdriver-custom-metrics indicates whether it is allowed to send metrics to + # Stackdriver using "global" resource type and custom metric type if the + # metrics are not supported by "knative_broker", "knative_trigger", and "knative_source" resource types. + # Setting this flag to "true" could cause extra Stackdriver charge. + # If metrics.backend-destination is not Stackdriver, this is ignored. + metrics.allow-stackdriver-custom-metrics: "false" + + # profiling.enable indicates whether it is allowed to retrieve runtime profiling data from + # the pods via an HTTP server in the format expected by the pprof visualization tool. When + # enabled, the Knative Eventing pods expose the profiling data on an alternate HTTP port 8008. + # The HTTP context root for profiling is then /debug/pprof/. + profiling.enable: "false" + + # sink-event-error-reporting.enable whether the adapter reports a kube event to the CRD indicating + # a failure to send a cloud event to the sink. + sink-event-error-reporting.enable: "false" + +--- +# Copyright 2021 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-tracing + namespace: knative-eventing + labels: + eventing.knative.dev/release: "v1.1.0" + knative.dev/config-propagation: original + knative.dev/config-category: eventing + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing + annotations: + knative.dev/example-checksum: "c8f8c47b" +data: + _example: | + ################################ + # # + # EXAMPLE CONFIGURATION # + # # + ################################ + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. + # + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + # + # This may be "zipkin" or "none", the default is "none" + backend: "none" + + # URL to zipkin collector where traces are sent. + # This must be specified when backend is "zipkin" + zipkin-endpoint: "http://zipkin.istio-system.svc.cluster.local:9411/api/v2/spans" + + # Enable zipkin debug mode. This allows all spans to be sent to the server + # bypassing sampling. + debug: "false" + + # Percentage (0-1) of requests to trace + sample-rate: "0.1" + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: imc-controller + namespace: knative-eventing + labels: + eventing.knative.dev/release: "v1.1.0" + knative.dev/high-availability: "true" + app.kubernetes.io/component: imc-controller + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +spec: + selector: + matchLabels: &labels + messaging.knative.dev/channel: in-memory-channel + messaging.knative.dev/role: controller + template: + metadata: + labels: + !!merge <<: *labels + app.kubernetes.io/component: imc-controller + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: *labels + topologyKey: kubernetes.io/hostname + weight: 100 + serviceAccountName: imc-controller + enableServiceLinks: false + containers: + - name: controller + image: gcr.io/knative-releases/knative.dev/eventing/cmd/in_memory/channel_controller@sha256:4fe048c1454e129dda9b32abac4da7bfdc7ca1b53316169e1748873095f5f565 + env: + - name: WEBHOOK_NAME + value: inmemorychannel-webhook + - name: WEBHOOK_PORT + value: "8443" + - name: CONFIG_LOGGING_NAME + value: config-logging + - name: CONFIG_OBSERVABILITY_NAME + value: config-observability + - name: METRICS_DOMAIN + value: knative.dev/inmemorychannel-controller + - name: SYSTEM_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: DISPATCHER_IMAGE + value: gcr.io/knative-releases/knative.dev/eventing/cmd/in_memory/channel_dispatcher@sha256:f68e7b2f99b590e60f034d91b771eef02ff9303da749da4aec389f4c8286d903 + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + capabilities: + drop: + - all + ports: + - name: metrics + containerPort: 9090 + - name: profiling + containerPort: 8008 + - name: https-webhook + containerPort: 8443 + readinessProbe: &probe + periodSeconds: 1 + httpGet: + scheme: HTTPS + port: 8443 + httpHeaders: + - name: k-kubelet-probe + value: "webhook" + livenessProbe: + !!merge <<: *probe + initialDelaySeconds: 20 + # Our webhook should gracefully terminate by lame ducking first, set this to a sufficiently + # high value that we respect whatever value it has configured for the lame duck grace period. + terminationGracePeriodSeconds: 300 +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: imc-controller + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing + eventing.knative.dev/release: "v1.1.0" + name: inmemorychannel-webhook + namespace: knative-eventing +spec: + ports: + - name: https-webhook + port: 443 + targetPort: 8443 + - name: http-metrics + port: 9090 + targetPort: 9090 + - name: http-profiling + port: 8008 + targetPort: 8008 + selector: + messaging.knative.dev/channel: in-memory-channel + messaging.knative.dev/role: controller + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: v1 +kind: Service +metadata: + name: imc-dispatcher + namespace: knative-eventing + labels: + eventing.knative.dev/release: "v1.1.0" + messaging.knative.dev/channel: in-memory-channel + messaging.knative.dev/role: dispatcher + app.kubernetes.io/component: imc-dispatcher + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +spec: + selector: + messaging.knative.dev/channel: in-memory-channel + messaging.knative.dev/role: dispatcher + ports: + - name: http-dispatcher + port: 80 + protocol: TCP + targetPort: 8080 + - name: http-metrics + port: 9090 + targetPort: 9090 + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: imc-dispatcher + namespace: knative-eventing + labels: + eventing.knative.dev/release: "v1.1.0" + knative.dev/high-availability: "true" + app.kubernetes.io/component: imc-dispatcher + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +spec: + selector: + matchLabels: &labels + messaging.knative.dev/channel: in-memory-channel + messaging.knative.dev/role: dispatcher + template: + metadata: + labels: + !!merge <<: *labels + app.kubernetes.io/component: imc-dispatcher + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: *labels + topologyKey: kubernetes.io/hostname + weight: 100 + serviceAccountName: imc-dispatcher + enableServiceLinks: false + containers: + - name: dispatcher + image: gcr.io/knative-releases/knative.dev/eventing/cmd/in_memory/channel_dispatcher@sha256:f68e7b2f99b590e60f034d91b771eef02ff9303da749da4aec389f4c8286d903 + readinessProbe: &probe + failureThreshold: 3 + httpGet: + path: /healthz + port: 8080 + scheme: HTTP + periodSeconds: 2 + successThreshold: 1 + timeoutSeconds: 1 + livenessProbe: + !!merge <<: *probe + initialDelaySeconds: 5 + env: + - name: CONFIG_LOGGING_NAME + value: config-logging + - name: CONFIG_OBSERVABILITY_NAME + value: config-observability + - name: METRICS_DOMAIN + value: knative.dev/inmemorychannel-dispatcher + - name: SYSTEM_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: CONTAINER_NAME + value: dispatcher + - name: MAX_IDLE_CONNS + value: "1000" + - name: MAX_IDLE_CONNS_PER_HOST + value: "1000" + ports: + - containerPort: 8080 + name: http + protocol: TCP + - containerPort: 9090 + name: metrics + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + capabilities: + drop: + - all + +--- +# Copyright 2019 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: inmemorychannels.messaging.knative.dev + labels: + eventing.knative.dev/release: "v1.1.0" + knative.dev/crd-install: "true" + messaging.knative.dev/subscribable: "true" + duck.knative.dev/addressable: "true" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +spec: + group: messaging.knative.dev + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: 'InMemoryChannel is a resource representing an in memory channel' + type: object + properties: + spec: + description: Spec defines the desired state of the Channel. + type: object + properties: + delivery: + description: DeliverySpec contains the default delivery spec for each subscription to this Channelable. Each subscription delivery spec, if any, overrides this global delivery spec. + type: object + properties: + backoffDelay: + description: 'BackoffDelay is the delay before retrying. More information on Duration format: - https://www.iso.org/iso-8601-date-and-time-format.html - https://en.wikipedia.org/wiki/ISO_8601 For linear policy, backoff delay is backoffDelay*. For exponential policy, backoff delay is backoffDelay*2^.' + type: string + backoffPolicy: + description: BackoffPolicy is the retry backoff policy (linear, exponential). + type: string + deadLetterSink: + description: DeadLetterSink is the sink receiving event that could not be sent to a destination. + type: object + properties: + ref: + description: Ref points to an Addressable. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ This is optional field, it gets defaulted to the object holding it if left out.' + type: string + uri: + description: URI can be an absolute URL(non-empty scheme and non-empty host) pointing to the target or a relative URI. Relative URIs will be resolved using the base URI retrieved from Ref. + type: string + retry: + description: Retry is the minimum number of retries the sender should attempt when sending an event before moving it to the dead letter sink. + type: integer + format: int32 + x-kubernetes-preserve-unknown-fields: true # This is necessary to enable the experimental feature delivery-timeout + subscribers: + description: This is the list of subscriptions for this subscribable. + type: array + items: + type: object + properties: + delivery: + description: DeliverySpec contains options controlling the event delivery + type: object + properties: + backoffDelay: + description: 'BackoffDelay is the delay before retrying. More information on Duration format: - https://www.iso.org/iso-8601-date-and-time-format.html - https://en.wikipedia.org/wiki/ISO_8601 For linear policy, backoff delay is backoffDelay*. For exponential policy, backoff delay is backoffDelay*2^.' + type: string + backoffPolicy: + description: BackoffPolicy is the retry backoff policy (linear, exponential). + type: string + deadLetterSink: + description: DeadLetterSink is the sink receiving event that could not be sent to a destination. + type: object + properties: + ref: + description: Ref points to an Addressable. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ This is optional field, it gets defaulted to the object holding it if left out.' + type: string + uri: + description: URI can be an absolute URL(non-empty scheme and non-empty host) pointing to the target or a relative URI. Relative URIs will be resolved using the base URI retrieved from Ref. + type: string + retry: + description: Retry is the minimum number of retries the sender should attempt when sending an event before moving it to the dead letter sink. + type: integer + format: int32 + x-kubernetes-preserve-unknown-fields: true # This is necessary to enable the experimental feature + generation: + description: Generation of the origin of the subscriber with uid:UID. + type: integer + format: int64 + replyUri: + description: ReplyURI is the endpoint for the reply + type: string + subscriberUri: + description: SubscriberURI is the endpoint for the subscriber + type: string + uid: + description: UID is used to understand the origin of the subscriber. + type: string + status: + description: Status represents the current state of the Channel. This data may be out of date. + type: object + properties: + address: + type: object + properties: + url: + type: string + annotations: + description: Annotations is additional Status fields for the Resource to save some additional State as well as convey more information to the user. This is roughly akin to Annotations on any k8s resource, just the reconciler conveying richer information outwards. + type: object + x-kubernetes-preserve-unknown-fields: true + conditions: + description: Conditions the latest available observations of a resource's current state. + type: array + items: + type: object + required: + - type + - status + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition transitioned from one status to another. We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic differences (all other things held constant). + type: string + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + severity: + description: Severity with which to treat failures of this type of condition. When this is not specified, it defaults to Error. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + deadLetterChannel: + description: DeadLetterChannel is a KReference and is set by the channel when it supports native error handling via a channel Failed messages are delivered here. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ This is optional field, it gets defaulted to the object holding it if left out.' + type: string + deadLetterSinkUri: + description: DeadLetterSinkURI is the resolved URI of the dead letter ref if one is specified in the Spec.Delivery. + type: string + observedGeneration: + description: ObservedGeneration is the 'Generation' of the Service that was last processed by the controller. + type: integer + format: int64 + subscribers: + description: This is the list of subscription's statuses for this channel. + type: array + items: + type: object + properties: + message: + description: A human readable message indicating details of Ready status. + type: string + observedGeneration: + description: Generation of the origin of the subscriber with uid:UID. + type: integer + format: int64 + ready: + description: Status of the subscriber. + type: string + uid: + description: UID is used to understand the origin of the subscriber. + type: string + additionalPrinterColumns: + - name: URL + type: string + jsonPath: .status.address.url + - name: Age + type: date + jsonPath: .metadata.creationTimestamp + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" + names: + kind: InMemoryChannel + plural: inmemorychannels + singular: inmemorychannel + categories: + - all + - knative + - messaging + - channel + shortNames: + - imc + scope: Namespaced + --- # Copyright 2019 The Knative Authors # @@ -43,8 +810,10 @@ kind: ClusterRole metadata: name: imc-addressable-resolver labels: - eventing.knative.dev/release: "v0.20.0" + eventing.knative.dev/release: "v1.1.0" duck.knative.dev/addressable: "true" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing # Do not use this role directly. These rules will be added to the "addressable-resolver" role. rules: - apiGroups: @@ -77,8 +846,10 @@ kind: ClusterRole metadata: name: imc-channelable-manipulator labels: - eventing.knative.dev/release: "v0.20.0" + eventing.knative.dev/release: "v1.1.0" duck.knative.dev/channelable: "true" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing # Do not use this role directly. These rules will be added to the "channelable-manipulator" role. rules: - apiGroups: @@ -93,6 +864,7 @@ rules: - watch - update - patch + - delete --- # Copyright 2019 The Knative Authors @@ -114,7 +886,9 @@ kind: ClusterRole metadata: name: imc-controller labels: - eventing.knative.dev/release: "v0.20.0" + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing rules: - apiGroups: - messaging.knative.dev @@ -132,6 +906,14 @@ rules: - inmemorychannels/finalizers verbs: - update + - apiGroups: + - messaging.knative.dev + resources: + - inmemorychannels/finalizers + - inmemorychannels/status + - inmemorychannels + verbs: + - patch - apiGroups: - "" resources: @@ -190,28 +972,39 @@ rules: resources: - leases verbs: *everything - ---- -# Copyright 2019 The Knative Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -apiVersion: v1 -kind: ServiceAccount -metadata: - name: imc-controller - namespace: knative-eventing - labels: - eventing.knative.dev/release: "v0.20.0" + # For actually registering our webhook. + - apiGroups: + - "admissionregistration.k8s.io" + resources: + - "mutatingwebhookconfigurations" + - "validatingwebhookconfigurations" + verbs: &everything + - "get" + - "list" + - "create" + - "update" + - "delete" + - "patch" + - "watch" + # For manipulating certs into secrets. + - apiGroups: + - "" + resources: + - "namespaces" + verbs: + - "get" + - "create" + - "update" + - "list" + - "watch" + - "patch" + # finalizers are needed for the owner reference of the webhook + - apiGroups: + - "" + resources: + - "namespaces/finalizers" + verbs: + - "update" --- # Copyright 2019 The Knative Authors @@ -232,7 +1025,9 @@ kind: ClusterRole metadata: name: imc-dispatcher labels: - eventing.knative.dev/release: "v0.20.0" + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing rules: - apiGroups: - messaging.knative.dev @@ -281,39 +1076,6 @@ rules: - update - patch ---- -# Copyright 2019 The Knative Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -apiVersion: v1 -kind: Service -metadata: - name: imc-dispatcher - namespace: knative-eventing - labels: - eventing.knative.dev/release: "v0.20.0" - messaging.knative.dev/channel: in-memory-channel - messaging.knative.dev/role: dispatcher -spec: - selector: - messaging.knative.dev/channel: in-memory-channel - messaging.knative.dev/role: dispatcher - ports: - - name: http-dispatcher - port: 80 - protocol: TCP - targetPort: 8080 - --- # Copyright 2020 The Knative Authors # @@ -329,51 +1091,37 @@ spec: # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: v1 -kind: ServiceAccount -metadata: - name: imc-dispatcher - namespace: knative-eventing - labels: - eventing.knative.dev/release: "v0.20.0" - ---- -# Copyright 2019 The Knative Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding +kind: Role metadata: - name: imc-controller + namespace: knative-eventing + name: knative-inmemorychannel-webhook labels: - eventing.knative.dev/release: "v0.20.0" -subjects: - - kind: ServiceAccount - name: imc-controller - namespace: knative-eventing -roleRef: - kind: ClusterRole - name: imc-controller - apiGroup: rbac.authorization.k8s.io + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +rules: + # For manipulating certs into secrets. + - apiGroups: + - "" + resources: + - "secrets" + verbs: + - "get" + - "create" + - "update" + - "list" + - "watch" + - "patch" --- -# Copyright 2020 The Knative Authors +# Copyright 2021 The Knative Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -381,107 +1129,33 @@ roleRef: # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: imc-dispatcher - labels: - eventing.knative.dev/release: "v0.20.0" -subjects: - - kind: ServiceAccount - name: imc-dispatcher - namespace: knative-eventing -roleRef: - kind: ClusterRole - name: imc-dispatcher - apiGroup: rbac.authorization.k8s.io - ---- -# Copyright 2019 The Knative Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration metadata: - name: inmemorychannels.messaging.knative.dev + name: inmemorychannel.eventing.knative.dev labels: - eventing.knative.dev/release: "v0.20.0" - knative.dev/crd-install: "true" - messaging.knative.dev/subscribable: "true" - duck.knative.dev/addressable: "true" -spec: - group: messaging.knative.dev - versions: - - &version - name: v1beta1 - served: true - storage: false - subresources: - status: {} - schema: - openAPIV3Schema: - type: object - # this is a work around so we don't need to flush out the - # schema for each version at this time - # - # see issue: https://github.com/knative/serving/issues/912 - x-kubernetes-preserve-unknown-fields: true - additionalPrinterColumns: - - name: URL - type: string - jsonPath: .status.address.url - - name: Age - type: date - jsonPath: .metadata.creationTimestamp - - name: Ready - type: string - jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" - - name: Reason - type: string - jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" - - !!merge <<: *version - name: v1 - served: true - storage: true - names: - kind: InMemoryChannel - plural: inmemorychannels - singular: inmemorychannel - categories: - - all - - knative - - messaging - - channel - shortNames: - - imc - scope: Namespaced - conversion: - strategy: Webhook - webhook: - conversionReviewVersions: ["v1", "v1beta1"] - clientConfig: - service: - name: eventing-webhook - namespace: knative-eventing + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +webhooks: + - admissionReviewVersions: ["v1"] + clientConfig: + service: + name: inmemorychannel-webhook + namespace: knative-eventing + sideEffects: None + failurePolicy: Fail + name: inmemorychannel.eventing.knative.dev + timeoutSeconds: 10 --- -# Copyright 2019 The Knative Authors +# Copyright 2021 The Knative Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -489,60 +1163,33 @@ spec: # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: apps/v1 -kind: Deployment +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration metadata: - name: imc-controller - namespace: knative-eventing + name: validation.inmemorychannel.eventing.knative.dev labels: - eventing.knative.dev/release: "v0.20.0" - knative.dev/high-availability: "true" -spec: - selector: - matchLabels: &labels - messaging.knative.dev/channel: in-memory-channel - messaging.knative.dev/role: controller - template: - metadata: - labels: *labels - spec: - serviceAccountName: imc-controller - containers: - - name: controller - image: gcr.io/knative-releases/channel_controller-3a8067a195a7aaea27173c325c9c8070@sha256:28faaeafc5dcf36cc6f7454d004fa9ec2657ee261642128d1bb47a972540ce57 - env: - - name: CONFIG_LOGGING_NAME - value: config-logging - - name: CONFIG_OBSERVABILITY_NAME - value: config-observability - - name: METRICS_DOMAIN - value: knative.dev/inmemorychannel-controller - - name: SYSTEM_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: DISPATCHER_IMAGE - value: gcr.io/knative-releases/channel_dispatcher-9c2b1af1e7c55c4005384a6b4a52bbda@sha256:93a02d57daee7f848706277a970ce18dd9afbcc6cd65195796baaa7e06d52969 - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - securityContext: - allowPrivilegeEscalation: false - ports: - - name: metrics - containerPort: 9090 - - name: profiling - containerPort: 8008 + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +webhooks: + - admissionReviewVersions: ["v1"] + clientConfig: + service: + name: inmemorychannel-webhook + namespace: knative-eventing + sideEffects: None + failurePolicy: Fail + name: validation.inmemorychannel.eventing.knative.dev + timeoutSeconds: 10 --- -# Copyright 2019 The Knative Authors +# Copyright 2021 The Knative Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -550,69 +1197,15 @@ spec: # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: apps/v1 -kind: Deployment +apiVersion: v1 +kind: Secret metadata: - name: imc-dispatcher + name: inmemorychannel-webhook-certs namespace: knative-eventing labels: - eventing.knative.dev/release: "v0.20.0" - knative.dev/high-availability: "true" -spec: - selector: - matchLabels: &labels - messaging.knative.dev/channel: in-memory-channel - messaging.knative.dev/role: dispatcher - template: - metadata: - labels: *labels - spec: - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - podAffinityTerm: - labelSelector: - matchLabels: *labels - topologyKey: kubernetes.io/hostname - weight: 100 - serviceAccountName: imc-dispatcher - containers: - - name: dispatcher - image: gcr.io/knative-releases/channel_dispatcher-9c2b1af1e7c55c4005384a6b4a52bbda@sha256:93a02d57daee7f848706277a970ce18dd9afbcc6cd65195796baaa7e06d52969 - readinessProbe: &probe - failureThreshold: 3 - httpGet: - path: /healthz - port: 8080 - scheme: HTTP - periodSeconds: 2 - successThreshold: 1 - timeoutSeconds: 1 - livenessProbe: - !!merge <<: *probe - initialDelaySeconds: 5 - env: - - name: CONFIG_LOGGING_NAME - value: config-logging - - name: CONFIG_OBSERVABILITY_NAME - value: config-observability - - name: METRICS_DOMAIN - value: knative.dev/inmemorychannel-dispatcher - - name: SYSTEM_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: CONTAINER_NAME - value: dispatcher - ports: - - containerPort: 8080 - name: http - protocol: TCP - - containerPort: 9090 - name: metrics + eventing.knative.dev/release: "v1.1.0" + app.kubernetes.io/version: "1.1.0" + app.kubernetes.io/name: knative-eventing +# The data is populated at install time. --- diff --git a/examples/knative/powershell/kn-ps-zapier/Dockerfile b/examples/knative/powershell/kn-ps-zapier/Dockerfile new file mode 100644 index 00000000..1b16e1ef --- /dev/null +++ b/examples/knative/powershell/kn-ps-zapier/Dockerfile @@ -0,0 +1,5 @@ +FROM us.gcr.io/daisy-284300/veba/ce-ps-base:1.4 + +COPY handler.ps1 handler.ps1 + +CMD ["pwsh","./server.ps1"] diff --git a/examples/knative/powershell/kn-ps-zapier/README.md b/examples/knative/powershell/kn-ps-zapier/README.md new file mode 100644 index 00000000..2ca1916d --- /dev/null +++ b/examples/knative/powershell/kn-ps-zapier/README.md @@ -0,0 +1,101 @@ +# kn-ps-zapier +Example Knative PowerShell function for sending to a [Zapier webhook](https://zapier.com/page/webhooks/) when a failed vCenter Server login has been detected. + +# Step 1 - Build + +Create the container image locally to test your function logic. + +``` +export TAG= +docker build -t /kn-ps-zapier:${TAG} . +``` + +# 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 + +* ZAPIER_WEBHOOK_URL - Zapier webhook URL + +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-zapier:${TAG} +``` + +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 +Detected change to subject-123 ... +04/22/2022 22:52:00 - PowerShell HTTP server start listening on 'http://*:8080/' +04/22/2022 22:52:00 - Processing Init + +04/22/2022 22:52:00 - Init Processing Completed + +04/22/2022 22:52:00 - Starting HTTP CloudEvent listener +04/22/2022 22:52:08 - Sending Webhook payload to Zapier ... +04/22/2022 22:52:09 - Successfully sent Webhook ... +``` + +# 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-zapier:${TAG} +``` + +Update the `zapier_secret.json` file with your Zapier webhook configurations and then create the kubernetes secret which can then be accessed from within the function by using the environment variable named called `ZAPIER_SECRET`. + +```console +# create secret + +kubectl -n vmware-functions create secret generic zapier-secret --from-file=ZAPIER_SECRET=zapier_secret.json + +# update label for secret to show up in VEBA UI +kubectl -n vmware-functions label secret zapier-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 `com.vmware.sso.LoginFailure` 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 zapier-secret +``` \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-zapier/function.yaml b/examples/knative/powershell/kn-ps-zapier/function.yaml new file mode 100644 index 00000000..fb162832 --- /dev/null +++ b/examples/knative/powershell/kn-ps-zapier/function.yaml @@ -0,0 +1,39 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kn-ps-zapier + labels: + app: veba-ui +spec: + template: + metadata: + annotations: + autoscaling.knative.dev/maxScale: "1" + autoscaling.knative.dev/minScale: "1" + spec: + containers: + - image: us.gcr.io/daisy-284300/veba/kn-ps-zapier:1.0 + envFrom: + - secretRef: + name: zapier-secret + env: + - name: FUNCTION_DEBUG + value: "false" +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: veba-ps-zapier-trigger + labels: + app: veba-ui +spec: + broker: default + filter: + attributes: + type: com.vmware.event.router/eventex + subject: com.vmware.sso.LoginFailure + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kn-ps-zapier diff --git a/examples/knative/powershell/kn-ps-zapier/handler.ps1 b/examples/knative/powershell/kn-ps-zapier/handler.ps1 new file mode 100644 index 00000000..0441ea9f --- /dev/null +++ b/examples/knative/powershell/kn-ps-zapier/handler.ps1 @@ -0,0 +1,75 @@ +Function Process-Init { + [CmdletBinding()] + param() + Write-Host "$(Get-Date) - Processing Init`n" + + Write-Host "$(Get-Date) - Init Processing Completed`n" +} + +Function Process-Shutdown { + [CmdletBinding()] + param() + Write-Host "$(Get-Date) - Processing Shutdown`n" + + Write-Host "$(Get-Date) - Shutdown Processing Completed`n" +} + +Function Process-Handler { + [CmdletBinding()] + 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:ZAPIER_SECRET} | ConvertFrom-Json + } catch { + throw "`nK8s secrets `$env:ZAPIER_SECRET does not look to be defined" + } + + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: K8s Secrets:`n${env:ZAPIER_SECRET}`n" + + Write-Host "$(Get-Date) - DEBUG: CloudEventData`n $(${cloudEventData} | Out-String)`n" + } + + # Arguments is returned as an array of Key/Value objects + $arguments = ${cloudEventData}.Arguments + foreach ($argument in $arguments) { + if($argument.Key -eq "userIp") { + $userIp = $argument.Value + break + } + } + + # Construct Zapier payload + $payload = @{ + Username = $cloudEventData.UserName + UserIP = $userIp + TimeStamp = $cloudEventData.CreatedTime + } + + # Convert Zapier payload into JSON + $body = $payload | ConvertTo-Json -Depth 5 + + if(${env:FUNCTION_DEBUG} -eq "true") { + Write-Host "$(Get-Date) - DEBUG: `"$body`"" + } + + Write-Host "$(Get-Date) - Sending Webhook payload to Zapier ..." + $ProgressPreference = "SilentlyContinue" + + try { + Invoke-WebRequest -Uri $(${jsonSecrets}.ZAPIER_WEBHOOK_URL) -Method POST -ContentType "application/json" -Body $body + } catch { + throw "$(Get-Date) - Failed to send Zapier Message: $($_)" + } + + Write-Host "$(Get-Date) - Successfully sent Webhook ..." +} diff --git a/examples/knative/powershell/kn-ps-zapier/test/docker-test-env-variable b/examples/knative/powershell/kn-ps-zapier/test/docker-test-env-variable new file mode 100644 index 00000000..22adee18 --- /dev/null +++ b/examples/knative/powershell/kn-ps-zapier/test/docker-test-env-variable @@ -0,0 +1 @@ +ZAPIER_SECRET={"ZAPIER_WEBHOOK_URL":"YOUR_WEBHOOK_URL"} \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-zapier/test/send-cloudevent-test.ps1 b/examples/knative/powershell/kn-ps-zapier/test/send-cloudevent-test.ps1 new file mode 100644 index 00000000..5e717cb6 --- /dev/null +++ b/examples/knative/powershell/kn-ps-zapier/test/send-cloudevent-test.ps1 @@ -0,0 +1,16 @@ + +$headers = @{ + "Content-Type" = "application/json"; + "ce-specversion" = "1.0"; + "ce-id" = "d70079f9-fddd-4b7f-aa76-1193f28b0611"; + "ce-source" = "https://vcenter.local/sdk"; + "ce-type" = "com.vmware.event.router/eventex"; + "ce-subject" = "com.vmware.sso.LoginFailure"; +} + +$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-zapier/test/send-cloudevent-test.sh b/examples/knative/powershell/kn-ps-zapier/test/send-cloudevent-test.sh new file mode 100755 index 00000000..22436474 --- /dev/null +++ b/examples/knative/powershell/kn-ps-zapier/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/eventex' \ + -H 'ce-subject: com.vmware.sso.LoginFailure' \ + -X POST localhost:8080 + +echo "See docker container console for output" diff --git a/examples/knative/powershell/kn-ps-zapier/test/test-payload.json b/examples/knative/powershell/kn-ps-zapier/test/test-payload.json new file mode 100644 index 00000000..4a460bb0 --- /dev/null +++ b/examples/knative/powershell/kn-ps-zapier/test/test-payload.json @@ -0,0 +1,44 @@ +{ + "Key": 9766460, + "ChainId": 9766460, + "CreatedTime": "2022-04-22T21:54:38.628999Z", + "UserName": "hacker@vsphere.local", + "Datacenter": null, + "ComputeResource": null, + "Host": null, + "Vm": null, + "Ds": null, + "Net": null, + "Dvs": null, + "FullFormattedMessage": "Failed login hacker@vsphere.local from 10.0.1.337 at 04/22/2022 21:53:06 GMT in SSO", + "ChangeTag": "", + "EventTypeId": "com.vmware.sso.LoginFailure", + "Severity": "info", + "Message": "", + "Arguments": [ + { + "Key": "userName", + "Value": "hacker@vsphere.local" + }, + { + "Key": "description", + "Value": "User hacker@vsphere.local@10.0.1.337 failed to log in with response code 401" + }, + { + "Key": "userIp", + "Value": "10.0.1.337" + }, + { + "Key": "timestamp", + "Value": "04/22/2022 21:53:06 GMT" + }, + { + "Key": "_sourcehost_", + "Value": "vcsa.primp-industries.local" + } + ], + "ObjectId": "", + "ObjectType": "", + "ObjectName": "", + "Fault": null + } \ No newline at end of file diff --git a/examples/knative/powershell/kn-ps-zapier/zapier_secret.json b/examples/knative/powershell/kn-ps-zapier/zapier_secret.json new file mode 100644 index 00000000..c301262f --- /dev/null +++ b/examples/knative/powershell/kn-ps-zapier/zapier_secret.json @@ -0,0 +1,3 @@ +{ + "ZAPIER_WEBHOOK_URL": "YOUR_WEBHOOK_URL" +} \ No newline at end of file diff --git a/examples/knative/python/kn-py-echo/README.md b/examples/knative/python/kn-py-echo/README.md index 05822251..5d934eb3 100644 --- a/examples/knative/python/kn-py-echo/README.md +++ b/examples/knative/python/kn-py-echo/README.md @@ -7,7 +7,7 @@ Example Python function with `Flask` REST API running in Knative to echo [Buildpacks](https://buildpacks.io) are used to create the container image. ```bash -IMAGE=/kn-py-echo:1.0 +IMAGE=/kn-py-echo:1.1 pack build -B gcr.io/buildpacks/builder:v1 ${IMAGE} ``` @@ -16,7 +16,7 @@ pack build -B gcr.io/buildpacks/builder:v1 ${IMAGE} 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 +docker run -e PORT=8080 -it --rm -p 8080:8080 /kn-py-echo:1.1 ``` You should see output similar to the following: ``` @@ -72,7 +72,7 @@ Return to the previous terminal window where you started the docker image, and y 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 +docker push /kn-py-echo:1.1 ``` 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. diff --git a/examples/knative/python/kn-py-echo/function.yaml b/examples/knative/python/kn-py-echo/function.yaml index 73546172..c83e7a9c 100644 --- a/examples/knative/python/kn-py-echo/function.yaml +++ b/examples/knative/python/kn-py-echo/function.yaml @@ -12,7 +12,7 @@ spec: autoscaling.knative.dev/minScale: "1" spec: containers: - - image: us.gcr.io/daisy-284300/veba/kn-py-echo:1.0 + - image: us.gcr.io/daisy-284300/veba/kn-py-echo:1.1 --- apiVersion: eventing.knative.dev/v1 kind: Trigger diff --git a/examples/knative/python/kn-py-echo/requirements.txt b/examples/knative/python/kn-py-echo/requirements.txt index c2e97b25..24025336 100644 --- a/examples/knative/python/kn-py-echo/requirements.txt +++ b/examples/knative/python/kn-py-echo/requirements.txt @@ -1,2 +1,14 @@ -flask==1.1.2 cloudevents==1.2.0 + deprecation==2.1.0 + packaging==21.3 + pyparsing==3.0.9 +Flask==2.1.2 + click==8.1.3 + importlib-metadata==4.11.4 + zipp==3.8.0 + itsdangerous==2.1.2 + Jinja2==3.1.2 + MarkupSafe==2.1.1 + Werkzeug==2.1.2 +pip==21.1 +setuptools==49.2.1 diff --git a/examples/knative/python/kn-py-slack/README.md b/examples/knative/python/kn-py-slack/README.md index fbeefe69..5f661927 100644 --- a/examples/knative/python/kn-py-slack/README.md +++ b/examples/knative/python/kn-py-slack/README.md @@ -6,7 +6,7 @@ Example Knative Python function for sending to a Slack webhook when a Virtual Ma [Buildpacks](https://buildpacks.io) are used to create the container image. ```bash -IMAGE=/kn-py-slack:1.0 +IMAGE=/kn-py-slack:1.1 pack build -B gcr.io/buildpacks/builder:v1 ${IMAGE} ``` @@ -25,7 +25,7 @@ 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 +docker run -e PORT=8080 --env-file docker-test-env-variable -it --rm -p 8080:8080 /kn-py-slack:1.1 Serving Flask app "handler.py" (lazy loading) * Environment: development @@ -73,7 +73,7 @@ Finally, check your Slack channel to see if the test event posted. 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 +docker push /kn-py-slack:1.1 ``` 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`. diff --git a/examples/knative/python/kn-py-slack/function.yaml b/examples/knative/python/kn-py-slack/function.yaml index 0d22fadd..5081dc20 100644 --- a/examples/knative/python/kn-py-slack/function.yaml +++ b/examples/knative/python/kn-py-slack/function.yaml @@ -12,7 +12,7 @@ spec: autoscaling.knative.dev/minScale: "1" spec: containers: - - image: us.gcr.io/daisy-284300/veba/kn-py-slack:1.0 + - image: us.gcr.io/daisy-284300/veba/kn-py-slack:1.1 envFrom: - secretRef: name: slack-secret diff --git a/examples/knative/python/kn-py-slack/handler.py b/examples/knative/python/kn-py-slack/handler.py index 16ac1ddb..119dbba2 100644 --- a/examples/knative/python/kn-py-slack/handler.py +++ b/examples/knative/python/kn-py-slack/handler.py @@ -8,7 +8,7 @@ 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') +url = os.environ.get('SLACK_SECRET') @app.route('/', methods=['POST']) def slack(): diff --git a/examples/knative/python/kn-py-slack/requirements.txt b/examples/knative/python/kn-py-slack/requirements.txt index 65f72562..54c1ccb6 100644 --- a/examples/knative/python/kn-py-slack/requirements.txt +++ b/examples/knative/python/kn-py-slack/requirements.txt @@ -1,3 +1,19 @@ -flask==1.1.2 cloudevents==1.2.0 -requests==2.20.0 \ No newline at end of file + deprecation==2.1.0 + packaging==21.3 + pyparsing==3.0.9 +Flask==2.1.2 + click==8.1.3 + importlib-metadata==4.11.4 + zipp==3.8.0 + itsdangerous==2.1.2 + Jinja2==3.1.2 + MarkupSafe==2.1.1 + Werkzeug==2.1.2 +pip==21.1 +requests==2.27.1 + certifi==2022.5.18.1 + chardet==3.0.4 + idna==2.7 + urllib3==1.26.5 +setuptools==49.2.1 diff --git a/examples/knative/python/kn-py-vm-attr/function.yaml b/examples/knative/python/kn-py-vm-attr/function.yaml index c6060ce5..41fa6c7b 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: us.gcr.io/daisy-284300/veba/kn-py-vm-attr:1.0 + - image: us.gcr.io/daisy-284300/veba/kn-py-vm-attr:1.1 envFrom: - secretRef: name: vcconfig-secret diff --git a/examples/knative/python/kn-py-vm-attr/requirements.txt b/examples/knative/python/kn-py-vm-attr/requirements.txt index d0ace9b9..b5cd185d 100644 --- a/examples/knative/python/kn-py-vm-attr/requirements.txt +++ b/examples/knative/python/kn-py-vm-attr/requirements.txt @@ -1,4 +1,22 @@ -flask==1.1.4 cloudevents==1.2.0 + deprecation==2.1.0 + packaging==21.3 + pyparsing==3.0.9 +Flask==2.1.2 + click==8.1.3 + importlib-metadata==4.11.4 + zipp==3.8.0 + itsdangerous==2.1.2 + Jinja2==3.1.2 + MarkupSafe==2.1.1 + Werkzeug==2.1.2 +pip==21.1 +python-dotenv==0.17.1 pyvmomi==7.0.2 -python-dotenv==0.17.1 \ No newline at end of file + requests==2.27.1 + certifi==2022.5.18.1 + charset-normalizer==2.0.12 + idna==3.3 + urllib3==1.26.9 + six==1.16.0 +setuptools==49.2.1 diff --git a/examples/knative/python/kn-py-vro/README.md b/examples/knative/python/kn-py-vro/README.md index cd96b42d..95862bdc 100644 --- a/examples/knative/python/kn-py-vro/README.md +++ b/examples/knative/python/kn-py-vro/README.md @@ -35,7 +35,7 @@ Requirements: - Docker ```bash -export IMAGE=/kn-py-vro:1.0 +export IMAGE=/kn-py-vro:1.1 pack build --builder gcr.io/buildpacks/builder:v1 ${IMAGE} ``` diff --git a/examples/knative/python/kn-py-vro/function.yaml b/examples/knative/python/kn-py-vro/function.yaml index fa01ba87..873a7e35 100644 --- a/examples/knative/python/kn-py-vro/function.yaml +++ b/examples/knative/python/kn-py-vro/function.yaml @@ -12,7 +12,7 @@ spec: autoscaling.knative.dev/minScale: "1" spec: containers: - - image: us.gcr.io/daisy-284300/veba/kn-py-vro:1.0 + - image: us.gcr.io/daisy-284300/veba/kn-py-vro:1.1 envFrom: - secretRef: name: vroconfig-secret diff --git a/examples/knative/python/kn-py-vro/requirements.txt b/examples/knative/python/kn-py-vro/requirements.txt index 8198d74c..906a33f6 100644 --- a/examples/knative/python/kn-py-vro/requirements.txt +++ b/examples/knative/python/kn-py-vro/requirements.txt @@ -1,7 +1,22 @@ -flask==2.0.2 -werkzeug==2.0.2 cloudevents==1.2.0 -urllib3==1.26.7 -requests==2.26.0 + deprecation==2.1.0 + packaging==21.3 + pyparsing==3.0.9 +Flask==2.1.2 + click==8.1.3 + importlib-metadata==4.11.4 + zipp==3.8.0 + itsdangerous==2.1.2 + Jinja2==3.1.2 + MarkupSafe==2.1.1 + Werkzeug==2.1.2 +pip==21.1 python-dateutil==2.8.2 -regex==2021.11.10 \ No newline at end of file + six==1.16.0 +regex==2021.11.10 +requests==2.26.0 + certifi==2022.5.18.1 + charset-normalizer==2.0.12 + idna==3.3 + urllib3==1.26.7 +setuptools==49.2.1 diff --git a/files/getOvfProperty.py b/files/getOvfProperty.py index 69505a50..c859349a 100755 --- a/files/getOvfProperty.py +++ b/files/getOvfProperty.py @@ -1,10 +1,13 @@ #!/usr/bin/env python3 -import subprocess +from subprocess import Popen, PIPE import sys from xml.dom.minidom import parseString +import xml.parsers.expat +import re -ovfenv_cmd="/usr/bin/vmtoolsd --cmd 'info-get guestinfo.ovfEnv'" + +ovfenv_cmd = "/usr/bin/vmtoolsd --cmd 'info-get guestinfo.ovfEnv'" def debug(s): @@ -17,16 +20,16 @@ def get_ovf_properties(): Return a dict of OVF properties in the ovfenv """ properties = {} - xml_parts = subprocess.Popen(ovfenv_cmd, shell=True, - stdout=subprocess.PIPE).stdout.read() + xml_parts = Popen(ovfenv_cmd, shell=True, + stdout=PIPE).stdout.read() try: raw_data = parseString(xml_parts) except xml.parsers.expat.ExpatError as err: - debug(e) + debug(xml.parsers.expat.ErrorString(err.code)) sys.exit(1) for property in raw_data.getElementsByTagName('Property'): - key, value = [ property.attributes['oe:key'].value, - property.attributes['oe:value'].value ] + key, value = [property.attributes['oe:key'].value, + property.attributes['oe:value'].value] properties[key] = value return properties @@ -35,18 +38,22 @@ def main(argv): if len(argv) != 1: debug('usage: getOvfProperty.py > ${PROXY_CONF} + echo "HTTP_PROXY='${HTTP_PROXY_URL}'" >> ${PROXY_CONF} sed -i "/^\[Install\]/i Environment=HTTP_PROXY=${HTTP_PROXY_URL}" ${CONTAINERD_CONF} else echo -e "\e[91mInvalid HTTP Proxy URL supplied" > /dev/console @@ -49,7 +49,7 @@ if [ -n "${HTTP_PROXY}" ] || [ -n "${HTTPS_PROXY}" ]; then else HTTPS_PROXY_URL="${HTTPS_PROXY_PROTOCOL}://${HTTPS_PROXY_SERVER_PORT}" fi - echo "HTTPS_PROXY=\"${HTTPS_PROXY_URL}\"" >> ${PROXY_CONF} + echo "HTTPS_PROXY='${HTTPS_PROXY_URL}'" >> ${PROXY_CONF} sed -i "/^\[Install\]/i Environment=HTTPS_PROXY=${HTTPS_PROXY_URL}" ${CONTAINERD_CONF} else echo -e "\e[91mInvalid HTTPS Proxy URL supplied" > /dev/console diff --git a/files/setup.sh b/files/setup.sh index 661ad6d3..cbf93969 100755 --- a/files/setup.sh +++ b/files/setup.sh @@ -94,6 +94,8 @@ else ESCAPED_WEBHOOK_USERNAME=$(eval echo -n '${WEBHOOK_USERNAME}' | jq -Rs .) ESCAPED_WEBHOOK_PASSWORD=$(eval echo -n '${WEBHOOK_PASSWORD}' | jq -Rs .) + ESCAPED_PROXY_PASSWORD=$(eval echo -n '${PROXY_PASSWORD}' | jq -Rs .) + cat > /root/config/veba-config.json < ' \"" + + +def test_doublequotedvalue(capsys): + with pytest.raises(SystemExit) as pytest_wrapped_e: + getOvfProperty.main(["guestinfo.doublequotedvalue"]) + captured = capsys.readouterr() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 0 + assert captured.out == 'Hello World' + + +def test_singlequotedvalue(capsys): + with pytest.raises(SystemExit) as pytest_wrapped_e: + getOvfProperty.main(["guestinfo.singlequotedvalue"]) + captured = capsys.readouterr() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 0 + assert captured.out == 'Hello World' + + +def test_mismatchedquotesvalue(capsys): + with pytest.raises(SystemExit) as pytest_wrapped_e: + getOvfProperty.main(["guestinfo.mismatchedquotes"]) + captured = capsys.readouterr() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 0 + assert captured.out == '\'Hello World"' + + +def test_onequotevalue(capsys): + with pytest.raises(SystemExit) as pytest_wrapped_e: + getOvfProperty.main(["guestinfo.onequote"]) + captured = capsys.readouterr() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 0 + assert captured.out == 'Hello World"' + + +def test_passwordvalue(capsys): + with pytest.raises(SystemExit) as pytest_wrapped_e: + getOvfProperty.main(["guestinfo.test_password"]) + captured = capsys.readouterr() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 0 + assert captured.out == '"My&Quoted!Password"' + + +# Patch the ovfenv_cmd global to return a dumy ovf env XML +@pytest.fixture(autouse=True) +def ovfenv(monkeypatch): + monkeypatch.setattr(getOvfProperty, 'ovfenv_cmd', """cat << 'EOF' + + + + VMware ESXi + 7.0.3 + VMware, Inc. + en_US + + + + + + + + + + + + + + + + +EOF +""") diff --git a/manual/photon.xml.template b/manual/photon.xml.template index 1de4762a..b17c6963 100644 --- a/manual/photon.xml.template +++ b/manual/photon.xml.template @@ -37,11 +37,11 @@ Proxy Settings (optional) - Enter HTTP Proxy URL followed by the port. Example: "http://proxy.provider.com:3128" + Enter HTTP Proxy URL followed by the port. Example: http://proxy.provider.com:3128 - Enter HTTPS Proxy URL followed by the port. Example: "https://proxy.provider.com:3128" + Enter HTTPS Proxy URL followed by the port. Example: https://proxy.provider.com:3128 diff --git a/veba-bom.json b/veba-bom.json index cbde0513..a0a10438 100644 --- a/veba-bom.json +++ b/veba-bom.json @@ -1,19 +1,19 @@ { "veba": { - "version": "v0.7.2" + "version": "v0.7.3" }, "vmware-event-router": { - "gitRepoTag": "v0.7.2", + "gitRepoTag": "v0.7.3", "containers": [{ "name": "us.gcr.io/daisy-284300/veba/router", - "version": "v0.7.2" + "version": "v0.7.3" }] }, "veba-ui": { - "gitRepoTag": "v0.6.0", + "gitRepoTag": "v0.0.0", "containers": [{ "name": "projects.registry.vmware.com/veba/veba-ui", - "version": "3e1f828b" + "version": "27b4e224" }] }, "antrea": { diff --git a/vmware-event-router/README.MD b/vmware-event-router/README.MD index 63dea988..21073dab 100644 --- a/vmware-event-router/README.MD +++ b/vmware-event-router/README.MD @@ -35,7 +35,7 @@ to normalize events from the supported event `providers`. See - with the [Horizon event provider](#provider-type-horizon) - with the [vcsim event provider](#provider-type-vcsim) -**Note:** All implemented event `processors` use built-in retry mechanisms so +> **Note:** All implemented event `processors` use built-in retry mechanisms so your function might still be involved multiple times depending on its response code. However, if an event `provider` crashes before sending an event to the configured `processor` or when the `processor` returns an error, the event is @@ -49,8 +49,8 @@ not retried and discarded. router crashes **within seconds** right after startup and having received *n* events but before creating the first valid checkpoint (current checkpoint interval is 5s) - If an event cannot be successfully delivered (retried) by an event `processor` it is - logged and discarded, i.e. there is currently no support for dead letter - queues (see note below) + logged and discarded, i.e. there is currently no support for [dead letter + queues](https://en.wikipedia.org/wiki/Dead_letter_queue) (see note below) - Retries in the [OpenFaaS event processor](#processor-type-openfaas) are only supported when running in synchronous mode, i.e. `async: false` (see this OpenFaaS [issue](https://github.com/openfaas/nats-queue-worker/issues/84)) @@ -93,7 +93,7 @@ version: The following sections describe the layout of the configuration file (YAML) and specific options for the supported event `providers`, `processors` and `metrics` -endpoint. Configuration examples are provided [here](deploy/). +endpoint. Configuration examples are provided [here](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/vmware-event-router/deploy). > **Note:** Currently only one event `provider` and one event `processor` can be > configured at a time, e.g. one vCenter Server instance streaming events to @@ -151,7 +151,7 @@ metricsProvider: ## JSON Schema Validation In order to simplify the configuration and validation of the YAML configuration -file a JSON schema [file](README.MD) is provided. Many editors/IDEs offer +file a JSON schema [file](https://github.com/vmware-samples/vcenter-event-broker-appliance/blob/master/vmware-event-router/routerconfig.schema.json) is provided. Many editors/IDEs offer support for registering a schema file, e.g. [Jetbrains](https://www.jetbrains.com/help/rider/Settings_Languages_JSON_Schema.html) and [VS @@ -382,15 +382,21 @@ only forwards events configured in the associated `rule` of an event bus. Rules in AWS EventBridge use pattern matching ([docs](https://docs.aws.amazon.com/eventbridge/latest/userguide/filtering-examples-structure.html)). Upon start, VMware Event Router contacts EventBridge (using the given IAM role) -to parse and extract event categories from the configured rule ARN (see -configuration option below). +to parse the configured rule ARN (see configuration option below). -The VMware Event Router uses the `"subject"` field in the event payload to store -the event category, e.g. `"VmPoweredOnEvent"`. Thus it is required that you use -a **specific pattern match** (`"detail->subject"`) that the VMware Event Router -can parse to retrieve the desired event (forwarding) categories. For example, -the following AWS EventBridge event pattern rule matches power on/off events -(including DRS-enabled clusters): +The VMware Event Router uses the pattern match library which supports a subset +of the EventBridge pattern rules. You may only use these supported patterns in +your specified EventBridge rule. Refer to [this +page](https://github.com/timbray/quamina/blob/v0.2.0/PATTERNS.md) for the +currently supported patterns in `Quamina`. + +> **Note:** EventBridge wraps each VMware Event Router event (CloudEvent) into +> an EventBridge message envelop. The `detail` field contains the JSON +> representation of the full CloudEvent as produced by the VMware Event Router. + +The following examples show supported and useful patterns. + +Example: Forward all CloudEvents containing one of the specified `subjects`: ```json { @@ -400,10 +406,34 @@ the following AWS EventBridge event pattern rule matches power on/off events } ``` -`"subject"` can contain one or more event categories. Wildcards (`"*"`) are not -supported. If one wants to modify the event pattern match rule **after** -deploying the VMware Event Router, its internal rules cache is periodically -synchronized with AWS EventBridge at a fixed interval of 5 minutes. +Example: Forward all CloudEvents containing a `subject` with the prefix `Vm`: + +```json +{ + "detail": { + "subject": [{ + "shellstyle": "Vm*" + }] + } +} +``` + +Example: Forward all CloudEvents containing virtual machines with the prefix +`Linux`: + +```json +{ + "detail": { + "data": { + "Vm": { + "Name": [{ + "shellstyle": "Linux*" + }] + } + } + } +} +``` > **Note:** A list of event names (categories) and how to retrieve them can be > found @@ -417,7 +447,7 @@ as an event `processor`. | `region` | String | AWS region to use, see [regions doc](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html). | true | `us-west-1` | | `eventBus` | String | Name of the event bus to use | true | `default` or `arn:aws:events:us-west-1:1234567890:event-bus/customBus` | | `ruleARN` | String | Rule ARN to use for event pattern matching | true | `arn:aws:events:us-west-1:1234567890:rule/vmware-event-router` | -| `` | Object | AWS IAM role credentials | true | (see `aws_access_key` example below) | +| `` | Object | AWS IAM role credentials | true | (see `aws_access_key` and `aws_iam_role` examples below) | ## The `auth` section @@ -444,6 +474,9 @@ Supported providers/processors: ### Type `aws_access_key` +Use an AWS IAM role with the provided access key ID and secret access key for +authentication. + Supported providers/processors: - `aws_event_bridge` @@ -455,8 +488,48 @@ Supported providers/processors: | `awsAccessKeyAuth.accessKey` | String | Access Key ID for the IAM role used | true | `ABCDEFGHIJK` | | `awsAccessKeyAuth.secretKey` | String | Secret Access Key for the IAM role used | true | `ZYXWVUTSRQPO` | -> **Note:** Currently only IAM user accounts with access key/secret are -> supported to authenticate against AWS EventBridge. Please follow the [user +> **Note:** Please follow the EventBridge IAM [user +> guide](https://docs.aws.amazon.com/eventbridge/latest/userguide/getting-set-up-eventbridge.html) +> before deploying the event router. Further information can also be found in +> the +> [authentication](https://docs.aws.amazon.com/eventbridge/latest/userguide/auth-and-access-control-eventbridge.html#authentication-eventbridge) +> section. + +In addition to the recommendation in the AWS EventBridge user guide you might +want to lock down the IAM role for the VMware Event Router and scope it to these +permissions ("Action"): + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "events:PutEvents", + "events:ListRules", + "events:TestEventPattern" + ], + "Resource": "*" + } + ] +} +``` + +### Type `aws_iam_role` + +Use an AWS IAM role configured from the shared credentials file. + +Supported providers/processors: + +- `aws_event_bridge` + +| Field | Type | Description | Required | Example | +|--------|--------|------------------------------|----------|----------------| +| `type` | String | Authentication method to use | true | `aws_iam_role` | + +> **Note:** Please follow the EventBridge IAM [user > guide](https://docs.aws.amazon.com/eventbridge/latest/userguide/getting-set-up-eventbridge.html) > before deploying the event router. Further information can also be found in > the @@ -546,7 +619,7 @@ using the Knative backend. ### Helm Deployment -The Helm files are located in the [chart](chart/) directory. The `values.yaml` +The Helm files are located in the [chart](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/vmware-event-router/chart) directory. The `values.yaml` file contains the allowed parameters and parameter descriptions which map to the VMware Event Router [configuration](#overview-configuration-file-structure-yaml) file. @@ -710,7 +783,7 @@ Create a namespace where the VMware Event Router will be deployed to: $ kubectl create namespace vmware ``` -Use one of the configuration files provided [here](deploy/) to configure the +Use one of the configuration files provided [here](https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/master/vmware-event-router/deploy) to configure the router with **one** VMware vCenter Server `eventProvider` and **one** OpenFaaS **or** AWS EventBridge `eventProcessor`. Change the values to match your environment. The following example will use the OpenFaaS config sample. diff --git a/vmware-event-router/chart/Chart.yaml b/vmware-event-router/chart/Chart.yaml index 25e7056d..bc23e0c2 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.7.3 -appVersion: v0.7.2 +version: v0.7.4 +appVersion: v0.7.3 maintainers: - name: Michael Gasch email: mgasch@vmware.com diff --git a/vmware-event-router/chart/releases/event-router-v0.7.3-pre-release.tgz b/vmware-event-router/chart/releases/event-router-v0.7.3-pre-release.tgz index 41961e74..ebe2c8a7 100644 Binary files a/vmware-event-router/chart/releases/event-router-v0.7.3-pre-release.tgz and b/vmware-event-router/chart/releases/event-router-v0.7.3-pre-release.tgz differ diff --git a/vmware-event-router/cmd/schemagen/main.go b/vmware-event-router/cmd/schemagen/main.go index 4865da76..155ba8fd 100644 --- a/vmware-event-router/cmd/schemagen/main.go +++ b/vmware-event-router/cmd/schemagen/main.go @@ -18,7 +18,6 @@ func main() { s := jsonschema.Reflect(&config.RouterConfig{}) b, err := s.MarshalJSON() - if err != nil { log.Fatalf("could not marshal to JSON: %v", err) } diff --git a/vmware-event-router/deploy/event-router-config-vcenter-aws-iam-role-auth.yaml b/vmware-event-router/deploy/event-router-config-vcenter-aws-iam-role-auth.yaml new file mode 100644 index 00000000..ca995201 --- /dev/null +++ b/vmware-event-router/deploy/event-router-config-vcenter-aws-iam-role-auth.yaml @@ -0,0 +1,38 @@ +apiVersion: event-router.vmware.com/v1alpha1 +kind: RouterConfig +metadata: + name: router-config-vcenter-aws + labels: + key: value +eventProvider: + type: vcenter + name: veba-demo-vc-01 + vcenter: + address: https://my-vcenter01.domain.local/sdk + insecureSSL: false + checkpoint: false + auth: + type: basic_auth + basicAuth: + username: administrator@vsphere.local + password: ReplaceMe +eventProcessor: + type: aws_event_bridge + name: veba-demo-aws + awsEventBridge: + eventBus: default + region: us-west-1 + ruleARN: arn:aws:events:us-west-1:1234567890:rule/vmware-event-router + auth: + type: aws_iam_role +metricsProvider: + type: default + name: veba-demo-metrics + default: + bindAddress: "0.0.0.0:8082" +# disabling auth for the metrics endpoint +# auth: +# type: basic_auth +# basicAuth: +# username: admin +# password: ReplaceMe diff --git a/vmware-event-router/go.mod b/vmware-event-router/go.mod index fd6a8fe3..a0a035a4 100644 --- a/vmware-event-router/go.mod +++ b/vmware-event-router/go.mod @@ -18,10 +18,11 @@ require ( github.com/openfaas-incubator/connector-sdk v0.0.0-20200902074656-7f648543d4aa github.com/openfaas/faas-provider v0.15.1 github.com/pkg/errors v0.9.1 + github.com/timbray/quamina v0.2.0 github.com/vmware/govmomi v0.24.1-0.20210210035757-ed60338583b0 go.uber.org/zap v1.16.0 - golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 - gotest.tools v2.2.0+incompatible + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 + gotest.tools/v3 v3.2.0 k8s.io/api v0.18.8 k8s.io/apimachinery v0.18.8 k8s.io/client-go v0.18.8 @@ -42,10 +43,10 @@ require ( github.com/fatih/color v1.10.0 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/go-logr/logr v0.1.0 // indirect - github.com/gogo/protobuf v1.3.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.4.3 // indirect - github.com/google/go-cmp v0.5.2 // indirect + github.com/google/go-cmp v0.5.5 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/googleapis/gnostic v0.4.0 // indirect diff --git a/vmware-event-router/go.sum b/vmware-event-router/go.sum index cf3349e8..13a13dd3 100644 --- a/vmware-event-router/go.sum +++ b/vmware-event-router/go.sum @@ -256,8 +256,9 @@ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFG github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -307,8 +308,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -413,6 +415,7 @@ github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dv github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= @@ -608,6 +611,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P 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/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/timbray/quamina v0.2.0 h1:18Bn0FwxBVGcqB/+iTvl/ltjlaOHuSqG+nK9PoAerlQ= +github.com/timbray/quamina v0.2.0/go.mod h1:ThK75zJCw/UZzxE4a1jReh0VBK+OVJbHUBhNSJh87KE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tsenart/go-tsz v0.0.0-20180814232043-cdeb9e1e981e/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo= github.com/tsenart/vegeta/v12 v12.8.4/go.mod h1:ZiJtwLn/9M4fTPdMY7bdbIeyNeFVE8/AHbWFqCsUuho= @@ -752,6 +757,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -768,8 +774,9 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -822,8 +829,10 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -892,12 +901,15 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3 h1:DywqrEscRX7O2phNjkT0L6lhHKGBoMLCNX+XcAe7t6s= golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1035,6 +1047,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= +gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/vmware-event-router/internal/config/v1alpha1/auth.go b/vmware-event-router/internal/config/v1alpha1/auth.go index a2978eeb..ea713bca 100644 --- a/vmware-event-router/internal/config/v1alpha1/auth.go +++ b/vmware-event-router/internal/config/v1alpha1/auth.go @@ -8,6 +8,10 @@ const ( BasicAuth AuthMethodType = "basic_auth" // AWSAccessKeyAuth represents the AWS IAM authentication method using access key and secret key AWSAccessKeyAuth AuthMethodType = "aws_access_key" + // AWSIAMRoleAuth represents the AWS IAM authentication method using temporary credentials provided + // by Security Token Service (STS). Intended for use as IAM role with a Kubernetes service account + // for use case of running under the Amazon EKS. + AWSIAMRoleAuth AuthMethodType = "aws_iam_role" // ActiveDirectory represents the MS Active Directory domain/user/password scheme ActiveDirectory AuthMethodType = "active_directory" ) @@ -15,7 +19,7 @@ const ( // AuthMethod configures authentication data type AuthMethod struct { // Type sets the authentication method - Type AuthMethodType `yaml:"type" json:"type" jsonschema:"enum=basic_auth,enum=aws_access_key,enum=active_directory,default=basic_auth,description=The authentication method to use"` + Type AuthMethodType `yaml:"type" json:"type" jsonschema:"enum=basic_auth,enum=aws_access_key,enum=aws_iam_role,enum=active_directory,default=basic_auth,description=The authentication method to use"` // +optional BasicAuth *BasicAuthMethod `yaml:"basicAuth,omitempty" json:"basicAuth,omitempty" jsonschema:"oneof_required=basicAuth,description=Basic authentication with username and password"` // +optional diff --git a/vmware-event-router/internal/config/v1alpha1/processor.go b/vmware-event-router/internal/config/v1alpha1/processor.go index 10ff6ee0..9682d45b 100644 --- a/vmware-event-router/internal/config/v1alpha1/processor.go +++ b/vmware-event-router/internal/config/v1alpha1/processor.go @@ -56,8 +56,8 @@ type ProcessorConfigEventBridge struct { // RuleARN is the ARN of the rule to use for configuring pattern matching and event forwarding // TODO (@mgasch): deprecate and support 1..n rules per given eventbus RuleARN string `yaml:"ruleARN" json:"ruleARN" jsonschema:"required,default=arn:aws:events:us-west-1:1234567890:rule/vmware-event-router"` - // Auth sets the AWS authentication credentials. Only aws_access_key is - // supported. + // Auth sets the AWS authentication credentials. Only aws_access_key or + // aws_iam_role is supported. Auth *AuthMethod `yaml:"auth,omitempty" json:"auth,omitempty" jsonschema:"oneof_required=auth,description=Authentication configuration for this section"` } diff --git a/vmware-event-router/internal/events/events_test.go b/vmware-event-router/internal/events/events_test.go index c3d9a25a..3129e237 100644 --- a/vmware-event-router/internal/events/events_test.go +++ b/vmware-event-router/internal/events/events_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package events @@ -9,7 +8,7 @@ import ( cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/vmware/govmomi/vim25/types" - "gotest.tools/assert" + "gotest.tools/v3/assert" ) func Test_GetEventDetails(t *testing.T) { diff --git a/vmware-event-router/internal/integration/aws_test.go b/vmware-event-router/internal/integration/aws_test.go index ef0ee8af..7410a5aa 100644 --- a/vmware-event-router/internal/integration/aws_test.go +++ b/vmware-event-router/internal/integration/aws_test.go @@ -1,8 +1,9 @@ -// +build integration,aws +//go:build integration && aws package integration_test import ( + "sync/atomic" "time" cloudevents "github.com/cloudevents/sdk-go/v2" @@ -16,7 +17,8 @@ import ( var _ = Describe("AWS Processor", func() { BeforeEach(func() { - p, err := aws.NewEventBridgeProcessor(ctx, cfg, receiver, log) + ebClient = createClient(cfg) + p, err := aws.NewEventBridgeProcessor(ctx, cfg, receiver, log, aws.WithClient(ebClient)) Expect(err).NotTo(HaveOccurred()) awsProcessor = p @@ -47,6 +49,11 @@ var _ = Describe("AWS Processor", func() { It("should not error", func() { Expect(err).ShouldNot(HaveOccurred()) }) + + It("should have sent the event", func() { + current := atomic.LoadInt32(&ebClient.sent) + Expect(current).To(Equal(int32(1))) + }) }) Context("when the EventBridge rule pattern does not match the given event type (LicenseEvent)", func() { diff --git a/vmware-event-router/internal/integration/events_test.go b/vmware-event-router/internal/integration/events_test.go index 896408ca..ece9b01c 100644 --- a/vmware-event-router/internal/integration/events_test.go +++ b/vmware-event-router/internal/integration/events_test.go @@ -1,11 +1,10 @@ //go:build integration -// +build integration package integration_test import "github.com/vmware/govmomi/vim25/types" -//nolint +// nolint func newVMPoweredOnEvent() types.BaseEvent { return &types.VmPoweredOnEvent{ VmEvent: types.VmEvent{ @@ -24,12 +23,12 @@ func newVMPoweredOnEvent() types.BaseEvent { } } -//nolint +// nolint func newLicenseEvent() types.BaseEvent { return types.BaseEvent(&types.LicenseEvent{}) } -//nolint +// nolint func newClusterCreatedEvent() types.BaseEvent { return types.BaseEvent(&types.ClusterCreatedEvent{}) } diff --git a/vmware-event-router/internal/integration/openfaas_test.go b/vmware-event-router/internal/integration/openfaas_test.go index d3ff6421..ade0781c 100644 --- a/vmware-event-router/internal/integration/openfaas_test.go +++ b/vmware-event-router/internal/integration/openfaas_test.go @@ -1,4 +1,4 @@ -// +build integration,openfaas +//go:build integration && openfaas package integration_test diff --git a/vmware-event-router/internal/integration/suite_aws_test.go b/vmware-event-router/internal/integration/suite_aws_test.go index 5e66bfdb..6722a304 100644 --- a/vmware-event-router/internal/integration/suite_aws_test.go +++ b/vmware-event-router/internal/integration/suite_aws_test.go @@ -1,12 +1,19 @@ -// +build integration,aws +//go:build integration && aws package integration_test import ( "context" "os" + "sync/atomic" "testing" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/eventbridge" + "github.com/aws/aws-sdk-go/service/eventbridge/eventbridgeiface" . "github.com/onsi/ginkgo" "go.uber.org/zap" "go.uber.org/zap/zaptest" @@ -28,6 +35,16 @@ func (r receiveFunc) Receive(stats *metrics.EventStats) { r(stats) } +type mockClient struct { + eventbridgeiface.EventBridgeAPI + sent int32 // number events sent +} + +func (m *mockClient) PutEventsWithContext(ctx aws.Context, input *eventbridge.PutEventsInput, opts ...request.Option) (*eventbridge.PutEventsOutput, error) { + atomic.AddInt32(&m.sent, 1) + return m.EventBridgeAPI.PutEventsWithContext(ctx, input, opts...) +} + var ( ctx context.Context log *zap.SugaredLogger @@ -35,6 +52,7 @@ var ( awsProcessor processor.Processor cfg *config.ProcessorConfigEventBridge receiver receiveFunc + ebClient *mockClient ) func TestAWS(t *testing.T) { @@ -75,3 +93,24 @@ var _ = BeforeSuite(func() { }) var _ = AfterSuite(func() {}) + +func createClient(cfg *config.ProcessorConfigEventBridge) *mockClient { + accessKey := cfg.Auth.AWSAccessKeyAuth.AccessKey + secretKey := cfg.Auth.AWSAccessKeyAuth.SecretKey + + awsSessionAccessKey, err := session.NewSession(&aws.Config{ + Region: aws.String(cfg.Region), + Credentials: credentials.NewStaticCredentials( + accessKey, + secretKey, + "", // a token will be created when the session is used. + ), + }) + + Expect(err).ShouldNot(HaveOccurred()) + + client := eventbridge.New(awsSessionAccessKey) + Expect(client).ToNot(BeNil()) + + return &mockClient{EventBridgeAPI: client} +} diff --git a/vmware-event-router/internal/integration/suite_openfaas_test.go b/vmware-event-router/internal/integration/suite_openfaas_test.go index b9c85594..57a18bb9 100644 --- a/vmware-event-router/internal/integration/suite_openfaas_test.go +++ b/vmware-event-router/internal/integration/suite_openfaas_test.go @@ -1,4 +1,4 @@ -// +build integration,openfaas +//go:build integration && openfaas package integration_test diff --git a/vmware-event-router/internal/metrics/metrics.go b/vmware-event-router/internal/metrics/metrics.go index 61614f31..b3a9347b 100644 --- a/vmware-event-router/internal/metrics/metrics.go +++ b/vmware-event-router/internal/metrics/metrics.go @@ -84,7 +84,6 @@ func loadAvg(position int) float64 { values := strings.Fields(string(data)) load, err := strconv.ParseFloat(values[position], 64) - if err != nil { return 0 } diff --git a/vmware-event-router/internal/metrics/server.go b/vmware-event-router/internal/metrics/server.go index 9996c510..0bedaecc 100644 --- a/vmware-event-router/internal/metrics/server.go +++ b/vmware-event-router/internal/metrics/server.go @@ -24,9 +24,7 @@ const ( endpoint = "/stats" ) -var ( - eventRouterStats = expvar.NewMap(mapName) -) +var eventRouterStats = expvar.NewMap(mapName) // Receiver receives metrics from metric providers type Receiver interface { @@ -132,7 +130,6 @@ func withBasicAuth(log logger.Logger, next http.Handler, u, p string) http.Handl if !ok || !(p == password && u == user) { w.WriteHeader(http.StatusUnauthorized) _, err := w.Write([]byte("invalid credentials")) - if err != nil { log.Errorf("could not write http response: %v", err) } diff --git a/vmware-event-router/internal/processor/aws/aws_event_bridge.go b/vmware-event-router/internal/processor/aws/aws_event_bridge.go index 69eaaf96..ec9c6342 100644 --- a/vmware-event-router/internal/processor/aws/aws_event_bridge.go +++ b/vmware-event-router/internal/processor/aws/aws_event_bridge.go @@ -15,6 +15,7 @@ import ( "github.com/aws/aws-sdk-go/service/eventbridge/eventbridgeiface" cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/pkg/errors" + "github.com/timbray/quamina" "go.uber.org/zap" config "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/config/v1alpha1" @@ -24,49 +25,23 @@ import ( ) const ( - defaultResyncInterval = time.Minute * 5 // resync rule patterns after interval - defaultPageLimit = 50 // max 50 results per page for list operations - defaultBatchSize = 10 // max 10 input events per batch sent to AWS + defaultPageLimit = 50 // max 50 results per page for list operations ) -// rules pattern to event bus mapping -type patternMap struct { - sync.RWMutex - subjects map[string]string -} - -// matches checks whether the given subject is in the pattern map and returns -// the associated event bus -func (pm *patternMap) matches(subject string) (string, bool) { - pm.RLock() - defer pm.RUnlock() - bus, matched := pm.subjects[subject] - return bus, matched -} - -// addRule adds a subject from the specified event bus to the pattern map -func (pm *patternMap) addSubject(subject, bus string) { - pm.Lock() - defer pm.Unlock() - pm.subjects[subject] = bus -} - -// init initializes the pattern map -func (pm *patternMap) init() { - pm.Lock() - defer pm.Unlock() - pm.subjects = map[string]string{} +// TODO(@mgasch): allow for multiple event rules for configured bus +type matcher struct { + *quamina.Quamina + bus string // uses cfg.eventbus as pattern name + pattern string } // EventBridgeProcessor implements the Processor interface type EventBridgeProcessor struct { session session.Session eventbridgeiface.EventBridgeAPI - patternMap *patternMap + matcher matcher // options - resyncInterval time.Duration - batchSize int logger.Logger mu sync.RWMutex @@ -76,44 +51,31 @@ type EventBridgeProcessor struct { // assert we implement Processor interface var _ processor.Processor = (*EventBridgeProcessor)(nil) -type eventPattern struct { - Detail struct { - Subject []string `json:"subject,omitempty"` - } `json:"detail,omitempty"` -} - // NewEventBridgeProcessor returns an AWS EventBridge processor for the given // configuration func NewEventBridgeProcessor(ctx context.Context, cfg *config.ProcessorConfigEventBridge, ms metrics.Receiver, log logger.Logger, opts ...Option) (*EventBridgeProcessor, error) { + // Initialize awsSession for the AWS SDK client + var awsSession *session.Session + awsLog := log if zapSugared, ok := log.(*zap.SugaredLogger); ok { proc := strings.ToUpper(string(config.ProcessorEventBridge)) awsLog = zapSugared.Named(fmt.Sprintf("[%s]", proc)) } - eventBridge := EventBridgeProcessor{ - resyncInterval: defaultResyncInterval, - batchSize: defaultBatchSize, - Logger: awsLog, - patternMap: &patternMap{}, + proc := EventBridgeProcessor{ + Logger: awsLog, } // apply options for _, opt := range opts { - opt(&eventBridge) + opt(&proc) } if cfg == nil { return nil, errors.New("no AWS EventBridge configuration found") } - if cfg.Auth == nil || cfg.Auth.AWSAccessKeyAuth == nil { - return nil, fmt.Errorf("invalid %s credentials: accessKey and secretKey must be set", config.AWSAccessKeyAuth) - } - - accessKey := cfg.Auth.AWSAccessKeyAuth.AccessKey - secretKey := cfg.Auth.AWSAccessKeyAuth.SecretKey - if cfg.Region == "" { return nil, errors.New("region must be specified") } @@ -126,64 +88,118 @@ func NewEventBridgeProcessor(ctx context.Context, cfg *config.ProcessorConfigEve return nil, errors.New("event bus must be specified") } - awsSession, err := session.NewSession(&aws.Config{ - Region: aws.String(cfg.Region), - Credentials: credentials.NewStaticCredentials( - accessKey, - secretKey, - "", // a token will be created when the session is used. - ), - }) - if err != nil { - return nil, errors.Wrap(err, "create AWS session") + if proc.EventBridgeAPI == nil { + // Check the Auth Method to determine how the Session should be established + if cfg.Auth.Type == config.AWSAccessKeyAuth { + if cfg.Auth == nil || cfg.Auth.AWSAccessKeyAuth == nil { + return nil, fmt.Errorf("invalid %s credentials: accessKey and secretKey must be set", config.AWSAccessKeyAuth) + } + accessKey := cfg.Auth.AWSAccessKeyAuth.AccessKey + secretKey := cfg.Auth.AWSAccessKeyAuth.SecretKey + + awsSessionAccessKey, err := session.NewSession(&aws.Config{ + Region: aws.String(cfg.Region), + Credentials: credentials.NewStaticCredentials( + accessKey, + secretKey, + "", // a token will be created when the session is used. + ), + }) + if err != nil { + return nil, errors.Wrap(err, "create AWS session") + } + // Set the AWS Session to the IAM Role authenticated session + awsSession = awsSessionAccessKey + } + if cfg.Auth.Type == config.AWSIAMRoleAuth { + // Create Session without additional options will load credentials region, + // and profile loaded from the environment and shared config automatically + awsSessionIam, err := session.NewSession(&aws.Config{ + Region: aws.String(cfg.Region), + }) + if err != nil { + return nil, errors.Wrap(err, "create AWS session") + } + // Set the AWS Session to the IAM Role authenticated session + awsSession = awsSessionIam + } + + proc.session = *awsSession + ebSession := eventbridge.New(awsSession) + + if ebSession == nil { + return nil, errors.New("create AWS event bridge session") + } + + proc.EventBridgeAPI = ebSession + } + + if err := configureRuleMatcher(ctx, &proc, cfg.EventBus, cfg.RuleARN); err != nil { + return nil, errors.Wrap(err, "configure rule matcher") + } + + // pre-populate the metrics stats + proc.stats = metrics.EventStats{ + Provider: string(config.ProcessorEventBridge), + Type: config.EventProcessor, + Address: cfg.RuleARN, // Using Rule ARN to uniquely identify and represent this processor + Started: time.Now().UTC(), + Invocations: make(map[string]*metrics.InvocationDetails), } - eventBridge.session = *awsSession - ebSession := eventbridge.New(awsSession) + go proc.PushMetrics(ctx, ms) + + return &proc, nil +} - if ebSession == nil { - return nil, errors.Errorf("create AWS event bridge session") +func configureRuleMatcher(ctx context.Context, proc *EventBridgeProcessor, bus string, ruleARN string) error { + q, err := quamina.New() + if err != nil { + return errors.Wrap(err, "create quamina pattern match instance") } - eventBridge.EventBridgeAPI = ebSession + proc.matcher = matcher{ + Quamina: q, + bus: bus, + } var ( found bool nextToken *string ) - eventBridge.patternMap.init() for !found { - rules, err := eventBridge.ListRulesWithContext(ctx, &eventbridge.ListRulesInput{ - EventBusName: aws.String(cfg.EventBus), // explicitly passing eventbus name because list assumes "default" otherwise + rules, err := proc.ListRulesWithContext(ctx, &eventbridge.ListRulesInput{ + EventBusName: aws.String(bus), // explicitly passing eventbus name because list assumes "default" otherwise Limit: aws.Int64(defaultPageLimit), // up to n results per page for requests. NextToken: nextToken, }) if err != nil { - return nil, errors.Wrap(err, "list event bridge rules") + return errors.Wrap(err, "list event bridge rules") } arnLoop: for _, rule := range rules.Rules { switch { - case *rule.Arn == cfg.RuleARN: + case *rule.Arn == ruleARN: if rule.EventPattern == nil { - return nil, errors.Errorf("rule event pattern must not be empty") + return errors.New("rule event pattern must not be nil") } - var e eventPattern - err := json.Unmarshal([]byte(*rule.EventPattern), &e) - if err != nil { - return nil, errors.Wrap(err, "parse rule event pattern") + pattern := *rule.EventPattern + if err := proc.matcher.AddPattern(proc.matcher.bus, pattern); err != nil { + return errors.Wrap(err, "add rule event pattern to matcher") } - if len(e.Detail.Subject) == 0 { // might be a valid scenario, emit warning - eventBridge.Warn("rule event pattern does not contain any subjects") - } - for _, s := range e.Detail.Subject { - eventBridge.Infow("adding rule event forwarding pattern to processor", "subject", s) - eventBridge.patternMap.addSubject(s, *rule.EventBusName) - } + proc.Infow( + "adding rule event forwarding pattern to processor", + "bus", + proc.matcher.bus, + "pattern", + pattern, + ) + proc.matcher.pattern = pattern + found = true break arnLoop @@ -199,28 +215,15 @@ func NewEventBridgeProcessor(ctx context.Context, cfg *config.ProcessorConfigEve nextToken = rules.NextToken continue default: // nothing found - return nil, errors.Errorf("rule %s not found for configured AWS event bridge account", cfg.RuleARN) + return errors.Errorf("rule %q not found for configured AWS event bridge account", ruleARN) } } - - // pre-populate the metrics stats - eventBridge.stats = metrics.EventStats{ - Provider: string(config.ProcessorEventBridge), - Type: config.EventProcessor, - Address: cfg.RuleARN, // Using Rule ARN to uniquely identify and represent this processor - Started: time.Now().UTC(), - Invocations: make(map[string]*metrics.InvocationDetails), - } - - go eventBridge.PushMetrics(ctx, ms) - go eventBridge.syncPatternMap(ctx, cfg.EventBus, cfg.RuleARN) // periodically sync rules - - return &eventBridge, nil + return nil } // Process implements the stream processor interface func (eb *EventBridgeProcessor) Process(ctx context.Context, ce cloudevents.Event) error { - eb.Debugw("processing event", "eventID", ce.ID(), "event", ce) + eb.Debugw("processing event", "eventID", ce.ID(), "event", ce.String()) subject := ce.Subject() eb.mu.Lock() @@ -230,123 +233,70 @@ func (eb *EventBridgeProcessor) Process(ctx context.Context, ce cloudevents.Even } eb.mu.Unlock() - if bus, ok := eb.patternMap.matches(subject); ok { - jsonBytes, err := json.Marshal(ce) - if err != nil { - return processor.NewError(config.ProcessorEventBridge, errors.Wrapf(err, "marshal event %s", ce.ID())) - } - - jsonString := string(jsonBytes) - entry := eventbridge.PutEventsRequestEntry{ - Detail: aws.String(jsonString), - EventBusName: aws.String(bus), - Source: aws.String(ce.Source()), - DetailType: aws.String(subject), - } - - // TODO: add batching (metrics stats currently assume single item) - input := eventbridge.PutEventsInput{ - Entries: []*eventbridge.PutEventsRequestEntry{&entry}, - } - - eb.Infow("sending event", "eventID", ce.ID(), "subject", subject) - resp, err := eb.PutEventsWithContext(ctx, &input) - eb.Debugw("got response", "eventID", ce.ID(), "response", resp) - eb.mu.Lock() - defer eb.mu.Unlock() - if err != nil { - eb.stats.Invocations[subject].Failure() - return processor.NewError(config.ProcessorEventBridge, errors.Wrapf(err, "send event %s", ce.ID())) - } - - eb.Infow("successfully sent event", "eventID", ce.ID()) - eb.stats.Invocations[subject].Success() - return nil + // this is the format eventbridge uses (also for matching) so we need to wrap + // the cloudevent into the Detail field in order to correctly match + awsEvent := struct { + Detail cloudevents.Event `json:"detail,omitempty"` + }{ + Detail: ce, } - eb.Infow("skipping event: pattern rule does not match", "eventID", ce.ID(), "subject", subject) - return nil -} + e, err := json.Marshal(awsEvent) + if err != nil { + return processor.NewError(config.ProcessorEventBridge, errors.Wrapf(err, "convert cloudevent to aws eventbridge event: %s", ce.ID())) + } -func (eb *EventBridgeProcessor) syncPatternMap(ctx context.Context, eventbus, ruleARN string) { - for { - select { - case <-ctx.Done(): - return - case <-time.After(eb.resyncInterval): - eb.Debugw("syncing pattern map for rule ARN", "ruleARN", ruleARN) + matches, err := eb.matcher.MatchesForEvent(e) + if err != nil { + return processor.NewError(config.ProcessorEventBridge, errors.Wrapf(err, "match event: %s", ce.ID())) + } - err := eb.syncRules(ctx, eventbus, ruleARN) + for _, m := range matches { + if m == eb.matcher.bus { + jsonBytes, err := json.Marshal(ce) if err != nil { - eb.Errorw("could not sync pattern map for rule ARN", "ruleARN", ruleARN, "error", err) - eb.Infof("retrying pattern map sync after %v", eb.resyncInterval) + return processor.NewError(config.ProcessorEventBridge, errors.Wrapf(err, "marshal event: %s", ce.ID())) } - eb.Debugw("successfully synced pattern map for rule ARN", "ruleARN", ruleARN) - } - } -} - -func (eb *EventBridgeProcessor) syncRules(ctx context.Context, eventbus, ruleARN string) error { - // reset pattern map - eb.patternMap.init() - - var ( - found bool - nextToken *string - ) + jsonString := string(jsonBytes) + entry := eventbridge.PutEventsRequestEntry{ + Detail: aws.String(jsonString), + EventBusName: aws.String(eb.matcher.bus), + Source: aws.String(ce.Source()), + DetailType: aws.String(subject), + } - for !found { - rules, err := eb.ListRulesWithContext(ctx, &eventbridge.ListRulesInput{ - EventBusName: aws.String(eventbus), // explicitly passing eventbus name because list assumes "default" otherwise - Limit: aws.Int64(defaultPageLimit), - NextToken: nextToken, - }) - if err != nil { - return errors.Wrap(err, "list event bridge rules") - } + // TODO: add batching (metrics stats currently assume single item) + input := eventbridge.PutEventsInput{ + Entries: []*eventbridge.PutEventsRequestEntry{&entry}, + } - arnLoop: - for _, rule := range rules.Rules { - switch { - case *rule.Arn == ruleARN: - if rule.EventPattern == nil { - return errors.Errorf("rule event pattern must not be empty") - } + eb.Infow("sending event", "eventID", ce.ID(), "type", ce.Type(), "subject", subject) + resp, err := eb.PutEventsWithContext(ctx, &input) + eb.Debugw("got response", "eventID", ce.ID(), "response", resp) - var e eventPattern - err := json.Unmarshal([]byte(*rule.EventPattern), &e) + updateStats := func(err error) error { + eb.mu.Lock() + defer eb.mu.Unlock() if err != nil { - return errors.Wrap(err, "parse rule event pattern") - } - - if len(e.Detail.Subject) == 0 { // might be a valid scenario, emit warning - eb.Warn("rule event pattern does not contain any subjects") - } - - for _, s := range e.Detail.Subject { - eb.Infow("adding rule event forwarding pattern to processor", "subject", s) - eb.patternMap.addSubject(s, *rule.EventBusName) + eb.stats.Invocations[subject].Failure() + return processor.NewError(config.ProcessorEventBridge, errors.Wrapf(err, "send event: %s", ce.ID())) } - found = true - break arnLoop - - default: - continue + eb.Infow("successfully sent event", "eventID", ce.ID()) + eb.stats.Invocations[subject].Success() + return nil } - } - - switch { - case found: // return early - return nil - case rules.NextToken != nil: // try next batch of rules, if any - nextToken = rules.NextToken - continue - default: // nothing found - return errors.Errorf("rule %s not found for configured AWS event bridge account", ruleARN) + return updateStats(err) } } + + eb.Debugw( + "skipping event: pattern rule does not match", + "eventID", ce.ID(), + "event", ce.String(), + "pattern", eb.matcher.pattern, + ) return nil } diff --git a/vmware-event-router/internal/processor/aws/aws_event_bridge_test.go b/vmware-event-router/internal/processor/aws/aws_event_bridge_test.go new file mode 100644 index 00000000..655aae81 --- /dev/null +++ b/vmware-event-router/internal/processor/aws/aws_event_bridge_test.go @@ -0,0 +1,364 @@ +//go:build unit + +package aws + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/eventbridge" + "github.com/aws/aws-sdk-go/service/eventbridge/eventbridgeiface" + cloudevents "github.com/cloudevents/sdk-go/v2" + "github.com/google/uuid" + "github.com/vmware/govmomi/vim25/types" + "go.uber.org/zap/zaptest" + "gotest.tools/v3/assert" + + config "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/config/v1alpha1" + "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/metrics" +) + +const ( + region = "test-region" + eventBus = "test-bus" + ruleARN = "test-rule" +) + +type mockMetrics struct { + sync.Mutex + success int + failed int + + once sync.Once + received chan struct{} // signal Receive was called once +} + +func (m *mockMetrics) Receive(stats *metrics.EventStats) { + m.Lock() + defer m.Unlock() + + if m.success != 0 || m.failed != 0 { + m.once.Do(func() { + // close and signal if we received at least one real metric update + close(m.received) + }) + + return + } + + for _, v := range stats.Invocations { + m.success += v.SuccessCount + m.failed += v.FailureCount + } +} + +type mockClient struct { + eventbridgeiface.EventBridgeAPI + failPut bool // returns generic failure on PutEvents + failList bool // returns generic failure on ListRules + pattern string + sent int32 // track successful put calls +} + +func (m *mockClient) PutEventsWithContext(_ aws.Context, input *eventbridge.PutEventsInput, _ ...request.Option) (*eventbridge.PutEventsOutput, error) { + if m.failPut { + return nil, fmt.Errorf("could not put event: %v", input) + } + + atomic.AddInt32(&m.sent, 1) + return &eventbridge.PutEventsOutput{}, nil +} + +func (m *mockClient) ListRulesWithContext(_ aws.Context, input *eventbridge.ListRulesInput, _ ...request.Option) (*eventbridge.ListRulesOutput, error) { + if m.failList { + return nil, fmt.Errorf("could not list rules for input %v", input) + } + + return &eventbridge.ListRulesOutput{ + NextToken: nil, + Rules: []*eventbridge.Rule{ + { + Arn: aws.String(ruleARN), + EventBusName: aws.String(eventBus), + EventPattern: aws.String(m.pattern), + }, + }, + }, nil +} + +func TestEventBridgeProcessor_New(t *testing.T) { + tests := []struct { + name string + pattern string + failList bool // fail list rule with error + wantError string // expect send error + }{ + { + name: "fails to create when rules cannot be listed", + failList: true, + pattern: `{"detail": {"subject": [{"exists":true}]}}`, + wantError: "could not list", + }, + { + name: "fails to create when pattern rule is empty", + failList: false, + pattern: "", + wantError: "empty pattern", + }, + { + name: "successfully creates processor", + failList: false, + pattern: `{"detail": {"subject": [{"exists":true}]}}`, + wantError: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logger := zaptest.NewLogger(t) + + cfg := config.ProcessorConfigEventBridge{ + Region: region, + EventBus: eventBus, + RuleARN: ruleARN, + } + + ebClient := mockClient{ + pattern: tt.pattern, + } + + ebClient.failList = tt.failList + + metricsClient := mockMetrics{ + received: make(chan struct{}), + } + + _, err := NewEventBridgeProcessor(ctx, &cfg, &metricsClient, logger.Sugar(), WithClient(&ebClient)) + if tt.wantError != "" { + assert.ErrorContains(t, err, tt.wantError) + } else { + assert.NilError(t, err) + } + }) + } +} + +func TestEventBridgeProcessor_Process(t *testing.T) { + tests := []struct { + name string + event cloudevents.Event + pattern string + wantSent int + wantFailed int + wantError string // expect send error + }{ + { + name: "fails to send when PutEvents returns error", + event: newTestEvent(t, "VmPoweredOnEvent", newVMPoweredOnEvent()), + pattern: `{"detail": {"subject": [{"exists":true}]}}`, // match any + wantSent: 0, + wantFailed: 1, + wantError: "could not put event", + }, + { + name: "successfully sends event", + event: newTestEvent(t, "VmPoweredOnEvent", newVMPoweredOnEvent()), + pattern: `{"detail": {"subject": [{"exists":true}]}}`, // match any + wantSent: 1, + wantFailed: 0, + wantError: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logger := zaptest.NewLogger(t) + + cfg := config.ProcessorConfigEventBridge{ + Region: region, + EventBus: eventBus, + RuleARN: ruleARN, + } + + ebClient := mockClient{ + pattern: tt.pattern, + } + + if tt.wantError != "" { + ebClient.failPut = true + } + + metricsClient := mockMetrics{ + received: make(chan struct{}), + } + + proc, err := NewEventBridgeProcessor(ctx, &cfg, &metricsClient, logger.Sugar(), WithClient(&ebClient)) + assert.NilError(t, err) + defer func() { + err = proc.Shutdown(ctx) + assert.NilError(t, err) + }() + + err = proc.Process(ctx, tt.event) + if tt.wantError != "" { + assert.ErrorContains(t, err, tt.wantError) + } else { + assert.NilError(t, err) + } + + <-metricsClient.received + success := func() int { + metricsClient.Lock() + defer metricsClient.Unlock() + return metricsClient.success + } + + failed := func() int { + metricsClient.Lock() + defer metricsClient.Unlock() + return metricsClient.failed + } + + assert.Equal(t, tt.wantSent, success()) + assert.Equal(t, tt.wantFailed, failed()) + }) + } +} + +func TestEventBridgeProcessor_Process_PatternMatch(t *testing.T) { + tests := []struct { + name string + event cloudevents.Event + pattern string + wantSent int32 + }{ + { + name: "pattern match on subject \"VmPoweredOnEvent\" or \"VmPoweredOffEvent\"", + event: newTestEvent(t, "VmPoweredOnEvent", newVMPoweredOnEvent()), + pattern: `{"detail": {"subject": ["VmPoweredOnEvent","VmPoweredOffEvent"]}}`, + wantSent: 1, + }, + { + name: "pattern match on subject with prefix \"Vm\"", + event: newTestEvent(t, "VmReconfiguredEvent", newVMReconfiguredEvent()), + pattern: `{"detail": {"subject": [{"shellstyle":"Vm*"}]}}`, + wantSent: 1, + }, + { + name: "pattern match on VM name with prefix \"Linux\"", + event: newTestEvent(t, "VmPoweredOnEvent", newVMPoweredOnEvent()), + pattern: `{"detail": {"data": {"Vm": {"Name": [{"shellstyle": "Linux*"}]}}}}`, + wantSent: 1, + }, + { + name: "pattern match on extended attribute eventclass anything-but \"eventex\" and \"extendedevent\"", + event: newTestEvent(t, "VmPoweredOnEvent", newVMPoweredOnEvent()), + pattern: `{"detail": {"eventclass": [{"anything-but": ["extendedevent","eventex"]}]}}`, + wantSent: 1, + }, + { + name: "no pattern match on VM name with prefix \"Windows\"", + event: newTestEvent(t, "VmPoweredOnEvent", newVMPoweredOnEvent()), + pattern: `{"detail": {"data": {"Vm": {"Name": [{"shellstyle": "Windows*"}]}}}}`, + wantSent: 0, + }, + { + name: "no pattern match on NULL Host value", + event: newTestEvent(t, "VmPoweredOnEvent", newVMPoweredOnEvent()), + pattern: `{"detail": {"data": {"Host": [{"exists": false}]}}}`, + wantSent: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logger := zaptest.NewLogger(t) + + cfg := config.ProcessorConfigEventBridge{ + Region: region, + EventBus: eventBus, + RuleARN: ruleARN, + } + + ebClient := mockClient{ + pattern: tt.pattern, + } + + proc, err := NewEventBridgeProcessor(ctx, &cfg, &mockMetrics{}, logger.Sugar(), WithClient(&ebClient)) + assert.NilError(t, err) + defer func() { + err = proc.Shutdown(ctx) + assert.NilError(t, err) + }() + + err = proc.Process(ctx, tt.event) + assert.NilError(t, err) + + sent := atomic.LoadInt32(&ebClient.sent) + assert.Equal(t, tt.wantSent, sent) + }) + } +} + +func newTestEvent(t *testing.T, subject string, data interface{}) cloudevents.Event { + t.Helper() + + e := cloudevents.NewEvent() + e.SetID(uuid.New().String()) + e.SetType("test.event.type.v0") + e.SetSubject(subject) + e.SetSource("test-source") + e.SetExtension("eventclass", "event") + + err := e.SetData(cloudevents.ApplicationJSON, data) + assert.NilError(t, err) + + return e +} + +func newVMPoweredOnEvent() types.BaseEvent { + return &types.VmPoweredOnEvent{ + VmEvent: types.VmEvent{ + Event: types.Event{ + Vm: &types.VmEventArgument{ + EntityEventArgument: types.EntityEventArgument{ + Name: "Linux-1234", + }, + Vm: types.ManagedObjectReference{ + Type: "VirtualMachine", + Value: "vm-1234", + }, + }, + }, + }, + } +} + +func newVMReconfiguredEvent() types.BaseEvent { + return &types.VmReconfiguredEvent{ + VmEvent: types.VmEvent{ + Event: types.Event{ + Vm: &types.VmEventArgument{ + EntityEventArgument: types.EntityEventArgument{ + Name: "Linux-1234", + }, + Vm: types.ManagedObjectReference{ + Type: "VirtualMachine", + Value: "vm-1234", + }, + }, + }, + }, + } +} diff --git a/vmware-event-router/internal/processor/aws/options.go b/vmware-event-router/internal/processor/aws/options.go index 244c0799..0876108e 100644 --- a/vmware-event-router/internal/processor/aws/options.go +++ b/vmware-event-router/internal/processor/aws/options.go @@ -1,24 +1,16 @@ package aws import ( - "time" + "github.com/aws/aws-sdk-go/service/eventbridge/eventbridgeiface" ) // Option configures the AWS processor // TODO: change signature to return errors type Option func(*EventBridgeProcessor) -// WithResyncInterval configures the interval to sync AWS EventBridge event -// pattern rules -func WithResyncInterval(interval time.Duration) Option { +// WithClient uses the specified EventBridge client, e.g. useful in testing +func WithClient(client eventbridgeiface.EventBridgeAPI) Option { return func(aws *EventBridgeProcessor) { - aws.resyncInterval = interval - } -} - -// WithBatchSize sets the batch size for PutEvents requests -func WithBatchSize(size int) Option { - return func(aws *EventBridgeProcessor) { - aws.batchSize = size + aws.EventBridgeAPI = client } } diff --git a/vmware-event-router/internal/processor/openfaas/openfaas_test.go b/vmware-event-router/internal/processor/openfaas/openfaas_test.go index 700d4850..2a65d9b7 100644 --- a/vmware-event-router/internal/processor/openfaas/openfaas_test.go +++ b/vmware-event-router/internal/processor/openfaas/openfaas_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package openfaas diff --git a/vmware-event-router/internal/processor/openfaas/retry_test.go b/vmware-event-router/internal/processor/openfaas/retry_test.go index 890a8beb..40e0671c 100644 --- a/vmware-event-router/internal/processor/openfaas/retry_test.go +++ b/vmware-event-router/internal/processor/openfaas/retry_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package openfaas diff --git a/vmware-event-router/internal/processor/processor_test.go b/vmware-event-router/internal/processor/processor_test.go index 615b1723..d4ecefa6 100644 --- a/vmware-event-router/internal/processor/processor_test.go +++ b/vmware-event-router/internal/processor/processor_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package processor diff --git a/vmware-event-router/internal/provider/fake/vcenter_fake_test.go b/vmware-event-router/internal/provider/fake/vcenter_fake_test.go index c841ff36..77808dea 100644 --- a/vmware-event-router/internal/provider/fake/vcenter_fake_test.go +++ b/vmware-event-router/internal/provider/fake/vcenter_fake_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package fake diff --git a/vmware-event-router/internal/provider/horizon/client_internal_test.go b/vmware-event-router/internal/provider/horizon/client_internal_test.go index 5deee453..c0c3149f 100644 --- a/vmware-event-router/internal/provider/horizon/client_internal_test.go +++ b/vmware-event-router/internal/provider/horizon/client_internal_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package horizon @@ -14,7 +13,7 @@ import ( "time" "go.uber.org/zap/zaptest" - "gotest.tools/assert" + "gotest.tools/v3/assert" ) const ( @@ -300,7 +299,7 @@ func (h *horizonAPIMock) logoutHandler(w http.ResponseWriter, r *http.Request) { func randomToken(n int) string { rand.Seed(time.Now().Unix()) - var letter = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + letter := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") b := make([]rune, n) for i := range b { b[i] = letter[rand.Intn(len(letter))] diff --git a/vmware-event-router/internal/provider/horizon/horizon.go b/vmware-event-router/internal/provider/horizon/horizon.go index cb772882..22c1aef7 100644 --- a/vmware-event-router/internal/provider/horizon/horizon.go +++ b/vmware-event-router/internal/provider/horizon/horizon.go @@ -30,14 +30,12 @@ const ( eventTypeScheme = "%s/horizon.%s.v0" // router prefix + normalized event type ) -var ( - defaultBackoff = backoff.Backoff{ - Factor: 2, - Jitter: false, - Min: time.Second, - Max: 5 * time.Second, - } -) +var defaultBackoff = backoff.Backoff{ + Factor: 2, + Jitter: false, + Min: time.Second, + Max: 5 * time.Second, +} // EventStream handles the connection to the Horizon events API type EventStream struct { diff --git a/vmware-event-router/internal/provider/horizon/horizon_internal_test.go b/vmware-event-router/internal/provider/horizon/horizon_internal_test.go index 6ce31070..064a229d 100644 --- a/vmware-event-router/internal/provider/horizon/horizon_internal_test.go +++ b/vmware-event-router/internal/provider/horizon/horizon_internal_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package horizon @@ -16,7 +15,7 @@ import ( ce "github.com/cloudevents/sdk-go/v2" "github.com/jpillora/backoff" "go.uber.org/zap/zaptest" - "gotest.tools/assert" + "gotest.tools/v3/assert" config "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/config/v1alpha1" "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/logger" @@ -192,7 +191,7 @@ func (f *fakeClient) GetEvents(_ context.Context, _ Timestamp) ([]AuditEventSumm f.log.Debugf("GetEvents invocations: %d", f.invocations) // preserve existing events slice - var newEvents = make([]AuditEventSummary, len(f.events)) + newEvents := make([]AuditEventSummary, len(f.events)) copy(newEvents, f.events) // Horizon API returns events ordered from newest to oldest diff --git a/vmware-event-router/internal/provider/horizon/horizon_test.go b/vmware-event-router/internal/provider/horizon/horizon_test.go index 52212d68..3923b89e 100644 --- a/vmware-event-router/internal/provider/horizon/horizon_test.go +++ b/vmware-event-router/internal/provider/horizon/horizon_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package horizon_test @@ -15,7 +14,7 @@ import ( "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/logger" "go.uber.org/zap/zaptest" - "gotest.tools/assert" + "gotest.tools/v3/assert" "knative.dev/pkg/logging" config "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/config/v1alpha1" diff --git a/vmware-event-router/internal/provider/vcenter/checkpoint.go b/vmware-event-router/internal/provider/vcenter/checkpoint.go index 82a924d4..75b9c4e4 100644 --- a/vmware-event-router/internal/provider/vcenter/checkpoint.go +++ b/vmware-event-router/internal/provider/vcenter/checkpoint.go @@ -21,9 +21,7 @@ const ( format = "cp-%s.json" // cp-.json ) -var ( - errInvalidEvent = errors.New("invalid event") -) +var errInvalidEvent = errors.New("invalid event") // checkpoint represents a checkpoint object type checkpoint struct { @@ -48,9 +46,7 @@ type checkpoint struct { // (i.e. default values) and it is the caller's responsibility to check for // validity using time.IsZero() on any timestamp. func getCheckpoint(ctx context.Context, host, dir string) (cp *checkpoint, path string, err error) { - var ( - skip bool - ) + var skip bool file := fileName(host) path = fullPath(file, dir) @@ -90,7 +86,7 @@ func initCheckpoint(_ context.Context, fullPath string) (*checkpoint, error) { dir := filepath.Dir(fullPath) // create if not exists - err := os.MkdirAll(dir, 0755) + err := os.MkdirAll(dir, 0o755) if err != nil { return nil, errors.Wrap(err, "could not create checkpoint directory") } @@ -102,7 +98,7 @@ func initCheckpoint(_ context.Context, fullPath string) (*checkpoint, error) { return nil, errors.Wrap(err, "could not marshal checkpoint to JSON object") } - err = ioutil.WriteFile(fullPath, jsonBytes, 0600) + err = ioutil.WriteFile(fullPath, jsonBytes, 0o600) if err != nil { return nil, errors.Wrap(err, "could not write checkpoint file") } diff --git a/vmware-event-router/internal/provider/vcenter/checkpoint_test.go b/vmware-event-router/internal/provider/vcenter/checkpoint_test.go index 3ff279b2..515855b3 100644 --- a/vmware-event-router/internal/provider/vcenter/checkpoint_test.go +++ b/vmware-event-router/internal/provider/vcenter/checkpoint_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package vcenter @@ -122,7 +121,8 @@ func Test_checkpoint(t *testing.T) { VmEvent: types.VmEvent{ Event: types.Event{ Key: lastEventKey, - CreatedTime: lastEventKeyTime}, + CreatedTime: lastEventKeyTime, + }, }, }, uuid: lastEventUUID, diff --git a/vmware-event-router/internal/provider/vcenter/vcenter.go b/vmware-event-router/internal/provider/vcenter/vcenter.go index 9d88d506..54f99461 100644 --- a/vmware-event-router/internal/provider/vcenter/vcenter.go +++ b/vmware-event-router/internal/provider/vcenter/vcenter.go @@ -283,7 +283,7 @@ func (vc *EventStream) stream(ctx context.Context, p processor.Processor, collec path := fullPath(f, dir) // always create/overwrite (existing) checkpoint - file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600) if err != nil { return errors.Wrap(err, "create checkpoint file") } diff --git a/vmware-event-router/internal/provider/vcenter/vcenter_test.go b/vmware-event-router/internal/provider/vcenter/vcenter_test.go index b4e997ff..98857015 100644 --- a/vmware-event-router/internal/provider/vcenter/vcenter_test.go +++ b/vmware-event-router/internal/provider/vcenter/vcenter_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package vcenter diff --git a/vmware-event-router/internal/provider/vcsim/vcsim_test.go b/vmware-event-router/internal/provider/vcsim/vcsim_test.go index f2a291b0..cefbc550 100644 --- a/vmware-event-router/internal/provider/vcsim/vcsim_test.go +++ b/vmware-event-router/internal/provider/vcsim/vcsim_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package vcsim @@ -135,8 +134,7 @@ func Test_eventHandler(t *testing.T) { } // fakeProcessor implements the processor interface -type fakeProcessor struct { -} +type fakeProcessor struct{} func (f fakeProcessor) PushMetrics(_ context.Context, _ metrics.Receiver) { } diff --git a/vmware-event-router/internal/provider/webhook/webhook.go b/vmware-event-router/internal/provider/webhook/webhook.go index fbd8260d..5fbe6e6c 100644 --- a/vmware-event-router/internal/provider/webhook/webhook.go +++ b/vmware-event-router/internal/provider/webhook/webhook.go @@ -41,10 +41,8 @@ const ( allowedRate = 1000 ) -var ( - // ErrInvalidPath is returned on an invalid webhook endpoint path - ErrInvalidPath = errors.New("invalid webhook endpoint path") -) +// ErrInvalidPath is returned on an invalid webhook endpoint path +var ErrInvalidPath = errors.New("invalid webhook endpoint path") // Server is a webhook event provider type Server struct { diff --git a/vmware-event-router/internal/provider/webhook/webhook_internal_test.go b/vmware-event-router/internal/provider/webhook/webhook_internal_test.go index 2466cdb0..83e52c86 100644 --- a/vmware-event-router/internal/provider/webhook/webhook_internal_test.go +++ b/vmware-event-router/internal/provider/webhook/webhook_internal_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package webhook diff --git a/vmware-event-router/internal/provider/webhook/webhook_test.go b/vmware-event-router/internal/provider/webhook/webhook_test.go index e591c1ae..3c2f5f8b 100644 --- a/vmware-event-router/internal/provider/webhook/webhook_test.go +++ b/vmware-event-router/internal/provider/webhook/webhook_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package webhook_test @@ -11,14 +10,15 @@ import ( ce "github.com/cloudevents/sdk-go/v2" cehttp "github.com/cloudevents/sdk-go/v2/protocol/http" - config "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/config/v1alpha1" - "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/metrics" - "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/provider/webhook" "go.uber.org/zap" "go.uber.org/zap/zaptest" "golang.org/x/sync/errgroup" - "gotest.tools/assert" + "gotest.tools/v3/assert" "knative.dev/pkg/logging" + + config "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/config/v1alpha1" + "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/metrics" + "github.com/vmware-samples/vcenter-event-broker-appliance/vmware-event-router/internal/provider/webhook" ) func Test_WebhookServer(t *testing.T) { diff --git a/vmware-event-router/internal/util/util_test.go b/vmware-event-router/internal/util/util_test.go index 1b6e16d8..fab8560b 100644 --- a/vmware-event-router/internal/util/util_test.go +++ b/vmware-event-router/internal/util/util_test.go @@ -1,5 +1,4 @@ //go:build unit -// +build unit package util diff --git a/vmware-event-router/routerconfig.schema.json b/vmware-event-router/routerconfig.schema.json index 4c0db482..40ebffb5 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"},"ActiveDirectoryAuthMethod":{"required":["domain","username","password"],"properties":{"domain":{"type":"string"},"username":{"type":"string"},"password":{"type":"string"}},"additionalProperties":false,"type":"object"},"AuthMethod":{"required":["type"],"properties":{"type":{"enum":["basic_auth","aws_access_key","active_directory"],"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"},"activeDirectoryAuth":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ActiveDirectoryAuthMethod","description":"Active Directory authentication with domain"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["basicAuth"],"title":"basicAuth"},{"required":["awsAccessKeyAuth"],"title":"awsAccessKeyAuth"},{"required":["activeDirectoryAuth"],"title":"activeDirectoryAuth"}]},"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","default":"0.0.0.0:8082"},"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","webhook","vcsim","horizon"],"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"},"webhook":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProviderConfigWebhook"},"horizon":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProviderConfigHorizon"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["vcenter"],"title":"vcenter"},{"required":["vcsim"],"title":"vcsim"},{"required":["webhook"],"title":"webhook"},{"required":["horizon"],"title":"horizon"}]},"ProviderConfigHorizon":{"required":["address","insecureSSL"],"properties":{"address":{"type":"string","default":"https://api.myhorizon.domain.local"},"insecureSSL":{"type":"boolean"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["auth"],"title":"auth"}]},"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"}]},"ProviderConfigWebhook":{"required":["bindAddress","path"],"properties":{"bindAddress":{"type":"string","default":"0.0.0.0:8080"},"path":{"type":"string","default":"/webhook"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object"},"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 +{"$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"},"ActiveDirectoryAuthMethod":{"required":["domain","username","password"],"properties":{"domain":{"type":"string"},"username":{"type":"string"},"password":{"type":"string"}},"additionalProperties":false,"type":"object"},"AuthMethod":{"required":["type"],"properties":{"type":{"enum":["basic_auth","aws_access_key","aws_iam_role","active_directory"],"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"},"activeDirectoryAuth":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ActiveDirectoryAuthMethod","description":"Active Directory authentication with domain"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["basicAuth"],"title":"basicAuth"},{"required":["awsAccessKeyAuth"],"title":"awsAccessKeyAuth"},{"required":["activeDirectoryAuth"],"title":"activeDirectoryAuth"}]},"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","default":"0.0.0.0:8082"},"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","webhook","vcsim","horizon"],"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"},"webhook":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProviderConfigWebhook"},"horizon":{"$schema":"http://json-schema.org/draft-04/schema#","$ref":"#/definitions/ProviderConfigHorizon"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["vcenter"],"title":"vcenter"},{"required":["vcsim"],"title":"vcsim"},{"required":["webhook"],"title":"webhook"},{"required":["horizon"],"title":"horizon"}]},"ProviderConfigHorizon":{"required":["address","insecureSSL"],"properties":{"address":{"type":"string","default":"https://api.myhorizon.domain.local"},"insecureSSL":{"type":"boolean"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object","oneOf":[{"required":["auth"],"title":"auth"}]},"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"}]},"ProviderConfigWebhook":{"required":["bindAddress","path"],"properties":{"bindAddress":{"type":"string","default":"0.0.0.0:8080"},"path":{"type":"string","default":"/webhook"},"auth":{"$ref":"#/definitions/AuthMethod","description":"Authentication configuration for this section"}},"additionalProperties":false,"type":"object"},"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