Explorar o código

Multiple backend for the cli

* implement a little azure backend
* implement an example backend
* use the right backend from the context
Djordje Lukic %!s(int64=5) %!d(string=hai) anos
pai
achega
f4bde8cb89
Modificáronse 13 ficheiros con 305 adicións e 139 borrados
  1. 1 9
      Makefile
  2. 81 0
      azure/backend.go
  3. 54 0
      backend/backend.go
  4. 2 1
      cli/cmd/context.go
  5. 41 0
      cli/cmd/ps.go
  6. 5 0
      cli/main.go
  7. 16 6
      client/client.go
  8. 22 0
      containers/api.go
  9. 2 5
      context/store/store.go
  10. 33 0
      example/backend.go
  11. 0 114
      example/backend/main.go
  12. 6 2
      go.mod
  13. 42 2
      go.sum

+ 1 - 9
Makefile

@@ -42,19 +42,11 @@ protos:
 cli: protos
 	GOOS=${GOOS} GOARCH=${GOARCH} go build -v -o bin/docker ./cli
 
-example: protos
-	cd example/backend && go build -v -o ../../bin/backend-example
-
 xcli: cli
 	GOOS=linux   GOARCH=amd64 go build -v -o bin/docker-linux-amd64 ./cli
 	GOOS=darwin  GOARCH=amd64 go build -v -o bin/docker-darwin-amd64 ./cli
 	GOOS=windows GOARCH=amd64 go build -v -o bin/docker-windows-amd64.exe ./cli
 
-xexample: example
-	GOOS=linux   GOARCH=amd64 go build -v -o bin/backend-example-linux-amd64 ./example/backend
-	GOOS=darwin  GOARCH=amd64 go build -v -o bin/backend-example-darwin-amd64 ./example/backend
-	GOOS=windows GOARCH=amd64 go build -v -o bin/backend-example-windows-amd64.exe ./example/backend
-
 dprotos:
 	docker build . \
 	--output type=local,dest=./backend/v1 \
@@ -81,4 +73,4 @@ test:
 
 FORCE:
 
-.PHONY: all xall protos example xexample xcli cli bins dbins dxbins dprotos
+.PHONY: all xall protos xcli cli bins dbins dxbins dprotos

+ 81 - 0
azure/backend.go

@@ -0,0 +1,81 @@
+package azure
+
+import (
+	"context"
+
+	"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
+	"github.com/Azure/go-autorest/autorest/azure/auth"
+	"github.com/pkg/errors"
+
+	"github.com/docker/api/backend"
+	"github.com/docker/api/containers"
+	apicontext "github.com/docker/api/context"
+	"github.com/docker/api/context/store"
+)
+
+type containerService struct {
+	cgc containerinstance.ContainerGroupsClient
+	ctx store.AciContext
+}
+
+func init() {
+	backend.Register("aci", "aci", func(ctx context.Context) (interface{}, error) {
+		return New(ctx)
+	})
+}
+
+func getter() interface{} {
+	return &store.AciContext{}
+}
+
+func New(ctx context.Context) (containers.ContainerService, error) {
+	cc := apicontext.CurrentContext(ctx)
+	s, err := store.New()
+	if err != nil {
+		return nil, err
+	}
+	m, err := s.Get(cc, getter)
+	if err != nil {
+		return nil, errors.Wrap(err, "wrong context type")
+	}
+	tc, _ := m.Metadata.Data.(store.AciContext)
+
+	auth, _ := auth.NewAuthorizerFromCLI()
+	containerGroupsClient := containerinstance.NewContainerGroupsClient(tc.SubscriptionID)
+	containerGroupsClient.Authorizer = auth
+
+	return &containerService{
+		cgc: containerGroupsClient,
+		ctx: tc,
+	}, nil
+}
+
+func (cs *containerService) List(ctx context.Context) ([]containers.Container, error) {
+	var cg []containerinstance.ContainerGroup
+	result, err := cs.cgc.ListByResourceGroup(ctx, cs.ctx.ResourceGroup)
+
+	if err != nil {
+		return []containers.Container{}, err
+	}
+
+	for result.NotDone() {
+		cg = append(cg, result.Values()...)
+		if err := result.NextWithContext(ctx); err != nil {
+			return []containers.Container{}, err
+		}
+	}
+
+	res := []containers.Container{}
+	for _, c := range cg {
+		cc := *c.Containers
+		for _, d := range cc {
+			res = append(res, containers.Container{
+				ID:    *c.Name,
+				Image: *d.Image,
+				// Command: strings.Join(*d.ContainerProperties.Command, " "), // TODO command can be null
+			})
+		}
+	}
+
+	return res, nil
+}

