diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..9a913bf
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,5 @@
+.vscode/
+.idea/
+docs/
+deploy/
+Dockerfile
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6b2e394
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.vscode
+
+.idea/*
+# allow shared run configurations
+!.idea/runConfigurations/
+
+vendor
+
diff --git a/.idea/runConfigurations/keptn_prometheus_sli_service__Kube_ContDeploy_.xml b/.idea/runConfigurations/keptn_prometheus_sli_service__Kube_ContDeploy_.xml
new file mode 100644
index 0000000..a7e9f08
--- /dev/null
+++ b/.idea/runConfigurations/keptn_prometheus_sli_service__Kube_ContDeploy_.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..fbfbdb1
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,118 @@
+dist: xenial
+language: bash
+services:
+ - docker
+env:
+ global:
+ - GO111MODULE=on
+before_install:
+ - export TZ=Europe/Vienna
+ - IMAGE=keptn/prometheus-sli-service
+ - REPO_URL="$(git remote get-url --all origin)"
+ # get the last tag
+ - GIT_LAST_TAG=$(git describe --tags $(git rev-list --tags) || echo "0.0.0")
+ - GIT_NUM_COMMITS_SINCE_LAST_TAG=$(git rev-list ${GIT_LAST_TAG}..HEAD --count || echo "trunk")
+ # get current branch name
+ - GIT_BRANCH_NAME=$(git branch | grep \* | cut -d ' ' -f2)
+ - GIT_SHA=$(git rev-parse --short HEAD)
+ - GIT_BRANCH_AND_COMMIT=$(git describe --exact-match 2> /dev/null || echo "`git symbolic-ref HEAD 2> /dev/null | cut -b 12-`-`git log --pretty=format:\"%h\" -1`")
+ # find out if we are on a tag (= exact match)
+ # if not: use tag + number of commit + commit hash
+ - VERSION="$(cat version | tr -d '[:space:]')"
+ - DATE="$(date +'%Y%m%d.%H%M')"
+ - ./writeManifest.sh
+ - cat MANIFEST
+ # uncomment certain lines from Dockerfile that are for travis builds only
+ - sed -i '/#travis-uncomment/s/^#travis-uncomment //g' Dockerfile
+jobs:
+ include:
+ - stage: codestyle
+ # Check Codestyle using go fmt
+ services: []
+ language: go
+ go:
+ - 1.12.x
+ # skip install
+ install: true
+ script:
+ - echo "Checking code style..."
+ - unformatted=$(gofmt -l .)
+ - |
+ if [ ! -z "$unformatted" ]; then
+ echo "Code Style Check failed for the following files: ${unformatted}".
+ echo "Please run: gofmt -w ."
+ echo "After that ammend your commit (e.g.: git add ${unformatted} && git commit --amend --no-edit) and force push the changes (git push -f)."
+ travis_terminate 1
+ fi
+
+ - stage: tests
+ # Run tests
+ services: []
+ language: go
+ go:
+ - 1.12.x
+ # cache some go files
+ cache:
+ directories:
+ - $HOME/.cache/go-build
+ - $HOME/gopath/pkg/mod
+ addons:
+ sonarcloud:
+ organization: "keptn-contrib"
+ script:
+ - sonar-scanner
+ - go build
+ - go test -race -v ./...
+
+ - stage: feature/bug/hotfix/patch
+ # build docker images for feature/bug/hotfix/patch branches
+ if: branch =~ ^feature.*$ OR branch =~ ^bug.*$ OR branch =~ ^hotfix.*$ OR branch =~ ^patch.*$
+ script:
+ - echo $TRAVIS_BUILD_STAGE_NAME
+ - TYPE="$(echo $TRAVIS_BRANCH | cut -d'/' -f1)"
+ - NUMBER="$(echo $TRAVIS_BRANCH | cut -d'/' -f2)"
+ - docker build . -t "${IMAGE}:${GIT_SHA}"
+ - docker tag "${IMAGE}:${GIT_SHA}" "${IMAGE}:${TYPE}.${NUMBER}.${DATE}"
+ after_success:
+ - echo "$REGISTRY_PASSWORD" | docker login --username $REGISTRY_USER --password-stdin
+ - docker push "${IMAGE}:${GIT_SHA}"
+ - docker push "${IMAGE}:${TYPE}.${NUMBER}.${DATE}"
+
+
+ - stage: develop
+ # build docker images for develop branch
+ if: branch = develop AND NOT type = pull_request
+ script:
+ - echo $TRAVIS_BUILD_STAGE_NAME
+ - docker build . -t "${IMAGE}:${GIT_SHA}"
+ - docker tag "${IMAGE}:${GIT_SHA}" "${IMAGE}:${DATE}"
+ - docker tag "${IMAGE}:${GIT_SHA}" "${IMAGE}:latest"
+ after_success:
+ - echo "$REGISTRY_PASSWORD" | docker login --username $REGISTRY_USER --password-stdin
+ - docker push "${IMAGE}:${GIT_SHA}"
+ - docker push "${IMAGE}:${DATE}"
+ - docker push "${IMAGE}:latest"
+
+ - stage: release-branch
+ # build docker images for release branches
+ if: branch =~ ^release.*$ AND NOT type = pull_request
+ script:
+ - echo $TRAVIS_BUILD_STAGE_NAME
+ - docker build . -t "${IMAGE}:${GIT_SHA}"
+ - docker tag "${IMAGE}:${GIT_SHA}" "${IMAGE}:${VERSION}.${DATE}"
+ - docker tag "${IMAGE}:${GIT_SHA}" "${IMAGE}:${VERSION}.latest"
+ after_success:
+ - echo "$REGISTRY_PASSWORD" | docker login --username $REGISTRY_USER --password-stdin
+ - docker push "${IMAGE}:${GIT_SHA}"
+ - docker push "${IMAGE}:${VERSION}.${DATE}"
+ - docker push "${IMAGE}:${VERSION}.latest"
+
+ - stage: tags
+ # build docker images for tags
+ if: tag IS present
+ script:
+ - echo $TRAVIS_BUILD_STAGE_NAME
+ - docker build . -t "${IMAGE}:${VERSION}"
+ after_success:
+ - echo "$REGISTRY_PASSWORD" | docker login --username $REGISTRY_USER --password-stdin
+ - docker push "${IMAGE}:${VERSION}"
diff --git a/CODEOWNERS b/CODEOWNERS
index 9f90b59..9f539c5 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1,4 +1,5 @@
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners.
-# These owners will be the default owners for everything in the repo.
\ No newline at end of file
+# These owners will be the default owners for everything in the repo.
+* @christian-kreuzberger-dtx @bacherfl
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..c1e3680
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,54 @@
+# from https://skaffold.dev/docs/workflows/debug/
+# Use the offical Golang image to create a build artifact.
+# This is based on Debian and sets the GOPATH to /go.
+# https://hub.docker.com/_/golang
+FROM golang:1.12 as builder
+
+WORKDIR /go/src/github.com/keptn-contrib/prometheus-sli-service
+
+ENV GO111MODULE=on
+ENV BUILDFLAGS=""
+
+# Copy `go.mod` for definitions and `go.sum` to invalidate the next layer
+# in case of a change in the dependencies
+COPY go.mod go.sum ./
+
+# download dependencies
+RUN go mod download
+
+ARG debugBuild
+
+# set buildflags for debug build
+RUN if [ ! -z "$debugBuild" ]; then export BUILDFLAGS='-gcflags "all=-N -l"'; fi
+
+# finally Copy local code to the container image.
+COPY . .
+
+# Build the command inside the container.
+# (You may fetch or manage dependencies here, either manually or with a tool like "godep".)
+RUN CGO_ENABLED=0 GOOS=linux go build $BUILDFLAGS -v -o prometheus-sli-service
+
+# Use a Docker multi-stage build to create a lean production image.
+# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
+FROM alpine:3.7
+RUN apk add --no-cache ca-certificates
+
+ARG debugBuild
+
+# IF we are debugging, we need to install libc6-compat for delve to work on alpine based containers
+RUN if [ ! -z "$debugBuild" ]; then apk add --no-cache libc6-compat; fi
+
+# Copy the binary to the production image from the builder stage.
+COPY --from=builder /go/src/github.com/keptn-contrib/prometheus-sli-service/prometheus-sli-service /prometheus-sli-service
+
+EXPOSE 8080
+
+# required for external tools to detect this as a go binary
+ENV GOTRACEBACK=all
+
+# KEEP THE FOLLOWING LINES COMMENTED OUT!!! (they will be included within the travis-ci build)
+#travis-uncomment ADD MANIFEST /
+#travis-uncomment COPY entrypoint.sh /
+#travis-uncomment ENTRYPOINT ["/entrypoint.sh"]
+
+CMD ["/prometheus-sli-service"]
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..d466a59
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,8 @@
+##########
+branch: MANIFEST_BRANCH
+repository: MANIFEST_REPOSITORY
+commitlink: MANIFEST_REPOSITORY/commit/MANIFEST_COMMIT
+repolink: MANIFEST_REPOSITORY/tree/MANIFEST_COMMIT
+travisbuild: MANIFEST_TRAVIS_JOB_URL
+timestamp: MANIFEST_DATE
+##########
diff --git a/README.md b/README.md
index 06802fd..54a5594 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,103 @@
# Prometheus SLI Service
+![GitHub release (latest by date)](https://img.shields.io/github/v/release/keptn-contrib/prometheus-sli-service)
+[![Build Status](https://travis-ci.org/keptn-contrib/prometheus-sli-service.svg?branch=master)](https://travis-ci.org/keptn-contrib/prometheus-sli-service)
+[![Go Report Card](https://goreportcard.com/badge/github.com/keptn-contrib/prometheus-sli-service)](https://goreportcard.com/report/github.com/keptn-contrib/prometheus-sli-service)
-The *prometheus-sli-service* is a Keptn service that is responsible for retrieving the values of Keptn-supported SLIs from a Prometheus API endpoint.
+This service is used for retrieving service level indicators (SLIs) from a prometheus API endpoint. Per default, it fetches metrics from the prometheus instance set up by Keptn
+(`prometheus-service.monitoring.svc.cluster.local:8080`), but it can also be configures to use any reachable Prometheus endpoint using basic authentication by providing the credentials
+via a secret in the `keptn` namespace of the cluster.
+
+The supported default SLIs are:
+
+ - throughput
+ - error_rate
+ - response_time_p50
+ - response_time_p90
+ - response_time_p95
+
+The provided SLIs are based on the [RED metrics](https://grafana.com/files/grafanacon_eu_2018/Tom_Wilkie_GrafanaCon_EU_2018.pdf)
+
+## Basic Usage
+
+Per default, the service works with the following assumptions regarding the setup of the Prometheus instance:
+
+ - Each **service** within a **stage** of a **project** has a Prometheus scrape job definition with the name: `--`
+
+ For example, if `project=sockshop`, `stage=production` and `service=carts`, the scrape job name would have to be `carts-sockshop-production`.
+
+ - Every service provides the following Metrics for its corresponding scrape job:
+ - http_response_time_milliseconds (Histogram)
+ - http_requests_total (Counter)
+
+ This metric has to contain the `status` label, indicating the HTTP response code of the requests handled by the service.
+ It is highly recommended that this metric also provides a label to query metric values for specific endpoints, e.g. `handler`
+
+ An example of an entry would look like this: `http_requests_total{method="GET",handler="VersionController.getInformation",status="200",} 4.0`
+
+ - Based on those metrics, the queries for the SLIs are built as follows:
+
+ - **throughput**: `sum(rate(http_requests_total{job="---canary"}[s]))`
+ - **error_rate**: `sum(rate(http_requests_total{job="---canary",status!~'2..'}[s]))/sum(rate(http_requests_total{job="---canary"}[s]))`
+ - **response_time_p50**: `histogram_quantile(0.50, sum(rate(http_response_time_milliseconds_bucket{job='---canary'}[s])) by (le))`
+ - **response_time_p90**: `histogram_quantile(0.90, sum(rate(http_response_time_milliseconds_bucket{job='---canary'}[s])) by (le))`
+ - **response_time_p95**: `histogram_quantile(0.95, sum(rate(http_response_time_milliseconds_bucket{job='---canary'}[s])) by (le))`
+
+## Advanced Usage
+
+### Using an external Prometheus instance
+To use a Prometheus instance other than the one that's being managed by Keptn for a certain project, a secret containing the URL and the access credentials has to be deployed into the `keptn` namespace. The secret must have the following format:
+
+```yaml
+user: test
+password: test
+url: http://prometheus-service.monitoring.svc.cluster.local:8080
+```
+
+If this information is stored in a file, e.g. `prometheus-creds.yaml`, it can be stored with the following command (don't forget to replace the `` placeholder with the name of your project:
+
+```bash
+kubectl create secret -n keptn generic prometheus-credentials- --from-file=prometheus-credentials=./mock_secret.yaml
+```
+
+Please note that there is a naming convention for the secret, because this can be configured per **project**. Therefore, the secret has to have the name `prometheus-credentials-`
+
+
+### Custom SLI queries
+
+Users can override the predefined queries, as well as add custom SLI queries by creating a `ConfigMap` with the name `prometheus-sli-config-` in the `keptn` namespace.
+In this ConfigMap, a YAML object containing the queries can be defined, e.g.:
+
+```yaml
+kind: ConfigMap
+apiVersion: v1
+metadata:
+ name: prometheus-sli-config-sockshop
+ namespace: keptn
+data:
+ custom-queries: |
+ throughput: "rate(my_custom_metric{job='$SERVICE-$PROJECT-$STAGE',handler=~'$handler'}[$DURATION_SECONDS])"
+ error_rate: "sum(rate(my_custom_metric{job='$SERVICE-$PROJECT-$STAGE',handler=~'$handler',status!~'2..'}[1s]))/sum(rate(my_custom_metric{job='$SERVICE-$PROJECT-$STAGE',handler=~'$handler'}[$DURATION_SECONDS]))"
+ response_time_p50: "histogram_quantile(0.50,sum(rate(my_custom_response_time_metric{job='$SERVICE-$PROJECT-$STAGE'}[$DURATION_SECONDS]))by(le))"
+ response_time_p90: "histogram_quantile(0.90,sum(rate(my_custom_response_time_metric{job='$SERVICE-$PROJECT-$STAGE'}[$DURATION_SECONDS]))by(le))"
+ response_time_p95: "histogram_quantile(0.95,sum(rate(my_custom_response_time_metric{job='$SERVICE-$PROJECT-$STAGE'}[$DURATION_SECONDS]))by(le))"
+ # Example for a custom SLI that is not part of the default SLIs
+ cpu_usage: avg(rate(container_cpu_usage_seconds_total{namespace="$PROJECT-$STAGE",pod_name=~"$SERVICE-primary-.*"}[5m]))
+```
+
+Note that, similarly, to the custom endpoint configuration, the name of the ConfigMap has to be `prometheus-sli-config-`, and has to be stored in the `keptn` namespace.
+
+Within the user-defined queries, the following variables can be used to dynamically build the query, depending on the project/stage/service, and the time frame:
+
+- $PROJECT: will be replaced with the name of the project
+- $STAGE: will be replaced with the name of the stage
+- $SERVICE: will be replaced with the name of the service
+- $DURATION_SECONDS: will be replaced with the test run duration, e.g. 30s
+
+For example, if an evaluation for the service **carts** in the stage **production** of the project **sockshop** is triggered, and the tests ran for 30s these will be the resulting queries:
+
+```
+rate(my_custom_metric{job='$SERVICE-$PROJECT-$STAGE',handler=~'$handler'}[$DURATION_SECONDS]) => rate(my_custom_metric{job='carts-sockshop-production',handler=~'$handler'}[30s])
+```
## Installation
diff --git a/deploy/distributor.yaml b/deploy/distributor.yaml
new file mode 100644
index 0000000..bc4f85c
--- /dev/null
+++ b/deploy/distributor.yaml
@@ -0,0 +1,36 @@
+---
+## prometheus-sli-service sh.keptn.internal.event.get-sli-distributor
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: prometheus-sli-service-monitoring-configure-distributor
+ namespace: keptn
+spec:
+ selector:
+ matchLabels:
+ run: distributor
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ run: distributor
+ spec:
+ containers:
+ - name: distributor
+ image: keptn/distributor:latest
+ ports:
+ - containerPort: 8080
+ resources:
+ requests:
+ memory: "32Mi"
+ cpu: "50m"
+ limits:
+ memory: "128Mi"
+ cpu: "500m"
+ env:
+ - name: PUBSUB_URL
+ value: 'nats://keptn-nats-cluster'
+ - name: PUBSUB_TOPIC
+ value: 'sh.keptn.internal.event.get-sli'
+ - name: PUBSUB_RECIPIENT
+ value: 'prometheus-sli-service'
diff --git a/deploy/service.yaml b/deploy/service.yaml
new file mode 100644
index 0000000..365a861
--- /dev/null
+++ b/deploy/service.yaml
@@ -0,0 +1,47 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: prometheus-sli-service
+ namespace: keptn
+spec:
+ selector:
+ matchLabels:
+ run: prometheus-sli-service
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ run: prometheus-sli-service
+ spec:
+ containers:
+ - name: prometheus-sli-service
+ image: keptn/prometheus-sli-service:0.1.0
+ ports:
+ - containerPort: 8080
+ resources:
+ requests:
+ memory: "32Mi"
+ cpu: "50m"
+ limits:
+ memory: "128Mi"
+ cpu: "500m"
+ env:
+ - name: CONFIGURATION_SERVICE
+ value: 'http://configuration-service.keptn.svc.cluster.local:8080'
+ - name: EVENTBROKER
+ value: 'http://event-broker.keptn.svc.cluster.local/keptn'
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: prometheus-sli-service
+ namespace: keptn
+ labels:
+ run: prometheus-sli-service
+spec:
+ ports:
+ - port: 8080
+ protocol: TCP
+ selector:
+ run: prometheus-sli-service
diff --git a/entrypoint.sh b/entrypoint.sh
new file mode 100755
index 0000000..5f692ca
--- /dev/null
+++ b/entrypoint.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+cat /MANIFEST
+
+exec "$@"
diff --git a/get-sli.http b/get-sli.http
new file mode 100644
index 0000000..023e0ae
--- /dev/null
+++ b/get-sli.http
@@ -0,0 +1,68 @@
+# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
+# paste cURL into the file and request will be converted to HTTP Request format.
+#
+# Following HTTP Request Live Templates are available:
+# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
+# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
+# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
+
+POST http://localhost:8080/
+Accept: application/json
+Cache-Control: no-cache
+Content-Type: application/cloudevents+json
+
+{
+ "contenttype": "application/json",
+ "data": {
+ "sliProvider": "prometheus",
+ "project": "sockshop",
+ "service": "carts",
+ "stage": "dev",
+ "start": "2019-10-20T07:57:27.152330783Z",
+ "end": "2019-10-22T08:57:27.152330783Z",
+ "indicators": ["throughput", "error_rate", "request_latency_p50", "request_latency_p90", "request_latency_p95"]
+ },
+ "id": "b3c3c357-eb3d-4f90-b26e-9ebfedfb8dbf",
+ "source": "jmeter-service",
+ "specversion": "0.2",
+ "time": "2019-10-14T08:00:09.416Z",
+ "type": "sh.keptn.internal.event.get-sli",
+ "shkeptncontext": "71270488-b923-400a-8ac5-7f471b15a181"
+}
+###
+
+POST http://localhost:8081/
+Accept: application/json
+Cache-Control: no-cache
+Content-Type: application/cloudevents+json
+
+{
+ "contenttype": "application/json",
+ "data": {
+ "sliProvider": "prometheus",
+ "project": "sockshop",
+ "service": "carts",
+ "stage": "dev",
+ "start": "2019-10-20T07:57:27.152330783Z",
+ "end": "2019-10-22T08:57:27.152330783Z",
+ "indicators": ["throughput", "error_rate", "request_latency_p50", "request_latency_p90", "request_latency_p95"],
+ "customFilters": [
+ {
+ "key": "handler",
+ "value": "=~.+ItemsController"
+ }
+ ]
+ },
+ "id": "b3c3c357-eb3d-4f90-b26e-9ebfedfb8dbf",
+ "source": "jmeter-service",
+ "specversion": "0.2",
+ "time": "2019-10-14T08:00:09.416Z",
+ "type": "sh.keptn.internal.event.get-sli",
+ "shkeptncontext": "71270488-b923-400a-8ac5-7f471b15a181"
+}
+
+###
+
+
+
+
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..1322613
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,17 @@
+module github.com/keptn-contrib/prometheus-sli-service
+
+go 1.12
+
+require (
+ github.com/cloudevents/sdk-go v0.10.0
+ github.com/google/uuid v1.1.1
+ github.com/kelseyhightower/envconfig v1.4.0
+ github.com/keptn/go-utils v0.4.0
+ github.com/stretchr/testify v1.4.0
+ golang.org/x/net v0.0.0-20191021144547-ec77196f6094
+ gopkg.in/yaml.v2 v2.2.4
+ k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1
+ k8s.io/client-go v11.0.0+incompatible
+)
+
+replace github.com/cloudevents/sdk-go => github.com/cloudevents/sdk-go v0.0.0-20190509003705-56931988abe3
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..5b8f9d8
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,439 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+contrib.go.opencensus.io/exporter/ocagent v0.4.12 h1:jGFvw3l57ViIVEPKKEUXPcLYIXJmQxLUh6ey1eJhwyc=
+contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
+github.com/Azure/azure-sdk-for-go v28.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
+github.com/Azure/go-autorest/autorest v0.2.0 h1:zBtSTOQTtjzHVRe+mhkiHvHwRTKHhjBEyo1m6DfI3So=
+github.com/Azure/go-autorest/autorest v0.2.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
+github.com/Azure/go-autorest/autorest/adal v0.1.0 h1:RSw/7EAullliqwkZvgIGDYZWQm1PGKXI8c4aY/87yuU=
+github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
+github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM=
+github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
+github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/autorest/to v0.1.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
+github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
+github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
+github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
+github.com/Azure/go-autorest/tracing v0.1.0 h1:TRBxC5Pj/fIuh4Qob0ZpkggbfT8RC0SubHbpV3p4/Vc=
+github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
+github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
+github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
+github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
+github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
+github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
+github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/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/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
+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/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
+github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudevents/sdk-go v0.0.0-20190509003705-56931988abe3 h1:DNM19kh6j6qGBx/FI7OmHKBL2vCW1eN28ESYK1+O5DY=
+github.com/cloudevents/sdk-go v0.0.0-20190509003705-56931988abe3/go.mod h1:j1nZWMLGg3om8SswStBoY6/SHvcLM19MuZqwDtMtmzs=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
+github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
+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/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/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
+github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+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 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
+github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
+github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
+github.com/go-openapi/analysis v0.19.4 h1:1TjOzrWkj+9BrjnM1yPAICbaoC0FyfD49oVkTBrSSa0=
+github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
+github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY=
+github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
+github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
+github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
+github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
+github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
+github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
+github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.19.2 h1:rf5ArTHmIJxyV5Oiks+Su0mUens1+AjpkPoWr5xFRcI=
+github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
+github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
+github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
+github.com/go-openapi/runtime v0.19.4 h1:csnOgcgAiuGoM/Po7PEpKDoNulCcF3FGbSnbHfxgjMI=
+github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
+github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
+github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
+github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc=
+github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
+github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
+github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
+github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
+github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
+github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA=
+github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
+github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
+github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
+github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
+github.com/go-openapi/validate v0.19.4 h1:LGjO87VyXY3bIKjlYpXSFuLRG2mTeuYlZyeNwFFWpyM=
+github.com/go-openapi/validate v0.19.4/go.mod h1:BkJ0ZmXui7yB0bJXWSXgLPNTmbLVeX/3D1xn/N9mMUM=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
+github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
+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.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+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/mock v1.1.1/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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+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/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
+github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
+github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
+github.com/gophercloud/gophercloud v0.6.0 h1:Xb2lcqZtml1XjgYZxbeayEemq7ASbeTp09m36gQFpEU=
+github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM=
+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.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE=
+github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
+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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
+github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
+github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
+github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
+github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
+github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
+github.com/keptn/go-utils v0.0.0-20191023080314-74864b263dd5/go.mod h1:YwobU0bBWWvx1b7N++ohTcYyEuz7VZjlbjkZAhzBiS8=
+github.com/keptn/go-utils v0.0.0-20191030112956-7e1cba200072 h1:JMzslHnHUWS+aIzto+uycnPyOzVmshbxjKC+k/xuMr4=
+github.com/keptn/go-utils v0.0.0-20191030112956-7e1cba200072/go.mod h1:R9a1HXkD+KCrhMFbLcEhtBtHGfYwXhO6dwZzmyoE98c=
+github.com/keptn/go-utils v0.0.0-20191030134731-341284448a02 h1:HYv+BJx9h4Dd+prE+D+pMYkIUb9mMiRPWWWnqm2RQ3w=
+github.com/keptn/go-utils v0.0.0-20191030134731-341284448a02/go.mod h1:R9a1HXkD+KCrhMFbLcEhtBtHGfYwXhO6dwZzmyoE98c=
+github.com/keptn/go-utils v0.3.0 h1:0Gm3Kmv3EXmlq1i3YcHiwLs9j6wCw0COe+IR2pNKq5k=
+github.com/keptn/go-utils v0.3.1-0.20191111100301-bff0cac85494 h1:iZ//qDbwRLRx41VbmPJQclhDNPK9QGuwe0K7mDHfsGQ=
+github.com/keptn/go-utils v0.3.1-0.20191111100301-bff0cac85494/go.mod h1:R9a1HXkD+KCrhMFbLcEhtBtHGfYwXhO6dwZzmyoE98c=
+github.com/keptn/go-utils v0.3.1-0.20191112090613-cf874a31b830 h1:J9CNDME07rc2w9RzP5O/8FSif2SMOfR+vTJJ7NOCAgw=
+github.com/keptn/go-utils v0.3.1-0.20191112090613-cf874a31b830/go.mod h1:R9a1HXkD+KCrhMFbLcEhtBtHGfYwXhO6dwZzmyoE98c=
+github.com/keptn/go-utils v0.4.0 h1:rVIXiYr05moKJIyorULbC3F/UcmKK7Yr3uNvh6pb5xQ=
+github.com/keptn/go-utils v0.4.0/go.mod h1:R9a1HXkD+KCrhMFbLcEhtBtHGfYwXhO6dwZzmyoE98c=
+github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
+github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+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/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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
+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/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 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+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 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+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/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ=
+github.com/nats-io/go-nats v1.7.0/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0=
+github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
+github.com/nats-io/nuid v1.0.0/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+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.10.1/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.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
+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/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_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/common v0.2.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/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/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
+github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/testify 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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
+github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
+go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.mongodb.org/mongo-driver v1.1.1 h1:Sq1fR+0c58RME5EoqKdjkiQAmPjmfHlZOoRI6fTUOcs=
+go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.20.2 h1:NAfh7zF0/3/HqtMvJNZ/RFrSlCE6ZTlHmKfhL/Dm1Jk=
+go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
+go.uber.org/zap v1.9.1/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-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/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-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+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-20181005035420-146acd28ed58/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-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8=
+golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
+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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190219203350-90b0e4468f99/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/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.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-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191023065245-6d3f0bb11be5 h1:KvJW0HDFZT3ZQ2kTXZ0xHbHm2BMbdJV4ff3petXiuxI=
+golang.org/x/time v0.0.0-20191023065245-6d3f0bb11be5/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+google.golang.org/api v0.3.1 h1:oJra/lMfmtm13/rgY/8i3MzjFWYXvQIAKjQ3HqofMk8=
+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 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
+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 h1:Lj2SnHtxkRGJDqnGaSjo+CCdIieEnwVazbOXILwQemk=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+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.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
+google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+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/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
+gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
+gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
+gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
+gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.4/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=
+k8s.io/api v0.0.0-20190313235455-40a48860b5ab h1:DG9A67baNpoeweOy2spF1OWHhnVY5KR7/Ek/+U1lVZc=
+k8s.io/api v0.0.0-20190313235455-40a48860b5ab/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
+k8s.io/api v0.0.0-20190606204050-af9c91bd2759/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
+k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1 h1:IS7K02iBkQXpCeieSiyJjGoLSdVOv2DbPaWHJ+ZtgKg=
+k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
+k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
+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/helm v2.12.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
+k8s.io/helm v2.14.3+incompatible h1:uzotTcZXa/b2SWVoUzM1xiCXVjI38TuxMujS/1s+3Gw=
+k8s.io/helm v2.14.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
+k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
+k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
+k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+k8s.io/utils v0.0.0-20191010214722-8d271d903fe4 h1:Gi+/O1saihwDqnlmC8Vhv1M5Sp4+rbOmK9TbsLn8ZEA=
+k8s.io/utils v0.0.0-20191010214722-8d271d903fe4/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
+pack.ag/amqp v0.11.0/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
+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=
diff --git a/lib/prometheus/prometheus.go b/lib/prometheus/prometheus.go
new file mode 100644
index 0000000..0aeaf72
--- /dev/null
+++ b/lib/prometheus/prometheus.go
@@ -0,0 +1,350 @@
+package prometheus
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "math"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+
+ keptnevents "github.com/keptn/go-utils/pkg/events"
+)
+
+const Throughput = "throughput"
+const ErrorRate = "error_rate"
+const RequestLatencyP50 = "request_latency_p50"
+const RequestLatencyP90 = "request_latency_p90"
+const RequestLatencyP95 = "request_latency_p95"
+
+type prometheusResponse struct {
+ Status string `json:"status"`
+ Data struct {
+ ResultType string `json:"resultType"`
+ Result []struct {
+ Metric struct {
+ } `json:"metric"`
+ Value []interface{} `json:"value"`
+ } `json:"result"`
+ } `json:"data"`
+}
+
+// Handler interacts with a prometheus API endpoint
+type Handler struct {
+ ApiURL string
+ Username string
+ Password string
+ Project string
+ Stage string
+ Service string
+ HTTPClient *http.Client
+ CustomFilters []*keptnevents.SLIFilter
+ CustomQueries map[string]string
+}
+
+// NewPrometheusHandler returns a new prometheus handler that interacts with the Prometheus REST API
+func NewPrometheusHandler(apiURL string, project string, stage string, service string, customFilters []*keptnevents.SLIFilter) *Handler {
+ ph := &Handler{
+ ApiURL: apiURL,
+ Project: project,
+ Stage: stage,
+ Service: service,
+ HTTPClient: &http.Client{},
+ CustomFilters: customFilters,
+ }
+
+ return ph
+}
+
+func (ph *Handler) GetSLIValue(metric string, start string, end string) (float64, error) {
+ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+
+ startUnix, err := parseUnixTimestamp(start)
+ if err != nil {
+ return 0, err
+ }
+ endUnix, _ := parseUnixTimestamp(end)
+ if err != nil {
+ return 0, err
+ }
+ query, err := ph.getMetricQuery(metric, startUnix, endUnix)
+ if err != nil {
+ return 0, err
+ }
+ queryString := ph.ApiURL + "/api/v1/query?query=" + url.QueryEscape(query) + "&time=" + strconv.FormatInt(endUnix.Unix(), 10)
+ req, err := http.NewRequest("GET", queryString, nil)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := ph.HTTPClient.Do(req)
+ if err != nil {
+ return 0, err
+ }
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+ if resp.StatusCode != 200 {
+ return 0, errors.New("metric could not be received")
+ }
+
+ prometheusResult := &prometheusResponse{}
+
+ err = json.Unmarshal(body, prometheusResult)
+ if err != nil {
+ return 0, err
+ }
+
+ if len(prometheusResult.Data.Result) == 0 || len(prometheusResult.Data.Result[0].Value) == 0 {
+ // for the error rate query, the result is received with no value if the error rate is 0, so we have to assume that's OK at this point
+ return 0, nil
+ }
+
+ parsedValue := fmt.Sprintf("%v", prometheusResult.Data.Result[0].Value[1])
+ floatValue, err := strconv.ParseFloat(parsedValue, 64)
+ if err != nil {
+ return 0, nil
+ }
+ return floatValue, nil
+}
+
+func (ph *Handler) getMetricQuery(metric string, start time.Time, end time.Time) (string, error) {
+ query := ph.CustomQueries[metric]
+ if query != "" {
+ query = ph.replaceQueryParameters(query, start, end)
+
+ return query, nil
+ }
+ switch metric {
+ case Throughput:
+ return ph.getThroughputQuery(start, end), nil
+ case ErrorRate:
+ return ph.getErrorRateQuery(start, end), nil
+ case RequestLatencyP50:
+ return ph.getRequestLatencyQuery("50", start, end), nil
+ case RequestLatencyP90:
+ return ph.getRequestLatencyQuery("90", start, end), nil
+ case RequestLatencyP95:
+ return ph.getRequestLatencyQuery("95", start, end), nil
+ default:
+ return "", errors.New("unsupported SLI")
+ }
+}
+
+func (ph *Handler) getThroughputQuery(start time.Time, end time.Time) string {
+ if ph.CustomQueries != nil && ph.CustomQueries["throughput"] != "" {
+ query := ph.CustomQueries["throughput"]
+ query = ph.replaceQueryParameters(query, start, end)
+ return query
+ }
+ return ph.getDefaultThroughputQuery(start, end)
+}
+
+func (ph *Handler) replaceQueryParameters(query string, start time.Time, end time.Time) string {
+ for _, filter := range ph.CustomFilters {
+ filter.Value = strings.Replace(filter.Value, "'", "", -1)
+ filter.Value = strings.Replace(filter.Value, "\"", "", -1)
+ query = strings.Replace(query, "$"+filter.Key, filter.Value, -1)
+ query = strings.Replace(query, "$"+strings.ToUpper(filter.Key), filter.Value, -1)
+ }
+ query = strings.Replace(query, "$PROJECT", ph.Project, -1)
+ query = strings.Replace(query, "$STAGE", ph.Stage, -1)
+ query = strings.Replace(query, "$SERVICE", ph.Service, -1)
+ query = strings.Replace(query, "$project", ph.Project, -1)
+ query = strings.Replace(query, "$stage", ph.Stage, -1)
+ query = strings.Replace(query, "$service", ph.Service, -1)
+ durationString := strconv.FormatInt(getDurationInSeconds(start, end), 10) + "s"
+
+ query = strings.Replace(query, "$DURATION_SECONDS", durationString, -1)
+ return query
+}
+
+func (ph *Handler) getDefaultThroughputQuery(start time.Time, end time.Time) string {
+ filterExpr := ph.getDefaultFilterExpression()
+ durationString := strconv.FormatInt(getDurationInSeconds(start, end), 10) + "s"
+ // e.g. sum(rate(http_requests_total{job="carts-sockshop-dev"}[30m]))&time=1571649085
+ /*
+ {
+ "status": "success",
+ "data": {
+ "resultType": "vector",
+ "result": [
+ {
+ "metric": {},
+ "value": [
+ 1571649085,
+ "0.20111420612813372"
+ ]
+ }
+ ]
+ }
+ }
+ */
+ return "sum(rate(http_requests_total{" + filterExpr + "}[" + durationString + "]))"
+}
+
+func (ph *Handler) getErrorRateQuery(start time.Time, end time.Time) string {
+ if ph.CustomQueries != nil && ph.CustomQueries["error_rate"] != "" {
+ query := ph.CustomQueries["error_rate"]
+ query = ph.replaceQueryParameters(query, start, end)
+ return query
+ }
+ return ph.getDefaultErrorRateQuery(start, end)
+}
+
+func (ph *Handler) getDefaultErrorRateQuery(start time.Time, end time.Time) string {
+ filterExpr := ph.getDefaultFilterExpression()
+ durationString := strconv.FormatInt(getDurationInSeconds(start, end), 10) + "s"
+ // e.g. sum(rate(http_requests_total{job="carts-sockshop-dev",status!~'2..'}[30m]))/sum(rate(http_requests_total{job="carts-sockshop-dev"}[30m]))&time=1571649085
+ /*
+ with value:
+ {
+ "status": "success",
+ "data": {
+ "resultType": "vector",
+ "result": [
+ {
+ "metric": {},
+ "value": [
+ 1571649085,
+ "1.00505917125441"
+ ]
+ }
+ ]
+ }
+ }
+
+ no value (error rate 0):
+ {
+ "status": "success",
+ "data": {
+ "resultType": "vector",
+ "result": []
+ }
+ }
+ */
+ return "sum(rate(http_requests_total{" + filterExpr + ",status!~'2..'}[" + durationString + "]))/sum(rate(http_requests_total{" + filterExpr + "}[" + durationString + "]))"
+}
+
+func (ph *Handler) getRequestLatencyQuery(percentile string, start time.Time, end time.Time) string {
+ if ph.CustomQueries != nil {
+ query := ""
+ switch percentile {
+ case "50":
+ query = ph.CustomQueries["response_time_p50"]
+ break
+ case "90":
+ query = ph.CustomQueries["response_time_p90"]
+ break
+ case "95":
+ query = ph.CustomQueries["response_time_p95"]
+ break
+ default:
+ query = ""
+ }
+ if query != "" {
+ query = ph.replaceQueryParameters(query, start, end)
+ return query
+ }
+ }
+ return ph.getDefaultRequestLatencyQuery(start, end, percentile)
+}
+
+func (ph *Handler) getDefaultRequestLatencyQuery(start time.Time, end time.Time, percentile string) string {
+ filterExpr := ph.getDefaultFilterExpression()
+ durationString := strconv.FormatInt(getDurationInSeconds(start, end), 10) + "s"
+ // e.g. histogram_quantile(0.95, sum(rate(http_response_time_milliseconds_bucket{job='carts-sockshop-dev'}[30m])) by (le))&time=1571649085
+ /*
+ {
+ "status": "success",
+ "data": {
+ "resultType": "vector",
+ "result": [
+ {
+ "metric": {},
+ "value": [
+ 1571649085,
+ "4.607481671642585"
+ ]
+ }
+ ]
+ }
+ }
+ */
+ return "histogram_quantile(0." + percentile + ",sum(rate(http_response_time_milliseconds_bucket{" + filterExpr + "}[" + durationString + "]))by(le))"
+}
+
+func (ph *Handler) getDefaultFilterExpression() string {
+ filterExpression := ""
+ jobFilterFound := false
+ if ph.CustomFilters != nil && len(ph.CustomFilters) > 0 {
+ for _, filter := range ph.CustomFilters {
+ if filter.Key == "job" {
+ jobFilterFound = true
+ }
+ /* if no operator has been included in the label filter, use exact matching (=), e.g.
+ e.g.:
+ key: handler
+ value: ItemsController
+ */
+ if !strings.HasPrefix(filter.Value, "=") && !strings.HasPrefix(filter.Value, "!=") && !strings.HasPrefix(filter.Value, "=~") && !strings.HasPrefix(filter.Value, "!~") {
+ filter.Value = strings.Replace(filter.Value, "'", "", -1)
+ filter.Value = strings.Replace(filter.Value, "\"", "", -1)
+ if filterExpression != "" {
+ filterExpression = filterExpression + "," + filter.Key + "='" + filter.Value + "'"
+ } else {
+ filterExpression = filter.Key + "='" + filter.Value + "'"
+ }
+
+ } else {
+ /* if a valid operator (=, !=, =~, !~) is prepended to the value, use that one
+ e.g.:
+ key: handler
+ value: !=HealthCheckController
+
+ OR
+
+ key: handler
+ value: =~.+ItemsController|.+VersionController
+ */
+ filter.Value = strings.Replace(filter.Value, "\"", "'", -1)
+ if filterExpression != "" {
+ filterExpression = filterExpression + "," + filter.Key + filter.Value
+ } else {
+ filterExpression = filter.Key + filter.Value
+ }
+ }
+ }
+ }
+ if !jobFilterFound {
+ if filterExpression != "" {
+ filterExpression = "job='" + ph.Service + "-" + ph.Project + "-" + ph.Stage + "-canary'" + "," + filterExpression
+ } else {
+ filterExpression = "job='" + ph.Service + "-" + ph.Project + "-" + ph.Stage + "-canary'"
+ }
+
+ }
+ return filterExpression
+}
+
+func parseUnixTimestamp(timestamp string) (time.Time, error) {
+ parsedTime, err := time.Parse(time.RFC3339, timestamp)
+ if err == nil {
+ return parsedTime, nil
+ }
+
+ timestampInt, err := strconv.ParseInt(timestamp, 10, 64)
+ if err != nil {
+ return time.Now(), err
+ }
+ unix := time.Unix(timestampInt, 0)
+ return unix, nil
+}
+
+func getDurationInSeconds(start, end time.Time) int64 {
+ seconds := end.Sub(start).Seconds()
+ return int64(math.Ceil(seconds))
+}
diff --git a/lib/prometheus/prometheus_test.go b/lib/prometheus/prometheus_test.go
new file mode 100644
index 0000000..6c8f378
--- /dev/null
+++ b/lib/prometheus/prometheus_test.go
@@ -0,0 +1,375 @@
+package prometheus
+
+import (
+ "github.com/stretchr/testify/assert"
+ "golang.org/x/net/context"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ keptnevents "github.com/keptn/go-utils/pkg/events"
+)
+
+func testingHTTPClient(handler http.Handler) (*http.Client, func()) {
+ s := httptest.NewServer(handler)
+
+ cli := &http.Client{
+ Transport: &http.Transport{
+ DialContext: func(_ context.Context, network, _ string) (net.Conn, error) {
+ return net.Dial(network, s.Listener.Addr().String())
+ },
+ },
+ }
+
+ return cli, s.Close
+}
+
+func TestGetErrorRateQueryWithoutFilter(t *testing.T) {
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", nil)
+
+ start := time.Unix(1571649084, 0)
+ end := time.Unix(1571649085, 0)
+ query := ph.getErrorRateQuery(start, end)
+
+ expectedQuery := "sum(rate(http_requests_total{job='carts-sockshop-dev-canary',status!~'2..'}[1s]))/sum(rate(http_requests_total{job='carts-sockshop-dev-canary'}[1s]))"
+
+ if strings.Compare(strings.Replace(query, " ", "", -1), strings.Replace(expectedQuery, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedQuery + "\n got: " + query)
+ }
+}
+
+func TestGetErrorRateQueryWithFilter(t *testing.T) {
+
+ var customFilters []*keptnevents.SLIFilter
+
+ customFilters = append(customFilters, &keptnevents.SLIFilter{
+ Key: "handler",
+ Value: "=~'ItemsController'",
+ })
+
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", customFilters)
+
+ start := time.Unix(1571649084, 0)
+ end := time.Unix(1571649085, 0)
+ query := ph.getErrorRateQuery(start, end)
+
+ expectedQuery := "sum(rate(http_requests_total{job='carts-sockshop-dev-canary',handler=~'ItemsController',status!~'2..'}[1s]))/sum(rate(http_requests_total{job='carts-sockshop-dev-canary',handler=~'ItemsController'}[1s]))"
+
+ if strings.Compare(strings.Replace(query, " ", "", -1), strings.Replace(expectedQuery, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedQuery + "\n got: " + query)
+ }
+}
+
+func TestGetCustomErrorRateQueryWithFilter(t *testing.T) {
+
+ var customFilters []*keptnevents.SLIFilter
+
+ customQueries := map[string]string{}
+ customQueries["error_rate"] = "sum(rate(my_custom_metric{job='$SERVICE-$PROJECT-$STAGE',handler=~'$HANDLER',status!~'2..'}[$DURATION_SECONDS]))/sum(rate(my_custom_metric{job='$SERVICE-$PROJECT-$STAGE',handler=~'$HANDLER'}[$DURATION_SECONDS]))"
+
+ customFilters = append(customFilters, &keptnevents.SLIFilter{
+ Key: "handler",
+ Value: "'ItemsController'",
+ })
+
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", customFilters)
+
+ ph.CustomQueries = customQueries
+
+ start := time.Unix(1571649084, 0)
+ end := time.Unix(1571649085, 0)
+ query := ph.getErrorRateQuery(start, end)
+
+ expectedQuery := "sum(rate(my_custom_metric{job='carts-sockshop-dev',handler=~'ItemsController',status!~'2..'}[1s]))/sum(rate(my_custom_metric{job='carts-sockshop-dev',handler=~'ItemsController'}[1s]))"
+
+ if strings.Compare(strings.Replace(query, " ", "", -1), strings.Replace(expectedQuery, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedQuery + "\n got: " + query)
+ }
+}
+
+func TestGetThroughputQuery(t *testing.T) {
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", nil)
+
+ start := time.Unix(1571649084, 0)
+ end := time.Unix(1571649085, 0)
+ query := ph.getThroughputQuery(start, end)
+
+ expectedQuery := "sum(rate(http_requests_total{job='carts-sockshop-dev-canary'}[1s]))"
+
+ if strings.Compare(strings.Replace(query, " ", "", -1), strings.Replace(expectedQuery, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedQuery + "\n got: " + query)
+ }
+}
+
+func TestGetCustomThroughputQueryWithFilter(t *testing.T) {
+
+ var customFilters []*keptnevents.SLIFilter
+
+ customQueries := map[string]string{}
+ customQueries["throughput"] = "rate(my_custom_metric{job='$SERVICE-$PROJECT-$STAGE',handler=~'$HANDLER'}[$DURATION_SECONDS])"
+
+ customFilters = append(customFilters, &keptnevents.SLIFilter{
+ Key: "handler",
+ Value: "'ItemsController'",
+ })
+
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", customFilters)
+ ph.CustomQueries = customQueries
+
+ start := time.Unix(1571649084, 0)
+ end := time.Unix(1571649085, 0)
+ query := ph.getThroughputQuery(start, end)
+
+ expectedQuery := "rate(my_custom_metric{job='carts-sockshop-dev',handler=~'ItemsController'}[1s])"
+
+ if strings.Compare(strings.Replace(query, " ", "", -1), strings.Replace(expectedQuery, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedQuery + "\n got: " + query)
+ }
+}
+
+func TestGetRequestLatencyQuery(t *testing.T) {
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", nil)
+
+ start := time.Unix(1571649084, 0)
+ end := time.Unix(1571649085, 0)
+ query := ph.getRequestLatencyQuery("95", start, end)
+
+ expectedQuery := "histogram_quantile(0.95,sum(rate(http_response_time_milliseconds_bucket{job='carts-sockshop-dev-canary'}[1s]))by(le))"
+
+ if strings.Compare(strings.Replace(query, " ", "", -1), strings.Replace(expectedQuery, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedQuery + "\n got: " + query)
+ }
+}
+
+func TestGetCustomResponseTimeQueryWithFilter(t *testing.T) {
+
+ var customFilters []*keptnevents.SLIFilter
+
+ customQueries := map[string]string{}
+ customQueries["response_time_p50"] = "histogram_quantile(0.50,sum(rate(my_custom_response_time_metric{job='$SERVICE-$PROJECT-$STAGE'}[$DURATION_SECONDS]))by(le))"
+
+ customFilters = append(customFilters, &keptnevents.SLIFilter{
+ Key: "handler",
+ Value: "'ItemsController'",
+ })
+
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", customFilters)
+
+ ph.CustomQueries = customQueries
+
+ start := time.Unix(1571649084, 0)
+ end := time.Unix(1571649085, 0)
+ query := ph.getRequestLatencyQuery("50", start, end)
+
+ expectedQuery := "histogram_quantile(0.50,sum(rate(my_custom_response_time_metric{job='carts-sockshop-dev'}[1s]))by(le))"
+
+ if strings.Compare(strings.Replace(query, " ", "", -1), strings.Replace(expectedQuery, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedQuery + "\n got: " + query)
+ }
+}
+
+func TestGetCustomQuery(t *testing.T) {
+
+ customQueries := map[string]string{}
+ customQueries["custom_query"] = "my_custom_query{job='$SERVICE-$PROJECT-$STAGE'}[$DURATION_SECONDS]"
+
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", nil)
+
+ ph.CustomQueries = customQueries
+
+ start := time.Unix(1571649084, 0)
+ end := time.Unix(1571649085, 0)
+ query, _ := ph.getMetricQuery("custom_query", start, end)
+
+ expectedQuery := "my_custom_query{job='carts-sockshop-dev'}[1s]"
+
+ if strings.Compare(strings.Replace(query, " ", "", -1), strings.Replace(expectedQuery, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedQuery + "\n got: " + query)
+ }
+}
+
+func TestGetDefaultFilterExpression(t *testing.T) {
+
+ var customFilters []*keptnevents.SLIFilter
+
+ customFilters = append(customFilters, &keptnevents.SLIFilter{
+ Key: "handler",
+ Value: "ItemsController",
+ })
+
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", customFilters)
+
+ filterExpression := ph.getDefaultFilterExpression()
+
+ expectedFilterExpression := "job='carts-sockshop-dev-canary',handler='ItemsController'"
+
+ if strings.Compare(strings.Replace(expectedFilterExpression, " ", "", -1), strings.Replace(filterExpression, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedFilterExpression + "\n got: " + filterExpression)
+ }
+}
+
+func TestGetDefaultFilterExpressionWithOperand(t *testing.T) {
+
+ var customFilters []*keptnevents.SLIFilter
+
+ customFilters = append(customFilters, &keptnevents.SLIFilter{
+ Key: "handler",
+ Value: "!='ItemsController'",
+ })
+
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", customFilters)
+
+ filterExpression := ph.getDefaultFilterExpression()
+
+ expectedFilterExpression := "job='carts-sockshop-dev-canary',handler!='ItemsController'"
+
+ if strings.Compare(strings.Replace(expectedFilterExpression, " ", "", -1), strings.Replace(filterExpression, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedFilterExpression + "\n got: " + filterExpression)
+ }
+}
+
+func TestGetDefaultFilterExpressionWithJobName(t *testing.T) {
+
+ var customFilters []*keptnevents.SLIFilter
+
+ customFilters = append(customFilters, &keptnevents.SLIFilter{
+ Key: "job",
+ Value: "my-job",
+ })
+
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", customFilters)
+
+ filterExpression := ph.getDefaultFilterExpression()
+
+ expectedFilterExpression := "job='my-job'"
+
+ if strings.Compare(strings.Replace(expectedFilterExpression, " ", "", -1), strings.Replace(filterExpression, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedFilterExpression + "\n got: " + filterExpression)
+ }
+}
+
+func TestGetDefaultFilterExpressionWithSingleQuote(t *testing.T) {
+
+ var customFilters []*keptnevents.SLIFilter
+
+ customFilters = append(customFilters, &keptnevents.SLIFilter{
+ Key: "handler",
+ Value: "'ItemsController'",
+ })
+
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", customFilters)
+
+ filterExpression := ph.getDefaultFilterExpression()
+
+ expectedFilterExpression := "job='carts-sockshop-dev-canary',handler='ItemsController'"
+
+ if strings.Compare(strings.Replace(expectedFilterExpression, " ", "", -1), strings.Replace(filterExpression, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedFilterExpression + "\n got: " + filterExpression)
+ }
+}
+
+func TestGetDefaultFilterExpressionWithDoubleQuote(t *testing.T) {
+
+ var customFilters []*keptnevents.SLIFilter
+
+ customFilters = append(customFilters, &keptnevents.SLIFilter{
+ Key: "handler",
+ Value: "\"ItemsController\"",
+ })
+
+ ph := NewPrometheusHandler("prometheus", "sockshop", "dev", "carts", customFilters)
+
+ filterExpression := ph.getDefaultFilterExpression()
+
+ expectedFilterExpression := "job='carts-sockshop-dev-canary',handler='ItemsController'"
+
+ if strings.Compare(strings.Replace(expectedFilterExpression, " ", "", -1), strings.Replace(filterExpression, " ", "", -1)) != 0 {
+ t.Errorf("Expected query did not match: \n expected: " + expectedFilterExpression + "\n got: " + filterExpression)
+ }
+}
+
+func TestGetSLIValue(t *testing.T) {
+
+ okResponse := `{
+ "status": "success",
+ "data": {
+ "resultType": "vector",
+ "result": [
+ {
+ "metric": {},
+ "value": [
+ 1571649085,
+ "0.20111420612813372"
+ ]
+ }
+ ]
+ }
+ }`
+
+ h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte(okResponse))
+ })
+
+ httpClient, teardown := testingHTTPClient(h)
+ defer teardown()
+
+ ph := NewPrometheusHandler("http://prometheus", "sockshop", "dev", "carts", nil)
+ ph.HTTPClient = httpClient
+
+ start := strconv.FormatInt(time.Unix(1571649084, 0).UTC().UnixNano(), 10)
+ end := strconv.FormatInt(time.Unix(1571649085, 0).UTC().UnixNano(), 10)
+ value, _ := ph.GetSLIValue(Throughput, start, end)
+
+ assert.EqualValues(t, value, 0.20111420612813372)
+}
+
+func TestGetSLIValueWithEmptyResult(t *testing.T) {
+
+ okResponse := `{
+ "status": "success",
+ "data": {
+ "resultType": "vector",
+ "result": []
+ }
+ }`
+
+ h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte(okResponse))
+ })
+
+ httpClient, teardown := testingHTTPClient(h)
+ defer teardown()
+
+ ph := NewPrometheusHandler("http://prometheus", "sockshop", "dev", "carts", nil)
+ ph.HTTPClient = httpClient
+
+ start := strconv.FormatInt(time.Unix(1571649084, 0).UTC().UnixNano(), 10)
+ end := strconv.FormatInt(time.Unix(1571649085, 0).UTC().UnixNano(), 10)
+ value, _ := ph.GetSLIValue(Throughput, start, end)
+
+ assert.EqualValues(t, value, 0.0)
+}
+
+func TestGetSLIValueWithErrorResponse(t *testing.T) {
+ h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // w.Write([]byte(response))
+ w.WriteHeader(http.StatusBadRequest)
+ })
+
+ httpClient, teardown := testingHTTPClient(h)
+ defer teardown()
+
+ ph := NewPrometheusHandler("http://prometheus", "sockshop", "dev", "carts", nil)
+ ph.HTTPClient = httpClient
+
+ start := strconv.FormatInt(time.Unix(1571649084, 0).UTC().UnixNano(), 10)
+ end := strconv.FormatInt(time.Unix(1571649085, 0).UTC().UnixNano(), 10)
+ value, err := ph.GetSLIValue(Throughput, start, end)
+
+ assert.EqualValues(t, value, 0.0)
+ assert.NotNil(t, err, nil)
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..5971625
--- /dev/null
+++ b/main.go
@@ -0,0 +1,346 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "github.com/keptn-contrib/prometheus-sli-service/lib/prometheus"
+ "gopkg.in/yaml.v2"
+ "log"
+ "net/url"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/cloudevents/sdk-go/pkg/cloudevents"
+ "github.com/cloudevents/sdk-go/pkg/cloudevents/client"
+ cloudeventshttp "github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http"
+ "github.com/cloudevents/sdk-go/pkg/cloudevents/types"
+ "github.com/google/uuid"
+ "github.com/kelseyhightower/envconfig"
+ keptnevents "github.com/keptn/go-utils/pkg/events"
+ keptnutils "github.com/keptn/go-utils/pkg/utils"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ v1 "k8s.io/client-go/kubernetes/typed/core/v1"
+)
+
+const configservice = "CONFIGURATION_SERVICE"
+const eventbroker = "EVENTBROKER"
+
+type envConfig struct {
+ // Port on which to listen for cloudevents
+ Port int `envconfig:"RCV_PORT" default:"8080"`
+ Path string `envconfig:"RCV_PATH" default:"/"`
+}
+
+type prometheusCredentials struct {
+ URL string `json:"url" yaml:"url"`
+ User string `json:"user" yaml:"user"`
+ Password string `json:"password" yaml:"password"`
+}
+
+func main() {
+ var env envConfig
+ if err := envconfig.Process("", &env); err != nil {
+ log.Fatalf("Failed to process env var: %s", err)
+ }
+ os.Exit(_main(os.Args[1:], env))
+}
+
+func _main(args []string, env envConfig) int {
+
+ ctx := context.Background()
+
+ t, err := cloudeventshttp.New(
+ cloudeventshttp.WithPort(env.Port),
+ cloudeventshttp.WithPath(env.Path),
+ )
+
+ if err != nil {
+ log.Fatalf("failed to create transport, %v", err)
+ }
+ c, err := client.New(t)
+ if err != nil {
+ log.Fatalf("failed to create client, %v", err)
+ }
+
+ log.Fatalf("failed to start receiver: %s", c.StartReceiver(ctx, gotEvent))
+
+ return 0
+}
+
+func gotEvent(ctx context.Context, event cloudevents.Event) error {
+
+ switch event.Type() {
+ case keptnevents.InternalGetSLIEventType:
+ return retrieveMetrics(event) // backwards compatibility to Keptn versions <= 0.5.x
+ default:
+ return errors.New("received unknown event type")
+ }
+}
+
+func retrieveMetrics(event cloudevents.Event) error {
+ var shkeptncontext string
+ event.Context.ExtensionAs("shkeptncontext", &shkeptncontext)
+ eventData := &keptnevents.InternalGetSLIEventData{}
+ err := event.DataAs(eventData)
+ if err != nil {
+ return err
+ }
+
+ // don't continue if SLIProvider != prometheus
+ if eventData.SLIProvider != "prometheus" {
+ return nil
+ }
+
+ stdLogger := keptnutils.NewLogger(shkeptncontext, event.Context.GetID(), "prometheus-sli-service")
+ stdLogger.Info("Retrieving prometheus metrics")
+ kubeClient, err := keptnutils.GetKubeAPI(true)
+ if err != nil {
+ stdLogger.Error("could not create kube client")
+ return errors.New("could not create kube client")
+ }
+ prometheusApiURL, err := getPrometheusApiURL(eventData.Project, kubeClient, stdLogger)
+
+ if err != nil {
+ return err
+ }
+
+ // get custom metrics for Keptn installation
+ customQueries, err := getGlobalCustomQueries(kubeClient, stdLogger)
+
+ if err != nil {
+ stdLogger.Error("Failed to get global custom queries")
+ stdLogger.Error(err.Error())
+ return err
+ }
+
+ // get custom metrics for project
+ projectCustomQueries, err := getCustomQueriesForProject(eventData.Project, kubeClient, stdLogger)
+
+ if err != nil {
+ stdLogger.Error("Failed to get custom queries for project " + eventData.Project)
+ stdLogger.Error(err.Error())
+ return err
+ }
+
+ log.Printf("Custom Query Config\n")
+
+ // make sure custom queries exists
+ if customQueries == nil {
+ customQueries = make(map[string]string)
+ } else {
+ for k, v := range customQueries {
+ log.Printf("\tFound custom query %s with value %s\n", k, v)
+ }
+ }
+
+ if projectCustomQueries != nil {
+ log.Println("Merging custom queries with projectCustomQueries")
+ // merge global custom queries and project custom queries
+ for k, v := range projectCustomQueries {
+ // overwrite / append project custom query on global custom queries
+ customQueries[k] = v
+ log.Printf("\tOverwriting custom query %s with value %s\n", k, v)
+ }
+ }
+
+ prometheusHandler := prometheus.NewPrometheusHandler(prometheusApiURL, eventData.Project, eventData.Stage, eventData.Service, eventData.CustomFilters)
+
+ if customQueries != nil {
+ prometheusHandler.CustomQueries = customQueries
+ }
+
+ var sliResults []*keptnevents.SLIResult
+
+ for _, indicator := range eventData.Indicators {
+ stdLogger.Info("Fetching indicator: " + indicator)
+ sliValue, err := prometheusHandler.GetSLIValue(indicator, eventData.Start, eventData.End)
+ if err != nil {
+ sliResults = append(sliResults, &keptnevents.SLIResult{
+ Metric: indicator,
+ Value: 0,
+ Success: false,
+ Message: err.Error(),
+ })
+ } else {
+ sliResults = append(sliResults, &keptnevents.SLIResult{
+ Metric: indicator,
+ Value: sliValue,
+ Success: true,
+ })
+ }
+ }
+ return sendInternalGetSLIDoneEvent(shkeptncontext, eventData.Project, eventData.Service, eventData.Stage,
+ sliResults, eventData.Start, eventData.End, eventData.TestStrategy, eventData.DeploymentStrategy)
+}
+
+const keptnPrometheusSliConfigMapName = "prometheus-sli-config"
+
+// Return Custom Queries for Keptn Installation
+func getGlobalCustomQueries(kubeClient v1.CoreV1Interface, logger *keptnutils.Logger) (map[string]string, error) {
+ logger.Info(fmt.Sprintf("Checking for custom SLI queries for Keptn installation (querying %s)", keptnPrometheusSliConfigMapName))
+
+ configMap, err := kubeClient.ConfigMaps("keptn").Get(keptnPrometheusSliConfigMapName, metav1.GetOptions{})
+ if err != nil {
+ logger.Info("No global custom queries defined")
+ return nil, nil
+ }
+
+ customQueries := make(map[string]string)
+ err = yaml.Unmarshal([]byte(configMap.Data["custom-queries"]), &customQueries)
+
+ if err != nil {
+ logger.Info("Global custom queries found, but could not parse them: " + err.Error())
+ return nil, err
+ }
+ logger.Info("Global custom queries found and parsed")
+ return customQueries, nil
+}
+
+// Return Custom Queries for Keptn Installation
+func getCustomQueriesForProject(project string, kubeClient v1.CoreV1Interface, logger *keptnutils.Logger) (map[string]string, error) {
+ logger.Info(fmt.Sprintf("Checking for custom SLI queries for Keptn installation (querying %s)", keptnPrometheusSliConfigMapName))
+
+ configMap, err := kubeClient.ConfigMaps("keptn").Get(keptnPrometheusSliConfigMapName+"-"+project, metav1.GetOptions{})
+ if err != nil {
+ logger.Info("No custom queries defined for project " + project)
+ return nil, nil
+ }
+
+ customQueries := make(map[string]string)
+ err = yaml.Unmarshal([]byte(configMap.Data["custom-queries"]), &customQueries)
+
+ if err != nil {
+ logger.Info("Project custom queries found, but could not parse them: " + err.Error())
+ return nil, err
+ }
+ logger.Info("Project custom queries found and parsed")
+ return customQueries, nil
+}
+
+func getPrometheusApiURL(project string, kubeClient v1.CoreV1Interface, logger *keptnutils.Logger) (string, error) {
+ logger.Info("Checking if external prometheus instance has been defined for project " + project)
+ // check if secret 'prometheus-credentials- exists
+
+ secret, err := kubeClient.Secrets("keptn").Get("prometheus-credentials-"+project, metav1.GetOptions{})
+
+ // return cluster-internal prometheus URL if no secret has been found
+ if err != nil {
+ logger.Info("No external prometheus instance defined for project " + project + ". Using default: http://prometheus-service.monitoring.svc.cluster.local:8080")
+ return "http://prometheus-service.monitoring.svc.cluster.local:8080", nil
+ }
+
+ /*
+ data format:
+ url: string
+ user: string
+ password: string
+ */
+ pc := &prometheusCredentials{}
+ err = yaml.Unmarshal(secret.Data["prometheus-credentials"], pc)
+
+ if err != nil {
+ logger.Error("Could not parse credentials for external prometheus instance: " + err.Error())
+ return "", errors.New("invalid credentials format found in secret 'prometheus-credentials-" + project)
+ }
+ logger.Info("Using external prometheus instance for project " + project + ": " + pc.URL)
+ prometheusURL := generatePrometheusURL(pc)
+
+ return prometheusURL, nil
+}
+
+func generatePrometheusURL(pc *prometheusCredentials) string {
+ prometheusURL := pc.URL
+
+ credentialsString := ""
+
+ if pc.User != "" && pc.Password != "" {
+ credentialsString = url.QueryEscape(pc.User) + ":" + url.QueryEscape(pc.Password) + "@"
+ }
+ if strings.HasPrefix(prometheusURL, "https://") {
+ prometheusURL = strings.TrimPrefix(prometheusURL, "https://")
+ prometheusURL = "https://" + credentialsString + prometheusURL
+ } else if strings.HasPrefix(prometheusURL, "http://") {
+ prometheusURL = strings.TrimPrefix(prometheusURL, "http://")
+ prometheusURL = "http://" + credentialsString + prometheusURL
+ } else {
+ // assume https transport
+ prometheusURL = "https://" + credentialsString + prometheusURL
+ }
+ return strings.Replace(prometheusURL, " ", "", -1)
+}
+
+func sendInternalGetSLIDoneEvent(shkeptncontext string, project string,
+ service string, stage string, indicatorValues []*keptnevents.SLIResult, start string, end string, testStrategy string, deploymentStrategy string) error {
+
+ source, _ := url.Parse("prometheus-sli-service")
+ contentType := "application/json"
+
+ getSLIEvent := keptnevents.InternalGetSLIDoneEventData{
+ Project: project,
+ Service: service,
+ Stage: stage,
+ IndicatorValues: indicatorValues,
+ Start: start,
+ End: end,
+ TestStrategy: testStrategy,
+ DeploymentStrategy: deploymentStrategy,
+ }
+ event := cloudevents.Event{
+ Context: cloudevents.EventContextV02{
+ ID: uuid.New().String(),
+ Time: &types.Timestamp{Time: time.Now()},
+ Type: keptnevents.InternalGetSLIDoneEventType,
+ Source: types.URLRef{URL: *source},
+ ContentType: &contentType,
+ Extensions: map[string]interface{}{"shkeptncontext": shkeptncontext},
+ }.AsV02(),
+ Data: getSLIEvent,
+ }
+
+ return sendEvent(event)
+}
+
+func sendEvent(event cloudevents.Event) error {
+ endPoint, err := getServiceEndpoint(eventbroker)
+ if err != nil {
+ return errors.New("Failed to retrieve endpoint of eventbroker. %s" + err.Error())
+ }
+
+ if endPoint.Host == "" {
+ return errors.New("Host of eventbroker not set")
+ }
+
+ transport, err := cloudeventshttp.New(
+ cloudeventshttp.WithTarget(endPoint.String()),
+ cloudeventshttp.WithEncoding(cloudeventshttp.StructuredV02),
+ )
+ if err != nil {
+ return errors.New("Failed to create transport:" + err.Error())
+ }
+
+ c, err := client.New(transport)
+ if err != nil {
+ return errors.New("Failed to create HTTP client:" + err.Error())
+ }
+
+ if _, err := c.Send(context.Background(), event); err != nil {
+ return errors.New("Failed to send cloudevent:, " + err.Error())
+ }
+ return nil
+}
+
+// getServiceEndpoint gets an endpoint stored in an environment variable and sets http as default scheme
+func getServiceEndpoint(service string) (url.URL, error) {
+ url, err := url.Parse(os.Getenv(service))
+ if err != nil {
+ return *url, fmt.Errorf("Failed to retrieve value from ENVIRONMENT_VARIABLE: %s", service)
+ }
+
+ if url.Scheme == "" {
+ url.Scheme = "http"
+ }
+
+ return *url, nil
+}
diff --git a/main_test.go b/main_test.go
new file mode 100644
index 0000000..ff787b3
--- /dev/null
+++ b/main_test.go
@@ -0,0 +1,60 @@
+package main
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+type test struct {
+ input prometheusCredentials
+ want string
+}
+
+func TestGeneratePrometheusURL(t *testing.T) {
+ tests := []test{
+ {
+ input: prometheusCredentials{
+ URL: "http://prometheus",
+ User: "test",
+ Password: "test",
+ },
+ want: "http://test:test@prometheus",
+ },
+ {
+ input: prometheusCredentials{
+ URL: "https://prometheus",
+ User: "test",
+ Password: "test",
+ },
+ want: "https://test:test@prometheus",
+ },
+ {
+ input: prometheusCredentials{
+ URL: "prometheus",
+ User: "test",
+ Password: "test",
+ },
+ want: "https://test:test@prometheus",
+ },
+ {
+ input: prometheusCredentials{
+ URL: " prometheus",
+ User: "test",
+ Password: "test",
+ },
+ want: "https://test:test@prometheus",
+ },
+ {
+ input: prometheusCredentials{
+ URL: "prometheus",
+ User: "",
+ Password: "test",
+ },
+ want: "https://prometheus",
+ },
+ }
+ for _, test := range tests {
+ url := generatePrometheusURL(&test.input)
+ assert.EqualValues(t, test.want, url)
+ }
+}
diff --git a/misc/custom_queries.yaml b/misc/custom_queries.yaml
new file mode 100644
index 0000000..38ef9bb
--- /dev/null
+++ b/misc/custom_queries.yaml
@@ -0,0 +1,8 @@
+kind: ConfigMap
+apiVersion: v1
+metadata:
+ name: prometheus-sli-config-sockshop
+ namespace: keptn
+data:
+ custom-queries: |
+ cpu_usage: avg(rate(container_cpu_usage_seconds_total{namespace="$PROJECT-$STAGE",pod_name=~"$SERVICE-primary-.*"}[5m]))
diff --git a/misc/mock_secret.yaml b/misc/mock_secret.yaml
new file mode 100644
index 0000000..adb5c51
--- /dev/null
+++ b/misc/mock_secret.yaml
@@ -0,0 +1,5 @@
+# apply with kubectl create secret -n keptn generic prometheus-credentials-sockshop --from-file=prometheus-credentials=./mock_secret.yaml
+
+user: test
+password: test
+url: http://prometheus-service.monitoring.svc.cluster.local:8080
diff --git a/releasenotes/releasenotes_V0.1.0.md b/releasenotes/releasenotes_V0.1.0.md
new file mode 100644
index 0000000..e1b2454
--- /dev/null
+++ b/releasenotes/releasenotes_V0.1.0.md
@@ -0,0 +1,17 @@
+# Release Notes 0.1.0
+
+This service is used for retrieving metrics from a prometheus API endpoint. Per default, it fetches metrics from the prometheus instance set up by Keptn
+(`prometheus-service.monitoring.svc.cluster.local:8080`), but it can also be configures to use any reachable Prometheus endpoint using basic authentication by providing the credentials
+via a secret in the `keptn` namespace of the cluster.
+
+The supported default SLIs are:
+
+ - throughput
+ - error_rate
+ - response_time_p50
+ - response_time_p90
+ - response_time_p95
+
+The queries for those SLIs can be overridden by providing custom Prometheus queries. Similarly, it is also possible to add additional custom SLIs and their queries.
+For detailed instructions, please head to the [README section](https://github.com/keptn-contrib/prometheus-sli-service/tree/0.1.0).
+
diff --git a/skaffold.yaml b/skaffold.yaml
new file mode 100644
index 0000000..280cb3a
--- /dev/null
+++ b/skaffold.yaml
@@ -0,0 +1,11 @@
+apiVersion: skaffold/v1beta13
+kind: Config
+build:
+ artifacts:
+ - image: keptn/prometheus-sli-service
+ docker: # beta describes an artifact built from a Dockerfile.
+ dockerfile: Dockerfile
+deploy:
+ kubectl:
+ manifests:
+ - deploy/service.yaml
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 0000000..fb6395b
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,13 @@
+sonar.projectKey=keptn-contrib_prometheus-sli-service
+sonar.organization=keptn-contrib
+
+# this is the name and version displayed in the SonarCloud UI.
+sonar.projectName=prometheus-sli-service
+sonar.projectVersion=VERSION_PLACEHOLDER
+
+# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
+# This property is optional if sonar.modules is set.
+sonar.sources=.
+
+# Encoding of the source code. Default is default system encoding
+#sonar.sourceEncoding=UTF-8
\ No newline at end of file
diff --git a/version b/version
new file mode 100644
index 0000000..6e8bf73
--- /dev/null
+++ b/version
@@ -0,0 +1 @@
+0.1.0
diff --git a/writeManifest.sh b/writeManifest.sh
new file mode 100755
index 0000000..68c5906
--- /dev/null
+++ b/writeManifest.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+DATE="$(date +'%Y%m%d.%H%M')"
+GIT_SHA="$(git rev-parse --short HEAD)"
+REPO_URL=https://github.com/$TRAVIS_REPO_SLUG
+
+sed -i 's~MANIFEST_REPOSITORY~'"$REPO_URL"'~' MANIFEST
+sed -i 's~MANIFEST_BRANCH~'"$TRAVIS_BRANCH"'~' MANIFEST
+sed -i 's~MANIFEST_COMMIT~'"$TRAVIS_COMMIT"'~' MANIFEST
+sed -i 's~MANIFEST_TRAVIS_JOB_URL~'"$TRAVIS_JOB_WEB_URL"'~' MANIFEST
+sed -i 's~MANIFEST_DATE~'"$DATE"'~' MANIFEST
\ No newline at end of file