From 74de423cc33db21f21668e58e87a9b3f674a91bf Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Fri, 13 Nov 2020 17:39:56 +0100 Subject: [PATCH] reuse ECS logConsumer to implement formatted compose log output Signed-off-by: Nicolas De Loof --- ecs/logs.go | 41 +--------------- {ecs => formatter}/colors.go | 2 +- formatter/logs.go | 91 ++++++++++++++++++++++++++++++++++++ local/backend.go | 1 + local/compose.go | 34 +++++++++++++- 5 files changed, 127 insertions(+), 42 deletions(-) rename {ecs => formatter}/colors.go (98%) create mode 100644 formatter/logs.go diff --git a/ecs/logs.go b/ecs/logs.go index 3f24ca29..53658b78 100644 --- a/ecs/logs.go +++ b/ecs/logs.go @@ -17,51 +17,14 @@ package ecs import ( - "bytes" "context" - "fmt" + "github.com/docker/compose-cli/formatter" "io" - "strconv" - "strings" ) 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 a08bc917..090a396b 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 00000000..a9e28774 --- /dev/null +++ b/formatter/logs.go @@ -0,0 +1,91 @@ +/* + 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" +) + + +func NewLogConsumer(w io.Writer) LogConsumer { + return LogConsumer{ + colors: map[string]colorFunc{}, + width: 0, + writer: w, + } +} + +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) 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 +} + +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/local/backend.go b/local/backend.go index b72658de..6fc5d08a 100644 --- a/local/backend.go +++ b/local/backend.go @@ -72,3 +72,4 @@ func (s *local) ResourceService() resources.Service { return nil } + diff --git a/local/compose.go b/local/compose.go index 41bd3875..1291ad4a 100644 --- a/local/compose.go +++ b/local/compose.go @@ -25,6 +25,7 @@ import ( "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" moby "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" @@ -43,7 +44,7 @@ import ( func (s *local) Up(ctx context.Context, project *types.Project, detach bool) error { for k, network := range project.Networks { - if !network.External.External { + if !network.External.External && network.Name != "" { network.Name = fmt.Sprintf("%s_%s", project.Name, k) project.Networks[k] = network } @@ -53,6 +54,17 @@ func (s *local) Up(ctx context.Context, project *types.Project, detach bool) 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 { containerConfig, hostConfig, networkingConfig, err := getContainerCreateOptions(project, service) if err != nil { @@ -104,11 +116,13 @@ func (s *local) Logs(ctx context.Context, projectName string, w io.Writer) error return err } var wg sync.WaitGroup + consumer := formatter.NewLogConsumer(w) for _, c := range list { + service := c.Labels["com.docker.compose.service"] go func() { s.containerService.Logs(ctx, c.ID, containers.LogsRequest{ Follow: true, - Writer: w, + Writer: consumer.GetWriter(service, c.ID), }) wg.Done() }() @@ -418,3 +432,19 @@ func (s *local) connectContainerToNetwork(ctx context.Context, id string, servic } 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) { + // TODO we miss support for driver_opts and labels + _, err := s.volumeService.Create(ctx, volume.Name, nil) + if err != nil { + return err + } + } + return err + } + return nil +}