瀏覽代碼

Add Kubernetes backend

Signed-off-by: aiordache <[email protected]>
aiordache 4 年之前
父節點
當前提交
50792c4621

+ 1 - 1
Makefile

@@ -39,7 +39,7 @@ protos: ## Generate go code from .proto files
 cli: ## Compile the cli
 	@docker build . --target cli \
 	--platform local \
-	--build-arg BUILD_TAGS=e2e \
+	--build-arg BUILD_TAGS=e2e,kube \
 	--build-arg GIT_TAG=$(GIT_TAG) \
 	--output ./bin
 

+ 6 - 0
api/context/store/contextmetadata.go

@@ -55,6 +55,12 @@ type EcsContext struct {
 	Profile            string `json:",omitempty"`
 }
 
+// KubeContext is the context for a kube backend
+type KubeContext struct {
+	Endpoint        string `json:",omitempty"`
+	FromEnvironment bool
+}
+
 // AwsContext is the context for the ecs plugin
 type AwsContext EcsContext
 

+ 6 - 0
api/context/store/store.go

@@ -55,6 +55,9 @@ const (
 	// LocalContextType is the endpoint key in the context endpoints for a new
 	// local backend
 	LocalContextType = "local"
+	// KubeContextType is the endpoint key in the context endpoints for a new
+	// kube backend
+	KubeContextType = "kube"
 )
 
 const (
@@ -328,5 +331,8 @@ func getters() map[string]func() interface{} {
 		LocalContextType: func() interface{} {
 			return &LocalContext{}
 		},
+		KubeContextType: func() interface{} {
+			return &KubeContext{}
+		},
 	}
 }

+ 86 - 0
cli/cmd/context/create_kube.go

@@ -0,0 +1,86 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package context
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+
+	"github.com/docker/compose-cli/api/context/store"
+	"github.com/docker/compose-cli/api/errdefs"
+	"github.com/docker/compose-cli/kube"
+)
+
+func init() {
+	extraCommands = append(extraCommands, createKubeCommand)
+	extraHelp = append(extraHelp, `
+Create a Kube context:
+$ docker context create kube CONTEXT [flags]
+(see docker context create kube --help)
+`)
+}
+
+func createKubeCommand() *cobra.Command {
+	var opts kube.ContextParams
+	cmd := &cobra.Command{
+		Use:   "kube CONTEXT [flags]",
+		Short: "Create context for a Kubernetes Cluster",
+		Args:  cobra.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.Name = args[0]
+
+			if opts.Endpoint != "" {
+				opts.FromEnvironment = false
+			}
+			return runCreateKube(cmd.Context(), args[0], opts)
+		},
+	}
+
+	addDescriptionFlag(cmd, &opts.Description)
+	cmd.Flags().StringVar(&opts.Endpoint, "endpoint", "", "The endpoint of the Kubernetes manager")
+	cmd.Flags().BoolVar(&opts.FromEnvironment, "from-env", true, "Get endpoint and creds from env vars")
+	return cmd
+}
+
+func runCreateKube(ctx context.Context, contextName string, opts kube.ContextParams) error {
+	if contextExists(ctx, contextName) {
+		return errors.Wrapf(errdefs.ErrAlreadyExists, "context %q", contextName)
+	}
+
+	contextData, description, err := createContextData(ctx, opts)
+	if err != nil {
+		return err
+	}
+	return createDockerContext(ctx, contextName, store.KubeContextType, description, contextData)
+}
+
+func createContextData(ctx context.Context, opts kube.ContextParams) (interface{}, string, error) {
+	description := ""
+	if opts.Description != "" {
+		description = fmt.Sprintf("%s (%s)", opts.Description, description)
+	}
+
+	return store.KubeContext{
+		Endpoint:        opts.Endpoint,
+		FromEnvironment: opts.FromEnvironment,
+	}, description, nil
+}

+ 5 - 4
go.mod

