diff --git a/ecs/logs.go b/ecs/logs.go index 3f24ca294..5d9e98470 100644 --- a/ecs/logs.go +++ b/ecs/logs.go @@ -17,51 +17,14 @@ package ecs import ( - "bytes" "context" - "fmt" "io" - "strconv" - "strings" + + "github.com/docker/compose-cli/formatter" ) func (b *ecsAPIService) Logs(ctx context.Context, project string, w io.Writer) error { - consumer := logConsumer{ - colors: map[string]colorFunc{}, - width: 0, - writer: w, - } + consumer := formatter.NewLogConsumer(w) err := b.aws.GetLogs(ctx, project, consumer.Log) return err } - -func (l *logConsumer) Log(service, container, message string) { - cf, ok := l.colors[service] - if !ok { - cf = <-loop - l.colors[service] = cf - l.computeWidth() - } - prefix := fmt.Sprintf("%-"+strconv.Itoa(l.width)+"s |", service) - - for _, line := range strings.Split(message, "\n") { - buf := bytes.NewBufferString(fmt.Sprintf("%s %s\n", cf(prefix), line)) - l.writer.Write(buf.Bytes()) // nolint:errcheck - } -} - -func (l *logConsumer) computeWidth() { - width := 0 - for n := range l.colors { - if len(n) > width { - width = len(n) - } - } - l.width = width + 3 -} - -type logConsumer struct { - colors map[string]colorFunc - width int - writer io.Writer -} diff --git a/ecs/colors.go b/formatter/colors.go similarity index 98% rename from ecs/colors.go rename to formatter/colors.go index a08bc9172..090a396b1 100644 --- a/ecs/colors.go +++ b/formatter/colors.go @@ -14,7 +14,7 @@ limitations under the License. */ -package ecs +package formatter import ( "fmt" diff --git a/formatter/logs.go b/formatter/logs.go new file mode 100644 index 000000000..3f68b7a26 --- /dev/null +++ b/formatter/logs.go @@ -0,0 +1,92 @@ +/* + 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 formatter + +import ( + "bytes" + "fmt" + "io" + "strconv" + "strings" +) + +// NewLogConsumer creates a new LogConsumer +func NewLogConsumer(w io.Writer) LogConsumer { + return LogConsumer{ + colors: map[string]colorFunc{}, + width: 0, + writer: w, + } +} + +// Log formats a log message as received from service/container +func (l *LogConsumer) Log(service, container, message string) { + cf, ok := l.colors[service] + if !ok { + cf = <-loop + l.colors[service] = cf + l.computeWidth() + } + prefix := fmt.Sprintf("%-"+strconv.Itoa(l.width)+"s |", service) + + for _, line := range strings.Split(message, "\n") { + buf := bytes.NewBufferString(fmt.Sprintf("%s %s\n", cf(prefix), line)) + l.writer.Write(buf.Bytes()) // nolint:errcheck + } +} + +// GetWriter creates a io.Writer that will actually split by line and format by LogConsumer +func (l *LogConsumer) GetWriter(service, container string) io.Writer { + return splitBuffer{ + service: service, + container: container, + consumer: l, + } +} + +func (l *LogConsumer) computeWidth() { + width := 0 + for n := range l.colors { + if len(n) > width { + width = len(n) + } + } + l.width = width + 3 +} + +// LogConsumer consume logs from services and format them +type LogConsumer struct { + colors map[string]colorFunc + width int + writer io.Writer +} + +type splitBuffer struct { + service string + container string + consumer *LogConsumer +} + +func (s splitBuffer) Write(b []byte) (n int, err error) { + split := bytes.Split(b, []byte{'\n'}) + for _, line := range split { + if len(line) != 0 { + s.consumer.Log(s.service, s.container, string(line)) + } + } + return len(b), nil +} diff --git a/go.mod b/go.mod index 38a2efbe4..21119c45f 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,9 @@ go 1.15 // we need to create a new release tag for docker/distribution replace github.com/docker/distribution => github.com/docker/distribution v0.0.0-20200708230824-53e18a9d9bfe +// unix.SYS_IOCTL has been removed from golang/sys but still in use by docker/docker until 20.x +replace golang.org/x/sys => golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 + require ( github.com/AlecAivazis/survey/v2 v2.1.1 github.com/Azure/azure-sdk-for-go v43.3.0+incompatible @@ -17,17 +20,18 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 github.com/Azure/go-autorest/autorest/to v0.4.0 github.com/Azure/go-autorest/autorest/validation v0.2.0 // indirect - github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 - github.com/Microsoft/hcsshim v0.8.9 // indirect + github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab + github.com/Microsoft/hcsshim v0.8.10 // indirect github.com/aws/aws-sdk-go v1.35.15 - github.com/awslabs/goformation/v4 v4.15.3 + github.com/awslabs/goformation/v4 v4.15.2 github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129 github.com/compose-spec/compose-go v0.0.0-20201116112017-777513ca88e2 - github.com/containerd/console v1.0.1 + github.com/containerd/console v1.0.0 github.com/containerd/containerd v1.3.5 // indirect + github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a // indirect github.com/docker/cli v0.0.0-20200528204125-dd360c7c0de8 github.com/docker/distribution v0.0.0-00010101000000-000000000000 // indirect - github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible + github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible github.com/docker/docker-credential-helpers v0.6.3 // indirect github.com/docker/go-connections v0.4.0 github.com/docker/go-units v0.4.0 @@ -45,8 +49,10 @@ 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/moby/term v0.0.0-20200915141129-7f0af18e79f2 + github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf github.com/morikuni/aec v1.0.0 + github.com/onsi/ginkgo v1.14.2 // indirect + github.com/onsi/gomega v1.10.2 // indirect github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/runc v0.1.1 // indirect github.com/pkg/errors v0.9.1 @@ -58,7 +64,7 @@ require ( github.com/stretchr/testify v1.6.1 github.com/valyala/fasttemplate v1.2.1 // indirect golang.org/x/mod v0.3.0 - golang.org/x/net v0.0.0-20201026091529-146b70c837a4 + golang.org/x/net v0.0.0-20200822124328-c89045814202 golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 google.golang.org/grpc v1.33.1 diff --git a/go.sum b/go.sum index 6f081194b..abe7818e2 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -79,10 +80,10 @@ 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/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/hcsshim v0.8.9 h1:VrfodqvztU8YSOvygU+DN1BGaSGxmrNfqOv5oOuX2Bk= -github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab h1:9pygWVFqbY9lPxM0peffumuVDyMuIMzNLyO9uFjJuQo= +github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.10 h1:k5wTrpnVU2/xv8ZuzGkbXVd3js5zJ8RnumPo5RxiIxU= +github.com/Microsoft/hcsshim v0.8.10/go.mod h1:g5uw8EV2mAlzqe94tfNBNdr89fnbD/n3HV0OhsddkmM= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -96,8 +97,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.35.15 h1:JdQNM8hJe+9N9xP53S54NDmX8GCaZn8CCJ4LBHfom4U= github.com/aws/aws-sdk-go v1.35.15/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= -github.com/awslabs/goformation/v4 v4.15.3 h1:zVRL+gAW5Xvyhd85KNsJpKaOuArxEyUYKK5+v13Bwrc= -github.com/awslabs/goformation/v4 v4.15.3/go.mod h1:wB5lKZf1J0MYH1Lt4B9w3opqz0uIjP7MMCAcib3QkwA= +github.com/awslabs/goformation/v4 v4.15.2 h1:sRfSdC1FnSBhsrz5G0XZZxapEtmJSlkNpnFQJf8ylfs= +github.com/awslabs/goformation/v4 v4.15.2/go.mod h1:GcJULxCJfloT+3pbqCluXftdEK2AD/UqpS3hkaaBntg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -120,36 +121,36 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf 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= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/compose-spec/compose-go v0.0.0-20201116112017-777513ca88e2 h1:m6m0rnxmq3mPapGNAWqO6FLYg6GST7tJAAZqJ0pjadk= github.com/compose-spec/compose-go v0.0.0-20201116112017-777513ca88e2/go.mod h1:cZJ8y+zQzrYfN5XnEu/kMeDEP0V2D9ojF7Rb4nrneUc= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s= -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/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc= -github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.0 h1:fU3UuQapBs+zLJu82NhR11Rif1ny2zfMMAyPJzSN5tQ= +github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.5 h1:l0iDHQtFwcOUmOvdepI6BB67q7beT6sRp2JYsfHS08c= github.com/containerd/containerd v1.3.5/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 h1:PUD50EuOMkXVcpBIA/R95d56duJR9VxhwncsFbNnxW4= +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= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de h1:dlfGmNcE3jDAecLqwKPMNX6nk2qh1c1Vg1/YTzpOOF4= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd h1:JNn81o/xG+8NEo3bC/vx9pbi/g2WI8mtP2/nXzu297Y= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= -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/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= @@ -165,8 +166,8 @@ github.com/docker/cli v0.0.0-20200528204125-dd360c7c0de8 h1:JRquW4uqIU+eSilDhuo9 github.com/docker/cli v0.0.0-20200528204125-dd360c7c0de8/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20200708230824-53e18a9d9bfe h1:pni13lAFm1g4cjHU6c3n4qGvvJGZQK4VvKRKMseQ42E= github.com/docker/distribution v0.0.0-20200708230824-53e18a9d9bfe/go.mod h1:Oqz4IonmMNc2N7GqfTL2xkhCQx0yS6nR+HrOZJnmKIk= -github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible h1:G2hY8RD7jB9QaSmcb8mYEIg8QbEvVAB7se8+lXHZHfg= -github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible h1:SiUATuP//KecDjpOK2tvZJgeScYAklvyjfK8JZlU6fo= +github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -178,6 +179,7 @@ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHz github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 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= @@ -208,8 +210,7 @@ github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs= github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= @@ -317,6 +318,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO 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= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 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= @@ -343,6 +345,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -389,8 +393,8 @@ github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8 github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1OPs= -github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2 h1:SPoLlS9qUUnXcIY4pvA4CTwYjk0Is5f4UPEkeESr53k= -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= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -403,16 +407,18 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.5.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.2.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -422,8 +428,7 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700 h1:eNUVfm/RFLIi1G7flU5/ZRTHvd4kcVuzfRnL6OFlzCI= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -472,6 +477,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= @@ -485,11 +491,13 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/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= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -505,7 +513,7 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= @@ -620,10 +628,6 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201026091529-146b70c837a4 h1:awiuzyrRjJDb+OXi9ceHO3SDxVoN3JER57mhtqkdQBs= -golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -644,55 +648,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2By golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/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-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirSiguP9Q/veMtkYyf0o8= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/local/backend.go b/local/backend.go index 134fddb00..78d1195cb 100644 --- a/local/backend.go +++ b/local/backend.go @@ -20,7 +20,6 @@ package local import ( "context" - "github.com/docker/docker/client" "github.com/docker/compose-cli/api/compose" @@ -42,7 +41,7 @@ func init() { } func service(ctx context.Context) (backend.Service, error) { - apiClient, err := client.NewClientWithOpts(client.FromEnv) + apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { return nil, err } @@ -53,22 +52,25 @@ func service(ctx context.Context) (backend.Service, error) { }, nil } -func (cs *containerService) ContainerService() containers.Service { - return cs +func (s *local) ContainerService() containers.Service { + return s.containerService } -func (ms *local) ComposeService() compose.Service { +func (s *local) ComposeService() compose.Service { + return s +} + +func (s *local) SecretsService() secrets.Service { return nil } -func (ms *local) SecretsService() secrets.Service { +func (s *local) VolumeService() volumes.Service { + return s.volumeService +} + +func (s *local) ResourceService() resources.Service { return nil } -func (vs *volumeService) VolumeService() volumes.Service { - return vs -} -func (ms *local) ResourceService() resources.Service { - return nil -} + diff --git a/local/compose.go b/local/compose.go new file mode 100644 index 000000000..14dc36852 --- /dev/null +++ b/local/compose.go @@ -0,0 +1,598 @@ +// +build local + +/* + 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 local + +import ( + "context" + "encoding/json" + "fmt" + "golang.org/x/sync/errgroup" + "io" + "path/filepath" + "strconv" + "strings" + "sync" + + "github.com/compose-spec/compose-go/types" + "github.com/docker/compose-cli/api/compose" + "github.com/docker/compose-cli/api/containers" + "github.com/docker/compose-cli/formatter" + "github.com/docker/compose-cli/progress" + moby "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/strslice" + "github.com/docker/docker/errdefs" + "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/go-connections/nat" + "github.com/pkg/errors" + "github.com/sanathkr/go-yaml" +) + +func (s *local) Up(ctx context.Context, project *types.Project, detach bool) error { + for k, network := range project.Networks { + if !network.External.External && network.Name != "" { + network.Name = fmt.Sprintf("%s_%s", project.Name, k) + project.Networks[k] = network + } + err := s.ensureNetwork(ctx, network) + if err != nil { + return err + } + } + + for k, volume := range project.Volumes { + if !volume.External.External && volume.Name != "" { + volume.Name = fmt.Sprintf("%s_%s", project.Name, k) + project.Volumes[k] = volume + } + err := s.ensureVolume(ctx, volume) + if err != nil { + return err + } + } + + for _, service := range project.Services { + err := s.applyPullPolicy(ctx, service) + if err != nil { + return err + } + } + + err := inDependencyOrder(ctx, project, func(service types.ServiceConfig) error { + return s.ensureService(ctx, project, service) + }) + return err +} + +func getContainerName(c moby.Container) string { + // Names return container canonical name /foo + link aliases /linked_by/foo + for _, name := range c.Names { + if strings.LastIndex(name, "/") == 0 { + return name[1:] + } + } + return c.Names[0][1:] +} + +func (s *local) applyPullPolicy(ctx context.Context, service types.ServiceConfig) error { + w := progress.ContextWriter(ctx) + // TODO build vs pull should be controlled by pull policy + // if service.Build {} + if service.Image != "" { + _, _, err := s.containerService.apiClient.ImageInspectWithRaw(ctx, service.Image) + if err != nil { + if errdefs.IsNotFound(err) { + stream, err := s.containerService.apiClient.ImagePull(ctx, service.Image, moby.ImagePullOptions{}) + if err != nil { + return err + } + dec := json.NewDecoder(stream) + for { + var jm jsonmessage.JSONMessage + if err := dec.Decode(&jm); err != nil { + if err == io.EOF { + break + } + return err + } + toProgressEvent(jm, w) + } + } + } + } + return nil +} + +func toProgressEvent(jm jsonmessage.JSONMessage, w progress.Writer) { + if jm.Progress != nil { + if jm.Progress.Total != 0 { + percentage := int(float64(jm.Progress.Current)/float64(jm.Progress.Total)*100) / 2 + numSpaces := 50 - percentage + w.Event(progress.Event{ + ID: jm.ID, + Text: jm.Status, + Status: 0, + StatusText: fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces)), + Done: jm.Status == "Pull complete", + }) + } else { + if jm.Error != nil { + w.Event(progress.Event{ + ID: jm.ID, + Text: jm.Status, + Status: progress.Error, + StatusText: jm.Error.Message, + Done: true, + }) + } else if jm.Status == "Pull complete" || jm.Status == "Already exists" { + w.Event(progress.Event{ + ID: jm.ID, + Text: jm.Status, + Status: progress.Done, + Done: true, + }) + } else { + w.Event(progress.Event{ + ID: jm.ID, + Text: jm.Status, + Status: progress.Working, + Done: false, + }) + } + } + } +} + +func (s *local) Down(ctx context.Context, projectName string) error { + list, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{ + Filters: filters.NewArgs( + projectFilter(projectName), + ), + }) + if err != nil { + return err + } + + eg, ctx := errgroup.WithContext(ctx) + w := progress.ContextWriter(ctx) + for _, c := range list { + container := c + eg.Go(func() error { + w.Event(progress.Event{ + ID: getContainerName(container), + Text: "Stopping", + Status: progress.Working, + Done: false, + }) + err := s.containerService.Stop(ctx, container.ID, nil) + if err != nil { + return err + } + w.Event(progress.Event{ + ID: getContainerName(container), + Text: "Removing", + Status: progress.Working, + Done: false, + }) + err = s.containerService.Delete(ctx, container.ID, containers.DeleteRequest{}) + if err != nil { + return err + } + w.Event(progress.Event{ + ID: getContainerName(container), + Text: "Removed", + Status: progress.Done, + Done: true, + }) + return nil + }) + } + return eg.Wait() +} + +func (s *local) Logs(ctx context.Context, projectName string, w io.Writer) error { + list, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{ + Filters: filters.NewArgs( + projectFilter(projectName), + ), + }) + if err != nil { + return err + } + var wg sync.WaitGroup + consumer := formatter.NewLogConsumer(w) + for _, c := range list { + service := c.Labels[serviceLabel] + containerId := c.ID + go func() { + s.containerService.Logs(ctx,containerId, containers.LogsRequest{ + Follow: true, + Writer: consumer.GetWriter(service, containerId), + }) + wg.Done() + }() + wg.Add(1) + } + wg.Wait() + return nil +} + +func (s *local) Ps(ctx context.Context, projectName string) ([]compose.ServiceStatus, error) { + list, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{ + Filters: filters.NewArgs( + projectFilter(projectName), + ), + }) + if err != nil { + return nil, err + } + var status []compose.ServiceStatus + for _, c := range list { + // TODO group by service + status = append(status, compose.ServiceStatus{ + ID: c.ID, + Name: c.Labels[serviceLabel], + Replicas: 0, + Desired: 0, + Ports: nil, + Publishers: nil, + }) + } + return status, nil +} + + +func (s *local) List(ctx context.Context, projectName string) ([]compose.Stack, error) { + _, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{All: true}) + if err != nil { + return nil, err + } + var stacks []compose.Stack + // TODO rebuild stacks based on containers + return stacks, nil +} + +func (s *local) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) { + switch format { + case "json": + return json.MarshalIndent(project, "", " ") + case "yaml": + return yaml.Marshal(project) + default: + return nil, fmt.Errorf("unsupported format %q", format) + } +} + +func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, number int, inherit *moby.Container) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) { + hash, err := jsonHash(s) + if err != nil { + return nil, nil, nil, err + } + labels := map[string]string{ + projectLabel: p.Name, + serviceLabel: s.Name, + configHashLabel: hash, + containerNumberLabel: strconv.Itoa(number), + } + + var ( + runCmd strslice.StrSlice + entrypoint strslice.StrSlice + ) + if len(s.Command) > 0 { + runCmd = strslice.StrSlice(s.Command) + } + if len(s.Entrypoint) > 0 { + entrypoint = strslice.StrSlice(s.Entrypoint) + } + image := s.Image + if s.Image == "" { + image = fmt.Sprintf("%s_%s", p.Name, s.Name) + } + + var ( + tty = s.Tty + stdinOpen = s.StdinOpen + attachStdin = false + ) + + containerConfig := container.Config{ + Hostname: s.Hostname, + Domainname: s.DomainName, + User: s.User, + ExposedPorts: buildContainerPorts(s), + Tty: tty, + OpenStdin: stdinOpen, + StdinOnce: true, + AttachStdin: attachStdin, + AttachStderr: true, + AttachStdout: true, + Cmd: runCmd, + Image: image, + WorkingDir: s.WorkingDir, + Entrypoint: entrypoint, + NetworkDisabled: s.NetworkMode == "disabled", + MacAddress: s.MacAddress, + Labels: labels, + StopSignal: s.StopSignal, + // Env: s.Environment, FIXME conversion + // Healthcheck: s.HealthCheck, FIXME conversion + // Volumes: // FIXME unclear to me the overlap with HostConfig.Mounts + // StopTimeout: s.StopGracePeriod FIXME conversion + } + + mountOptions, err := buildContainerMountOptions(p, s, inherit) + if err != nil { + return nil, nil, nil, err + } + + bindings, err := buildContainerBindingOptions(s) + if err != nil { + return nil, nil, nil, err + } + + networkMode := getNetworkMode(p, s) + hostConfig := container.HostConfig{ + Mounts: mountOptions, + CapAdd: strslice.StrSlice(s.CapAdd), + CapDrop: strslice.StrSlice(s.CapDrop), + NetworkMode: networkMode, + Init: s.Init, + ReadonlyRootfs: s.ReadOnly, + // ShmSize: , TODO + Sysctls: s.Sysctls, + PortBindings: bindings, + } + + networkConfig := buildDefaultNetworkConfig(s, networkMode) + return &containerConfig, &hostConfig, networkConfig, nil +} + +func buildContainerPorts(s types.ServiceConfig) nat.PortSet { + ports := nat.PortSet{} + for _, p := range s.Ports { + p := nat.Port(fmt.Sprintf("%d/%s", p.Target, p.Protocol)) + ports[p] = struct{}{} + } + return ports +} + +func buildContainerBindingOptions(s types.ServiceConfig) (nat.PortMap, error) { + bindings := nat.PortMap{} + for _, port := range s.Ports { + p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol)) + bind := []nat.PortBinding{} + binding := nat.PortBinding{} + if port.Published > 0 { + binding.HostPort = fmt.Sprint(port.Published) + } + bind = append(bind, binding) + bindings[p] = bind + } + return bindings, nil +} + +func buildContainerMountOptions(p *types.Project, s types.ServiceConfig, inherit *moby.Container) ([]mount.Mount, error) { + mounts := []mount.Mount{} + var inherited []string + if inherit != nil { + for _, m := range inherit.Mounts { + if m.Type == "tmpfs" { + continue + } + src := m.Source + if m.Type == "volume" { + src = m.Name + } + mounts = append(mounts, mount.Mount{ + Type: m.Type, + Source: src, + Target: m.Destination, + ReadOnly: !m.RW, + }) + inherited = append(inherited, m.Destination) + } + } + + for _, v := range s.Volumes { + if contains(inherited, v.Target) { + continue + } + source := v.Source + if v.Type == "bind" && !filepath.IsAbs(source) { + // FIXME handle ~/ + source = filepath.Join(p.WorkingDir, source) + } + + mounts = append(mounts, mount.Mount{ + Type: mount.Type(v.Type), + Source: source, + Target: v.Target, + ReadOnly: v.ReadOnly, + Consistency: mount.Consistency(v.Consistency), + BindOptions: buildBindOption(v.Bind), + VolumeOptions: buildVolumeOptions(v.Volume), + TmpfsOptions: buildTmpfsOptions(v.Tmpfs), + }) + } + return mounts, nil +} + +func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions { + if bind == nil { + return nil + } + return &mount.BindOptions{ + Propagation: mount.Propagation(bind.Propagation), + // NonRecursive: false, FIXME missing from model ? + } +} + +func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions { + if vol == nil { + return nil + } + return &mount.VolumeOptions{ + NoCopy: vol.NoCopy, + // Labels: , // FIXME missing from model ? + // DriverConfig: , // FIXME missing from model ? + } +} + +func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions { + if tmpfs == nil { + return nil + } + return &mount.TmpfsOptions{ + SizeBytes: tmpfs.Size, + // Mode: , // FIXME missing from model ? + } +} + +func buildDefaultNetworkConfig(s types.ServiceConfig, networkMode container.NetworkMode) *network.NetworkingConfig { + config := map[string]*network.EndpointSettings{} + net := string(networkMode) + config[net] = &network.EndpointSettings{ + Aliases: getAliases(s, s.Networks[net]), + } + + return &network.NetworkingConfig{ + EndpointsConfig: config, + } +} + +func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string { + aliases := []string{s.Name} + if c != nil { + aliases = append(aliases, c.Aliases...) + } + return aliases +} + +func getNetworkMode(p *types.Project, service types.ServiceConfig) container.NetworkMode { + mode := service.NetworkMode + if mode == "" { + if len(p.Networks) > 0 { + for name := range getNetworksForService(service) { + return container.NetworkMode(p.Networks[name].Name) + } + } + return container.NetworkMode("none") + } + + /// FIXME incomplete implementation + if strings.HasPrefix(mode, "service:") { + panic("Not yet implemented") + } + if strings.HasPrefix(mode, "container:") { + panic("Not yet implemented") + } + + return container.NetworkMode(mode) +} + +func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetworkConfig { + if len(s.Networks) > 0 { + return s.Networks + } + return map[string]*types.ServiceNetworkConfig{"default": nil} +} + +func (s *local) ensureNetwork(ctx context.Context, n types.NetworkConfig) error { + _, err := s.containerService.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{}) + if err != nil { + if errdefs.IsNotFound(err) { + createOpts := moby.NetworkCreate{ + // TODO NameSpace Labels + Labels: n.Labels, + Driver: n.Driver, + Options: n.DriverOpts, + Internal: n.Internal, + Attachable: n.Attachable, + } + + if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 { + createOpts.IPAM = &network.IPAM{} + } + + if n.Ipam.Driver != "" { + createOpts.IPAM.Driver = n.Ipam.Driver + } + + for _, ipamConfig := range n.Ipam.Config { + config := network.IPAMConfig{ + Subnet: ipamConfig.Subnet, + } + createOpts.IPAM.Config = append(createOpts.IPAM.Config, config) + } + w := progress.ContextWriter(ctx) + w.Event(progress.Event{ + ID: fmt.Sprintf("Network %q", n.Name), + Status: progress.Working, + StatusText: "Create", + Done: false, + }) + if _, err := s.containerService.apiClient.NetworkCreate(context.Background(), n.Name, createOpts); err != nil { + return errors.Wrapf(err, "failed to create network %s", n.Name) + } + w.Event(progress.Event{ + ID: fmt.Sprintf("Network %q", n.Name), + Status: progress.Done, + StatusText: "Created", + Done: true, + }) + return nil + } else { + return err + } + } + return nil +} + +func (s *local) ensureVolume(ctx context.Context, volume types.VolumeConfig) error { + // TODO could identify volume by label vs name + _, err := s.volumeService.Inspect(ctx, volume.Name) + if err != nil { + if errdefs.IsNotFound(err) { + w := progress.ContextWriter(ctx) + w.Event(progress.Event{ + ID: fmt.Sprintf("Volume %q", volume.Name), + Status: progress.Working, + StatusText: "Create", + Done: false, + }) + // TODO we miss support for driver_opts and labels + _, err := s.volumeService.Create(ctx, volume.Name, nil) + w.Event(progress.Event{ + ID: fmt.Sprintf("Volume %q", volume.Name), + Status: progress.Done, + StatusText: "Created", + Done: true, + }) + if err != nil { + return err + } + } + return err + } + return nil +} diff --git a/local/containers.go b/local/containers.go index 327a991e5..84afccb31 100644 --- a/local/containers.go +++ b/local/containers.go @@ -28,6 +28,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/stringid" @@ -134,13 +135,21 @@ func (cs *containerService) Run(ctx context.Context, r containers.ContainerConfi }, } - created, err := cs.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, r.ID) + id, err := cs.create(ctx, containerConfig, hostConfig, nil, r.ID) + if err != nil { + return err + } + return cs.apiClient.ContainerStart(ctx, id, types.ContainerStartOptions{}) +} + +func (cs *containerService) create(ctx context.Context, containerConfig *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, name string) (string, error) { + created, err := cs.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, name) if err != nil { if client.IsErrNotFound(err) { - io, err := cs.apiClient.ImagePull(ctx, r.Image, types.ImagePullOptions{}) + io, err := cs.apiClient.ImagePull(ctx, containerConfig.Image, types.ImagePullOptions{}) if err != nil { - return err + return "", err } scanner := bufio.NewScanner(io) @@ -149,21 +158,20 @@ func (cs *containerService) Run(ctx context.Context, r containers.ContainerConfi } if err = scanner.Err(); err != nil { - return err + return "", err } if err = io.Close(); err != nil { - return err + return "", err } - created, err = cs.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, r.ID) + created, err = cs.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, name) if err != nil { - return err + return "", err } } else { - return err + return "", err } } - - return cs.apiClient.ContainerStart(ctx, created.ID, types.ContainerStartOptions{}) + return created.ID, nil } func (cs *containerService) Start(ctx context.Context, containerID string) error { diff --git a/local/convergence.go b/local/convergence.go new file mode 100644 index 000000000..cca0f9668 --- /dev/null +++ b/local/convergence.go @@ -0,0 +1,241 @@ +// +build local + +/* + 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 local + +import ( + "context" + "fmt" + "github.com/compose-spec/compose-go/types" + "github.com/docker/compose-cli/api/containers" + "github.com/docker/compose-cli/progress" + moby "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/network" + "golang.org/x/sync/errgroup" + "strconv" +) + +func (s *local) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig) error { + actual, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{ + Filters: filters.NewArgs( + filters.Arg("label", fmt.Sprintf("%s=%s", projectLabel, project.Name)), + filters.Arg("label", fmt.Sprintf("%s=%s", serviceLabel, service.Name)), + ), + }) + if err != nil { + return err + } + + scale := getScale(service) + + eg, ctx := errgroup.WithContext(ctx) + if len(actual) < scale { + next, err := nextContainerNumber(actual) + if err != nil { + return err + } + missing := scale - len(actual) + for i := 0; i < missing; i++ { + number := next + i + name := fmt.Sprintf("%s_%s_%d", project.Name, service.Name, number) + eg.Go(func() error { + return s.createContainer(ctx, project, service, name, number) + }) + } + } + + if len(actual) > scale { + for i := scale; i < len(actual); i++ { + container := actual[i] + eg.Go(func() error { + err := s.containerService.Stop(ctx, container.ID, nil) + if err != nil { + return err + } + return s.containerService.Delete(ctx, container.ID, containers.DeleteRequest{}) + }) + } + actual = actual[:scale] + } + + expected, err := jsonHash(service) + if err != nil { + return err + } + for _, container := range actual { + container := container + diverged := container.Labels[configHashLabel] != expected + if diverged { + eg.Go(func() error { + return s.recreateContainer(ctx, project, service, container) + }) + continue + } + + if container.State == "running" { + // already running, skip + continue + } + + eg.Go(func() error { + return s.restartContainer(ctx, service, container) + }) + } + return eg.Wait() +} + +func nextContainerNumber(containers []moby.Container) (int, error) { + max := 0 + for _, c := range containers { + n, err := strconv.Atoi(c.Labels[containerNumberLabel]) + if err != nil { + return 0, err + } + if n > max { + max = n + } + } + return max + 1, nil + +} + +func getScale(config types.ServiceConfig) int { + if config.Deploy != nil && config.Deploy.Replicas != nil { + return int(*config.Deploy.Replicas) + } + if config.Scale != 0 { + return config.Scale + } + return 1 +} + +func (s *local) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int) error { + w := progress.ContextWriter(ctx) + w.Event(progress.Event{ + ID: fmt.Sprintf("Service %q", service.Name), + Status: progress.Working, + StatusText: "Create", + Done: false, + }) + err := s.runContainer(ctx, project, service, name, number, nil) + if err != nil { + return err + } + w.Event(progress.Event{ + ID: fmt.Sprintf("Service %q", service.Name), + Status: progress.Done, + StatusText: "Created", + Done: true, + }) + return nil +} + +func (s *local) recreateContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, container moby.Container) error { + w := progress.ContextWriter(ctx) + w.Event(progress.Event{ + ID: fmt.Sprintf("Service %q", service.Name), + Status: progress.Working, + StatusText: "Recreate", + Done: false, + }) + err := s.containerService.Stop(ctx, container.ID, nil) + if err != nil { + return err + } + name := getContainerName(container) + tmpName := fmt.Sprintf("%s_%s", container.ID[:12], name) + err = s.containerService.apiClient.ContainerRename(ctx, container.ID, tmpName) + if err != nil { + return err + } + number, err := strconv.Atoi(container.Labels[containerNumberLabel]) + if err != nil { + return err + } + err = s.runContainer(ctx, project, service, name, number, &container) + if err != nil { + return err + } + err = s.containerService.Delete(ctx, container.ID, containers.DeleteRequest{}) + if err != nil { + return err + } + w.Event(progress.Event{ + ID: fmt.Sprintf("Service %q", service.Name), + Status: progress.Done, + StatusText: "Recreated", + Done: true, + }) + return nil +} + +func (s *local) restartContainer(ctx context.Context, service types.ServiceConfig, container moby.Container) error { + w := progress.ContextWriter(ctx) + w.Event(progress.Event{ + ID: fmt.Sprintf("Service %q", service.Name), + Status: progress.Working, + StatusText: "Restart", + Done: false, + }) + err := s.containerService.Start(ctx, container.ID) + if err != nil { + return err + } + w.Event(progress.Event{ + ID: fmt.Sprintf("Service %q", service.Name), + Status: progress.Done, + StatusText: "Restarted", + Done: true, + }) + return nil +} + +func (s *local) runContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, container *moby.Container) error { + containerConfig, hostConfig, networkingConfig, err := getContainerCreateOptions(project, service, number, container) + if err != nil { + return err + } + id, err := s.containerService.create(ctx, containerConfig, hostConfig, networkingConfig, name) + if err != nil { + return err + } + for net := range service.Networks { + name := fmt.Sprintf("%s_%s", project.Name, net) + err = s.connectContainerToNetwork(ctx, id, service.Name, name) + if err != nil { + return err + } + } + err = s.containerService.apiClient.ContainerStart(ctx, id, moby.ContainerStartOptions{}) + if err != nil { + return err + } + return nil +} + + +func (s *local) connectContainerToNetwork(ctx context.Context, id string, service string, n string) error { + err := s.containerService.apiClient.NetworkConnect(ctx, n, id, &network.EndpointSettings{ + Aliases: []string{service}, + }) + if err != nil { + return err + } + return nil +} diff --git a/local/dependencies.go b/local/dependencies.go new file mode 100644 index 000000000..d26d1c3c1 --- /dev/null +++ b/local/dependencies.go @@ -0,0 +1,60 @@ +// +build local + +/* + 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 local + +import ( + "context" + "github.com/compose-spec/compose-go/types" + "golang.org/x/sync/errgroup" +) + +func inDependencyOrder(ctx context.Context, project *types.Project, fn func(types.ServiceConfig) error) error { + eg, ctx := errgroup.WithContext(ctx) + var ( + scheduled []string + ready []string + ) + results := make(chan string) + for len(ready) < len(project.Services) { + for _, service := range project.Services { + if contains(scheduled, service.Name) { + continue + } + if containsAll(ready, service.GetDependencies()) { + service := service + scheduled = append(scheduled, service.Name) + eg.Go(func() error { + err := fn(service) + if err != nil { + close(results) + return err + } + results <- service.Name + return nil + }) + } + } + result, ok := <-results + if !ok { + break + } + ready = append(ready, result) + } + return eg.Wait() +} diff --git a/local/dependencies_test.go b/local/dependencies_test.go new file mode 100644 index 000000000..035eda4ee --- /dev/null +++ b/local/dependencies_test.go @@ -0,0 +1,57 @@ +// +build local + +/* + 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 local + +import ( + "context" + "gotest.tools/v3/assert" + "testing" + + "github.com/compose-spec/compose-go/types" +) + +func TestInDependencyOrder(t *testing.T) { + order := make(chan string) + project := types.Project{ + Services: []types.ServiceConfig{ + { + Name: "test1", + DependsOn: map[string]types.ServiceDependency{ + "test2": {}, + }, + }, + { + Name: "test2", + DependsOn: map[string]types.ServiceDependency{ + "test3": {}, + }, + }, + { + Name: "test3", + }, + }, + } + go inDependencyOrder(context.TODO(), &project, func(config types.ServiceConfig) error { + order <- config.Name + return nil + }) + assert.Equal(t, <- order, "test3") + assert.Equal(t, <- order, "test2") + assert.Equal(t, <- order, "test1") +} \ No newline at end of file diff --git a/local/labels.go b/local/labels.go new file mode 100644 index 000000000..29966b4d0 --- /dev/null +++ b/local/labels.go @@ -0,0 +1,35 @@ +// +build local + +/* + 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 local + +import ( + "fmt" + "github.com/docker/docker/api/types/filters" +) + +const ( + projectLabel = "com.docker.compose.project" + serviceLabel = "com.docker.compose.service" + configHashLabel = "com.docker.compose.config-hash" + containerNumberLabel = "com.docker.compose.container-number" +) + +func projectFilter(projectName string) filters.KeyValuePair { + return filters.Arg("label", fmt.Sprintf("%s=%s", projectLabel, projectName)) +} \ No newline at end of file diff --git a/local/util.go b/local/util.go new file mode 100644 index 000000000..cabd257c4 --- /dev/null +++ b/local/util.go @@ -0,0 +1,50 @@ +// +build local + +/* + 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 local + +import ( + "encoding/json" + "github.com/opencontainers/go-digest" +) + +func jsonHash(o interface{}) (string, error) { + bytes, err := json.Marshal(o) + if err != nil { + return "", nil + } + return digest.SHA256.FromBytes(bytes).String(), nil +} + +func contains(slice []string, item string) bool { + for _, v := range slice { + if v == item { + return true + } + } + return false +} + +func containsAll(slice []string, items []string) bool { + for _, i := range items { + if !contains(slice, i) { + return false + } + } + return true +}