├── .gitignore
├── assets
└── grafonnet-example.png
├── ui
├── style
│ ├── app.css
│ └── components
│ │ ├── editor.css
│ │ ├── graph.css
│ │ ├── control.css
│ │ └── empty.css
└── js
│ ├── reducers
│ ├── index.js
│ └── runReducer.js
│ ├── actions
│ ├── run.js
│ ├── types.js
│ ├── themeUpdate.js
│ ├── wrapText.js
│ └── codeUpdate.js
│ ├── components
│ ├── graph.js
│ ├── empty.js
│ ├── control.js
│ └── editor.js
│ ├── app.js
│ └── store.js
├── .babelrc
├── webpack.dev.js
├── handlers
├── health.go
└── run.go
├── webpack.prod.js
├── public
└── index.html
├── grafana
├── models.go
├── cleaner.go
├── priority_queue.go
├── priority_queue_test.go
└── grafana.go
├── webpack.common.js
├── Dockerfile
├── main.go
├── README.md
├── go.mod
├── application.yml.sample
├── Makefile
├── package.json
├── config
└── config.go
├── LICENSE
└── go.sum
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | out
4 | application.yml
5 |
--------------------------------------------------------------------------------
/assets/grafonnet-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lahsivjar/grafonnet-playground/HEAD/assets/grafonnet-example.png
--------------------------------------------------------------------------------
/ui/style/app.css:
--------------------------------------------------------------------------------
1 | body, html {
2 | margin: 0px;
3 | height: 100%;
4 | }
5 |
6 | .root {
7 | height: 100%;
8 | }
9 |
10 | .app {
11 | height: 100%;
12 | }
13 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/preset-react"
5 | ],
6 | "plugins": [
7 | "@babel/plugin-proposal-class-properties"
8 | ]
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const merge = require("webpack-merge");
2 | const common = require("./webpack.common.js");
3 |
4 | module.exports = merge(common, {
5 | devtool: "inline-source-map"
6 | });
7 |
8 |
--------------------------------------------------------------------------------
/ui/js/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import RunReducer from './runReducer'
3 |
4 | const reducers = combineReducers({
5 | RunReducer: RunReducer,
6 | });
7 |
8 | export default reducers;
9 |
--------------------------------------------------------------------------------
/ui/style/components/editor.css:
--------------------------------------------------------------------------------
1 | .editor {
2 | height: calc(100vh - 48px);
3 | overflow: auto;
4 | }
5 |
6 | .react-codemirror2 {
7 | height: 100%;
8 | }
9 |
10 | .CodeMirror {
11 | font-family: monospace;
12 | height: 100%;
13 | direction: ltr;
14 | }
15 |
--------------------------------------------------------------------------------
/handlers/health.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | // HealthCheckHandler provides a health check endpoint for probes
10 | func HealthCheckHandler(c *gin.Context) {
11 | c.JSON(http.StatusOK, gin.H{
12 | "status": "up",
13 | })
14 | }
15 |
--------------------------------------------------------------------------------
/ui/style/components/graph.css:
--------------------------------------------------------------------------------
1 | .graph {
2 | height: 100%;
3 | width: 100%;
4 | }
5 |
6 | .graph-frame {
7 | height: 100%;
8 | width: 100%;
9 | }
10 |
11 | iframe {
12 | margin: none;
13 | padding: none;
14 | border: none;
15 | line-height: 0;
16 | display: block;
17 | }
18 |
--------------------------------------------------------------------------------
/ui/js/actions/run.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | import { RUN } from './types';
4 |
5 | export function Run(data) {
6 |
7 | return dispatch => dispatch({
8 | type: RUN,
9 | payload: axios.post('/playground/api/v1/run', data)
10 | .then(res => res.data)
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/ui/js/actions/types.js:
--------------------------------------------------------------------------------
1 | export const RUN = 'RUN';
2 | export const RUN_PENDING = 'RUN_PENDING';
3 | export const RUN_FULFILLED = 'RUN_FULFILLED';
4 | export const RUN_REJECTED = 'RUN_REJECTED';
5 |
6 | export const CODE_UPDATE = 'CODE_UPDATE';
7 | export const THEME_UPDATE = 'THEME_UPDATE';
8 | export const WRAP_TEXT = 'WRAP_TEXT';
9 |
--------------------------------------------------------------------------------
/ui/style/components/control.css:
--------------------------------------------------------------------------------
1 | .control {
2 | height: 48px;
3 | background: #81D4FA;
4 | }
5 |
6 | .control-select-theme {
7 | font-family: '"Roboto", "Helvetica", "Arial", sans-serif';
8 | font-weight: 400;
9 | font-size: 16px;
10 | padding: 0px 6px;
11 | }
12 |
13 | .control-select-container {
14 | padding: 6px;
15 | }
16 |
--------------------------------------------------------------------------------
/ui/js/actions/themeUpdate.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { set } from 'idb-keyval';
3 |
4 | import { THEME_UPDATE } from './types';
5 |
6 | export function ThemeUpdate(theme) {
7 | const payload = {
8 | theme: theme,
9 | };
10 |
11 | set('theme', payload)
12 | .catch(err => console.error('Failed to set theme in local storage'))
13 |
14 | return dispatch => dispatch({
15 | type: THEME_UPDATE,
16 | payload: payload,
17 | });
18 | }
19 |
--------------------------------------------------------------------------------
/ui/js/actions/wrapText.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { set } from 'idb-keyval';
3 |
4 | import { WRAP_TEXT } from './types';
5 |
6 | export function WrapText(isWrapEnabled) {
7 | const payload = {
8 | wrap: isWrapEnabled,
9 | };
10 |
11 | set('wrap', payload)
12 | .catch(err => console.error('Failed to set wrap in local storage'))
13 |
14 | return dispatch => dispatch({
15 | type: WRAP_TEXT,
16 | payload: payload,
17 | });
18 | }
19 |
--------------------------------------------------------------------------------
/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const merge = require("webpack-merge");
2 | const TerserPlugin = require('terser-webpack-plugin');
3 | const common = require("./webpack.common.js");
4 | const webpack = require("webpack");
5 |
6 | module.exports = merge(common, {
7 | devtool: "source-map",
8 | plugins: [
9 | new webpack.DefinePlugin({
10 | "process.env.NODE_ENV": JSON.stringify("production")
11 | }),
12 | ],
13 | optimization: {
14 | minimizer: [new TerserPlugin()],
15 | },
16 | });
17 |
18 |
--------------------------------------------------------------------------------
/ui/js/actions/codeUpdate.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { throttle } from 'throttle-debounce';
3 | import { set } from 'idb-keyval';
4 |
5 | import { CODE_UPDATE } from './types';
6 |
7 | export function CodeUpdate(code) {
8 | const payload = {
9 | code: code,
10 | };
11 |
12 | throttledSave(payload);
13 |
14 | return dispatch => dispatch({
15 | type: CODE_UPDATE,
16 | payload: payload,
17 | });
18 | }
19 |
20 | const throttledSave = throttle(100, (payload) => {
21 | set('code', payload)
22 | .catch(err => console.error('Failed to set code in local storage'))
23 | })
24 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Grafonnet playground
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/grafana/models.go:
--------------------------------------------------------------------------------
1 | package grafana
2 |
3 | // CreateRequest models parameters required for creating/updating dashboard
4 | type CreateRequest struct {
5 | Dashboard interface{} `json:"dashboard"`
6 | Overwrite bool `json:"overwrite"`
7 | FolderID int `json:"folderId,omitempty"`
8 | }
9 |
10 | // CreateResponse models response from grafana for creating/updating dashboard
11 | type CreateResponse struct {
12 | ID int `json:"id"`
13 | UID string `json:"uid"`
14 | URL string `json:"url"`
15 | Status string `json:"status"`
16 | Version int `json:"version"`
17 | }
18 |
19 | // NewCreateRequest returns a new create request model
20 | func NewCreateRequest(folderID int) *CreateRequest {
21 | return &CreateRequest{
22 | Overwrite: true,
23 | FolderID: folderID,
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/webpack.common.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const CleanWebpackPlugin = require("clean-webpack-plugin");
3 |
4 | module.exports = {
5 | entry: {
6 | app: './ui/js/app.js',
7 | },
8 | output: {
9 | path: path.resolve(process.cwd(), 'dist'),
10 | filename: '[name].js'
11 | },
12 | plugins: [
13 | new CleanWebpackPlugin()
14 | ],
15 | module: {
16 | rules: [
17 | {
18 | test: path.join(__dirname, "./ui/js"),
19 | exclude: /node_modules/,
20 | use: {
21 | loader: "babel-loader"
22 | }
23 | },
24 | {
25 | test: /\.css$/,
26 | use: [
27 | "style-loader",
28 | "css-loader"
29 | ]
30 | }
31 | ]
32 | }
33 | };
34 |
35 |
--------------------------------------------------------------------------------
/ui/js/components/graph.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | import Empty from './empty';
5 | import '../../style/components/graph.css';
6 |
7 | class Graph extends React.Component {
8 | getContent = () => {
9 | if (this.props.url) {
10 | return ;
11 | }
12 |
13 | return (
14 |
18 | )
19 | }
20 |
21 | render() {
22 | return(
23 |
24 | {this.getContent()}
25 |
26 | );
27 | }
28 | }
29 |
30 | const mapStateToProps = state => {
31 | return { ...state.RunReducer };
32 | }
33 |
34 | export default connect(mapStateToProps, {}) (Graph);
35 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.12.1-alpine3.9 AS bob
2 |
3 | RUN apk add --update build-base git nodejs nodejs-npm ca-certificates && \
4 | rm -rf /var/cache/apk/*
5 |
6 | WORKDIR /src
7 |
8 | COPY . ./
9 | RUN npm install webpack
10 |
11 | RUN make clean build-release && \
12 | git clone https://github.com/grafana/grafonnet-lib.git dist/grafonnet-lib && \
13 | git clone https://github.com/gojekfarm/grafonnet-bigquery-panel.git dist/grafonnet-bigquery-panel && \
14 | git clone https://github.com/grafana/grafonnet.git dist/grafonnet
15 |
16 | FROM alpine:3.9
17 |
18 | RUN apk add --update ca-certificates git && \
19 | rm -rf /var/cache/apk/*
20 |
21 | WORKDIR /app
22 |
23 | COPY --from=bob /src/dist /app/dist
24 |
25 | COPY --from=bob /src/public /app/public
26 |
27 | COPY --from=bob /src/out/grafonnet-playground /app
28 |
29 | ENV GRAFONNET_LIB_DIRS='/app/dist/grafonnet-lib /app/dist/grafonnet-bigquery-panel /app/dist/grafonnet'
30 |
31 | CMD ["./grafonnet-playground"]
32 |
33 |
--------------------------------------------------------------------------------
/ui/js/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import CssBaseline from '@material-ui/core/CssBaseline';
5 | import Paper from '@material-ui/core/Paper';
6 | import Grid from '@material-ui/core/Grid';
7 | import { Provider } from 'react-redux';
8 |
9 | import Editor from './components/editor';
10 | import Graph from './components/graph';
11 | import Control from './components/control';
12 | import '../style/app.css';
13 |
14 | import store from './store';
15 |
16 | function App() {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | ReactDOM.render(
33 | ,
34 | document.getElementById('container'),
35 | );
36 |
--------------------------------------------------------------------------------
/ui/style/components/empty.css:
--------------------------------------------------------------------------------
1 | .empty {
2 | height: 100%;
3 | width: 100%;
4 | background: #EEEEEE;
5 | display: table;
6 | -webkit-box-shadow: inset 0px 0px 4px 4px rgba(245,245,245,1);
7 | -moz-box-shadow: inset 0px 0px 4px 4px rgba(245,245,245,1);
8 | box-shadow: inset 0px 0px 4px 4px rgba(245,245,245,1);
9 | }
10 |
11 | .loading-content {
12 | display: table-cell;
13 | text-align: center;
14 | vertical-align: middle;
15 | }
16 |
17 | .error-container {
18 | display: table-cell;
19 | vertical-align: middle;
20 | padding-left: 48px;
21 | padding-right: 16px;
22 | }
23 |
24 | .warning-icon {
25 | color: #B71C1C;
26 | margin-right: 8px;
27 | }
28 |
29 | .error-container-header {
30 | font-size: 24px;
31 | color: #B71C1C;
32 | }
33 |
34 | .error-container-body {
35 | padding-top: 8px;
36 | white-space: pre-wrap;
37 | color: #D32F2F;
38 | }
39 |
40 | .empty-container {
41 | display: table-cell;
42 | text-align: center;
43 | vertical-align: middle;
44 | color: #616161;
45 | }
46 |
47 | .empty-container-header {
48 | display: block;
49 | font-size: 28px;
50 | padding-bottom: 16px;
51 | }
52 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/gin-gonic/contrib/static"
7 | "github.com/gin-gonic/gin"
8 | "github.com/lahsivjar/grafonnet-playground/config"
9 | "github.com/lahsivjar/grafonnet-playground/grafana"
10 | "github.com/lahsivjar/grafonnet-playground/handlers"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | func main() {
15 | setupLogging()
16 |
17 | cfg := config.Load()
18 | grafanaService := grafana.NewService(cfg)
19 |
20 | if cfg.AutoCleanup {
21 | err := grafanaService.SetupCleanerJob(context.Background())
22 | if err != nil {
23 | panic(err)
24 | }
25 | }
26 |
27 | router := setupGin()
28 |
29 | router.Use(
30 | static.Serve("/playground", static.LocalFile("./public", true)),
31 | static.Serve("/playground/dist", static.LocalFile("./dist", true)),
32 | )
33 |
34 | router.GET("/health", handlers.HealthCheckHandler)
35 |
36 | api := router.Group("/playground/api/v1")
37 | {
38 | api.POST("/run", handlers.RunHandler(cfg, grafanaService))
39 | }
40 |
41 | router.Run(":8080")
42 | }
43 |
44 | func setupGin() *gin.Engine {
45 | r := gin.New()
46 |
47 | r.Use(
48 | gin.Logger(),
49 | gin.Recovery(),
50 | )
51 |
52 | return r
53 | }
54 |
55 | func setupLogging() {
56 | log.SetFormatter(&log.TextFormatter{
57 | FullTimestamp: true,
58 | })
59 | }
60 |
--------------------------------------------------------------------------------
/ui/js/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import logger from 'redux-logger';
4 | import promise from 'redux-promise-middleware';
5 | import { get } from 'idb-keyval';
6 |
7 | import { CODE_UPDATE, THEME_UPDATE, WRAP_TEXT } from './actions/types';
8 | import rootReducer from './reducers/index';
9 |
10 | const middlewares = [promise, thunk];
11 |
12 | if (process.env.NODE_ENV === `development`) {
13 | middlewares.push(logger);
14 | }
15 |
16 | const store = createStore(
17 | rootReducer,
18 | applyMiddleware(...middlewares)
19 | )
20 |
21 | get('code')
22 | .then(val => {
23 | if (val !== undefined) {
24 | store.dispatch({
25 | type: CODE_UPDATE,
26 | payload: val,
27 | })
28 | }
29 | })
30 |
31 | get('theme')
32 | .then(val => {
33 | if (val !== undefined) {
34 | store.dispatch({
35 | type: THEME_UPDATE,
36 | payload: val,
37 | })
38 | }
39 | })
40 |
41 | get('wrap')
42 | .then(val => {
43 | if (val !== undefined) {
44 | store.dispatch({
45 | type: WRAP_TEXT,
46 | payload: val,
47 | })
48 | }
49 | })
50 |
51 | export default store;
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Grafonnet Playground
2 | Playground for grafanna with jsonnet. Allows users to write [grafonnet](https://github.com/grafana/grafonnet-lib) and see the rendered [grafana](https://grafana.com/) dashboard.
3 |
4 | 
5 |
6 | ## Usage
7 |
8 | Run using dockerhub image (note the variables in the command)
9 |
10 | ```
11 | docker run \
12 | -e "GRAFANA_API_KEY=${grafana_api_key}" \
13 | -e "GRAFANA_GET_URL=${grafana_url}" \
14 | -e "GRAFANA_POST_URL=${grafana_url}" \
15 | -e "GRAFONNET_PLAYGROUND_FOLDER_ID=${grafana_playground_folder_id}" \
16 | -e "GIN_MODE=release" \
17 | -p 8080:8080 \
18 | lahsivjar/grafonnet-playground
19 | ```
20 |
21 | *Note*: For debugging remove the `GIN_MODE` environment variable from above command
22 |
23 | ## Cleaning up dashboards created by playground
24 |
25 | From version 1.0.0, grafonnet-playground allows automatically cleaning up the dashboards that were created. This can be configured via `AUTO_CLEANUP` config and can be further tuned via other available configuration. For a list of all configurations please check [application.yml.sample](application.yml.sample) file.
26 |
27 | *Note*: Even when the dashboard is deleted after it has been rendered, it will continue to work and display data.
28 |
29 | ## Development build
30 | - Copy config file
31 | ```
32 | make copy-config
33 | ```
34 | - Update properties in the file `application.yml`
35 | - Build code
36 | ```
37 | make build-dev
38 | ```
39 | - Execute go binary
40 | ```
41 | ./out/grafonnet-playground
42 | ```
43 |
--------------------------------------------------------------------------------
/grafana/cleaner.go:
--------------------------------------------------------------------------------
1 | package grafana
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/lahsivjar/grafonnet-playground/config"
8 | log "github.com/sirupsen/logrus"
9 | )
10 |
11 | // startCleaner starts a job to periodically delete any stale dashboards
12 | func startCleaner(ctx context.Context, pq *PriorityQueue, gSvc Service, cfg *config.Config) {
13 | log.Info("Starting cleaner...")
14 | ifBeforeNow := func(i *Item) bool {
15 | return i.ProcessAt.Before(time.Now())
16 | }
17 |
18 | ticker := time.NewTicker(cfg.AutoCleanupInterval)
19 |
20 | go func() {
21 | for ; true; <-ticker.C {
22 | select {
23 | case <-ctx.Done():
24 | return
25 | default:
26 | item := pq.PopConditionally(ifBeforeNow)
27 |
28 | if item != nil {
29 | err := gSvc.DeleteDashboard(item.Key)
30 |
31 | if err != nil {
32 | log.WithFields(log.Fields{
33 | "uid": item.Key,
34 | "retryCount": item.RetryCount,
35 | "err": err,
36 | }).Error("Failed to delete dashboard")
37 | item.RetryCount = item.RetryCount + 1
38 | item.ProcessAt = time.Now().
39 | Add(getBackoff(
40 | cfg.AutoCleanupMinBackoff,
41 | cfg.AutoCleanupMaxBackoff,
42 | item.RetryCount,
43 | ))
44 | pq.Push(item)
45 | }
46 | }
47 | }
48 | }
49 | }()
50 | }
51 |
52 | func getBackoff(minBackoff, maxBackoff time.Duration, retryCount int) time.Duration {
53 | currentBackoff := time.Duration(retryCount) * minBackoff
54 |
55 | if currentBackoff < maxBackoff {
56 | return currentBackoff
57 | }
58 | return maxBackoff
59 | }
60 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/lahsivjar/grafonnet-playground
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
7 | github.com/fsnotify/fsnotify v1.4.9 // indirect
8 | github.com/gin-gonic/contrib v0.0.0-20200810162008-6dee08bf958e
9 | github.com/gin-gonic/gin v1.7.4
10 | github.com/go-playground/validator/v10 v10.9.0 // indirect
11 | github.com/golang/protobuf v1.5.2 // indirect
12 | github.com/google/go-jsonnet v0.16.0
13 | github.com/json-iterator/go v1.1.12 // indirect
14 | github.com/mattn/go-isatty v0.0.14 // indirect
15 | github.com/mitchellh/mapstructure v1.3.3 // indirect
16 | github.com/pelletier/go-toml v1.8.0 // indirect
17 | github.com/sirupsen/logrus v1.6.0
18 | github.com/spf13/afero v1.3.4 // indirect
19 | github.com/spf13/cast v1.3.1 // indirect
20 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
21 | github.com/spf13/pflag v1.0.5 // indirect
22 | github.com/spf13/viper v1.7.1
23 | github.com/stretchr/testify v1.7.0
24 | github.com/ugorji/go v1.2.6 // indirect
25 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect
26 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
27 | golang.org/x/sys v0.0.0-20211109065445-02f5c0300f6e // indirect
28 | golang.org/x/text v0.3.7 // indirect
29 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
30 | google.golang.org/protobuf v1.27.1 // indirect
31 | gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
32 | gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
33 | gopkg.in/ini.v1 v1.60.0 // indirect
34 | gopkg.in/yaml.v2 v2.4.0 // indirect
35 | )
36 |
--------------------------------------------------------------------------------
/application.yml.sample:
--------------------------------------------------------------------------------
1 | # GRAFANA_POST_URL points to a running grafana instance to create the editor
2 | # GRAFANA_GET_URL points to what the iframe will load from client side
3 | # The above two url's are separated because there might be use cases where
4 | # view and edit use different auth mechanism and thus require different endpoints
5 | GRAFANA_POST_URL: "https://target-grafana-url"
6 | GRAFANA_GET_URL: "https://target-grafana-url"
7 |
8 | # GRAFANA_API_KEY is the admin api key required to create dashboards from playground code
9 | GRAFANA_API_KEY: "secret-api-key"
10 |
11 | # GRAFANA_API_KEY_HEADER_NAME is the header name used to sent grafana API key to create and delete dashboards
12 | GRAFANA_API_KEY_HEADER_NAME: "Authorization"
13 |
14 | # GRAFONNET_LIB_DIR is the location where grafonnet-lib is cloned
15 | # It can also have other plugins as a slice
16 | # See https://github.com/grafana/grafonnet-lib
17 | GRAFONNET_LIB_DIRS: /location/to/grafonnet-lib
18 |
19 | # GRAFONNET_PLAYGROUND_FOLDER_ID is the folder where grafonnet-playground will create
20 | # dashboards when from playground code
21 | GRAFONNET_PLAYGROUND_FOLDER_ID: 0
22 |
23 | # Setting AUTO_CLEANUP to true will automatically delete the dashboard after the iframe
24 | # loads it
25 | AUTO_CLEANUP: false
26 |
27 | # Setting AUTO_CLEANUP_INTERVAL will configure how often does the cleanup job runs
28 | # Example: 5s, 1m
29 | AUTO_CLEANUP_INTERVAL: 30s
30 |
31 | # CleanupAfter configures time after which the dashboard created is considered
32 | # stale and can be deleted. It doesn't stop the already loaded dashboard from working
33 | CLEANUP_AFTER: 10s
34 |
35 | # Setting AUTO_CLEANUP_MIN_BACKOFF will configure minimum backoff for deleting dashboard
36 | # Example: 5s, 1m
37 | AUTO_CLEANUP_MIN_BACKOFF: 30s
38 |
39 | # Setting AUTO_CLEANUP_MAX_BACKOFF will configure maximum backoff for deleting dashboard
40 | AUTO_CLEANUP_MAX_BACKOFF: 5m
41 |
--------------------------------------------------------------------------------
/handlers/run.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 |
7 | "github.com/gin-gonic/gin"
8 | "github.com/google/go-jsonnet"
9 | "github.com/lahsivjar/grafonnet-playground/config"
10 | "github.com/lahsivjar/grafonnet-playground/grafana"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | type runRequest struct {
15 | Code string `json:"code" binding:"required"`
16 | }
17 |
18 | type runResponse struct {
19 | URL string `json:"url"`
20 | }
21 |
22 | // RunHandler handles the run endpoint which converts jsonnet to json and
23 | // creates a grafana snapshot, returning it to the client
24 | func RunHandler(cfg *config.Config, gSvc grafana.Service) func(*gin.Context) {
25 | return func(c *gin.Context) {
26 | var rReq runRequest
27 | if err := c.ShouldBindJSON(&rReq); err != nil {
28 | c.JSON(http.StatusBadRequest, gin.H{"errorMsg": err.Error()})
29 | return
30 | }
31 |
32 | j, err := getJsonnetVM(cfg.GrafonnetLibDirs).
33 | EvaluateSnippet("grafonnet-playground", rReq.Code)
34 | if err != nil {
35 | log.Error("Failed to evaluate jsonnet", err)
36 | c.JSON(http.StatusBadRequest, gin.H{"errorMsg": err.Error()})
37 | return
38 | }
39 |
40 | gReq := grafana.NewCreateRequest(cfg.GrafonnetPlaygroundFolderID)
41 | if err := json.Unmarshal([]byte(j), &gReq.Dashboard); err != nil {
42 | log.Error("Failed to create POST dashboard request", err)
43 | c.JSON(http.StatusBadRequest, gin.H{"errorMsg": err.Error()})
44 | return
45 | }
46 |
47 | gRes, err := gSvc.CreateDashboard(gReq)
48 | if err != nil {
49 | log.Error("Failed to create dashboard", err)
50 | c.JSON(http.StatusBadRequest, gin.H{"errorMsg": err.Error()})
51 | return
52 | }
53 |
54 | c.JSON(http.StatusOK, runResponse{
55 | URL: cfg.GrafanaGetURL + gRes.URL,
56 | })
57 | }
58 | }
59 |
60 | func getJsonnetVM(jPaths []string) *jsonnet.VM {
61 | vm := jsonnet.MakeVM()
62 | i := &jsonnet.FileImporter{
63 | JPaths: jPaths,
64 | }
65 | vm.Importer(i)
66 |
67 | return vm
68 | }
69 |
--------------------------------------------------------------------------------
/grafana/priority_queue.go:
--------------------------------------------------------------------------------
1 | package grafana
2 |
3 | import (
4 | "container/heap"
5 | "sync"
6 | "time"
7 | )
8 |
9 | // PriorityQueue defines a thread safe priority queue
10 | type PriorityQueue struct {
11 | lock sync.Mutex
12 | queue queue
13 | }
14 |
15 | // Item defines a struct that the PriorityQueue consumes
16 | type Item struct {
17 | Key string
18 | RetryCount int
19 | ProcessAt time.Time
20 | }
21 |
22 | func (item *Item) priority() int64 {
23 | return -1 * item.ProcessAt.UnixNano()
24 | }
25 |
26 | type queue []*Item
27 |
28 | func (q queue) Len() int {
29 | return len(q)
30 | }
31 |
32 | func (q queue) Less(i, j int) bool {
33 | return q[i].priority() > q[j].priority()
34 | }
35 |
36 | func (q queue) Swap(i, j int) {
37 | q[i], q[j] = q[j], q[i]
38 | }
39 |
40 | func (q *queue) Push(x interface{}) {
41 | *q = append(*q, x.(*Item))
42 | }
43 |
44 | func (q *queue) Pop() interface{} {
45 | old := *q
46 | n := len(old)
47 | x := old[n-1]
48 | *q = old[0 : n-1]
49 | return x
50 | }
51 |
52 | // NewPriorityQueue creates a new priority queue
53 | func NewPriorityQueue() *PriorityQueue {
54 | return &PriorityQueue{}
55 | }
56 |
57 | // Push pushes a new item to priority queue with a given value and priority
58 | func (pq *PriorityQueue) Push(item *Item) {
59 | pq.lock.Lock()
60 | defer pq.lock.Unlock()
61 |
62 | heap.Push(&pq.queue, item)
63 | }
64 |
65 | // Pop returns the lowest priority item from the queue
66 | func (pq *PriorityQueue) Pop() *Item {
67 | pq.lock.Lock()
68 | defer pq.lock.Unlock()
69 |
70 | raw := heap.Pop(&pq.queue)
71 | return raw.(*Item)
72 | }
73 |
74 | // PopConditionally pods an item if the given condition is met
75 | func (pq *PriorityQueue) PopConditionally(f func(*Item) bool) *Item {
76 | pq.lock.Lock()
77 | defer pq.lock.Unlock()
78 |
79 | if len(pq.queue) > 0 {
80 | item := pq.queue[0]
81 |
82 | if f(item) {
83 | raw := heap.Pop(&pq.queue)
84 | return raw.(*Item)
85 | }
86 | }
87 | return nil
88 | }
89 |
90 | // Size returns the current size of the priority queue
91 | func (pq *PriorityQueue) Size() int {
92 | return pq.queue.Len()
93 | }
94 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: clean test build
2 |
3 | APP=grafonnet-playground
4 | ALL_PACKAGES=$(shell go list ./...)
5 | SOURCE_DIRS=$(shell go list ./... | cut -d "/" -f4 | uniq)
6 |
7 | setup:
8 | go get -u golang.org/x/lint/golint
9 | go get -u golang.org/x/tools/cmd/goimports
10 | go get -u github.com/fzipp/gocyclo
11 | go get -u github.com/gordonklaus/ineffassign
12 | go get -u github.com/client9/misspell
13 | go get -u github.com/alecthomas/gometalinter
14 |
15 | tidy:
16 | GO111MODULE=on go mod tidy -v
17 |
18 | clean: tidy
19 | rm -rf ./dist
20 | rm -rf ./out
21 |
22 | check-quality: lint fmt cyclo vet
23 |
24 | go-build:
25 | GO111MODULE=on go build -o "./out/${APP}"
26 |
27 | go-release-build:
28 | GO111MODULE=on GOOS=linux GOARCH=amd64 go build -o "./out/${APP}"
29 |
30 | npm-build-dev:
31 | npm run dev
32 |
33 | npm-build:
34 | npm run build
35 |
36 | build-dev: npm-build-dev go-build
37 |
38 | build: npm-build go-build
39 |
40 | build-release: npm-build go-release-build
41 |
42 | test:
43 | GO111MODULE=on go test -count 1 -cover -v ./...
44 |
45 | fmt:
46 | gofmt -l -s -w $(SOURCE_DIRS)
47 |
48 | imports:
49 | go get -u golang.org/x/tools/cmd/goimports
50 | goimports -l -w -v $(SOURCE_DIRS)
51 |
52 | cyclo:
53 | go get -u github.com/fzipp/gocyclo
54 | gocyclo -over 7 $(SOURCE_DIRS)
55 |
56 | vet:
57 | GO111MODULE=on go vet ./...
58 |
59 | lint:
60 | go get -u golang.org/x/lint/golint
61 | golint -set_exit_status ./...
62 |
63 | race:
64 | GO111MODULE=on go test -race -short ./...
65 |
66 | msan: # Memory sanitizer (will only work in linux/amd64)
67 | GO111MODULE=on go test -msan -short ./...
68 |
69 | coverage:
70 | GO111MODULE=on go test -coverprofile=coverage.out ./...
71 | # Below displays global test coverage
72 | GO111MODULE=on go tool cover -func=coverage.out
73 |
74 | coverage-html:
75 | GO111MODULE=on go test -coverprofile=coverage.out ./...
76 | # Below displays global test coverage
77 | GO111MODULE=on go tool cover -func=coverage.out
78 | mkdir public
79 | GO111MODULE=on go tool cover -html=./coverage.out -o public/coverage.html
80 |
81 | copy-config:
82 | cp application.yml.sample application.yml
83 |
84 |
--------------------------------------------------------------------------------
/ui/js/reducers/runReducer.js:
--------------------------------------------------------------------------------
1 | import querystring from 'query-string';
2 | import random from 'math-random';
3 |
4 | import { RUN_PENDING, RUN_FULFILLED, RUN_REJECTED } from '../actions/types';
5 | import { CODE_UPDATE } from '../actions/types';
6 | import { THEME_UPDATE } from '../actions/types';
7 | import { WRAP_TEXT } from '../actions/types';
8 |
9 | const initialState ={
10 | url: '',
11 | errorMsg: '',
12 | code: '',
13 | theme: 'default',
14 | wrap: true,
15 | loading: false,
16 | error: false,
17 | }
18 |
19 | function getUrl(baseurl) {
20 | const parsed = querystring.parse(baseurl);
21 | parsed.kiosk = 1;
22 | parsed.buster = random();
23 |
24 | return baseurl + '?' + querystring.stringify(parsed);
25 | }
26 |
27 | export default function RunReducer(state = initialState, action) {
28 | switch(action.type) {
29 | case RUN_PENDING:
30 | return {
31 | ...state,
32 | loading: true,
33 | error: false,
34 | }
35 | case RUN_FULFILLED:
36 | return {
37 | ...state,
38 | ...action.payload,
39 | url: getUrl(action.payload.url),
40 | loading: false,
41 | error: false,
42 | }
43 | case RUN_REJECTED:
44 | var errMsg = 'Unknown error occurred while attempting to run jsonnet';
45 | if (action.payload.response && action.payload.response.data) {
46 | errMsg = action.payload.response.data.errorMsg;
47 | } else if (action.payload.message) {
48 | errMsg = action.payload.message
49 | }
50 | return {
51 | ...state,
52 | errorMsg: errMsg,
53 | url: '',
54 | loading: false,
55 | error: true,
56 | }
57 | case CODE_UPDATE:
58 | case THEME_UPDATE:
59 | case WRAP_TEXT:
60 | return {
61 | ...state,
62 | ...action.payload,
63 | }
64 | default:
65 | return state
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/ui/js/components/empty.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CircularProgress from '@material-ui/core/CircularProgress';
3 | import WarningIcon from '@material-ui/icons/Warning';
4 | import Grid from '@material-ui/core/Grid';
5 |
6 | import '../../style/components/empty.css';
7 |
8 | class Empty extends React.Component {
9 | getContent = () => {
10 | if (this.props.loading) {
11 | return (
12 |
13 |
14 |
15 | )
16 | }
17 | if (this.props.errorMsg) {
18 | return (
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 | Error occurred while evaluating jsonnet code
30 |
31 |
32 |
33 |
34 | {this.props.errorMsg}
35 |
36 |
37 | )
38 | }
39 | return (
40 |
41 |
42 | Grafonnet Playground
43 |
44 |
45 | Edit grafonnet code in editor and view the rendered graph
46 |
47 |
48 | )
49 | }
50 |
51 | render() {
52 | return(
53 |
54 | {this.getContent()}
55 |
56 | );
57 | }
58 | }
59 |
60 | const mapStateToProps = state => {
61 | return { ...state.RunReducer };
62 | }
63 |
64 | export default Empty;
65 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grafonnet-playground",
3 | "version": "1.3.0",
4 | "description": "Grafonnet playground",
5 | "scripts": {
6 | "build": "webpack --mode production --config webpack.prod.js",
7 | "dev": "webpack --mode development --config webpack.dev.js",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/lahsivjar/grafonnet-playground.git"
13 | },
14 | "keywords": [
15 | "grafonnet",
16 | "playgroud"
17 | ],
18 | "author": "lahsivjar",
19 | "license": "Apache-2.0",
20 | "bugs": {
21 | "url": "https://github.com/lahsivjar/grafonnet-playground/issues"
22 | },
23 | "homepage": "https://github.com/lahsivjar/grafonnet-playground#readme",
24 | "dependencies": {
25 | "@material-ui/core": "^4.11.0",
26 | "@material-ui/icons": "^4.9.1",
27 | "axios": "^0.21.2",
28 | "codemirror": "^5.58.2",
29 | "codemirror-mode-jsonnet": "^1.0.0",
30 | "idb-keyval": "^3.2.0",
31 | "js-file-download": "^0.4.12",
32 | "math-random": "^1.0.4",
33 | "query-string": "^6.13.1",
34 | "react": "^16.13.1",
35 | "react-codemirror2": "^7.2.1",
36 | "react-dom": "^16.13.1",
37 | "react-redux": "^7.2.1",
38 | "redux": "^4.0.5",
39 | "redux-logger": "^3.0.6",
40 | "redux-promise-middleware": "^6.1.2",
41 | "redux-thunk": "^2.4.0",
42 | "throttle-debounce": "^2.3.0"
43 | },
44 | "eslintConfig": {
45 | "extends": "react-app"
46 | },
47 | "browserslist": {
48 | "production": [
49 | ">0.2%",
50 | "not dead",
51 | "not op_mini all"
52 | ],
53 | "development": [
54 | "last 1 chrome version",
55 | "last 1 firefox version",
56 | "last 1 safari version"
57 | ]
58 | },
59 | "devDependencies": {
60 | "@babel/core": "^7.16.0",
61 | "@babel/plugin-proposal-class-properties": "^7.16.0",
62 | "@babel/preset-env": "^7.16.0",
63 | "@babel/preset-react": "^7.16.0",
64 | "babel-loader": "^8.2.3",
65 | "clean-webpack-plugin": "^2.0.2",
66 | "css-loader": "^2.1.1",
67 | "style-loader": "^0.23.1",
68 | "terser-webpack-plugin": "^1.4.5",
69 | "webpack": "^4.46.0",
70 | "webpack-cli": "^3.3.12",
71 | "webpack-merge": "^4.2.2"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/grafana/priority_queue_test.go:
--------------------------------------------------------------------------------
1 | package grafana
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestNewPriorityQueue(t *testing.T) {
11 | assert := assert.New(t)
12 |
13 | pq := NewPriorityQueue()
14 | assert.NotNil(pq)
15 | }
16 |
17 | func TestPush(t *testing.T) {
18 | assert := assert.New(t)
19 |
20 | pq := NewPriorityQueue()
21 | assert.NotNil(pq)
22 |
23 | pq.Push(&Item{Key: "1", ProcessAt: time.Now().Add(-2 * time.Second)})
24 |
25 | assert.Equal(1, pq.Size())
26 | }
27 |
28 | func TestPop(t *testing.T) {
29 | assert := assert.New(t)
30 |
31 | pq := NewPriorityQueue()
32 | assert.NotNil(pq)
33 |
34 | pq.Push(&Item{Key: "1", ProcessAt: time.Now().Add(-2 * time.Second)})
35 | pq.Push(&Item{Key: "2", ProcessAt: time.Now().Add(-1 * time.Second)})
36 |
37 | assert.Equal(2, pq.Size())
38 | assert.Equal("1", pq.Pop().Key)
39 | assert.Equal(1, pq.Size())
40 | assert.Equal("2", pq.Pop().Key)
41 | assert.Equal(0, pq.Size())
42 | }
43 |
44 | func TestPopConditionally(t *testing.T) {
45 | assert := assert.New(t)
46 |
47 | pq := NewPriorityQueue()
48 | assert.NotNil(pq)
49 |
50 | pq.Push(&Item{Key: "1", ProcessAt: time.Now().Add(-2 * time.Second)})
51 | pq.Push(&Item{Key: "2", ProcessAt: time.Now().Add(-1 * time.Second)})
52 | pq.Push(&Item{Key: "3", ProcessAt: time.Now().Add(-1 * time.Minute)})
53 |
54 | ifBeforeNow := func(i *Item) bool {
55 | return i.ProcessAt.Before(time.Now())
56 | }
57 |
58 | assert.Equal(3, pq.Size())
59 | assert.Equal("3", pq.PopConditionally(ifBeforeNow).Key)
60 | assert.Equal("1", pq.PopConditionally(ifBeforeNow).Key)
61 | assert.Equal("2", pq.PopConditionally(ifBeforeNow).Key)
62 | assert.Equal(0, pq.Size())
63 | }
64 |
65 | func TestPopConditionally_Late(t *testing.T) {
66 | assert := assert.New(t)
67 |
68 | pq := NewPriorityQueue()
69 | assert.NotNil(pq)
70 |
71 | pq.Push(&Item{Key: "1", ProcessAt: time.Now().Add(2 * time.Second)})
72 |
73 | ifBeforeNow := func(i *Item) bool {
74 | return i.ProcessAt.Before(time.Now())
75 | }
76 |
77 | assert.Equal(1, pq.Size())
78 | assert.Nil(pq.PopConditionally(ifBeforeNow))
79 | assert.Equal(1, pq.Size())
80 |
81 | time.Sleep(2 * time.Second)
82 | assert.Equal("1", pq.PopConditionally(ifBeforeNow).Key)
83 | assert.Equal(0, pq.Size())
84 | }
85 |
--------------------------------------------------------------------------------
/grafana/grafana.go:
--------------------------------------------------------------------------------
1 | package grafana
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "io/ioutil"
10 | "net/http"
11 | "strings"
12 | "time"
13 |
14 | "github.com/lahsivjar/grafonnet-playground/config"
15 | )
16 |
17 | // Service exposes methods to handle grafana dashboards
18 | type Service interface {
19 | SetupCleanerJob(ctx context.Context) error
20 | CreateDashboard(*CreateRequest) (*CreateResponse, error)
21 | DeleteDashboard(string) error
22 | }
23 |
24 | type grafana struct {
25 | cfg *config.Config
26 | pq *PriorityQueue
27 | }
28 |
29 | // NewService returns a new service with default implementations for grafana service
30 | // It also starts the cleaner job if cleaup is enabled in the configuration
31 | func NewService(cfg *config.Config) Service {
32 | g := &grafana{
33 | cfg: cfg,
34 | }
35 | if cfg.AutoCleanup {
36 | g.pq = NewPriorityQueue()
37 | }
38 | return g
39 | }
40 |
41 | // SetupCleanerJob starts the cleaner job if cleanup is enabled in the configurations
42 | func (g *grafana) SetupCleanerJob(ctx context.Context) error {
43 | if g.pq == nil {
44 | return errors.New("Priority queue for cleanup job is nil")
45 | }
46 |
47 | startCleaner(ctx, g.pq, g, g.cfg)
48 | return nil
49 | }
50 |
51 | // CreateDashboard creates a new grafana dashboard
52 | func (g *grafana) CreateDashboard(cr *CreateRequest) (*CreateResponse, error) {
53 | reqBody, err := getRequestBody(cr)
54 | if err != nil {
55 | return nil, err
56 | }
57 |
58 | req, err := http.NewRequest("POST", g.cfg.GrafanaPostURL+"/api/dashboards/db", reqBody)
59 | if err != nil {
60 | return nil, err
61 | }
62 | req.Header.Set(g.cfg.GrafanaAPIKeyHeaderName, "Bearer "+g.cfg.GrafanaAPIKey)
63 | req.Header.Set("Content-Type", "application/json")
64 | resp, err := http.DefaultClient.Do(req)
65 |
66 | if err != nil {
67 | return nil, err
68 | }
69 | defer resp.Body.Close()
70 |
71 | if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
72 | var gRes CreateResponse
73 | err = json.NewDecoder(resp.Body).Decode(&gRes)
74 | if err != nil {
75 | return nil, err
76 | }
77 |
78 | if g.cfg.AutoCleanup {
79 | g.pq.Push(&Item{Key: gRes.UID, ProcessAt: time.Now().Add(g.cfg.CleanupAfter)})
80 | }
81 | return &gRes, nil
82 | }
83 |
84 | errorMsg, err := ioutil.ReadAll(resp.Body)
85 | return nil, fmt.Errorf("Error occurred while creating graph: %s", errorMsg)
86 | }
87 |
88 | func (g *grafana) DeleteDashboard(uid string) error {
89 | req, err := http.NewRequest("DELETE", g.cfg.GrafanaPostURL+"/api/dashboards/uid/"+uid, nil)
90 | if err != nil {
91 | return err
92 | }
93 | req.Header.Set(g.cfg.GrafanaAPIKeyHeaderName, "Bearer "+g.cfg.GrafanaAPIKey)
94 | req.Header.Set("Content-Type", "application/json")
95 | resp, err := http.DefaultClient.Do(req)
96 |
97 | if err != nil {
98 | return err
99 | }
100 | defer resp.Body.Close()
101 |
102 | if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
103 | return nil
104 | }
105 |
106 | errorMsg, err := ioutil.ReadAll(resp.Body)
107 | return fmt.Errorf("Error occurred while deleting graph: %s", errorMsg)
108 | }
109 |
110 | func getRequestBody(req *CreateRequest) (io.Reader, error) {
111 | b, err := json.Marshal(req)
112 | if err != nil {
113 | return nil, err
114 | }
115 |
116 | return strings.NewReader(string(b)), nil
117 | }
118 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/spf13/viper"
8 | )
9 |
10 | // Config required by grafonnet-playground
11 | type Config struct {
12 | // GrafanaPostURL is the url where grafana can be accessed for creating dashboards
13 | GrafanaPostURL string
14 | // GrafanaGetURL is the url where grafana dashboards will be loaded in iframe
15 | GrafanaGetURL string
16 | // GrafanaApiKey is the admin api key for grafana to create dashboards
17 | GrafanaAPIKey string
18 | // GrafanaAPIKeyHeaderName is the header name to sent grafanaAPIKey
19 | GrafanaAPIKeyHeaderName string
20 | // GrafonnetLibDirs is a slice of location of grafonnet lib that the app should
21 | // have access to. It can be managed as the situation dictates during the build
22 | // or package phase
23 | GrafonnetLibDirs []string
24 | // GrafonnetPlaygroundFolderID is the folder id in grafana where the playground
25 | // dashboards will be created. A separate folder is used to ensure that the
26 | // dashboards can be identified and deleted with ease if required
27 | GrafonnetPlaygroundFolderID int
28 | // AutoCleanup configures automatically cleaning up of dashboards after it is
29 | // loaded by the iframe
30 | AutoCleanup bool
31 | // CleanupAfter is the time after which the dashboard created is considered stale
32 | // and can be deleted. It doesn't stop the already loaded dashboard from working
33 | CleanupAfter time.Duration
34 | // AutoCleanupInterval configures interval between between running cleanup job
35 | // loaded by the iframe
36 | AutoCleanupInterval time.Duration
37 | // AutoCleanupMinBackoff configures minimum backoff for cleaner job
38 | AutoCleanupMinBackoff time.Duration
39 | // AutoCleanupMaxBackoff configures maximum backoff for cleaner job
40 | AutoCleanupMaxBackoff time.Duration
41 | }
42 |
43 | // Load config from application.yaml file or from environment variables
44 | func Load() *Config {
45 | viper.SetConfigName("application")
46 | viper.AddConfigPath("./")
47 | viper.SetConfigType("yaml")
48 | viper.SetDefault("AUTO_CLEANUP", "false")
49 | viper.SetDefault("CLEANUP_AFTER", "10s")
50 | viper.SetDefault("AUTO_CLEANUP_INTERVAL", "30s")
51 | viper.SetDefault("AUTO_CLEANUP_MIN_BACKOFF", "30s")
52 | viper.SetDefault("AUTO_CLEANUP_MAX_BACKOFF", "5m")
53 | viper.SetDefault("GRAFANA_API_KEY_HEADER_NAME", "Authorization")
54 | viper.ReadInConfig()
55 | viper.AutomaticEnv()
56 |
57 | cfg := &Config{
58 | GrafanaPostURL: mustHaveString("GRAFANA_POST_URL"),
59 | GrafanaGetURL: mustHaveString("GRAFANA_GET_URL"),
60 | GrafanaAPIKey: mustHaveString("GRAFANA_API_KEY"),
61 | GrafonnetLibDirs: mustHaveStringSlice("GRAFONNET_LIB_DIRS"),
62 | GrafonnetPlaygroundFolderID: viper.GetInt("GRAFONNET_PLAYGROUND_FOLDER_ID"),
63 | GrafanaAPIKeyHeaderName: viper.GetString("GRAFANA_API_KEY_HEADER_NAME"),
64 | AutoCleanup: viper.GetBool("AUTO_CLEANUP"),
65 | CleanupAfter: viper.GetDuration("CLEANUP_AFTER"),
66 | AutoCleanupInterval: viper.GetDuration("AUTO_CLEANUP_INTERVAL"),
67 | AutoCleanupMinBackoff: viper.GetDuration("AUTO_CLEANUP_MIN_BACKOFF"),
68 | AutoCleanupMaxBackoff: viper.GetDuration("AUTO_CLEANUP_MAX_BACKOFF"),
69 | }
70 | return cfg
71 | }
72 |
73 | func mustHaveString(key string) string {
74 | if !viper.IsSet(key) {
75 | panic(fmt.Sprintf("key %s is not set", key))
76 | } else {
77 | return viper.GetString(key)
78 | }
79 | }
80 |
81 | func mustHaveStringSlice(key string) []string {
82 | if !viper.IsSet(key) {
83 | panic(fmt.Sprintf("key %s is not set", key))
84 | } else {
85 | return viper.GetStringSlice(key)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/ui/js/components/control.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { withStyles } from '@material-ui/core/styles';
4 | import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
5 | import SendIcon from '@material-ui/icons/Send';
6 | import IconButton from '@material-ui/core/IconButton';
7 | import WrapTextIcon from '@material-ui/icons/WrapText';
8 | import Select from '@material-ui/core/Select';
9 | import MenuItem from '@material-ui/core/MenuItem';
10 | import InputLabel from '@material-ui/core/InputLabel';
11 | import Tooltip from '@material-ui/core/Tooltip';
12 | import Grid from '@material-ui/core/Grid';
13 | import Icon from '@material-ui/core/Icon';
14 | import { connect } from 'react-redux';
15 | import { Run } from '../actions/run';
16 | import { ThemeUpdate } from '../actions/themeUpdate';
17 | import { WrapText } from '../actions/wrapText';
18 | import fileDownload from 'js-file-download';
19 |
20 | import 'codemirror/theme/ambiance.css';
21 | import 'codemirror/theme/ayu-mirage.css';
22 | import 'codemirror/theme/cobalt.css';
23 | import 'codemirror/theme/darcula.css';
24 | import 'codemirror/theme/material.css';
25 | import 'codemirror/theme/monokai.css';
26 | import 'codemirror/theme/solarized.css';
27 | import '../../style/components/control.css';
28 |
29 | const styles = theme => ({
30 | button: {
31 | marginRight: '8px',
32 | }
33 | });
34 |
35 | const themes = [
36 | 'default',
37 | 'ambiance',
38 | 'ayu-mirage', 'cobalt',
39 | 'darcula',
40 | 'material',
41 | 'monokai',
42 | 'solarized dark',
43 | 'solarized light',
44 | ];
45 |
46 | class Control extends React.Component {
47 | runCode = () => {
48 | const data = {
49 | code: this.props.code,
50 | }
51 | this.props.Run(data)
52 | }
53 |
54 | themeUpdate = (event) => {
55 | this.props.ThemeUpdate(event.target.value)
56 | }
57 |
58 | wrapTextToggle = (event) => {
59 | this.props.WrapText(!this.props.wrap)
60 | }
61 |
62 | download = () => {
63 | fileDownload(
64 | this.props.code,
65 | 'grafonnet-playground.jsonnet',
66 | 'text/plain;charset=utf-8'
67 | )
68 | }
69 |
70 | render() {
71 | const { classes } = this.props;
72 | return(
73 |
74 |
75 |
76 |
77 |
78 | Theme
79 |
88 |
89 |
90 |
91 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | );
126 | }
127 | }
128 |
129 | const mapStateToProps = state => {
130 | return { ...state.RunReducer };
131 | }
132 |
133 | export default connect(mapStateToProps, { Run, WrapText, ThemeUpdate }) (withStyles(styles) (Control));
134 |
--------------------------------------------------------------------------------
/ui/js/components/editor.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Controlled as CodeMirror } from 'react-codemirror2'
3 | import { connect } from 'react-redux';
4 | import { CodeUpdate } from '../actions/codeUpdate';
5 |
6 | import 'codemirror/lib/codemirror.css';
7 | import '../../style/components/editor.css';
8 |
9 | class Editor extends React.Component {
10 | updateCode = (editor, data, newCode) => {
11 | this.props.CodeUpdate(newCode);
12 | }
13 |
14 | render() { const { code } = this.props;
15 | const options = {
16 | mode: 'jsonnet',
17 | lineNumbers: true,
18 | indentUnit: 2,
19 | matchBrackets: true,
20 | deleteLine: true,
21 | undo: true,
22 | redo: true,
23 | smartIndent: true,
24 | autoCursor: false,
25 | lineWrapping: this.props.wrap,
26 | theme: this.props.theme,
27 | };
28 | const customMode = {
29 | name: 'jsonnet',
30 | fn: jsonnetMode,
31 | };
32 | return(
33 |
34 |
40 |
41 | );
42 | }
43 | }
44 |
45 | const mapStateToProps = state => {
46 | return { ...state.RunReducer };
47 | }
48 |
49 | const keywords = {
50 | "local": "keyword",
51 | "self": "keyword",
52 | "super": "keyword",
53 | "assert": "keyword",
54 | "function": "keyword",
55 | "if": "keyword",
56 | "then": "keyword",
57 | "else": "keyword",
58 | "for": "keyword",
59 | "in": "keyword",
60 | "tailstrict": "keyword",
61 | "error": "keyword",
62 | "true": "atom",
63 | "false": "atom",
64 | "null": "atom",
65 | };
66 |
67 | const jsonnetMode = function() {
68 | return {
69 | token: function(stream, state) {
70 | // Handle special states:
71 |
72 | // In a C-style comment
73 | if (state.cComment) {
74 | if (stream.match(/\*\//)) {
75 | state.cComment = false;
76 | return "comment";
77 | }
78 | stream.next();
79 | return "comment";
80 | }
81 |
82 | // In a text block (|||)
83 | if (state.textBlock) {
84 | if (stream.match(/$/)) {
85 | stream.next();
86 | return "string";
87 | }
88 | if (state.textBlockIndent == null) {
89 | if (stream.match(/\s*\|\|\|/)) {
90 | state.textBlock = false;
91 | return "string";
92 | }
93 | state.textBlockIndent = stream.indentation();
94 | }
95 | if (state.textBlockIndent != null
96 | && stream.indentation() >= state.textBlockIndent) {
97 | stream.skipToEnd();
98 | return "string";
99 | }
100 | if (stream.match(/\s*\|\|\|/)) {
101 | state.textBlock = false;
102 | return "string";
103 | }
104 | stream.next();
105 | return "error";
106 | }
107 |
108 | // In a string (all 4 variants)
109 | if (state.string || state.importString) {
110 | let mode = state.string ? "string" : "meta";
111 | if (state.stringRaw) {
112 | if (stream.match(state.stringSingle ? /''/ : /""/)) {
113 | return "string-2";
114 | }
115 | } else {
116 | if (stream.match(/\\[\\"'\/bfnrt0]/)) {
117 | return "string-2";
118 | }
119 | if (stream.match(/\\u[0-9a-fA-F]{4}/)) {
120 | return "string-2";
121 | }
122 | if (stream.match(/\\/)) {
123 | return "error";
124 | }
125 | }
126 | if (stream.match(state.stringSingle ? /'/ : /"/)) {
127 | state.string = false;
128 | state.importString = false;
129 | state.stringRaw = false;
130 | state.stringDouble = false;
131 | return mode;
132 | }
133 | stream.next();
134 | return mode;
135 | }
136 |
137 | // Regular (whole token at a time) processing:
138 |
139 | // Comments.
140 | if (stream.match(/\/\//) || stream.match(/#/)) {
141 | stream.skipToEnd();
142 | return "comment";
143 | }
144 | if (stream.match(/\/\*/)) {
145 | state.cComment = true;
146 | return "comment";
147 | }
148 |
149 | // Imports (including the strings after them).
150 | if (stream.match(/import(?:str)?\s*"/)) {
151 | state.importString = true;
152 | state.stringSingle = false;
153 | state.stringRaw = false;
154 | return "meta";
155 | }
156 |
157 | if (stream.match(/import(?:str)?\s*'/)) {
158 | state.importString = true;
159 | state.stringSingle = true;
160 | state.stringRaw = false;
161 | return "meta";
162 | }
163 |
164 | if (stream.match(/import(?:str)?\s*@"/)) {
165 | state.importString = true;
166 | state.stringSingle = false;
167 | state.stringRaw = true;
168 | return "meta";
169 | }
170 |
171 | if (stream.match(/import(?:str)?\s*@'/)) {
172 | state.importString = true;
173 | state.stringSingle = true;
174 | state.stringRaw = true;
175 | return "meta";
176 | }
177 |
178 | // Strings (without imports)
179 | if (stream.match(/"/)) {
180 | state.string = true;
181 | state.stringSingle = false;
182 | state.stringRaw = false;
183 | return "string";
184 | }
185 |
186 | if (stream.match(/'/)) {
187 | state.string = true;
188 | state.stringSingle = true;
189 | state.stringRaw = false;
190 | return "string";
191 | }
192 |
193 | if (stream.match(/@"/)) {
194 | state.string = true;
195 | state.stringSingle = false;
196 | state.stringRaw = true;
197 | return "string";
198 | }
199 |
200 | if (stream.match(/@'/)) {
201 | state.string = true;
202 | state.stringSingle = true;
203 | state.stringRaw = true;
204 | return "string";
205 | }
206 |
207 | // Enter text block.
208 | if (stream.match(/\|\|\|/)) {
209 | state.textBlock = true;
210 | state.textBlockIndent = null;
211 | return "string";
212 | }
213 |
214 | if (stream.match(/\$/)) return "keyword";
215 | if (stream.match(/(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i)) return "number";
216 | if (stream.match(/[-+\/*=<>!&~^|$%]+/)) return "operator";
217 |
218 | // Identifiers and keywords that look like identifiers.
219 | let identifier = stream.match(/[a-zA-Z_][a-zA-Z0-9_]*/);
220 | if (identifier) {
221 | identifier = identifier[0];
222 | // If it's not in the dict, we return null to indicate no special
223 | // syntax highlighting.
224 | return keywords.hasOwnProperty(identifier) ? keywords[identifier] : undefined;
225 | }
226 |
227 | stream.next();
228 |
229 | return null;
230 | },
231 | startState: function() {
232 | return {
233 | cComment: false,
234 | textBlock: false,
235 | importString: false,
236 | string: false,
237 | stringSingle: false,
238 | stringRaw: false,
239 | };
240 | },
241 | };
242 | }
243 |
244 | export default connect(mapStateToProps, { CodeUpdate }) (Editor);
245 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
14 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
15 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
16 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
17 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
18 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
19 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
20 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
21 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
22 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
23 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
24 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
25 | github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
26 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
27 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
28 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
29 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
30 | github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
31 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
32 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
33 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
34 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
35 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
36 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
37 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
38 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
39 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
40 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
41 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
42 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
43 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
44 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
45 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
46 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
47 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
48 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
49 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
50 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
51 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
52 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
53 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
54 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
55 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
56 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
57 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
58 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
59 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
60 | github.com/gin-gonic/contrib v0.0.0-20190526021735-7fb7810ed2a0 h1:R0oj52DmXWiPxZEedx/Uxxbvs6yB0l8hLgrubGpKsq4=
61 | github.com/gin-gonic/contrib v0.0.0-20190526021735-7fb7810ed2a0/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
62 | github.com/gin-gonic/contrib v0.0.0-20200810162008-6dee08bf958e h1:jV4//AYvgzLAXVNYX5a4/kZdPV2LV/PkgLsS6z+9cFs=
63 | github.com/gin-gonic/contrib v0.0.0-20200810162008-6dee08bf958e/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
64 | github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
65 | github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
66 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
67 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
68 | github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
69 | github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
70 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
71 | github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
72 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
73 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
74 | github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
75 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
76 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
77 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
78 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
79 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
80 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
81 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
82 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
83 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
84 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
85 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
86 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
87 | github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o=
88 | github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
89 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
90 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
91 | github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
92 | github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
93 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
94 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
95 | github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
96 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
97 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
98 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
99 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
100 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
101 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
102 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
103 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
104 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
105 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
106 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
107 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
108 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
109 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
110 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
111 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
112 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
113 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
114 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
115 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
116 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
117 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
118 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
119 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
120 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
121 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
122 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
123 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
124 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
125 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
126 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
127 | github.com/google/go-jsonnet v0.12.1 h1:v0iUm/b4SBz7lR/diMoz9tLAz8lqtnNRKIwMrmU2HEU=
128 | github.com/google/go-jsonnet v0.12.1/go.mod h1:gVu3UVSfOt5fRFq+dh9duBqXa5905QY8S1QvMNcEIVs=
129 | github.com/google/go-jsonnet v0.16.0 h1:Nb4EEOp+rdeGGyB1rQ5eisgSAqrTnhf9ip+X6lzZbY0=
130 | github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=
131 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
132 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
133 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
134 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
135 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
136 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
137 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
138 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
139 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
140 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
141 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
142 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
143 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
144 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
145 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
146 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
147 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
148 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
149 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
150 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
151 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
152 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
153 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
154 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
155 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
156 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
157 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
158 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
159 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
160 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
161 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
162 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
163 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
164 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
165 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
166 | github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
167 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
168 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
169 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
170 | github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
171 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
172 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
173 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
174 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
175 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
176 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
177 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
178 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
179 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
180 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
181 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
182 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
183 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
184 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
185 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
186 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
187 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
188 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
189 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
190 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
191 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
192 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
193 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
194 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
195 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
196 | github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
197 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
198 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
199 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
200 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
201 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
202 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
203 | github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
204 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
205 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
206 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
207 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
208 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
209 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
210 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
211 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
212 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
213 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
214 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
215 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
216 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
217 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
218 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
219 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
220 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
221 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
222 | github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
223 | github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
224 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
225 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
226 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
227 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
228 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
229 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
230 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
231 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
232 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
233 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
234 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
235 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
236 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
237 | github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
238 | github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
239 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
240 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
241 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
242 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
243 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
244 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
245 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
246 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
247 | github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
248 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
249 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
250 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
251 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
252 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
253 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
254 | github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
255 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
256 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
257 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
258 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
259 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
260 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
261 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
262 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
263 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
264 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
265 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
266 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
267 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
268 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
269 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
270 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
271 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
272 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
273 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
274 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
275 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
276 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
277 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
278 | github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
279 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
280 | github.com/spf13/afero v1.3.4 h1:8q6vk3hthlpb2SouZcnBVKboxWQWMDNF38bwholZrJc=
281 | github.com/spf13/afero v1.3.4/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
282 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
283 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
284 | github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
285 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
286 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
287 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
288 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
289 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
290 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
291 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
292 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
293 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
294 | github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
295 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
296 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
297 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
298 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
299 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
300 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
301 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
302 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
303 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
304 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
305 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
306 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
307 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
308 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
309 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
310 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
311 | github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
312 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
313 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
314 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
315 | github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
316 | github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
317 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
318 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
319 | github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
320 | github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
321 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
322 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
323 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
324 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
325 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
326 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
327 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
328 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
329 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
330 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
331 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
332 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
333 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
334 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
335 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
336 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
337 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
338 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
339 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
340 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
341 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
342 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
343 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
344 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
345 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
346 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
347 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
348 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
349 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
350 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
351 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
352 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
353 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
354 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
355 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
356 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
357 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
358 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
359 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
360 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
361 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
362 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
363 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
364 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
365 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
366 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
367 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
368 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
369 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
370 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
371 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
372 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
373 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
374 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
375 | golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
376 | golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
377 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
378 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
379 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
380 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
381 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
382 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
383 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
384 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
385 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
386 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
387 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
388 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
389 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
390 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
391 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
392 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
393 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
394 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
395 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
396 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
397 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
398 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
399 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
400 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
401 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
402 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
403 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
404 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
405 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
406 | golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38=
407 | golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
408 | golang.org/x/sys v0.0.0-20200821140526-fda516888d29 h1:mNuhGagCf3lDDm5C0376C/sxh6V7fy9WbdEu/YDNA04=
409 | golang.org/x/sys v0.0.0-20200821140526-fda516888d29/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
410 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
411 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
412 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
413 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
414 | golang.org/x/sys v0.0.0-20211109065445-02f5c0300f6e h1:i6Vklmyu+fZMFYpum+sR4ZWABGW7MyIxfJZXYvcnbns=
415 | golang.org/x/sys v0.0.0-20211109065445-02f5c0300f6e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
416 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
417 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
418 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
419 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
420 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
421 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
422 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
423 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
424 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
425 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
426 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
427 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
428 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
429 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
430 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
431 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
432 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
433 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
434 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
435 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
436 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
437 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
438 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
439 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
440 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
441 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
442 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
443 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
444 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
445 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
446 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
447 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
448 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
449 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
450 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
451 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
452 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
453 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
454 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
455 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
456 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
457 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
458 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
459 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
460 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
461 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
462 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
463 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
464 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
465 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
466 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
467 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
468 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
469 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
470 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
471 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
472 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
473 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
474 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
475 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
476 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
477 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
478 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
479 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
480 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
481 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
482 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
483 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
484 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
485 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
486 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
487 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
488 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
489 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
490 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
491 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
492 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
493 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
494 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
495 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
496 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
497 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
498 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
499 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
500 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
501 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
502 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
503 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
504 | gopkg.in/ini.v1 v1.60.0 h1:P5ZzC7RJO04094NJYlEnBdFK2wwmnCAy/+7sAzvWs60=
505 | gopkg.in/ini.v1 v1.60.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
506 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
507 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
508 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
509 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
510 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
511 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
512 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
513 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
514 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
515 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
516 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
517 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
518 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
519 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
520 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
521 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
522 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
523 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
524 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
525 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
526 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
527 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
528 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
529 |
--------------------------------------------------------------------------------