Merge pull request #1073 from docker/logs_service

allow to collect logs for a subset of project services
This commit is contained in:
Nicolas De loof 2020-12-17 10:43:16 +01:00 committed by GitHub
commit 76ba85fe5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 60 additions and 12 deletions

View File

@ -194,7 +194,7 @@ func (cs *aciComposeService) List(ctx context.Context, project string) ([]compos
return stacks, nil return stacks, nil
} }
func (cs *aciComposeService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer) error { func (cs *aciComposeService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer, options compose.LogOptions) error {
return errdefs.ErrNotImplemented return errdefs.ErrNotImplemented
} }

View File

@ -56,7 +56,7 @@ func (c *composeService) Down(context.Context, string) error {
return errdefs.ErrNotImplemented return errdefs.ErrNotImplemented
} }
func (c *composeService) Logs(context.Context, string, compose.LogConsumer) error { func (c *composeService) Logs(context.Context, string, compose.LogConsumer, compose.LogOptions) error {
return errdefs.ErrNotImplemented return errdefs.ErrNotImplemented
} }

View File

@ -39,7 +39,7 @@ type Service interface {
// Down executes the equivalent to a `compose down` // Down executes the equivalent to a `compose down`
Down(ctx context.Context, projectName string) error Down(ctx context.Context, projectName string) error
// Logs executes the equivalent to a `compose logs` // Logs executes the equivalent to a `compose logs`
Logs(ctx context.Context, projectName string, consumer LogConsumer) error Logs(ctx context.Context, projectName string, consumer LogConsumer, options LogOptions) error
// Ps executes the equivalent to a `compose ps` // Ps executes the equivalent to a `compose ps`
Ps(ctx context.Context, projectName string) ([]ContainerSummary, error) Ps(ctx context.Context, projectName string) ([]ContainerSummary, error)
// List executes the equivalent to a `docker stack ls` // List executes the equivalent to a `docker stack ls`
@ -76,6 +76,11 @@ type ServiceStatus struct {
Publishers []PortPublisher Publishers []PortPublisher
} }
// LogOptions defines optional parameters for the `Log` API
type LogOptions struct {
Services []string
}
const ( const (
// STARTING indicates that stack is being deployed // STARTING indicates that stack is being deployed
STARTING string = "Starting" STARTING string = "Starting"

View File

@ -21,18 +21,18 @@ import (
"os" "os"
"github.com/docker/compose-cli/api/client" "github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/formatter" "github.com/docker/compose-cli/formatter"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func logsCommand() *cobra.Command { func logsCommand() *cobra.Command {
opts := composeOptions{} opts := composeOptions{}
logsCmd := &cobra.Command{ logsCmd := &cobra.Command{
Use: "logs", Use: "logs [service...]",
Short: "View output from containers", Short: "View output from containers",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runLogs(cmd.Context(), opts) return runLogs(cmd.Context(), opts, args)
}, },
} }
logsCmd.Flags().StringVarP(&opts.Name, "project-name", "p", "", "Project name") logsCmd.Flags().StringVarP(&opts.Name, "project-name", "p", "", "Project name")
@ -42,7 +42,7 @@ func logsCommand() *cobra.Command {
return logsCmd return logsCmd
} }
func runLogs(ctx context.Context, opts composeOptions) error { func runLogs(ctx context.Context, opts composeOptions, services []string) error {
c, err := client.NewWithDefaultLocalBackend(ctx) c, err := client.NewWithDefaultLocalBackend(ctx)
if err != nil { if err != nil {
return err return err
@ -53,5 +53,7 @@ func runLogs(ctx context.Context, opts composeOptions) error {
return err return err
} }
consumer := formatter.NewLogConsumer(ctx, os.Stdout) consumer := formatter.NewLogConsumer(ctx, os.Stdout)
return c.ComposeService().Logs(ctx, projectName, consumer) return c.ComposeService().Logs(ctx, projectName, consumer, compose.LogOptions{
Services: services,
})
} }

View File

@ -180,7 +180,7 @@ services:
return cmd.Run() return cmd.Run()
} }
func (e ecsLocalSimulation) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer) error { func (e ecsLocalSimulation) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer, options compose.LogOptions) error {
list, err := e.moby.ContainerList(ctx, types2.ContainerListOptions{ list, err := e.moby.ContainerList(ctx, types2.ContainerListOptions{
Filters: filters.NewArgs(filters.Arg("label", "com.docker.compose.project="+projectName)), Filters: filters.NewArgs(filters.Arg("label", "com.docker.compose.project="+projectName)),
}) })

View File

@ -22,7 +22,35 @@ import (
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
) )
func (b *ecsAPIService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer) error { func (b *ecsAPIService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer, options compose.LogOptions) error {
if len(options.Services) > 0 {
consumer = filteredLogConsumer(consumer, options.Services)
}
err := b.aws.GetLogs(ctx, projectName, consumer.Log) err := b.aws.GetLogs(ctx, projectName, consumer.Log)
return err return err
} }
func filteredLogConsumer(consumer compose.LogConsumer, services []string) compose.LogConsumer {
if len(services) == 0 {
return consumer
}
allowed := map[string]bool{}
for _, s := range services {
allowed[s] = true
}
return &allowListLogConsumer{
allowList: allowed,
delegate: consumer,
}
}
type allowListLogConsumer struct {
allowList map[string]bool
delegate compose.LogConsumer
}
func (a *allowListLogConsumer) Log(service, container, message string) {
if a.allowList[service] {
a.delegate.Log(service, container, message)
}
}

View File

@ -175,7 +175,7 @@ func (cs *composeService) Ps(ctx context.Context, projectName string) ([]compose
func (cs *composeService) List(ctx context.Context, project string) ([]compose.Stack, error) { func (cs *composeService) List(ctx context.Context, project string) ([]compose.Stack, error) {
return nil, errdefs.ErrNotImplemented return nil, errdefs.ErrNotImplemented
} }
func (cs *composeService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer) error { func (cs *composeService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer, options compose.LogOptions) error {
return errdefs.ErrNotImplemented return errdefs.ErrNotImplemented
} }

View File

@ -29,18 +29,31 @@ import (
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
func (s *composeService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer) error { func (s *composeService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer, options compose.LogOptions) error {
list, err := s.apiClient.ContainerList(ctx, types.ContainerListOptions{ list, err := s.apiClient.ContainerList(ctx, types.ContainerListOptions{
Filters: filters.NewArgs( Filters: filters.NewArgs(
projectFilter(projectName), projectFilter(projectName),
), ),
}) })
ignore := func(string) bool {
return false
}
if len(options.Services) > 0 {
ignore = func(s string) bool {
return !contains(options.Services, s)
}
}
if err != nil { if err != nil {
return err return err
} }
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for _, c := range list { for _, c := range list {
service := c.Labels[serviceLabel] service := c.Labels[serviceLabel]
if ignore(service) {
continue
}
container, err := s.apiClient.ContainerInspect(ctx, c.ID) container, err := s.apiClient.ContainerInspect(ctx, c.ID)
if err != nil { if err != nil {
return err return err