@@ -39,14 +39,13 @@ require (
 	github.com/joho/godotenv v1.3.0
 	github.com/labstack/echo v3.3.10+incompatible
 	github.com/labstack/gommon v0.3.0 // indirect
-	github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
 	github.com/moby/buildkit v0.8.1-0.20201205083753-0af7b1b9c693
 	github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf
 	github.com/morikuni/aec v1.0.0
 	github.com/opencontainers/go-digest v1.0.0
 	github.com/opencontainers/image-spec v1.0.1
 	github.com/pkg/errors v0.9.1
-	github.com/prometheus/procfs v0.2.0 // indirect
+	github.com/prometheus/common v0.10.0
 	github.com/prometheus/tsdb v0.10.0
 	github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b
 	github.com/sirupsen/logrus v1.7.0
@@ -57,13 +56,15 @@ require (
 	golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
 	golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58
 	golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
-	google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect
 	google.golang.org/grpc v1.33.2
 	google.golang.org/protobuf v1.25.0
 	gopkg.in/ini.v1 v1.62.0
+	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
 	gotest.tools v2.2.0+incompatible
 	gotest.tools/v3 v3.0.3
-	k8s.io/client-go v0.20.1 // indirect
+	helm.sh/helm/v3 v3.5.0
+	k8s.io/api v0.20.1
+	k8s.io/apimachinery v0.20.1
 	sigs.k8s.io/kustomize/kyaml v0.10.5
 )
 

+ 212 - 0
go.sum

@@ -128,16 +128,30 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
+github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
 github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
 github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
 github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
 github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
 github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo=
 github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU=
+github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
+github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
+github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
 github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/sprig/v3 v3.2.0 h1:P1ekkbuU73Ui/wS0nK1HOM37hh4xdfZo485UPf8rc+Y=
+github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI=
+github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8=
+github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
+github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
 github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
@@ -164,10 +178,13 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
 github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
 github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
+github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
+github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
 github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
 github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
@@ -175,14 +192,17 @@ github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4Rq
 github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
 github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
 github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ=
 github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs=
 github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
@@ -195,8 +215,12 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=
+github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
+github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
 github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
 github.com/aws/aws-sdk-go v1.15.90/go.mod h1:es1KtYUFs7le0xQ3rOihkuoVD90z7D0fR2Qm4S00/gU=
@@ -205,10 +229,12 @@ github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
 github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
 github.com/aws/aws-sdk-go v1.35.33 h1:8qPRZqCRok5i7VNN51k/Ky7CuyoXMdSs4mUfKyCqvPw=
 github.com/aws/aws-sdk-go v1.35.33/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/awslabs/goformation/v4 v4.15.6 h1:9F0MbtJVSMkuI19G6Fm+qHc1nqScHcOIf+3YRRv+Ohc=
 github.com/awslabs/goformation/v4 v4.15.6/go.mod h1:wB5lKZf1J0MYH1Lt4B9w3opqz0uIjP7MMCAcib3QkwA=
 github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
@@ -218,28 +244,35 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
+github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
 github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
 github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
 github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U=
 github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg=
 github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
 github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
 github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
+github.com/bshuster-repo/logrus-logstash-hook v0.4.1 h1:pgAtgj+A31JBVtEHu2uHuEx0n+2ukqUJnS2vVe5pQNA=
 github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
 github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129 h1:gfAMKE626QEuKG3si0pdTRcr/YEbBoxY+3GOH3gWvl4=
 github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129/go.mod h1:u9UyCz2eTrSGy6fbupqJ54eY5c4IC8gREQ1053dK12U=
 github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
 github.com/bugsnag/bugsnag-go v1.4.1/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
+github.com/bugsnag/bugsnag-go v1.5.0 h1:tP8hiPv1pGGW3LA6LKy5lW6WG+y9J2xWUdPd3WC452k=
 github.com/bugsnag/bugsnag-go v1.5.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
 github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
 github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
+github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA=
 github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
 github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw=
 github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo=
+github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
 github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
@@ -251,6 +284,7 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
 github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
 github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@@ -258,6 +292,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
 github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
 github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775 h1:cHzBGGVew0ezFsq2grfy2RsB8hO/eNyBgOLHBCqfR1U=
 github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
+github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY=
 github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
@@ -273,6 +308,7 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z
 github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/compose-spec/compose-go v0.0.0-20210119095023-cd294eea46e9 h1:fk9KYzKkVy6q1ETSXOPDHxeoj2ZBKZFP27XVfVMRMUM=
 github.com/compose-spec/compose-go v0.0.0-20210119095023-cd294eea46e9/go.mod h1:rz7rjxJGA/pWpLdBmDdqymGm2okEDYgBE7yx569xW+I=
+github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
 github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
 github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340 h1:9atoWyI9RtXFwf7UDbme/6M8Ud0rFrx+Q3ZWgSnsxtw=
@@ -286,12 +322,14 @@ github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX
 github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
+github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/containerd v1.4.3 h1:ijQT13JedHSHrQGWFcGEwzcNKrAGIiZ+jSD5QQG07SY=
 github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
 github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
+github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
 github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo=
 github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a h1:jEIoR0aA5GogXZ8pP3DUzE+zrhaF6/1rYZy+7KkYEWM=
 github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a/go.mod h1:W0qIOTD7mp2He++YVq+kgfXezRYqzP1uDuMVH1bITDY=
@@ -338,14 +376,19 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
 github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
 github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
 github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
 github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
+github.com/deislabs/oras v0.8.1 h1:If674KraJVpujYR00rzdi0QAmW4BxzMJPVAZJKuhQ0c=
+github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As=
 github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
 github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
+github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
 github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@@ -358,6 +401,7 @@ github.com/docker/buildx v0.5.1 h1:lkMHQPKHyUUDbO2QO2JE9LLi8mlOULpzSoV7B/2XKso=
 github.com/docker/buildx v0.5.1/go.mod h1:YlxswdEKSMrxCCSYWU2p/Ii1oOOwu8lT3tJzJDpP7J4=
 github.com/docker/cli v0.0.0-20190925022749-754388324470/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/cli v20.10.0-beta1.0.20201029214301-1d20b15adc38+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/cli v20.10.1+incompatible h1:7wfpCo1keo9xXmH6fFu29m0BCK0+Uzu4oIopGUS09bM=
 github.com/docker/cli v20.10.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
@@ -369,6 +413,7 @@ github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r
 github.com/docker/docker v1.4.2-0.20180531152204-71cd53e4a197/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v1.4.2-0.20181229214054-f76d6a078d88/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v17.12.0-ce-rc1.0.20200730172259-9f28837c1d93+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v20.10.0-beta1.0.20201110211921-af34b94a78a1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker v20.10.1+incompatible h1:u0HIBLwOJdemyBdTCkoBX34u3lb5KyBo0rQE3a5Yg+E=
@@ -396,6 +441,7 @@ github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4/go.mod h1:cyGadeNE
 github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
 github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
 github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
+github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s=
 github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -404,33 +450,47 @@ github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT
 github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
 github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
+github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 h1:pEtiCjIXx3RvGjlUJuCNxNOw0MNblyR9Wi+vJGBFh+8=
 github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
+github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
 github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
 github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
+github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
+github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
+github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
+github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko=
 github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
 github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
@@ -446,10 +506,14 @@ github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3I
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
+github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
 github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY=
 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
@@ -506,6 +570,7 @@ github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85n
 github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -525,7 +590,17 @@ github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslW
 github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
 github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
 github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
+github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8=
+github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
 github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
+github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg=
+github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
+github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
+github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
+github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o=
+github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
+github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
 github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
@@ -539,10 +614,15 @@ github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7
 github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
 github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg=
 github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
 github.com/gofrs/flock v0.7.3 h1:I0EKY9l8HZCXTMYC4F80vwT6KNypV9uYKP3Alm/hjmQ=
 github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
+github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY=
+github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
+github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
 github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
 github.com/gogo/googleapis v1.3.0/go.mod h1:d+q1s/xVJxZGKWwC/6UfPIF33J+G1Tq4GYv9Y+Tg/EU=
 github.com/gogo/googleapis v1.3.2 h1:kX1es4djPJrsDhY7aZKJy7aZasdcB5oSOEphMjSB53c=
@@ -554,6 +634,7 @@ github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5
 github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -608,8 +689,12 @@ github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bz
 github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
 github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
 github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
+github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho=
+github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8=
+github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
 github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
 github.com/google/crfs v0.0.0-20191108021818-71d77da419c9/go.mod h1:etGhoOqfwPkooV6aqoX3eBGQOJblqdoc9XvWOeuxpPw=
@@ -674,6 +759,7 @@ github.com/goreleaser/goreleaser v0.136.0/go.mod h1:wiKrPUeSNh6Wu8nUHxZydSOVQ/OZ
 github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhjYcR6G9w=
 github.com/goreleaser/nfpm v1.3.0/go.mod h1:w0p7Kc9TAUgWMyrub63ex3M2Mgw88M4GZXoTq5UCb40=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 h1:893HsJqtxp9z1SF76gg6hY70hRY1wVlTSnC/h1yUDCo=
 github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -685,8 +771,11 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
 github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
+github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
+github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
 github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
 github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
 github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@@ -705,7 +794,9 @@ github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMW
 github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
 github.com/hanwen/go-fuse/v2 v2.0.3/go.mod h1:0EQM6aH2ctVpvZ6a+onrQ/vaykxh2GH7hy3e13vzTUY=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -743,6 +834,9 @@ github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09Uny
 github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
 github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
+github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/iancoleman/strcase v0.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U=
 github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -753,6 +847,7 @@ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
 github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg=
 github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
 github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
@@ -776,6 +871,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo=
 github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
+github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
+github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5 h1:lrdPtrORjGv1HbbEvKWDUAy97mPpFm4B8hp77tcCUJY=
 github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
 github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
 github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
@@ -795,6 +892,7 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
+github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
@@ -813,6 +911,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -829,11 +928,23 @@ github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8
 github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
 github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
+github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
+github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
+github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
+github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
 github.com/lib/pq v0.0.0-20180201184707-88edab080323/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
+github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
+github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
+github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
+github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
+github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
 github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
+github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -865,12 +976,17 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y
 github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
 github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
 github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.12.0 h1:u/x3mp++qUxvYfulZ4HKOvVO0JWhk7HtE8lWhbGz/Do=
+github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@@ -884,12 +1000,16 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N
 github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19/go.mod h1:WCBAbTOdfhHhz7YXujeZMF7owC4tPb1naKFsgfUISjo=
 github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
 github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
 github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
 github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
@@ -899,6 +1019,8 @@ github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
 github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks=
 github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
+github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/moby/buildkit v0.8.1-0.20201205083753-0af7b1b9c693 h1:WD0QJVZm1JRoUYhnu37QoCHC1wsrJYIUnRaPzX55Xlc=
 github.com/moby/buildkit v0.8.1-0.20201205083753-0af7b1b9c693/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ=
 github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
@@ -910,6 +1032,7 @@ github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+S
 github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
 github.com/moby/sys/mountinfo v0.4.0 h1:1KInV3Huv18akCu58V7lzNlt+jFmqlu1EaErnEHE/VM=
 github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
+github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
 github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2/go.mod h1:TjQg8pa4iejrUrjiz0MCtMV38jdMNW4doKSiBrEvCQQ=
 github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf h1:Un6PNx5oMK6CCwO3QTUyPiK2mtZnPrpDl5UnZ64eCkw=
 github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
@@ -934,6 +1057,13 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
 github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c=
+github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
+github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
+github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
+github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
+github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
 github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
@@ -941,8 +1071,12 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
 github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
 github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
+github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -984,20 +1118,32 @@ github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6/go.m
 github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
 github.com/opencontainers/selinux v1.6.0 h1:+bIAS/Za3q5FTwWym4fTB0vObnfCf3G/NC7K6Jx62mY=
 github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
+github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
 github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU=
+github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
+github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
+github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
 github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
 github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
 github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
+github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
+github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
+github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
+github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
+github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pivotal/image-relocation v0.0.0-20191111101224-e94aff6df06c/go.mod h1:/JNbQwGylYm3AQh8q+MBF8e/h0W1Jy20JGTvozuXYTE=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -1005,6 +1151,7 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
 github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -1017,12 +1164,14 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
+github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
@@ -1043,6 +1192,7 @@ github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7z
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
 github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
@@ -1062,15 +1212,23 @@ github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uY
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
+github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w=
 github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 h1:HXr/qUllAWv9riaI4zh2eXWKmCSDqVS/XH1MRHLKRwk=
+github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg=
 github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
+github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryancurrah/gomodguard v1.0.4/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0fVb9d5fSEaLhoE=
 github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
 github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b h1:jUK33OXuZP/l6babJtnLo1qsGvq6G9so9KMflGAm4YA=
 github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b/go.mod h1:8458kAagoME2+LN5//WxE71ysZ3B7r22fdgb7qVmXSY=
 github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522 h1:fOCp11H0yuyAt2wqlbJtbyPzSgaxHTv8uN1pMpkG1t8=
@@ -1090,6 +1248,8 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX
 github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
 github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
 github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
+github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
 github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@@ -1110,12 +1270,14 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
 github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@@ -1136,6 +1298,9 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
 github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
 github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -1232,11 +1397,15 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
 github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
+github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
 github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
+github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=
 github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
 github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
 github.com/zclconf/go-cty v1.4.0/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ=
+github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
 github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
@@ -1247,6 +1416,7 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
 go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
 go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
@@ -1255,6 +1425,7 @@ go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
 go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A=
 go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -1265,8 +1436,12 @@ go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWK
 go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
 gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI=
 golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4=
@@ -1285,6 +1460,7 @@ golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -1292,6 +1468,7 @@ golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o=
@@ -1431,6 +1608,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1457,6 +1635,7 @@ golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1544,8 +1723,11 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191014205221-18e3458ac98b/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -1557,6 +1739,7 @@ golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200102140908-9497f49d5709/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -1576,8 +1759,10 @@ golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roY
 golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
@@ -1675,6 +1860,7 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE
 google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1703,6 +1889,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
 google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1716,8 +1903,11 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
+gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
 gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I=
+gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw=
+gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
 gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
@@ -1731,6 +1921,7 @@ gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1749,6 +1940,8 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
 gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
 grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
+helm.sh/helm/v3 v3.5.0 h1:uqIT3Bh4hVEyZRThyTPik8FkiABj3VJIY+POvDFT3a4=
+helm.sh/helm/v3 v3.5.0/go.mod h1:bjwXfmGAF+SEuJZ2AtN1xmTuz4FqaNYOJrXP+vtj6Tw=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1766,6 +1959,8 @@ k8s.io/api v0.19.0 h1:XyrFIJqTYZJ2DU7FBE/bSPz7b1HvbVBuBf07oeo6eTc=
 k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw=
 k8s.io/api v0.20.1 h1:ud1c3W3YNzGd6ABJlbFfKXBKXO+1KdGfcgGGNgFR03E=
 k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
+k8s.io/apiextensions-apiserver v0.20.1 h1:ZrXQeslal+6zKM/HjDXLzThlz/vPSxrfK3OqL8txgVQ=
+k8s.io/apiextensions-apiserver v0.20.1/go.mod h1:ntnrZV+6a3dB504qwC5PN/Yg9PBiDNt1EVqbW2kORVk=
 k8s.io/apimachinery v0.0.0-20180904193909-def12e63c512/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
 k8s.io/apimachinery v0.0.0-20190806215851-162a2dabc72f/go.mod h1:+ntn62igV2hyNj7/0brOvXSMONE2KxcePkSxK7/9FFQ=
 k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ=
@@ -1776,6 +1971,10 @@ k8s.io/apimachinery v0.20.1 h1:LAhz8pKbgR8tUwn7boK+b2HZdt7MiTu2mkYtFMUjTRQ=
 k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
 k8s.io/apiserver v0.17.4 h1:bYc9LvDPEF9xAL3fhbDzqNOQOAnNF2ZYCrMW8v52/mE=
 k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I=
+k8s.io/apiserver v0.20.1 h1:yEqdkxlnQbxi/3e74cp0X16h140fpvPrNnNRAJBDuBk=
+k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
+k8s.io/cli-runtime v0.20.1 h1:fJhRQ9EfTpJpCqSFOAqnYLuu5aAM7yyORWZ26qW1jJc=
+k8s.io/cli-runtime v0.20.1/go.mod h1:6wkMM16ZXTi7Ow3JLYPe10bS+XBnIkL6V9dmEz0mbuY=
 k8s.io/client-go v0.0.0-20180910083459-2cefa64ff137/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
 k8s.io/client-go v0.0.0-20191016111102-bec269661e48/go.mod h1:hrwktSwYGI4JK+TJA3dMaFyyvHVi/aLarVHpbs8bgCU=
 k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc=
@@ -1785,13 +1984,18 @@ k8s.io/client-go v0.20.1 h1:Qquik0xNFbK9aUG92pxHYsyfea5/RPO9o9bSywNor+M=
 k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
 k8s.io/cloud-provider v0.17.4/go.mod h1:XEjKDzfD+b9MTLXQFlDGkk6Ho8SGMpaU8Uugx/KNK9U=
 k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
+k8s.io/code-generator v0.20.1/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg=
 k8s.io/component-base v0.17.4 h1:H9cdWZyiGVJfWmWIcHd66IsNBWTk1iEgU7D4kJksEnw=
 k8s.io/component-base v0.17.4/go.mod h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE=
+k8s.io/component-base v0.20.1 h1:6OQaHr205NSl24t5wOF2IhdrlxZTWEZwuGlLvBgaeIg=
+k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
+k8s.io/component-helpers v0.20.1/go.mod h1:Q8trCj1zyLNdeur6pD2QvsF8d/nWVfK71YjN5+qVXy4=
 k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM=
 k8s.io/csi-translation-lib v0.17.4/go.mod h1:CsxmjwxEI0tTNMzffIAcgR9lX4wOh6AKHdxQrT7L0oo=
 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
+k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
@@ -1808,11 +2012,15 @@ k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4y
 k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
 k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
+k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd h1:sOHNzJIkytDF6qadMNKhhDRpc6ODik8lVC6nOur7B2c=
 k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
+k8s.io/kubectl v0.20.1 h1:7h1vSrL/B3hLrhlCJhbTADElPKDbx+oVUt3+QDSXxBo=
+k8s.io/kubectl v0.20.1/go.mod h1:2bE0JLYTRDVKDiTREFsjLAx4R2GvUtL/mGYFXfFFMzY=
 k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
 k8s.io/kubernetes v1.13.0 h1:qTfB+u5M92k2fCCCVP2iuhgwwSOv1EkAkvQY1tQODD8=
 k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
 k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuBkhu+yxOc1Js=
+k8s.io/metrics v0.20.1/go.mod h1:JhpBE/fad3yRGsgEpiZz5FQQM5wJ18OTLkD7Tv40c0s=
 k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg=
@@ -1832,6 +2040,9 @@ pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
+sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
+sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
 sigs.k8s.io/kustomize/kyaml v0.10.5 h1:PbJcsZsEM7O3hHtUWTR+4WkHVbQRW9crSy75or1gRbI=
 sigs.k8s.io/kustomize/kyaml v0.10.5/go.mod h1:P6Oy/ah/GZMKzJMIJA2a3/bc8YrBkuL5kJji13PSIzY=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
@@ -1844,6 +2055,7 @@ sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
+sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
 sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
 sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4=
 vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=

+ 84 - 0
kube/backend.go

@@ -0,0 +1,84 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package kube
+
+import (
+	"context"
+
+	"github.com/docker/compose-cli/api/backend"
+	"github.com/docker/compose-cli/api/cloud"
+	"github.com/docker/compose-cli/api/compose"
+	"github.com/docker/compose-cli/api/containers"
+	apicontext "github.com/docker/compose-cli/api/context"
+	"github.com/docker/compose-cli/api/context/store"
+	"github.com/docker/compose-cli/api/resources"
+	"github.com/docker/compose-cli/api/secrets"
+	"github.com/docker/compose-cli/api/volumes"
+)
+
+const backendType = store.KubeContextType
+
+type kubeAPIService struct {
+	ctx            store.KubeContext
+	composeService compose.Service
+}
+
+func init() {
+	backend.Register(backendType, backendType, service, cloud.NotImplementedCloudService)
+}
+
+func service(ctx context.Context) (backend.Service, error) {
+
+	contextStore := store.ContextStore(ctx)
+	currentContext := apicontext.CurrentContext(ctx)
+	var kubeContext store.KubeContext
+
+	if err := contextStore.GetEndpoint(currentContext, &kubeContext); err != nil {
+		return nil, err
+	}
+
+	s, err := NewComposeService(kubeContext)
+	if err != nil {
+		return nil, err
+	}
+	return &kubeAPIService{
+		ctx:            kubeContext,
+		composeService: s,
+	}, nil
+}
+
+func (s *kubeAPIService) ContainerService() containers.Service {
+	return nil
+}
+
+func (s *kubeAPIService) ComposeService() compose.Service {
+	return s.composeService
+}
+
+func (s *kubeAPIService) SecretsService() secrets.Service {
+	return nil
+}
+
+func (s *kubeAPIService) VolumeService() volumes.Service {
+	return nil
+}
+
+func (s *kubeAPIService) ResourceService() resources.Service {
+	return nil
+}

+ 126 - 0
kube/charts/charts.go

@@ -0,0 +1,126 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package charts
+
+import (
+	"context"
+	"path/filepath"
+	"strings"
+
+	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/compose-cli/api/compose"
+	"github.com/docker/compose-cli/api/context/store"
+	"github.com/docker/compose-cli/kube/charts/helm"
+	"github.com/docker/compose-cli/kube/charts/kubernetes"
+	kubeutils "github.com/docker/compose-cli/kube/utils"
+	chart "helm.sh/helm/v3/pkg/chart"
+	util "helm.sh/helm/v3/pkg/chartutil"
+	helmenv "helm.sh/helm/v3/pkg/cli"
+)
+
+// API defines management methods for helm charts
+type API interface {
+	GetDefaultEnv() *helmenv.EnvSettings
+	Connect(ctx context.Context) error
+	GenerateChart(project *types.Project, dirname string) error
+	GetChartInMemory(project *types.Project) (*chart.Chart, error)
+	SaveChart(project *types.Project, dest string) error
+
+	Install(project *types.Project) error
+	Uninstall(projectName string) error
+	List(projectName string) ([]compose.Stack, error)
+}
+
+type sdk struct {
+	h           *helm.HelmActions
+	environment map[string]string
+}
+
+// sdk implement API
+var _ API = sdk{}
+
+func NewSDK(ctx store.KubeContext) (sdk, error) {
+	return sdk{
+		environment: kubeutils.Environment(),
+		h:           helm.NewHelmActions(nil),
+	}, nil
+}
+
+func (s sdk) Connect(ctx context.Context) error {
+	return nil
+}
+
+// Install deploys a Compose stack
+func (s sdk) Install(project *types.Project) error {
+	chart, err := s.GetChartInMemory(project)
+	if err != nil {
+		return err
+	}
+	return s.h.InstallChart(project.Name, chart)
+}
+
+// Uninstall removes a runnign compose stack
+func (s sdk) Uninstall(projectName string) error {
+	return s.h.Uninstall(projectName)
+}
+
+// List returns a list of compose stacks
+func (s sdk) List(projectName string) ([]compose.Stack, error) {
+	return s.h.ListReleases()
+}
+
+// GetDefault initializes Helm EnvSettings
+func (s sdk) GetDefaultEnv() *helmenv.EnvSettings {
+	return helmenv.New()
+}
+
+func (s sdk) GetChartInMemory(project *types.Project) (*chart.Chart, error) {
+	// replace _ with - in volume names
+	for k, v := range project.Volumes {
+		volumeName := strings.ReplaceAll(k, "_", "-")
+		if volumeName != k {
+			project.Volumes[volumeName] = v
+			delete(project.Volumes, k)
+		}
+	}
+	objects, err := kubernetes.MapToKubernetesObjects(project)
+	if err != nil {
+		return nil, err
+	}
+	//in memory files
+	return helm.ConvertToChart(project.Name, objects)
+}
+
+func (s sdk) SaveChart(project *types.Project, dest string) error {
+	chart, err := s.GetChartInMemory(project)
+	if err != nil {
+		return err
+	}
+	return util.SaveDir(chart, dest)
+}
+
+func (s sdk) GenerateChart(project *types.Project, dirname string) error {
+	if strings.Contains(dirname, ".") {
+		splits := strings.SplitN(dirname, ".", 2)
+		dirname = splits[0]
+	}
+
+	dirname = filepath.Dir(dirname)
+	return s.SaveChart(project, dirname)
+}

+ 108 - 0
kube/charts/helm/chart.go

@@ -0,0 +1,108 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package helm
+
+import (
+	"bytes"
+	"encoding/json"
+	"html/template"
+	"path/filepath"
+
+	"gopkg.in/yaml.v3"
+
+	chart "helm.sh/helm/v3/pkg/chart"
+	loader "helm.sh/helm/v3/pkg/chart/loader"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func ConvertToChart(name string, objects map[string]runtime.Object) (*chart.Chart, error) {
+
+	files := []*loader.BufferedFile{
+		&loader.BufferedFile{
+			Name: "README.md",
+			Data: []byte("This chart was created by converting a Compose file"),
+		}}
+
+	chart := `name: {{.Name}}
+description: A generated Helm Chart for {{.Name}} from Skippbox Kompose
+version: 0.0.1
+apiVersion: v1
+keywords:
+  - {{.Name}}
+sources:
+home:
+`
+
+	t, err := template.New("ChartTmpl").Parse(chart)
+	if err != nil {
+		return nil, err
+	}
+	type ChartDetails struct {
+		Name string
+	}
+	var chartData bytes.Buffer
+	err = t.Execute(&chartData, ChartDetails{Name: name})
+	if err != nil {
+		return nil, err
+	}
+	files = append(files, &loader.BufferedFile{
+		Name: "Chart.yaml",
+		Data: chartData.Bytes(),
+	})
+
+	for name, o := range objects {
+		j, err := json.Marshal(o)
+		if err != nil {
+			return nil, err
+		}
+		buf, err := jsonToYaml(j, 2)
+		if err != nil {
+			return nil, err
+		}
+		files = append(files, &loader.BufferedFile{
+			Name: filepath.Join("templates", name),
+			Data: buf,
+		})
+
+	}
+	return loader.LoadFiles(files)
+}
+
+// Convert JSON to YAML.
+func jsonToYaml(j []byte, spaces int) ([]byte, error) {
+	// Convert the JSON to an object.
+	var jsonObj interface{}
+	// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
+	// Go JSON library doesn't try to pick the right number type (int, float,
+	// etc.) when unmarshling to interface{}, it just picks float64
+	// universally. go-yaml does go through the effort of picking the right
+	// number type, so we can preserve number type throughout this process.
+	err := yaml.Unmarshal(j, &jsonObj)
+	if err != nil {
+		return nil, err
+	}
+
+	var b bytes.Buffer
+	encoder := yaml.NewEncoder(&b)
+	encoder.SetIndent(spaces)
+	if err := encoder.Encode(jsonObj); err != nil {
+		return nil, err
+	}
+	return b.Bytes(), nil
+}

+ 135 - 0
kube/charts/helm/helm.go

@@ -0,0 +1,135 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package helm
+
+import (
+	"errors"
+	"log"
+
+	"github.com/docker/compose-cli/api/compose"
+	action "helm.sh/helm/v3/pkg/action"
+	chart "helm.sh/helm/v3/pkg/chart"
+	loader "helm.sh/helm/v3/pkg/chart/loader"
+	env "helm.sh/helm/v3/pkg/cli"
+	"helm.sh/helm/v3/pkg/release"
+)
+
+type HelmActions struct {
+	Config         *action.Configuration
+	Settings       *env.EnvSettings
+	kube_conn_init bool
+}
+
+func NewHelmActions(settings *env.EnvSettings) *HelmActions {
+	if settings == nil {
+		settings = env.New()
+	}
+	return &HelmActions{
+		Config:         new(action.Configuration),
+		Settings:       settings,
+		kube_conn_init: false,
+	}
+}
+
+func (hc *HelmActions) initKubeClient() error {
+	if hc.kube_conn_init {
+		return nil
+	}
+	if err := hc.Config.Init(
+		hc.Settings.RESTClientGetter(),
+		hc.Settings.Namespace(),
+		"configmap",
+		log.Printf,
+	); err != nil {
+		log.Fatal(err)
+	}
+	if err := hc.Config.KubeClient.IsReachable(); err != nil {
+		return err
+	}
+	hc.kube_conn_init = true
+	return nil
+}
+
+func (hc *HelmActions) InstallChartFromDir(name string, chartpath string) error {
+	chart, err := loader.Load(chartpath)
+	if err != nil {
+		return err
+	}
+	return hc.InstallChart(name, chart)
+}
+
+func (hc *HelmActions) InstallChart(name string, chart *chart.Chart) error {
+	hc.initKubeClient()
+
+	actInstall := action.NewInstall(hc.Config)
+	actInstall.ReleaseName = name
+	actInstall.Namespace = hc.Settings.Namespace()
+
+	release, err := actInstall.Run(chart, map[string]interface{}{})
+	if err != nil {
+		return err
+	}
+	log.Println("Release status: ", release.Info.Status)
+	log.Println(release.Info.Description)
+	return nil
+}
+
+func (hc *HelmActions) Uninstall(name string) error {
+	hc.initKubeClient()
+	release, err := hc.Get(name)
+	if err != nil {
+		return err
+	}
+	if release == nil {
+		return errors.New("No release found with the name provided.")
+	}
+	actUninstall := action.NewUninstall(hc.Config)
+	response, err := actUninstall.Run(name)
+	if err != nil {
+		return err
+	}
+	log.Println(response.Release.Info.Description)
+	return nil
+}
+
+func (hc *HelmActions) Get(name string) (*release.Release, error) {
+	hc.initKubeClient()
+
+	actGet := action.NewGet(hc.Config)
+	return actGet.Run(name)
+}
+
+func (hc *HelmActions) ListReleases() ([]compose.Stack, error) {
+	hc.initKubeClient()
+
+	actList := action.NewList(hc.Config)
+	releases, err := actList.Run()
+	if err != nil {
+		return nil, err
+	}
+	result := []compose.Stack{}
+	for _, rel := range releases {
+		result = append(result, compose.Stack{
+			ID:     rel.Name,
+			Name:   rel.Name,
+			Status: string(rel.Info.Status),
+		})
+	}
+	return result, nil
+}

+ 232 - 0
kube/charts/kubernetes/kube.go

@@ -0,0 +1,232 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package kubernetes
+
+import (
+	"fmt"
+	"log"
+	"strings"
+	"time"
+
+	"github.com/compose-spec/compose-go/types"
+	apps "k8s.io/api/apps/v1"
+	core "k8s.io/api/core/v1"
+	resource "k8s.io/apimachinery/pkg/api/resource"
+	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+func MapToKubernetesObjects(project *types.Project) (map[string]runtime.Object, error) {
+	objects := map[string]runtime.Object{}
+
+	for _, service := range project.Services {
+		svcObject := mapToService(project, service)
+		if svcObject != nil {
+			objects[fmt.Sprintf("%s-service.yaml", service.Name)] = svcObject
+		} else {
+			log.Println("Missing port mapping from service config.")
+		}
+
+		if service.Deploy != nil && service.Deploy.Mode == "global" {
+			daemonset, err := mapToDaemonset(project, service, project.Name)
+			if err != nil {
+				return nil, err
+			}
+			objects[fmt.Sprintf("%s-daemonset.yaml", service.Name)] = daemonset
+		} else {
+			deployment, err := mapToDeployment(project, service, project.Name)
+			if err != nil {
+				return nil, err
+			}
+			objects[fmt.Sprintf("%s-deployment.yaml", service.Name)] = deployment
+		}
+		for _, vol := range service.Volumes {
+			if vol.Type == "volume" {
+				vol.Source = strings.ReplaceAll(vol.Source, "_", "-")
+				objects[fmt.Sprintf("%s-persistentvolumeclaim.yaml", vol.Source)] = mapToPVC(service, vol)
+			}
+		}
+	}
+	return objects, nil
+}
+
+func mapToService(project *types.Project, service types.ServiceConfig) *core.Service {
+	ports := []core.ServicePort{}
+	for _, p := range service.Ports {
+		ports = append(ports,
+			core.ServicePort{
+				Name:       fmt.Sprintf("%d-%s", p.Target, strings.ToLower(string(p.Protocol))),
+				Port:       int32(p.Target),
+				TargetPort: intstr.FromInt(int(p.Target)),
+				Protocol:   toProtocol(p.Protocol),
+			})
+	}
+	if len(ports) == 0 {
+		return nil
+	}
+	return &core.Service{
+		TypeMeta: meta.TypeMeta{
+			Kind:       "Service",
+			APIVersion: "v1",
+		},
+		ObjectMeta: meta.ObjectMeta{
+			Name: service.Name,
+		},
+		Spec: core.ServiceSpec{
+			Selector: map[string]string{"com.docker.compose.service": service.Name},
+			Ports:    ports,
+			Type:     mapServiceToServiceType(project, service),
+		},
+	}
+}
+
+func mapServiceToServiceType(project *types.Project, service types.ServiceConfig) core.ServiceType {
+	serviceType := core.ServiceTypeClusterIP
+	if len(service.Networks) == 0 {
+		// service is implicitly attached to "default" network
+		serviceType = core.ServiceTypeLoadBalancer
+	}
+	for name := range service.Networks {
+		if !project.Networks[name].Internal {
+			serviceType = core.ServiceTypeLoadBalancer
+		}
+	}
+	for _, port := range service.Ports {
+		if port.Published != 0 {
+			serviceType = core.ServiceTypeNodePort
+		}
+	}
+	return serviceType
+}
+
+func mapToDeployment(project *types.Project, service types.ServiceConfig, name string) (*apps.Deployment, error) {
+	labels := map[string]string{
+		"com.docker.compose.service": service.Name,
+		"com.docker.compose.project": name,
+	}
+	podTemplate, err := toPodTemplate(project, service, labels)
+	if err != nil {
+		return nil, err
+	}
+	selector := new(meta.LabelSelector)
+	selector.MatchLabels = make(map[string]string)
+	for key, val := range labels {
+		selector.MatchLabels[key] = val
+	}
+	return &apps.Deployment{
+		TypeMeta: meta.TypeMeta{
+			Kind:       "Deployment",
+			APIVersion: "apps/v1",
+		},
+		ObjectMeta: meta.ObjectMeta{
+			Name:   service.Name,
+			Labels: labels,
+		},
+		Spec: apps.DeploymentSpec{
+			Selector: selector,
+			Replicas: toReplicas(service.Deploy),
+			Strategy: toDeploymentStrategy(service.Deploy),
+			Template: podTemplate,
+		},
+	}, nil
+}
+
+func mapToDaemonset(project *types.Project, service types.ServiceConfig, name string) (*apps.DaemonSet, error) {
+	labels := map[string]string{
+		"com.docker.compose.service": service.Name,
+		"com.docker.compose.project": name,
+	}
+	podTemplate, err := toPodTemplate(project, service, labels)
+	if err != nil {
+		return nil, err
+	}
+
+	return &apps.DaemonSet{
+		ObjectMeta: meta.ObjectMeta{
+			Name:   service.Name,
+			Labels: labels,
+		},
+		Spec: apps.DaemonSetSpec{
+			Template: podTemplate,
+		},
+	}, nil
+}
+
+func toReplicas(deploy *types.DeployConfig) *int32 {
+	v := int32(1)
+	if deploy != nil {
+		v = int32(*deploy.Replicas)
+	}
+	return &v
+}
+
+func toDeploymentStrategy(deploy *types.DeployConfig) apps.DeploymentStrategy {
+	if deploy == nil || deploy.UpdateConfig == nil {
+		return apps.DeploymentStrategy{
+			Type: apps.RecreateDeploymentStrategyType,
+		}
+	}
+	return apps.DeploymentStrategy{
+		Type: apps.RollingUpdateDeploymentStrategyType,
+		RollingUpdate: &apps.RollingUpdateDeployment{
+			MaxUnavailable: &intstr.IntOrString{
+				Type:   intstr.Int,
+				IntVal: int32(*deploy.UpdateConfig.Parallelism),
+			},
+			MaxSurge: nil,
+		},
+	}
+}
+
+func mapToPVC(service types.ServiceConfig, vol types.ServiceVolumeConfig) runtime.Object {
+	rwaccess := core.ReadWriteOnce
+	if vol.ReadOnly {
+		rwaccess = core.ReadOnlyMany
+	}
+	return &core.PersistentVolumeClaim{
+		TypeMeta: meta.TypeMeta{
+			Kind:       "PersistentVolumeClaim",
+			APIVersion: "v1",
+		},
+		ObjectMeta: meta.ObjectMeta{
+			Name:   vol.Source,
+			Labels: map[string]string{"com.docker.compose.service": service.Name},
+		},
+		Spec: core.PersistentVolumeClaimSpec{
+			VolumeName:  vol.Source,
+			AccessModes: []core.PersistentVolumeAccessMode{rwaccess},
+			Resources: core.ResourceRequirements{
+				Requests: core.ResourceList{
+					core.ResourceStorage: resource.MustParse("100Mi"),
+				},
+			},
+		},
+	}
+}
+
+// toSecondsOrDefault converts a duration string in seconds and defaults to a
+// given value if the duration is nil.
+// The supported units are us, ms, s, m and h.
+func toSecondsOrDefault(duration *types.Duration, defaultValue int32) int32 { //nolint: unparam
+	if duration == nil {
+		return defaultValue
+	}
+	return int32(time.Duration(*duration).Seconds())
+}

+ 144 - 0
kube/charts/kubernetes/placement.go

@@ -0,0 +1,144 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package kubernetes
+
+import (
+	"regexp"
+	"strings"
+
+	"github.com/compose-spec/compose-go/types"
+	"github.com/pkg/errors"
+	apiv1 "k8s.io/api/core/v1"
+)
+
+var constraintEquals = regexp.MustCompile(`([\w\.]*)\W*(==|!=)\W*([\w\.]*)`)
+
+const (
+	kubernetesOs       = "beta.kubernetes.io/os"
+	kubernetesArch     = "beta.kubernetes.io/arch"
+	kubernetesHostname = "kubernetes.io/hostname"
+)
+
+// node.id	Node ID	node.id == 2ivku8v2gvtg4
+// node.hostname	Node hostname	node.hostname != node-2
+// node.role	Node role	node.role == manager
+// node.labels	user defined node labels	node.labels.security == high
+// engine.labels	Docker Engine's labels	engine.labels.operatingsystem == ubuntu 14.04
+func toNodeAffinity(deploy *types.DeployConfig) (*apiv1.Affinity, error) {
+	constraints := []string{}
+	if deploy != nil && deploy.Placement.Constraints != nil {
+		constraints = deploy.Placement.Constraints
+	}
+	requirements := []apiv1.NodeSelectorRequirement{}
+	for _, constraint := range constraints {
+		matches := constraintEquals.FindStringSubmatch(constraint)
+		if len(matches) == 4 {
+			key := matches[1]
+			operator, err := toRequirementOperator(matches[2])
+			if err != nil {
+				return nil, err
+			}
+			value := matches[3]
+
+			switch {
+			case key == constraintOs:
+				requirements = append(requirements, apiv1.NodeSelectorRequirement{
+					Key:      kubernetesOs,
+					Operator: operator,
+					Values:   []string{value},
+				})
+			case key == constraintArch:
+				requirements = append(requirements, apiv1.NodeSelectorRequirement{
+					Key:      kubernetesArch,
+					Operator: operator,
+					Values:   []string{value},
+				})
+			case key == constraintHostname:
+				requirements = append(requirements, apiv1.NodeSelectorRequirement{
+					Key:      kubernetesHostname,
+					Operator: operator,
+					Values:   []string{value},
+				})
+			case strings.HasPrefix(key, constraintLabelPrefix):
+				requirements = append(requirements, apiv1.NodeSelectorRequirement{
+					Key:      strings.TrimPrefix(key, constraintLabelPrefix),
+					Operator: operator,
+					Values:   []string{value},
+				})
+			}
+		}
+	}
+
+	if !hasRequirement(requirements, kubernetesOs) {
+		requirements = append(requirements, apiv1.NodeSelectorRequirement{
+			Key:      kubernetesOs,
+			Operator: apiv1.NodeSelectorOpIn,
+			Values:   []string{"linux"},
+		})
+	}
+	if !hasRequirement(requirements, kubernetesArch) {
+		requirements = append(requirements, apiv1.NodeSelectorRequirement{
+			Key:      kubernetesArch,
+			Operator: apiv1.NodeSelectorOpIn,
+			Values:   []string{"amd64"},
+		})
+	}
+	return &apiv1.Affinity{
+		NodeAffinity: &apiv1.NodeAffinity{
+			RequiredDuringSchedulingIgnoredDuringExecution: &apiv1.NodeSelector{
+				NodeSelectorTerms: []apiv1.NodeSelectorTerm{
+					{
+						MatchExpressions: requirements,
+					},
+				},
+			},
+		},
+	}, nil
+}
+
+const (
+	constraintOs          = "node.platform.os"
+	constraintArch        = "node.platform.arch"
+	constraintHostname    = "node.hostname"
+	constraintLabelPrefix = "node.labels."
+)
+
+func hasRequirement(requirements []apiv1.NodeSelectorRequirement, key string) bool {
+	for _, r := range requirements {
+		if r.Key == key {
+			return true
+		}
+	}
+	return false
+}
+
+func toRequirementOperator(sign string) (apiv1.NodeSelectorOperator, error) {
+	switch sign {
+	case "==":
+		return apiv1.NodeSelectorOpIn, nil
+	case "!=":
+		return apiv1.NodeSelectorOpNotIn, nil
+	case ">":
+		return apiv1.NodeSelectorOpGt, nil
+	case "<":
+		return apiv1.NodeSelectorOpLt, nil
+	default:
+		return "", errors.Errorf("operator %s not supported", sign)
+	}
+}

+ 181 - 0
kube/charts/kubernetes/placement_test.go

@@ -0,0 +1,181 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package kubernetes
+
+import (
+	"reflect"
+	"sort"
+	"testing"
+
+	"github.com/compose-spec/compose-go/types"
+
+	"github.com/stretchr/testify/assert"
+	apiv1 "k8s.io/api/core/v1"
+)
+
+func TestToPodWithPlacement(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:
+    image: redis:alpine
+    deploy:
+      placement:
+        constraints:
+          - node.platform.os == linux
+          - node.platform.arch == amd64
+          - node.hostname == node01
+          - node.labels.label1 == value1
+          - node.labels.label2.subpath != value2
+`)
+
+	expectedRequirements := []apiv1.NodeSelectorRequirement{
+		{Key: "beta.kubernetes.io/os", Operator: apiv1.NodeSelectorOpIn, Values: []string{"linux"}},
+		{Key: "beta.kubernetes.io/arch", Operator: apiv1.NodeSelectorOpIn, Values: []string{"amd64"}},
+		{Key: "kubernetes.io/hostname", Operator: apiv1.NodeSelectorOpIn, Values: []string{"node01"}},
+		{Key: "label1", Operator: apiv1.NodeSelectorOpIn, Values: []string{"value1"}},
+		{Key: "label2.subpath", Operator: apiv1.NodeSelectorOpNotIn, Values: []string{"value2"}},
+	}
+
+	requirements := podTemplate.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions
+
+	sort.Slice(expectedRequirements, func(i, j int) bool { return expectedRequirements[i].Key < expectedRequirements[j].Key })
+	sort.Slice(requirements, func(i, j int) bool { return requirements[i].Key < requirements[j].Key })
+
+	assert.EqualValues(t, expectedRequirements, requirements)
+}
+
+type keyValue struct {
+	key   string
+	value string
+}
+
+func kv(key, value string) keyValue {
+	return keyValue{key: key, value: value}
+}
+
+func makeExpectedAffinity(kvs ...keyValue) *apiv1.Affinity {
+
+	var matchExpressions []apiv1.NodeSelectorRequirement
+	for _, kv := range kvs {
+		matchExpressions = append(
+			matchExpressions,
+			apiv1.NodeSelectorRequirement{
+				Key:      kv.key,
+				Operator: apiv1.NodeSelectorOpIn,
+				Values:   []string{kv.value},
+			},
+		)
+	}
+	return &apiv1.Affinity{
+		NodeAffinity: &apiv1.NodeAffinity{
+			RequiredDuringSchedulingIgnoredDuringExecution: &apiv1.NodeSelector{
+				NodeSelectorTerms: []apiv1.NodeSelectorTerm{
+					{
+						MatchExpressions: matchExpressions,
+					},
+				},
+			},
+		},
+	}
+}
+
+func TestNodeAfinity(t *testing.T) {
+	cases := []struct {
+		name     string
+		source   []string
+		expected *apiv1.Affinity
+	}{
+		{
+			name: "nil",
+			expected: makeExpectedAffinity(
+				kv(kubernetesOs, "linux"),
+				kv(kubernetesArch, "amd64"),
+			),
+		},
+		{
+			name:   "hostname",
+			source: []string{"node.hostname == test"},
+			expected: makeExpectedAffinity(
+				kv(kubernetesHostname, "test"),
+				kv(kubernetesOs, "linux"),
+				kv(kubernetesArch, "amd64"),
+			),
+		},
+		{
+			name:   "os",
+			source: []string{"node.platform.os == windows"},
+			expected: makeExpectedAffinity(
+				kv(kubernetesOs, "windows"),
+				kv(kubernetesArch, "amd64"),
+			),
+		},
+		{
+			name:   "arch",
+			source: []string{"node.platform.arch == arm64"},
+			expected: makeExpectedAffinity(
+				kv(kubernetesArch, "arm64"),
+				kv(kubernetesOs, "linux"),
+			),
+		},
+		{
+			name:   "custom-labels",
+			source: []string{"node.platform.os == windows", "node.platform.arch == arm64"},
+			expected: makeExpectedAffinity(
+				kv(kubernetesArch, "arm64"),
+				kv(kubernetesOs, "windows"),
+			),
+		},
+	}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			result, err := toNodeAffinity(&types.DeployConfig{
+				Placement: types.Placement{
+					Constraints: c.source,
+				},
+			})
+			assert.NoError(t, err)
+			assert.True(t, nodeAffinityMatch(c.expected, result))
+		})
+	}
+}
+
+func nodeSelectorRequirementsToMap(source []apiv1.NodeSelectorRequirement, result map[string]apiv1.NodeSelectorRequirement) {
+	for _, t := range source {
+		result[t.Key] = t
+	}
+}
+
+func nodeAffinityMatch(expected, actual *apiv1.Affinity) bool {
+	expectedTerms := expected.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms
+	actualTerms := actual.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms
+	expectedExpressions := make(map[string]apiv1.NodeSelectorRequirement)
+	expectedFields := make(map[string]apiv1.NodeSelectorRequirement)
+	actualExpressions := make(map[string]apiv1.NodeSelectorRequirement)
+	actualFields := make(map[string]apiv1.NodeSelectorRequirement)
+	for _, v := range expectedTerms {
+		nodeSelectorRequirementsToMap(v.MatchExpressions, expectedExpressions)
+		nodeSelectorRequirementsToMap(v.MatchFields, expectedFields)
+	}
+	for _, v := range actualTerms {
+		nodeSelectorRequirementsToMap(v.MatchExpressions, actualExpressions)
+		nodeSelectorRequirementsToMap(v.MatchFields, actualFields)
+	}
+	return reflect.DeepEqual(expectedExpressions, actualExpressions) && reflect.DeepEqual(expectedFields, actualFields)
+}

+ 367 - 0
kube/charts/kubernetes/pod.go

@@ -0,0 +1,367 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package kubernetes
+
+import (
+	"fmt"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/docker/api/types/swarm"
+
+	"github.com/pkg/errors"
+	apiv1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func toPodTemplate(project *types.Project, serviceConfig types.ServiceConfig, labels map[string]string) (apiv1.PodTemplateSpec, error) {
+	tpl := apiv1.PodTemplateSpec{}
+	//nodeAffinity, err := toNodeAffinity(serviceConfig.Deploy)
+	//if err != nil {
+	//	return apiv1.PodTemplateSpec{}, err
+	//}
+	hostAliases, err := toHostAliases(serviceConfig.ExtraHosts)
+	if err != nil {
+		return apiv1.PodTemplateSpec{}, err
+	}
+	env, err := toEnv(serviceConfig.Environment)
+	if err != nil {
+		return apiv1.PodTemplateSpec{}, err
+	}
+	restartPolicy, err := toRestartPolicy(serviceConfig)
+	if err != nil {
+		return apiv1.PodTemplateSpec{}, err
+	}
+
+	var limits apiv1.ResourceList
+	if serviceConfig.Deploy != nil && serviceConfig.Deploy.Resources.Limits != nil {
+		limits, err = toResource(serviceConfig.Deploy.Resources.Limits)
+		if err != nil {
+			return apiv1.PodTemplateSpec{}, err
+		}
+	}
+	var requests apiv1.ResourceList
+	if serviceConfig.Deploy != nil && serviceConfig.Deploy.Resources.Reservations != nil {
+		requests, err = toResource(serviceConfig.Deploy.Resources.Reservations)
+		if err != nil {
+			return apiv1.PodTemplateSpec{}, err
+		}
+	}
+
+	volumes, err := toVolumes(project, serviceConfig)
+	if err != nil {
+		return apiv1.PodTemplateSpec{}, err
+	}
+	volumeMounts, err := toVolumeMounts(project, serviceConfig)
+	if err != nil {
+		return apiv1.PodTemplateSpec{}, err
+	}
+	/*	pullPolicy, err := toImagePullPolicy(serviceConfig.Image, x-kubernetes-pull-policy)
+		if err != nil {
+			return apiv1.PodTemplateSpec{}, err
+		} */
+	tpl.ObjectMeta = metav1.ObjectMeta{
+		Labels:      labels,
+		Annotations: serviceConfig.Labels,
+	}
+	tpl.Spec.RestartPolicy = restartPolicy
+	tpl.Spec.Volumes = volumes
+	tpl.Spec.HostPID = toHostPID(serviceConfig.Pid)
+	tpl.Spec.HostIPC = toHostIPC(serviceConfig.Ipc)
+	tpl.Spec.Hostname = serviceConfig.Hostname
+	tpl.Spec.TerminationGracePeriodSeconds = toTerminationGracePeriodSeconds(serviceConfig.StopGracePeriod)
+	tpl.Spec.HostAliases = hostAliases
+	//tpl.Spec.Affinity = nodeAffinity
+	// we dont want to remove all containers and recreate them because:
+	// an admission plugin can add sidecar containers
+	// we for sure want to keep the main container to be additive
+	if len(tpl.Spec.Containers) == 0 {
+		tpl.Spec.Containers = []apiv1.Container{{}}
+	}
+
+	containerIX := 0
+	for ix, c := range tpl.Spec.Containers {
+		if c.Name == serviceConfig.Name {
+			containerIX = ix
+			break
+		}
+	}
+	tpl.Spec.Containers[containerIX].Name = serviceConfig.Name
+	tpl.Spec.Containers[containerIX].Image = serviceConfig.Image
+	// FIXME tpl.Spec.Containers[containerIX].ImagePullPolicy = pullPolicy
+	tpl.Spec.Containers[containerIX].Command = serviceConfig.Entrypoint
+	tpl.Spec.Containers[containerIX].Args = serviceConfig.Command
+	tpl.Spec.Containers[containerIX].WorkingDir = serviceConfig.WorkingDir
+	tpl.Spec.Containers[containerIX].TTY = serviceConfig.Tty
+	tpl.Spec.Containers[containerIX].Stdin = serviceConfig.StdinOpen
+	tpl.Spec.Containers[containerIX].Ports = toPorts(serviceConfig.Ports)
+	tpl.Spec.Containers[containerIX].LivenessProbe = toLivenessProbe(serviceConfig.HealthCheck)
+	tpl.Spec.Containers[containerIX].Env = env
+	tpl.Spec.Containers[containerIX].VolumeMounts = volumeMounts
+	tpl.Spec.Containers[containerIX].SecurityContext = toSecurityContext(serviceConfig)
+	tpl.Spec.Containers[containerIX].Resources = apiv1.ResourceRequirements{
+		Limits:   limits,
+		Requests: requests,
+	}
+
+	/* FIXME
+	if serviceConfig.PullSecret != "" {
+		pullSecrets := map[string]struct{}{}
+		for _, ps := range tpl.Spec.ImagePullSecrets {
+			pullSecrets[ps.Name] = struct{}{}
+		}
+		if _, ok := pullSecrets[serviceConfig.PullSecret]; !ok {
+			tpl.Spec.ImagePullSecrets = append(tpl.Spec.ImagePullSecrets, apiv1.LocalObjectReference{Name: serviceConfig.PullSecret})
+		}
+	}
+	*/
+	return tpl, nil
+}
+
+func toImagePullPolicy(image string, specifiedPolicy string) (apiv1.PullPolicy, error) {
+	if specifiedPolicy == "" {
+		if strings.HasSuffix(image, ":latest") {
+			return apiv1.PullAlways, nil
+		}
+		return apiv1.PullIfNotPresent, nil
+	}
+	switch apiv1.PullPolicy(specifiedPolicy) {
+	case apiv1.PullAlways, apiv1.PullIfNotPresent, apiv1.PullNever:
+		return apiv1.PullPolicy(specifiedPolicy), nil
+	default:
+		return "", errors.Errorf("invalid pull policy %q, must be %q, %q or %q", specifiedPolicy, apiv1.PullAlways, apiv1.PullIfNotPresent, apiv1.PullNever)
+	}
+}
+
+func toHostAliases(extraHosts []string) ([]apiv1.HostAlias, error) {
+	if extraHosts == nil {
+		return nil, nil
+	}
+
+	byHostnames := map[string]string{}
+	for _, host := range extraHosts {
+		split := strings.SplitN(host, ":", 2)
+		if len(split) != 2 {
+			return nil, errors.Errorf("malformed host %s", host)
+		}
+		byHostnames[split[0]] = split[1]
+	}
+
+	byIPs := map[string][]string{}
+	for k, v := range byHostnames {
+		byIPs[v] = append(byIPs[v], k)
+	}
+
+	aliases := make([]apiv1.HostAlias, len(byIPs))
+	i := 0
+	for key, hosts := range byIPs {
+		sort.Strings(hosts)
+		aliases[i] = apiv1.HostAlias{
+			IP:        key,
+			Hostnames: hosts,
+		}
+		i++
+	}
+	sort.Slice(aliases, func(i, j int) bool { return aliases[i].IP < aliases[j].IP })
+	return aliases, nil
+}
+
+func toHostPID(pid string) bool {
+	return "host" == pid
+}
+
+func toHostIPC(ipc string) bool {
+	return "host" == ipc
+}
+
+func toTerminationGracePeriodSeconds(duration *types.Duration) *int64 {
+	if duration == nil {
+		return nil
+	}
+	gracePeriod := int64(time.Duration(*duration).Seconds())
+	return &gracePeriod
+}
+
+func toLivenessProbe(hc *types.HealthCheckConfig) *apiv1.Probe {
+	if hc == nil || len(hc.Test) < 1 || hc.Test[0] == "NONE" {
+		return nil
+	}
+
+	command := hc.Test[1:]
+	if hc.Test[0] == "CMD-SHELL" {
+		command = append([]string{"sh", "-c"}, command...)
+	}
+
+	return &apiv1.Probe{
+		TimeoutSeconds:   toSecondsOrDefault(hc.Timeout, 1),
+		PeriodSeconds:    toSecondsOrDefault(hc.Interval, 1),
+		FailureThreshold: int32(defaultUint64(hc.Retries, 3)),
+		Handler: apiv1.Handler{
+			Exec: &apiv1.ExecAction{
+				Command: command,
+			},
+		},
+	}
+}
+
+func toEnv(env map[string]*string) ([]apiv1.EnvVar, error) {
+	var envVars []apiv1.EnvVar
+
+	for k, v := range env {
+		if v == nil {
+			return nil, errors.Errorf("%s has no value, unsetting an environment variable is not supported", k)
+		}
+		envVars = append(envVars, toEnvVar(k, *v))
+	}
+	sort.Slice(envVars, func(i, j int) bool { return envVars[i].Name < envVars[j].Name })
+	return envVars, nil
+}
+
+func toEnvVar(key, value string) apiv1.EnvVar {
+	return apiv1.EnvVar{
+		Name:  key,
+		Value: value,
+	}
+}
+
+func toPorts(list []types.ServicePortConfig) []apiv1.ContainerPort {
+	var ports []apiv1.ContainerPort
+
+	for _, v := range list {
+		ports = append(ports, apiv1.ContainerPort{
+			ContainerPort: int32(v.Target),
+			Protocol:      toProtocol(v.Protocol),
+		})
+	}
+
+	return ports
+}
+
+func toProtocol(value string) apiv1.Protocol {
+	if value == "udp" {
+		return apiv1.ProtocolUDP
+	}
+	return apiv1.ProtocolTCP
+}
+
+func toRestartPolicy(s types.ServiceConfig) (apiv1.RestartPolicy, error) {
+	if s.Deploy == nil || s.Deploy.RestartPolicy == nil {
+		return apiv1.RestartPolicyAlways, nil
+	}
+	policy := s.Deploy.RestartPolicy
+
+	switch policy.Condition {
+	case string(swarm.RestartPolicyConditionAny):
+		return apiv1.RestartPolicyAlways, nil
+	case string(swarm.RestartPolicyConditionNone):
+		return apiv1.RestartPolicyNever, nil
+	case string(swarm.RestartPolicyConditionOnFailure):
+		return apiv1.RestartPolicyOnFailure, nil
+	default:
+		return "", errors.Errorf("unsupported restart policy %s", policy.Condition)
+	}
+}
+
+func toResource(res *types.Resource) (apiv1.ResourceList, error) {
+	list := make(apiv1.ResourceList)
+	if res.NanoCPUs != "" {
+		cpus, err := resource.ParseQuantity(res.NanoCPUs)
+		if err != nil {
+			return nil, err
+		}
+		list[apiv1.ResourceCPU] = cpus
+	}
+	if res.MemoryBytes != 0 {
+		memory, err := resource.ParseQuantity(fmt.Sprintf("%v", res.MemoryBytes))
+		if err != nil {
+			return nil, err
+		}
+		list[apiv1.ResourceMemory] = memory
+	}
+	return list, nil
+}
+
+func toSecurityContext(s types.ServiceConfig) *apiv1.SecurityContext {
+	isPrivileged := toBoolPointer(s.Privileged)
+	isReadOnly := toBoolPointer(s.ReadOnly)
+
+	var capabilities *apiv1.Capabilities
+	if s.CapAdd != nil || s.CapDrop != nil {
+		capabilities = &apiv1.Capabilities{
+			Add:  toCapabilities(s.CapAdd),
+			Drop: toCapabilities(s.CapDrop),
+		}
+	}
+
+	var userID *int64
+	if s.User != "" {
+		numerical, err := strconv.Atoi(s.User)
+		if err == nil {
+			unixUserID := int64(numerical)
+			userID = &unixUserID
+		}
+	}
+
+	if isPrivileged == nil && isReadOnly == nil && capabilities == nil && userID == nil {
+		return nil
+	}
+
+	return &apiv1.SecurityContext{
+		RunAsUser:              userID,
+		Privileged:             isPrivileged,
+		ReadOnlyRootFilesystem: isReadOnly,
+		Capabilities:           capabilities,
+	}
+}
+
+func toBoolPointer(value bool) *bool {
+	if value {
+		return &value
+	}
+
+	return nil
+}
+
+func defaultUint64(v *uint64, defaultValue uint64) uint64 { //nolint: unparam
+	if v == nil {
+		return defaultValue
+	}
+
+	return *v
+}
+
+func toCapabilities(list []string) (capabilities []apiv1.Capability) {
+	for _, c := range list {
+		capabilities = append(capabilities, apiv1.Capability(c))
+	}
+	return
+}
+
+//nolint: unparam
+func forceRestartPolicy(podTemplate apiv1.PodTemplateSpec, forcedRestartPolicy apiv1.RestartPolicy) apiv1.PodTemplateSpec {
+	if podTemplate.Spec.RestartPolicy != "" {
+		podTemplate.Spec.RestartPolicy = forcedRestartPolicy
+	}
+
+	return podTemplate
+}

+ 1005 - 0
kube/charts/kubernetes/pod_test.go

@@ -0,0 +1,1005 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package kubernetes
+
+import (
+	"fmt"
+	"os"
+	"runtime"
+	"testing"
+
+	"github.com/compose-spec/compose-go/loader"
+	"github.com/compose-spec/compose-go/types"
+	"github.com/stretchr/testify/assert"
+	apiv1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+)
+
+func loadYAML(yaml string) (*loader.Config, error) {
+	dict, err := loader.ParseYAML([]byte(yaml))
+	if err != nil {
+		return nil, err
+	}
+	workingDir, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+	configs := []types.ConfigFiles{}
+	config := types.ConfigDetails{
+		WorkingDir:  workingDir,
+		ConfigFiles: configs,
+		Environment: utils.Environment(),
+	}
+	model, err := loader.Load(config)
+	return model
+}
+
+func podTemplate(t *testing.T, yaml string) apiv1.PodTemplateSpec {
+	res, err := podTemplateWithError(yaml)
+	assert.NoError(t, err)
+	return res
+}
+
+func podTemplateWithError(yaml string) (apiv1.PodTemplateSpec, error) {
+	model, err := loadYAML(yaml)
+	if err != nil {
+		return apiv1.PodTemplateSpec{}, err
+	}
+
+	return toPodTemplate(model.Services[0], nil, model)
+}
+
+func TestToPodWithDockerSocket(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		t.Skip("on windows, source path validation is broken (and actually, source validation for windows workload is broken too). Skip it for now, as we don't support it yet")
+		return
+	}
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:
+    image: "redis:alpine"
+    volumes:
+      - "/var/run/docker.sock:/var/run/docker.sock"
+`)
+
+	expectedVolume := apiv1.Volume{
+		Name: "mount-0",
+		VolumeSource: apiv1.VolumeSource{
+			HostPath: &apiv1.HostPathVolumeSource{
+				Path: "/var/run",
+			},
+		},
+	}
+
+	expectedMount := apiv1.VolumeMount{
+		Name:      "mount-0",
+		MountPath: "/var/run/docker.sock",
+		SubPath:   "docker.sock",
+	}
+
+	assert.Len(t, podTemplate.Spec.Volumes, 1)
+	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
+	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
+	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
+}
+
+func TestToPodWithFunkyCommand(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:
+    image: basi/node-exporter
+    command: ["-collector.procfs", "/host/proc", "-collector.sysfs", "/host/sys"]
+`)
+
+	expectedArgs := []string{
+		`-collector.procfs`,
+		`/host/proc`, // ?
+		`-collector.sysfs`,
+		`/host/sys`, // ?
+	}
+	assert.Equal(t, expectedArgs, podTemplate.Spec.Containers[0].Args)
+}
+
+func TestToPodWithGlobalVolume(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  db:
+    image: "postgres:9.4"
+    volumes:
+      - dbdata:/var/lib/postgresql/data
+`)
+
+	expectedMount := apiv1.VolumeMount{
+		Name:      "dbdata",
+		MountPath: "/var/lib/postgresql/data",
+	}
+	assert.Len(t, podTemplate.Spec.Volumes, 0)
+	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
+	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
+}
+
+func TestToPodWithResources(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  db:
+    image: "postgres:9.4"
+    deploy:
+      resources:
+        limits:
+          cpus: "0.001"
+          memory: 50Mb
+        reservations:
+          cpus: "0.0001"
+          memory: 20Mb
+`)
+
+	expectedResourceRequirements := apiv1.ResourceRequirements{
+		Limits: map[apiv1.ResourceName]resource.Quantity{
+			apiv1.ResourceCPU:    resource.MustParse("0.001"),
+			apiv1.ResourceMemory: resource.MustParse(fmt.Sprintf("%d", 50*1024*1024)),
+		},
+		Requests: map[apiv1.ResourceName]resource.Quantity{
+			apiv1.ResourceCPU:    resource.MustParse("0.0001"),
+			apiv1.ResourceMemory: resource.MustParse(fmt.Sprintf("%d", 20*1024*1024)),
+		},
+	}
+	assert.Equal(t, expectedResourceRequirements, podTemplate.Spec.Containers[0].Resources)
+}
+
+func TestToPodWithCapabilities(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:
+    image: "redis:alpine"
+    cap_add: 
+      - ALL
+    cap_drop: 
+      - NET_ADMIN
+      - SYS_ADMIN
+`)
+
+	expectedSecurityContext := &apiv1.SecurityContext{
+		Capabilities: &apiv1.Capabilities{
+			Add:  []apiv1.Capability{"ALL"},
+			Drop: []apiv1.Capability{"NET_ADMIN", "SYS_ADMIN"},
+		},
+	}
+
+	assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext)
+}
+
+func TestToPodWithReadOnly(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:	
+    image: "redis:alpine"
+    read_only: true
+`)
+
+	yes := true
+	expectedSecurityContext := &apiv1.SecurityContext{
+		ReadOnlyRootFilesystem: &yes,
+	}
+	assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext)
+}
+
+func TestToPodWithPrivileged(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:	
+    image: "redis:alpine"
+    privileged: true
+`)
+
+	yes := true
+	expectedSecurityContext := &apiv1.SecurityContext{
+		Privileged: &yes,
+	}
+	assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext)
+}
+
+func TestToPodWithEnvNilShouldErrorOut(t *testing.T) {
+	_, err := podTemplateWithError(`
+version: "3"
+services:
+  redis:	
+    image: "redis:alpine"
+    environment: 
+      - SESSION_SECRET
+`)
+	assert.Error(t, err)
+}
+
+func TestToPodWithEnv(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:
+    image: "redis:alpine"
+    environment: 
+      - RACK_ENV=development
+      - SHOW=true
+`)
+
+	expectedEnv := []apiv1.EnvVar{
+		{
+			Name:  "RACK_ENV",
+			Value: "development",
+		},
+		{
+			Name:  "SHOW",
+			Value: "true",
+		},
+	}
+
+	assert.Equal(t, expectedEnv, podTemplate.Spec.Containers[0].Env)
+}
+
+func TestToPodWithVolume(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		t.Skip("on windows, source path validation is broken (and actually, source validation for windows workload is broken too). Skip it for now, as we don't support it yet")
+		return
+	}
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  nginx:
+    image: nginx
+    volumes: 
+      - /ignore:/ignore
+      - /opt/data:/var/lib/mysql:ro
+`)
+
+	assert.Len(t, podTemplate.Spec.Volumes, 2)
+	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 2)
+}
+
+func /*FIXME Test*/ ToPodWithRelativeVolumes(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		t.Skip("on windows, source path validation is broken (and actually, source validation for windows workload is broken too). Skip it for now, as we don't support it yet")
+		return
+	}
+	_, err := podTemplateWithError(`
+version: "3"
+services:
+  nginx:  
+    image: nginx
+    volumes: 
+      - ./fail:/ignore
+`)
+
+	assert.Error(t, err)
+}
+
+func TestToPodWithHealthCheck(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  nginx:
+    image: nginx
+    healthcheck: 
+      test: ["CMD", "curl", "-f", "http://localhost"]
+      interval: 90s
+      timeout: 10s
+      retries: 3
+`)
+
+	expectedLivenessProbe := &apiv1.Probe{
+		TimeoutSeconds:   10,
+		PeriodSeconds:    90,
+		FailureThreshold: 3,
+		Handler: apiv1.Handler{
+			Exec: &apiv1.ExecAction{
+				Command: []string{"curl", "-f", "http://localhost"},
+			},
+		},
+	}
+
+	assert.Equal(t, expectedLivenessProbe, podTemplate.Spec.Containers[0].LivenessProbe)
+}
+
+func TestToPodWithShellHealthCheck(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  nginx:
+    image: nginx
+    healthcheck: 
+      test: ["CMD-SHELL", "curl -f http://localhost"]
+`)
+
+	expectedLivenessProbe := &apiv1.Probe{
+		TimeoutSeconds:   1,
+		PeriodSeconds:    1,
+		FailureThreshold: 3,
+		Handler: apiv1.Handler{
+			Exec: &apiv1.ExecAction{
+				Command: []string{"sh", "-c", "curl -f http://localhost"},
+			},
+		},
+	}
+
+	assert.Equal(t, expectedLivenessProbe, podTemplate.Spec.Containers[0].LivenessProbe)
+}
+
+func TestToPodWithTargetlessExternalSecret(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  nginx:
+    image: nginx
+    secrets: 
+      - my_secret 
+`)
+
+	expectedVolume := apiv1.Volume{
+		Name: "secret-0",
+		VolumeSource: apiv1.VolumeSource{
+			Secret: &apiv1.SecretVolumeSource{
+				SecretName: "my_secret",
+				Items: []apiv1.KeyToPath{
+					{
+						Key:  "file", // TODO: This is the key we assume external secrets use
+						Path: "secret-0",
+					},
+				},
+			},
+		},
+	}
+
+	expectedMount := apiv1.VolumeMount{
+		Name:      "secret-0",
+		ReadOnly:  true,
+		MountPath: "/run/secrets/my_secret",
+		SubPath:   "secret-0",
+	}
+
+	assert.Len(t, podTemplate.Spec.Volumes, 1)
+	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
+	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
+	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
+}
+
+func TestToPodWithExternalSecret(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  nginx:
+    image: nginx
+    secrets:
+      - source: my_secret
+        target: nginx_secret
+`)
+
+	expectedVolume := apiv1.Volume{
+		Name: "secret-0",
+		VolumeSource: apiv1.VolumeSource{
+			Secret: &apiv1.SecretVolumeSource{
+				SecretName: "my_secret",
+				Items: []apiv1.KeyToPath{
+					{
+						Key:  "file", // TODO: This is the key we assume external secrets use
+						Path: "secret-0",
+					},
+				},
+			},
+		},
+	}
+
+	expectedMount := apiv1.VolumeMount{
+		Name:      "secret-0",
+		ReadOnly:  true,
+		MountPath: "/run/secrets/nginx_secret",
+		SubPath:   "secret-0",
+	}
+
+	assert.Len(t, podTemplate.Spec.Volumes, 1)
+	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
+	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
+	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
+}
+
+func /*FIXME Test*/ ToPodWithFileBasedSecret(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  nginx:
+    image: nginx
+    secrets:
+      - source: my_secret
+secrets:
+  my_secret:
+    file: ./secret.txt
+`)
+
+	expectedVolume := apiv1.Volume{
+		Name: "secret-0",
+		VolumeSource: apiv1.VolumeSource{
+			Secret: &apiv1.SecretVolumeSource{
+				SecretName: "my_secret",
+				Items: []apiv1.KeyToPath{
+					{
+						Key:  "secret.txt",
+						Path: "secret-0",
+					},
+				},
+			},
+		},
+	}
+
+	expectedMount := apiv1.VolumeMount{
+		Name:      "secret-0",
+		ReadOnly:  true,
+		MountPath: "/run/secrets/my_secret",
+		SubPath:   "secret-0",
+	}
+
+	assert.Len(t, podTemplate.Spec.Volumes, 1)
+	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
+	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
+	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
+}
+
+func /*FIXME Test*/ ToPodWithTwoFileBasedSecrets(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  nginx:
+    image: nginx
+    secrets:
+      - source: my_secret1
+      - source: my_secret2
+        target: secret2
+secrets:
+  my_secret1:
+    file: ./secret1.txt
+  my_secret2:
+    file: ./secret2.txt
+`)
+
+	expectedVolumes := []apiv1.Volume{
+		{
+			Name: "secret-0",
+			VolumeSource: apiv1.VolumeSource{
+				Secret: &apiv1.SecretVolumeSource{
+					SecretName: "my_secret1",
+					Items: []apiv1.KeyToPath{
+						{
+							Key:  "secret1.txt",
+							Path: "secret-0",
+						},
+					},
+				},
+			},
+		},
+		{
+			Name: "secret-1",
+			VolumeSource: apiv1.VolumeSource{
+				Secret: &apiv1.SecretVolumeSource{
+					SecretName: "my_secret2",
+					Items: []apiv1.KeyToPath{
+						{
+							Key:  "secret2.txt",
+							Path: "secret-1",
+						},
+					},
+				},
+			},
+		},
+	}
+
+	expectedMounts := []apiv1.VolumeMount{
+		{
+			Name:      "secret-0",
+			ReadOnly:  true,
+			MountPath: "/run/secrets/my_secret1",
+			SubPath:   "secret-0",
+		},
+		{
+			Name:      "secret-1",
+			ReadOnly:  true,
+			MountPath: "/run/secrets/secret2",
+			SubPath:   "secret-1",
+		},
+	}
+
+	assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes)
+	assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts)
+}
+
+func TestToPodWithTerminationGracePeriod(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:
+    image: "redis:alpine"
+    stop_grace_period: 100s
+`)
+
+	expected := int64(100)
+	assert.Equal(t, &expected, podTemplate.Spec.TerminationGracePeriodSeconds)
+}
+
+func TestToPodWithTmpfs(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:
+    image: "redis:alpine"
+    tmpfs: 
+      - /tmp
+`)
+
+	expectedVolume := apiv1.Volume{
+		Name: "tmp-0",
+		VolumeSource: apiv1.VolumeSource{
+			EmptyDir: &apiv1.EmptyDirVolumeSource{
+				Medium: "Memory",
+			},
+		},
+	}
+
+	expectedMount := apiv1.VolumeMount{
+		Name:      "tmp-0",
+		MountPath: "/tmp",
+	}
+
+	assert.Len(t, podTemplate.Spec.Volumes, 1)
+	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
+	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
+	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
+}
+
+func TestToPodWithNumericalUser(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:
+    image: "redis:alpine"
+    user: "1000"
+`)
+
+	userID := int64(1000)
+
+	expectedSecurityContext := &apiv1.SecurityContext{
+		RunAsUser: &userID,
+	}
+
+	assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext)
+}
+
+func TestToPodWithGitVolume(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:
+    image: "redis:alpine"
+    volumes: 
+      - source: "[email protected]:moby/moby.git"
+        target: /sources
+        type: git
+`)
+
+	expectedVolume := apiv1.Volume{
+		Name: "mount-0",
+		VolumeSource: apiv1.VolumeSource{
+			GitRepo: &apiv1.GitRepoVolumeSource{
+				Repository: "[email protected]:moby/moby.git",
+			},
+		},
+	}
+
+	expectedMount := apiv1.VolumeMount{
+		Name:      "mount-0",
+		ReadOnly:  false,
+		MountPath: "/sources",
+	}
+
+	assert.Len(t, podTemplate.Spec.Volumes, 1)
+	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
+	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
+	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
+}
+
+func /*FIXME Test*/ ToPodWithFileBasedConfig(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+ redis:
+    image: "redis:alpine"
+    configs: 
+      - source: my_config
+        target: /usr/share/nginx/html/index.html
+        uid: "103"
+        gid: "103"
+        mode: 0440
+configs:
+  my_config:
+    file: ./file.html
+`)
+
+	mode := int32(0440)
+
+	expectedVolume := apiv1.Volume{
+		Name: "config-0",
+		VolumeSource: apiv1.VolumeSource{
+			ConfigMap: &apiv1.ConfigMapVolumeSource{
+				LocalObjectReference: apiv1.LocalObjectReference{
+					Name: "my_config",
+				},
+				Items: []apiv1.KeyToPath{
+					{
+						Key:  "file.html",
+						Path: "config-0",
+						Mode: &mode,
+					},
+				},
+			},
+		},
+	}
+
+	expectedMount := apiv1.VolumeMount{
+		Name:      "config-0",
+		ReadOnly:  true,
+		MountPath: "/usr/share/nginx/html/index.html",
+		SubPath:   "config-0",
+	}
+
+	assert.Len(t, podTemplate.Spec.Volumes, 1)
+	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
+	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
+	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
+}
+
+func /*FIXME Test*/ ToPodWithTargetlessFileBasedConfig(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:
+    image: "redis:alpine"
+    configs: 
+      - my_config
+configs:
+  my_config:
+    file: ./file.html
+`)
+
+	expectedVolume := apiv1.Volume{
+		Name: "config-0",
+		VolumeSource: apiv1.VolumeSource{
+			ConfigMap: &apiv1.ConfigMapVolumeSource{
+				LocalObjectReference: apiv1.LocalObjectReference{
+					Name: "myconfig",
+				},
+				Items: []apiv1.KeyToPath{
+					{
+						Key:  "file.html",
+						Path: "config-0",
+					},
+				},
+			},
+		},
+	}
+
+	expectedMount := apiv1.VolumeMount{
+		Name:      "config-0",
+		ReadOnly:  true,
+		MountPath: "/myconfig",
+		SubPath:   "config-0",
+	}
+
+	assert.Len(t, podTemplate.Spec.Volumes, 1)
+	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
+	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
+	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
+}
+
+func TestToPodWithExternalConfig(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  redis:
+    image: "redis:alpine"
+    configs: 
+      - source: my_config
+        target: /usr/share/nginx/html/index.html
+        uid: "103"
+        gid: "103"
+        mode: 0440
+configs:
+  my_config:
+    external: true
+`)
+
+	mode := int32(0440)
+
+	expectedVolume := apiv1.Volume{
+		Name: "config-0",
+		VolumeSource: apiv1.VolumeSource{
+			ConfigMap: &apiv1.ConfigMapVolumeSource{
+				LocalObjectReference: apiv1.LocalObjectReference{
+					Name: "my_config",
+				},
+				Items: []apiv1.KeyToPath{
+					{
+						Key:  "file", // TODO: This is the key we assume external config use
+						Path: "config-0",
+						Mode: &mode,
+					},
+				},
+			},
+		},
+	}
+
+	expectedMount := apiv1.VolumeMount{
+		Name:      "config-0",
+		ReadOnly:  true,
+		MountPath: "/usr/share/nginx/html/index.html",
+		SubPath:   "config-0",
+	}
+
+	assert.Len(t, podTemplate.Spec.Volumes, 1)
+	assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1)
+	assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0])
+	assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0])
+}
+
+func /*FIXME Test*/ ToPodWithTwoConfigsSameMountPoint(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  nginx:
+    image: nginx
+    configs: 
+      - source: first
+        target: /data/first.json
+        mode: "0440"
+      - source: second
+        target: /data/second.json
+        mode: "0550"
+configs:
+  first:
+    file: ./file1
+  secondv:
+    file: ./file2
+`)
+
+	mode0440 := int32(0440)
+	mode0550 := int32(0550)
+
+	expectedVolumes := []apiv1.Volume{
+		{
+			Name: "config-0",
+			VolumeSource: apiv1.VolumeSource{
+				ConfigMap: &apiv1.ConfigMapVolumeSource{
+					LocalObjectReference: apiv1.LocalObjectReference{
+						Name: "first",
+					},
+					Items: []apiv1.KeyToPath{
+						{
+							Key:  "file1",
+							Path: "config-0",
+							Mode: &mode0440,
+						},
+					},
+				},
+			},
+		},
+		{
+			Name: "config-1",
+			VolumeSource: apiv1.VolumeSource{
+				ConfigMap: &apiv1.ConfigMapVolumeSource{
+					LocalObjectReference: apiv1.LocalObjectReference{
+						Name: "second",
+					},
+					Items: []apiv1.KeyToPath{
+						{
+							Key:  "file2",
+							Path: "config-1",
+							Mode: &mode0550,
+						},
+					},
+				},
+			},
+		},
+	}
+
+	expectedMounts := []apiv1.VolumeMount{
+		{
+			Name:      "config-0",
+			ReadOnly:  true,
+			MountPath: "/data/first.json",
+			SubPath:   "config-0",
+		},
+		{
+			Name:      "config-1",
+			ReadOnly:  true,
+			MountPath: "/data/second.json",
+			SubPath:   "config-1",
+		},
+	}
+
+	assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes)
+	assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts)
+}
+
+func TestToPodWithTwoExternalConfigsSameMountPoint(t *testing.T) {
+	podTemplate := podTemplate(t, `
+version: "3"
+services:
+  nginx:
+    image: nginx
+    configs: 
+      - source: first
+        target: /data/first.json
+      - source: second
+        target: /data/second.json
+configs:
+  first:
+    file: ./file1
+  second:
+    file: ./file2
+`)
+
+	expectedVolumes := []apiv1.Volume{
+		{
+			Name: "config-0",
+			VolumeSource: apiv1.VolumeSource{
+				ConfigMap: &apiv1.ConfigMapVolumeSource{
+					LocalObjectReference: apiv1.LocalObjectReference{
+						Name: "first",
+					},
+					Items: []apiv1.KeyToPath{
+						{
+							Key:  "file",
+							Path: "config-0",
+						},
+					},
+				},
+			},
+		},
+		{
+			Name: "config-1",
+			VolumeSource: apiv1.VolumeSource{
+				ConfigMap: &apiv1.ConfigMapVolumeSource{
+					LocalObjectReference: apiv1.LocalObjectReference{
+						Name: "second",
+					},
+					Items: []apiv1.KeyToPath{
+						{
+							Key:  "file",
+							Path: "config-1",
+						},
+					},
+				},
+			},
+		},
+	}
+
+	expectedMounts := []apiv1.VolumeMount{
+		{
+			Name:      "config-0",
+			ReadOnly:  true,
+			MountPath: "/data/first.json",
+			SubPath:   "config-0",
+		},
+		{
+			Name:      "config-1",
+			ReadOnly:  true,
+			MountPath: "/data/second.json",
+			SubPath:   "config-1",
+		},
+	}
+
+	assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes)
+	assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts)
+}
+
+func /*FIXME Test*/ ToPodWithPullSecret(t *testing.T) {
+	podTemplateWithSecret := podTemplate(t, `
+version: "3"
+services:
+  nginx:
+    image: nginx
+    x-kubernetes.pull-secret: test-pull-secret
+`)
+
+	assert.Equal(t, 1, len(podTemplateWithSecret.Spec.ImagePullSecrets))
+	assert.Equal(t, "test-pull-secret", podTemplateWithSecret.Spec.ImagePullSecrets[0].Name)
+
+	podTemplateNoSecret := podTemplate(t, `
+version: "3"
+services:
+  nginx:
+    image: nginx
+`)
+
+	assert.Nil(t, podTemplateNoSecret.Spec.ImagePullSecrets)
+}
+
+func /*FIXME Test*/ ToPodWithPullPolicy(t *testing.T) {
+	cases := []struct {
+		name           string
+		stack          string
+		expectedPolicy apiv1.PullPolicy
+		expectedError  string
+	}{
+		{
+			name: "specific tag",
+			stack: `
+version: "3"
+services:
+  nginx:
+    image: nginx:specific
+`,
+			expectedPolicy: apiv1.PullIfNotPresent,
+		},
+		{
+			name: "latest tag",
+			stack: `
+version: "3"
+services:
+  nginx:
+    image: nginx:latest
+`,
+			expectedPolicy: apiv1.PullAlways,
+		},
+		{
+			name: "explicit policy",
+			stack: `
+version: "3"
+services:
+  nginx:
+    image: nginx:specific
+    x-kubernetes.pull-policy: Never
+`,
+			expectedPolicy: apiv1.PullNever,
+		},
+		{
+			name: "invalid policy",
+			stack: `
+version: "3"
+services:
+  nginx:
+    image: nginx:specific
+    x-kubernetes.pull-policy: Invalid
+`,
+			expectedError: `invalid pull policy "Invalid", must be "Always", "IfNotPresent" or "Never"`,
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			pod, err := podTemplateWithError(c.stack)
+			if c.expectedError != "" {
+				assert.EqualError(t, err, c.expectedError)
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, pod.Spec.Containers[0].ImagePullPolicy, c.expectedPolicy)
+			}
+		})
+	}
+}

