Multiple backend for the cli

* implement a little azure backend
* implement an example backend
* use the right backend from the context
This commit is contained in:
Djordje Lukic 2020-04-29 19:57:53 +02:00
parent f5bf355d1f
commit f4bde8cb89
13 changed files with 305 additions and 139 deletions

View File

@ -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
azure/backend.go Normal file
View File

@ -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
backend/backend.go Normal file
View File

@ -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")
}

View File

@ -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
cli/cmd/ps.go Normal file
View File

@ -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()
},
}

View File

@ -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,
)

View File

@ -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)
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
containers/api.go Normal file
View File

@ -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)
}

View File

@ -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)
}
@ -196,6 +192,7 @@ func (s *store) Create(name string, data TypedContext) error {
Metadata: data,
Endpoints: map[string]interface{}{
"docker": DummyContext{},
(data.Type): DummyContext{},
},
}

33
example/backend.go Normal file
View File

@ -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
}

View File

@ -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
}

8
go.mod
View File

@ -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

44
go.sum
View File

@ -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=