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 }