+ 246 - 0
kube/charts/kubernetes/volumes.go

@@ -0,0 +1,246 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package kubernetes
+
+import (
+	"fmt"
+	"path"
+	"path/filepath"
+	"strings"
+
+	"github.com/compose-spec/compose-go/types"
+
+	"github.com/pkg/errors"
+	apiv1 "k8s.io/api/core/v1"
+)
+
+const dockerSock = "/var/run/docker.sock"
+
+type volumeSpec struct {
+	mount  apiv1.VolumeMount
+	source *apiv1.VolumeSource
+}
+
+func hasPersistentVolumes(s types.ServiceConfig) bool {
+	for _, volume := range s.Volumes {
+		if volume.Type == "volume" {
+			return true
+		}
+	}
+
+	return false
+}
+
+func toVolumeSpecs(project *types.Project, s types.ServiceConfig) ([]volumeSpec, error) {
+	var specs []volumeSpec
+	for i, m := range s.Volumes {
+		var source *apiv1.VolumeSource
+		name := fmt.Sprintf("mount-%d", i)
+		subpath := ""
+		if m.Source == dockerSock && m.Target == dockerSock {
+			subpath = "docker.sock"
+			source = hostPathVolume("/var/run")
+		} else if strings.HasSuffix(m.Source, ".git") {
+			source = gitVolume(m.Source)
+		} else if m.Type == "volume" {
+			if m.Source != "" {
+				name = strings.ReplaceAll(m.Source, "_", "-")
+			}
+		} else {
+			// bind mount
+			if !filepath.IsAbs(m.Source) {
+				return nil, errors.Errorf("%s: only absolute paths can be specified in mount source", m.Source)
+			}
+			if m.Source == "/" {
+				source = hostPathVolume("/")
+			} else {
+				parent, file := filepath.Split(m.Source)
+				if parent != "/" {
+					parent = strings.TrimSuffix(parent, "/")
+				}
+				source = hostPathVolume(parent)
+				subpath = file
+			}
+		}
+
+		specs = append(specs, volumeSpec{
+			source: source,
+			mount:  volumeMount(name, m.Target, m.ReadOnly, subpath),
+		})
+	}
+
+	for i, m := range s.Tmpfs {
+		name := fmt.Sprintf("tmp-%d", i)
+
+		specs = append(specs, volumeSpec{
+			source: emptyVolumeInMemory(),
+			mount:  volumeMount(name, m, false, ""),
+		})
+	}
+
+	for i, s := range s.Secrets {
+		name := fmt.Sprintf("secret-%d", i)
+
+		target := path.Join("/run/secrets", or(s.Target, s.Source))
+		subPath := name
+		readOnly := true
+
+		specs = append(specs, volumeSpec{
+			source: secretVolume(s, project.Secrets[name], subPath),
+			mount:  volumeMount(name, target, readOnly, subPath),
+		})
+	}
+
+	for i, c := range s.Configs {
+		name := fmt.Sprintf("config-%d", i)
+
+		target := or(c.Target, "/"+c.Source)
+		subPath := name
+		readOnly := true
+
+		specs = append(specs, volumeSpec{
+			source: configVolume(c, project.Configs[name], subPath),
+			mount:  volumeMount(name, target, readOnly, subPath),
+		})
+	}
+
+	return specs, nil
+}
+
+func or(v string, defaultValue string) string {
+	if v != "" && v != "." {
+		return v
+	}
+
+	return defaultValue
+}
+
+func toVolumeMounts(project *types.Project, s types.ServiceConfig) ([]apiv1.VolumeMount, error) {
+	var mounts []apiv1.VolumeMount
+	specs, err := toVolumeSpecs(project, s)
+	if err != nil {
+		return nil, err
+	}
+	for _, spec := range specs {
+		mounts = append(mounts, spec.mount)
+	}
+	return mounts, nil
+}
+
+func toVolumes(project *types.Project, s types.ServiceConfig) ([]apiv1.Volume, error) {
+	var volumes []apiv1.Volume
+	specs, err := toVolumeSpecs(project, s)
+	if err != nil {
+		return nil, err
+	}
+	for _, spec := range specs {
+		if spec.source == nil {
+			spec.source = emptyVolumeInMemory()
+		}
+		volumes = append(volumes, apiv1.Volume{
+			Name:         spec.mount.Name,
+			VolumeSource: *spec.source,
+		})
+	}
+	return volumes, nil
+}
+
+func gitVolume(path string) *apiv1.VolumeSource {
+	return &apiv1.VolumeSource{
+		GitRepo: &apiv1.GitRepoVolumeSource{
+			Repository: filepath.ToSlash(path),
+		},
+	}
+}
+
+func hostPathVolume(path string) *apiv1.VolumeSource {
+	return &apiv1.VolumeSource{
+		HostPath: &apiv1.HostPathVolumeSource{
+			Path: path,
+		},
+	}
+}
+
+func defaultMode(mode *uint32) *int32 {
+	var defaultMode *int32
+
+	if mode != nil {
+		signedMode := int32(*mode)
+		defaultMode = &signedMode
+	}
+
+	return defaultMode
+}
+
+func secretVolume(config types.ServiceSecretConfig, topLevelConfig types.SecretConfig, subPath string) *apiv1.VolumeSource {
+	return &apiv1.VolumeSource{
+		Secret: &apiv1.SecretVolumeSource{
+			SecretName: config.Source,
+			Items: []apiv1.KeyToPath{
+				{
+					Key:  toKey(topLevelConfig.File),
+					Path: subPath,
+					Mode: defaultMode(config.Mode),
+				},
+			},
+		},
+	}
+}
+
+func volumeMount(name, path string, readOnly bool, subPath string) apiv1.VolumeMount {
+	return apiv1.VolumeMount{
+		Name:      name,
+		MountPath: path,
+		ReadOnly:  readOnly,
+		SubPath:   subPath,
+	}
+}
+
+func configVolume(config types.ServiceConfigObjConfig, topLevelConfig types.ConfigObjConfig, subPath string) *apiv1.VolumeSource {
+	return &apiv1.VolumeSource{
+		ConfigMap: &apiv1.ConfigMapVolumeSource{
+			LocalObjectReference: apiv1.LocalObjectReference{
+				Name: config.Source,
+			},
+			Items: []apiv1.KeyToPath{
+				{
+					Key:  toKey(topLevelConfig.File),
+					Path: subPath,
+					Mode: defaultMode(config.Mode),
+				},
+			},
+		},
+	}
+}
+
+func toKey(file string) string {
+	if file != "" {
+		return path.Base(file)
+	}
+
+	return "file" // TODO: hard-coded key for external configs
+}
+
+func emptyVolumeInMemory() *apiv1.VolumeSource {
+	return &apiv1.VolumeSource{
+		EmptyDir: &apiv1.EmptyDirVolumeSource{
+			Medium: apiv1.StorageMediumMemory,
+		},
+	}
+}