+ 54 - 0
backend/backend.go

@@ -0,0 +1,54 @@
+package backend
+
+import (
+	"context"
+	"errors"
+)
+
+var (
+	ErrNoType         = errors.New("backend: no type")
+	ErrNoName         = errors.New("backend: no name")
+	ErrTypeRegistered = errors.New("backend: already registered")
+)
+
+type InitFunc func(context.Context) (interface{}, error)
+
+type Backend struct {
+	name        string
+	backendType string
+	init        InitFunc
+}
+
+var backends = struct {
+	r []*Backend
+}{}
+
+func Register(name string, backendType string, init InitFunc) {
+	if name == "" {
+		panic(ErrNoName)
+	}
+	if backendType == "" {
+		panic(ErrNoType)
+	}
+	for _, b := range backends.r {
+		if b.backendType == backendType {
+			panic(ErrTypeRegistered)
+		}
+	}
+
+	backends.r = append(backends.r, &Backend{
+		name,
+		backendType,
+		init,
+	})
+}
+
+func Get(ctx context.Context, backendType string) (interface{}, error) {
+	for _, b := range backends.r {
+		if b.backendType == backendType {
+			return b.init(ctx)
+		}
+	}
+
+	return nil, errors.New("not found")
+}

+ 2 - 1
cli/cmd/context.go

@@ -99,6 +99,7 @@ func runCreate(ctx context.Context, opts createOpts, name string, contextType st
 	default:
 		s := store.ContextStore(ctx)
 		return s.Create(name, store.TypedContext{
+			Type:        contextType,
 			Description: opts.description,
 		})
 	}
@@ -129,7 +130,7 @@ func runList(ctx context.Context) error {
 	format := "%s\t%s\t%s\n"
 
 	for _, c := range contexts {
-		fmt.Fprintf(w, format, c.Name, c.Metadata.Description)
+		fmt.Fprintf(w, format, c.Name, c.Metadata.Description, c.Metadata.Type)
 	}
 
 	return w.Flush()

+ 41 - 0
cli/cmd/ps.go

@@ -0,0 +1,41 @@
+package cmd
+
+import (
+	"fmt"
+	"os"
+	"text/tabwriter"
+
+	client2 "github.com/docker/api/client"
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+)
+
+var PsCommand = cobra.Command{
+	Use:   "ps",
+	Short: "List containers",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		ctx := cmd.Context()
+
+		// get our current context
+		ctx = current(ctx)
+
+		client, err := client2.New(ctx)
+		if err != nil {
+			return errors.Wrap(err, "cannot connect to backend")
+		}
+		defer client.Close()
+
+		containers, err := client.ContainerService(ctx).List(ctx)
+		if err != nil {
+			return errors.Wrap(err, "fetch containers")
+		}
+
+		w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
+		fmt.Fprintf(w, "NAME\tIMAGE\tCOMMAND\n")
+		format := "%s\t%s\t%s\n"
+		for _, c := range containers {
+			fmt.Fprintf(w, format, c.ID, c.Image, c.Command)
+		}
+		return w.Flush()
+	},
+}

+ 5 - 0
cli/main.go

