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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/hollowtrees.iml" filepath="$PROJECT_DIR$/.idea/hollowtrees.iml" />
</modules>
</component>
</project>
================================================
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:
<p align="center">
<a href="https://beta.banzaicloud.io">
<img src="https://camo.githubusercontent.com/a487fb3128bcd1ef9fc1bf97ead8d6d6a442049a/68747470733a2f2f62616e7a6169636c6f75642e636f6d2f696d672f7472795f706970656c696e655f627574746f6e2e737667">
</a>
</p>
**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)

## 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.<AlertName>`. Prometheus labels are converted to the `data` payload as JSON. Data payload elements can be used in the action flows to forward events to the plugins only when it matches a specific string.
### 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<string, string> 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 <<EOF
// Copyright © DATE 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.
EOF
STATUS=0
FILES=$(find . -name "*.go" -not -name "*.pb.go" -not -path "./vendor/*")
for FILE in $FILES; do
# Replace the actual year with DATE so we can ignore the year when
# checking for the license header.
HEADER=$(head -n 13 $FILE | sed -E -e 's/Copyright © [0-9]+/Copyright © DATE/')
if [ "$HEADER" != "$EXPECTED" ]; then
echo "incorrect license header: $FILE"
STATUS=1
fi
done
exit $STATUS
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
SYMBOL INDEX (186 symbols across 36 files)
FILE: cmd/daemon/configuration.go
function configure (line 27) | func configure() {
FILE: cmd/daemon/main.go
function init (line 37) | func init() {
function main (line 42) | func main() {
FILE: examples/grpc_plugin/main.go
type dummyEventHandler (line 25) | type dummyEventHandler struct
method Handle (line 28) | func (d *dummyEventHandler) Handle(event *gp.CloudEvent) (*gp.Result, ...
function init (line 36) | func init() {
function main (line 40) | func main() {
FILE: internal/ce/ce.go
type Event (line 23) | type Event struct
method GetExtensions (line 28) | func (e Event) GetExtensions() map[string]string {
method getExtensionsForPrometheusAlert (line 42) | func (e Event) getExtensionsForPrometheusAlert() map[string]string {
FILE: internal/flows/config.go
type FlowConfig (line 27) | type FlowConfig struct
method Validate (line 41) | func (c FlowConfig) Validate(plugins plugin.PluginManager, id string) ...
type FlowConfigs (line 38) | type FlowConfigs
FILE: internal/flows/event_dispatcher.go
constant CEIncomingTopic (line 18) | CEIncomingTopic = "cloud.events.incoming"
type baseEventSubscriber (line 21) | type baseEventSubscriber interface
type eventSubscriber (line 25) | type eventSubscriber interface
type flowEventDispatcher (line 29) | type flowEventDispatcher interface
type eventDispatcher (line 33) | type eventDispatcher struct
method SubscribeAsync (line 45) | func (b *eventDispatcher) SubscribeAsync(topic string, flow ActionFlow...
function NewEventDispatcher (line 38) | func NewEventDispatcher(eb baseEventSubscriber) flowEventDispatcher {
FILE: internal/flows/eventflow.go
constant EventFlowCompleted (line 24) | EventFlowCompleted EventFlowStatus = "completed"
constant EventFlowFailed (line 25) | EventFlowFailed EventFlowStatus = "failed"
constant EventFlowInProgress (line 26) | EventFlowInProgress EventFlowStatus = "inprogress"
constant EventFlowInitialized (line 27) | EventFlowInitialized EventFlowStatus = "initialized"
constant EventFlowCoolingDown (line 28) | EventFlowCoolingDown EventFlowStatus = "coolingdown"
type EventFlowStatus (line 31) | type EventFlowStatus
type EventFlow (line 35) | type EventFlow struct
method Exec (line 54) | func (ef *EventFlow) Exec() error {
function NewEventFlow (line 44) | func NewEventFlow(flow *Flow, event *ce.Event) *EventFlow {
FILE: internal/flows/flows.go
type ActionFlow (line 29) | type ActionFlow interface
type Flow (line 34) | type Flow struct
method Handle (line 66) | func (f *Flow) Handle(event interface{}) {
method handleEvent (line 79) | func (f *Flow) handleEvent(event *ce.Event) error {
method createOrGetEventFlow (line 117) | func (f *Flow) createOrGetEventFlow(event *ce.Event, key string) (*Eve...
method getEventKey (line 141) | func (f *Flow) getEventKey(event *ce.Event, groupBy []string) string {
method isEventMatched (line 159) | func (f *Flow) isEventMatched(event *ce.Event) bool {
method isEventTypeAllowed (line 173) | func (f *Flow) isEventTypeAllowed(eventType string) bool {
function NewFlow (line 49) | func NewFlow(manager FlowManager, cache FlowStore, id string, name strin...
FILE: internal/flows/manager.go
type FlowManager (line 26) | type FlowManager interface
type Manager (line 33) | type Manager struct
method Logger (line 51) | func (m *Manager) Logger() log.Logger {
method ErrorHandler (line 56) | func (m *Manager) ErrorHandler() emperror.Handler {
method Plugins (line 61) | func (m *Manager) Plugins() plugin.PluginManager {
method LoadFlows (line 67) | func (m *Manager) LoadFlows(v *viper.Viper) error {
function NewManager (line 41) | func NewManager(logger log.Logger, errorHandler emperror.Handler, dispat...
FILE: internal/flows/option.go
type Option (line 20) | type Option interface
type Cooldown (line 25) | type Cooldown
method apply (line 27) | func (o Cooldown) apply(f *Flow) {
type AllowedEvents (line 32) | type AllowedEvents
method apply (line 34) | func (o AllowedEvents) apply(f *Flow) {
type GroupBy (line 39) | type GroupBy
method apply (line 41) | func (o GroupBy) apply(f *Flow) {
type Plugins (line 46) | type Plugins
method apply (line 48) | func (o Plugins) apply(f *Flow) {
type Filters (line 53) | type Filters
method apply (line 55) | func (o Filters) apply(f *Flow) {
type Description (line 60) | type Description
method apply (line 62) | func (o Description) apply(f *Flow) {
FILE: internal/flows/store.go
type FlowStore (line 23) | type FlowStore interface
type InMemoryFlowStore (line 29) | type InMemoryFlowStore struct
method Get (line 39) | func (i *InMemoryFlowStore) Get(key string) (*EventFlow, error) {
method Set (line 49) | func (i *InMemoryFlowStore) Set(key string, ef *EventFlow, ttl time.Du...
method Delete (line 54) | func (i *InMemoryFlowStore) Delete(key string) {
function NewInMemFlowStore (line 33) | func NewInMemFlowStore() *InMemoryFlowStore {
FILE: internal/platform/config/config.go
constant ServiceName (line 33) | ServiceName = "hollowtrees"
constant FriendlyServiceName (line 36) | FriendlyServiceName = "Hollowtrees"
constant ConfigEnvPrefix (line 39) | ConfigEnvPrefix = "HT"
type Config (line 44) | type Config struct
method Validate (line 62) | func (c Config) Validate() error {
function Configure (line 82) | func Configure(v *viper.Viper, p *pflag.FlagSet) {
FILE: internal/platform/config/error_handler.go
function ErrorHandler (line 31) | func ErrorHandler(logger log.Logger) emperror.Handler {
function newErrorHandler (line 39) | func newErrorHandler(logger log.Logger) emperror.Handler {
FILE: internal/platform/errors/handler.go
type handler (line 23) | type handler struct
method Handle (line 33) | func (h *handler) Handle(err error) {
function NewHandler (line 28) | func NewHandler(logger log.Logger) *handler {
FILE: internal/platform/errors/keyvals.go
function ToMap (line 25) | func ToMap(keyvals []interface{}) map[string]interface{} {
function merge (line 43) | func merge(dst map[string]interface{}, k, v interface{}) {
function safeString (line 65) | func safeString(str fmt.Stringer) (s string) {
function safeError (line 81) | func safeError(err error) (s interface{}) {
FILE: internal/platform/gin/correlationid/logger.go
constant correlationIdField (line 23) | correlationIdField = "correlation-id"
function Logger (line 26) | func Logger(logger log.Logger, ctx *gin.Context) log.Logger {
FILE: internal/platform/gin/correlationid/middleware.go
constant ContextKey (line 23) | ContextKey = "correlationid"
constant defaultHeader (line 26) | defaultHeader = "Correlation-ID"
type MiddlewareOption (line 29) | type MiddlewareOption interface
type Header (line 34) | type Header
method apply (line 37) | func (h Header) apply(m *middleware) {
function Middleware (line 42) | func Middleware(opts ...MiddlewareOption) gin.HandlerFunc {
type middleware (line 56) | type middleware struct
method Handle (line 61) | func (m *middleware) Handle(ctx *gin.Context) {
FILE: internal/platform/gin/log/middleware.go
constant correlationIdField (line 26) | correlationIdField = "correlation-id"
function Middleware (line 29) | func Middleware(logger log.Logger, notlogged ...string) gin.HandlerFunc {
FILE: internal/platform/healthcheck/config.go
type Config (line 19) | type Config struct
method Validate (line 25) | func (c Config) Validate() error {
FILE: internal/platform/healthcheck/healthcheck.go
function New (line 27) | func New(config Config, logger log.Logger, errorHandler emperror.Handler) {
FILE: internal/platform/log/config.go
type Config (line 22) | type Config struct
method Validate (line 35) | func (c Config) Validate() error {
FILE: internal/platform/log/logger.go
type Logger (line 22) | type Logger interface
function NewLogger (line 46) | func NewLogger(config Config) Logger {
FILE: internal/platform/log/logrus_adapter.go
type logrusAdapter (line 23) | type logrusAdapter struct
method WithField (line 28) | func (a *logrusAdapter) WithField(key string, value interface{}) Logger {
method WithFields (line 34) | func (a *logrusAdapter) WithFields(fields Fields) Logger {
function NewLogrusLogger (line 38) | func NewLogrusLogger(config Config) Logger {
FILE: internal/plugin/config.go
type PluginConfig (line 23) | type PluginConfig struct
method Validate (line 32) | func (c PluginConfig) Validate() error {
type PluginConfigs (line 29) | type PluginConfigs
FILE: internal/plugin/grpc.go
type grpcPlugin (line 26) | type grpcPlugin struct
method Handle (line 42) | func (p *grpcPlugin) Handle(event *ce.Event) error {
function NewGrpcPlugin (line 32) | func NewGrpcPlugin(name string, address string) *grpcPlugin {
FILE: internal/plugin/internal.go
type internalPlugin (line 22) | type internalPlugin struct
method Handle (line 38) | func (p *internalPlugin) Handle(event *ce.Event) error {
function NewInternalPlugin (line 28) | func NewInternalPlugin(name string, logger log.Logger) *internalPlugin {
FILE: internal/plugin/manager.go
type PluginManager (line 27) | type PluginManager interface
type Manager (line 34) | type Manager struct
method Add (line 55) | func (m *Manager) Add(plugins ...EventHandlerPlugin) {
method GetByNames (line 62) | func (m *Manager) GetByNames(names ...string) (map[string]EventHandler...
method GetByName (line 77) | func (m *Manager) GetByName(name string) (EventHandlerPlugin, error) {
method LoadFromConfig (line 87) | func (m *Manager) LoadFromConfig(v *viper.Viper) error {
function NewManager (line 42) | func NewManager(logger log.Logger, errorHandler emperror.Handler) *Manag...
FILE: internal/plugin/plugin.go
type EventHandlerPlugin (line 22) | type EventHandlerPlugin interface
type BasePlugin (line 28) | type BasePlugin struct
method GetName (line 33) | func (p *BasePlugin) GetName() string {
FILE: internal/promalert/alert.go
type Alerts (line 30) | type Alerts
method Validate (line 32) | func (alerts Alerts) Validate() error {
method Authorize (line 50) | func (alerts Alerts) Authorize(user *auth.User) error {
type Alert (line 61) | type Alert struct
method convertToCE (line 70) | func (a *Alert) convertToCE(cid string) (*ce.Event, error) {
FILE: internal/promalert/config.go
type Config (line 19) | type Config struct
method Validate (line 31) | func (c Config) Validate() error {
FILE: internal/promalert/event_dispatcher.go
type baseEventPublisher (line 21) | type baseEventPublisher interface
type eventDispatcher (line 25) | type eventDispatcher struct
method Publish (line 41) | func (b *eventDispatcher) Publish(topic string, event *ce.Event) {
type eventPublisher (line 29) | type eventPublisher interface
function NewEventDispatcher (line 34) | func NewEventDispatcher(eb baseEventPublisher) *eventDispatcher {
FILE: internal/promalert/promalert.go
constant EventTopic (line 30) | EventTopic = "cloud.events.incoming"
constant CETypePrefix (line 31) | CETypePrefix = "prometheus.server.alert."
type PromAlertHandler (line 35) | type PromAlertHandler struct
method Run (line 59) | func (p *PromAlertHandler) Run() {
method handle (line 80) | func (p *PromAlertHandler) handle(c *gin.Context) {
method publishAlerts (line 126) | func (p *PromAlertHandler) publishAlerts(alerts []Alert, cid string) {
function New (line 46) | func New(config Config, logger log.Logger, errorHandler emperror.Handler...
FILE: pkg/auth/auth.go
type User (line 31) | type User struct
type TokenGenerator (line 36) | type TokenGenerator interface
type tokenGenerator (line 40) | type tokenGenerator struct
method Generate (line 54) | func (g *tokenGenerator) Generate(userID, orgID uint, expiresAt *time....
function NewTokenGenerator (line 46) | func NewTokenGenerator(issuer, audience, signingKey string) TokenGenerat...
function GetCurrentUser (line 87) | func GetCurrentUser(c *gin.Context) *User {
function Handler (line 94) | func Handler(signingKey string) gin.HandlerFunc {
function claimConverter (line 98) | func claimConverter(claims *bauth.ScopedClaims) interface{} {
FILE: pkg/grpcplugin/handler.go
type EventHandler (line 25) | type EventHandler interface
type CloudEvent (line 29) | type CloudEvent
type Result (line 30) | type Result
type handler (line 32) | type handler struct
method Handle (line 44) | func (h *handler) Handle(ctx context.Context, ce *proto.CloudEvent) (*...
function NewHandler (line 37) | func NewHandler(eh EventHandler) *handler {
FILE: pkg/grpcplugin/proto/event.pb.go
constant _ (line 35) | _ = proto1.ProtoPackageIsVersion2
type CloudEvent (line 37) | type CloudEvent struct
method Reset (line 49) | func (m *CloudEvent) Reset() { *m = CloudEvent{} }
method String (line 50) | func (m *CloudEvent) String() string { return proto1.Compac...
method ProtoMessage (line 51) | func (*CloudEvent) ProtoMessage() {}
method Descriptor (line 52) | func (*CloudEvent) Descriptor() ([]byte, []int) { return fileDescripto...
method GetSpecversion (line 54) | func (m *CloudEvent) GetSpecversion() string {
method GetType (line 61) | func (m *CloudEvent) GetType() string {
method GetSource (line 68) | func (m *CloudEvent) GetSource() string {
method GetId (line 75) | func (m *CloudEvent) GetId() string {
method GetTime (line 82) | func (m *CloudEvent) GetTime() string {
method GetSchemaurl (line 89) | func (m *CloudEvent) GetSchemaurl() string {
method GetContenttype (line 96) | func (m *CloudEvent) GetContenttype() string {
method GetData (line 103) | func (m *CloudEvent) GetData() []byte {
method GetExtensions (line 110) | func (m *CloudEvent) GetExtensions() map[string]string {
type Result (line 117) | type Result struct
method Reset (line 121) | func (m *Result) Reset() { *m = Result{} }
method String (line 122) | func (m *Result) String() string { return proto1.CompactTex...
method ProtoMessage (line 123) | func (*Result) ProtoMessage() {}
method Descriptor (line 124) | func (*Result) Descriptor() ([]byte, []int) { return fileDescriptor0, ...
method GetStatus (line 126) | func (m *Result) GetStatus() string {
function init (line 133) | func init() {
constant _ (line 144) | _ = grpc.SupportPackageIsVersion4
type EventHandlerClient (line 148) | type EventHandlerClient interface
type eventHandlerClient (line 152) | type eventHandlerClient struct
method Handle (line 160) | func (c *eventHandlerClient) Handle(ctx context.Context, in *CloudEven...
function NewEventHandlerClient (line 156) | func NewEventHandlerClient(cc *grpc.ClientConn) EventHandlerClient {
type EventHandlerServer (line 171) | type EventHandlerServer interface
function RegisterEventHandlerServer (line 175) | func RegisterEventHandlerServer(s *grpc.Server, srv EventHandlerServer) {
function _EventHandler_Handle_Handler (line 179) | func _EventHandler_Handle_Handler(srv interface{}, ctx context.Context, ...
function init (line 210) | func init() { proto1.RegisterFile("event.proto", fileDescriptor0) }
FILE: pkg/grpcplugin/server.go
function Serve (line 27) | func Serve(bindAddress string, handler EventHandler, opt ...grpc.ServerO...
Condensed preview — 64 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (170K chars).
[
{
"path": ".circleci/config.yml",
"chars": 2645,
"preview": "version: 2.1\n\norbs:\n helm: banzaicloud/helm@0.0.5\n\njobs:\n build:\n docker:\n - image: circleci/golang:1.12\n "
},
{
"path": ".gitignore",
"chars": 304,
"preview": "/bin/\n/build/\n/config.*\n!config.yaml.dist\n/.docker/\n/docker-compose.override.yml\n/.env\n/.env.test\n/vendor/\n/.licensei.ca"
},
{
"path": ".idea/hollowtrees.iml",
"chars": 322,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"WEB_MODULE\" version=\"4\">\n <component name=\"Go\" enabled=\"true\" />\n "
},
{
"path": ".idea/modules.xml",
"chars": 274,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectModuleManager\">\n <modules>\n "
},
{
"path": ".licensei.toml",
"chars": 401,
"preview": "approved = [\n \"mit\",\n \"apache-2.0\",\n \"bsd-3-clause\",\n \"bsd-2-clause\",\n \"mpl-2.0\",\n]\n\nignored = [\n \"git"
},
{
"path": "Dockerfile",
"chars": 522,
"preview": "ARG GO_VERSION=1.12\n\nFROM golang:${GO_VERSION}-alpine AS builder\n\nRUN apk add --update --no-cache ca-certificates=201901"
},
{
"path": "Dockerfile.local",
"chars": 264,
"preview": "FROM alpine:3.7 AS builder\n\nRUN apk add --update --no-cache ca-certificates\n\n\nFROM alpine:3.7\n\nCOPY --from=builder /etc/"
},
{
"path": "LICENSE.md",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Makefile",
"chars": 5052,
"preview": "# A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html\n\n# Project variables\nPA"
},
{
"path": "README.md",
"chars": 4950,
"preview": "# Hollowtrees\n\n_Hollowtrees is a wave for the highest level, the pin-up centrefold for the Mentawai islands bringing a n"
},
{
"path": "charts/hollowtrees-with-ps/.helmignore",
"chars": 342,
"preview": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation"
},
{
"path": "charts/hollowtrees-with-ps/Chart.yaml",
"chars": 440,
"preview": "apiVersion: v1\nname: hollowtrees-with-ps\nhome: https://banzaicloud.com\nsources:\n - https://banzaicloud.com\n - https://"
},
{
"path": "charts/hollowtrees-with-ps/README.md",
"chars": 1290,
"preview": "# Hollowtrees with Pipeline Scaler Plugin Helm Chart\n\n## TL;DR\n\n```bash\n$ helm repo add banzaicloud-stable https://kuber"
},
{
"path": "charts/hollowtrees-with-ps/templates/_helpers.tpl",
"chars": 1487,
"preview": "{{/* vim: set filetype=mustache: */}}\n{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"hollowtrees-with-ps.name\" -}}\n"
},
{
"path": "charts/hollowtrees-with-ps/templates/configmap-ht.yaml",
"chars": 1209,
"preview": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: {{ include \"hollowtrees-with-ps.fullname\" . }}\n labels:\n{{ include \"ho"
},
{
"path": "charts/hollowtrees-with-ps/templates/configmap-htpsp.yaml",
"chars": 1007,
"preview": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: {{ include \"hollowtrees-with-ps.fullname\" . }}-htpsp\n labels:\n{{ inclu"
},
{
"path": "charts/hollowtrees-with-ps/templates/deployment.yaml",
"chars": 3523,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: {{ include \"hollowtrees-with-ps.fullname\" . }}\n labels:\n{{ inclu"
},
{
"path": "charts/hollowtrees-with-ps/templates/ingress.yaml",
"chars": 867,
"preview": "{{- if .Values.ingress.enabled -}}\n{{- $fullName := include \"hollowtrees-with-ps.fullname\" . -}}\napiVersion: extensions/"
},
{
"path": "charts/hollowtrees-with-ps/templates/secret.yaml",
"chars": 370,
"preview": "{{- if .Values.promalert.useJWTAuth -}}\napiVersion: v1\nkind: Secret\nmetadata:\n name: {{ template \"hollowtrees-with-ps.f"
},
{
"path": "charts/hollowtrees-with-ps/templates/service.yaml",
"chars": 444,
"preview": "apiVersion: v1\nkind: Service\nmetadata:\n name: {{ include \"hollowtrees-with-ps.fullname\" . }}\n labels:\n{{ include \"holl"
},
{
"path": "charts/hollowtrees-with-ps/values.yaml",
"chars": 1568,
"preview": "# Default values for hollowtrees-with-ps\n# This is a YAML-formatted file.\n# Declare variables to be passed into your tem"
},
{
"path": "cmd/daemon/build.go",
"chars": 725,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "cmd/daemon/configuration.go",
"chars": 1291,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "cmd/daemon/main.go",
"chars": 2923,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "config.yaml.dist",
"chars": 611,
"preview": "# logger settings\nlog:\n format: \"logfmt\"\n level: \"debug\"\n\n# action plugins\nplugins:\n - name: \"dummy-plugin-1\"\n add"
},
{
"path": "docs/DEVELOPMENT.md",
"chars": 2183,
"preview": "# Development guide\n\n## Building and running Hollowtrees on your local machine\n\nThe project can be built with `make`, co"
},
{
"path": "examples/grpc_plugin/main.go",
"chars": 1319,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "go.mod",
"chars": 1536,
"preview": "module github.com/banzaicloud/hollowtrees\n\ngo 1.12\n\nrequire (\n\tgithub.com/asaskevich/EventBus v0.0.0-20180315140547-d469"
},
{
"path": "go.sum",
"chars": 43154,
"preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
},
{
"path": "internal/ce/ce.go",
"chars": 1174,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/flows/config.go",
"chars": 1696,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/flows/event_dispatcher.go",
"chars": 1330,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/flows/eventflow.go",
"chars": 1920,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/flows/flows.go",
"chars": 3907,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/flows/manager.go",
"chars": 2739,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/flows/option.go",
"chars": 1676,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/flows/store.go",
"chars": 1441,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/platform/config/config.go",
"chars": 3058,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/platform/config/error_handler.go",
"chars": 1494,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/platform/errors/handler.go",
"chars": 1578,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/platform/errors/keyvals.go",
"chars": 1887,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/platform/gin/correlationid/logger.go",
"chars": 1015,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/platform/gin/correlationid/middleware.go",
"chars": 1762,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/platform/gin/log/middleware.go",
"chars": 2059,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/platform/healthcheck/config.go",
"chars": 964,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/platform/healthcheck/healthcheck.go",
"chars": 1186,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/platform/log/config.go",
"chars": 1236,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/platform/log/logger.go",
"chars": 1505,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/platform/log/logrus_adapter.go",
"chars": 1617,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/plugin/config.go",
"chars": 1279,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/plugin/grpc.go",
"chars": 1782,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/plugin/internal.go",
"chars": 1166,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/plugin/manager.go",
"chars": 2855,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/plugin/plugin.go",
"chars": 991,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/promalert/alert.go",
"chars": 2521,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/promalert/config.go",
"chars": 1096,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/promalert/event_dispatcher.go",
"chars": 1207,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "internal/promalert/promalert.go",
"chars": 3582,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "pkg/auth/auth.go",
"chars": 2767,
"preview": "// Copyright © 2019 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "pkg/grpcplugin/handler.go",
"chars": 1693,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "pkg/grpcplugin/proto/event.pb.go",
"chars": 7788,
"preview": "// Code generated by protoc-gen-go.\n// source: event.proto\n// DO NOT EDIT!\n\n/*\nPackage proto is a generated protocol buf"
},
{
"path": "pkg/grpcplugin/proto/event.proto",
"chars": 552,
"preview": "syntax = \"proto3\";\n\noption java_multiple_files = true;\noption java_package = \"com.banzaicloud.hollowtrees.handleEvent\";\n"
},
{
"path": "pkg/grpcplugin/server.go",
"chars": 1157,
"preview": "// Copyright © 2018 Banzai Cloud\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not us"
},
{
"path": "scripts/check-header.sh",
"chars": 1079,
"preview": "#!/usr/bin/env bash\n\nread -r -d '' EXPECTED <<EOF\n// Copyright © DATE Banzai Cloud\n//\n// Licensed under the Apache Licen"
}
]
About this extraction
This page contains the full source code of the banzaicloud/hollowtrees GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 64 files (155.4 KB), approximately 55.0k tokens, and a symbol index with 186 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.