[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2.1\n\norbs:\n  helm: banzaicloud/helm@0.0.5\n\njobs:\n  build:\n    docker:\n      - image: circleci/golang:1.12\n        environment:\n          GOFLAGS: -mod=readonly\n\n    steps:\n      - checkout\n\n      - restore_cache:\n          name: Restore build dependencies\n          keys:\n            - build-deps-v1-{{ .Branch }}-{{ checksum \"Makefile\" }}\n\n      - restore_cache:\n          name: Restore Go module cache\n          keys:\n            - gomod-v1-{{ .Branch }}-{{ checksum \"go.sum\" }}\n            - gomod-v1-{{ .Branch }}\n            - gomod-v1-master\n            - gomod-v1\n\n      - run:\n          name: Download Go module cache\n          command: go mod download\n\n      - save_cache:\n          name: Save Go module cache\n          key: gomod-v1-{{ .Branch }}-{{ checksum \"go.sum\" }}\n          paths:\n            - /go/pkg/mod\n\n      -\n          restore_cache:\n              name: Restore license cache\n              keys:\n                  - licensei-v1-{{ .Branch }}-{{ checksum \"go.sum\" }}\n                  - licensei-v1-{{ .Branch }}\n                  - licensei-v1-master\n                  - licensei-v1\n\n      -\n          run:\n              name: Download license information for dependencies\n              command: make license-cache\n\n      -\n          save_cache:\n              name: Save license cache\n              key: licensei-v1-{{ .Branch }}-{{ checksum \"go.sum\" }}\n              paths:\n                  - .licensei.cache\n\n      -\n          run:\n              name: Check dependency licenses\n              command: make license-check\n\n      - run:\n          name: Run tests\n          command: TEST_PKGS=$(echo `go list ./... | circleci tests split`) TEST_REPORT_NAME=results_${CIRCLE_NODE_INDEX}.xml make test\n\n      - run:\n          name: Run integration tests\n          command: TEST_PKGS=$(echo `go list ./... | circleci tests split`) TEST_REPORT_NAME=results_${CIRCLE_NODE_INDEX}.xml make test-integration\n\n      - run:\n          name: Run linter\n          command: make lint\n\n      - save_cache:\n          name: Save build dependencies\n          key: build-deps-v1-{{ .Branch }}-{{ checksum \"Makefile\" }}\n          paths:\n            - bin/\n\n      - store_test_results:\n          path: build/test_results/\n\nworkflows:\n  version: 2\n  ci:\n    jobs:\n      - build\n\n  helm-chart:\n    jobs:\n      - helm/lint-chart:\n          charts-dir: charts\n          filters:\n            tags:\n              ignore: /.*/\n\n      - helm/publish-chart:\n          context: helm\n          charts-dir: charts\n          filters:\n            tags:\n              only: /chart\\/\\S+\\/\\d+.\\d+.\\d+/\n            branches:\n              ignore: /.*/\n"
  },
  {
    "path": ".gitignore",
    "content": "/bin/\n/build/\n/config.*\n!config.yaml.dist\n/.docker/\n/docker-compose.override.yml\n/.env\n/.env.test\n/vendor/\n/.licensei.cache\n\n# IDE integration\n/.idea/*\n!/.idea/copyright/\n!/.idea/*.iml\n!/.idea/externalDependencies.xml\n!/.idea/go.imports.xml\n!/.idea/modules.xml\n!/.idea/runConfigurations/\n!/.idea/scopes/\n"
  },
  {
    "path": ".idea/hollowtrees.iml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"WEB_MODULE\" version=\"4\">\n  <component name=\"Go\" enabled=\"true\" />\n  <component name=\"NewModuleRootManager\">\n    <content url=\"file://$MODULE_DIR$\" />\n    <orderEntry type=\"inheritedJdk\" />\n    <orderEntry type=\"sourceFolder\" forTests=\"false\" />\n  </component>\n</module>"
  },
  {
    "path": ".idea/modules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n      <module fileurl=\"file://$PROJECT_DIR$/.idea/hollowtrees.iml\" filepath=\"$PROJECT_DIR$/.idea/hollowtrees.iml\" />\n    </modules>\n  </component>\n</project>"
  },
  {
    "path": ".licensei.toml",
    "content": "approved = [\n    \"mit\",\n    \"apache-2.0\",\n    \"bsd-3-clause\",\n    \"bsd-2-clause\",\n    \"mpl-2.0\",\n]\n\nignored = [\n    \"github.com/ghodss/yaml\",\n    \"github.com/davecgh/go-spew\", # ISC license\n    \"github.com/gogo/protobuf\", # BSD\n    \"gomodules.xyz/jsonpatch/v2\",\n    \"gopkg.in/fsnotify.v1\",\n\n    # Vanity URLS :\\\n    \"google.golang.org/grpc\",\n    \"google.golang.org/genproto\",\n    \"sigs.k8s.io/yaml\",\n]"
  },
  {
    "path": "Dockerfile",
    "content": "ARG GO_VERSION=1.12\n\nFROM golang:${GO_VERSION}-alpine AS builder\n\nRUN apk add --update --no-cache ca-certificates=20190108-r0 make=4.2.1-r2 git=2.22.0-r0 curl=7.65.1-r0\n\nENV GOFLAGS=\"-mod=readonly\"\n\nRUN mkdir -p /build\nWORKDIR /build\n\nCOPY go.* /build/\nRUN go mod download\n\nCOPY . /build\nENV PATH /build/bin /bin:$PATH\nRUN BUILD_DIR='' BINARY_NAME=app make build-release\n\n\nFROM alpine:3.7\nCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\nCOPY --from=builder /app /app\nUSER nobody:nobody\nCMD [\"/app\"]\n"
  },
  {
    "path": "Dockerfile.local",
    "content": "FROM alpine:3.7 AS builder\n\nRUN apk add --update --no-cache ca-certificates\n\n\nFROM alpine:3.7\n\nCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n\nARG BUILD_DIR\nARG BINARY_NAME\n\nCOPY $BUILD_DIR/$BINARY_NAME /app\nUSER nobody:nobody\nCMD [\"/app\"]\n"
  },
  {
    "path": "LICENSE.md",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "# A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html\n\n# Project variables\nPACKAGE = $(shell echo $${PWD\\#\\#*src/})\nBINARY_NAME ?= $(shell basename $$PWD)\nDOCKER_IMAGE ?= banzaicloud/hollowtrees\n\n# Build variables\nBUILD_DIR ?= build\nBUILD_PACKAGE = ${PACKAGE}/cmd/daemon\nVERSION ?= $(shell git symbolic-ref -q --short HEAD || git describe --tags --exact-match)\nCOMMIT_HASH ?= $(shell git rev-parse --short HEAD 2>/dev/null)\nBUILD_DATE ?= $(shell date +%FT%T%z)\nLDFLAGS += -X main.version=${VERSION} -X main.commitHash=${COMMIT_HASH} -X main.buildDate=${BUILD_DATE}\nexport CGO_ENABLED ?= 0\nexport GOOS = $(shell go env GOOS)\nifeq (${VERBOSE}, 1)\nifeq ($(filter -v,${GOARGS}),)\n\tGOARGS += -v\nendif\nendif\n\n# Docker variables\nDOCKER_TAG ?= ${VERSION}\n\n# Dependency versions\nDEP_VERSION = 0.5.0\nGOLANGCI_VERSION = 1.17.1\nLICENSEI_VERSION = 0.1.0\nPROTOC_VERSION = 3.6.1\nGOLANG_VERSION = 1.11\n\n# Add the ability to override some variables\n# Use with care\n-include override.mk\n\n.PHONY: clean\nclean: ## Clean the working area and the project\n\trm -rf bin/ ${BUILD_DIR}/\n\nbin/dep: bin/dep-${DEP_VERSION}\n\t@ln -sf dep-${DEP_VERSION} bin/dep\nbin/dep-${DEP_VERSION}:\n\t@mkdir -p bin\n\tcurl https://raw.githubusercontent.com/golang/dep/master/install.sh | INSTALL_DIRECTORY=bin DEP_RELEASE_TAG=v${DEP_VERSION} sh\n\t@mv bin/dep $@\n\n.PHONY: build\nbuild: ## Build a binary\nifeq (${VERBOSE}, 1)\n\tgo env\nendif\nifneq (${IGNORE_GOLANG_VERSION_REQ}, 1)\n\t@printf \"${GOLANG_VERSION}\\n$$(go version | awk '{sub(/^go/, \"\", $$3);print $$3}')\" | sort -t '.' -k 1,1 -k 2,2 -k 3,3 -g | head -1 | grep -q -E \"^${GOLANG_VERSION}$$\" || (printf \"Required Go version is ${GOLANG_VERSION}\\nInstalled: `go version`\" && exit 1)\nendif\n\n\t@$(eval GENERATED_BINARY_NAME = ${BINARY_NAME})\n\t@$(if $(strip ${BINARY_NAME_SUFFIX}),$(eval GENERATED_BINARY_NAME = ${BINARY_NAME}-$(subst $(eval) ,-,$(strip ${BINARY_NAME_SUFFIX}))),)\n\tgo build ${GOARGS} -tags \"${GOTAGS}\" -ldflags \"${LDFLAGS}\" -o ${BUILD_DIR}/${GENERATED_BINARY_NAME} ${BUILD_PACKAGE}\n\n.PHONY: build-release\nbuild-release: LDFLAGS += -w\nbuild-release: build ## Build a binary without debug information\n\n.PHONY: build-debug\nbuild-debug: GOARGS += -gcflags \"all=-N -l\"\nbuild-debug: BINARY_NAME_SUFFIX += debug\nbuild-debug: build ## Build a binary with remote debugging capabilities\n\n.PHONY: docker\ndocker: export GOOS = linux\ndocker: BINARY_NAME_SUFFIX += docker\ndocker: build-release ## Build a Docker image\n\tdocker build --build-arg BUILD_DIR=${BUILD_DIR} --build-arg BINARY_NAME=${GENERATED_BINARY_NAME} -t ${DOCKER_IMAGE}:${DOCKER_TAG} -f Dockerfile.local .\nifeq (${DOCKER_LATEST}, 1)\n\tdocker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:latest\nendif\n\n.PHONY: check\ncheck: test-all lint ## Run tests and linters\n\nbin/go-junit-report:\n\t@mkdir -p bin\n\tGOBIN=${PWD}/bin/ go get -u github.com/jstemmer/go-junit-report\n\nTEST_PKGS ?= ./...\nTEST_REPORT_NAME ?= results.xml\n.PHONY: test\ntest: TEST_REPORT ?= main\ntest: SHELL = /bin/bash\ntest: bin/go-junit-report ## Run tests\n\t@mkdir -p ${BUILD_DIR}/test_results/${TEST_REPORT}\n\t@set -o pipefail\n\tgo test -v $(filter-out -v,${GOARGS}) ${TEST_PKGS} 2>&1 | tee >(bin/go-junit-report > ${BUILD_DIR}/test_results/${TEST_REPORT}/${TEST_REPORT_NAME})\n\twait\n\n.PHONY: test-all\ntest-all: ## Run all tests\n\t@${MAKE} GOARGS=\"${GOARGS} -run .\\*\" TEST_REPORT=all test\n\n.PHONY: test-integration\ntest-integration: ## Run integration tests\n\t@${MAKE} GOARGS=\"${GOARGS} -run ^TestIntegration\\$$\\$$\" TEST_REPORT=integration test\n\nbin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION}\n\t@ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint\nbin/golangci-lint-${GOLANGCI_VERSION}:\n\t@mkdir -p bin\n\tcurl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b ./bin/ v${GOLANGCI_VERSION}\n\t@mv bin/golangci-lint $@\n\n.PHONY: lint\nlint: bin/golangci-lint ## Run linter\n\tbin/golangci-lint run\n\nbin/licensei: bin/licensei-${LICENSEI_VERSION}\n\t@ln -sf licensei-${LICENSEI_VERSION} bin/licensei\nbin/licensei-${LICENSEI_VERSION}:\n\t@mkdir -p bin\n\tcurl -sfL https://raw.githubusercontent.com/goph/licensei/master/install.sh | bash -s v${LICENSEI_VERSION}\n\t@mv bin/licensei $@\n\n.PHONY: license-check\nlicense-check: bin/licensei ## Run license check\n\tbin/licensei check\n\t./scripts/check-header.sh\n\n.PHONY: license-cache\nlicense-cache: bin/licensei ## Generate license cache\n\tbin/licensei cache\n\nprotoc: ## Run protobuf generation\n\tprotoc -I pkg/grpcplugin/proto/ pkg/grpcplugin/proto/event.proto --go_out=plugins=grpc:pkg/grpcplugin/proto\n\n.PHONY: list\nlist: ## List all make targets\n\t@${MAKE} -pRrn : -f $(MAKEFILE_LIST) 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ \"^[#.]\") {print $$1}}' | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | sort\n\n.PHONY: help\n.DEFAULT_GOAL := help\nhelp:\n\t@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n\n# Variable outputting/exporting rules\nvar-%: ; @echo $($*)\nvarexport-%: ; @echo $*=$($*)\n"
  },
  {
    "path": "README.md",
    "content": "# Hollowtrees\n\n_Hollowtrees is a wave for the highest level, the pin-up centrefold for the Mentawai islands bringing a new machine-like level to the word perfection. Watch out for the vigilant guardian aptly named The Surgeons Table, whose sole purpose is to take parts of you as a trophy._\n\n_Hollowtrees, a ruleset based watchguard is keeping spot/preemptible instance based clusters safe and allows to use them in production.\nHandles spot price surges within one region or availability zone and reschedules applications before instances are taking down. Hollowtrees follows the \"batteries included but removable\" principle and has plugins for different runtimes and frameworks. At the lowest level it manages spot based clusters of virtual machines, however it contains plugins for Kubernetes, Prometheus and Pipeline as well._\n\nHollowtrees is a core building block of the Pipeline platform. Check out the developer beta:\n<p align=\"center\">\n  <a href=\"https://beta.banzaicloud.io\">\n  <img src=\"https://camo.githubusercontent.com/a487fb3128bcd1ef9fc1bf97ead8d6d6a442049a/68747470733a2f2f62616e7a6169636c6f75642e636f6d2f696d672f7472795f706970656c696e655f627574746f6e2e737667\">\n  </a>\n</p>\n\n\n**Warning:** _Hollowtrees is experimental, under development and does not have a stable release yet. If in doubt, don't go out._\n\n## Quick start\n\nBuilding the project is as simple as running a go build command. The result is a statically linked executable binary.\n\n```bash\nmake build\n./build/hollowtrees\n```\n\nConfiguration of the project is done through a YAML config file. An example for that can be found under `config.yaml.dist`\n\n## Quick architecture overview\n\n>For an introduction and overview of the architecture please read the following blog [post](https://banzaicloud.com/blog/hollowtrees)\n\n![Hollowtrees](docs/images/hollowtrees-overview.png)\n\n## Configuring Prometheus to send alerts to Hollowtrees\n\nHollowtrees is listening on an API similar to the [Prometheus Alert Manager](https://prometheus.io/docs/alerting/alertmanager/) and it can be configured in Prometheus as an Alert Manager. For example if Hollowtrees is running locally on port 9092 (configurable through `global.bindAddr`), Prometheus can be configured like this to send its alerts to Hollowtrees directly:\n\n```yaml\n# Alertmanager configuration\nalerting:\n  alertmanagers:\n  - static_configs:\n    - targets:\n       - localhost:9092\n```\n\n### Configuring action flows\n\nAfter a Prometheus alert is received by Hollowtrees, it first converts it to an event that complies to the [OpenEvents](https://openevents.io) specification, then it processes it based on the action flows configured in the `config.yaml` file, and sends events to its configured action plugins. An example configuration can be found in `config.yaml.dist` under `plugins` and `flows`.\n\nHollowtrees uses gRPC to send events to its action plugins, and calls the action plugins sequentially. This very simple rule engine will probably change once Hollowtrees will have a release and will support different calling mechanisms, and passing of configuration parameters to the plugins.\n\nAlerts coming from Prometheus are converted to events with a type of `prometheus.server.alert.<AlertName>`. Prometheus labels are converted to the `data` payload as JSON. Data payload elements can be used in the action flows to forward events to the plugins only when it matches a specific string.\n\n### Advanced control structures in action flows\n\n* `cooldown`: Cooldown time that passes after an action flow is successfully finished. During the cooldown the action flow is considered `in progress`. Format: golang time, e.g.: `5m30s`\n* `groupBy`: Categorizes subsequent events as the same, if all the corresponding values of these attributes match\n* `filters`: Filter events by event values\n\n### Action plugins\n\nAction plugins are microservices that can react to different Hollowtrees events. They are listening on a gRPC endpoint and processing events in an arbitrary way. An example action plugin is in `examples/grpc_plugin`.\n\nTo create an action plugin, the [grpcplugin](github.com/banzaicloud/hollowtrees/pkg/grpcplugin) package must be imported, the `EventHandler` interface must be implemented and the gRPC server must be started with\n\n```go\nas.Serve(port, newEventHandler())\n```\n\n### License\n\nCopyright (c) 2017-2019 [Banzai Cloud, Inc.](https://banzaicloud.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "charts/hollowtrees-with-ps/.helmignore",
    "content": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation (prefixed with !). Only one pattern per line.\n.DS_Store\n# Common VCS dirs\n.git/\n.gitignore\n.bzr/\n.bzrignore\n.hg/\n.hgignore\n.svn/\n# Common backup files\n*.swp\n*.bak\n*.tmp\n*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n.vscode/\n"
  },
  {
    "path": "charts/hollowtrees-with-ps/Chart.yaml",
    "content": "apiVersion: v1\nname: hollowtrees-with-ps\nhome: https://banzaicloud.com\nsources:\n  - https://banzaicloud.com\n  - https://github.com/banzaicloud\n  - https://github.com/banzaicloud/hollowtrees\n  - https://github.com/banzaicloud/hollowtrees/charts/hollowtrees-with-ps\nversion: 0.2.1\nappVersion: 0.1.1\ndescription: Hollowtrees with Pipeline Scaler plugin\nkeywords:\n- scaling\n- hollowtrees\nicon: https://banzaicloud.com/img/banzai-cloud-logo.png\n"
  },
  {
    "path": "charts/hollowtrees-with-ps/README.md",
    "content": "# Hollowtrees with Pipeline Scaler Plugin Helm Chart\n\n## TL;DR\n\n```bash\n$ helm repo add banzaicloud-stable https://kubernetes-charts.banzaicloud.com\n$ helm repo update\n$ helm install --name=htpsp banzaicloud-stable/hollowtrees-with-ps\n```\n\n## Prerequisites\n\n- Kubernetes 1.10+\n\n## Installing the Chart\n\nTo install the chart with the release name `htpsp`:\n\n```bash\nhelm install --name=htpsp banzaicloud-stable/hollowtrees-with-ps\n```\n\nThe command deploys the application on the Kubernetes cluster with the default configuration.\nThe configuration section lists the parameters that can be configured during installation.\n\n> Tip: List all releases using `helm list`\n\n## Uninstalling the Chart\n\nTo uninstall/delete the htpsp release:\n\n```bash\n$ helm del --purge htpsp\n```\n\nThe command removes all the Kubernetes components associated with the chart and deletes the release.\n\n## Configuration\n\nThe configurable parameters and default values are listed in [`values.yaml`](values.yaml).\n\nSpecify each parameter using the `--set key=value[,key=value]` argument to `helm install`.\n\nAlternatively, a YAML file that specifies the values for the parameters can be provided during the chart installation:\n\n```bash\n$ helm install --name htpsp -f my-values.yaml banzaicloud-stable/hollowtrees-with-ps\n```\n"
  },
  {
    "path": "charts/hollowtrees-with-ps/templates/_helpers.tpl",
    "content": "{{/* vim: set filetype=mustache: */}}\n{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"hollowtrees-with-ps.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"hollowtrees-with-ps.fullname\" -}}\n{{- if .Values.fullnameOverride -}}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" -}}\n{{- else -}}\n{{- $name := default .Chart.Name .Values.nameOverride -}}\n{{- if contains $name .Release.Name -}}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" -}}\n{{- else -}}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n{{- end -}}\n{{- end -}}\n\n{{/*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"hollowtrees-with-ps.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n\n{{/*\nCommon labels\n*/}}\n{{- define \"hollowtrees-with-ps.labels\" -}}\napp.kubernetes.io/name: {{ include \"hollowtrees-with-ps.name\" . }}\nhelm.sh/chart: {{ include \"hollowtrees-with-ps.chart\" . }}\napp.kubernetes.io/instance: {{ .Release.Name }}\n{{- if .Chart.AppVersion }}\napp.kubernetes.io/version: {{ .Chart.AppVersion | quote }}\n{{- end }}\napp.kubernetes.io/managed-by: {{ .Release.Service }}\n{{- end -}}\n"
  },
  {
    "path": "charts/hollowtrees-with-ps/templates/configmap-ht.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: {{ include \"hollowtrees-with-ps.fullname\" . }}\n  labels:\n{{ include \"hollowtrees-with-ps.labels\" . | indent 4 }}\ndata:\n  config.yaml: |-\n    log:\n      format: {{ .Values.log.format | quote }}\n      level: {{ .Values.log.level | quote }}\n\n    healthcheck:\n      listenAddress: \":{{ .Values.healthcheck.listenPort }}\"\n      endpoint: {{ .Values.healthcheck.endpoint | quote }}\n\n    promalert:\n      listenAddress: \":{{ .Values.promalert.listenPort }}\"\n      useJWTAuth: {{ .Values.promalert.useJWTAuth }}\n\n    plugins:\n    - name: {{ .Values.scaler.name | quote }}\n      address: \"{{ .Values.scaler.hostname }}:{{ .Values.scaler.port }}\"\n      type: {{ .Values.scaler.type | quote }}\n\n    flows:\n      scaler:\n        name: {{ .Values.scaler.name | quote }}\n        description: {{ .Values.scaler.description | quote }}\n        allowedEvents:\n        {{- range .Values.scaler.allowedEvents }}\n        - {{ . | quote }}\n        {{- end }}\n        plugins:\n        - {{ .Values.scaler.name | quote }}\n        cooldown: {{ .Values.scaler.cooldown | quote }}\n        groupBy:\n        {{- range .Values.scaler.groupBy }}\n        - {{ . | quote }}\n        {{- end }}\n"
  },
  {
    "path": "charts/hollowtrees-with-ps/templates/configmap-htpsp.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: {{ include \"hollowtrees-with-ps.fullname\" . }}-htpsp\n  labels:\n{{ include \"hollowtrees-with-ps.labels\" . | indent 4 }}\ndata:\n{{- with .Values.htpsp }}\n  config.yaml: |-\n    log:\n      format: {{ .log.format | quote }}\n      level: {{ .log.level | quote }}\n\n    healthcheck:\n      listenAddress: \":{{ .healthcheck.listenPort }}\"\n      endpoint: {{ .healthcheck.endpoint | quote }}\n\n    eventHandler:\n      listenAddress: \":{{ .listenPort }}\"\n\n    scaler:\n      pipeline:\n        url: {{ required \"Pipeline URL must be defined\" .scaler.pipeline.url | quote }}\n        skipTLSVerify: {{ .scaler.pipeline.skipTLSVerify }}\n      telescopes:\n        url: {{ required \"Telescopes URL must be defined\" .scaler.telescopes.url | quote }}\n        skipTLSVerify: {{ .scaler.telescopes.skipTLSVerify }}\n      retries: {{ .scaler.retries }}\n      waitBetweenRetries: {{ .scaler.waitBetweenRetries | quote }}\n      waitInQueue: {{ .scaler.waitInQueue | quote }}\n{{- end }}\n"
  },
  {
    "path": "charts/hollowtrees-with-ps/templates/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ include \"hollowtrees-with-ps.fullname\" . }}\n  labels:\n{{ include \"hollowtrees-with-ps.labels\" . | indent 4 }}\nspec:\n  replicas: {{ .Values.replicaCount }}\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"hollowtrees-with-ps.name\" . }}\n      app.kubernetes.io/instance: {{ .Release.Name }}\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: {{ include \"hollowtrees-with-ps.name\" . }}\n        app.kubernetes.io/instance: {{ .Release.Name }}\n      {{- with .Values.podAnnotations }}\n      annotations:\n      {{- toYaml . | nindent 8 }}\n      {{- end }}\n    spec:\n      containers:\n      - name: \"hollowtrees\"\n        image: \"{{ .Values.image.repository }}:{{ .Values.image.tag }}\"\n        imagePullPolicy: {{ .Values.image.imagePullPolicy | quote }}\n        {{- if .Values.promalert.useJWTAuth }}\n        env:\n        - name: HT_PROMALERT_JWTSIGNINGKEY\n          valueFrom:\n            secretKeyRef:\n              name: {{ include \"hollowtrees-with-ps.fullname\" . }}\n              key: tokenSigningKey\n        {{- end }}\n        ports:\n        - name: http\n          containerPort: {{ .Values.promalert.listenPort }}\n        - name: healthcheck\n          containerPort: {{ .Values.healthcheck.listenPort }}\n        livenessProbe:\n          httpGet:\n            path: {{ .Values.healthcheck.endpoint | quote }}\n            port: healthcheck\n          initialDelaySeconds: 10\n          timeoutSeconds: 3\n          periodSeconds: 5\n        readinessProbe:\n          httpGet:\n            path: {{ .Values.healthcheck.endpoint | quote }}\n            port: healthcheck\n          initialDelaySeconds: 10\n          timeoutSeconds: 3\n          periodSeconds: 5\n        resources:\n        {{- toYaml .Values.resources | nindent 10 }}\n        volumeMounts:\n        - name: config\n          mountPath: /config/\n      - name: \"pipeline-scaler-plugin\"\n        image: \"{{ .Values.htpsp.image.repository }}:{{ .Values.htpsp.image.tag }}\"\n        imagePullPolicy: {{ .Values.htpsp.image.imagePullPolicy | quote }}\n        env:\n        - name: HTPSP_CONFIG_DIR\n          value: \"/config\"\n        ports:\n        - name: http\n          containerPort: {{ .Values.htpsp.listenPort }}\n        - name: healthcheck\n          containerPort: {{ .Values.htpsp.healthcheck.listenPort }}\n        livenessProbe:\n          httpGet:\n            path: {{ .Values.htpsp.healthcheck.endpoint | quote }}\n            port: healthcheck\n          initialDelaySeconds: 10\n          timeoutSeconds: 3\n          periodSeconds: 5\n        readinessProbe:\n          httpGet:\n            path: {{ .Values.htpsp.healthcheck.endpoint | quote }}\n            port: healthcheck\n          initialDelaySeconds: 10\n          timeoutSeconds: 3\n          periodSeconds: 5\n        volumeMounts:\n        - name: htpsp-config\n          mountPath: /config/\n        resources:\n        {{- toYaml .Values.htpsp.resources | nindent 10 }}\n      volumes:\n      - name: config\n        configMap:\n          name: {{ include \"hollowtrees-with-ps.fullname\" . }}\n      - name: htpsp-config\n        configMap:\n          name: {{ include \"hollowtrees-with-ps.fullname\" . }}-htpsp\n      {{- with .Values.nodeSelector }}\n      nodeSelector:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n    {{- with .Values.affinity }}\n      affinity:\n        {{- toYaml . | nindent 8 }}\n    {{- end }}\n    {{- with .Values.tolerations }}\n      tolerations:\n        {{- toYaml . | nindent 8 }}\n    {{- end }}\n"
  },
  {
    "path": "charts/hollowtrees-with-ps/templates/ingress.yaml",
    "content": "{{- if .Values.ingress.enabled -}}\n{{- $fullName := include \"hollowtrees-with-ps.fullname\" . -}}\napiVersion: extensions/v1beta1\nkind: Ingress\nmetadata:\n  name: {{ $fullName }}\n  labels:\n{{ include \"hollowtrees-with-ps.labels\" . | indent 4 }}\n  {{- with .Values.ingress.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\nspec:\n{{- if .Values.ingress.tls }}\n  tls:\n  {{- range .Values.ingress.tls }}\n    - hosts:\n      {{- range .hosts }}\n        - {{ . | quote }}\n      {{- end }}\n      secretName: {{ .secretName }}\n  {{- end }}\n{{- end }}\n  rules:\n  {{- range .Values.ingress.hosts }}\n    {{- $url := splitList \"/\" . }}\n    - host: {{ first $url }}\n      http:\n        paths:\n          - path: /{{ rest $url | join \"/\" }}\n            backend:\n              serviceName: {{ $fullName }}\n              servicePort: http\n  {{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/hollowtrees-with-ps/templates/secret.yaml",
    "content": "{{- if .Values.promalert.useJWTAuth -}}\napiVersion: v1\nkind: Secret\nmetadata:\n  name: {{ template \"hollowtrees-with-ps.fullname\" . }}\n  labels:\n{{ include \"hollowtrees-with-ps.labels\" . | indent 4 }}\ntype: Opaque\ndata:\n  tokenSigningKey: {{ required \"tokenSigningKey must be defined if JWT auth is enabled\" .Values.promalert.jwtSigningKey | b64enc | quote }}\n{{- end }}\n"
  },
  {
    "path": "charts/hollowtrees-with-ps/templates/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"hollowtrees-with-ps.fullname\" . }}\n  labels:\n{{ include \"hollowtrees-with-ps.labels\" . | indent 4 }}\nspec:\n  type: {{ .Values.service.type }}\n  ports:\n    - port: {{ .Values.service.port }}\n      targetPort: http\n      protocol: TCP\n      name: http\n  selector:\n    app.kubernetes.io/name: {{ include \"hollowtrees-with-ps.name\" . }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n"
  },
  {
    "path": "charts/hollowtrees-with-ps/values.yaml",
    "content": "# Default values for hollowtrees-with-ps\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\nimage:\n  repository: banzaicloud/hollowtrees\n  tag: \"0.1.1\"\n  imagePullPolicy: IfNotPresent\n\nreplicaCount: 1\n\nservice:\n  type: ClusterIP\n  port: 8080\n\nlog:\n  format: logfmt\n  level: debug\n\nhealthcheck:\n  listenPort: 8082\n  endpoint: /healthz\n\npromalert:\n  listenPort: 8080\n  useJWTAuth: false\n\nscaler:\n  name: pipeline-scaler\n  description: Pipeline cluster scaling\n  hostname: localhost\n  port: 9993\n  type: grpc\n  allowedEvents:\n  - prometheus.server.alert.InstanceTermination\n  groupBy:\n  - org_id\n  - cluster_id\n  - cluster_name\n  - instance_id\n  cooldown: 5m\n\ningress:\n  enabled: false\n  annotations:\n    kubernetes.io/ingress.class: traefik\n    traefik.ingress.kubernetes.io/rewrite-target: /\n  hosts:\n    - \"/hollowtrees-alerts\"\n\n  tls: []\n  #  - secretName: chart-example-tls\n  #    hosts:\n  #      - chart-example.local\n\nresources:\n  requests:\n    memory: 256Mi\n    cpu: 120m\n\nhtpsp:\n  listenPort: 9993\n  image:\n    repository: banzaicloud/ht-pipeline-scaler-plugin\n    tag: \"0.1.0\"\n    imagePullPolicy: IfNotPresent\n  log:\n    format: logfmt\n    level: debug\n  healthcheck:\n    listenPort: 8083\n    endpoint: /healthz\n  scaler:\n    pipeline:\n      url: \n      skipTLSVerify: false\n    telescopes:\n      url: \n      skipTLSVerify: false\n    retries: 2\n    waitBetweenRetries: 10s\n    waitInQueue: 60s\n  resources:\n    requests:\n      memory: 256Mi\n      cpu: 120m\n\nnodeSelector: {}\ntolerations: []\naffinity: {}\npodAnnotations: {}\n"
  },
  {
    "path": "cmd/daemon/build.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\n// Provisioned by ldflags\n// nolint: gochecknoglobals\nvar (\n\tversion    string\n\tcommitHash string\n\tbuildDate  string\n)\n"
  },
  {
    "path": "cmd/daemon/configuration.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"github.com/goph/emperror\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/config\"\n)\n\nvar configuration config.Config\n\nfunc configure() {\n\tconfig.Configure(viper.GetViper(), pflag.CommandLine)\n\tpflag.Parse()\n\n\terr := viper.ReadInConfig()\n\tif _, ok := err.(viper.ConfigFileNotFoundError); err != nil && !ok {\n\t\tpanic(emperror.Wrap(err, \"failed to read configuration\"))\n\t}\n\n\terr = viper.Unmarshal(&configuration)\n\tif err != nil {\n\t\tpanic(emperror.Wrap(err, \"failed to unmarshal configuration\"))\n\t}\n\n\terr = configuration.Validate()\n\tif err != nil {\n\t\tpanic(emperror.Wrap(err, \"cloud not validate configuration\"))\n\t}\n}\n"
  },
  {
    "path": "cmd/daemon/main.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\n\tevbus \"github.com/asaskevich/EventBus\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n\tyaml \"gopkg.in/yaml.v2\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/flows\"\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/config\"\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/healthcheck\"\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/log\"\n\t\"github.com/banzaicloud/hollowtrees/internal/plugin\"\n\t\"github.com/banzaicloud/hollowtrees/internal/promalert\"\n)\n\n// nolint: gochecknoinits\nfunc init() {\n\tpflag.Bool(\"version\", false, \"Show version information\")\n\tpflag.Bool(\"dump-config\", false, \"Dump configuration to the console\")\n}\n\nfunc main() {\n\t// Loads and validates configuration\n\tconfigure()\n\n\t// Show version if asked for\n\tif viper.GetBool(\"version\") {\n\t\tfmt.Printf(\"%s version %s (%s) built on %s\\n\", config.FriendlyServiceName, version, commitHash, buildDate)\n\t\tos.Exit(0)\n\t}\n\n\t// Dump config if asked for\n\tif viper.GetBool(\"dump-config\") {\n\t\tc := viper.AllSettings()\n\t\ty, err := yaml.Marshal(c)\n\t\tif err != nil {\n\t\t\tpanic(errors.Wrap(err, \"failed to dump configuration\"))\n\t\t}\n\t\tfmt.Print(string(y))\n\t\tos.Exit(0)\n\t}\n\n\t// Create logger\n\tlogger := log.NewLogger(configuration.Log)\n\n\t// Create error handler\n\terrorHandler := config.ErrorHandler(logger)\n\n\t// Create event bus\n\teventBus := evbus.New()\n\n\t// Create plugin manager\n\tpluginManager := plugin.NewManager(logger, errorHandler)\n\terr := pluginManager.LoadFromConfig(viper.GetViper())\n\tif err != nil {\n\t\terrorHandler.Handle(err)\n\t\tos.Exit(2)\n\t}\n\t// Add internal demo plugin\n\tpluginManager.Add(plugin.NewInternalPlugin(\"internal-demo\", logger))\n\n\t// Create flow manager\n\tflowManager := flows.NewManager(logger, errorHandler, flows.NewEventDispatcher(eventBus), pluginManager)\n\terr = flowManager.LoadFlows(viper.GetViper())\n\tif err != nil {\n\t\terrorHandler.Handle(err)\n\t\tos.Exit(2)\n\t}\n\n\tvar wg sync.WaitGroup\n\n\t// Starts health check HTTP server\n\twg.Add(1)\n\tgo func() {\n\t\thealthcheck.New(configuration.Healthcheck, logger, errorHandler)\n\t}()\n\n\t// Starts prometheus alert manager\n\twg.Add(1)\n\tgo func() {\n\t\tpromalert.New(configuration.Promalert, logger, errorHandler, promalert.NewEventDispatcher(eventBus)).Run()\n\t}()\n\n\tlogger.Infof(\"%s started\", config.FriendlyServiceName)\n\n\twg.Wait()\n}\n"
  },
  {
    "path": "config.yaml.dist",
    "content": "# logger settings\nlog:\n  format: \"logfmt\"\n  level: \"debug\"\n\n# action plugins\nplugins:\n  - name: \"dummy-plugin-1\"\n    address: \"localhost:9091\"\n    type: \"grpc\"\n\n  - name: \"dummy-plugin-2\"\n    address: \"localhost:9091\"\n    type: \"grpc\"\n\n# action flows\nflows:\n  simple:\n    name: \"Simple Flow\"\n    description: \"simple dummy flow\"\n    allowedEvents:\n    - \"prometheus.server.alert.TestAlert\"\n    - \"prometheus.server.alert.DummyTestAlert2\"\n    plugins:\n    - \"internal-demo\"\n    - \"dummy-plugin-1\"\n    cooldown: 1m\n    groupBy:\n    - cluster_name\n    - instance_id\n    filters:\n    - cluster_name: \"test-cluster\"\n"
  },
  {
    "path": "docs/DEVELOPMENT.md",
    "content": "# Development guide\n\n## Building and running Hollowtrees on your local machine\n\nThe project can be built with `make`, configuration is done via `config.yaml`.\nCopy the `config.yaml.dist` file, or create a new one with custom action plugins and rules.\n\n```bash\nmake build\n./build/hollowtrees\n```\n\n## Setting up action plugins\n\nCurrently only the `grpc` plugin type is supported.\n\n```bash\nplugins:\n  - name: \"grpc-dummy\"\n    address: \"localhost:9091\"\n    type: \"grpc\"\n```\n\n## Triggering the Hollowtrees server with an alert\n\nThe Hollowtrees server is triggered by Prometheus alerts in a Kubernetes deployment, but to test things out the API can be triggered with a simple `cURL` command that simulates an alert sent by Prometheus.\nSave the following JSON in a file named `alert.json`, and run the `cURL` command below.\nThe `alertname` label is required, it will be converted to an event type that's used to select action flows for a specific event.\nThe other labels are optional and arbitrary, and all of the labels will be sent to the action plugins as `data`.\n\n```json\n[\n  {\n    \"annotations\": {},\n    \"startsAt\": \"2006-01-02T15:04:05Z\",\n    \"endsAt\":\"2007-01-02T15:04:05Z\",\n    \"generatorURL\":\"http://test\",\n    \"labels\": {\n      \"alertname\": \"TestAlert\",\n      \"cluster_name\":\"test-cluster\",\n      \"Name\":\"test\"\n    }\n  }\n]\n```\n\n```bash\ncurl -X POST -d @alert.json localhost:9092/api/v1/alerts\n```\n\n## Setting up an action flow for Hollowtrees\n\nHere's an example config snippet that describes an action flow that can be triggered with the above JSON:\n\n```bash\nflows:\n  test:\n    name: \"Test Flow\"\n    description: \"test flow that triggers a grpc plugin if the event type is `prometheus.server.alert.TestAlert` and the cluster_name label matches `test-cluster`\"\n    plugins:\n    - \"dummy-plugin-1\"\n    allowedEvents:\n    - \"prometheus.server.alert.TestAlert\"\n    filters:\n    - cluster_name: \"test-cluster\"\n```\n\n## Running a (dummy) gRPC action plugin\n\nThere is an example gRPC action plugin in `examples/grpc_plugin`. Enter that directory, build the plugin with `go build .` and run the binary.\nThe dummy plugin accepts every event type and logs the requests it gets through gRPC.\n"
  },
  {
    "path": "examples/grpc_plugin/main.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\n\tgp \"github.com/banzaicloud/hollowtrees/pkg/grpcplugin\"\n)\n\n// dummyEventHandler dummy implementation of EventHandler\ntype dummyEventHandler struct{}\n\n// Handle dummy implementation\nfunc (d *dummyEventHandler) Handle(event *gp.CloudEvent) (*gp.Result, error) {\n\tfmt.Printf(\"got GRPC request, handling alert: %s\\n\", event.Data)\n\n\treturn &gp.Result{Status: \"ok\"}, nil\n}\n\nvar listenAddr string\n\nfunc init() {\n\tflag.StringVar(&listenAddr, \"listen-addr\", \":9091\", \"address to listen on\")\n}\n\nfunc main() {\n\tflag.Parse()\n\n\tfmt.Printf(\"Hollowtrees Dummy GRPC EventHandler Plugin listening on %s\\n\", listenAddr)\n\terr := gp.Serve(listenAddr, &dummyEventHandler{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/banzaicloud/hollowtrees\n\ngo 1.12\n\nrequire (\n\tgithub.com/asaskevich/EventBus v0.0.0-20180315140547-d46933a94f05\n\tgithub.com/banzaicloud/bank-vaults/pkg/sdk v0.1.3-0.20190826065836-26d654c87254\n\tgithub.com/cloudevents/sdk-go v0.0.0-20181211100118-3a3d34a7231e\n\tgithub.com/dgrijalva/jwt-go v3.2.0+incompatible\n\tgithub.com/gin-gonic/gin v1.4.0\n\tgithub.com/go-playground/locales v0.12.1 // indirect\n\tgithub.com/go-playground/universal-translator v0.16.0 // indirect\n\tgithub.com/gofrs/uuid v3.2.0+incompatible\n\tgithub.com/golang/protobuf v1.3.1\n\tgithub.com/goph/emperror v0.14.0\n\tgithub.com/goph/logur v0.5.0\n\tgithub.com/leodido/go-urn v1.1.0 // indirect\n\tgithub.com/patrickmn/go-cache v2.1.0+incompatible\n\tgithub.com/pkg/errors v0.8.1\n\tgithub.com/satori/go.uuid v1.2.0\n\tgithub.com/sirupsen/logrus v1.4.2\n\tgithub.com/spf13/cast v1.3.0\n\tgithub.com/spf13/pflag v1.0.3\n\tgithub.com/spf13/viper v1.4.0\n\tgithub.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect\n\tgolang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc\n\tgolang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f // indirect\n\tgolang.org/x/text v0.3.2 // indirect\n\tgoogle.golang.org/grpc v1.22.0\n\tgopkg.in/go-playground/validator.v8 v8.18.2\n\tgopkg.in/go-playground/validator.v9 v9.29.1\n\tgopkg.in/yaml.v2 v2.2.2\n)\n\nreplace (\n\tgithub.com/ugorji/go => github.com/ugorji/go/codec v1.1.7\n\tk8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93\n\tk8s.io/client-go => k8s.io/client-go v2.0.0-alpha.0.0.20181213151034-8d9ed539ba31+incompatible\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=\ngithub.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/InVisionApp/go-logger v1.0.1/go.mod h1:+cGTDSn+P8105aZkeOfIhdd7vFO5X1afUHcjvanY0L8=\ngithub.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=\ngithub.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=\ngithub.com/ThreeDotsLabs/watermill v0.1.2/go.mod h1:c0DOrvvuqbB8uhZlgY/fukFFfv1WZ6HinSktALd9b38=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=\ngithub.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=\ngithub.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=\ngithub.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/asaskevich/EventBus v0.0.0-20180315140547-d46933a94f05 h1:Shem5lRG4gJyrrg9YMIl7dOQazyWCq0Daz4LjompZ28=\ngithub.com/asaskevich/EventBus v0.0.0-20180315140547-d46933a94f05/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII=\ngithub.com/banzaicloud/bank-vaults/pkg/sdk v0.1.1 h1:FVru2fTDY1dcvMZAFVVvJE7N2JC4WXMD+vuDQjlIaKo=\ngithub.com/banzaicloud/bank-vaults/pkg/sdk v0.1.1/go.mod h1:BBgi3VY8BvvLBMWDtdiM+DTyRW2n6Sbvzvif+/AXHXI=\ngithub.com/banzaicloud/bank-vaults/pkg/sdk v0.1.2-0.20190824120735-50600eba199e h1:NH/cYHOQB8+ezTLy1xiRx7QwGwrW9WtZ9Zq7DS7WtmY=\ngithub.com/banzaicloud/bank-vaults/pkg/sdk v0.1.2-0.20190824120735-50600eba199e/go.mod h1:t8CI6t3iGDKQuTFLFjhY/HBw/p3B6dCsLAgikct0amc=\ngithub.com/banzaicloud/bank-vaults/pkg/sdk v0.1.3-0.20190826065836-26d654c87254 h1:EDSd7zAvlWFymtHnGZtnto+bPoLZmHAYs9LZoj8juW4=\ngithub.com/banzaicloud/bank-vaults/pkg/sdk v0.1.3-0.20190826065836-26d654c87254/go.mod h1:t8CI6t3iGDKQuTFLFjhY/HBw/p3B6dCsLAgikct0amc=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudevents/sdk-go v0.0.0-20181211100118-3a3d34a7231e h1:AYCa3CZ+okj+HmCL1hwJs9DfmZg7WyRn3KH9VihG2O4=\ngithub.com/cloudevents/sdk-go v0.0.0-20181211100118-3a3d34a7231e/go.mod h1:xV7GfuhjnJoK6+2MgCk3kfkoO4YRIuARdY3UpSwGz+U=\ngithub.com/confluentinc/confluent-kafka-go v0.11.6/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg=\ngithub.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=\ngithub.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=\ngithub.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=\ngithub.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=\ngithub.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=\ngithub.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=\ngithub.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=\ngithub.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=\ngithub.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=\ngithub.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=\ngithub.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=\ngithub.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=\ngithub.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=\ngithub.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=\ngithub.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=\ngithub.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=\ngithub.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=\ngithub.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=\ngithub.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=\ngithub.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=\ngithub.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=\ngithub.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=\ngithub.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=\ngithub.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=\ngithub.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=\ngithub.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=\ngithub.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=\ngithub.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=\ngithub.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=\ngithub.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=\ngithub.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=\ngithub.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=\ngithub.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=\ngithub.com/goph/emperror v0.14.0 h1:Pfrf2wGvdHTuya8Ajm6KI1zcZsVhkbZnc5IGsnIr378=\ngithub.com/goph/emperror v0.14.0/go.mod h1:vakOpsf2BTE0/C0snxzXm5/l8pv6qjBhIqm/qlgDYC8=\ngithub.com/goph/logur v0.5.0 h1:eT2w3qegvJRtvKjMb/BHQAjyioKO9GDeYgyJayQBAJ8=\ngithub.com/goph/logur v0.5.0/go.mod h1:12WYraXUaqPchdbrHQj9y9wneP4L9PDiesLJGFF3Ox4=\ngithub.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=\ngithub.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=\ngithub.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/gosimple/slug v1.7.0 h1:BlCZq+BMGn+riOZuRKnm60Fe7+jX9ck6TzzkN1r8TW8=\ngithub.com/gosimple/slug v1.7.0/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=\ngithub.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=\ngithub.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=\ngithub.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=\ngithub.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=\ngithub.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=\ngithub.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=\ngithub.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE=\ngithub.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=\ngithub.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8=\ngithub.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=\ngithub.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=\ngithub.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=\ngithub.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU=\ngithub.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=\ngithub.com/hashicorp/vault/sdk v0.1.13 h1:mOEPeOhT7jl0J4AMl1E705+BcmeRs1VmKNb9F0sMLy8=\ngithub.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=\ngithub.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=\ngithub.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/jinzhu/gorm v1.9.10 h1:HvrsqdhCW78xpJF67g1hMxS6eCToo9PZH4LDB8WKPac=\ngithub.com/jinzhu/gorm v1.9.10/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY=\ngithub.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=\ngithub.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=\ngithub.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=\ngithub.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=\ngithub.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=\ngithub.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=\ngithub.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=\ngithub.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=\ngithub.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=\ngithub.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=\ngithub.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=\ngithub.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=\ngithub.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=\ngithub.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=\ngithub.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=\ngithub.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=\ngithub.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=\ngithub.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=\ngithub.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=\ngithub.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=\ngithub.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=\ngithub.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=\ngithub.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=\ngithub.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=\ngithub.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=\ngithub.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=\ngithub.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=\ngithub.com/qor/qor v0.0.0-20190319081902-186b0237364b h1:0z+LJ7Efz/a+SYR2Wr/CfSyLuFgzSVVFyu9hqGEY74Y=\ngithub.com/qor/qor v0.0.0-20190319081902-186b0237364b/go.mod h1:oG+LgDEnsI9avcFFdczoZnBe3rw42s4cG433w6XpEig=\ngithub.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=\ngithub.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=\ngithub.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=\ngithub.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=\ngithub.com/rs/zerolog v1.11.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=\ngithub.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=\ngithub.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=\ngithub.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=\ngithub.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=\ngithub.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=\ngithub.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=\ngithub.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=\ngithub.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=\ngithub.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=\ngithub.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=\ngithub.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=\ngithub.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=\ngithub.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=\ngithub.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=\ngithub.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=\ngithub.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=\ngithub.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=\ngithub.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=\ngo.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=\ngo.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=\ngolang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=\ngolang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=\ngolang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngoogle.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo=\ngoogle.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=\ngoogle.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=\ngoogle.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=\ngopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=\ngopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=\ngopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=\ngopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=\ngopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=\ngopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=\ngopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=\ngopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\nhonnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nk8s.io/api v0.0.0-20181213150558-05914d821849/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=\nk8s.io/api v0.0.0-20190820101039-d651a1528133/go.mod h1:AlhL1I0Xqh5Tyz0HsxjEhy+iKci9l1Qy3UMDFW7iG3A=\nk8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93 h1:tT6oQBi0qwLbbZSfDkdIsb23EwaLY85hoAV4SpXfdao=\nk8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=\nk8s.io/apimachinery v0.0.0-20190820100750-21ddcbbef9e1/go.mod h1:EZoIMuAgG/4v58YL+bz0kqnivqupk28fKYxFCa5e6X8=\nk8s.io/apimachinery v0.0.0-20190823012420-8ca64af22337 h1:fmhgJs4JGkYIBji/BkYrFgdNIIRB/WIyA9Mw+DL+kMQ=\nk8s.io/apimachinery v0.0.0-20190823012420-8ca64af22337/go.mod h1:EZoIMuAgG/4v58YL+bz0kqnivqupk28fKYxFCa5e6X8=\nk8s.io/client-go v2.0.0-alpha.0.0.20181213151034-8d9ed539ba31+incompatible h1:R1v0j9UjjxL/TkPCMtyXtC7ECvD7bxoe2/8GD3MitMU=\nk8s.io/client-go v2.0.0-alpha.0.0.20181213151034-8d9ed539ba31+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=\nk8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=\nk8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=\nk8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=\nk8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=\nk8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE=\nk8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=\nk8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ=\nk8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=\nk8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4=\nk8s.io/utils v0.0.0-20190809000727-6c36bc71fc4a h1:uy5HAgt4Ha5rEMbhZA+aM1j2cq5LmR6LQ71EYC2sVH4=\nk8s.io/utils v0.0.0-20190809000727-6c36bc71fc4a/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=\nsigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=\nsigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=\nsigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=\n"
  },
  {
    "path": "internal/ce/ce.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage ce\n\nimport (\n\tce \"github.com/cloudevents/sdk-go/v02\"\n\t\"github.com/spf13/cast\"\n)\n\n// Event describes a wrapped CloudEvent\ntype Event struct {\n\tce.Event\n}\n\n// GetExtensions gets extention values by `eventType`\nfunc (e Event) GetExtensions() map[string]string {\n\tt, ok := e.GetString(\"eventType\")\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tswitch t {\n\tcase \"prometheus\":\n\t\treturn e.getExtensionsForPrometheusAlert()\n\t}\n\n\treturn nil\n}\n\nfunc (e Event) getExtensionsForPrometheusAlert() map[string]string {\n\tif l, ok := e.Get(\"labels\"); ok {\n\t\treturn cast.ToStringMapString(l)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/flows/config.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage flows\n\nimport (\n\t\"time\"\n\n\t\"github.com/goph/emperror\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/plugin\"\n)\n\n// FlowConfig holds configuration values for an action flow\ntype FlowConfig struct {\n\tName    string   `mapstructure:\"name\"`\n\tPlugins []string `mapstructure:\"plugins\"`\n\n\tDescription   string            `mapstructure:\"description\"`\n\tAllowedEvents []string          `mapstructure:\"allowedEvents\"`\n\tGroupBy       []string          `mapstructure:\"groupBy\"`\n\tFilters       map[string]string `mapstructure:\"filters\"`\n\tCooldown      time.Duration     `mapstructure:\"cooldown\"`\n}\n\ntype FlowConfigs map[string]FlowConfig\n\n// Validate validates flow configuration\nfunc (c FlowConfig) Validate(plugins plugin.PluginManager, id string) error {\n\tif c.Name == \"\" {\n\t\treturn errors.New(\"name must be set\")\n\t}\n\n\tif len(c.Plugins) == 0 {\n\t\treturn emperror.WrapWith(errors.New(\"no plugins defined\"), \"invalid flow config\", \"flow\", id)\n\t}\n\n\t_, err := plugins.GetByNames(c.Plugins...)\n\tif err != nil {\n\t\treturn emperror.WrapWith(err, \"invalid flow\", \"flow\", id)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/flows/event_dispatcher.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage flows\n\nconst (\n\tCEIncomingTopic = \"cloud.events.incoming\"\n)\n\ntype baseEventSubscriber interface {\n\tSubscribeAsync(topic string, fn interface{}, transactional bool) error\n}\n\ntype eventSubscriber interface {\n\tSubscribeAsync(topic string, flow ActionFlow) error\n}\n\ntype flowEventDispatcher interface {\n\teventSubscriber\n}\n\ntype eventDispatcher struct {\n\teb baseEventSubscriber\n}\n\n// NewEventDispatcher returns an initialized eventDispatcher\nfunc NewEventDispatcher(eb baseEventSubscriber) flowEventDispatcher {\n\treturn &eventDispatcher{\n\t\teb: eb,\n\t}\n}\n\n// SubscribeAsync implements interface func\nfunc (b *eventDispatcher) SubscribeAsync(topic string, flow ActionFlow) error {\n\treturn b.eb.SubscribeAsync(topic, flow.Handle, false)\n}\n"
  },
  {
    "path": "internal/flows/eventflow.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage flows\n\nimport (\n\t\"time\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/ce\"\n)\n\nconst (\n\tEventFlowCompleted   EventFlowStatus = \"completed\"\n\tEventFlowFailed      EventFlowStatus = \"failed\"\n\tEventFlowInProgress  EventFlowStatus = \"inprogress\"\n\tEventFlowInitialized EventFlowStatus = \"initialized\"\n\tEventFlowCoolingDown EventFlowStatus = \"coolingdown\"\n)\n\ntype EventFlowStatus string\n\n// EventFlow is an actual sequential executing of defined plugins\n// for a particular event and a defined action flow\ntype EventFlow struct {\n\tStatus EventFlowStatus\n\tError  error\n\n\tflow  *Flow\n\tevent *ce.Event\n}\n\n// NewEventFlow returns an initialized EventFlow\nfunc NewEventFlow(flow *Flow, event *ce.Event) *EventFlow {\n\treturn &EventFlow{\n\t\tStatus: EventFlowInitialized,\n\n\t\tflow:  flow,\n\t\tevent: event,\n\t}\n}\n\n// Exec executes the defined plugins sequentially\nfunc (ef *EventFlow) Exec() error {\n\tef.Status = EventFlowInProgress\n\n\tplugins, err := ef.flow.manager.Plugins().GetByNames(ef.flow.plugins...)\n\tif err != nil {\n\t\tef.Status = EventFlowFailed\n\t\tef.Error = err\n\t\treturn err\n\t}\n\n\tfor _, plugin := range plugins {\n\t\terr := plugin.Handle(ef.event)\n\t\tif err != nil {\n\t\t\tef.flow.manager.ErrorHandler().Handle(err)\n\t\t}\n\t}\n\n\tef.Status = EventFlowCoolingDown\n\n\ttime.Sleep(ef.flow.cooldown)\n\n\tef.Status = EventFlowCompleted\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/flows/flows.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage flows\n\nimport (\n\t\"path\"\n\t\"time\"\n\n\t\"github.com/goph/emperror\"\n\t\"github.com/goph/logur\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/ce\"\n)\n\n// ActionFlow defines an action flow\ntype ActionFlow interface {\n\tHandle(event interface{})\n}\n\n// Flow describes an action flow\ntype Flow struct {\n\tid            string\n\tname          string\n\tdescription   string\n\tallowedEvents []string\n\tcooldown      time.Duration\n\tgroupBy       []string\n\tplugins       []string\n\tfilters       map[string]string\n\n\tcache   FlowStore\n\tmanager FlowManager\n}\n\n// NewFlow returns an initialized action flow\nfunc NewFlow(manager FlowManager, cache FlowStore, id string, name string, opts ...Option) *Flow {\n\tf := &Flow{\n\t\tid:   id,\n\t\tname: name,\n\n\t\tmanager: manager,\n\t\tcache:   cache,\n\t}\n\n\tfor _, o := range opts {\n\t\to.apply(f)\n\t}\n\n\treturn f\n}\n\n// Handle handles the event by starting and event flow which executes the defined plugins\nfunc (f *Flow) Handle(event interface{}) {\n\te, ok := event.(*ce.Event)\n\tif !ok {\n\t\tf.manager.ErrorHandler().Handle(emperror.With(errors.Errorf(\"invalid event value: %#v\", event)))\n\t\treturn\n\t}\n\n\terr := f.handleEvent(e)\n\tif err != nil {\n\t\tf.manager.ErrorHandler().Handle(emperror.WrapWith(err, \"could not handle event\", \"type\", e.Type, \"id\", e.ID, \"flow\", f.name))\n\t}\n}\n\nfunc (f *Flow) handleEvent(event *ce.Event) error {\n\tkey := f.getEventKey(event, f.groupBy)\n\tcid, _ := event.GetString(\"correlationid\")\n\tlog := f.manager.Logger().WithFields(logur.Fields{\n\t\t\"correlation-id\": cid,\n\t\t\"event-id\":       event.ID,\n\t\t\"flow-id\":        f.id,\n\t\t\"type\":           event.Type,\n\t\t\"group-key\":      key,\n\t})\n\n\tif !f.isEventTypeAllowed(event.Type) {\n\t\tlog.Debug(\"skip flow - disallowed event type\")\n\t\treturn nil\n\t}\n\n\tif !f.isEventMatched(event) {\n\t\tlog.Debug(\"skip flow - filter does not match\")\n\t\treturn nil\n\t}\n\n\tef, created, err := f.createOrGetEventFlow(event, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif created {\n\t\tlog.Debugf(\"executing event flow - %s\", ef.Status)\n\t\terr = ef.Exec()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tf.cache.Delete(key)\n\t}\n\n\treturn nil\n}\n\nfunc (f *Flow) createOrGetEventFlow(event *ce.Event, key string) (*EventFlow, bool, error) { // f.mux.Lock()\n\tcreated := false\n\n\tef, err := f.cache.Get(key)\n\tif err != nil {\n\t\treturn nil, created, err\n\t}\n\n\tif ef != nil && ef.Status == EventFlowCompleted {\n\t\tef = nil\n\t}\n\n\tif ef == nil {\n\t\tef = NewEventFlow(f, event)\n\t\terr := f.cache.Set(key, ef, f.cooldown+time.Duration(5)*time.Minute)\n\t\tif err != nil {\n\t\t\treturn nil, created, err\n\t\t}\n\t\tcreated = true\n\t}\n\n\treturn ef, created, nil\n}\n\nfunc (f *Flow) getEventKey(event *ce.Event, groupBy []string) string {\n\tkey := event.Type\n\n\tgrouped := false\n\tfor _, g := range groupBy {\n\t\tif s, ok := event.GetString(g); ok {\n\t\t\tgrouped = true\n\t\t\tkey = path.Join(key, s)\n\t\t}\n\t}\n\n\tif !grouped {\n\t\treturn path.Join(key, event.ID)\n\t}\n\n\treturn key\n}\n\nfunc (f *Flow) isEventMatched(event *ce.Event) bool {\n\tif len(f.filters) == 0 {\n\t\treturn true\n\t}\n\n\tfor key, value := range f.filters {\n\t\tif v, ok := event.GetString(key); !ok || v != value {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (f *Flow) isEventTypeAllowed(eventType string) bool {\n\tif len(f.allowedEvents) == 0 {\n\t\treturn true\n\t}\n\n\tfor _, t := range f.allowedEvents {\n\t\tif t == eventType {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "internal/flows/manager.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage flows\n\nimport (\n\t\"github.com/goph/emperror\"\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/log\"\n\t\"github.com/banzaicloud/hollowtrees/internal/plugin\"\n)\n\n// FlowManager is used for managing action flows\ntype FlowManager interface {\n\tLogger() log.Logger\n\tErrorHandler() emperror.Handler\n\tPlugins() plugin.PluginManager\n}\n\n// Manager describes a FlowManager implementation\ntype Manager struct {\n\tlogger       log.Logger\n\terrorHandler emperror.Handler\n\tdispatcher   eventSubscriber\n\tplugins      plugin.PluginManager\n}\n\n// NewManager returns an initialized FlowManager implementation\nfunc NewManager(logger log.Logger, errorHandler emperror.Handler, dispatcher flowEventDispatcher, plugins plugin.PluginManager) *Manager {\n\treturn &Manager{\n\t\tlogger:       logger,\n\t\terrorHandler: errorHandler,\n\t\tdispatcher:   dispatcher,\n\t\tplugins:      plugins,\n\t}\n}\n\n// Logger returns the logger\nfunc (m *Manager) Logger() log.Logger {\n\treturn m.logger\n}\n\n// ErrorHandler returns the error handler\nfunc (m *Manager) ErrorHandler() emperror.Handler {\n\treturn m.errorHandler\n}\n\n// Plugins returns the plugin manager\nfunc (m *Manager) Plugins() plugin.PluginManager {\n\treturn m.plugins\n}\n\n// LoadFlows loads flow definitions from config,  initializes Flows\n// and subscribes them to the event dispatcher\nfunc (m *Manager) LoadFlows(v *viper.Viper) error {\n\tvar flows FlowConfigs\n\n\terr := viper.UnmarshalKey(\"flows\", &flows)\n\tif err != nil {\n\t\treturn emperror.Wrap(err, \"could not unmarshal flow configs\")\n\t}\n\n\tfor id, config := range flows {\n\t\terr := config.Validate(m.plugins, id)\n\t\tif err != nil {\n\t\t\treturn emperror.WrapWith(err, \"could not load flow\", \"flow\", id)\n\t\t}\n\n\t\tf := NewFlow(m, NewInMemFlowStore(), id, config.Name,\n\t\t\tDescription(config.Description),\n\t\t\tAllowedEvents(config.AllowedEvents),\n\t\t\tCooldown(config.Cooldown),\n\t\t\tGroupBy(config.GroupBy),\n\t\t\tPlugins(config.Plugins),\n\t\t\tFilters(config.Filters),\n\t\t)\n\n\t\terr = m.dispatcher.SubscribeAsync(CEIncomingTopic, f)\n\t\tif err != nil {\n\t\t\tm.errorHandler.Handle(emperror.WrapWith(err, \"could not subscribe to event dispatcher\", \"flow\", id))\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/flows/option.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage flows\n\nimport \"time\"\n\n// Option sets configuration on the Flow\ntype Option interface {\n\tapply(*Flow)\n}\n\n// Cooldown time that passes after an event flow is successfully finished\ntype Cooldown time.Duration\n\nfunc (o Cooldown) apply(f *Flow) {\n\tf.cooldown = time.Duration(o)\n}\n\n// AllowedEvents defines allowed event types for the flow\ntype AllowedEvents []string\n\nfunc (o AllowedEvents) apply(f *Flow) {\n\tf.allowedEvents = []string(o)\n}\n\n// GroupBy categorizes subsequent events as the same if all the corresponding values of these attributes match\ntype GroupBy []string\n\nfunc (o GroupBy) apply(f *Flow) {\n\tf.groupBy = []string(o)\n}\n\n// Plugins defines the plugins to execute in an event flow\ntype Plugins []string\n\nfunc (o Plugins) apply(f *Flow) {\n\tf.plugins = []string(o)\n}\n\n// Filters defines simple filter on event values\ntype Filters map[string]string\n\nfunc (o Filters) apply(f *Flow) {\n\tf.filters = map[string]string(o)\n}\n\n// Description sets the description of the action flow\ntype Description string\n\nfunc (o Description) apply(f *Flow) {\n\tf.description = string(o)\n}\n"
  },
  {
    "path": "internal/flows/store.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage flows\n\nimport (\n\t\"time\"\n\n\tcache \"github.com/patrickmn/go-cache\"\n)\n\ntype FlowStore interface {\n\tGet(string) (*EventFlow, error)\n\tSet(string, *EventFlow, time.Duration) error\n\tDelete(string)\n}\n\ntype InMemoryFlowStore struct {\n\tEventFlowCache *cache.Cache\n}\n\nfunc NewInMemFlowStore() *InMemoryFlowStore {\n\treturn &InMemoryFlowStore{\n\t\tEventFlowCache: cache.New(time.Duration(10)*time.Minute, time.Duration(1)*time.Minute),\n\t}\n}\n\nfunc (i *InMemoryFlowStore) Get(key string) (*EventFlow, error) {\n\ta, ok := i.EventFlowCache.Get(key)\n\tif a != nil && ok {\n\t\tef := a.(*EventFlow)\n\t\treturn ef, nil\n\t} else {\n\t\treturn nil, nil\n\t}\n}\n\nfunc (i *InMemoryFlowStore) Set(key string, ef *EventFlow, ttl time.Duration) error {\n\ti.EventFlowCache.Set(key, ef, ttl)\n\treturn nil\n}\n\nfunc (i *InMemoryFlowStore) Delete(key string) {\n\ti.EventFlowCache.Delete(key)\n}\n"
  },
  {
    "path": "internal/platform/config/config.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage config\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/goph/emperror\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/healthcheck\"\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/log\"\n\t\"github.com/banzaicloud/hollowtrees/internal/promalert\"\n)\n\nconst (\n\t// ServiceName is an identifier-like name used anywhere this app needs to be identified.\n\tServiceName = \"hollowtrees\"\n\n\t// FriendlyServiceName is the visible name of the service.\n\tFriendlyServiceName = \"Hollowtrees\"\n\n\t// ConfigEnvPrefix defines the prefix that ENVIRONMENT variables will use\n\tConfigEnvPrefix = \"HT\"\n)\n\n// Config holds any kind of configuration that comes from the outside world and\n// is necessary for running the application\ntype Config struct {\n\t// Meaningful values are recommended (eg. production, development, staging, release/123, etc)\n\tEnvironment string\n\n\t// Turns on some debug functionality (eg. more verbose logs)\n\tDebug bool\n\n\t// Log configuration\n\tLog log.Config\n\n\t// Healthcheck configuration\n\tHealthcheck healthcheck.Config\n\n\t// Prometheus alert handler configuration\n\tPromalert promalert.Config\n}\n\n// Validate validates the configuration\nfunc (c Config) Validate() error {\n\terr := c.Log.Validate()\n\tif err != nil {\n\t\treturn emperror.Wrap(err, \"could not validate log config\")\n\t}\n\n\terr = c.Promalert.Validate()\n\tif err != nil {\n\t\treturn emperror.Wrap(err, \"could not validate promalert config\")\n\t}\n\n\terr = c.Healthcheck.Validate()\n\tif err != nil {\n\t\treturn emperror.Wrap(err, \"could not validate healthcheck config\")\n\t}\n\n\treturn nil\n}\n\n// Configure configures some defaults in the Viper instance\nfunc Configure(v *viper.Viper, p *pflag.FlagSet) {\n\tv.AddConfigPath(\".\")\n\tv.AddConfigPath(\"$HOME/config\")\n\tp.Init(FriendlyServiceName, pflag.ExitOnError)\n\tpflag.Usage = func() {\n\t\tfmt.Fprintf(os.Stderr, \"Usage of %s:\\n\", FriendlyServiceName)\n\t\tpflag.PrintDefaults()\n\t}\n\tv.BindPFlags(p) // nolint:errcheck\n\n\tv.SetEnvPrefix(ConfigEnvPrefix)\n\tv.SetEnvKeyReplacer(strings.NewReplacer(\".\", \"_\"))\n\tv.AutomaticEnv()\n\n\t// Log configuration\n\tv.SetDefault(\"log.format\", \"logfmt\")\n\tv.SetDefault(\"log.level\", \"info\")\n\n\t// Healthcheck HTTP endpoint\n\tv.SetDefault(\"healthcheck.listenAddress\", \":8082\")\n\tv.SetDefault(\"healthcheck.endpoint\", \"/healthz\")\n\n\t// Prometheus alert handler\n\tv.SetDefault(\"promalert.listenAddress\", \":8081\")\n\tv.SetDefault(\"promalert.useJWTAuth\", false)\n\tv.SetDefault(\"promalert.jwtSigningKey\", \"\")\n}\n"
  },
  {
    "path": "internal/platform/config/error_handler.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage config\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/goph/emperror\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/errors\"\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/log\"\n)\n\nvar errorHandler emperror.Handler\nvar errorHandlerOnce sync.Once\n\n// ErrorHandler returns an error handler.\nfunc ErrorHandler(logger log.Logger) emperror.Handler {\n\terrorHandlerOnce.Do(func() {\n\t\terrorHandler = newErrorHandler(logger)\n\t})\n\n\treturn errorHandler\n}\n\nfunc newErrorHandler(logger log.Logger) emperror.Handler {\n\tloggerHandler := errors.NewHandler(logger)\n\n\treturn emperror.HandlerFunc(func(err error) {\n\t\tif stackTrace, ok := emperror.StackTrace(err); ok && len(stackTrace) > 0 {\n\t\t\tframe := stackTrace[0]\n\n\t\t\terr = emperror.With(\n\t\t\t\terr,\n\t\t\t\t\"func\", fmt.Sprintf(\"%n\", frame), // nolint: govet\n\t\t\t\t\"file\", fmt.Sprintf(\"%v\", frame), // nolint: govet\n\t\t\t)\n\t\t}\n\n\t\tloggerHandler.Handle(err)\n\t})\n}\n"
  },
  {
    "path": "internal/platform/errors/handler.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage errors\n\nimport (\n\t\"github.com/goph/emperror\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/log\"\n)\n\ntype handler struct {\n\tlogger log.Logger\n}\n\n// NewHandler returns a handler which logs errors using the platform logger\nfunc NewHandler(logger log.Logger) *handler {\n\treturn &handler{logger: logger}\n}\n\n// Handle logs an error\nfunc (h *handler) Handle(err error) {\n\tvar ctx map[string]interface{}\n\n\t// Extract context from the error and attach it to the log\n\tif kvs := emperror.Context(err); len(kvs) > 0 {\n\t\tctx = ToMap(kvs)\n\t}\n\n\tlogger := h.logger.WithFields(log.Fields(ctx))\n\n\ttype errorCollection interface {\n\t\tErrors() []error\n\t}\n\n\tif errs, ok := err.(errorCollection); ok {\n\t\tfor _, e := range errs.Errors() {\n\t\t\tctx = nil\n\t\t\t// Extract context from the error and attach it to the log\n\t\t\tif kvs := emperror.Context(e); len(kvs) > 0 {\n\t\t\t\tctx = ToMap(kvs)\n\t\t\t}\n\t\t\th.logger.WithFields(log.Fields(ctx)).Error(e.Error())\n\t\t}\n\t} else {\n\t\tlogger.Error(err.Error())\n\t}\n}\n"
  },
  {
    "path": "internal/platform/errors/keyvals.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage errors\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n)\n\n// ToMap creates a map of key-value pairs.\n//\n// The implementation bellow is from go-kit's JSON logger.\nfunc ToMap(keyvals []interface{}) map[string]interface{} {\n\tm := map[string]interface{}{}\n\n\tif len(keyvals) == 0 {\n\t\treturn m\n\t}\n\n\tif len(keyvals)%2 == 1 {\n\t\tkeyvals = append(keyvals, nil)\n\t}\n\n\tfor i := 0; i < len(keyvals); i += 2 {\n\t\tmerge(m, keyvals[i], keyvals[i+1])\n\t}\n\n\treturn m\n}\n\nfunc merge(dst map[string]interface{}, k, v interface{}) {\n\tvar key string\n\n\tswitch x := k.(type) {\n\tcase string:\n\t\tkey = x\n\tcase fmt.Stringer:\n\t\tkey = safeString(x)\n\tdefault:\n\t\tkey = fmt.Sprint(x)\n\t}\n\n\tswitch x := v.(type) {\n\tcase error:\n\t\tv = safeError(x)\n\tcase fmt.Stringer:\n\t\tv = safeString(x)\n\t}\n\n\tdst[key] = v\n}\n\nfunc safeString(str fmt.Stringer) (s string) {\n\tdefer func() {\n\t\tif panicVal := recover(); panicVal != nil {\n\t\t\tif v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() {\n\t\t\t\ts = \"NULL\"\n\t\t\t} else {\n\t\t\t\tpanic(panicVal)\n\t\t\t}\n\t\t}\n\t}()\n\n\ts = str.String()\n\n\treturn\n}\n\nfunc safeError(err error) (s interface{}) {\n\tdefer func() {\n\t\tif panicVal := recover(); panicVal != nil {\n\t\t\tif v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {\n\t\t\t\ts = nil\n\t\t\t} else {\n\t\t\t\tpanic(panicVal)\n\t\t\t}\n\t\t}\n\t}()\n\n\ts = err.Error()\n\n\treturn\n}\n"
  },
  {
    "path": "internal/platform/gin/correlationid/logger.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage correlationid\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/log\"\n)\n\nconst correlationIdField = \"correlation-id\"\n\n// Logger returns a new logger instance with a correlation ID in it.\nfunc Logger(logger log.Logger, ctx *gin.Context) log.Logger {\n\tcid := ctx.GetString(ContextKey)\n\n\tif cid == \"\" {\n\t\treturn logger\n\t}\n\n\treturn logger.WithField(correlationIdField, cid)\n}\n"
  },
  {
    "path": "internal/platform/gin/correlationid/middleware.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage correlationid\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/satori/go.uuid\"\n)\n\n// ContextKey is the key the retrieved (or generated) correlation ID is stored under in the gin Context.\nconst ContextKey = \"correlationid\"\n\n// Default correlation ID header\nconst defaultHeader = \"Correlation-ID\"\n\n// MiddlewareOption configures the correlation ID middleware.\ntype MiddlewareOption interface {\n\tapply(*middleware)\n}\n\n// Header configures the header from where the correlation ID will be retrieved.\ntype Header string\n\n// apply implements the MiddlewareOption interface.\nfunc (h Header) apply(m *middleware) {\n\tm.header = string(h)\n}\n\n// Middleware returns a gin compatible handler.\nfunc Middleware(opts ...MiddlewareOption) gin.HandlerFunc {\n\tm := new(middleware)\n\n\tfor _, opt := range opts {\n\t\topt.apply(m)\n\t}\n\n\tif m.header == \"\" {\n\t\tm.header = defaultHeader\n\t}\n\n\treturn m.Handle\n}\n\ntype middleware struct {\n\theader string\n}\n\n// Handle sets correlation id\nfunc (m *middleware) Handle(ctx *gin.Context) {\n\tif header := ctx.GetHeader(m.header); header != \"\" {\n\t\tctx.Set(ContextKey, header)\n\t} else {\n\t\tctx.Set(ContextKey, uuid.NewV4().String())\n\t}\n\n\tctx.Next()\n}\n"
  },
  {
    "path": "internal/platform/gin/log/middleware.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage log\n\nimport (\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/gin/correlationid\"\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/log\"\n)\n\nconst correlationIdField = \"correlation-id\"\n\n// Middleware returns a gin compatible handler\nfunc Middleware(logger log.Logger, notlogged ...string) gin.HandlerFunc {\n\tvar skip map[string]struct{}\n\n\tif length := len(notlogged); length > 0 {\n\t\tskip = make(map[string]struct{}, length)\n\n\t\tfor _, path := range notlogged {\n\t\t\tskip[path] = struct{}{}\n\t\t}\n\t}\n\n\treturn func(c *gin.Context) {\n\t\t// start timer\n\t\tstart := time.Now()\n\n\t\t// prevent middlewares from faking the request path\n\t\tpath := c.Request.URL.Path\n\t\traw := c.Request.URL.RawQuery\n\n\t\tc.Next()\n\n\t\t// Log only when path is not being skipped\n\t\tif _, ok := skip[path]; !ok {\n\t\t\tend := time.Now()\n\t\t\tlatency := end.Sub(start)\n\n\t\t\tif raw != \"\" {\n\t\t\t\tpath = path + \"?\" + raw\n\t\t\t}\n\n\t\t\tfields := log.Fields{\n\t\t\t\t\"status\":     c.Writer.Status(),\n\t\t\t\t\"method\":     c.Request.Method,\n\t\t\t\t\"path\":       path,\n\t\t\t\t\"ip\":         c.ClientIP(),\n\t\t\t\t\"latency\":    latency,\n\t\t\t\t\"user-agent\": c.Request.UserAgent(),\n\t\t\t}\n\n\t\t\tif cid := c.GetString(correlationid.ContextKey); cid != \"\" {\n\t\t\t\tfields[correlationIdField] = cid\n\t\t\t}\n\n\t\t\tentry := logger.WithFields(fields)\n\n\t\t\tif len(c.Errors) > 0 {\n\t\t\t\t// Append error field if this is an erroneous request.\n\t\t\t\tentry.Error(c.Errors.String())\n\t\t\t} else {\n\t\t\t\tentry.Info()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/platform/healthcheck/config.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage healthcheck\n\nimport \"errors\"\n\ntype Config struct {\n\tListenAddress string\n\tEndpoint      string\n}\n\n// Validate checks that the configuration is valid.\nfunc (c Config) Validate() error {\n\tif c.ListenAddress == \"\" {\n\t\treturn errors.New(\"listen address must not be empty\")\n\t}\n\n\tif c.Endpoint == \"\" {\n\t\treturn errors.New(\"endpoint must not be empty\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/platform/healthcheck/healthcheck.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage healthcheck\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/goph/emperror\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/log\"\n)\n\n// New runs the health check endpoint\nfunc New(config Config, logger log.Logger, errorHandler emperror.Handler) {\n\tlogger.WithFields(log.Fields{\"addr\": config.ListenAddress, \"endpoint\": config.Endpoint}).Info(\"starting health check http server\")\n\n\tr := gin.New()\n\tr.GET(config.Endpoint, func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"ok\")\n\t})\n\terr := r.Run(config.ListenAddress)\n\tif err != nil {\n\t\terrorHandler.Handle(err)\n\t}\n}\n"
  },
  {
    "path": "internal/platform/log/config.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage log\n\nimport (\n\t\"github.com/pkg/errors\"\n)\n\n// Config holds details necessary for logging.\ntype Config struct {\n\t// Format specifies the output log format.\n\t// Accepted values are: json, logfmt\n\tFormat string\n\n\t// Level is the minimum log level that should appear on the output.\n\tLevel string\n\n\t// NoColor makes sure that no log output gets colorized.\n\tNoColor bool\n}\n\n// Validate validates the configuration.\nfunc (c Config) Validate() error {\n\tif c.Format == \"\" {\n\t\treturn errors.New(\"log format is required\")\n\t}\n\n\tif c.Format != \"json\" && c.Format != \"logfmt\" {\n\t\treturn errors.New(\"invalid log format: \" + c.Format)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/platform/log/logger.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n// Package log configures a new logger for an application.\n\npackage log\n\nimport (\n\t\"github.com/goph/logur\"\n)\n\ntype Logger interface {\n\tTrace(args ...interface{})\n\tDebug(args ...interface{})\n\tInfo(args ...interface{})\n\tWarn(args ...interface{})\n\tError(args ...interface{})\n\tTraceln(args ...interface{})\n\tDebugln(args ...interface{})\n\tInfoln(args ...interface{})\n\tWarnln(args ...interface{})\n\tErrorln(args ...interface{})\n\tTracef(format string, args ...interface{})\n\tDebugf(format string, args ...interface{})\n\tInfof(format string, args ...interface{})\n\tWarnf(format string, args ...interface{})\n\tErrorf(format string, args ...interface{})\n\tWithFields(fields Fields) Logger\n\tWithField(key string, value interface{}) Logger\n}\n\n// Fields is an alias to log.Fields for easier usage.\ntype Fields = logur.Fields\n\n// NewLogger creates a new logger.\nfunc NewLogger(config Config) Logger {\n\treturn NewLogrusLogger(config)\n}\n"
  },
  {
    "path": "internal/platform/log/logrus_adapter.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage log\n\nimport (\n\t\"os\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype logrusAdapter struct {\n\t*logrus.Entry\n}\n\n// WithField adds a single field to the Entry\nfunc (a *logrusAdapter) WithField(key string, value interface{}) Logger {\n\treturn &logrusAdapter{a.Entry.WithField(key, value)}\n}\n\n// WithFields returns a new logger based on the original logger with\n// the additional supplied fields.\nfunc (a *logrusAdapter) WithFields(fields Fields) Logger {\n\treturn &logrusAdapter{a.Entry.WithFields(logrus.Fields(fields))}\n}\n\nfunc NewLogrusLogger(config Config) Logger {\n\tlogger := logrus.New()\n\n\tlogger.SetOutput(os.Stdout)\n\tlogger.SetFormatter(&logrus.TextFormatter{\n\t\tDisableColors:             config.NoColor,\n\t\tEnvironmentOverrideColors: true,\n\t})\n\n\tswitch config.Format {\n\tcase \"logfmt\":\n\t\t// Already the default\n\n\tcase \"json\":\n\t\tlogger.SetFormatter(&logrus.JSONFormatter{})\n\t}\n\n\tif level, err := logrus.ParseLevel(config.Level); err == nil {\n\t\tlogger.SetLevel(level)\n\t}\n\n\treturn &logrusAdapter{\n\t\tlogrus.NewEntry(logger),\n\t}\n}\n"
  },
  {
    "path": "internal/plugin/config.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage plugin\n\nimport (\n\t\"github.com/goph/emperror\"\n\t\"github.com/pkg/errors\"\n)\n\n// PluginConfig describes a plugin configuration\ntype PluginConfig struct {\n\tName    string `mapstructure:\"name\"`\n\tType    string `mapstructure:\"type\"`\n\tAddress string `mapstructure:\"address\"`\n}\n\ntype PluginConfigs []PluginConfig\n\n// Validate validates plugin configuration\nfunc (c PluginConfig) Validate() error {\n\tif c.Name == \"\" {\n\t\treturn errors.New(\"name must be set\")\n\t}\n\n\tif c.Type != \"grpc\" {\n\t\treturn emperror.With(errors.New(\"invalid plugin type\"), \"type\", c.Type)\n\t}\n\n\tif c.Type == \"grpc\" && c.Address == \"\" {\n\t\treturn errors.New(\"address must not be empty for a GRPC plugin\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/plugin/grpc.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage plugin\n\nimport (\n\t\"context\"\n\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/ce\"\n\t\"github.com/banzaicloud/hollowtrees/pkg/grpcplugin/proto\"\n)\n\ntype grpcPlugin struct {\n\tBasePlugin\n\taddress string\n}\n\n// NewGrpcPlugin initializes a grpcPlugin\nfunc NewGrpcPlugin(name string, address string) *grpcPlugin {\n\treturn &grpcPlugin{\n\t\tBasePlugin: BasePlugin{\n\t\t\tname: name,\n\t\t},\n\t\taddress: address,\n\t}\n}\n\n// Handle sends the CloudEvent to a GRPC plugin endpoint\nfunc (p *grpcPlugin) Handle(event *ce.Event) error {\n\tconn, err := grpc.Dial(p.address, grpc.WithInsecure())\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer conn.Close()\n\n\tj, err := event.MarshalJSON()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient := proto.NewEventHandlerClient(conn)\n\tez := &proto.CloudEvent{\n\t\tSpecversion: event.SpecVersion,\n\t\tType:        event.Type,\n\t\tSource:      event.Source.String(),\n\t\tId:          event.ID,\n\t\tTime:        event.Time.String(),\n\t\tSchemaurl:   event.SchemaURL.String(),\n\t\tContenttype: \"application/cloudevents+json\",\n\t\tExtensions:  event.GetExtensions(),\n\t\tData:        j,\n\t}\n\t_, err = client.Handle(context.Background(), ez)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/plugin/internal.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage plugin\n\nimport (\n\t\"github.com/banzaicloud/hollowtrees/internal/ce\"\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/log\"\n)\n\ntype internalPlugin struct {\n\tBasePlugin\n\tlogger log.Logger\n}\n\n// NewInternalPlugin returns an initialized internalPlugin\nfunc NewInternalPlugin(name string, logger log.Logger) *internalPlugin {\n\treturn &internalPlugin{\n\t\tBasePlugin: BasePlugin{\n\t\t\tname: name,\n\t\t},\n\t\tlogger: logger,\n\t}\n}\n\n// Handle handles\nfunc (p *internalPlugin) Handle(event *ce.Event) error {\n\tp.logger.Infof(\"internal-demo-plugin: %s\", event.Type)\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/plugin/manager.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage plugin\n\nimport (\n\t\"errors\"\n\n\t\"github.com/goph/emperror\"\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/log\"\n)\n\n// PluginManager describes what a plugin manager implementation must provide\ntype PluginManager interface {\n\tAdd(plugin ...EventHandlerPlugin)\n\tGetByNames(names ...string) (map[string]EventHandlerPlugin, error)\n\tGetByName(name string) (EventHandlerPlugin, error)\n}\n\n// Manager is a PluginManager implementation\ntype Manager struct {\n\tlogger       log.Logger\n\terrorHandler emperror.Handler\n\n\tplugins map[string]EventHandlerPlugin\n}\n\n// NewManager returns an initialized Manager\nfunc NewManager(logger log.Logger, errorHandler emperror.Handler) *Manager {\n\tm := &Manager{\n\t\tlogger:       logger,\n\t\terrorHandler: errorHandler,\n\t}\n\n\tplugins := make(map[string]EventHandlerPlugin)\n\tm.plugins = plugins\n\n\treturn m\n}\n\n// Add add an initialized plugin\nfunc (m *Manager) Add(plugins ...EventHandlerPlugin) {\n\tfor _, plugin := range plugins {\n\t\tm.plugins[plugin.GetName()] = plugin\n\t}\n}\n\n// GetByNames returns a map of plugins by their names\nfunc (m *Manager) GetByNames(names ...string) (map[string]EventHandlerPlugin, error) {\n\tplugins := make(map[string]EventHandlerPlugin)\n\n\tfor _, name := range names {\n\t\tp, err := m.GetByName(name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tplugins[p.GetName()] = p\n\t}\n\n\treturn plugins, nil\n}\n\n// GetByName returns a plugin by it's name\nfunc (m *Manager) GetByName(name string) (EventHandlerPlugin, error) {\n\tp := m.plugins[name]\n\tif p == nil {\n\t\treturn nil, emperror.With(errors.New(\"plugin not found\"), \"name\", name)\n\t}\n\n\treturn p, nil\n}\n\n// LoadFromConfig loads plugins from configuration\nfunc (m *Manager) LoadFromConfig(v *viper.Viper) error {\n\tvar plugins PluginConfigs\n\n\terr := viper.UnmarshalKey(\"plugins\", &plugins)\n\tif err != nil {\n\t\treturn emperror.Wrap(err, \"could not unmarshal plugin configs\")\n\t}\n\n\tif len(plugins) == 0 {\n\t\treturn emperror.Wrap(err, \"no plugins were defined\")\n\t}\n\n\tfor _, plugin := range plugins {\n\t\terr := plugin.Validate()\n\t\tif err != nil {\n\t\t\treturn emperror.WrapWith(err, \"invalid plugin configuration\", \"plugin\", plugin.Name)\n\t\t}\n\t\tswitch plugin.Type {\n\t\tcase \"grpc\":\n\t\t\tm.Add(NewGrpcPlugin(plugin.Name, plugin.Address))\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/plugin/plugin.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage plugin\n\nimport (\n\t\"github.com/banzaicloud/hollowtrees/internal/ce\"\n)\n\n// EventHandlerPlugin defines an event handler plugin\ntype EventHandlerPlugin interface {\n\tGetName() string\n\tHandle(event *ce.Event) error\n}\n\n// BasePlugin describes a basic plugin struct\ntype BasePlugin struct {\n\tname string\n}\n\n// GetName returns the plugin name\nfunc (p *BasePlugin) GetName() string {\n\treturn p.name\n}\n"
  },
  {
    "path": "internal/promalert/alert.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage promalert\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\tuuid \"github.com/satori/go.uuid\"\n\t\"gopkg.in/go-playground/validator.v9\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/ce\"\n\t\"github.com/banzaicloud/hollowtrees/pkg/auth\"\n)\n\ntype Alerts []Alert\n\nfunc (alerts Alerts) Validate() error {\n\tfor _, alert := range alerts {\n\t\tif alert.Labels[\"cluster_id\"] == \"\" {\n\t\t\treturn errors.New(\"invalid alert: mandatory 'cluster_id' parameter is missing\")\n\t\t}\n\t\tif alert.Labels[\"org_id\"] == \"\" {\n\t\t\treturn errors.New(\"invalid alert: mandatory 'org_id' parameter is missing\")\n\t\t}\n\n\t\terr := validator.New().Struct(alert)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (alerts Alerts) Authorize(user *auth.User) error {\n\tfor _, alert := range alerts {\n\t\tif alert.Labels[\"cluster_id\"] == \"\" || alert.Labels[\"org_id\"] == \"\" || alert.Labels[\"cluster_id\"] != user.ClusterID || alert.Labels[\"org_id\"] != user.OrgID {\n\t\t\treturn errors.Errorf(\"invalid alert: unauthorized\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Alert describes an incoming Prometheus alert\ntype Alert struct {\n\tLabels       map[string]string `json:\"labels\"`\n\tAnnotations  map[string]string `json:\"annotations\"`\n\tStartsAt     time.Time         `json:\"startsAt\"`\n\tEndsAt       time.Time         `json:\"endsAt\"`\n\tGeneratorURL string            `json:\"generatorURL\" validate:\"url\"`\n}\n\n// convertToCE converts incoming prometheus alert struct to CloudEvent struct\nfunc (a *Alert) convertToCE(cid string) (*ce.Event, error) {\n\te := &ce.Event{}\n\n\tfor k, v := range a.Labels {\n\t\te.Set(k, v)\n\t}\n\te.Set(\"correlationid\", cid)\n\te.Set(\"labels\", a.Labels)\n\n\te.Set(\"id\", uuid.NewV4().String())\n\te.Set(\"type\", fmt.Sprintf(\"%s%s\", CETypePrefix, a.Labels[\"alertname\"]))\n\te.Set(\"specversion\", \"0.2\")\n\tu, err := url.Parse(a.GeneratorURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\te.Set(\"source\", *u)\n\te.Set(\"time\", &a.StartsAt)\n\te.Set(\"eventType\", \"prometheus\")\n\n\treturn e, nil\n}\n"
  },
  {
    "path": "internal/promalert/config.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage promalert\n\nimport \"github.com/pkg/errors\"\n\ntype Config struct {\n\t// HTTP listen address\n\tListenAddress string\n\n\t// JWT auth\n\tUseJWTAuth bool\n\n\t// JWT signing key\n\tJWTSigningKey string\n}\n\n// Validate checks that the configuration is valid.\nfunc (c Config) Validate() error {\n\tif c.ListenAddress == \"\" {\n\t\treturn errors.New(\"listen address must not be empty\")\n\t}\n\n\tif c.UseJWTAuth && c.JWTSigningKey == \"\" {\n\t\treturn errors.New(\"JWTSigningKey must be set if JWT auth is enabled\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/promalert/event_dispatcher.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage promalert\n\nimport (\n\t\"github.com/banzaicloud/hollowtrees/internal/ce\"\n)\n\ntype baseEventPublisher interface {\n\tPublish(topic string, args ...interface{})\n}\n\ntype eventDispatcher struct {\n\teb baseEventPublisher\n}\n\ntype eventPublisher interface {\n\tPublish(topic string, event *ce.Event)\n}\n\n// NewEventDispatcher returns a new event dispatcher\nfunc NewEventDispatcher(eb baseEventPublisher) *eventDispatcher {\n\treturn &eventDispatcher{\n\t\teb: eb,\n\t}\n}\n\n// Publish sends the given event through the event dispatcher\nfunc (b *eventDispatcher) Publish(topic string, event *ce.Event) {\n\tb.eb.Publish(topic, event)\n}\n"
  },
  {
    "path": "internal/promalert/promalert.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage promalert\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/goph/emperror\"\n\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/gin/correlationid\"\n\tginlog \"github.com/banzaicloud/hollowtrees/internal/platform/gin/log\"\n\t\"github.com/banzaicloud/hollowtrees/internal/platform/log\"\n\t\"github.com/banzaicloud/hollowtrees/pkg/auth\"\n)\n\nconst (\n\tEventTopic   = \"cloud.events.incoming\"\n\tCETypePrefix = \"prometheus.server.alert.\"\n)\n\n// PromAlertHandler describes a Prometheus alert handler\ntype PromAlertHandler struct {\n\tuseJWTAuth    bool\n\tjwtSigningKey string\n\tlistenAddress string\n\n\tlogger       log.Logger\n\terrorHandler emperror.Handler\n\teb           eventPublisher\n}\n\n// New returns an initialized PromAlertHandler\nfunc New(config Config, logger log.Logger, errorHandler emperror.Handler, eb eventPublisher) *PromAlertHandler {\n\treturn &PromAlertHandler{\n\t\tuseJWTAuth:    config.UseJWTAuth,\n\t\tjwtSigningKey: config.JWTSigningKey,\n\t\tlistenAddress: config.ListenAddress,\n\n\t\tlogger:       logger,\n\t\terrorHandler: errorHandler,\n\t\teb:           eb,\n\t}\n}\n\n// Run runs the alert handler HTTP listener\nfunc (p *PromAlertHandler) Run() {\n\tp.logger.WithField(\"addr\", p.listenAddress).WithField(\"useJWTAuth\", p.useJWTAuth).Info(\"starting prometheus alert handler\")\n\n\tr := gin.New()\n\tr.Use(gin.Recovery())\n\n\tr.Use(correlationid.Middleware())\n\tr.Use(ginlog.Middleware(p.logger))\n\tif p.useJWTAuth {\n\t\tr.Use(auth.Handler(p.jwtSigningKey))\n\t}\n\n\tr.POST(\"/api/v1/alerts\", p.handle)\n\n\terr := r.Run(p.listenAddress)\n\tif err != nil {\n\t\tp.errorHandler.Handle(err)\n\t}\n}\n\n// handle handles the incoming HTTP request\nfunc (p *PromAlertHandler) handle(c *gin.Context) {\n\tvar alerts Alerts\n\n\tlog := correlationid.Logger(p.logger, c)\n\n\tif err := c.ShouldBindJSON(&alerts); err != nil {\n\t\tc.AbortWithStatusJSON(http.StatusBadRequest, gin.H{\n\t\t\t\"status\":  http.StatusBadRequest,\n\t\t\t\"message\": \"failed to process alerts\",\n\t\t\t\"error\":   err.Error(),\n\t\t})\n\t\treturn\n\t}\n\n\tif err := alerts.Validate(); err != nil {\n\t\tc.AbortWithStatusJSON(http.StatusBadRequest, gin.H{\n\t\t\t\"status\":  http.StatusBadRequest,\n\t\t\t\"message\": \"invalid alert\",\n\t\t\t\"error\":   err.Error(),\n\t\t})\n\t\treturn\n\t}\n\n\tif p.useJWTAuth {\n\t\tif err := alerts.Authorize(auth.GetCurrentUser(c)); err != nil {\n\t\t\tc.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{\n\t\t\t\t\"status\":  http.StatusUnauthorized,\n\t\t\t\t\"message\": \"could not process alerts\",\n\t\t\t\t\"error\":   err.Error(),\n\t\t\t})\n\t\t\treturn\n\t\t}\n\t}\n\n\tlog.WithField(\"alert-count\", len(alerts)).Debug(\"alerts received\")\n\n\tcid := c.GetString(correlationid.ContextKey)\n\tp.publishAlerts(alerts, cid)\n\n\tc.JSON(http.StatusOK, gin.H{\n\t\t\"status\": http.StatusOK,\n\t\t\"data\":   \"ok\",\n\t})\n}\n\n// publishAlerts publishing incoming alerts through the event dispatcher\nfunc (p *PromAlertHandler) publishAlerts(alerts []Alert, cid string) {\n\tfor _, alert := range alerts {\n\t\tevent, err := alert.convertToCE(cid)\n\t\tif err != nil {\n\t\t\tp.errorHandler.Handle(err)\n\t\t\tcontinue\n\t\t}\n\t\tp.eb.Publish(EventTopic, event)\n\t}\n}\n"
  },
  {
    "path": "pkg/auth/auth.go",
    "content": "// Copyright © 2019 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage auth\n\nimport (\n\t\"encoding/base32\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\tjwt \"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gofrs/uuid\"\n\t\"github.com/pkg/errors\"\n\n\tbauth \"github.com/banzaicloud/bank-vaults/pkg/sdk/auth\"\n)\n\ntype User struct {\n\tClusterID string `json:\"clusterID\"`\n\tOrgID     string `json:\"orgID\"`\n}\n\ntype TokenGenerator interface {\n\tGenerate(userID, orgID uint, expiresAt *time.Time) (string, string, error)\n}\n\ntype tokenGenerator struct {\n\tIssuer     string\n\tAudience   string\n\tSigningKey string\n}\n\nfunc NewTokenGenerator(issuer, audience, signingKey string) TokenGenerator {\n\treturn &tokenGenerator{\n\t\tIssuer:     issuer,\n\t\tAudience:   audience,\n\t\tSigningKey: signingKey,\n\t}\n}\n\nfunc (g *tokenGenerator) Generate(userID, orgID uint, expiresAt *time.Time) (string, string, error) {\n\ttokenID := uuid.Must(uuid.NewV4()).String()\n\n\tvar expiresAtUnix int64\n\tif expiresAt != nil {\n\t\texpiresAtUnix = expiresAt.Unix()\n\t}\n\n\t// Create the Claims\n\tclaims := &bauth.ScopedClaims{\n\t\tStandardClaims: jwt.StandardClaims{\n\t\t\tIssuer:    g.Issuer,\n\t\t\tAudience:  g.Audience,\n\t\t\tIssuedAt:  jwt.TimeFunc().Unix(),\n\t\t\tExpiresAt: expiresAtUnix,\n\t\t\tSubject:   fmt.Sprintf(\"clusters/%d/%d\", orgID, userID),\n\t\t\tId:        tokenID,\n\t\t},\n\t\tScope: \"api:invoke\",\n\t}\n\n\tjwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n\tif g.SigningKey == \"\" {\n\t\treturn \"\", \"\", errors.New(\"missing signingKeyBase32\")\n\t}\n\tsignedToken, err := jwtToken.SignedString([]byte(base32.StdEncoding.EncodeToString([]byte(g.SigningKey))))\n\tif err != nil {\n\t\treturn \"\", \"\", errors.Wrap(err, \"failed to sign user token\")\n\t}\n\n\treturn tokenID, signedToken, nil\n}\n\nfunc GetCurrentUser(c *gin.Context) *User {\n\tif u, ok := bauth.GetCurrentUser(c).(*User); ok {\n\t\treturn u\n\t}\n\treturn nil\n}\n\nfunc Handler(signingKey string) gin.HandlerFunc {\n\treturn bauth.JWTAuth(nil, signingKey, claimConverter)\n}\n\nfunc claimConverter(claims *bauth.ScopedClaims) interface{} {\n\tif !strings.HasPrefix(claims.Subject, \"clusters/\") {\n\t\treturn nil\n\t}\n\n\tsegments := strings.Split(claims.Subject, \"/\")\n\tif len(segments) < 2 {\n\t\treturn nil\n\t}\n\n\treturn &User{\n\t\tClusterID: segments[2],\n\t\tOrgID:     segments[1],\n\t}\n}\n"
  },
  {
    "path": "pkg/grpcplugin/handler.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage grpcplugin\n\nimport (\n\t\"context\"\n\n\t\"github.com/banzaicloud/hollowtrees/pkg/grpcplugin/proto\"\n\t\"github.com/goph/emperror\"\n)\n\n// EventHandler should be implemented by the plugins that are doing some actions based on alerts\ntype EventHandler interface {\n\tHandle(*CloudEvent) (*Result, error)\n}\n\ntype CloudEvent proto.CloudEvent\ntype Result proto.Result\n\ntype handler struct {\n\tEventHandler EventHandler\n}\n\n// NewHandler returns an initialized handler\nfunc NewHandler(eh EventHandler) *handler {\n\treturn &handler{\n\t\tEventHandler: eh,\n\t}\n}\n\n// Handle converts and passes the incoming event to the defined event handler\nfunc (h *handler) Handle(ctx context.Context, ce *proto.CloudEvent) (*proto.Result, error) {\n\tvar e = CloudEvent{\n\t\tSpecversion: ce.Specversion,\n\t\tType:        ce.Type,\n\t\tSource:      ce.Source,\n\t\tId:          ce.Id,\n\t\tTime:        ce.Time,\n\t\tContenttype: ce.Contenttype,\n\t\tData:        ce.Data,\n\t}\n\n\tresult, err := h.EventHandler.Handle(&e)\n\tif err != nil {\n\t\treturn nil, emperror.Wrap(err, \"could not handle event\")\n\t}\n\n\treturn &proto.Result{\n\t\tStatus: result.Status,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/grpcplugin/proto/event.pb.go",
    "content": "// Code generated by protoc-gen-go.\n// source: event.proto\n// DO NOT EDIT!\n\n/*\nPackage proto is a generated protocol buffer package.\n\nIt is generated from these files:\n\tevent.proto\n\nIt has these top-level messages:\n\tCloudEvent\n\tResult\n*/\npackage proto\n\nimport proto1 \"github.com/golang/protobuf/proto\"\nimport fmt \"fmt\"\nimport math \"math\"\n\nimport (\n\tcontext \"golang.org/x/net/context\"\n\tgrpc \"google.golang.org/grpc\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto1.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto1.ProtoPackageIsVersion2 // please upgrade the proto package\n\ntype CloudEvent struct {\n\tSpecversion string            `protobuf:\"bytes,1,opt,name=specversion\" json:\"specversion,omitempty\"`\n\tType        string            `protobuf:\"bytes,2,opt,name=type\" json:\"type,omitempty\"`\n\tSource      string            `protobuf:\"bytes,3,opt,name=source\" json:\"source,omitempty\"`\n\tId          string            `protobuf:\"bytes,4,opt,name=id\" json:\"id,omitempty\"`\n\tTime        string            `protobuf:\"bytes,5,opt,name=time\" json:\"time,omitempty\"`\n\tSchemaurl   string            `protobuf:\"bytes,6,opt,name=schemaurl\" json:\"schemaurl,omitempty\"`\n\tContenttype string            `protobuf:\"bytes,7,opt,name=contenttype\" json:\"contenttype,omitempty\"`\n\tData        []byte            `protobuf:\"bytes,8,opt,name=data,proto3\" json:\"data,omitempty\"`\n\tExtensions  map[string]string `protobuf:\"bytes,9,rep,name=extensions\" json:\"extensions,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n}\n\nfunc (m *CloudEvent) Reset()                    { *m = CloudEvent{} }\nfunc (m *CloudEvent) String() string            { return proto1.CompactTextString(m) }\nfunc (*CloudEvent) ProtoMessage()               {}\nfunc (*CloudEvent) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }\n\nfunc (m *CloudEvent) GetSpecversion() string {\n\tif m != nil {\n\t\treturn m.Specversion\n\t}\n\treturn \"\"\n}\n\nfunc (m *CloudEvent) GetType() string {\n\tif m != nil {\n\t\treturn m.Type\n\t}\n\treturn \"\"\n}\n\nfunc (m *CloudEvent) GetSource() string {\n\tif m != nil {\n\t\treturn m.Source\n\t}\n\treturn \"\"\n}\n\nfunc (m *CloudEvent) GetId() string {\n\tif m != nil {\n\t\treturn m.Id\n\t}\n\treturn \"\"\n}\n\nfunc (m *CloudEvent) GetTime() string {\n\tif m != nil {\n\t\treturn m.Time\n\t}\n\treturn \"\"\n}\n\nfunc (m *CloudEvent) GetSchemaurl() string {\n\tif m != nil {\n\t\treturn m.Schemaurl\n\t}\n\treturn \"\"\n}\n\nfunc (m *CloudEvent) GetContenttype() string {\n\tif m != nil {\n\t\treturn m.Contenttype\n\t}\n\treturn \"\"\n}\n\nfunc (m *CloudEvent) GetData() []byte {\n\tif m != nil {\n\t\treturn m.Data\n\t}\n\treturn nil\n}\n\nfunc (m *CloudEvent) GetExtensions() map[string]string {\n\tif m != nil {\n\t\treturn m.Extensions\n\t}\n\treturn nil\n}\n\ntype Result struct {\n\tStatus string `protobuf:\"bytes,1,opt,name=status\" json:\"status,omitempty\"`\n}\n\nfunc (m *Result) Reset()                    { *m = Result{} }\nfunc (m *Result) String() string            { return proto1.CompactTextString(m) }\nfunc (*Result) ProtoMessage()               {}\nfunc (*Result) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }\n\nfunc (m *Result) GetStatus() string {\n\tif m != nil {\n\t\treturn m.Status\n\t}\n\treturn \"\"\n}\n\nfunc init() {\n\tproto1.RegisterType((*CloudEvent)(nil), \"proto.CloudEvent\")\n\tproto1.RegisterType((*Result)(nil), \"proto.Result\")\n}\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ context.Context\nvar _ grpc.ClientConn\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\nconst _ = grpc.SupportPackageIsVersion4\n\n// Client API for EventHandler service\n\ntype EventHandlerClient interface {\n\tHandle(ctx context.Context, in *CloudEvent, opts ...grpc.CallOption) (*Result, error)\n}\n\ntype eventHandlerClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewEventHandlerClient(cc *grpc.ClientConn) EventHandlerClient {\n\treturn &eventHandlerClient{cc}\n}\n\nfunc (c *eventHandlerClient) Handle(ctx context.Context, in *CloudEvent, opts ...grpc.CallOption) (*Result, error) {\n\tout := new(Result)\n\terr := grpc.Invoke(ctx, \"/proto.EventHandler/Handle\", in, out, c.cc, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// Server API for EventHandler service\n\ntype EventHandlerServer interface {\n\tHandle(context.Context, *CloudEvent) (*Result, error)\n}\n\nfunc RegisterEventHandlerServer(s *grpc.Server, srv EventHandlerServer) {\n\ts.RegisterService(&_EventHandler_serviceDesc, srv)\n}\n\nfunc _EventHandler_Handle_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CloudEvent)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(EventHandlerServer).Handle(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/proto.EventHandler/Handle\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(EventHandlerServer).Handle(ctx, req.(*CloudEvent))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _EventHandler_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"proto.EventHandler\",\n\tHandlerType: (*EventHandlerServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Handle\",\n\t\t\tHandler:    _EventHandler_Handle_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"event.proto\",\n}\n\nfunc init() { proto1.RegisterFile(\"event.proto\", fileDescriptor0) }\n\nvar fileDescriptor0 = []byte{\n\t// 330 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x50, 0x41, 0x4f, 0xf2, 0x40,\n\t0x10, 0xfd, 0x5a, 0xa0, 0x9f, 0x0c, 0xa8, 0xb8, 0x31, 0x66, 0x43, 0x3c, 0x54, 0x2e, 0x72, 0x30,\n\t0x3d, 0xe0, 0xc5, 0x18, 0x39, 0x88, 0x21, 0xe1, 0x48, 0xfa, 0x0f, 0x96, 0x76, 0x12, 0x1a, 0xb7,\n\t0xbb, 0x64, 0x77, 0x8b, 0xe2, 0xef, 0xf4, 0x07, 0x99, 0x9d, 0x56, 0x69, 0xf4, 0xd4, 0x79, 0xef,\n\t0xed, 0x7b, 0x7d, 0x33, 0x30, 0xc0, 0x3d, 0x2a, 0x97, 0xec, 0x8c, 0x76, 0x9a, 0xf5, 0xe8, 0x33,\n\t0xf9, 0x0c, 0x01, 0x5e, 0xa4, 0xae, 0xf2, 0xa5, 0xd7, 0x58, 0x0c, 0x03, 0xbb, 0xc3, 0x6c, 0x8f,\n\t0xc6, 0x16, 0x5a, 0xf1, 0x20, 0x0e, 0xa6, 0xfd, 0xb4, 0x4d, 0x31, 0x06, 0x5d, 0x77, 0xd8, 0x21,\n\t0x0f, 0x49, 0xa2, 0x99, 0x5d, 0x41, 0x64, 0x75, 0x65, 0x32, 0xe4, 0x1d, 0x62, 0x1b, 0xc4, 0xce,\n\t0x20, 0x2c, 0x72, 0xde, 0x25, 0x2e, 0x2c, 0x72, 0xf2, 0x16, 0x25, 0xf2, 0x5e, 0xe3, 0x2d, 0x4a,\n\t0x64, 0xd7, 0xd0, 0xb7, 0xd9, 0x16, 0x4b, 0x51, 0x19, 0xc9, 0x23, 0x12, 0x8e, 0x84, 0xef, 0x93,\n\t0x69, 0xe5, 0x50, 0x39, 0xfa, 0xe9, 0xff, 0xba, 0x4f, 0x8b, 0xf2, 0x99, 0xb9, 0x70, 0x82, 0x9f,\n\t0xc4, 0xc1, 0x74, 0x98, 0xd2, 0xcc, 0x9e, 0x01, 0xf0, 0xdd, 0xa1, 0xf2, 0x85, 0x2d, 0xef, 0xc7,\n\t0x9d, 0xe9, 0x60, 0x76, 0x53, 0xef, 0x9d, 0x1c, 0x97, 0x4d, 0x96, 0x3f, 0x6f, 0x96, 0xca, 0x99,\n\t0x43, 0xda, 0x32, 0x8d, 0xe7, 0x70, 0xfe, 0x4b, 0x66, 0x23, 0xe8, 0xbc, 0xe2, 0xa1, 0xb9, 0x89,\n\t0x1f, 0xd9, 0x25, 0xf4, 0xf6, 0x42, 0x56, 0xdf, 0xc7, 0xa8, 0xc1, 0x63, 0xf8, 0x10, 0x4c, 0x62,\n\t0x88, 0x52, 0xb4, 0x95, 0x74, 0x74, 0x1b, 0x27, 0x5c, 0x65, 0x1b, 0x63, 0x83, 0x66, 0x4f, 0x30,\n\t0xa4, 0x16, 0x2b, 0xa1, 0x72, 0x89, 0x86, 0xdd, 0x41, 0x54, 0x8f, 0xec, 0xe2, 0x4f, 0xd3, 0xf1,\n\t0x69, 0x43, 0xd5, 0x99, 0x93, 0x7f, 0x8b, 0x39, 0xdc, 0x66, 0xba, 0x4c, 0x36, 0x42, 0x7d, 0x88,\n\t0x22, 0xf3, 0x0f, 0x93, 0xad, 0x96, 0x52, 0xbf, 0x39, 0x83, 0x68, 0x93, 0x2d, 0x25, 0x91, 0x77,\n\t0x31, 0x5a, 0x1d, 0xc1, 0xda, 0xa7, 0xac, 0x83, 0x4d, 0x44, 0x71, 0xf7, 0x5f, 0x01, 0x00, 0x00,\n\t0xff, 0xff, 0x30, 0x95, 0x57, 0x04, 0x12, 0x02, 0x00, 0x00,\n}\n"
  },
  {
    "path": "pkg/grpcplugin/proto/event.proto",
    "content": "syntax = \"proto3\";\n\noption java_multiple_files = true;\noption java_package = \"com.banzaicloud.hollowtrees.handleEvent\";\noption java_outer_classname = \"HandleEventProto\";\n\npackage proto;\n\nservice EventHandler {\n    rpc Handle (CloudEvent) returns (Result) {}\n}\n\nmessage CloudEvent {\n    string specversion = 1;\n    string type = 2;\n    string source = 3;\n    string id = 4;\n    string time = 5;\n    string schemaurl = 6;\n    string contenttype = 7;\n    bytes data = 8;\n    map<string, string> extensions = 9;\n}\n\nmessage Result {\n    string status = 1;\n}"
  },
  {
    "path": "pkg/grpcplugin/server.go",
    "content": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage grpcplugin\n\nimport (\n\t\"net\"\n\n\t\"github.com/goph/emperror\"\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/banzaicloud/hollowtrees/pkg/grpcplugin/proto\"\n)\n\n// Serve registers the EventHandler and starts the GRPC server\nfunc Serve(bindAddress string, handler EventHandler, opt ...grpc.ServerOption) error {\n\tlistener, err := net.Listen(\"tcp\", bindAddress)\n\tif err != nil {\n\t\treturn emperror.Wrap(err, \"failed to listen\")\n\t}\n\n\tgrpcServer := grpc.NewServer(opt...)\n\tproto.RegisterEventHandlerServer(grpcServer, NewHandler(handler))\n\n\treturn grpcServer.Serve(listener)\n}\n"
  },
  {
    "path": "scripts/check-header.sh",
    "content": "#!/usr/bin/env bash\n\nread -r -d '' EXPECTED <<EOF\n// Copyright © DATE Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\nEOF\n\nSTATUS=0\nFILES=$(find . -name \"*.go\" -not -name \"*.pb.go\" -not -path \"./vendor/*\")\n\nfor FILE in $FILES; do\n    # Replace the actual year with DATE so we can ignore the year when\n    # checking for the license header.\n    HEADER=$(head -n 13 $FILE | sed -E -e 's/Copyright © [0-9]+/Copyright © DATE/')\n    if [ \"$HEADER\" != \"$EXPECTED\" ]; then\n        echo \"incorrect license header: $FILE\"\n        STATUS=1\n    fi\ndone\n\nexit $STATUS\n"
  }
]