+ 106 - 0
kube/compose.go

@@ -0,0 +1,106 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package kube
+
+import (
+	"context"
+
+	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/compose-cli/api/compose"
+	"github.com/docker/compose-cli/api/context/store"
+	"github.com/docker/compose-cli/api/errdefs"
+	"github.com/docker/compose-cli/kube/charts"
+)
+
+// NewComposeService create a kubernetes implementation of the compose.Service API
+func NewComposeService(ctx store.KubeContext) (compose.Service, error) {
+	apiclient, err := charts.NewSDK(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return &composeService{
+		ctx: ctx,
+		sdk: apiclient,
+	}, nil
+}
+
+type composeService struct {
+	ctx store.KubeContext
+	sdk charts.API
+}
+
+// Up executes the equivalent to a `compose up`
+func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
+	return s.sdk.Install(project)
+}
+
+// Down executes the equivalent to a `compose down`
+func (s *composeService) Down(ctx context.Context, projectName string, options compose.DownOptions) error {
+	return s.sdk.Uninstall(projectName)
+}
+
+// List executes the equivalent to a `docker stack ls`
+func (s *composeService) List(ctx context.Context, projectName string) ([]compose.Stack, error) {
+	return s.sdk.List(projectName)
+}
+
+// Build executes the equivalent to a `compose build`
+func (s *composeService) Build(ctx context.Context, project *types.Project) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Push executes the equivalent ot a `compose push`
+func (s *composeService) Push(ctx context.Context, project *types.Project) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Pull executes the equivalent of a `compose pull`
+func (s *composeService) Pull(ctx context.Context, project *types.Project) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Create executes the equivalent to a `compose create`
+func (s *composeService) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Start executes the equivalent to a `compose start`
+func (s *composeService) Start(ctx context.Context, project *types.Project, consumer compose.LogConsumer) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Logs executes the equivalent to a `compose logs`
+func (s *composeService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer, options compose.LogOptions) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Ps executes the equivalent to a `compose ps`
+func (s *composeService) Ps(ctx context.Context, projectName string) ([]compose.ContainerSummary, error) {
+	return nil, errdefs.ErrNotImplemented
+}
+
+// Convert translate compose model into backend's native format
+func (s *composeService) Convert(ctx context.Context, project *types.Project, options compose.ConvertOptions) ([]byte, error) {
+	return nil, errdefs.ErrNotImplemented
+}
+
+// RunOneOffContainer creates a service oneoff container and starts its dependencies
+func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
+	return errdefs.ErrNotImplemented
+}