@@ -36,6 +36,10 @@ import (
 	"path/filepath"
 	"strings"
 
+	// Backend registrations
+	_ "github.com/docker/api/azure"
+	_ "github.com/docker/api/example"
+
 	"github.com/docker/api/cli/cmd"
 	apicontext "github.com/docker/api/context"
 	"github.com/docker/api/context/store"
@@ -107,6 +111,7 @@ func main() {
 
 	root.AddCommand(
 		cmd.ContextCommand(),
+		&cmd.PsCommand,
 		&cmd.ExampleCommand,
 	)
 

+ 16 - 6
client/client.go

@@ -29,14 +29,15 @@ package client
 
 import (
 	"context"
+	"errors"
 
 	"google.golang.org/grpc"
 
+	"github.com/docker/api/backend"
 	v1 "github.com/docker/api/backend/v1"
 	"github.com/docker/api/containers"
 	apicontext "github.com/docker/api/context"
 	"github.com/docker/api/context/store"
-	"github.com/docker/api/example"
 )
 
 // New returns a GRPC client
@@ -50,20 +51,29 @@ func New(ctx context.Context) (*Client, error) {
 	}
 	contextType := s.GetType(cc)
 
-	return &Client{
-		backendType: contextType,
-	}, nil
+	b, err := backend.Get(ctx, contextType)
+	if err != nil {
+		return nil, err
+	}
+
+	if ba, ok := b.(containers.ContainerService); ok {
+		return &Client{
+			backendType: contextType,
+			cc:          ba,
+		}, nil
+	}
+	return nil, errors.New("backend not found")
 }
 
 type Client struct {
 	conn *grpc.ClientConn
 	v1.BackendClient
 	backendType string
+	cc          containers.ContainerService
 }
 
 func (c *Client) ContainerService(ctx context.Context) containers.ContainerService {
-	return example.New()
-
+	return c.cc
 }
 
 func (c *Client) Close() error {

+ 22 - 0
containers/api.go

@@ -0,0 +1,22 @@
+package containers
+
+import (
+	"context"
+)
+
+type Container struct {
+	ID          string
+	Status      string
+	Image       string
+	Command     string
+	CpuTime     uint64
+	MemoryUsage uint64
+	MemoryLimit uint64
+	PidsCurrent uint64
+	PidsLimit   uint64
+	Labels      []string
+}
+
+type ContainerService interface {
+	List(context.Context) ([]Container, error)
+}

+ 2 - 5
context/store/store.go

@@ -111,10 +111,6 @@ func New(opts ...StoreOpt) (Store, error) {
 
 // Get returns the context with the given name
 func (s *store) Get(name string, getter func() interface{}) (*Metadata, error) {
-	if name == "default" {
-		return &Metadata{}, nil
-	}
-
 	meta := filepath.Join(s.root, contextsDir, metadataDir, contextdirOf(name), metaFile)
 	return read(meta, getter)
 }
@@ -195,7 +191,8 @@ func (s *store) Create(name string, data TypedContext) error {
 		Name:     name,
 		Metadata: data,
 		Endpoints: map[string]interface{}{
-			"docker": DummyContext{},
+			"docker":    DummyContext{},
+			(data.Type): DummyContext{},
 		},
 	}
 

+ 33 - 0
example/backend.go

@@ -0,0 +1,33 @@
+package example
+
+import (
+	"context"
+
+	"github.com/docker/api/backend"
+	"github.com/docker/api/containers"
+)
+
+type containerService struct{}
+
+func init() {
+	backend.Register("example", "example", func(ctx context.Context) (interface{}, error) {
+		return New(), nil
+	})
+}
+
+func New() containers.ContainerService {
+	return &containerService{}
+}
+
+func (cs *containerService) List(ctx context.Context) ([]containers.Container, error) {
+	return []containers.Container{
+		{
+			ID:    "id",
+			Image: "nginx",
+		},
+		{
+			ID:    "1234",
+			Image: "alpine",
+		},
+	}, nil
+}

+ 0 - 114
example/backend/main.go

@@ -1,114 +0,0 @@
-/*
-	Copyright (c) 2020 Docker Inc.
-
-	Permission is hereby granted, free of charge, to any person
-	obtaining a copy of this software and associated documentation
-	files (the "Software"), to deal in the Software without
-	restriction, including without limitation the rights to use, copy,
-	modify, merge, publish, distribute, sublicense, and/or sell copies
-	of the Software, and to permit persons to whom the Software is
-	furnished to do so, subject to the following conditions:
-
-	The above copyright notice and this permission notice shall be
-	included in all copies or substantial portions of the Software.
-
-	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-	EXPRESS OR IMPLIED,
-	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-	HOLDERS BE LIABLE FOR ANY CLAIM,
-	DAMAGES OR OTHER LIABILITY,
-	WHETHER IN AN ACTION OF CONTRACT,
-	TORT OR OTHERWISE,
-	ARISING FROM, OUT OF OR IN CONNECTION WITH
-	THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-*/
-
-package main
-
-import (
-	"context"
-	"fmt"
-	"net"
-	"os"
-
-	"github.com/golang/protobuf/ptypes/empty"
-	"github.com/pkg/errors"
-	"github.com/sirupsen/logrus"
-	"github.com/urfave/cli/v2"
-
-	v1 "github.com/docker/api/backend/v1"
-	"github.com/docker/api/server"
-	apiUtil "github.com/docker/api/util"
-)
-
-func main() {
-	app := cli.NewApp()
-	app.Name = "example"
-	app.Usage = "example backend"
-	app.Description = ""
-	app.UseShortOptionHandling = true
-	app.EnableBashCompletion = true
-	app.Flags = []cli.Flag{
-		&cli.BoolFlag{
-			Name:  "debug",
-			Usage: "enable debug output in the logs",
-		},
-		&cli.StringFlag{
-			Name:  "address,a",
-			Usage: "address of the server",
-		},
-	}
-	app.Before = func(clix *cli.Context) error {
-		if clix.Bool("debug") {
-			logrus.SetLevel(logrus.DebugLevel)
-		}
-		return nil
-	}
-	app.Action = func(clix *cli.Context) error {
-		ctx, cancel := apiUtil.NewSigContext()
-		defer cancel()
-
-		// create a new GRPC server with the provided server package
-		s := server.New()
-
-		// listen on a socket to accept connects
-		l, err := net.Listen("unix", clix.String("address"))
-		if err != nil {
-			return errors.Wrap(err, "listen unix socket")
-		}
-		defer l.Close()
-
-		// create our instance of the backend server implementation
-		backend := &backend{}
-
-		// register our instance with the GRPC server
-		v1.RegisterBackendServer(s, backend)
-
-		// handle context being closed or canceled
-		go func() {
-			<-ctx.Done()
-			logrus.Info("backend signaled to stop")
-
-			s.Stop()
-		}()
-
-		logrus.WithField("address", clix.String("address")).Info("serving daemon API")
-		// start the GRPC server to serve on the listener
-		return s.Serve(l)
-	}
-	if err := app.Run(os.Args); err != nil {
-		fmt.Fprintln(os.Stderr, err)
-		os.Exit(1)
-	}
-}
-
-type backend struct {
-}
-
-func (b *backend) BackendInformation(ctx context.Context, _ *empty.Empty) (*v1.BackendInformationResponse, error) {
-	return &v1.BackendInformationResponse{
-		Id: "com.docker.api.backend.example.v1",
-	}, nil
-}

+ 6 - 2
go.mod

@@ -3,7 +3,11 @@ module github.com/docker/api
 go 1.13
 
 require (
-	github.com/coreos/etcd v3.3.10+incompatible
+	github.com/Azure/azure-sdk-for-go v42.0.0+incompatible
+	github.com/Azure/go-autorest/autorest v0.10.0 // indirect
+	github.com/Azure/go-autorest/autorest/azure/auth v0.4.2
+	github.com/Azure/go-autorest/autorest/to v0.3.0 // indirect
+	github.com/Azure/go-autorest/autorest/validation v0.2.0 // indirect
 	github.com/golang/protobuf v1.4.0
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
 	github.com/mitchellh/go-homedir v1.1.0
@@ -15,7 +19,7 @@ require (
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.5.1
 	github.com/urfave/cli/v2 v2.2.0
-	golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect
+	golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0
 	golang.org/x/text v0.3.2 // indirect
 	google.golang.org/grpc v1.29.1
 	google.golang.org/protobuf v1.21.0

+ 42 - 2
go.sum

@@ -1,4 +1,39 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/Azure/azure-sdk-for-go v41.3.0+incompatible h1:W5px0x53aa47nmIAuF1XWR1ZzFuUnkJBGUuzHnNp+Nk=
+github.com/Azure/azure-sdk-for-go v41.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go v42.0.0+incompatible h1:yz6sFf5bHZ+gEOQVuK5JhPqTTAmv+OvSLSaqgzqaCwY=
+github.com/Azure/azure-sdk-for-go v42.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/go-autorest v14.0.1+incompatible h1:YhojO9jolWIvvTW7ORhz2ZSNF6Q1TbLqUunKd3jrtyw=
+github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
+github.com/Azure/go-autorest/autorest v0.9.3 h1:OZEIaBbMdUE/Js+BQKlpO81XlISgipr6yDJ+PSwsgi4=
+github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0=
+github.com/Azure/go-autorest/autorest v0.10.0 h1:mvdtztBqcL8se7MdrUweNieTNi4kfNG6GOJuurQJpuY=
+github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=
+github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
+github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
+github.com/Azure/go-autorest/autorest/adal v0.8.1 h1:pZdL8o72rK+avFWl+p9nE8RWi1JInZrWJYlnpfXJwHk=
+github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
+github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0=
+github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
+github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 h1:iM6UAvjR97ZIeR93qTcwpKNMpV+/FTWjwEbuPD495Tk=
+github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM=
+github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 h1:LXl088ZQlP0SBppGFsRZonW6hSvwgL5gRByMbvUbx8U=
+github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw=
+github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
+github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM=
+github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
+github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc=
+github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
+github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8=
+github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA=
+github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4=
+github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI=
+github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
+github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
+github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
+github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -30,8 +65,11 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
+github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
 github.com/envoyproxy/go-control-plane v0.9.0/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=
@@ -183,6 +221,8 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
+golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -194,11 +234,10 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
-golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -213,6 +252,7 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=