Repository: banzaicloud/hollowtrees Branch: master Commit: d76060afb4b4 Files: 64 Total size: 155.4 KB Directory structure: gitextract_xefxug8p/ ├── .circleci/ │ └── config.yml ├── .gitignore ├── .idea/ │ ├── hollowtrees.iml │ └── modules.xml ├── .licensei.toml ├── Dockerfile ├── Dockerfile.local ├── LICENSE.md ├── Makefile ├── README.md ├── charts/ │ └── hollowtrees-with-ps/ │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── templates/ │ │ ├── _helpers.tpl │ │ ├── configmap-ht.yaml │ │ ├── configmap-htpsp.yaml │ │ ├── deployment.yaml │ │ ├── ingress.yaml │ │ ├── secret.yaml │ │ └── service.yaml │ └── values.yaml ├── cmd/ │ └── daemon/ │ ├── build.go │ ├── configuration.go │ └── main.go ├── config.yaml.dist ├── docs/ │ └── DEVELOPMENT.md ├── examples/ │ └── grpc_plugin/ │ └── main.go ├── go.mod ├── go.sum ├── internal/ │ ├── ce/ │ │ └── ce.go │ ├── flows/ │ │ ├── config.go │ │ ├── event_dispatcher.go │ │ ├── eventflow.go │ │ ├── flows.go │ │ ├── manager.go │ │ ├── option.go │ │ └── store.go │ ├── platform/ │ │ ├── config/ │ │ │ ├── config.go │ │ │ └── error_handler.go │ │ ├── errors/ │ │ │ ├── handler.go │ │ │ └── keyvals.go │ │ ├── gin/ │ │ │ ├── correlationid/ │ │ │ │ ├── logger.go │ │ │ │ └── middleware.go │ │ │ └── log/ │ │ │ └── middleware.go │ │ ├── healthcheck/ │ │ │ ├── config.go │ │ │ └── healthcheck.go │ │ └── log/ │ │ ├── config.go │ │ ├── logger.go │ │ └── logrus_adapter.go │ ├── plugin/ │ │ ├── config.go │ │ ├── grpc.go │ │ ├── internal.go │ │ ├── manager.go │ │ └── plugin.go │ └── promalert/ │ ├── alert.go │ ├── config.go │ ├── event_dispatcher.go │ └── promalert.go ├── pkg/ │ ├── auth/ │ │ └── auth.go │ └── grpcplugin/ │ ├── handler.go │ ├── proto/ │ │ ├── event.pb.go │ │ └── event.proto │ └── server.go └── scripts/ └── check-header.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ version: 2.1 orbs: helm: banzaicloud/helm@0.0.5 jobs: build: docker: - image: circleci/golang:1.12 environment: GOFLAGS: -mod=readonly steps: - checkout - restore_cache: name: Restore build dependencies keys: - build-deps-v1-{{ .Branch }}-{{ checksum "Makefile" }} - restore_cache: name: Restore Go module cache keys: - gomod-v1-{{ .Branch }}-{{ checksum "go.sum" }} - gomod-v1-{{ .Branch }} - gomod-v1-master - gomod-v1 - run: name: Download Go module cache command: go mod download - save_cache: name: Save Go module cache key: gomod-v1-{{ .Branch }}-{{ checksum "go.sum" }} paths: - /go/pkg/mod - restore_cache: name: Restore license cache keys: - licensei-v1-{{ .Branch }}-{{ checksum "go.sum" }} - licensei-v1-{{ .Branch }} - licensei-v1-master - licensei-v1 - run: name: Download license information for dependencies command: make license-cache - save_cache: name: Save license cache key: licensei-v1-{{ .Branch }}-{{ checksum "go.sum" }} paths: - .licensei.cache - run: name: Check dependency licenses command: make license-check - run: name: Run tests command: TEST_PKGS=$(echo `go list ./... | circleci tests split`) TEST_REPORT_NAME=results_${CIRCLE_NODE_INDEX}.xml make test - run: name: Run integration tests command: TEST_PKGS=$(echo `go list ./... | circleci tests split`) TEST_REPORT_NAME=results_${CIRCLE_NODE_INDEX}.xml make test-integration - run: name: Run linter command: make lint - save_cache: name: Save build dependencies key: build-deps-v1-{{ .Branch }}-{{ checksum "Makefile" }} paths: - bin/ - store_test_results: path: build/test_results/ workflows: version: 2 ci: jobs: - build helm-chart: jobs: - helm/lint-chart: charts-dir: charts filters: tags: ignore: /.*/ - helm/publish-chart: context: helm charts-dir: charts filters: tags: only: /chart\/\S+\/\d+.\d+.\d+/ branches: ignore: /.*/ ================================================ FILE: .gitignore ================================================ /bin/ /build/ /config.* !config.yaml.dist /.docker/ /docker-compose.override.yml /.env /.env.test /vendor/ /.licensei.cache # IDE integration /.idea/* !/.idea/copyright/ !/.idea/*.iml !/.idea/externalDependencies.xml !/.idea/go.imports.xml !/.idea/modules.xml !/.idea/runConfigurations/ !/.idea/scopes/ ================================================ FILE: .idea/hollowtrees.iml ================================================ ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .licensei.toml ================================================ approved = [ "mit", "apache-2.0", "bsd-3-clause", "bsd-2-clause", "mpl-2.0", ] ignored = [ "github.com/ghodss/yaml", "github.com/davecgh/go-spew", # ISC license "github.com/gogo/protobuf", # BSD "gomodules.xyz/jsonpatch/v2", "gopkg.in/fsnotify.v1", # Vanity URLS :\ "google.golang.org/grpc", "google.golang.org/genproto", "sigs.k8s.io/yaml", ] ================================================ FILE: Dockerfile ================================================ ARG GO_VERSION=1.12 FROM golang:${GO_VERSION}-alpine AS builder RUN apk add --update --no-cache ca-certificates=20190108-r0 make=4.2.1-r2 git=2.22.0-r0 curl=7.65.1-r0 ENV GOFLAGS="-mod=readonly" RUN mkdir -p /build WORKDIR /build COPY go.* /build/ RUN go mod download COPY . /build ENV PATH /build/bin /bin:$PATH RUN BUILD_DIR='' BINARY_NAME=app make build-release FROM alpine:3.7 COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /app /app USER nobody:nobody CMD ["/app"] ================================================ FILE: Dockerfile.local ================================================ FROM alpine:3.7 AS builder RUN apk add --update --no-cache ca-certificates FROM alpine:3.7 COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ ARG BUILD_DIR ARG BINARY_NAME COPY $BUILD_DIR/$BINARY_NAME /app USER nobody:nobody CMD ["/app"] ================================================ FILE: LICENSE.md ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Makefile ================================================ # A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html # Project variables PACKAGE = $(shell echo $${PWD\#\#*src/}) BINARY_NAME ?= $(shell basename $$PWD) DOCKER_IMAGE ?= banzaicloud/hollowtrees # Build variables BUILD_DIR ?= build BUILD_PACKAGE = ${PACKAGE}/cmd/daemon VERSION ?= $(shell git symbolic-ref -q --short HEAD || git describe --tags --exact-match) COMMIT_HASH ?= $(shell git rev-parse --short HEAD 2>/dev/null) BUILD_DATE ?= $(shell date +%FT%T%z) LDFLAGS += -X main.version=${VERSION} -X main.commitHash=${COMMIT_HASH} -X main.buildDate=${BUILD_DATE} export CGO_ENABLED ?= 0 export GOOS = $(shell go env GOOS) ifeq (${VERBOSE}, 1) ifeq ($(filter -v,${GOARGS}),) GOARGS += -v endif endif # Docker variables DOCKER_TAG ?= ${VERSION} # Dependency versions DEP_VERSION = 0.5.0 GOLANGCI_VERSION = 1.17.1 LICENSEI_VERSION = 0.1.0 PROTOC_VERSION = 3.6.1 GOLANG_VERSION = 1.11 # Add the ability to override some variables # Use with care -include override.mk .PHONY: clean clean: ## Clean the working area and the project rm -rf bin/ ${BUILD_DIR}/ bin/dep: bin/dep-${DEP_VERSION} @ln -sf dep-${DEP_VERSION} bin/dep bin/dep-${DEP_VERSION}: @mkdir -p bin curl https://raw.githubusercontent.com/golang/dep/master/install.sh | INSTALL_DIRECTORY=bin DEP_RELEASE_TAG=v${DEP_VERSION} sh @mv bin/dep $@ .PHONY: build build: ## Build a binary ifeq (${VERBOSE}, 1) go env endif ifneq (${IGNORE_GOLANG_VERSION_REQ}, 1) @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) endif @$(eval GENERATED_BINARY_NAME = ${BINARY_NAME}) @$(if $(strip ${BINARY_NAME_SUFFIX}),$(eval GENERATED_BINARY_NAME = ${BINARY_NAME}-$(subst $(eval) ,-,$(strip ${BINARY_NAME_SUFFIX}))),) go build ${GOARGS} -tags "${GOTAGS}" -ldflags "${LDFLAGS}" -o ${BUILD_DIR}/${GENERATED_BINARY_NAME} ${BUILD_PACKAGE} .PHONY: build-release build-release: LDFLAGS += -w build-release: build ## Build a binary without debug information .PHONY: build-debug build-debug: GOARGS += -gcflags "all=-N -l" build-debug: BINARY_NAME_SUFFIX += debug build-debug: build ## Build a binary with remote debugging capabilities .PHONY: docker docker: export GOOS = linux docker: BINARY_NAME_SUFFIX += docker docker: build-release ## Build a Docker image docker build --build-arg BUILD_DIR=${BUILD_DIR} --build-arg BINARY_NAME=${GENERATED_BINARY_NAME} -t ${DOCKER_IMAGE}:${DOCKER_TAG} -f Dockerfile.local . ifeq (${DOCKER_LATEST}, 1) docker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:latest endif .PHONY: check check: test-all lint ## Run tests and linters bin/go-junit-report: @mkdir -p bin GOBIN=${PWD}/bin/ go get -u github.com/jstemmer/go-junit-report TEST_PKGS ?= ./... TEST_REPORT_NAME ?= results.xml .PHONY: test test: TEST_REPORT ?= main test: SHELL = /bin/bash test: bin/go-junit-report ## Run tests @mkdir -p ${BUILD_DIR}/test_results/${TEST_REPORT} @set -o pipefail go test -v $(filter-out -v,${GOARGS}) ${TEST_PKGS} 2>&1 | tee >(bin/go-junit-report > ${BUILD_DIR}/test_results/${TEST_REPORT}/${TEST_REPORT_NAME}) wait .PHONY: test-all test-all: ## Run all tests @${MAKE} GOARGS="${GOARGS} -run .\*" TEST_REPORT=all test .PHONY: test-integration test-integration: ## Run integration tests @${MAKE} GOARGS="${GOARGS} -run ^TestIntegration\$$\$$" TEST_REPORT=integration test bin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION} @ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint bin/golangci-lint-${GOLANGCI_VERSION}: @mkdir -p bin curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b ./bin/ v${GOLANGCI_VERSION} @mv bin/golangci-lint $@ .PHONY: lint lint: bin/golangci-lint ## Run linter bin/golangci-lint run bin/licensei: bin/licensei-${LICENSEI_VERSION} @ln -sf licensei-${LICENSEI_VERSION} bin/licensei bin/licensei-${LICENSEI_VERSION}: @mkdir -p bin curl -sfL https://raw.githubusercontent.com/goph/licensei/master/install.sh | bash -s v${LICENSEI_VERSION} @mv bin/licensei $@ .PHONY: license-check license-check: bin/licensei ## Run license check bin/licensei check ./scripts/check-header.sh .PHONY: license-cache license-cache: bin/licensei ## Generate license cache bin/licensei cache protoc: ## Run protobuf generation protoc -I pkg/grpcplugin/proto/ pkg/grpcplugin/proto/event.proto --go_out=plugins=grpc:pkg/grpcplugin/proto .PHONY: list list: ## List all make targets @${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 .PHONY: help .DEFAULT_GOAL := help help: @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' # Variable outputting/exporting rules var-%: ; @echo $($*) varexport-%: ; @echo $*=$($*) ================================================ FILE: README.md ================================================ # Hollowtrees _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._ _Hollowtrees, a ruleset based watchguard is keeping spot/preemptible instance based clusters safe and allows to use them in production. Handles 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._ Hollowtrees is a core building block of the Pipeline platform. Check out the developer beta:

**Warning:** _Hollowtrees is experimental, under development and does not have a stable release yet. If in doubt, don't go out._ ## Quick start Building the project is as simple as running a go build command. The result is a statically linked executable binary. ```bash make build ./build/hollowtrees ``` Configuration of the project is done through a YAML config file. An example for that can be found under `config.yaml.dist` ## Quick architecture overview >For an introduction and overview of the architecture please read the following blog [post](https://banzaicloud.com/blog/hollowtrees) ![Hollowtrees](docs/images/hollowtrees-overview.png) ## Configuring Prometheus to send alerts to Hollowtrees Hollowtrees 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: ```yaml # Alertmanager configuration alerting: alertmanagers: - static_configs: - targets: - localhost:9092 ``` ### Configuring action flows After 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`. Hollowtrees 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. Alerts coming from Prometheus are converted to events with a type of `prometheus.server.alert.`. 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. ### Advanced control structures in action flows * `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` * `groupBy`: Categorizes subsequent events as the same, if all the corresponding values of these attributes match * `filters`: Filter events by event values ### Action plugins Action 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`. To 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 ```go as.Serve(port, newEventHandler()) ``` ### License Copyright (c) 2017-2019 [Banzai Cloud, Inc.](https://banzaicloud.com) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: charts/hollowtrees-with-ps/.helmignore ================================================ # Patterns to ignore when building packages. # This supports shell glob matching, relative path matching, and # negation (prefixed with !). Only one pattern per line. .DS_Store # Common VCS dirs .git/ .gitignore .bzr/ .bzrignore .hg/ .hgignore .svn/ # Common backup files *.swp *.bak *.tmp *~ # Various IDEs .project .idea/ *.tmproj .vscode/ ================================================ FILE: charts/hollowtrees-with-ps/Chart.yaml ================================================ apiVersion: v1 name: hollowtrees-with-ps home: https://banzaicloud.com sources: - https://banzaicloud.com - https://github.com/banzaicloud - https://github.com/banzaicloud/hollowtrees - https://github.com/banzaicloud/hollowtrees/charts/hollowtrees-with-ps version: 0.2.1 appVersion: 0.1.1 description: Hollowtrees with Pipeline Scaler plugin keywords: - scaling - hollowtrees icon: https://banzaicloud.com/img/banzai-cloud-logo.png ================================================ FILE: charts/hollowtrees-with-ps/README.md ================================================ # Hollowtrees with Pipeline Scaler Plugin Helm Chart ## TL;DR ```bash $ helm repo add banzaicloud-stable https://kubernetes-charts.banzaicloud.com $ helm repo update $ helm install --name=htpsp banzaicloud-stable/hollowtrees-with-ps ``` ## Prerequisites - Kubernetes 1.10+ ## Installing the Chart To install the chart with the release name `htpsp`: ```bash helm install --name=htpsp banzaicloud-stable/hollowtrees-with-ps ``` The command deploys the application on the Kubernetes cluster with the default configuration. The configuration section lists the parameters that can be configured during installation. > Tip: List all releases using `helm list` ## Uninstalling the Chart To uninstall/delete the htpsp release: ```bash $ helm del --purge htpsp ``` The command removes all the Kubernetes components associated with the chart and deletes the release. ## Configuration The configurable parameters and default values are listed in [`values.yaml`](values.yaml). Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. Alternatively, a YAML file that specifies the values for the parameters can be provided during the chart installation: ```bash $ helm install --name htpsp -f my-values.yaml banzaicloud-stable/hollowtrees-with-ps ``` ================================================ FILE: charts/hollowtrees-with-ps/templates/_helpers.tpl ================================================ {{/* vim: set filetype=mustache: */}} {{/* Expand the name of the chart. */}} {{- define "hollowtrees-with-ps.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} {{- define "hollowtrees-with-ps.fullname" -}} {{- if .Values.fullnameOverride -}} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- if contains $name .Release.Name -}} {{- .Release.Name | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} {{- end -}} {{- end -}} {{/* Create chart name and version as used by the chart label. */}} {{- define "hollowtrees-with-ps.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Common labels */}} {{- define "hollowtrees-with-ps.labels" -}} app.kubernetes.io/name: {{ include "hollowtrees-with-ps.name" . }} helm.sh/chart: {{ include "hollowtrees-with-ps.chart" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end -}} ================================================ FILE: charts/hollowtrees-with-ps/templates/configmap-ht.yaml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: {{ include "hollowtrees-with-ps.fullname" . }} labels: {{ include "hollowtrees-with-ps.labels" . | indent 4 }} data: config.yaml: |- log: format: {{ .Values.log.format | quote }} level: {{ .Values.log.level | quote }} healthcheck: listenAddress: ":{{ .Values.healthcheck.listenPort }}" endpoint: {{ .Values.healthcheck.endpoint | quote }} promalert: listenAddress: ":{{ .Values.promalert.listenPort }}" useJWTAuth: {{ .Values.promalert.useJWTAuth }} plugins: - name: {{ .Values.scaler.name | quote }} address: "{{ .Values.scaler.hostname }}:{{ .Values.scaler.port }}" type: {{ .Values.scaler.type | quote }} flows: scaler: name: {{ .Values.scaler.name | quote }} description: {{ .Values.scaler.description | quote }} allowedEvents: {{- range .Values.scaler.allowedEvents }} - {{ . | quote }} {{- end }} plugins: - {{ .Values.scaler.name | quote }} cooldown: {{ .Values.scaler.cooldown | quote }} groupBy: {{- range .Values.scaler.groupBy }} - {{ . | quote }} {{- end }} ================================================ FILE: charts/hollowtrees-with-ps/templates/configmap-htpsp.yaml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: {{ include "hollowtrees-with-ps.fullname" . }}-htpsp labels: {{ include "hollowtrees-with-ps.labels" . | indent 4 }} data: {{- with .Values.htpsp }} config.yaml: |- log: format: {{ .log.format | quote }} level: {{ .log.level | quote }} healthcheck: listenAddress: ":{{ .healthcheck.listenPort }}" endpoint: {{ .healthcheck.endpoint | quote }} eventHandler: listenAddress: ":{{ .listenPort }}" scaler: pipeline: url: {{ required "Pipeline URL must be defined" .scaler.pipeline.url | quote }} skipTLSVerify: {{ .scaler.pipeline.skipTLSVerify }} telescopes: url: {{ required "Telescopes URL must be defined" .scaler.telescopes.url | quote }} skipTLSVerify: {{ .scaler.telescopes.skipTLSVerify }} retries: {{ .scaler.retries }} waitBetweenRetries: {{ .scaler.waitBetweenRetries | quote }} waitInQueue: {{ .scaler.waitInQueue | quote }} {{- end }} ================================================ FILE: charts/hollowtrees-with-ps/templates/deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "hollowtrees-with-ps.fullname" . }} labels: {{ include "hollowtrees-with-ps.labels" . | indent 4 }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app.kubernetes.io/name: {{ include "hollowtrees-with-ps.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} template: metadata: labels: app.kubernetes.io/name: {{ include "hollowtrees-with-ps.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} spec: containers: - name: "hollowtrees" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.imagePullPolicy | quote }} {{- if .Values.promalert.useJWTAuth }} env: - name: HT_PROMALERT_JWTSIGNINGKEY valueFrom: secretKeyRef: name: {{ include "hollowtrees-with-ps.fullname" . }} key: tokenSigningKey {{- end }} ports: - name: http containerPort: {{ .Values.promalert.listenPort }} - name: healthcheck containerPort: {{ .Values.healthcheck.listenPort }} livenessProbe: httpGet: path: {{ .Values.healthcheck.endpoint | quote }} port: healthcheck initialDelaySeconds: 10 timeoutSeconds: 3 periodSeconds: 5 readinessProbe: httpGet: path: {{ .Values.healthcheck.endpoint | quote }} port: healthcheck initialDelaySeconds: 10 timeoutSeconds: 3 periodSeconds: 5 resources: {{- toYaml .Values.resources | nindent 10 }} volumeMounts: - name: config mountPath: /config/ - name: "pipeline-scaler-plugin" image: "{{ .Values.htpsp.image.repository }}:{{ .Values.htpsp.image.tag }}" imagePullPolicy: {{ .Values.htpsp.image.imagePullPolicy | quote }} env: - name: HTPSP_CONFIG_DIR value: "/config" ports: - name: http containerPort: {{ .Values.htpsp.listenPort }} - name: healthcheck containerPort: {{ .Values.htpsp.healthcheck.listenPort }} livenessProbe: httpGet: path: {{ .Values.htpsp.healthcheck.endpoint | quote }} port: healthcheck initialDelaySeconds: 10 timeoutSeconds: 3 periodSeconds: 5 readinessProbe: httpGet: path: {{ .Values.htpsp.healthcheck.endpoint | quote }} port: healthcheck initialDelaySeconds: 10 timeoutSeconds: 3 periodSeconds: 5 volumeMounts: - name: htpsp-config mountPath: /config/ resources: {{- toYaml .Values.htpsp.resources | nindent 10 }} volumes: - name: config configMap: name: {{ include "hollowtrees-with-ps.fullname" . }} - name: htpsp-config configMap: name: {{ include "hollowtrees-with-ps.fullname" . }}-htpsp {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} ================================================ FILE: charts/hollowtrees-with-ps/templates/ingress.yaml ================================================ {{- if .Values.ingress.enabled -}} {{- $fullName := include "hollowtrees-with-ps.fullname" . -}} apiVersion: extensions/v1beta1 kind: Ingress metadata: name: {{ $fullName }} labels: {{ include "hollowtrees-with-ps.labels" . | indent 4 }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: {{- if .Values.ingress.tls }} tls: {{- range .Values.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} {{- end }} secretName: {{ .secretName }} {{- end }} {{- end }} rules: {{- range .Values.ingress.hosts }} {{- $url := splitList "/" . }} - host: {{ first $url }} http: paths: - path: /{{ rest $url | join "/" }} backend: serviceName: {{ $fullName }} servicePort: http {{- end }} {{- end }} ================================================ FILE: charts/hollowtrees-with-ps/templates/secret.yaml ================================================ {{- if .Values.promalert.useJWTAuth -}} apiVersion: v1 kind: Secret metadata: name: {{ template "hollowtrees-with-ps.fullname" . }} labels: {{ include "hollowtrees-with-ps.labels" . | indent 4 }} type: Opaque data: tokenSigningKey: {{ required "tokenSigningKey must be defined if JWT auth is enabled" .Values.promalert.jwtSigningKey | b64enc | quote }} {{- end }} ================================================ FILE: charts/hollowtrees-with-ps/templates/service.yaml ================================================ apiVersion: v1 kind: Service metadata: name: {{ include "hollowtrees-with-ps.fullname" . }} labels: {{ include "hollowtrees-with-ps.labels" . | indent 4 }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: http protocol: TCP name: http selector: app.kubernetes.io/name: {{ include "hollowtrees-with-ps.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} ================================================ FILE: charts/hollowtrees-with-ps/values.yaml ================================================ # Default values for hollowtrees-with-ps # This is a YAML-formatted file. # Declare variables to be passed into your templates. image: repository: banzaicloud/hollowtrees tag: "0.1.1" imagePullPolicy: IfNotPresent replicaCount: 1 service: type: ClusterIP port: 8080 log: format: logfmt level: debug healthcheck: listenPort: 8082 endpoint: /healthz promalert: listenPort: 8080 useJWTAuth: false scaler: name: pipeline-scaler description: Pipeline cluster scaling hostname: localhost port: 9993 type: grpc allowedEvents: - prometheus.server.alert.InstanceTermination groupBy: - org_id - cluster_id - cluster_name - instance_id cooldown: 5m ingress: enabled: false annotations: kubernetes.io/ingress.class: traefik traefik.ingress.kubernetes.io/rewrite-target: / hosts: - "/hollowtrees-alerts" tls: [] # - secretName: chart-example-tls # hosts: # - chart-example.local resources: requests: memory: 256Mi cpu: 120m htpsp: listenPort: 9993 image: repository: banzaicloud/ht-pipeline-scaler-plugin tag: "0.1.0" imagePullPolicy: IfNotPresent log: format: logfmt level: debug healthcheck: listenPort: 8083 endpoint: /healthz scaler: pipeline: url: skipTLSVerify: false telescopes: url: skipTLSVerify: false retries: 2 waitBetweenRetries: 10s waitInQueue: 60s resources: requests: memory: 256Mi cpu: 120m nodeSelector: {} tolerations: [] affinity: {} podAnnotations: {} ================================================ FILE: cmd/daemon/build.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main // Provisioned by ldflags // nolint: gochecknoglobals var ( version string commitHash string buildDate string ) ================================================ FILE: cmd/daemon/configuration.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "github.com/goph/emperror" "github.com/spf13/pflag" "github.com/spf13/viper" "github.com/banzaicloud/hollowtrees/internal/platform/config" ) var configuration config.Config func configure() { config.Configure(viper.GetViper(), pflag.CommandLine) pflag.Parse() err := viper.ReadInConfig() if _, ok := err.(viper.ConfigFileNotFoundError); err != nil && !ok { panic(emperror.Wrap(err, "failed to read configuration")) } err = viper.Unmarshal(&configuration) if err != nil { panic(emperror.Wrap(err, "failed to unmarshal configuration")) } err = configuration.Validate() if err != nil { panic(emperror.Wrap(err, "cloud not validate configuration")) } } ================================================ FILE: cmd/daemon/main.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "os" "sync" evbus "github.com/asaskevich/EventBus" "github.com/pkg/errors" "github.com/spf13/pflag" "github.com/spf13/viper" yaml "gopkg.in/yaml.v2" "github.com/banzaicloud/hollowtrees/internal/flows" "github.com/banzaicloud/hollowtrees/internal/platform/config" "github.com/banzaicloud/hollowtrees/internal/platform/healthcheck" "github.com/banzaicloud/hollowtrees/internal/platform/log" "github.com/banzaicloud/hollowtrees/internal/plugin" "github.com/banzaicloud/hollowtrees/internal/promalert" ) // nolint: gochecknoinits func init() { pflag.Bool("version", false, "Show version information") pflag.Bool("dump-config", false, "Dump configuration to the console") } func main() { // Loads and validates configuration configure() // Show version if asked for if viper.GetBool("version") { fmt.Printf("%s version %s (%s) built on %s\n", config.FriendlyServiceName, version, commitHash, buildDate) os.Exit(0) } // Dump config if asked for if viper.GetBool("dump-config") { c := viper.AllSettings() y, err := yaml.Marshal(c) if err != nil { panic(errors.Wrap(err, "failed to dump configuration")) } fmt.Print(string(y)) os.Exit(0) } // Create logger logger := log.NewLogger(configuration.Log) // Create error handler errorHandler := config.ErrorHandler(logger) // Create event bus eventBus := evbus.New() // Create plugin manager pluginManager := plugin.NewManager(logger, errorHandler) err := pluginManager.LoadFromConfig(viper.GetViper()) if err != nil { errorHandler.Handle(err) os.Exit(2) } // Add internal demo plugin pluginManager.Add(plugin.NewInternalPlugin("internal-demo", logger)) // Create flow manager flowManager := flows.NewManager(logger, errorHandler, flows.NewEventDispatcher(eventBus), pluginManager) err = flowManager.LoadFlows(viper.GetViper()) if err != nil { errorHandler.Handle(err) os.Exit(2) } var wg sync.WaitGroup // Starts health check HTTP server wg.Add(1) go func() { healthcheck.New(configuration.Healthcheck, logger, errorHandler) }() // Starts prometheus alert manager wg.Add(1) go func() { promalert.New(configuration.Promalert, logger, errorHandler, promalert.NewEventDispatcher(eventBus)).Run() }() logger.Infof("%s started", config.FriendlyServiceName) wg.Wait() } ================================================ FILE: config.yaml.dist ================================================ # logger settings log: format: "logfmt" level: "debug" # action plugins plugins: - name: "dummy-plugin-1" address: "localhost:9091" type: "grpc" - name: "dummy-plugin-2" address: "localhost:9091" type: "grpc" # action flows flows: simple: name: "Simple Flow" description: "simple dummy flow" allowedEvents: - "prometheus.server.alert.TestAlert" - "prometheus.server.alert.DummyTestAlert2" plugins: - "internal-demo" - "dummy-plugin-1" cooldown: 1m groupBy: - cluster_name - instance_id filters: - cluster_name: "test-cluster" ================================================ FILE: docs/DEVELOPMENT.md ================================================ # Development guide ## Building and running Hollowtrees on your local machine The project can be built with `make`, configuration is done via `config.yaml`. Copy the `config.yaml.dist` file, or create a new one with custom action plugins and rules. ```bash make build ./build/hollowtrees ``` ## Setting up action plugins Currently only the `grpc` plugin type is supported. ```bash plugins: - name: "grpc-dummy" address: "localhost:9091" type: "grpc" ``` ## Triggering the Hollowtrees server with an alert The 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. Save the following JSON in a file named `alert.json`, and run the `cURL` command below. The `alertname` label is required, it will be converted to an event type that's used to select action flows for a specific event. The other labels are optional and arbitrary, and all of the labels will be sent to the action plugins as `data`. ```json [ { "annotations": {}, "startsAt": "2006-01-02T15:04:05Z", "endsAt":"2007-01-02T15:04:05Z", "generatorURL":"http://test", "labels": { "alertname": "TestAlert", "cluster_name":"test-cluster", "Name":"test" } } ] ``` ```bash curl -X POST -d @alert.json localhost:9092/api/v1/alerts ``` ## Setting up an action flow for Hollowtrees Here's an example config snippet that describes an action flow that can be triggered with the above JSON: ```bash flows: test: name: "Test Flow" 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`" plugins: - "dummy-plugin-1" allowedEvents: - "prometheus.server.alert.TestAlert" filters: - cluster_name: "test-cluster" ``` ## Running a (dummy) gRPC action plugin There is an example gRPC action plugin in `examples/grpc_plugin`. Enter that directory, build the plugin with `go build .` and run the binary. The dummy plugin accepts every event type and logs the requests it gets through gRPC. ================================================ FILE: examples/grpc_plugin/main.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "flag" "fmt" gp "github.com/banzaicloud/hollowtrees/pkg/grpcplugin" ) // dummyEventHandler dummy implementation of EventHandler type dummyEventHandler struct{} // Handle dummy implementation func (d *dummyEventHandler) Handle(event *gp.CloudEvent) (*gp.Result, error) { fmt.Printf("got GRPC request, handling alert: %s\n", event.Data) return &gp.Result{Status: "ok"}, nil } var listenAddr string func init() { flag.StringVar(&listenAddr, "listen-addr", ":9091", "address to listen on") } func main() { flag.Parse() fmt.Printf("Hollowtrees Dummy GRPC EventHandler Plugin listening on %s\n", listenAddr) err := gp.Serve(listenAddr, &dummyEventHandler{}) if err != nil { panic(err) } } ================================================ FILE: go.mod ================================================ module github.com/banzaicloud/hollowtrees go 1.12 require ( github.com/asaskevich/EventBus v0.0.0-20180315140547-d46933a94f05 github.com/banzaicloud/bank-vaults/pkg/sdk v0.1.3-0.20190826065836-26d654c87254 github.com/cloudevents/sdk-go v0.0.0-20181211100118-3a3d34a7231e github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/gin-gonic/gin v1.4.0 github.com/go-playground/locales v0.12.1 // indirect github.com/go-playground/universal-translator v0.16.0 // indirect github.com/gofrs/uuid v3.2.0+incompatible github.com/golang/protobuf v1.3.1 github.com/goph/emperror v0.14.0 github.com/goph/logur v0.5.0 github.com/leodido/go-urn v1.1.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.8.1 github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.4.2 github.com/spf13/cast v1.3.0 github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.4.0 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f // indirect golang.org/x/text v0.3.2 // indirect google.golang.org/grpc v1.22.0 gopkg.in/go-playground/validator.v8 v8.18.2 gopkg.in/go-playground/validator.v9 v9.29.1 gopkg.in/yaml.v2 v2.2.2 ) replace ( github.com/ugorji/go => github.com/ugorji/go/codec v1.1.7 k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93 k8s.io/client-go => k8s.io/client-go v2.0.0-alpha.0.0.20181213151034-8d9ed539ba31+incompatible ) ================================================ FILE: go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/InVisionApp/go-logger v1.0.1/go.mod h1:+cGTDSn+P8105aZkeOfIhdd7vFO5X1afUHcjvanY0L8= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/ThreeDotsLabs/watermill v0.1.2/go.mod h1:c0DOrvvuqbB8uhZlgY/fukFFfv1WZ6HinSktALd9b38= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/EventBus v0.0.0-20180315140547-d46933a94f05 h1:Shem5lRG4gJyrrg9YMIl7dOQazyWCq0Daz4LjompZ28= github.com/asaskevich/EventBus v0.0.0-20180315140547-d46933a94f05/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII= github.com/banzaicloud/bank-vaults/pkg/sdk v0.1.1 h1:FVru2fTDY1dcvMZAFVVvJE7N2JC4WXMD+vuDQjlIaKo= github.com/banzaicloud/bank-vaults/pkg/sdk v0.1.1/go.mod h1:BBgi3VY8BvvLBMWDtdiM+DTyRW2n6Sbvzvif+/AXHXI= github.com/banzaicloud/bank-vaults/pkg/sdk v0.1.2-0.20190824120735-50600eba199e h1:NH/cYHOQB8+ezTLy1xiRx7QwGwrW9WtZ9Zq7DS7WtmY= github.com/banzaicloud/bank-vaults/pkg/sdk v0.1.2-0.20190824120735-50600eba199e/go.mod h1:t8CI6t3iGDKQuTFLFjhY/HBw/p3B6dCsLAgikct0amc= github.com/banzaicloud/bank-vaults/pkg/sdk v0.1.3-0.20190826065836-26d654c87254 h1:EDSd7zAvlWFymtHnGZtnto+bPoLZmHAYs9LZoj8juW4= github.com/banzaicloud/bank-vaults/pkg/sdk v0.1.3-0.20190826065836-26d654c87254/go.mod h1:t8CI6t3iGDKQuTFLFjhY/HBw/p3B6dCsLAgikct0amc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudevents/sdk-go v0.0.0-20181211100118-3a3d34a7231e h1:AYCa3CZ+okj+HmCL1hwJs9DfmZg7WyRn3KH9VihG2O4= github.com/cloudevents/sdk-go v0.0.0-20181211100118-3a3d34a7231e/go.mod h1:xV7GfuhjnJoK6+2MgCk3kfkoO4YRIuARdY3UpSwGz+U= github.com/confluentinc/confluent-kafka-go v0.11.6/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/goph/emperror v0.14.0 h1:Pfrf2wGvdHTuya8Ajm6KI1zcZsVhkbZnc5IGsnIr378= github.com/goph/emperror v0.14.0/go.mod h1:vakOpsf2BTE0/C0snxzXm5/l8pv6qjBhIqm/qlgDYC8= github.com/goph/logur v0.5.0 h1:eT2w3qegvJRtvKjMb/BHQAjyioKO9GDeYgyJayQBAJ8= github.com/goph/logur v0.5.0/go.mod h1:12WYraXUaqPchdbrHQj9y9wneP4L9PDiesLJGFF3Ox4= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gosimple/slug v1.7.0 h1:BlCZq+BMGn+riOZuRKnm60Fe7+jX9ck6TzzkN1r8TW8= github.com/gosimple/slug v1.7.0/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE= github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8= github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU= github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= github.com/hashicorp/vault/sdk v0.1.13 h1:mOEPeOhT7jl0J4AMl1E705+BcmeRs1VmKNb9F0sMLy8= github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jinzhu/gorm v1.9.10 h1:HvrsqdhCW78xpJF67g1hMxS6eCToo9PZH4LDB8WKPac= github.com/jinzhu/gorm v1.9.10/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/qor/qor v0.0.0-20190319081902-186b0237364b h1:0z+LJ7Efz/a+SYR2Wr/CfSyLuFgzSVVFyu9hqGEY74Y= github.com/qor/qor v0.0.0-20190319081902-186b0237364b/go.mod h1:oG+LgDEnsI9avcFFdczoZnBe3rw42s4cG433w6XpEig= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rs/zerolog v1.11.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68= golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.0.0-20181213150558-05914d821849/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= k8s.io/api v0.0.0-20190820101039-d651a1528133/go.mod h1:AlhL1I0Xqh5Tyz0HsxjEhy+iKci9l1Qy3UMDFW7iG3A= k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93 h1:tT6oQBi0qwLbbZSfDkdIsb23EwaLY85hoAV4SpXfdao= k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= k8s.io/apimachinery v0.0.0-20190820100750-21ddcbbef9e1/go.mod h1:EZoIMuAgG/4v58YL+bz0kqnivqupk28fKYxFCa5e6X8= k8s.io/apimachinery v0.0.0-20190823012420-8ca64af22337 h1:fmhgJs4JGkYIBji/BkYrFgdNIIRB/WIyA9Mw+DL+kMQ= k8s.io/apimachinery v0.0.0-20190823012420-8ca64af22337/go.mod h1:EZoIMuAgG/4v58YL+bz0kqnivqupk28fKYxFCa5e6X8= k8s.io/client-go v2.0.0-alpha.0.0.20181213151034-8d9ed539ba31+incompatible h1:R1v0j9UjjxL/TkPCMtyXtC7ECvD7bxoe2/8GD3MitMU= k8s.io/client-go v2.0.0-alpha.0.0.20181213151034-8d9ed539ba31+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ= k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= k8s.io/utils v0.0.0-20190809000727-6c36bc71fc4a h1:uy5HAgt4Ha5rEMbhZA+aM1j2cq5LmR6LQ71EYC2sVH4= k8s.io/utils v0.0.0-20190809000727-6c36bc71fc4a/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= ================================================ FILE: internal/ce/ce.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ce import ( ce "github.com/cloudevents/sdk-go/v02" "github.com/spf13/cast" ) // Event describes a wrapped CloudEvent type Event struct { ce.Event } // GetExtensions gets extention values by `eventType` func (e Event) GetExtensions() map[string]string { t, ok := e.GetString("eventType") if !ok { return nil } switch t { case "prometheus": return e.getExtensionsForPrometheusAlert() } return nil } func (e Event) getExtensionsForPrometheusAlert() map[string]string { if l, ok := e.Get("labels"); ok { return cast.ToStringMapString(l) } return nil } ================================================ FILE: internal/flows/config.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package flows import ( "time" "github.com/goph/emperror" "github.com/pkg/errors" "github.com/banzaicloud/hollowtrees/internal/plugin" ) // FlowConfig holds configuration values for an action flow type FlowConfig struct { Name string `mapstructure:"name"` Plugins []string `mapstructure:"plugins"` Description string `mapstructure:"description"` AllowedEvents []string `mapstructure:"allowedEvents"` GroupBy []string `mapstructure:"groupBy"` Filters map[string]string `mapstructure:"filters"` Cooldown time.Duration `mapstructure:"cooldown"` } type FlowConfigs map[string]FlowConfig // Validate validates flow configuration func (c FlowConfig) Validate(plugins plugin.PluginManager, id string) error { if c.Name == "" { return errors.New("name must be set") } if len(c.Plugins) == 0 { return emperror.WrapWith(errors.New("no plugins defined"), "invalid flow config", "flow", id) } _, err := plugins.GetByNames(c.Plugins...) if err != nil { return emperror.WrapWith(err, "invalid flow", "flow", id) } return nil } ================================================ FILE: internal/flows/event_dispatcher.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package flows const ( CEIncomingTopic = "cloud.events.incoming" ) type baseEventSubscriber interface { SubscribeAsync(topic string, fn interface{}, transactional bool) error } type eventSubscriber interface { SubscribeAsync(topic string, flow ActionFlow) error } type flowEventDispatcher interface { eventSubscriber } type eventDispatcher struct { eb baseEventSubscriber } // NewEventDispatcher returns an initialized eventDispatcher func NewEventDispatcher(eb baseEventSubscriber) flowEventDispatcher { return &eventDispatcher{ eb: eb, } } // SubscribeAsync implements interface func func (b *eventDispatcher) SubscribeAsync(topic string, flow ActionFlow) error { return b.eb.SubscribeAsync(topic, flow.Handle, false) } ================================================ FILE: internal/flows/eventflow.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package flows import ( "time" "github.com/banzaicloud/hollowtrees/internal/ce" ) const ( EventFlowCompleted EventFlowStatus = "completed" EventFlowFailed EventFlowStatus = "failed" EventFlowInProgress EventFlowStatus = "inprogress" EventFlowInitialized EventFlowStatus = "initialized" EventFlowCoolingDown EventFlowStatus = "coolingdown" ) type EventFlowStatus string // EventFlow is an actual sequential executing of defined plugins // for a particular event and a defined action flow type EventFlow struct { Status EventFlowStatus Error error flow *Flow event *ce.Event } // NewEventFlow returns an initialized EventFlow func NewEventFlow(flow *Flow, event *ce.Event) *EventFlow { return &EventFlow{ Status: EventFlowInitialized, flow: flow, event: event, } } // Exec executes the defined plugins sequentially func (ef *EventFlow) Exec() error { ef.Status = EventFlowInProgress plugins, err := ef.flow.manager.Plugins().GetByNames(ef.flow.plugins...) if err != nil { ef.Status = EventFlowFailed ef.Error = err return err } for _, plugin := range plugins { err := plugin.Handle(ef.event) if err != nil { ef.flow.manager.ErrorHandler().Handle(err) } } ef.Status = EventFlowCoolingDown time.Sleep(ef.flow.cooldown) ef.Status = EventFlowCompleted return nil } ================================================ FILE: internal/flows/flows.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package flows import ( "path" "time" "github.com/goph/emperror" "github.com/goph/logur" "github.com/pkg/errors" "github.com/banzaicloud/hollowtrees/internal/ce" ) // ActionFlow defines an action flow type ActionFlow interface { Handle(event interface{}) } // Flow describes an action flow type Flow struct { id string name string description string allowedEvents []string cooldown time.Duration groupBy []string plugins []string filters map[string]string cache FlowStore manager FlowManager } // NewFlow returns an initialized action flow func NewFlow(manager FlowManager, cache FlowStore, id string, name string, opts ...Option) *Flow { f := &Flow{ id: id, name: name, manager: manager, cache: cache, } for _, o := range opts { o.apply(f) } return f } // Handle handles the event by starting and event flow which executes the defined plugins func (f *Flow) Handle(event interface{}) { e, ok := event.(*ce.Event) if !ok { f.manager.ErrorHandler().Handle(emperror.With(errors.Errorf("invalid event value: %#v", event))) return } err := f.handleEvent(e) if err != nil { f.manager.ErrorHandler().Handle(emperror.WrapWith(err, "could not handle event", "type", e.Type, "id", e.ID, "flow", f.name)) } } func (f *Flow) handleEvent(event *ce.Event) error { key := f.getEventKey(event, f.groupBy) cid, _ := event.GetString("correlationid") log := f.manager.Logger().WithFields(logur.Fields{ "correlation-id": cid, "event-id": event.ID, "flow-id": f.id, "type": event.Type, "group-key": key, }) if !f.isEventTypeAllowed(event.Type) { log.Debug("skip flow - disallowed event type") return nil } if !f.isEventMatched(event) { log.Debug("skip flow - filter does not match") return nil } ef, created, err := f.createOrGetEventFlow(event, key) if err != nil { return err } if created { log.Debugf("executing event flow - %s", ef.Status) err = ef.Exec() if err != nil { return err } f.cache.Delete(key) } return nil } func (f *Flow) createOrGetEventFlow(event *ce.Event, key string) (*EventFlow, bool, error) { // f.mux.Lock() created := false ef, err := f.cache.Get(key) if err != nil { return nil, created, err } if ef != nil && ef.Status == EventFlowCompleted { ef = nil } if ef == nil { ef = NewEventFlow(f, event) err := f.cache.Set(key, ef, f.cooldown+time.Duration(5)*time.Minute) if err != nil { return nil, created, err } created = true } return ef, created, nil } func (f *Flow) getEventKey(event *ce.Event, groupBy []string) string { key := event.Type grouped := false for _, g := range groupBy { if s, ok := event.GetString(g); ok { grouped = true key = path.Join(key, s) } } if !grouped { return path.Join(key, event.ID) } return key } func (f *Flow) isEventMatched(event *ce.Event) bool { if len(f.filters) == 0 { return true } for key, value := range f.filters { if v, ok := event.GetString(key); !ok || v != value { return false } } return true } func (f *Flow) isEventTypeAllowed(eventType string) bool { if len(f.allowedEvents) == 0 { return true } for _, t := range f.allowedEvents { if t == eventType { return true } } return false } ================================================ FILE: internal/flows/manager.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package flows import ( "github.com/goph/emperror" "github.com/spf13/viper" "github.com/banzaicloud/hollowtrees/internal/platform/log" "github.com/banzaicloud/hollowtrees/internal/plugin" ) // FlowManager is used for managing action flows type FlowManager interface { Logger() log.Logger ErrorHandler() emperror.Handler Plugins() plugin.PluginManager } // Manager describes a FlowManager implementation type Manager struct { logger log.Logger errorHandler emperror.Handler dispatcher eventSubscriber plugins plugin.PluginManager } // NewManager returns an initialized FlowManager implementation func NewManager(logger log.Logger, errorHandler emperror.Handler, dispatcher flowEventDispatcher, plugins plugin.PluginManager) *Manager { return &Manager{ logger: logger, errorHandler: errorHandler, dispatcher: dispatcher, plugins: plugins, } } // Logger returns the logger func (m *Manager) Logger() log.Logger { return m.logger } // ErrorHandler returns the error handler func (m *Manager) ErrorHandler() emperror.Handler { return m.errorHandler } // Plugins returns the plugin manager func (m *Manager) Plugins() plugin.PluginManager { return m.plugins } // LoadFlows loads flow definitions from config, initializes Flows // and subscribes them to the event dispatcher func (m *Manager) LoadFlows(v *viper.Viper) error { var flows FlowConfigs err := viper.UnmarshalKey("flows", &flows) if err != nil { return emperror.Wrap(err, "could not unmarshal flow configs") } for id, config := range flows { err := config.Validate(m.plugins, id) if err != nil { return emperror.WrapWith(err, "could not load flow", "flow", id) } f := NewFlow(m, NewInMemFlowStore(), id, config.Name, Description(config.Description), AllowedEvents(config.AllowedEvents), Cooldown(config.Cooldown), GroupBy(config.GroupBy), Plugins(config.Plugins), Filters(config.Filters), ) err = m.dispatcher.SubscribeAsync(CEIncomingTopic, f) if err != nil { m.errorHandler.Handle(emperror.WrapWith(err, "could not subscribe to event dispatcher", "flow", id)) } } return nil } ================================================ FILE: internal/flows/option.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package flows import "time" // Option sets configuration on the Flow type Option interface { apply(*Flow) } // Cooldown time that passes after an event flow is successfully finished type Cooldown time.Duration func (o Cooldown) apply(f *Flow) { f.cooldown = time.Duration(o) } // AllowedEvents defines allowed event types for the flow type AllowedEvents []string func (o AllowedEvents) apply(f *Flow) { f.allowedEvents = []string(o) } // GroupBy categorizes subsequent events as the same if all the corresponding values of these attributes match type GroupBy []string func (o GroupBy) apply(f *Flow) { f.groupBy = []string(o) } // Plugins defines the plugins to execute in an event flow type Plugins []string func (o Plugins) apply(f *Flow) { f.plugins = []string(o) } // Filters defines simple filter on event values type Filters map[string]string func (o Filters) apply(f *Flow) { f.filters = map[string]string(o) } // Description sets the description of the action flow type Description string func (o Description) apply(f *Flow) { f.description = string(o) } ================================================ FILE: internal/flows/store.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package flows import ( "time" cache "github.com/patrickmn/go-cache" ) type FlowStore interface { Get(string) (*EventFlow, error) Set(string, *EventFlow, time.Duration) error Delete(string) } type InMemoryFlowStore struct { EventFlowCache *cache.Cache } func NewInMemFlowStore() *InMemoryFlowStore { return &InMemoryFlowStore{ EventFlowCache: cache.New(time.Duration(10)*time.Minute, time.Duration(1)*time.Minute), } } func (i *InMemoryFlowStore) Get(key string) (*EventFlow, error) { a, ok := i.EventFlowCache.Get(key) if a != nil && ok { ef := a.(*EventFlow) return ef, nil } else { return nil, nil } } func (i *InMemoryFlowStore) Set(key string, ef *EventFlow, ttl time.Duration) error { i.EventFlowCache.Set(key, ef, ttl) return nil } func (i *InMemoryFlowStore) Delete(key string) { i.EventFlowCache.Delete(key) } ================================================ FILE: internal/platform/config/config.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "fmt" "os" "strings" "github.com/goph/emperror" "github.com/spf13/pflag" "github.com/spf13/viper" "github.com/banzaicloud/hollowtrees/internal/platform/healthcheck" "github.com/banzaicloud/hollowtrees/internal/platform/log" "github.com/banzaicloud/hollowtrees/internal/promalert" ) const ( // ServiceName is an identifier-like name used anywhere this app needs to be identified. ServiceName = "hollowtrees" // FriendlyServiceName is the visible name of the service. FriendlyServiceName = "Hollowtrees" // ConfigEnvPrefix defines the prefix that ENVIRONMENT variables will use ConfigEnvPrefix = "HT" ) // Config holds any kind of configuration that comes from the outside world and // is necessary for running the application type Config struct { // Meaningful values are recommended (eg. production, development, staging, release/123, etc) Environment string // Turns on some debug functionality (eg. more verbose logs) Debug bool // Log configuration Log log.Config // Healthcheck configuration Healthcheck healthcheck.Config // Prometheus alert handler configuration Promalert promalert.Config } // Validate validates the configuration func (c Config) Validate() error { err := c.Log.Validate() if err != nil { return emperror.Wrap(err, "could not validate log config") } err = c.Promalert.Validate() if err != nil { return emperror.Wrap(err, "could not validate promalert config") } err = c.Healthcheck.Validate() if err != nil { return emperror.Wrap(err, "could not validate healthcheck config") } return nil } // Configure configures some defaults in the Viper instance func Configure(v *viper.Viper, p *pflag.FlagSet) { v.AddConfigPath(".") v.AddConfigPath("$HOME/config") p.Init(FriendlyServiceName, pflag.ExitOnError) pflag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", FriendlyServiceName) pflag.PrintDefaults() } v.BindPFlags(p) // nolint:errcheck v.SetEnvPrefix(ConfigEnvPrefix) v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.AutomaticEnv() // Log configuration v.SetDefault("log.format", "logfmt") v.SetDefault("log.level", "info") // Healthcheck HTTP endpoint v.SetDefault("healthcheck.listenAddress", ":8082") v.SetDefault("healthcheck.endpoint", "/healthz") // Prometheus alert handler v.SetDefault("promalert.listenAddress", ":8081") v.SetDefault("promalert.useJWTAuth", false) v.SetDefault("promalert.jwtSigningKey", "") } ================================================ FILE: internal/platform/config/error_handler.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "fmt" "sync" "github.com/goph/emperror" "github.com/banzaicloud/hollowtrees/internal/platform/errors" "github.com/banzaicloud/hollowtrees/internal/platform/log" ) var errorHandler emperror.Handler var errorHandlerOnce sync.Once // ErrorHandler returns an error handler. func ErrorHandler(logger log.Logger) emperror.Handler { errorHandlerOnce.Do(func() { errorHandler = newErrorHandler(logger) }) return errorHandler } func newErrorHandler(logger log.Logger) emperror.Handler { loggerHandler := errors.NewHandler(logger) return emperror.HandlerFunc(func(err error) { if stackTrace, ok := emperror.StackTrace(err); ok && len(stackTrace) > 0 { frame := stackTrace[0] err = emperror.With( err, "func", fmt.Sprintf("%n", frame), // nolint: govet "file", fmt.Sprintf("%v", frame), // nolint: govet ) } loggerHandler.Handle(err) }) } ================================================ FILE: internal/platform/errors/handler.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package errors import ( "github.com/goph/emperror" "github.com/banzaicloud/hollowtrees/internal/platform/log" ) type handler struct { logger log.Logger } // NewHandler returns a handler which logs errors using the platform logger func NewHandler(logger log.Logger) *handler { return &handler{logger: logger} } // Handle logs an error func (h *handler) Handle(err error) { var ctx map[string]interface{} // Extract context from the error and attach it to the log if kvs := emperror.Context(err); len(kvs) > 0 { ctx = ToMap(kvs) } logger := h.logger.WithFields(log.Fields(ctx)) type errorCollection interface { Errors() []error } if errs, ok := err.(errorCollection); ok { for _, e := range errs.Errors() { ctx = nil // Extract context from the error and attach it to the log if kvs := emperror.Context(e); len(kvs) > 0 { ctx = ToMap(kvs) } h.logger.WithFields(log.Fields(ctx)).Error(e.Error()) } } else { logger.Error(err.Error()) } } ================================================ FILE: internal/platform/errors/keyvals.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package errors import ( "fmt" "reflect" ) // ToMap creates a map of key-value pairs. // // The implementation bellow is from go-kit's JSON logger. func ToMap(keyvals []interface{}) map[string]interface{} { m := map[string]interface{}{} if len(keyvals) == 0 { return m } if len(keyvals)%2 == 1 { keyvals = append(keyvals, nil) } for i := 0; i < len(keyvals); i += 2 { merge(m, keyvals[i], keyvals[i+1]) } return m } func merge(dst map[string]interface{}, k, v interface{}) { var key string switch x := k.(type) { case string: key = x case fmt.Stringer: key = safeString(x) default: key = fmt.Sprint(x) } switch x := v.(type) { case error: v = safeError(x) case fmt.Stringer: v = safeString(x) } dst[key] = v } func safeString(str fmt.Stringer) (s string) { defer func() { if panicVal := recover(); panicVal != nil { if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { s = "NULL" } else { panic(panicVal) } } }() s = str.String() return } func safeError(err error) (s interface{}) { defer func() { if panicVal := recover(); panicVal != nil { if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { s = nil } else { panic(panicVal) } } }() s = err.Error() return } ================================================ FILE: internal/platform/gin/correlationid/logger.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package correlationid import ( "github.com/gin-gonic/gin" "github.com/banzaicloud/hollowtrees/internal/platform/log" ) const correlationIdField = "correlation-id" // Logger returns a new logger instance with a correlation ID in it. func Logger(logger log.Logger, ctx *gin.Context) log.Logger { cid := ctx.GetString(ContextKey) if cid == "" { return logger } return logger.WithField(correlationIdField, cid) } ================================================ FILE: internal/platform/gin/correlationid/middleware.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package correlationid import ( "github.com/gin-gonic/gin" "github.com/satori/go.uuid" ) // ContextKey is the key the retrieved (or generated) correlation ID is stored under in the gin Context. const ContextKey = "correlationid" // Default correlation ID header const defaultHeader = "Correlation-ID" // MiddlewareOption configures the correlation ID middleware. type MiddlewareOption interface { apply(*middleware) } // Header configures the header from where the correlation ID will be retrieved. type Header string // apply implements the MiddlewareOption interface. func (h Header) apply(m *middleware) { m.header = string(h) } // Middleware returns a gin compatible handler. func Middleware(opts ...MiddlewareOption) gin.HandlerFunc { m := new(middleware) for _, opt := range opts { opt.apply(m) } if m.header == "" { m.header = defaultHeader } return m.Handle } type middleware struct { header string } // Handle sets correlation id func (m *middleware) Handle(ctx *gin.Context) { if header := ctx.GetHeader(m.header); header != "" { ctx.Set(ContextKey, header) } else { ctx.Set(ContextKey, uuid.NewV4().String()) } ctx.Next() } ================================================ FILE: internal/platform/gin/log/middleware.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package log import ( "time" "github.com/gin-gonic/gin" "github.com/banzaicloud/hollowtrees/internal/platform/gin/correlationid" "github.com/banzaicloud/hollowtrees/internal/platform/log" ) const correlationIdField = "correlation-id" // Middleware returns a gin compatible handler func Middleware(logger log.Logger, notlogged ...string) gin.HandlerFunc { var skip map[string]struct{} if length := len(notlogged); length > 0 { skip = make(map[string]struct{}, length) for _, path := range notlogged { skip[path] = struct{}{} } } return func(c *gin.Context) { // start timer start := time.Now() // prevent middlewares from faking the request path path := c.Request.URL.Path raw := c.Request.URL.RawQuery c.Next() // Log only when path is not being skipped if _, ok := skip[path]; !ok { end := time.Now() latency := end.Sub(start) if raw != "" { path = path + "?" + raw } fields := log.Fields{ "status": c.Writer.Status(), "method": c.Request.Method, "path": path, "ip": c.ClientIP(), "latency": latency, "user-agent": c.Request.UserAgent(), } if cid := c.GetString(correlationid.ContextKey); cid != "" { fields[correlationIdField] = cid } entry := logger.WithFields(fields) if len(c.Errors) > 0 { // Append error field if this is an erroneous request. entry.Error(c.Errors.String()) } else { entry.Info() } } } } ================================================ FILE: internal/platform/healthcheck/config.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package healthcheck import "errors" type Config struct { ListenAddress string Endpoint string } // Validate checks that the configuration is valid. func (c Config) Validate() error { if c.ListenAddress == "" { return errors.New("listen address must not be empty") } if c.Endpoint == "" { return errors.New("endpoint must not be empty") } return nil } ================================================ FILE: internal/platform/healthcheck/healthcheck.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package healthcheck import ( "net/http" "github.com/gin-gonic/gin" "github.com/goph/emperror" "github.com/banzaicloud/hollowtrees/internal/platform/log" ) // New runs the health check endpoint func New(config Config, logger log.Logger, errorHandler emperror.Handler) { logger.WithFields(log.Fields{"addr": config.ListenAddress, "endpoint": config.Endpoint}).Info("starting health check http server") r := gin.New() r.GET(config.Endpoint, func(c *gin.Context) { c.String(http.StatusOK, "ok") }) err := r.Run(config.ListenAddress) if err != nil { errorHandler.Handle(err) } } ================================================ FILE: internal/platform/log/config.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package log import ( "github.com/pkg/errors" ) // Config holds details necessary for logging. type Config struct { // Format specifies the output log format. // Accepted values are: json, logfmt Format string // Level is the minimum log level that should appear on the output. Level string // NoColor makes sure that no log output gets colorized. NoColor bool } // Validate validates the configuration. func (c Config) Validate() error { if c.Format == "" { return errors.New("log format is required") } if c.Format != "json" && c.Format != "logfmt" { return errors.New("invalid log format: " + c.Format) } return nil } ================================================ FILE: internal/platform/log/logger.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package log configures a new logger for an application. package log import ( "github.com/goph/logur" ) type Logger interface { Trace(args ...interface{}) Debug(args ...interface{}) Info(args ...interface{}) Warn(args ...interface{}) Error(args ...interface{}) Traceln(args ...interface{}) Debugln(args ...interface{}) Infoln(args ...interface{}) Warnln(args ...interface{}) Errorln(args ...interface{}) Tracef(format string, args ...interface{}) Debugf(format string, args ...interface{}) Infof(format string, args ...interface{}) Warnf(format string, args ...interface{}) Errorf(format string, args ...interface{}) WithFields(fields Fields) Logger WithField(key string, value interface{}) Logger } // Fields is an alias to log.Fields for easier usage. type Fields = logur.Fields // NewLogger creates a new logger. func NewLogger(config Config) Logger { return NewLogrusLogger(config) } ================================================ FILE: internal/platform/log/logrus_adapter.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package log import ( "os" "github.com/sirupsen/logrus" ) type logrusAdapter struct { *logrus.Entry } // WithField adds a single field to the Entry func (a *logrusAdapter) WithField(key string, value interface{}) Logger { return &logrusAdapter{a.Entry.WithField(key, value)} } // WithFields returns a new logger based on the original logger with // the additional supplied fields. func (a *logrusAdapter) WithFields(fields Fields) Logger { return &logrusAdapter{a.Entry.WithFields(logrus.Fields(fields))} } func NewLogrusLogger(config Config) Logger { logger := logrus.New() logger.SetOutput(os.Stdout) logger.SetFormatter(&logrus.TextFormatter{ DisableColors: config.NoColor, EnvironmentOverrideColors: true, }) switch config.Format { case "logfmt": // Already the default case "json": logger.SetFormatter(&logrus.JSONFormatter{}) } if level, err := logrus.ParseLevel(config.Level); err == nil { logger.SetLevel(level) } return &logrusAdapter{ logrus.NewEntry(logger), } } ================================================ FILE: internal/plugin/config.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( "github.com/goph/emperror" "github.com/pkg/errors" ) // PluginConfig describes a plugin configuration type PluginConfig struct { Name string `mapstructure:"name"` Type string `mapstructure:"type"` Address string `mapstructure:"address"` } type PluginConfigs []PluginConfig // Validate validates plugin configuration func (c PluginConfig) Validate() error { if c.Name == "" { return errors.New("name must be set") } if c.Type != "grpc" { return emperror.With(errors.New("invalid plugin type"), "type", c.Type) } if c.Type == "grpc" && c.Address == "" { return errors.New("address must not be empty for a GRPC plugin") } return nil } ================================================ FILE: internal/plugin/grpc.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( "context" "google.golang.org/grpc" "github.com/banzaicloud/hollowtrees/internal/ce" "github.com/banzaicloud/hollowtrees/pkg/grpcplugin/proto" ) type grpcPlugin struct { BasePlugin address string } // NewGrpcPlugin initializes a grpcPlugin func NewGrpcPlugin(name string, address string) *grpcPlugin { return &grpcPlugin{ BasePlugin: BasePlugin{ name: name, }, address: address, } } // Handle sends the CloudEvent to a GRPC plugin endpoint func (p *grpcPlugin) Handle(event *ce.Event) error { conn, err := grpc.Dial(p.address, grpc.WithInsecure()) if err != nil { return err } defer conn.Close() j, err := event.MarshalJSON() if err != nil { return err } client := proto.NewEventHandlerClient(conn) ez := &proto.CloudEvent{ Specversion: event.SpecVersion, Type: event.Type, Source: event.Source.String(), Id: event.ID, Time: event.Time.String(), Schemaurl: event.SchemaURL.String(), Contenttype: "application/cloudevents+json", Extensions: event.GetExtensions(), Data: j, } _, err = client.Handle(context.Background(), ez) if err != nil { return err } return nil } ================================================ FILE: internal/plugin/internal.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( "github.com/banzaicloud/hollowtrees/internal/ce" "github.com/banzaicloud/hollowtrees/internal/platform/log" ) type internalPlugin struct { BasePlugin logger log.Logger } // NewInternalPlugin returns an initialized internalPlugin func NewInternalPlugin(name string, logger log.Logger) *internalPlugin { return &internalPlugin{ BasePlugin: BasePlugin{ name: name, }, logger: logger, } } // Handle handles func (p *internalPlugin) Handle(event *ce.Event) error { p.logger.Infof("internal-demo-plugin: %s", event.Type) return nil } ================================================ FILE: internal/plugin/manager.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( "errors" "github.com/goph/emperror" "github.com/spf13/viper" "github.com/banzaicloud/hollowtrees/internal/platform/log" ) // PluginManager describes what a plugin manager implementation must provide type PluginManager interface { Add(plugin ...EventHandlerPlugin) GetByNames(names ...string) (map[string]EventHandlerPlugin, error) GetByName(name string) (EventHandlerPlugin, error) } // Manager is a PluginManager implementation type Manager struct { logger log.Logger errorHandler emperror.Handler plugins map[string]EventHandlerPlugin } // NewManager returns an initialized Manager func NewManager(logger log.Logger, errorHandler emperror.Handler) *Manager { m := &Manager{ logger: logger, errorHandler: errorHandler, } plugins := make(map[string]EventHandlerPlugin) m.plugins = plugins return m } // Add add an initialized plugin func (m *Manager) Add(plugins ...EventHandlerPlugin) { for _, plugin := range plugins { m.plugins[plugin.GetName()] = plugin } } // GetByNames returns a map of plugins by their names func (m *Manager) GetByNames(names ...string) (map[string]EventHandlerPlugin, error) { plugins := make(map[string]EventHandlerPlugin) for _, name := range names { p, err := m.GetByName(name) if err != nil { return nil, err } plugins[p.GetName()] = p } return plugins, nil } // GetByName returns a plugin by it's name func (m *Manager) GetByName(name string) (EventHandlerPlugin, error) { p := m.plugins[name] if p == nil { return nil, emperror.With(errors.New("plugin not found"), "name", name) } return p, nil } // LoadFromConfig loads plugins from configuration func (m *Manager) LoadFromConfig(v *viper.Viper) error { var plugins PluginConfigs err := viper.UnmarshalKey("plugins", &plugins) if err != nil { return emperror.Wrap(err, "could not unmarshal plugin configs") } if len(plugins) == 0 { return emperror.Wrap(err, "no plugins were defined") } for _, plugin := range plugins { err := plugin.Validate() if err != nil { return emperror.WrapWith(err, "invalid plugin configuration", "plugin", plugin.Name) } switch plugin.Type { case "grpc": m.Add(NewGrpcPlugin(plugin.Name, plugin.Address)) } } return nil } ================================================ FILE: internal/plugin/plugin.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugin import ( "github.com/banzaicloud/hollowtrees/internal/ce" ) // EventHandlerPlugin defines an event handler plugin type EventHandlerPlugin interface { GetName() string Handle(event *ce.Event) error } // BasePlugin describes a basic plugin struct type BasePlugin struct { name string } // GetName returns the plugin name func (p *BasePlugin) GetName() string { return p.name } ================================================ FILE: internal/promalert/alert.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package promalert import ( "fmt" "net/url" "time" "github.com/pkg/errors" uuid "github.com/satori/go.uuid" "gopkg.in/go-playground/validator.v9" "github.com/banzaicloud/hollowtrees/internal/ce" "github.com/banzaicloud/hollowtrees/pkg/auth" ) type Alerts []Alert func (alerts Alerts) Validate() error { for _, alert := range alerts { if alert.Labels["cluster_id"] == "" { return errors.New("invalid alert: mandatory 'cluster_id' parameter is missing") } if alert.Labels["org_id"] == "" { return errors.New("invalid alert: mandatory 'org_id' parameter is missing") } err := validator.New().Struct(alert) if err != nil { return err } } return nil } func (alerts Alerts) Authorize(user *auth.User) error { for _, alert := range alerts { if alert.Labels["cluster_id"] == "" || alert.Labels["org_id"] == "" || alert.Labels["cluster_id"] != user.ClusterID || alert.Labels["org_id"] != user.OrgID { return errors.Errorf("invalid alert: unauthorized") } } return nil } // Alert describes an incoming Prometheus alert type Alert struct { Labels map[string]string `json:"labels"` Annotations map[string]string `json:"annotations"` StartsAt time.Time `json:"startsAt"` EndsAt time.Time `json:"endsAt"` GeneratorURL string `json:"generatorURL" validate:"url"` } // convertToCE converts incoming prometheus alert struct to CloudEvent struct func (a *Alert) convertToCE(cid string) (*ce.Event, error) { e := &ce.Event{} for k, v := range a.Labels { e.Set(k, v) } e.Set("correlationid", cid) e.Set("labels", a.Labels) e.Set("id", uuid.NewV4().String()) e.Set("type", fmt.Sprintf("%s%s", CETypePrefix, a.Labels["alertname"])) e.Set("specversion", "0.2") u, err := url.Parse(a.GeneratorURL) if err != nil { return nil, err } e.Set("source", *u) e.Set("time", &a.StartsAt) e.Set("eventType", "prometheus") return e, nil } ================================================ FILE: internal/promalert/config.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package promalert import "github.com/pkg/errors" type Config struct { // HTTP listen address ListenAddress string // JWT auth UseJWTAuth bool // JWT signing key JWTSigningKey string } // Validate checks that the configuration is valid. func (c Config) Validate() error { if c.ListenAddress == "" { return errors.New("listen address must not be empty") } if c.UseJWTAuth && c.JWTSigningKey == "" { return errors.New("JWTSigningKey must be set if JWT auth is enabled") } return nil } ================================================ FILE: internal/promalert/event_dispatcher.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package promalert import ( "github.com/banzaicloud/hollowtrees/internal/ce" ) type baseEventPublisher interface { Publish(topic string, args ...interface{}) } type eventDispatcher struct { eb baseEventPublisher } type eventPublisher interface { Publish(topic string, event *ce.Event) } // NewEventDispatcher returns a new event dispatcher func NewEventDispatcher(eb baseEventPublisher) *eventDispatcher { return &eventDispatcher{ eb: eb, } } // Publish sends the given event through the event dispatcher func (b *eventDispatcher) Publish(topic string, event *ce.Event) { b.eb.Publish(topic, event) } ================================================ FILE: internal/promalert/promalert.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package promalert import ( "net/http" "github.com/gin-gonic/gin" "github.com/goph/emperror" "github.com/banzaicloud/hollowtrees/internal/platform/gin/correlationid" ginlog "github.com/banzaicloud/hollowtrees/internal/platform/gin/log" "github.com/banzaicloud/hollowtrees/internal/platform/log" "github.com/banzaicloud/hollowtrees/pkg/auth" ) const ( EventTopic = "cloud.events.incoming" CETypePrefix = "prometheus.server.alert." ) // PromAlertHandler describes a Prometheus alert handler type PromAlertHandler struct { useJWTAuth bool jwtSigningKey string listenAddress string logger log.Logger errorHandler emperror.Handler eb eventPublisher } // New returns an initialized PromAlertHandler func New(config Config, logger log.Logger, errorHandler emperror.Handler, eb eventPublisher) *PromAlertHandler { return &PromAlertHandler{ useJWTAuth: config.UseJWTAuth, jwtSigningKey: config.JWTSigningKey, listenAddress: config.ListenAddress, logger: logger, errorHandler: errorHandler, eb: eb, } } // Run runs the alert handler HTTP listener func (p *PromAlertHandler) Run() { p.logger.WithField("addr", p.listenAddress).WithField("useJWTAuth", p.useJWTAuth).Info("starting prometheus alert handler") r := gin.New() r.Use(gin.Recovery()) r.Use(correlationid.Middleware()) r.Use(ginlog.Middleware(p.logger)) if p.useJWTAuth { r.Use(auth.Handler(p.jwtSigningKey)) } r.POST("/api/v1/alerts", p.handle) err := r.Run(p.listenAddress) if err != nil { p.errorHandler.Handle(err) } } // handle handles the incoming HTTP request func (p *PromAlertHandler) handle(c *gin.Context) { var alerts Alerts log := correlationid.Logger(p.logger, c) if err := c.ShouldBindJSON(&alerts); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ "status": http.StatusBadRequest, "message": "failed to process alerts", "error": err.Error(), }) return } if err := alerts.Validate(); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ "status": http.StatusBadRequest, "message": "invalid alert", "error": err.Error(), }) return } if p.useJWTAuth { if err := alerts.Authorize(auth.GetCurrentUser(c)); err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "status": http.StatusUnauthorized, "message": "could not process alerts", "error": err.Error(), }) return } } log.WithField("alert-count", len(alerts)).Debug("alerts received") cid := c.GetString(correlationid.ContextKey) p.publishAlerts(alerts, cid) c.JSON(http.StatusOK, gin.H{ "status": http.StatusOK, "data": "ok", }) } // publishAlerts publishing incoming alerts through the event dispatcher func (p *PromAlertHandler) publishAlerts(alerts []Alert, cid string) { for _, alert := range alerts { event, err := alert.convertToCE(cid) if err != nil { p.errorHandler.Handle(err) continue } p.eb.Publish(EventTopic, event) } } ================================================ FILE: pkg/auth/auth.go ================================================ // Copyright © 2019 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package auth import ( "encoding/base32" "fmt" "strings" "time" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/gofrs/uuid" "github.com/pkg/errors" bauth "github.com/banzaicloud/bank-vaults/pkg/sdk/auth" ) type User struct { ClusterID string `json:"clusterID"` OrgID string `json:"orgID"` } type TokenGenerator interface { Generate(userID, orgID uint, expiresAt *time.Time) (string, string, error) } type tokenGenerator struct { Issuer string Audience string SigningKey string } func NewTokenGenerator(issuer, audience, signingKey string) TokenGenerator { return &tokenGenerator{ Issuer: issuer, Audience: audience, SigningKey: signingKey, } } func (g *tokenGenerator) Generate(userID, orgID uint, expiresAt *time.Time) (string, string, error) { tokenID := uuid.Must(uuid.NewV4()).String() var expiresAtUnix int64 if expiresAt != nil { expiresAtUnix = expiresAt.Unix() } // Create the Claims claims := &bauth.ScopedClaims{ StandardClaims: jwt.StandardClaims{ Issuer: g.Issuer, Audience: g.Audience, IssuedAt: jwt.TimeFunc().Unix(), ExpiresAt: expiresAtUnix, Subject: fmt.Sprintf("clusters/%d/%d", orgID, userID), Id: tokenID, }, Scope: "api:invoke", } jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) if g.SigningKey == "" { return "", "", errors.New("missing signingKeyBase32") } signedToken, err := jwtToken.SignedString([]byte(base32.StdEncoding.EncodeToString([]byte(g.SigningKey)))) if err != nil { return "", "", errors.Wrap(err, "failed to sign user token") } return tokenID, signedToken, nil } func GetCurrentUser(c *gin.Context) *User { if u, ok := bauth.GetCurrentUser(c).(*User); ok { return u } return nil } func Handler(signingKey string) gin.HandlerFunc { return bauth.JWTAuth(nil, signingKey, claimConverter) } func claimConverter(claims *bauth.ScopedClaims) interface{} { if !strings.HasPrefix(claims.Subject, "clusters/") { return nil } segments := strings.Split(claims.Subject, "/") if len(segments) < 2 { return nil } return &User{ ClusterID: segments[2], OrgID: segments[1], } } ================================================ FILE: pkg/grpcplugin/handler.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package grpcplugin import ( "context" "github.com/banzaicloud/hollowtrees/pkg/grpcplugin/proto" "github.com/goph/emperror" ) // EventHandler should be implemented by the plugins that are doing some actions based on alerts type EventHandler interface { Handle(*CloudEvent) (*Result, error) } type CloudEvent proto.CloudEvent type Result proto.Result type handler struct { EventHandler EventHandler } // NewHandler returns an initialized handler func NewHandler(eh EventHandler) *handler { return &handler{ EventHandler: eh, } } // Handle converts and passes the incoming event to the defined event handler func (h *handler) Handle(ctx context.Context, ce *proto.CloudEvent) (*proto.Result, error) { var e = CloudEvent{ Specversion: ce.Specversion, Type: ce.Type, Source: ce.Source, Id: ce.Id, Time: ce.Time, Contenttype: ce.Contenttype, Data: ce.Data, } result, err := h.EventHandler.Handle(&e) if err != nil { return nil, emperror.Wrap(err, "could not handle event") } return &proto.Result{ Status: result.Status, }, nil } ================================================ FILE: pkg/grpcplugin/proto/event.pb.go ================================================ // Code generated by protoc-gen-go. // source: event.proto // DO NOT EDIT! /* Package proto is a generated protocol buffer package. It is generated from these files: event.proto It has these top-level messages: CloudEvent Result */ package proto import proto1 "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" import ( context "golang.org/x/net/context" grpc "google.golang.org/grpc" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto1.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto1.ProtoPackageIsVersion2 // please upgrade the proto package type CloudEvent struct { Specversion string `protobuf:"bytes,1,opt,name=specversion" json:"specversion,omitempty"` Type string `protobuf:"bytes,2,opt,name=type" json:"type,omitempty"` Source string `protobuf:"bytes,3,opt,name=source" json:"source,omitempty"` Id string `protobuf:"bytes,4,opt,name=id" json:"id,omitempty"` Time string `protobuf:"bytes,5,opt,name=time" json:"time,omitempty"` Schemaurl string `protobuf:"bytes,6,opt,name=schemaurl" json:"schemaurl,omitempty"` Contenttype string `protobuf:"bytes,7,opt,name=contenttype" json:"contenttype,omitempty"` Data []byte `protobuf:"bytes,8,opt,name=data,proto3" json:"data,omitempty"` Extensions 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"` } func (m *CloudEvent) Reset() { *m = CloudEvent{} } func (m *CloudEvent) String() string { return proto1.CompactTextString(m) } func (*CloudEvent) ProtoMessage() {} func (*CloudEvent) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } func (m *CloudEvent) GetSpecversion() string { if m != nil { return m.Specversion } return "" } func (m *CloudEvent) GetType() string { if m != nil { return m.Type } return "" } func (m *CloudEvent) GetSource() string { if m != nil { return m.Source } return "" } func (m *CloudEvent) GetId() string { if m != nil { return m.Id } return "" } func (m *CloudEvent) GetTime() string { if m != nil { return m.Time } return "" } func (m *CloudEvent) GetSchemaurl() string { if m != nil { return m.Schemaurl } return "" } func (m *CloudEvent) GetContenttype() string { if m != nil { return m.Contenttype } return "" } func (m *CloudEvent) GetData() []byte { if m != nil { return m.Data } return nil } func (m *CloudEvent) GetExtensions() map[string]string { if m != nil { return m.Extensions } return nil } type Result struct { Status string `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` } func (m *Result) Reset() { *m = Result{} } func (m *Result) String() string { return proto1.CompactTextString(m) } func (*Result) ProtoMessage() {} func (*Result) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } func (m *Result) GetStatus() string { if m != nil { return m.Status } return "" } func init() { proto1.RegisterType((*CloudEvent)(nil), "proto.CloudEvent") proto1.RegisterType((*Result)(nil), "proto.Result") } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion4 // Client API for EventHandler service type EventHandlerClient interface { Handle(ctx context.Context, in *CloudEvent, opts ...grpc.CallOption) (*Result, error) } type eventHandlerClient struct { cc *grpc.ClientConn } func NewEventHandlerClient(cc *grpc.ClientConn) EventHandlerClient { return &eventHandlerClient{cc} } func (c *eventHandlerClient) Handle(ctx context.Context, in *CloudEvent, opts ...grpc.CallOption) (*Result, error) { out := new(Result) err := grpc.Invoke(ctx, "/proto.EventHandler/Handle", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } // Server API for EventHandler service type EventHandlerServer interface { Handle(context.Context, *CloudEvent) (*Result, error) } func RegisterEventHandlerServer(s *grpc.Server, srv EventHandlerServer) { s.RegisterService(&_EventHandler_serviceDesc, srv) } func _EventHandler_Handle_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CloudEvent) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(EventHandlerServer).Handle(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/proto.EventHandler/Handle", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(EventHandlerServer).Handle(ctx, req.(*CloudEvent)) } return interceptor(ctx, in, info, handler) } var _EventHandler_serviceDesc = grpc.ServiceDesc{ ServiceName: "proto.EventHandler", HandlerType: (*EventHandlerServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Handle", Handler: _EventHandler_Handle_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "event.proto", } func init() { proto1.RegisterFile("event.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ // 330 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x50, 0x41, 0x4f, 0xf2, 0x40, 0x10, 0xfd, 0x5a, 0xa0, 0x9f, 0x0c, 0xa8, 0xb8, 0x31, 0x66, 0x43, 0x3c, 0x54, 0x2e, 0x72, 0x30, 0x3d, 0xe0, 0xc5, 0x18, 0x39, 0x88, 0x21, 0xe1, 0x48, 0xfa, 0x0f, 0x96, 0x76, 0x12, 0x1a, 0xb7, 0xbb, 0x64, 0x77, 0x8b, 0xe2, 0xef, 0xf4, 0x07, 0x99, 0x9d, 0x56, 0x69, 0xf4, 0xd4, 0x79, 0xef, 0xed, 0x7b, 0x7d, 0x33, 0x30, 0xc0, 0x3d, 0x2a, 0x97, 0xec, 0x8c, 0x76, 0x9a, 0xf5, 0xe8, 0x33, 0xf9, 0x0c, 0x01, 0x5e, 0xa4, 0xae, 0xf2, 0xa5, 0xd7, 0x58, 0x0c, 0x03, 0xbb, 0xc3, 0x6c, 0x8f, 0xc6, 0x16, 0x5a, 0xf1, 0x20, 0x0e, 0xa6, 0xfd, 0xb4, 0x4d, 0x31, 0x06, 0x5d, 0x77, 0xd8, 0x21, 0x0f, 0x49, 0xa2, 0x99, 0x5d, 0x41, 0x64, 0x75, 0x65, 0x32, 0xe4, 0x1d, 0x62, 0x1b, 0xc4, 0xce, 0x20, 0x2c, 0x72, 0xde, 0x25, 0x2e, 0x2c, 0x72, 0xf2, 0x16, 0x25, 0xf2, 0x5e, 0xe3, 0x2d, 0x4a, 0x64, 0xd7, 0xd0, 0xb7, 0xd9, 0x16, 0x4b, 0x51, 0x19, 0xc9, 0x23, 0x12, 0x8e, 0x84, 0xef, 0x93, 0x69, 0xe5, 0x50, 0x39, 0xfa, 0xe9, 0xff, 0xba, 0x4f, 0x8b, 0xf2, 0x99, 0xb9, 0x70, 0x82, 0x9f, 0xc4, 0xc1, 0x74, 0x98, 0xd2, 0xcc, 0x9e, 0x01, 0xf0, 0xdd, 0xa1, 0xf2, 0x85, 0x2d, 0xef, 0xc7, 0x9d, 0xe9, 0x60, 0x76, 0x53, 0xef, 0x9d, 0x1c, 0x97, 0x4d, 0x96, 0x3f, 0x6f, 0x96, 0xca, 0x99, 0x43, 0xda, 0x32, 0x8d, 0xe7, 0x70, 0xfe, 0x4b, 0x66, 0x23, 0xe8, 0xbc, 0xe2, 0xa1, 0xb9, 0x89, 0x1f, 0xd9, 0x25, 0xf4, 0xf6, 0x42, 0x56, 0xdf, 0xc7, 0xa8, 0xc1, 0x63, 0xf8, 0x10, 0x4c, 0x62, 0x88, 0x52, 0xb4, 0x95, 0x74, 0x74, 0x1b, 0x27, 0x5c, 0x65, 0x1b, 0x63, 0x83, 0x66, 0x4f, 0x30, 0xa4, 0x16, 0x2b, 0xa1, 0x72, 0x89, 0x86, 0xdd, 0x41, 0x54, 0x8f, 0xec, 0xe2, 0x4f, 0xd3, 0xf1, 0x69, 0x43, 0xd5, 0x99, 0x93, 0x7f, 0x8b, 0x39, 0xdc, 0x66, 0xba, 0x4c, 0x36, 0x42, 0x7d, 0x88, 0x22, 0xf3, 0x0f, 0x93, 0xad, 0x96, 0x52, 0xbf, 0x39, 0x83, 0x68, 0x93, 0x2d, 0x25, 0x91, 0x77, 0x31, 0x5a, 0x1d, 0xc1, 0xda, 0xa7, 0xac, 0x83, 0x4d, 0x44, 0x71, 0xf7, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x30, 0x95, 0x57, 0x04, 0x12, 0x02, 0x00, 0x00, } ================================================ FILE: pkg/grpcplugin/proto/event.proto ================================================ syntax = "proto3"; option java_multiple_files = true; option java_package = "com.banzaicloud.hollowtrees.handleEvent"; option java_outer_classname = "HandleEventProto"; package proto; service EventHandler { rpc Handle (CloudEvent) returns (Result) {} } message CloudEvent { string specversion = 1; string type = 2; string source = 3; string id = 4; string time = 5; string schemaurl = 6; string contenttype = 7; bytes data = 8; map extensions = 9; } message Result { string status = 1; } ================================================ FILE: pkg/grpcplugin/server.go ================================================ // Copyright © 2018 Banzai Cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package grpcplugin import ( "net" "github.com/goph/emperror" "google.golang.org/grpc" "github.com/banzaicloud/hollowtrees/pkg/grpcplugin/proto" ) // Serve registers the EventHandler and starts the GRPC server func Serve(bindAddress string, handler EventHandler, opt ...grpc.ServerOption) error { listener, err := net.Listen("tcp", bindAddress) if err != nil { return emperror.Wrap(err, "failed to listen") } grpcServer := grpc.NewServer(opt...) proto.RegisterEventHandlerServer(grpcServer, NewHandler(handler)) return grpcServer.Serve(listener) } ================================================ FILE: scripts/check-header.sh ================================================ #!/usr/bin/env bash read -r -d '' EXPECTED <