+ 27 - 0
kube/context.go

@@ -0,0 +1,27 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package kube
+
+// ContextParams options for creating a Kubernetes context
+type ContextParams struct {
+	Name            string
+	Description     string
+	Endpoint        string
+	FromEnvironment bool
+}

+ 142 - 0
kube/utils/config.go

@@ -0,0 +1,142 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package utils
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+
+	"github.com/compose-spec/compose-go/loader"
+	"github.com/compose-spec/compose-go/types"
+	"github.com/prometheus/common/log"
+)
+
+var SupportedFilenames = []string{"compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml"}
+
+func GetConfigs(name string, configPaths []string) (string, []types.ConfigFile, error) {
+	configPath, err := getConfigPaths(configPaths)
+	if err != nil {
+		return "", nil, err
+	}
+	if configPath == nil {
+		return "", nil, nil
+	}
+	workingDir := filepath.Dir(configPath[0])
+
+	if name == "" {
+		name = os.Getenv("COMPOSE_PROJECT_NAME")
+	}
+	if name == "" {
+		r := regexp.MustCompile(`[^a-z0-9\\-_]+`)
+		name = r.ReplaceAllString(strings.ToLower(filepath.Base(workingDir)), "")
+	}
+
+	configs, err := parseConfigs(configPath)
+	if err != nil {
+		return "", nil, err
+	}
+	return workingDir, configs, nil
+}
+
+func getConfigPaths(configPaths []string) ([]string, error) {
+	paths := []string{}
+	pwd, err := os.Getwd()
+	if err != nil {
+		return nil, err
+	}
+
+	if len(configPaths) != 0 {
+		for _, f := range configPaths {
+			if f == "-" {
+				paths = append(paths, f)
+				continue
+			}
+			if !filepath.IsAbs(f) {
+				f = filepath.Join(pwd, f)
+			}
+			if _, err := os.Stat(f); err != nil {
+				return nil, err
+			}
+			paths = append(paths, f)
+		}
+		return paths, nil
+	}
+
+	sep := os.Getenv("COMPOSE_FILE_SEPARATOR")
+	if sep == "" {
+		sep = string(os.PathListSeparator)
+	}
+	f := os.Getenv("COMPOSE_FILE")
+	if f != "" {
+		return strings.Split(f, sep), nil
+	}
+
+	for {
+		candidates := []string{}
+		for _, n := range SupportedFilenames {
+			f := filepath.Join(pwd, n)
+			if _, err := os.Stat(f); err == nil {
+				candidates = append(candidates, f)
+			}
+		}
+		if len(candidates) > 0 {
+			winner := candidates[0]
+			if len(candidates) > 1 {
+				log.Warnf("Found multiple config files with supported names: %s", strings.Join(candidates, ", "))
+				log.Warnf("Using %s\n", winner)
+			}
+			return []string{winner}, nil
+		}
+		parent := filepath.Dir(pwd)
+		if parent == pwd {
+			return nil, nil
+		}
+		pwd = parent
+	}
+}
+
+func parseConfigs(configPaths []string) ([]types.ConfigFile, error) {
+	files := []types.ConfigFile{}
+	for _, f := range configPaths {
+		var (
+			b   []byte
+			err error
+		)
+		if f == "-" {
+			b, err = ioutil.ReadAll(os.Stdin)
+		} else {
+			if _, err := os.Stat(f); err != nil {
+				return nil, err
+			}
+			b, err = ioutil.ReadFile(f)
+		}
+		if err != nil {
+			return nil, err
+		}
+		config, err := loader.ParseYAML(b)
+		if err != nil {
+			return nil, err
+		}
+		files = append(files, types.ConfigFile{Filename: f, Config: config})
+	}
+	return files, nil
+}

