Full Code of banzaicloud/hollowtrees for AI

master d76060afb4b4 cached
64 files
155.4 KB
55.0k tokens
186 symbols
1 requests
Download .txt
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)

![Hollowtrees](docs/images/hollowtrees-overview.png)

## Configuring Prometheus to send alerts to Hollowtrees

Hollowtrees is listening on an API similar to the [Prometheus Alert Manager](https://prometheus.io/docs/alerting/alertmanager/) and it can be configured in Prometheus as an Alert Manager. For example if Hollowtrees is running locally on port 9092 (configurable through `global.bindAddr`), Prometheus can be configured like this to send its alerts to Hollowtrees directly:

```yaml
# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
       - localhost:9092
```

### Configuring action flows

After a Prometheus alert is received by Hollowtrees, it first converts it to an event that complies to the [OpenEvents](https://openevents.io) specification, then it processes it based on the action flows configured in the `config.yaml` file, and sends events to its configured action plugins. An example configuration can be found in `config.yaml.dist` under `plugins` and `flows`.

Hollowtrees uses gRPC to send events to its action plugins, and calls the action plugins sequentially. This very simple rule engine will probably change once Hollowtrees will have a release and will support different calling mechanisms, and passing of configuration parameters to the plugins.

Alerts coming from Prometheus are converted to events with a type of `prometheus.server.alert.<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
Download .txt
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
Download .txt
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.

Copied to clipboard!