github.com/containerd/nerdctl@v1.7.7/pkg/composer/copy.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package composer
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"strings"
    24  
    25  	"github.com/containerd/containerd"
    26  	"github.com/containerd/log"
    27  	"github.com/containerd/nerdctl/pkg/labels"
    28  	"github.com/docker/docker/pkg/system"
    29  )
    30  
    31  type CopyOptions struct {
    32  	Source      string
    33  	Destination string
    34  	Index       int
    35  	FollowLink  bool
    36  	DryRun      bool
    37  }
    38  
    39  type copyDirection int
    40  
    41  const (
    42  	fromService copyDirection = 0
    43  	toService   copyDirection = 1
    44  )
    45  
    46  func (c *Composer) Copy(ctx context.Context, co CopyOptions) error {
    47  	srcService, srcPath := splitCpArg(co.Source)
    48  	destService, dstPath := splitCpArg(co.Destination)
    49  	var serviceName string
    50  	var direction copyDirection
    51  
    52  	if srcService != "" && destService != "" {
    53  		return errors.New("copying between services is not supported")
    54  	}
    55  	if srcService == "" && destService == "" {
    56  		return errors.New("unknown copy direction")
    57  	}
    58  
    59  	if srcService != "" {
    60  		direction = fromService
    61  		serviceName = srcService
    62  	}
    63  	if destService != "" {
    64  		direction = toService
    65  		serviceName = destService
    66  	}
    67  
    68  	containers, err := c.listContainersTargetedForCopy(ctx, co.Index, direction, serviceName)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	for _, container := range containers {
    74  		args := []string{"cp"}
    75  		if co.FollowLink {
    76  			args = append(args, "--follow-link")
    77  		}
    78  		if direction == fromService {
    79  			args = append(args, fmt.Sprintf("%s:%s", container.ID(), srcPath), dstPath)
    80  		}
    81  		if direction == toService {
    82  			args = append(args, srcPath, fmt.Sprintf("%s:%s", container.ID(), dstPath))
    83  		}
    84  		err := c.logCopyMsg(ctx, container, direction, srcService, srcPath, destService, dstPath, co.DryRun)
    85  		if err != nil {
    86  			return err
    87  		}
    88  		if !co.DryRun {
    89  			if err := c.runNerdctlCmd(ctx, args...); err != nil {
    90  				return err
    91  			}
    92  		}
    93  	}
    94  	return nil
    95  }
    96  
    97  func (c *Composer) logCopyMsg(ctx context.Context, container containerd.Container, direction copyDirection, srcService string, srcPath string, destService string, dstPath string, dryRun bool) error {
    98  	containerLabels, err := container.Labels(ctx)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	containerName := containerLabels[labels.Name]
   103  	msg := ""
   104  	if dryRun {
   105  		msg = "DRY-RUN MODE - "
   106  	}
   107  	if direction == fromService {
   108  		msg = msg + fmt.Sprintf("copy %s:%s to %s", containerName, srcPath, dstPath)
   109  	}
   110  	if direction == toService {
   111  		msg = msg + fmt.Sprintf("copy %s to %s:%s", srcPath, containerName, dstPath)
   112  	}
   113  	log.G(ctx).Info(msg)
   114  	return nil
   115  }
   116  
   117  func (c *Composer) listContainersTargetedForCopy(ctx context.Context, index int, direction copyDirection, serviceName string) ([]containerd.Container, error) {
   118  	var containers []containerd.Container
   119  	var err error
   120  
   121  	containers, err = c.Containers(ctx, serviceName)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	if index > 0 {
   127  		if index > len(containers) {
   128  			return nil, fmt.Errorf("index (%d) out of range: only %d running instances from service %s",
   129  				index, len(containers), serviceName)
   130  		}
   131  		container := containers[index-1]
   132  		return []containerd.Container{container}, nil
   133  	}
   134  
   135  	if len(containers) < 1 {
   136  		return nil, fmt.Errorf("no container found for service %q", serviceName)
   137  	}
   138  	if direction == fromService {
   139  		return containers[:1], err
   140  
   141  	}
   142  	return containers, err
   143  }
   144  
   145  // https://github.com/docker/compose/blob/v2.21.0/pkg/compose/cp.go#L307
   146  func splitCpArg(arg string) (container, path string) {
   147  	if system.IsAbs(arg) {
   148  		// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
   149  		return "", arg
   150  	}
   151  
   152  	parts := strings.SplitN(arg, ":", 2)
   153  
   154  	if len(parts) == 1 || strings.HasPrefix(parts[0], ".") {
   155  		// Either there's no `:` in the arg
   156  		// OR it's an explicit local relative path like `./file:name.txt`.
   157  		return "", arg
   158  	}
   159  
   160  	return parts[0], parts[1]
   161  }