Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 12 additions & 15 deletions pkg/compose/commit.go → internal/containerops/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,38 @@
limitations under the License.
*/

package compose
package containerops

import (
"context"
"strings"

"github.com/moby/moby/client"

"github.com/docker/compose/v5/internal/inspect"
"github.com/docker/compose/v5/internal/naming"
"github.com/docker/compose/v5/pkg/api"
)

func (s *composeService) Commit(ctx context.Context, projectName string, options api.CommitOptions) error {
return Run(ctx, func(ctx context.Context) error {
return s.commit(ctx, projectName, options)
}, "commit", s.events)
}

func (s *composeService) commit(ctx context.Context, projectName string, options api.CommitOptions) error {
// Commit creates an image from the changes made to a container of a service.
func (s *Service) Commit(ctx context.Context, projectName string, options api.CommitOptions) error {
projectName = strings.ToLower(projectName)

ctr, err := s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, options.Service, options.Index)
ctr, err := s.backend.SpecifiedContainer(ctx, projectName, inspect.OneOffInclude, false, options.Service, options.Index)
if err != nil {
return err
}

name := getCanonicalContainerName(ctr)
name := naming.CanonicalContainerName(ctr)

s.events.On(api.Resource{
s.backend.Events().On(api.Resource{
ID: name,
Status: api.Working,
Text: api.StatusCommitting,
})

if s.dryRun {
s.events.On(api.Resource{
if s.backend.DryRun() {
s.backend.Events().On(api.Resource{
ID: name,
Status: api.Done,
Text: api.StatusCommitted,
Expand All @@ -57,7 +54,7 @@ func (s *composeService) commit(ctx context.Context, projectName string, options
return nil
}

response, err := s.apiClient().ContainerCommit(ctx, ctr.ID, client.ContainerCommitOptions{
response, err := s.backend.APIClient().ContainerCommit(ctx, ctr.ID, client.ContainerCommitOptions{
Reference: options.Reference,
Comment: options.Comment,
Author: options.Author,
Expand All @@ -68,7 +65,7 @@ func (s *composeService) commit(ctx context.Context, projectName string, options
return err
}

s.events.On(api.Resource{
s.backend.Events().On(api.Resource{
ID: name,
Text: "Committed as " + response.ID,
Status: api.Done,
Expand Down
80 changes: 80 additions & 0 deletions internal/containerops/containerops.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
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 containerops implements the operations that act from the outside on
// the container of a service: exec, cp, export and commit.
package containerops

import (
"context"

"github.com/docker/cli/cli/command"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"golang.org/x/sync/errgroup"

"github.com/docker/compose/v5/internal/inspect"
"github.com/docker/compose/v5/pkg/api"
)

// Backend is what the operations need from the engine they act on.
//
// The terminal reaches them through CLI: stdin, stdout and the TTY all come
// from the Docker CLI's streams, the one seam through which an operation that
// talks to the user does so (exec hands the whole CLI to the Docker CLI's own
// implementation).
type Backend interface {
// APIClient talks to the Docker daemon.
APIClient() client.APIClient
// MaxConcurrency bounds the daemon calls made in parallel, 0 meaning no bound.
MaxConcurrency() int
// Events receives the progress of the operations.
Events() api.EventProcessor
// DryRun tells whether the operations only report what they would do.
DryRun() bool
// CLI is the Docker CLI Compose runs in, and the owner of the terminal streams.
CLI() command.Cli
// Containers lists the containers of a project, narrowed to services when given.
Containers(ctx context.Context, projectName string, oneOff inspect.OneOff, all bool, services ...string) ([]container.Summary, error)
// SpecifiedContainer returns the container of a service, the replica with
// the given index when it is above zero.
SpecifiedContainer(ctx context.Context, projectName string, oneOff inspect.OneOff, all bool, serviceName string, index int) (container.Summary, error)
}

// Service runs the container operations on a Backend.
type Service struct {
backend Backend
}

// New creates a Service acting on the given Backend.
func New(backend Backend) *Service {
return &Service{backend: backend}
}

// forEachContainerConcurrent runs fn for every container concurrently, at most
// maxConcurrency at a time (0 meaning no bound), and waits for all of them.
func forEachContainerConcurrent(ctx context.Context, maxConcurrency int, containers []container.Summary, fn func(context.Context, container.Summary) error) error {
eg, ctx := errgroup.WithContext(ctx)
if maxConcurrency > 0 {
eg.SetLimit(maxConcurrency)
}
for _, ctr := range containers {
eg.Go(func() error {
return fn(ctx, ctr)
})
}
return eg.Wait()
}
58 changes: 28 additions & 30 deletions pkg/compose/cp.go → internal/containerops/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
limitations under the License.
*/

package compose
package containerops

import (
"context"
Expand All @@ -30,6 +30,9 @@ import (
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"

"github.com/docker/compose/v5/internal/inspect"
"github.com/docker/compose/v5/internal/naming"
"github.com/docker/compose/v5/internal/relay"
"github.com/docker/compose/v5/pkg/api"
)

Expand All @@ -41,13 +44,8 @@ const (
acrossServices = fromService | toService
)

func (s *composeService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
return Run(ctx, func(ctx context.Context) error {
return s.copy(ctx, projectName, options)
}, "copy", s.events)
}

func (s *composeService) copy(ctx context.Context, projectName string, options api.CopyOptions) error {
// Copy copies files between the host and the containers of a service.
func (s *Service) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
projectName = strings.ToLower(projectName)
srcService, srcPath := splitCpArg(options.Source)
destService, dstPath := splitCpArg(options.Destination)
Expand Down Expand Up @@ -78,15 +76,15 @@ func (s *composeService) copy(ctx context.Context, projectName string, options a
return err
}

return forEachContainerConcurrent(ctx, s.maxConcurrency, containers, func(ctx context.Context, ctr container.Summary) error {
name := getCanonicalContainerName(ctr)
return forEachContainerConcurrent(ctx, s.backend.MaxConcurrency(), containers, func(ctx context.Context, ctr container.Summary) error {
name := naming.CanonicalContainerName(ctr)
var msg string
if direction == fromService {
msg = fmt.Sprintf("%s:%s to %s", name, srcPath, dstPath)
} else {
msg = fmt.Sprintf("%s to %s:%s", srcPath, name, dstPath)
}
s.events.On(api.Resource{
s.backend.Events().On(api.Resource{
ID: name,
Text: api.StatusCopying,
Details: msg,
Expand All @@ -95,7 +93,7 @@ func (s *composeService) copy(ctx context.Context, projectName string, options a
if err := copyFunc(ctx, ctr.ID, srcPath, dstPath, options); err != nil {
return err
}
s.events.On(api.Resource{
s.backend.Events().On(api.Resource{
ID: name,
Text: api.StatusCopied,
Details: msg,
Expand All @@ -105,30 +103,30 @@ func (s *composeService) copy(ctx context.Context, projectName string, options a
})
}

func (s *composeService) listContainersTargetedForCopy(ctx context.Context, projectName string, options api.CopyOptions, direction copyDirection, serviceName string) (Containers, error) {
var containers Containers
func (s *Service) listContainersTargetedForCopy(ctx context.Context, projectName string, options api.CopyOptions, direction copyDirection, serviceName string) ([]container.Summary, error) {
var containers []container.Summary
var err error
switch {
case options.Index > 0:
ctr, err := s.getSpecifiedContainer(ctx, projectName, oneOffExclude, true, serviceName, options.Index)
ctr, err := s.backend.SpecifiedContainer(ctx, projectName, inspect.OneOffExclude, true, serviceName, options.Index)
if err != nil {
return nil, err
}
if err := checkRelayTarget(ctr, serviceName, "cp"); err != nil {
if err := relay.CheckTarget(ctr, serviceName, "cp"); err != nil {
return nil, err
}
return append(containers, ctr), nil
default:
withOneOff := oneOffExclude
withOneOff := inspect.OneOffExclude
if options.All {
withOneOff = oneOffInclude
withOneOff = inspect.OneOffInclude
}
containers, err = s.getContainers(ctx, projectName, withOneOff, true, serviceName)
containers, err = s.backend.Containers(ctx, projectName, withOneOff, true, serviceName)
if err != nil {
return nil, err
}
for _, ctr := range containers {
if err := checkRelayTarget(ctr, serviceName, "cp"); err != nil {
if err := relay.CheckTarget(ctr, serviceName, "cp"); err != nil {
return nil, err
}
}
Expand All @@ -143,7 +141,7 @@ func (s *composeService) listContainersTargetedForCopy(ctx context.Context, proj
}
}

func (s *composeService) copyToContainer(ctx context.Context, containerID string, srcPath string, dstPath string, options api.CopyOptions) error {
func (s *Service) copyToContainer(ctx context.Context, containerID string, srcPath string, dstPath string, options api.CopyOptions) error {
var err error
if srcPath != "-" {
// Get an absolute source path.
Expand All @@ -156,7 +154,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
// Prepare destination copy info by stat-ing the container path.
dstInfo := archive.CopyInfo{Path: dstPath}
var dstStat container.PathStat
res, err := s.apiClient().ContainerStatPath(ctx, containerID, client.ContainerStatPathOptions{
res, err := s.backend.APIClient().ContainerStatPath(ctx, containerID, client.ContainerStatPathOptions{
Path: dstPath,
})
if err == nil {
Expand All @@ -173,7 +171,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
}

dstInfo.Path = linkTarget
res, err = s.apiClient().ContainerStatPath(ctx, containerID, client.ContainerStatPathOptions{
res, err = s.backend.APIClient().ContainerStatPath(ctx, containerID, client.ContainerStatPathOptions{
Path: linkTarget,
})
if err == nil {
Expand Down Expand Up @@ -202,7 +200,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
)

if srcPath == "-" {
content = s.stdin()
content = s.backend.CLI().In()
resolvedDstPath = dstInfo.Path
if !dstInfo.IsDir {
return fmt.Errorf("destination \"%s:%s\" must be a directory", containerID, dstPath)
Expand Down Expand Up @@ -233,7 +231,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
// info which directory to extract to, which may be the parent of the
// destination that the user specified.
// Don't create the archive if running in Dry Run mode
if !s.dryRun {
if !s.backend.DryRun() {
dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
if err != nil {
return err
Expand All @@ -245,7 +243,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
}
}

_, err = s.apiClient().CopyToContainer(ctx, containerID, client.CopyToContainerOptions{
_, err = s.backend.APIClient().CopyToContainer(ctx, containerID, client.CopyToContainerOptions{
DestinationPath: resolvedDstPath,
Content: content,
AllowOverwriteDirWithFile: false,
Expand All @@ -254,7 +252,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
return err
}

func (s *composeService) copyFromContainer(ctx context.Context, containerID, srcPath, dstPath string, options api.CopyOptions) error {
func (s *Service) copyFromContainer(ctx context.Context, containerID, srcPath, dstPath string, options api.CopyOptions) error {
var err error
if dstPath != "-" {
// Get an absolute destination path.
Expand All @@ -272,7 +270,7 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
var rebaseName string
if options.FollowLink {
var srcStat container.PathStat
res, err := s.apiClient().ContainerStatPath(ctx, containerID, client.ContainerStatPathOptions{
res, err := s.backend.APIClient().ContainerStatPath(ctx, containerID, client.ContainerStatPathOptions{
Path: srcPath,
})
if err == nil {
Expand All @@ -293,7 +291,7 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
}
}

res, err := s.apiClient().CopyFromContainer(ctx, containerID, client.CopyFromContainerOptions{
res, err := s.backend.APIClient().CopyFromContainer(ctx, containerID, client.CopyFromContainerOptions{
SourcePath: srcPath,
})
if err != nil {
Expand All @@ -302,7 +300,7 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
defer res.Content.Close() //nolint:errcheck

if dstPath == "-" {
_, err = io.Copy(s.stdout(), res.Content)
_, err = io.Copy(s.backend.CLI().Out(), res.Content)
return err
}

Expand Down
Loading
Loading