reuse ECS logConsumer to implement formatted compose log output

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2020-11-13 17:39:56 +01:00
parent 3f52508efe
commit 74de423cc3
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
5 changed files with 127 additions and 42 deletions

View File

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

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
package ecs
package formatter
import (
"fmt"

91
formatter/logs.go Normal file
View File

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

View File

@ -72,3 +72,4 @@ func (s *local) ResourceService() resources.Service {
return nil
}

View File

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