+ 56 - 0
kube/utils/errors.go

@@ -0,0 +1,56 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package utils
+
+import (
+	"fmt"
+	"strings"
+)
+
+func CombineErrors(errors []error) error {
+	if len(errors) == 0 {
+		return nil
+	}
+	if len(errors) == 1 {
+		return errors[0]
+	}
+	err := combinedError{}
+	for _, e := range errors {
+		if c, ok := e.(combinedError); ok {
+			err.errors = append(err.errors, c.errors...)
+		} else {
+			err.errors = append(err.errors, e)
+		}
+	}
+	return combinedError{errors}
+}
+
+type combinedError struct {
+	errors []error
+}
+
+func (c combinedError) Error() string {
+	points := make([]string, len(c.errors))
+	for i, err := range c.errors {
+		points[i] = fmt.Sprintf("* %s", err.Error())
+	}
+	return fmt.Sprintf(
+		"%d errors occurred:\n\t%s",
+		len(c.errors), strings.Join(points, "\n\t"))
+}

+ 35 - 0
kube/utils/labels.go

@@ -0,0 +1,35 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package utils
+
+const (
+	LabelDockerComposePrefix = "com.docker.compose"
+	LabelService             = LabelDockerComposePrefix + ".service"
+	LabelVersion             = LabelDockerComposePrefix + ".version"
+	LabelContainerNumber     = LabelDockerComposePrefix + ".container-number"
+	LabelOneOff              = LabelDockerComposePrefix + ".oneoff"
+	LabelNetwork             = LabelDockerComposePrefix + ".network"
+	LabelSlug                = LabelDockerComposePrefix + ".slug"
+	LabelVolume              = LabelDockerComposePrefix + ".volume"
+	LabelConfigHash          = LabelDockerComposePrefix + ".config-hash"
+	LabelProject             = LabelDockerComposePrefix + ".project"
+	LabelWorkingDir          = LabelDockerComposePrefix + ".working_dir"
+	LabelConfigFiles         = LabelDockerComposePrefix + ".config_files"
+	LabelEnvironmentFile     = LabelDockerComposePrefix + ".environment_file"
+)

+ 34 - 0
kube/utils/utils.go

@@ -0,0 +1,34 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package utils
+
+import (
+	"os"
+	"strings"
+)
+
+func Environment() map[string]string {
+	vars := make(map[string]string)
+	env := os.Environ()
+	for _, v := range env {
+		k := strings.SplitN(v, "=", 2)
+		vars[k[0]] = k[1]
+	}
+	return vars
+}