From 4a6709e5eb752aa06cc839c8a33568a5f2614e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Etzlstorfer?= Date: Fri, 20 Aug 2021 10:32:01 +0200 Subject: [PATCH] Initial commit --- .ci_env | 2 + .dockerignore | 0 .github/dependabot.yml | 12 + .github/workflows/CI.yml | 181 ++++++ .github/workflows/reviewdog.yml | 24 + .gitignore | 27 + .idea/runConfigurations/Build_Dockerfile.xml | 16 + .../Develop_on_Kubernetes.xml | 15 + .idea/runConfigurations/Run_on_Kubernetes.xml | 15 + ..._example_com_keptn_service_template_go.xml | 10 + .reviewdog.yml | 12 + CODEOWNERS | 10 + Dockerfile | 67 +++ LICENSE | 201 +++++++ README.md | 163 ++++++ deploy/service.yaml | 67 +++ eventhandler_test.go | 179 ++++++ eventhandlers.go | 224 ++++++++ go.mod | 12 + go.sum | 226 ++++++++ helm/.helmignore | 23 + helm/Chart.yaml | 6 + helm/README.md | 44 ++ helm/templates/_helpers.tpl | 63 ++ helm/templates/deployment.yaml | 101 ++++ helm/templates/service.yaml | 15 + helm/templates/serviceaccount.yaml | 30 + helm/templates/tests/test-api-connection.yaml | 19 + helm/values.schema.json | 110 ++++ helm/values.yaml | 59 ++ main.go | 539 ++++++++++++++++++ releasenotes/releasenotes_develop.md | 8 + skaffold.yaml | 13 + test-events/action.triggered.json | 30 + test-events/deployment.triggered.json | 30 + test-events/evaluation.triggered.json | 28 + test-events/get-sli.triggered.json | 27 + test-events/release.triggered.json | 25 + test-events/send-test-events.http | 56 ++ test-events/service.create.finished.json | 24 + 40 files changed, 2713 insertions(+) create mode 100644 .ci_env create mode 100644 .dockerignore create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/CI.yml create mode 100644 .github/workflows/reviewdog.yml create mode 100644 .gitignore create mode 100644 .idea/runConfigurations/Build_Dockerfile.xml create mode 100644 .idea/runConfigurations/Develop_on_Kubernetes.xml create mode 100644 .idea/runConfigurations/Run_on_Kubernetes.xml create mode 100644 .idea/runConfigurations/go_build_example_com_keptn_service_template_go.xml create mode 100644 .reviewdog.yml create mode 100644 CODEOWNERS create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 deploy/service.yaml create mode 100644 eventhandler_test.go create mode 100644 eventhandlers.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 helm/.helmignore create mode 100644 helm/Chart.yaml create mode 100644 helm/README.md create mode 100644 helm/templates/_helpers.tpl create mode 100644 helm/templates/deployment.yaml create mode 100644 helm/templates/service.yaml create mode 100644 helm/templates/serviceaccount.yaml create mode 100644 helm/templates/tests/test-api-connection.yaml create mode 100644 helm/values.schema.json create mode 100644 helm/values.yaml create mode 100644 main.go create mode 100644 releasenotes/releasenotes_develop.md create mode 100644 skaffold.yaml create mode 100644 test-events/action.triggered.json create mode 100644 test-events/deployment.triggered.json create mode 100644 test-events/evaluation.triggered.json create mode 100644 test-events/get-sli.triggered.json create mode 100644 test-events/release.triggered.json create mode 100644 test-events/send-test-events.http create mode 100644 test-events/service.create.finished.json diff --git a/.ci_env b/.ci_env new file mode 100644 index 0000000..9ffe598 --- /dev/null +++ b/.ci_env @@ -0,0 +1,2 @@ +DOCKER_ORGANIZATION="keptnsandbox" +IMAGE="keptn-service-template-go" \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e69de29 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5f4c27a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + - package-ecosystem: gomod + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..0608f0c --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,181 @@ +name: CI +on: + # always execute docker build when something is pushed to master or release-* branches + push: + branches: + - 'master' + - 'main' + - 'release-*' + # in addition, execute for pull requests to those branches + pull_request: + branches: + - 'master' + - 'main' + - 'release-*' +defaults: + run: + shell: bash +jobs: + prepare_ci_run: + name: Prepare CI Run + # Prepare CI Run looks at what has been changed in this commit/PR/... and determines which artifacts should be + # built afterwards (in other jobs that depend on this one). + runs-on: ubuntu-20.04 + outputs: # declare what this job outputs (so it can be re-used for other jobs) + # build config + # metadata + GIT_SHA: ${{ steps.extract_branch.outputs.GIT_SHA }} + BRANCH: ${{ steps.extract_branch.outputs.BRANCH }} + BRANCH_SLUG: ${{ steps.extract_branch.outputs.BRANCH_SLUG }} + VERSION: ${{ steps.get_version.outputs.VERSION }} + DATE: ${{ steps.get_datetime.outputs.DATE }} + TIME: ${{ steps.get_datetime.outputs.TIME }} + DATETIME: ${{ steps.get_datetime.outputs.DATETIME }} + + steps: + - name: Check out code + uses: actions/checkout@v2.3.4 + with: + fetch-depth: 0 # need to checkout "all commits" for certain features to work (e.g., get all changed files) + + - name: Load CI Environment from .ci_env + id: load_ci_env + uses: c-py/action-dotenv-to-setenv@v3 + with: + env-file: .ci_env + + - name: Extract branch name + id: extract_branch + # see https://github.com/keptn/gh-action-extract-branch-name for details + uses: keptn/gh-action-extract-branch-name@main + + - name: 'Get Previous tag' + id: get_previous_tag + uses: "WyriHaximus/github-action-get-previous-tag@v1.1" + with: + fallback: "0.0.1" + - name: 'Get next patch version' + id: get_next_semver_tag + uses: "WyriHaximus/github-action-next-semvers@v1.1" + with: + version: ${{ steps.get_previous_tag.outputs.tag }} + - name: Get the version + id: get_version + env: + BRANCH: ${{ steps.extract_branch.outputs.BRANCH }} + BRANCH_SLUG: ${{ steps.extract_branch.outputs.BRANCH_SLUG }} + shell: bash + run: | + # determine version + GIT_LAST_TAG=${{ steps.get_previous_tag.outputs.tag }} + GIT_NEXT_TAG=${{ steps.get_next_semver_tag.outputs.patch }} + echo "GIT_LAST_TAG=${GIT_LAST_TAG}, GIT_NEXT_TAG=${GIT_NEXT_TAG}" + + if [[ "$BRANCH" == "release-"* ]]; then + # Release Branch: extract version from branch name + VERSION=${BRANCH#"release-"} + else + if [[ "$BRANCH" == "master" ]]; then + # master branch = latest + VERSION="${GIT_NEXT_TAG}-dev" + else + # Feature/Development Branch - use last tag with branch slug + VERSION="${GIT_NEXT_TAG}-dev-${BRANCH_SLUG}" + fi + fi + + echo "VERSION=${VERSION}" + + echo "##[set-output name=VERSION;]$(echo ${VERSION})" + - name: Get current date and time + id: get_datetime + run: | + echo "::set-output name=DATE::$(date +'%Y%m%d')" + echo "::set-output name=TIME::$(date +'%H%M')" + echo "::set-output name=DATETIME::$(date +'%Y%m%d')$(date +'%H%M')" + + ############################################################################ + # Unit tests # + ############################################################################ + unit-tests: + name: Unit Tests + needs: prepare_ci_run + runs-on: ubuntu-20.04 + steps: + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ^1.16 + - name: Checkout Code + uses: actions/checkout@v2.3.4 + + - name: Load CI Environment from .ci_env + id: load_ci_env + uses: c-py/action-dotenv-to-setenv@v3 + with: + env-file: .ci_env + + - name: Test + run: go test -coverprofile=coverage.txt -covermode=atomic -v ./... + working-directory: . + + + ############################################################################ + # Build Docker Image # + ############################################################################ + docker_build: + needs: [prepare_ci_run, unit-tests] + name: Docker Build + runs-on: ubuntu-20.04 + env: + BRANCH: ${{ needs.prepare_ci_run.outputs.BRANCH }} + VERSION: ${{ needs.prepare_ci_run.outputs.VERSION }} + DATETIME: ${{ needs.prepare_ci_run.outputs.DATE }}${{ needs.prepare_ci_run.outputs.TIME }} + GIT_SHA: ${{ needs.prepare_ci_run.outputs.GIT_SHA }} + steps: + - name: Checkout Code + uses: actions/checkout@v2.3.4 + + - name: Load CI Environment from .ci_env + id: load_ci_env + uses: c-py/action-dotenv-to-setenv@v3 + with: + env-file: .ci_env + + - id: docker_login + name: Docker Login + # only run docker login on pushes; also for PRs, but only if this is not a fork + if: (github.event_name == 'push') || (github.event.pull_request.head.repo.full_name == github.repository) + # note: GH does not allow to access secrets for PRs from a forked repositories due to security reasons + # that's fine, but it means we can't push images to dockerhub + uses: docker/login-action@v1.9.0 + with: + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - id: docker_build + name: Docker Build + uses: keptn/gh-action-build-docker-image@master + with: + VERSION: ${{ env.VERSION }} + IMAGE_NAME: "${{ env.DOCKER_ORGANIZATION }}/${{ env.IMAGE }}" + DATETIME: ${{ env.DATETIME }} + + - id: create_docker_build_report + name: Create Docker Build Report + run: | + echo "The following Docker Images have been built: " > docker_build_report_final.txt + cat docker_build_report.txt >> docker_build_report_final.txt || echo "* No images have been built or uploaded" >> docker_build_report_final.txt + echo "---" + cat docker_build_report_final.txt + + - id: report_docker_build_to_pr + # Comment the docker build report to the PR + # This only works for PRs coming from inside of the repo, not from forks + name: Report Docker Build to PR + if: (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) + uses: marocchino/sticky-pull-request-comment@v2.1.0 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + path: docker_build_report_final.txt + recreate: true \ No newline at end of file diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml new file mode 100644 index 0000000..be7a8f9 --- /dev/null +++ b/.github/workflows/reviewdog.yml @@ -0,0 +1,24 @@ +name: reviewdog +on: [pull_request] +jobs: + reviewdog: + name: reviewdog + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + id: go + - name: Check out code. + uses: actions/checkout@v2.3.4 + - name: Install linters + run: '( mkdir linters && cd linters && go get golang.org/x/lint/golint )' + - uses: reviewdog/action-setup@v1.0.3 + with: + reviewdog_version: latest + - name: Run reviewdog + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + reviewdog -reporter=github-pr-review \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57d2be8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +vendor/* +.DS_Store + +# binaries (created by go build on Linux/OSX) +main +keptn-service-template-go + +# IDE specific folders +.vscode + +# only ignore contents of local .idea +.idea/* +# but allow .idea shared run configurations +!.idea/runConfigurations/ diff --git a/.idea/runConfigurations/Build_Dockerfile.xml b/.idea/runConfigurations/Build_Dockerfile.xml new file mode 100644 index 0000000..83a1b39 --- /dev/null +++ b/.idea/runConfigurations/Build_Dockerfile.xml @@ -0,0 +1,16 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Develop_on_Kubernetes.xml b/.idea/runConfigurations/Develop_on_Kubernetes.xml new file mode 100644 index 0000000..1d51e33 --- /dev/null +++ b/.idea/runConfigurations/Develop_on_Kubernetes.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Run_on_Kubernetes.xml b/.idea/runConfigurations/Run_on_Kubernetes.xml new file mode 100644 index 0000000..8b112e5 --- /dev/null +++ b/.idea/runConfigurations/Run_on_Kubernetes.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/go_build_example_com_keptn_service_template_go.xml b/.idea/runConfigurations/go_build_example_com_keptn_service_template_go.xml new file mode 100644 index 0000000..4948018 --- /dev/null +++ b/.idea/runConfigurations/go_build_example_com_keptn_service_template_go.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.reviewdog.yml b/.reviewdog.yml new file mode 100644 index 0000000..111e075 --- /dev/null +++ b/.reviewdog.yml @@ -0,0 +1,12 @@ +runner: + golint: + cmd: golint ./... + errorformat: + - "%f:%l:%c: %m" + level: warning + gofmt: + cmd: gofmt -l -s .|xargs -I{} echo {}:1 file {} is not gofmted + errorformat: + - "%f:%l %m" + govet: + cmd: go vet -all . diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..c291844 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,10 @@ +############################################################## +# +# Lists the owners of this project +# +############################################################## +# +# Learn about CODEOWNERS file format: +# https://help.github.com/en/articles/about-code-owners +# + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f2dfd25 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,67 @@ +# Use the offical Golang image to create a build artifact. +# This is based on Debian and sets the GOPATH to /go. +# https://hub.docker.com/_/golang +FROM golang:1.16.2-alpine as builder + +RUN apk add --no-cache gcc libc-dev git + +WORKDIR /src/keptn-service-template-go + +ARG version=develop +ENV VERSION="${version}" + +# Force the go compiler to use modules +ENV GO111MODULE=on +ENV BUILDFLAGS="" +ENV GOPROXY=https://proxy.golang.org + +# Copy `go.mod` for definitions and `go.sum` to invalidate the next layer +# in case of a change in the dependencies +COPY go.mod go.sum ./ + +# Download dependencies +RUN go mod download + +ARG debugBuild + +# set buildflags for debug build +RUN if [ ! -z "$debugBuild" ]; then export BUILDFLAGS='-gcflags "all=-N -l"'; fi + +# Copy local code to the container image. +COPY . . + +# Build the command inside the container. +# (You may fetch or manage dependencies here, either manually or with a tool like "godep".) +RUN GOOS=linux go build -ldflags '-linkmode=external' $BUILDFLAGS -v -o keptn-service-template-go + +# Use a Docker multi-stage build to create a lean production image. +# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds +FROM alpine:3.13 +ENV ENV=production + +# Install extra packages +# See https://github.com/gliderlabs/docker-alpine/issues/136#issuecomment-272703023 + +RUN apk update && apk upgrade \ + && apk add ca-certificates libc6-compat \ + && update-ca-certificates \ + && rm -rf /var/cache/apk/* + +ARG version=develop +ENV VERSION="${version}" + +# Copy the binary to the production image from the builder stage. +COPY --from=builder /src/keptn-service-template-go/keptn-service-template-go /keptn-service-template-go + +EXPOSE 8080 + +# required for external tools to detect this as a go binary +ENV GOTRACEBACK=all + +# KEEP THE FOLLOWING LINES COMMENTED OUT!!! (they will be included within the travis-ci build) +#build-uncomment ADD MANIFEST / +#build-uncomment COPY entrypoint.sh / +#build-uncomment ENTRYPOINT ["/entrypoint.sh"] + +# Run the web service on container startup. +CMD ["/keptn-service-template-go"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7cadfe2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Dynatrace LLC + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9851796 --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +# README + +This is a Keptn Service Template written in GoLang. Follow the instructions below for writing your own Keptn integration. + +Quick start: + +1. In case you want to contribute your service to keptn-sandbox or keptn-contrib, make sure you have read and understood the [Contributing Guidelines](https://github.com/keptn-sandbox/contributing). +1. Click [Use this template](https://github.com/keptn-sandbox/keptn-service-template-go/generate) on top of the repository, or download the repo as a zip-file, extract it into a new folder named after the service you want to create (e.g., simple-service) +1. Replace every occurrence of (docker) image names and tags from `keptnsandbox/keptn-service-template-go` to your docker organization and image name (e.g., `yourorganization/simple-service`) +1. Replace every occurrence of `keptn-service-template-go` with the name of your service (e.g., `simple-service`) +1. Optional (but recommended): Create a git repo (e.g., on `github.com/your-username/simple-service`) +1. Àdapt the [go.mod](go.mod) file and change `example.com/` to the actual package name (e.g., `github.com/your-username/simple-service`) +1. Add yourself to the [CODEOWNERS](CODEOWNERS) file +1. Initialize a git repository: + * `git init .` + * `git add .` + * `git commit -m "Initial Commit"` +1. Optional: Push your code an upstream git repo (e.g., GitHub) and adapt all links that contain `github.com` (e.g., to `github.com/your-username/simple-service`) +1. Figure out whether your Kubernetes Deployment requires [any RBAC rules or a different service-account](https://github.com/keptn-sandbox/contributing#rbac-guidelines), and adapt [deploy/service.yaml](deploy/service.yaml) accordingly (initial setup is `serviceAccountName: keptn-default`). +1. Last but not least: Remove this intro within the README file and make sure the README file properly states what this repository is about + +--- + +# keptn-service-template-go +![GitHub release (latest by date)](https://img.shields.io/github/v/release/keptn-sandbox/keptn-service-template-go) +[![Go Report Card](https://goreportcard.com/badge/github.com/keptn-sandbox/keptn-service-template-go)](https://goreportcard.com/report/github.com/keptn-sandbox/keptn-service-template-go) + +This implements a keptn-service-template-go for Keptn. If you want to learn more about Keptn visit us on [keptn.sh](https://keptn.sh) + +## Compatibility Matrix + +*Please fill in your versions accordingly* + +| Keptn Version | [Keptn-Service-Template-Go Docker Image](https://hub.docker.com/r/keptnsandbox/keptn-service-template-go/tags) | +|:----------------:|:----------------------------------------:| +| 0.6.1 | keptnsandbox/keptn-service-template-go:0.1.0 | +| 0.7.1 | keptnsandbox/keptn-service-template-go:0.1.1 | +| 0.7.2 | keptnsandbox/keptn-service-template-go:0.1.2 | + +## Installation + +The *keptn-service-template-go* can be installed as a part of [Keptn's uniform](https://keptn.sh). + +### Deploy in your Kubernetes cluster + +To deploy the current version of the *keptn-service-template-go* in your Keptn Kubernetes cluster, apply the [`deploy/service.yaml`](deploy/service.yaml) file: + +```console +kubectl apply -f deploy/service.yaml +``` + +This should install the `keptn-service-template-go` together with a Keptn `distributor` into the `keptn` namespace, which you can verify using + +```console +kubectl -n keptn get deployment keptn-service-template-go -o wide +kubectl -n keptn get pods -l run=keptn-service-template-go +``` + +### Up- or Downgrading + +Adapt and use the following command in case you want to up- or downgrade your installed version (specified by the `$VERSION` placeholder): + +```console +kubectl -n keptn set image deployment/keptn-service-template-go keptn-service-template-go=keptnsandbox/keptn-service-template-go:$VERSION --record +``` + +### Uninstall + +To delete a deployed *keptn-service-template-go*, use the file `deploy/*.yaml` files from this repository and delete the Kubernetes resources: + +```console +kubectl delete -f deploy/service.yaml +``` + +## Development + +Development can be conducted using any GoLang compatible IDE/editor (e.g., Jetbrains GoLand, VSCode with Go plugins). + +It is recommended to make use of branches as follows: + +* `master` contains the latest potentially unstable version +* `release-*` contains a stable version of the service (e.g., `release-0.1.0` contains version 0.1.0) +* create a new branch for any changes that you are working on, e.g., `feature/my-cool-stuff` or `bug/overflow` +* once ready, create a pull request from that branch back to the `master` branch + +When writing code, it is recommended to follow the coding style suggested by the [Golang community](https://github.com/golang/go/wiki/CodeReviewComments). + +### Where to start + +If you don't care about the details, your first entrypoint is [eventhandlers.go](eventhandlers.go). Within this file + you can add implementation for pre-defined Keptn Cloud events. + +To better understand all variants of Keptn CloudEvents, please look at the [Keptn Spec](https://github.com/keptn/spec). + +If you want to get more insights into processing those CloudEvents or even defining your own CloudEvents in code, please + look into [main.go](main.go) (specifically `processKeptnCloudEvent`), [deploy/service.yaml](deploy/service.yaml), + consult the [Keptn docs](https://keptn.sh/docs/) as well as existing [Keptn Core](https://github.com/keptn/keptn) and + [Keptn Contrib](https://github.com/keptn-contrib/) services. + +### Common tasks + +* Build the binary: `go build -ldflags '-linkmode=external' -v -o keptn-service-template-go` +* Run tests: `go test -race -v ./...` +* Build the docker image: `docker build . -t keptnsandbox/keptn-service-template-go:dev` (Note: Ensure that you use the correct DockerHub account/organization) +* Run the docker image locally: `docker run --rm -it -p 8080:8080 keptnsandbox/keptn-service-template-go:dev` +* Push the docker image to DockerHub: `docker push keptnsandbox/keptn-service-template-go:dev` (Note: Ensure that you use the correct DockerHub account/organization) +* Deploy the service using `kubectl`: `kubectl apply -f deploy/` +* Delete/undeploy the service using `kubectl`: `kubectl delete -f deploy/` +* Watch the deployment using `kubectl`: `kubectl -n keptn get deployment keptn-service-template-go -o wide` +* Get logs using `kubectl`: `kubectl -n keptn logs deployment/keptn-service-template-go -f` +* Watch the deployed pods using `kubectl`: `kubectl -n keptn get pods -l run=keptn-service-template-go` +* Deploy the service using [Skaffold](https://skaffold.dev/): `skaffold run --default-repo=your-docker-registry --tail` (Note: Replace `your-docker-registry` with your DockerHub username; also make sure to adapt the image name in [skaffold.yaml](skaffold.yaml)) + + +### Testing Cloud Events + +We have dummy cloud-events in the form of [RFC 2616](https://ietf.org/rfc/rfc2616.txt) requests in the [test-events/](test-events/) directory. These can be easily executed using third party plugins such as the [Huachao Mao REST Client in VS Code](https://marketplace.visualstudio.com/items?itemName=humao.rest-client). + +## Automation + +### GitHub Actions: Automated Pull Request Review + +This repo uses [reviewdog](https://github.com/reviewdog/reviewdog) for automated reviews of Pull Requests. + +You can find the details in [.github/workflows/reviewdog.yml](.github/workflows/reviewdog.yml). + +### GitHub Actions: Unit Tests + +This repo has automated unit tests for pull requests. + +You can find the details in [.github/workflows/tests.yml](.github/workflows/tests.yml). + +### GH Actions/Workflow: Build Docker Images + +This repo uses GH Actions and Workflows to test the code and automatically build docker images. + +Docker Images are automatically pushed based on the configuration done in [.ci_env](.ci_env) and the two [GitHub Secrets](https://github.com/keptn-sandbox/keptn-service-template-go/settings/secrets/actions) +* `REGISTRY_USER` - your DockerHub username +* `REGISTRY_PASSWORD` - a DockerHub [access token](https://hub.docker.com/settings/security) (alternatively, your DockerHub password) + +## How to release a new version of this service + +It is assumed that the current development takes place in the master branch (either via Pull Requests or directly). + +To make use of the built-in automation using GH Actions for releasing a new version of this service, you should + +* branch away from master to a branch called `release-x.y.z` (where `x.y.z` is your version), +* write release notes in the [releasenotes/](releasenotes/) folder, +* check the output of GH Actions builds for the release branch, +* verify that your image was built and pushed to DockerHub with the right tags, +* update the image tags in [deploy/service.yaml], and +* test your service against a working Keptn installation. + +If any problems occur, fix them in the release branch and test them again. + +Once you have confirmed that everything works and your version is ready to go, you should + +* create a new release on the release branch using the [GitHub releases page](https://github.com/keptn-sandbox/keptn-service-template-go/releases), and +* merge any changes from the release branch back to the master branch. + +## License + +Please find more information in the [LICENSE](LICENSE) file. diff --git a/deploy/service.yaml b/deploy/service.yaml new file mode 100644 index 0000000..8a39c30 --- /dev/null +++ b/deploy/service.yaml @@ -0,0 +1,67 @@ +--- +# Deployment of our keptn-service-template-go +apiVersion: apps/v1 +kind: Deployment +metadata: + name: keptn-service-template-go + namespace: keptn +spec: + selector: + matchLabels: + run: keptn-service-template-go + replicas: 1 + template: + metadata: + labels: + run: keptn-service-template-go + spec: + containers: + - name: keptn-service-template-go + image: keptnsandbox/keptn-service-template-go:latest # Todo: Replace this with your image name + ports: + - containerPort: 8080 + env: + - name: CONFIGURATION_SERVICE + value: 'http://configuration-service:8080' + - name: distributor + image: keptn/distributor:0.8.3 + livenessProbe: + httpGet: + path: /health + port: 10999 + initialDelaySeconds: 5 + periodSeconds: 5 + imagePullPolicy: Always + ports: + - containerPort: 8080 + resources: + requests: + memory: "16Mi" + cpu: "25m" + limits: + memory: "128Mi" + cpu: "250m" + env: + - name: PUBSUB_URL + value: 'nats://keptn-nats-cluster' + - name: PUBSUB_TOPIC + value: 'sh.keptn.>' + - name: PUBSUB_RECIPIENT + value: '127.0.0.1' + serviceAccountName: keptn-default +--- +# Expose keptn-service-template-go via Port 8080 within the cluster +apiVersion: v1 +kind: Service +metadata: + name: keptn-service-template-go + namespace: keptn + labels: + run: keptn-service-template-go +spec: + ports: + - port: 8080 + protocol: TCP + selector: + run: keptn-service-template-go + diff --git a/eventhandler_test.go b/eventhandler_test.go new file mode 100644 index 0000000..e741e17 --- /dev/null +++ b/eventhandler_test.go @@ -0,0 +1,179 @@ +package main + +import ( + "encoding/json" + "fmt" + "github.com/keptn/go-utils/pkg/lib/v0_2_0/fake" + "io/ioutil" + "testing" + + keptn "github.com/keptn/go-utils/pkg/lib/keptn" + keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" + + cloudevents "github.com/cloudevents/sdk-go/v2" // make sure to use v2 cloudevents here +) + +/** + * loads a cloud event from the passed test json file and initializes a keptn object with it + */ +func initializeTestObjects(eventFileName string) (*keptnv2.Keptn, *cloudevents.Event, error) { + // load sample event + eventFile, err := ioutil.ReadFile(eventFileName) + if err != nil { + return nil, nil, fmt.Errorf("Cant load %s: %s", eventFileName, err.Error()) + } + + incomingEvent := &cloudevents.Event{} + err = json.Unmarshal(eventFile, incomingEvent) + if err != nil { + return nil, nil, fmt.Errorf("Error parsing: %s", err.Error()) + } + + // Add a Fake EventSender to KeptnOptions + var keptnOptions = keptn.KeptnOpts{ + EventSender: &fake.EventSender{}, + } + keptnOptions.UseLocalFileSystem = true + myKeptn, err := keptnv2.NewKeptn(incomingEvent, keptnOptions) + + return myKeptn, incomingEvent, err +} + +// Tests HandleActionTriggeredEvent +// TODO: Add your test-code +func TestHandleActionTriggeredEvent(t *testing.T) { + myKeptn, incomingEvent, err := initializeTestObjects("test-events/action.triggered.json") + if err != nil { + t.Error(err) + return + } + + specificEvent := &keptnv2.ActionTriggeredEventData{} + err = incomingEvent.DataAs(specificEvent) + if err != nil { + t.Errorf("Error getting keptn event data") + } + + err = HandleActionTriggeredEvent(myKeptn, *incomingEvent, specificEvent) + if err != nil { + t.Errorf("Error: " + err.Error()) + } + + gotEvents := len(myKeptn.EventSender.(*fake.EventSender).SentEvents) + + // Verify that HandleGetSliTriggeredEvent has sent 2 cloudevents + if gotEvents != 2 { + t.Errorf("Expected two events to be sent, but got %v", gotEvents) + } + + // Verify that the first CE sent is a .started event + if keptnv2.GetStartedEventType(keptnv2.ActionTaskName) != myKeptn.EventSender.(*fake.EventSender).SentEvents[0].Type() { + t.Errorf("Expected a action.started event type") + } + + // Verify that the second CE sent is a .finished event + if keptnv2.GetFinishedEventType(keptnv2.ActionTaskName) != myKeptn.EventSender.(*fake.EventSender).SentEvents[1].Type() { + t.Errorf("Expected a action.finished event type") + } +} + +// Tests HandleDeploymentTriggeredEvent +// TODO: Add your test-code +func TestHandleDeploymentTriggeredEvent(t *testing.T) { + myKeptn, incomingEvent, err := initializeTestObjects("test-events/evaluation.triggered.json") + if err != nil { + t.Error(err) + return + } + + specificEvent := &keptnv2.DeploymentTriggeredEventData{} + err = incomingEvent.DataAs(specificEvent) + if err != nil { + t.Errorf("Error getting keptn event data") + } + + err = HandleDeploymentTriggeredEvent(myKeptn, *incomingEvent, specificEvent) + if err != nil { + t.Errorf("Error: " + err.Error()) + } +} + +// Tests HandleEvaluationTriggeredEvent +// TODO: Add your test-code +func TestHandleEvaluationTriggeredEvent(t *testing.T) { + myKeptn, incomingEvent, err := initializeTestObjects("test-events/evaluation.triggered.json") + if err != nil { + t.Error(err) + return + } + + specificEvent := &keptnv2.EvaluationTriggeredEventData{} + err = incomingEvent.DataAs(specificEvent) + if err != nil { + t.Errorf("Error getting keptn event data") + } + + err = HandleEvaluationTriggeredEvent(myKeptn, *incomingEvent, specificEvent) + if err != nil { + t.Errorf("Error: " + err.Error()) + } +} + +// Tests the HandleGetSliTriggeredEvent Handler +// TODO: Add your test-code +func TestHandleGetSliTriggered(t *testing.T) { + myKeptn, incomingEvent, err := initializeTestObjects("test-events/get-sli.triggered.json") + if err != nil { + t.Error(err) + return + } + + specificEvent := &keptnv2.GetSLITriggeredEventData{} + err = incomingEvent.DataAs(specificEvent) + if err != nil { + t.Errorf("Error getting keptn event data") + } + + err = HandleGetSliTriggeredEvent(myKeptn, *incomingEvent, specificEvent) + if err != nil { + t.Errorf("Error: " + err.Error()) + } + + gotEvents := len(myKeptn.EventSender.(*fake.EventSender).SentEvents) + + // Verify that HandleGetSliTriggeredEvent has sent 2 cloudevents + if gotEvents != 2 { + t.Errorf("Expected two events to be sent, but got %v", gotEvents) + } + + // Verify that the first CE sent is a .started event + if keptnv2.GetStartedEventType(keptnv2.GetSLITaskName) != myKeptn.EventSender.(*fake.EventSender).SentEvents[0].Type() { + t.Errorf("Expected a get-sli.started event type") + } + + // Verify that the second CE sent is a .finished event + if keptnv2.GetFinishedEventType(keptnv2.GetSLITaskName) != myKeptn.EventSender.(*fake.EventSender).SentEvents[1].Type() { + t.Errorf("Expected a get-sli.finished event type") + } +} + +// Tests the HandleReleaseTriggeredEvent Handler +// TODO: Add your test-code +func TestHandleReleaseTriggeredEvent(t *testing.T) { + myKeptn, incomingEvent, err := initializeTestObjects("test-events/release.triggered.json") + if err != nil { + t.Error(err) + return + } + + specificEvent := &keptnv2.ReleaseTriggeredEventData{} + err = incomingEvent.DataAs(specificEvent) + if err != nil { + t.Errorf("Error getting keptn event data") + } + + err = HandleReleaseTriggeredEvent(myKeptn, *incomingEvent, specificEvent) + if err != nil { + t.Errorf("Error: " + err.Error()) + } +} diff --git a/eventhandlers.go b/eventhandlers.go new file mode 100644 index 0000000..8cb5419 --- /dev/null +++ b/eventhandlers.go @@ -0,0 +1,224 @@ +package main + +import ( + "fmt" + "log" + "time" + + cloudevents "github.com/cloudevents/sdk-go/v2" // make sure to use v2 cloudevents here + keptn "github.com/keptn/go-utils/pkg/lib" + keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" +) + +/** +* Here are all the handler functions for the individual event +* See https://github.com/keptn/spec/blob/0.8.0-alpha/cloudevents.md for details on the payload +**/ + +// GenericLogKeptnCloudEventHandler is a generic handler for Keptn Cloud Events that logs the CloudEvent +func GenericLogKeptnCloudEventHandler(myKeptn *keptnv2.Keptn, incomingEvent cloudevents.Event, data interface{}) error { + log.Printf("Handling %s Event: %s", incomingEvent.Type(), incomingEvent.Context.GetID()) + log.Printf("CloudEvent %T: %v", data, data) + + return nil +} + +// OldHandleConfigureMonitoringEvent handles old configure-monitoring events +// TODO: add in your handler code +func OldHandleConfigureMonitoringEvent(myKeptn *keptnv2.Keptn, incomingEvent cloudevents.Event, data *keptn.ConfigureMonitoringEventData) error { + log.Printf("Handling old configure-monitoring Event: %s", incomingEvent.Context.GetID()) + + return nil +} + +// HandleConfigureMonitoringTriggeredEvent handles configure-monitoring.triggered events +// TODO: add in your handler code +func HandleConfigureMonitoringTriggeredEvent(myKeptn *keptnv2.Keptn, incomingEvent cloudevents.Event, data *keptnv2.ConfigureMonitoringTriggeredEventData) error { + log.Printf("Handling configure-monitoring.triggered Event: %s", incomingEvent.Context.GetID()) + + return nil +} + +// HandleDeploymentTriggeredEvent handles deployment.triggered events +// TODO: add in your handler code +func HandleDeploymentTriggeredEvent(myKeptn *keptnv2.Keptn, incomingEvent cloudevents.Event, data *keptnv2.DeploymentTriggeredEventData) error { + log.Printf("Handling deployment.triggered Event: %s", incomingEvent.Context.GetID()) + + return nil +} + +// HandleTestTriggeredEvent handles test.triggered events +// TODO: add in your handler code +func HandleTestTriggeredEvent(myKeptn *keptnv2.Keptn, incomingEvent cloudevents.Event, data *keptnv2.TestTriggeredEventData) error { + log.Printf("Handling test.triggered Event: %s", incomingEvent.Context.GetID()) + + return nil +} + +// HandleApprovalTriggeredEvent handles approval.triggered events +// TODO: add in your handler code +func HandleApprovalTriggeredEvent(myKeptn *keptnv2.Keptn, incomingEvent cloudevents.Event, data *keptnv2.ApprovalTriggeredEventData) error { + log.Printf("Handling approval.triggered Event: %s", incomingEvent.Context.GetID()) + + return nil +} + +// HandleEvaluationTriggeredEvent handles evaluation.triggered events +// TODO: add in your handler code +func HandleEvaluationTriggeredEvent(myKeptn *keptnv2.Keptn, incomingEvent cloudevents.Event, data *keptnv2.EvaluationTriggeredEventData) error { + log.Printf("Handling evaluation.triggered Event: %s", incomingEvent.Context.GetID()) + + return nil +} + +// HandleReleaseTriggeredEvent handles release.triggered events +// TODO: add in your handler code +func HandleReleaseTriggeredEvent(myKeptn *keptnv2.Keptn, incomingEvent cloudevents.Event, data *keptnv2.ReleaseTriggeredEventData) error { + log.Printf("Handling release.triggered Event: %s", incomingEvent.Context.GetID()) + + return nil +} + +// HandleGetSliTriggeredEvent handles get-sli.triggered events if SLIProvider == keptn-service-template-go +// This function acts as an example showing how to handle get-sli events by sending .started and .finished events +// TODO: adapt handler code to your needs +func HandleGetSliTriggeredEvent(myKeptn *keptnv2.Keptn, incomingEvent cloudevents.Event, data *keptnv2.GetSLITriggeredEventData) error { + log.Printf("Handling get-sli.triggered Event: %s", incomingEvent.Context.GetID()) + + // Step 1 - Do we need to do something? + // Lets make sure we are only processing an event that really belongs to our SLI Provider + if data.GetSLI.SLIProvider != "keptn-service-template-go" { + log.Printf("Not handling get-sli event as it is meant for %s", data.GetSLI.SLIProvider) + return nil + } + + // Step 2 - Send out a get-sli.started CloudEvent + // The get-sli.started cloud-event is new since Keptn 0.8.0 and is required to be send when the task is started + _, err := myKeptn.SendTaskStartedEvent(data, ServiceName) + + if err != nil { + errMsg := fmt.Sprintf("Failed to send task started CloudEvent (%s), aborting...", err.Error()) + log.Println(errMsg) + return err + } + + // Step 4 - prep-work + // Get any additional input / configuration data + // - Labels: get the incoming labels for potential config data and use it to pass more labels on result, e.g: links + // - SLI.yaml: if your service uses SLI.yaml to store query definitions for SLIs get that file from Keptn + labels := data.Labels + if labels == nil { + labels = make(map[string]string) + } + testRunID := labels["testRunId"] + + // Step 5 - get SLI Config File + // Get SLI File from keptn-service-template-go subdirectory of the config repo - to add the file use: + // keptn add-resource --project=PROJECT --stage=STAGE --service=SERVICE --resource=my-sli-config.yaml --resourceUri=keptn-service-template-go/sli.yaml + sliFile := "keptn-service-template-go/sli.yaml" + sliConfigFileContent, err := myKeptn.GetKeptnResource(sliFile) + + // FYI you do not need to "fail" if sli.yaml is missing, you can also assume smart defaults like we do + // in keptn-contrib/dynatrace-service and keptn-contrib/prometheus-service + if err != nil { + // failed to fetch sli config file + errMsg := fmt.Sprintf("Failed to fetch SLI file %s from config repo: %s", sliFile, err.Error()) + log.Println(errMsg) + // send a get-sli.finished event with status=error and result=failed back to Keptn + + _, err = myKeptn.SendTaskFinishedEvent(&keptnv2.EventData{ + Status: keptnv2.StatusErrored, + Result: keptnv2.ResultFailed, + }, ServiceName) + + return err + } + + fmt.Println(sliConfigFileContent) + + // Step 6 - do your work - iterate through the list of requested indicators and return their values + // Indicators: this is the list of indicators as requested in the SLO.yaml + // SLIResult: this is the array that will receive the results + indicators := data.GetSLI.Indicators + sliResults := []*keptnv2.SLIResult{} + + for _, indicatorName := range indicators { + sliResult := &keptnv2.SLIResult{ + Metric: indicatorName, + Value: 123.4, // ToDo: Fetch the values from your monitoring tool here + } + sliResults = append(sliResults, sliResult) + } + + // Step 7 - add additional context via labels (e.g., a backlink to the monitoring or CI tool) + labels["Link to Data Source"] = "https://mydatasource/myquery?testRun=" + testRunID + + // Step 8 - Build get-sli.finished event data + getSliFinishedEventData := &keptnv2.GetSLIFinishedEventData{ + EventData: keptnv2.EventData{ + Status: keptnv2.StatusSucceeded, + Result: keptnv2.ResultPass, + }, + GetSLI: keptnv2.GetSLIFinished{ + IndicatorValues: sliResults, + Start: data.GetSLI.Start, + End: data.GetSLI.End, + }, + } + + _, err = myKeptn.SendTaskFinishedEvent(getSliFinishedEventData, ServiceName) + + if err != nil { + errMsg := fmt.Sprintf("Failed to send task finished CloudEvent (%s), aborting...", err.Error()) + log.Println(errMsg) + return err + } + + return nil +} + +// HandleProblemEvent handles two problem events: +// - ProblemOpenEventType = "sh.keptn.event.problem.open" +// - ProblemEventType = "sh.keptn.events.problem" +// TODO: add in your handler code +func HandleProblemEvent(myKeptn *keptnv2.Keptn, incomingEvent cloudevents.Event, data *keptn.ProblemEventData) error { + log.Printf("Handling Problem Event: %s", incomingEvent.Context.GetID()) + + // Deprecated since Keptn 0.7.0 - use the HandleActionTriggeredEvent instead + + return nil +} + +// HandleActionTriggeredEvent handles action.triggered events +// TODO: add in your handler code +func HandleActionTriggeredEvent(myKeptn *keptnv2.Keptn, incomingEvent cloudevents.Event, data *keptnv2.ActionTriggeredEventData) error { + log.Printf("Handling Action Triggered Event: %s", incomingEvent.Context.GetID()) + log.Printf("Action=%s\n", data.Action.Action) + + // check if action is supported + if data.Action.Action == "action-xyz" { + // ----------------------------------------------------- + // 1. Send Action.Started Cloud-Event + // ----------------------------------------------------- + myKeptn.SendTaskStartedEvent(data, ServiceName) + + // ----------------------------------------------------- + // 2. Implement your remediation action here + // ----------------------------------------------------- + time.Sleep(5 * time.Second) // Example: Wait 5 seconds. Maybe the problem fixes itself. + + // ----------------------------------------------------- + // 3. Send Action.Finished Cloud-Event + // ----------------------------------------------------- + myKeptn.SendTaskFinishedEvent(&keptnv2.EventData{ + Status: keptnv2.StatusSucceeded, // alternative: keptnv2.StatusErrored + Result: keptnv2.ResultPass, // alternative: keptnv2.ResultFailed + Message: "Successfully sleeped!", + }, ServiceName) + + } else { + log.Printf("Retrieved unknown action %s, skipping...", data.Action.Action) + return nil + } + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6d01b29 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module example.com/keptn-service-template-go + +go 1.16 + +require ( + github.com/cloudevents/sdk-go/v2 v2.3.1 + github.com/kelseyhightower/envconfig v1.4.0 + github.com/keptn/go-utils v0.8.4 + github.com/mitchellh/mapstructure v1.2.2 // indirect + github.com/onsi/ginkgo v1.12.0 // indirect + github.com/onsi/gomega v1.9.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..48cded7 --- /dev/null +++ b/go.sum @@ -0,0 +1,226 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudevents/sdk-go/v2 v2.3.1 h1:QRTu0yRA4FbznjRSds0/4Hy6cVYpWV2wInlNJSHWAtw= +github.com/cloudevents/sdk-go/v2 v2.3.1/go.mod h1:4fO2UjPMYYR1/7KPJQCwTPb0lFA8zYuitkUpAZFSY1Q= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.4 h1:1TjOzrWkj+9BrjnM1yPAICbaoC0FyfD49oVkTBrSSa0= +github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2 h1:rf5ArTHmIJxyV5Oiks+Su0mUens1+AjpkPoWr5xFRcI= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4 h1:csnOgcgAiuGoM/Po7PEpKDoNulCcF3FGbSnbHfxgjMI= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +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-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.4 h1:LGjO87VyXY3bIKjlYpXSFuLRG2mTeuYlZyeNwFFWpyM= +github.com/go-openapi/validate v0.19.4/go.mod h1:BkJ0ZmXui7yB0bJXWSXgLPNTmbLVeX/3D1xn/N9mMUM= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +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/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/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.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/keptn/go-utils v0.8.4 h1:v+Wlpw5iD8hauW1nnAt7yKF5+QYEKibljQSozgKouQY= +github.com/keptn/go-utils v0.8.4/go.mod h1:8cm/j/fPLl+qpSIDyyw5WVqT7W6W/ZlsAtMv8dLKtPU= +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/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/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac h1:+2b6iGRJe3hvV/yVXrd41yVEjxuFHxasJqDhkIjS4gk= +github.com/lightstep/tracecontext.go v0.0.0-20181129014701-1757c391b1ac/go.mod h1:Frd2bnT3w5FB5q49ENTfVlztJES+1k/7lyWX2+9gq/M= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +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.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +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.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/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/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1 h1:Sq1fR+0c58RME5EoqKdjkiQAmPjmfHlZOoRI6fTUOcs= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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-20181005035420-146acd28ed58/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-20190320064053-1272bf9dcd53/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-20190613194153-d28f0bde5980/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-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +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-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/sys v0.0.0-20180830151530-49385e6e1522/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +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-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +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.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 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/helm/.helmignore b/helm/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 0000000..23f555d --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +appVersion: 0.8.0 +description: Helm Chart for the keptn keptn-service-template-go +name: keptn-service-template-go +type: application +version: 0.8.0 diff --git a/helm/README.md b/helm/README.md new file mode 100644 index 0000000..a841961 --- /dev/null +++ b/helm/README.md @@ -0,0 +1,44 @@ + +keptn-service-template-go +=========== + +Helm Chart for the keptn keptn-service-template-go + + +## Configuration + +The following table lists the configurable parameters of the keptn-service-template-go chart and their default values. + +| Parameter | Description | Default | +| ------------------------ | ----------------------- | -------------- | +| `keptnservice.image.repository` | Container image name | `"docker.io/keptnsandbox/keptn-service-template-go"` | +| `keptnservice.image.pullPolicy` | Kubernetes image pull policy | `"IfNotPresent"` | +| `keptnservice.image.tag` | Container tag | `""` | +| `keptnservice.service.enabled` | Creates a kubernetes service for the keptn-service-template-go | `true` | +| `distributor.stageFilter` | Sets the stage this helm service belongs to | `""` | +| `distributor.serviceFilter` | Sets the service this helm service belongs to | `""` | +| `distributor.projectFilter` | Sets the project this helm service belongs to | `""` | +| `distributor.image.repository` | Container image name | `"docker.io/keptn/distributor"` | +| `distributor.image.pullPolicy` | Kubernetes image pull policy | `"IfNotPresent"` | +| `distributor.image.tag` | Container tag | `""` | +| `remoteControlPlane.enabled` | Enables remote execution plane mode | `false` | +| `remoteControlPlane.api.protocol` | Used protocol (http, https | `"https"` | +| `remoteControlPlane.api.hostname` | Hostname of the control plane cluster (and port) | `""` | +| `remoteControlPlane.api.apiValidateTls` | Defines if the control plane certificate should be validated | `true` | +| `remoteControlPlane.api.token` | Keptn api token | `""` | +| `imagePullSecrets` | Secrets to use for container registry credentials | `[]` | +| `serviceAccount.create` | Enables the service account creation | `true` | +| `serviceAccount.annotations` | Annotations to add to the service account | `{}` | +| `serviceAccount.name` | The name of the service account to use. | `""` | +| `podAnnotations` | Annotations to add to the created pods | `{}` | +| `podSecurityContext` | Set the pod security context (e.g. fsgroups) | `{}` | +| `securityContext` | Set the security context (e.g. runasuser) | `{}` | +| `resources` | Resource limits and requests | `{}` | +| `nodeSelector` | Node selector configuration | `{}` | +| `tolerations` | Tolerations for the pods | `[]` | +| `affinity` | Affinity rules | `{}` | + + + + + diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl new file mode 100644 index 0000000..2967d68 --- /dev/null +++ b/helm/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "keptn-service.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "keptn-service.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "keptn-service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "keptn-service.labels" -}} +helm.sh/chart: {{ include "keptn-service.chart" . }} +{{ include "keptn-service.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + + +{{/* +Selector labels +*/}} +{{- define "keptn-service.selectorLabels" -}} +app.kubernetes.io/name: {{ include "keptn-service.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "keptn-service.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "keptn-service.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml new file mode 100644 index 0000000..a8a7e40 --- /dev/null +++ b/helm/templates/deployment.yaml @@ -0,0 +1,101 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "keptn-service.fullname" . }} + labels: + {{- include "keptn-service.labels" . | nindent 4 }} + +spec: + replicas: 1 + selector: + matchLabels: + {{- include "keptn-service.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "keptn-service.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "keptn-service.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: keptn-service + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- if .Values.image }} + image: {{ .Values.image }} # use image from .Values.image (e.g., when starting via skaffold) + {{- else }} + image: "{{ .Values.keptnservice.image.repository }}:{{ .Values.keptnservice.image.tag | default .Chart.AppVersion }}" + {{- end }} + imagePullPolicy: {{ .Values.keptnservice.image.pullPolicy }} + ports: + - containerPort: 80 + env: + - name: CONFIGURATION_SERVICE + value: "http://localhost:8081/configuration-service" + - name: env + value: 'production' + livenessProbe: + httpGet: + path: /health + port: 10999 + resources: + {{- toYaml .Values.resources | nindent 12 }} + - name: distributor + image: "{{ .Values.distributor.image.repository }}:{{ .Values.distributor.image.tag | default .Chart.AppVersion }}" + livenessProbe: + httpGet: + path: /health + port: 10999 + initialDelaySeconds: 5 + periodSeconds: 5 + imagePullPolicy: Always + ports: + - containerPort: 8080 + resources: + requests: + memory: "32Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "500m" + env: + - name: PUBSUB_TOPIC + value: 'sh.keptn.event.test.triggered' + - name: PUBSUB_RECIPIENT + value: '127.0.0.1' + - name: STAGE_FILTER + value: "{{ .Values.distributor.stageFilter }}" + - name: PROJECT_FILTER + value: "{{ .Values.distributor.projectFilter }}" + - name: SERVICE_FILTER + value: "{{ .Values.distributor.serviceFilter }}" + {{- if .Values.remoteControlPlane.enabled }} + - name: KEPTN_API_ENDPOINT + value: "{{ .Values.remoteControlPlane.api.protocol }}://{{ .Values.remoteControlPlane.api.hostname }}/api" + - name: KEPTN_API_TOKEN + value: "{{ .Values.remoteControlPlane.api.token }}" + - name: HTTP_SSL_VERIFY + value: "{{ .Values.remoteControlPlane.api.apiValidateTls | default "true" }}" + {{- end }} + + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/templates/service.yaml b/helm/templates/service.yaml new file mode 100644 index 0000000..8bb05ca --- /dev/null +++ b/helm/templates/service.yaml @@ -0,0 +1,15 @@ +{{- if .Values.keptnservice.service.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "keptn-service.fullname" . }} + labels: + {{- include "keptn-service.labels" . | nindent 4 }} +spec: + type: ClusterIP + ports: + - port: 8080 + protocol: TCP + selector: + {{- include "keptn-service.selectorLabels" . | nindent 4 }} + {{- end }} \ No newline at end of file diff --git a/helm/templates/serviceaccount.yaml b/helm/templates/serviceaccount.yaml new file mode 100644 index 0000000..cff7450 --- /dev/null +++ b/helm/templates/serviceaccount.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "keptn-service.serviceAccountName" . }} + labels: + {{- include "keptn-service.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: keptn-{{ .Release.Namespace }}-keptn-service-cluster-admin + labels: + {{- include "keptn-service.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +subjects: + - kind: ServiceAccount + name: {{ include "keptn-service.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: cluster-admin + apiGroup: rbac.authorization.k8s.io diff --git a/helm/templates/tests/test-api-connection.yaml b/helm/templates/tests/test-api-connection.yaml new file mode 100644 index 0000000..08294b8 --- /dev/null +++ b/helm/templates/tests/test-api-connection.yaml @@ -0,0 +1,19 @@ +{{- if .Values.remoteControlPlane.enabled -}} +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "keptn-service.fullname" . }}-test-api-connection" + labels: + {{- include "keptn-service.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + args: + - wget + - '--header=x-token: {{ .Values.remoteControlPlane.api.token }}' + - {{ .Values.remoteControlPlane.api.protocol }}://{{ .Values.remoteControlPlane.api.hostname }}/api/v1/metadata + restartPolicy: Never + {{- end -}} \ No newline at end of file diff --git a/helm/values.schema.json b/helm/values.schema.json new file mode 100644 index 0000000..e533dbb --- /dev/null +++ b/helm/values.schema.json @@ -0,0 +1,110 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "properties": { + "remoteControlPlane": { + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "type": "boolean" + } + }, + "if": { + "properties": { + "enabled": { + "const": true + } + } + }, + "then": { + "properties": { + "api": { + "type": "object", + "required": [ + "protocol", + "hostname", + "token" + ], + "properties": { + "hostname": { + "pattern": "^[a-z0-9][a-z0-9-.]{2,63}$" + }, + "protocol": { + "enum": [ + "http", + "https" + ] + }, + "apiValidateTls": { + "type": "boolean" + }, + "token": { + "pattern": "^[A-Za-z0-9-.]{2,63}$" + } + } + } + } + } + }, + "keptnservice": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "properties": { + "repository": { + "pattern": "^[a-z][a-z0-9-./]{2,63}$" + }, + "pullPolicy": { + "enum": [ + "IfNotPresent", + "Always" + ] + } + } + }, + "service": { + "properties": { + "enabled": { + "type": "boolean" + } + } + } + } + }, + "distributor": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "properties": { + "repository": { + "pattern": "[a-z][a-z0-9-./]{2,63}$" + }, + "pullPolicy": { + "enum": [ + "IfNotPresent", + "Always" + ] + } + } + }, + "stageFilter": { + "pattern": "^$|[A-Za-z0-9-.]{2,63}$" + }, + "serviceFilter": { + "pattern": "^$|[A-Za-z0-9-.]{2,63}$" + }, + "projectFilter": { + "pattern": "^$|[A-Za-z0-9-.]{2,63}$" + } + } + } + } +} \ No newline at end of file diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 0000000..0bd1f67 --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,59 @@ +keptnservice: + image: + repository: docker.io/keptnsandbox/keptn-service-template-go # Container Image Name + pullPolicy: Always # Kubernetes Image Pull Policy + tag: "dev" # Container Tag + service: + enabled: true # Creates a Kubernetes Service for the keptn-service-template-go + +distributor: + stageFilter: "" # Sets the stage this helm service belongs to + serviceFilter: "" # Sets the service this helm service belongs to + projectFilter: "" # Sets the project this helm service belongs to + image: + repository: docker.io/keptn/distributor # Container Image Name + pullPolicy: IfNotPresent # Kubernetes Image Pull Policy + tag: "" # Container Tag + +remoteControlPlane: + enabled: true # Enables remote execution plane mode + api: + protocol: "http" # Used Protocol (http, https) + hostname: "" # Hostname of the control plane cluster (and Port) + apiValidateTls: true # Defines if the control plane certificate should be validated + token: "" # Keptn API Token + +imagePullSecrets: [] # Secrets to use for container registry credentials + +serviceAccount: + create: true # Enables the service account creation + annotations: {} # Annotations to add to the service account + name: "" # The name of the service account to use. + +podAnnotations: {} # Annotations to add to the created pods + +podSecurityContext: {} # Set the pod security context (e.g. fsGroups) + # fsGroup: 2000 + +securityContext: {} # Set the security context (e.g. runAsUser) +# readOnlyRootFilesystem: true +# runAsNonRoot: true +# runAsUser: 1000 + +resources: # Resource limits and requests + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + +nodeSelector: {} # Node selector configuration + +tolerations: [] # Tolerations for the pods + +affinity: {} # Affinity rules diff --git a/main.go b/main.go new file mode 100644 index 0000000..85f8421 --- /dev/null +++ b/main.go @@ -0,0 +1,539 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "os" + + cloudevents "github.com/cloudevents/sdk-go/v2" // make sure to use v2 cloudevents here + "github.com/kelseyhightower/envconfig" + keptnlib "github.com/keptn/go-utils/pkg/lib" + keptn "github.com/keptn/go-utils/pkg/lib/keptn" + keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0" +) + +var keptnOptions = keptn.KeptnOpts{} + +type envConfig struct { + // Port on which to listen for cloudevents + Port int `envconfig:"RCV_PORT" default:"8080"` + // Path to which cloudevents are sent + Path string `envconfig:"RCV_PATH" default:"/"` + // Whether we are running locally (e.g., for testing) or on production + Env string `envconfig:"ENV" default:"local"` + // URL of the Keptn configuration service (this is where we can fetch files from the config repo) + ConfigurationServiceUrl string `envconfig:"CONFIGURATION_SERVICE" default:""` +} + +// ServiceName specifies the current services name (e.g., used as source when sending CloudEvents) +const ServiceName = "keptn-service-template-go" + +/** + * Parses a Keptn Cloud Event payload (data attribute) + */ +func parseKeptnCloudEventPayload(event cloudevents.Event, data interface{}) error { + err := event.DataAs(data) + if err != nil { + log.Fatalf("Got Data Error: %s", err.Error()) + return err + } + return nil +} + +/** + * This method gets called when a new event is received from the Keptn Event Distributor + * Depending on the Event Type will call the specific event handler functions, e.g: handleDeploymentFinishedEvent + * See https://github.com/keptn/spec/blob/0.2.0-alpha/cloudevents.md for details on the payload + */ +func processKeptnCloudEvent(ctx context.Context, event cloudevents.Event) error { + // create keptn handler + log.Printf("Initializing Keptn Handler") + myKeptn, err := keptnv2.NewKeptn(&event, keptnOptions) + if err != nil { + return errors.New("Could not create Keptn Handler: " + err.Error()) + } + + log.Printf("gotEvent(%s): %s - %s", event.Type(), myKeptn.KeptnContext, event.Context.GetID()) + + if err != nil { + log.Printf("failed to parse incoming cloudevent: %v", err) + return err + } + + /** + * CloudEvents types in Keptn 0.8.0 follow the following pattern: + * - sh.keptn.event.${EVENTNAME}.triggered + * - sh.keptn.event.${EVENTNAME}.started + * - sh.keptn.event.${EVENTNAME}.status.changed + * - sh.keptn.event.${EVENTNAME}.finished + * + * For convenience, types can be generated using the following methods: + * - triggered: keptnv2.GetTriggeredEventType(${EVENTNAME}) (e.g,. keptnv2.GetTriggeredEventType(keptnv2.DeploymentTaskName)) + * - started: keptnv2.GetStartedEventType(${EVENTNAME}) (e.g,. keptnv2.GetStartedEventType(keptnv2.DeploymentTaskName)) + * - status.changed: keptnv2.GetStatusChangedEventType(${EVENTNAME}) (e.g,. keptnv2.GetStatusChangedEventType(keptnv2.DeploymentTaskName)) + * - finished: keptnv2.GetFinishedEventType(${EVENTNAME}) (e.g,. keptnv2.GetFinishedEventType(keptnv2.DeploymentTaskName)) + * + * Keptn reserves some Cloud Event types, please read up on that here: https://keptn.sh/docs/0.8.x/manage/shipyard/ + * + * For those Cloud Events the keptn/go-utils library conveniently provides several data structures + * and strings in github.com/keptn/go-utils/pkg/lib/v0_2_0, e.g.: + * - deployment: DeploymentTaskName, DeploymentTriggeredEventData, DeploymentStartedEventData, DeploymentFinishedEventData + * - test: TestTaskName, TestTriggeredEventData, TestStartedEventData, TestFinishedEventData + * - ... (they all follow the same pattern) + * + * + * In most cases you will be interested in processing .triggered events (e.g., sh.keptn.event.deployment.triggered), + * which you an achieve as follows: + * if event.type() == keptnv2.GetTriggeredEventType(keptnv2.DeploymentTaskName) { ... } + * + * Processing the event payload can be achieved as follows: + * + * eventData := &keptnv2.DeploymentTriggeredEventData{} + * parseKeptnCloudEventPayload(event, eventData) + * + * See https://github.com/keptn/spec/blob/0.2.0-alpha/cloudevents.md for more details of Keptn Cloud Events and their payload + * Also, see https://github.com/keptn-sandbox/echo-service/blob/a90207bc119c0aca18368985c7bb80dea47309e9/pkg/events.go as an example how to create your own CloudEvents + **/ + + /** + * The following code presents a very generic implementation of processing almost all possible + * Cloud Events that are retrieved by this service. + * Please follow the documentation provided above for more guidance on the different types. + * Feel free to delete parts that you don't need. + **/ + switch event.Type() { + + // ------------------------------------------------------- + // sh.keptn.event.project.create - Note: This is due to change + case keptnv2.GetStartedEventType(keptnv2.ProjectCreateTaskName): // sh.keptn.event.project.create.started + log.Printf("Processing Project.Create.Started Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.ProjectCreateStartedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetFinishedEventType(keptnv2.ProjectCreateTaskName): // sh.keptn.event.project.create.finished + log.Printf("Processing Project.Create.Finished Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.ProjectCreateFinishedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + // ------------------------------------------------------- + // sh.keptn.event.service.create - Note: This is due to change + case keptnv2.GetStartedEventType(keptnv2.ServiceCreateTaskName): // sh.keptn.event.service.create.started + log.Printf("Processing Service.Create.Started Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.ServiceCreateStartedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetFinishedEventType(keptnv2.ServiceCreateTaskName): // sh.keptn.event.service.create.finished + log.Printf("Processing Service.Create.Finished Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.ServiceCreateFinishedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + + // ------------------------------------------------------- + // sh.keptn.event.approval + case keptnv2.GetTriggeredEventType(keptnv2.ApprovalTaskName): // sh.keptn.event.approval.triggered + log.Printf("Processing Approval.Triggered Event") + + eventData := &keptnv2.ApprovalTriggeredEventData{} + parseKeptnCloudEventPayload(event, eventData) + + return HandleApprovalTriggeredEvent(myKeptn, event, eventData) + case keptnv2.GetStartedEventType(keptnv2.ApprovalTaskName): // sh.keptn.event.approval.started + log.Printf("Processing Approval.Started Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.ApprovalStartedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetFinishedEventType(keptnv2.ApprovalTaskName): // sh.keptn.event.approval.finished + log.Printf("Processing Approval.Finished Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.ApprovalFinishedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + + // ------------------------------------------------------- + // sh.keptn.event.deployment + case keptnv2.GetTriggeredEventType(keptnv2.DeploymentTaskName): // sh.keptn.event.deployment.triggered + log.Printf("Processing Deployment.Triggered Event") + + eventData := &keptnv2.DeploymentTriggeredEventData{} + parseKeptnCloudEventPayload(event, eventData) + + return HandleDeploymentTriggeredEvent(myKeptn, event, eventData) + case keptnv2.GetStartedEventType(keptnv2.DeploymentTaskName): // sh.keptn.event.deployment.started + log.Printf("Processing Deployment.Started Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.DeploymentStartedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetFinishedEventType(keptnv2.DeploymentTaskName): // sh.keptn.event.deployment.finished + log.Printf("Processing Deployment.Finished Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.DeploymentFinishedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + + // ------------------------------------------------------- + // sh.keptn.event.test + case keptnv2.GetTriggeredEventType(keptnv2.TestTaskName): // sh.keptn.event.test.triggered + log.Printf("Processing Test.Triggered Event") + + eventData := &keptnv2.TestTriggeredEventData{} + parseKeptnCloudEventPayload(event, eventData) + + return HandleTestTriggeredEvent(myKeptn, event, eventData) + case keptnv2.GetStartedEventType(keptnv2.TestTaskName): // sh.keptn.event.test.started + log.Printf("Processing Test.Started Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.TestStartedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetFinishedEventType(keptnv2.TestTaskName): // sh.keptn.event.test.finished + log.Printf("Processing Test.Finished Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.TestFinishedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + + // ------------------------------------------------------- + // sh.keptn.event.evaluation + case keptnv2.GetTriggeredEventType(keptnv2.EvaluationTaskName): // sh.keptn.event.evaluation.triggered + log.Printf("Processing Evaluation.Triggered Event") + + eventData := &keptnv2.EvaluationTriggeredEventData{} + parseKeptnCloudEventPayload(event, eventData) + + return HandleEvaluationTriggeredEvent(myKeptn, event, eventData) + case keptnv2.GetStartedEventType(keptnv2.EvaluationTaskName): // sh.keptn.event.evaluation.started + log.Printf("Processing Evaluation.Started Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.EvaluationStartedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetFinishedEventType(keptnv2.EvaluationTaskName): // sh.keptn.event.evaluation.finished + log.Printf("Processing Evaluation.Finished Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.EvaluationFinishedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + + // ------------------------------------------------------- + // sh.keptn.event.release + case keptnv2.GetTriggeredEventType(keptnv2.ReleaseTaskName): // sh.keptn.event.release.triggered + log.Printf("Processing Release.Triggered Event") + + eventData := &keptnv2.ReleaseTriggeredEventData{} + parseKeptnCloudEventPayload(event, eventData) + + return HandleReleaseTriggeredEvent(myKeptn, event, eventData) + case keptnv2.GetStartedEventType(keptnv2.ReleaseTaskName): // sh.keptn.event.release.started + log.Printf("Processing Release.Started Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.ReleaseStartedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetStatusChangedEventType(keptnv2.ReleaseTaskName): // sh.keptn.event.release.status.changed + log.Printf("Processing Release.Status.Changed Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.ReleaseStatusChangedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetFinishedEventType(keptnv2.ReleaseTaskName): // sh.keptn.event.release.finished + log.Printf("Processing Release.Finished Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.ReleaseFinishedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + + // ------------------------------------------------------- + // sh.keptn.event.get-action (sent by shipyard-controller to extract a remediation action from remediations.yaml) + case keptnv2.GetTriggeredEventType(keptnv2.GetActionTaskName): // sh.keptn.event.action.triggered + log.Printf("Processing Get-Action.Triggered Event") + + eventData := &keptnv2.GetActionTriggeredEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetStartedEventType(keptnv2.GetActionTaskName): // sh.keptn.event.action.started + log.Printf("Processing Get-Action.Started Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.GetActionStartedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetFinishedEventType(keptnv2.GetActionTaskName): // sh.keptn.event.action.finished + log.Printf("Processing Get-Action.Finished Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.GetActionFinishedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + + // ------------------------------------------------------- + // sh.keptn.event.action (sent by shipyard-controller to execute a remediation action) + case keptnv2.GetTriggeredEventType(keptnv2.ActionTaskName): // sh.keptn.event.action.triggered + log.Printf("Processing Action.Triggered Event") + + eventData := &keptnv2.ActionTriggeredEventData{} + parseKeptnCloudEventPayload(event, eventData) + + return HandleActionTriggeredEvent(myKeptn, event, eventData) + case keptnv2.GetStartedEventType(keptnv2.ActionTaskName): // sh.keptn.event.action.started + log.Printf("Processing Action.Started Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.ActionStartedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetFinishedEventType(keptnv2.ActionTaskName): // sh.keptn.event.action.finished + log.Printf("Processing Action.Finished Event") + // Please note: Processing .started, .status.changed and .finished events is only recommended when you want to + // notify an external service (e.g., for logging purposes). + + eventData := &keptnv2.ActionFinishedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + + // ------------------------------------------------------- + // sh.keptn.event.problem / problem.open + // Please Note: This is deprecated; Use Action.Triggered instead + case keptnlib.ProblemEventType: // sh.keptn.event.problem - e.g., sent by Dynatrace to Keptn Api + log.Printf("Processing problem Event") + log.Printf("Subscribing to a problem.open or problem event is not recommended since Keptn 0.7. Please subscribe to event of type: sh.keptn.event.action.triggered") + + eventData := &keptnlib.ProblemEventData{} + parseKeptnCloudEventPayload(event, eventData) + + return HandleProblemEvent(myKeptn, event, eventData) + + // ------------------------------------------------------- + // sh.keptn.event.get-sli (sent by lighthouse-service to fetch SLIs from the sli provider) + case keptnv2.GetTriggeredEventType(keptnv2.GetSLITaskName): // sh.keptn.event.get-sli.triggered + log.Printf("Processing Get-SLI.Triggered Event") + + eventData := &keptnv2.GetSLITriggeredEventData{} + parseKeptnCloudEventPayload(event, eventData) + + return HandleGetSliTriggeredEvent(myKeptn, event, eventData) + case keptnv2.GetStartedEventType(keptnv2.GetSLITaskName): // sh.keptn.event.get-sli.started + log.Printf("Processing Get-SLI.Started Event") + + eventData := &keptnv2.GetSLIStartedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetFinishedEventType(keptnv2.GetSLITaskName): // sh.keptn.event.get-sli.finished + log.Printf("Processing Get-SLI.Finished Event") + + eventData := &keptnv2.GetSLIFinishedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + + // ------------------------------------------------------- + // sh.keptn.event.configure-monitoring + case keptnlib.ConfigureMonitoringEventType: // old configure-monitoring CE; compatibility with 0.8.0-alpha + log.Printf("Processing old configure-monitoring Event") + log.Printf("Note: This will be deprecated with Keptn 0.8.0") + + eventData := &keptnlib.ConfigureMonitoringEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Handle old configure-monitoring event + return OldHandleConfigureMonitoringEvent(myKeptn, event, eventData) + + case keptnv2.GetTriggeredEventType(keptnv2.ConfigureMonitoringTaskName): // sh.keptn.event.configure-monitoring.triggered + log.Printf("Processing configure-monitoring.Triggered Event") + + eventData := &keptnv2.ConfigureMonitoringTriggeredEventData{} + parseKeptnCloudEventPayload(event, eventData) + + return HandleConfigureMonitoringTriggeredEvent(myKeptn, event, eventData) + case keptnv2.GetStartedEventType(keptnv2.ConfigureMonitoringTaskName): // sh.keptn.event.configure-monitoring.started + log.Printf("Processing configure-monitoring.Started Event") + + eventData := &keptnv2.ConfigureMonitoringStartedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + case keptnv2.GetFinishedEventType(keptnv2.ConfigureMonitoringTaskName): // sh.keptn.event.configure-monitoring.finished + log.Printf("Processing configure-monitoring.Finished Event") + + eventData := &keptnv2.ConfigureMonitoringFinishedEventData{} + parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + + // ------------------------------------------------------- + // your custom cloud event, e.g., sh.keptn.your-event + // see https://github.com/keptn-sandbox/echo-service/blob/a90207bc119c0aca18368985c7bb80dea47309e9/pkg/events.go + // for an example on how to generate your own CloudEvents and structs + case keptnv2.GetTriggeredEventType("your-event"): // sh.keptn.event.your-event.triggered + log.Printf("Processing your-event.triggered Event") + + // eventData := &keptnv2.YourEventTriggeredEventData{} + // parseKeptnCloudEventPayload(event, eventData) + + break + case keptnv2.GetStartedEventType(keptnv2.ConfigureMonitoringTaskName): // sh.keptn.event.your-event.started + log.Printf("Processing your-event.started Event") + + // eventData := &keptnv2.YourEventStartedEventData{} + // parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + // return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + + break + case keptnv2.GetFinishedEventType(keptnv2.ConfigureMonitoringTaskName): // sh.keptn.event.your-event.finished + log.Printf("Processing your-event.finished Event") + + // eventData := &keptnv2.YourEventFinishedEventData{} + // parseKeptnCloudEventPayload(event, eventData) + + // Just log this event + // return GenericLogKeptnCloudEventHandler(myKeptn, event, eventData) + + break + } + + // Unknown Event -> Throw Error! + var errorMsg string + errorMsg = fmt.Sprintf("Unhandled Keptn Cloud Event: %s", event.Type()) + + log.Print(errorMsg) + return errors.New(errorMsg) +} + +/** + * Usage: ./main + * no args: starts listening for cloudnative events on localhost:port/path + * + * Environment Variables + * env=runlocal -> will fetch resources from local drive instead of configuration service + */ +func main() { + var env envConfig + if err := envconfig.Process("", &env); err != nil { + log.Fatalf("Failed to process env var: %s", err) + } + + os.Exit(_main(os.Args[1:], env)) +} + +/** + * Opens up a listener on localhost:port/path and passes incoming requets to gotEvent + */ +func _main(args []string, env envConfig) int { + // configure keptn options + if env.Env == "local" { + log.Println("env=local: Running with local filesystem to fetch resources") + keptnOptions.UseLocalFileSystem = true + } + + keptnOptions.ConfigurationServiceURL = env.ConfigurationServiceUrl + + log.Println("Starting keptn-service-template-go...") + log.Printf(" on Port = %d; Path=%s", env.Port, env.Path) + + ctx := context.Background() + ctx = cloudevents.WithEncodingStructured(ctx) + + log.Printf("Creating new http handler") + + // configure http server to receive cloudevents + p, err := cloudevents.NewHTTP(cloudevents.WithPath(env.Path), cloudevents.WithPort(env.Port)) + + if err != nil { + log.Fatalf("failed to create client, %v", err) + } + c, err := cloudevents.NewClient(p) + if err != nil { + log.Fatalf("failed to create client, %v", err) + } + + log.Printf("Starting receiver") + log.Fatal(c.StartReceiver(ctx, processKeptnCloudEvent)) + + return 0 +} diff --git a/releasenotes/releasenotes_develop.md b/releasenotes/releasenotes_develop.md new file mode 100644 index 0000000..c9faad2 --- /dev/null +++ b/releasenotes/releasenotes_develop.md @@ -0,0 +1,8 @@ +# Release Notes develop + +## New Features + +## Fixed Issues + +## Known Limitations + diff --git a/skaffold.yaml b/skaffold.yaml new file mode 100644 index 0000000..59e36ad --- /dev/null +++ b/skaffold.yaml @@ -0,0 +1,13 @@ +apiVersion: skaffold/v1beta13 +kind: Config +build: + artifacts: + - image: keptnsandbox/keptn-service-template-go # Todo: Replace this with your image name + docker: + dockerfile: Dockerfile + buildArgs: + debugBuild: true +deploy: + kubectl: + manifests: + - deploy/service.yaml diff --git a/test-events/action.triggered.json b/test-events/action.triggered.json new file mode 100644 index 0000000..08897aa --- /dev/null +++ b/test-events/action.triggered.json @@ -0,0 +1,30 @@ +{ + "type": "sh.keptn.event.action.triggered", + "specversion": "1.0", + "source": "test-events", + "id": "f2b878d3-03c0-4e8f-bc3f-454bc1b3d79b", + "time": "2019-06-07T07:02:15.64489Z", + "contenttype": "application/json", + "shkeptncontext": "08735340-6f9e-4b32-97ff-3b6c292bc50i", + "data": { + "project": "sockshop", + "stage": "dev", + "service": "carts", + "labels": { + "testId": "4711", + "buildId": "build-17", + "owner": "JohnDoe" + }, + "status": "succeeded", + "result": "pass", + + "action": { + "name": "my action name", + "action": "action-xyz", + "description": "so something as defined in remediation.yaml", + "value" : "1" + }, + "problem": { + } + } + } \ No newline at end of file diff --git a/test-events/deployment.triggered.json b/test-events/deployment.triggered.json new file mode 100644 index 0000000..3a4205b --- /dev/null +++ b/test-events/deployment.triggered.json @@ -0,0 +1,30 @@ +{ + "type": "sh.keptn.event.deployment.triggered", + "specversion": "1.0", + "source": "test-events", + "id": "f2b878d3-03c0-4e8f-bc3f-454bc1b3d79a", + "time": "2019-06-07T07:02:15.64489Z", + "contenttype": "application/json", + "shkeptncontext": "08735340-6f9e-4b32-97ff-3b6c292bc50g", + "data": { + "project": "sockshop", + "stage": "dev", + "service": "carts", + "labels": { + "testId": "4711", + "buildId": "build-17", + "owner": "JohnDoe" + }, + "status": "succeeded", + "result": "pass", + + "configurationChange": { + "values": { + "image": "docker.io/keptnexamples/carts:0.11.2" + } + }, + "deployment": { + "deploymentstrategy": "blue_green_service" + } + } + } \ No newline at end of file diff --git a/test-events/evaluation.triggered.json b/test-events/evaluation.triggered.json new file mode 100644 index 0000000..e009eb6 --- /dev/null +++ b/test-events/evaluation.triggered.json @@ -0,0 +1,28 @@ +{ + "data": { + "deployment": { + "deploymentNames": null + }, + "evaluation": { + "end": "2021-01-15T15:09:45.000Z", + "start": "2021-01-15T15:04:45.000Z" + }, + "labels": null, + "message": "", + "project": "sockshop", + "result": "", + "service": "carts", + "stage": "staging", + "status": "", + "test": { + "end": "", + "start": "" + } + }, + "id": "5afa758e-697c-4496-8deb-4d7cc1c93967", + "source": "test-events", + "specversion": "1.0", + "time": "2021-01-15T15:09:46.006Z", + "type": "sh.keptn.event.evaluation.triggered", + "shkeptncontext": "da7aec34-78c4-4182-a2c8-51eb88f5871d" + } \ No newline at end of file diff --git a/test-events/get-sli.triggered.json b/test-events/get-sli.triggered.json new file mode 100644 index 0000000..f32b2e7 --- /dev/null +++ b/test-events/get-sli.triggered.json @@ -0,0 +1,27 @@ +{ + "data": { + "get-sli": { + "customFilters": [], + "end": "2021-01-15T15:09:45.000Z", + "indicators": [ + "response_time_p95", + "some_other_metric" + ], + "sliProvider": "keptn-service-template-go", + "start": "2021-01-15T15:04:45.000Z" + }, + "labels": null, + "message": "", + "project": "sockshop", + "result": "", + "service": "carts", + "stage": "staging", + "status": "" + }, + "id": "409539ae-c0b9-436e-abc6-c257292e28ff", + "source": "test-events", + "specversion": "1.0", + "time": "2021-01-15T15:09:46.144Z", + "type": "sh.keptn.event.get-sli.triggered", + "shkeptncontext": "da7aec34-78c4-4182-a2c8-51eb88f5871d" + } \ No newline at end of file diff --git a/test-events/release.triggered.json b/test-events/release.triggered.json new file mode 100644 index 0000000..28234f4 --- /dev/null +++ b/test-events/release.triggered.json @@ -0,0 +1,25 @@ +{ + "type": "sh.keptn.event.release.triggered", + "specversion": "1.0", + "source": "test-events", + "id": "f2b878d3-03c0-4e8f-bc3f-454bc1b3d79b", + "time": "2019-06-07T07:02:15.64489Z", + "contenttype": "application/json", + "shkeptncontext": "08735340-6f9e-4b32-97ff-3b6c292bc50h", + "data": { + "project": "sockshop", + "stage": "dev", + "service": "carts", + "labels": { + "testId": "4711", + "buildId": "build-17", + "owner": "JohnDoe" + }, + "status": "succeeded", + "result": "pass", + + "deployment": { + "deploymentstrategy": "blue_green_service" + } + } + } \ No newline at end of file diff --git a/test-events/send-test-events.http b/test-events/send-test-events.http new file mode 100644 index 0000000..1d1a24b --- /dev/null +++ b/test-events/send-test-events.http @@ -0,0 +1,56 @@ +# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or +# paste cURL into the file and request will be converted to HTTP Request format. +# +# Following HTTP Request Live Templates are available: +# * 'gtrp' and 'gtr' create a GET request with or without query parameters; +# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body; +# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data); + +# send service.create.finished test-event +POST http://localhost:8080/ +Accept: application/json +Cache-Control: no-cache +Content-Type: application/cloudevents+json + +< ./service.create.finished.json +### + +# send action.triggered test-event +POST http://localhost:8080/ +Accept: application/json +Cache-Control: no-cache +Content-Type: application/cloudevents+json + +< ./action.triggered.json + +### + +# send deployment.triggered test-event +POST http://localhost:8080/ +Accept: application/json +Cache-Control: no-cache +Content-Type: application/cloudevents+json + +< ./deployment.triggered.json + +### + +# send evaluation.triggered test-event +POST http://localhost:8080/ +Accept: application/json +Cache-Control: no-cache +Content-Type: application/cloudevents+json + +< ./evaluation.triggered.json + +### + +# send get-sli.triggered test-event +POST http://localhost:8080/ +Accept: application/json +Cache-Control: no-cache +Content-Type: application/cloudevents+json + +< ./get-sli.triggered.json + +### \ No newline at end of file diff --git a/test-events/service.create.finished.json b/test-events/service.create.finished.json new file mode 100644 index 0000000..dc59670 --- /dev/null +++ b/test-events/service.create.finished.json @@ -0,0 +1,24 @@ +{ + "type": "sh.keptn.event.service.create.finished", + "specversion": "1.0", + "source": "test-events", + "id": "f2b878d3-03c0-4e8f-bc3f-454bc1b3d79d", + "time": "2019-06-07T07:02:15.64489Z", + "contenttype": "application/json", + "shkeptncontext": "08735340-6f9e-4b32-97ff-3b6c292bc50f", + "data": { + "project": "sockshop", + "service": "carts", + "labels": { + "testId": "4711", + "buildId": "build-17", + "owner": "JohnDoe" + }, + "status": "succeeded", + "result": "pass", + + "helm": { + "chart": "H4sIFAAAAAAA/ykAK2FIUjBjSE02THk5NWIzVjBkUzVpWlM5Nk9WVjZNV2xqYW5keVRRbz1IZWxtAOxYS2/jNhDOWb9i4Mu2B8uUH7sLAXsIHHc33dgxHCdAURQGI41jNpTIkpSxRur/XlAvy5adFGgeCOq5OJoXOdTMx08JqDK61V9QZdwVjfjJCwghhHzsdtNfQsjuL/HaH0+8zieP9Hrtttc5IV67QzonQF5iM7uSaEPVCfnPa+0W906ESnaDSjMR+7D0nBB1oJg06fMpfEMeQWCbA+ZCgUa1ZAFC2jROTCP087+XRQ7iei5x3rqqo/xbyeZ/SXmC+qUA4Kn5b3vt3fnvdXrH+X8NYRG9Qx9CEdyjcplo3aM0Mf6gkeSoW2l7+MT1PNdzFErOAtoXSWx88Bwn9fUdAKnEnxgYH7QI7vVCSAdAmywzLu1DhhwFXACEKLlYRWgzhUxhYN76JP6fks2/wUhyalC3Nu/l+dDgifnv2mHfnv9et0OO8/8a0mw2wamSACqlbi09557FoQ9nZTs4ERoaUkPtvFevfi0xsLocHbQPDw/g3mQ3ShUyYL1OYUFRg3crGwKgBOcsvruWITWYqQAi+uM6pkvKOL3l6ANJ9WYl0YdJNSAFFo6BESqLjagJFhf0FrkuklEpN6hT9HnuXVQEuS/fityJLVXufXKLKkaD2gJmdhaVmlNUdAuulBZ9KJbF2tDY4mLjkQTNmm0zpbBeNx5ZIBCRFHGKslSyRxwlVaYp5vs2koP7EytFNKZ3GDZvVz58t3GP+JZs8eEBftKSMwMNv1Eum95JP7szD/6GEOc04QYaKTyZRnGeRddZCURsKItRla+uudWhxUbyu65aYaraqizVjBPOx4KzYOXD+XwkzFihtkNQeElh78XysVhvYYwslem1aEQguA/T/riiLzc8Fsr48Jl8JqUV42U979l01r++ml4OZ+PJZTVTStx8aOQn+sWWln3M5QMN6zUoXLLSOkGOVKM7yZWZQ6qza235jGhkDwfSPpjlffDlkQbJPfPWrXtuerrwtCRhj5/JXkvutWn3uuuBUShObnx5NhudDge7Z/aLEpFfUQLMGfJwgvNtba4fU7PwoVEghmuT1xc7G4wvLn8bDkbTl1gzQ6ffP2wq/vBHfQ/9y9H09Hw0mMzOh6dfa3t4qv2LNN8H4+nIdtuvg/70QJKs0fImOZTiarpnF89w+lrSYM8ryBcdTG7O+/uKTxGhHnY9uhicXn1LAweT2fXkYk+snW6/1UpiOx0LN/9thrhsUck2STlbYoxaj5W4xWpBNsFXNNs1yrS41gIpN4ttSx0dAFjMDKP8DDldXWEg4lD78LHqIVExEZY2r2ozLEKRmI2xVxoV0pC9x21rkagAdXV7nEXM6N1eCmRi8xIS7RgijIRa+dAm3c9DVjEq/CtBfSBT73Aij7S7Q/Ye/g2yy/9zhH7WfwU8+f3/aZf/dz+R4/f/q0iN/5fU/yrrBGeLJdeIv1VmzLzPE21QnVt6ktEja6sxowIdsocaQTJU3aHZ4kUlxc9oeoWUv/XhHeUoRznKO5Z/AgAA///9mYwrABwAAA==" + } + } + } \ No newline at end of file