...

Source file src/github.com/google/go-containerregistry/pkg/crane/copy.go

Documentation: github.com/google/go-containerregistry/pkg/crane

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package crane
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"net/http"
    21  
    22  	"github.com/google/go-containerregistry/pkg/logs"
    23  	"github.com/google/go-containerregistry/pkg/name"
    24  	"github.com/google/go-containerregistry/pkg/v1/remote"
    25  	"github.com/google/go-containerregistry/pkg/v1/remote/transport"
    26  	"golang.org/x/sync/errgroup"
    27  )
    28  
    29  // Copy copies a remote image or index from src to dst.
    30  func Copy(src, dst string, opt ...Option) error {
    31  	o := makeOptions(opt...)
    32  	srcRef, err := name.ParseReference(src, o.Name...)
    33  	if err != nil {
    34  		return fmt.Errorf("parsing reference %q: %w", src, err)
    35  	}
    36  
    37  	dstRef, err := name.ParseReference(dst, o.Name...)
    38  	if err != nil {
    39  		return fmt.Errorf("parsing reference for %q: %w", dst, err)
    40  	}
    41  
    42  	puller, err := remote.NewPuller(o.Remote...)
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	if tag, ok := dstRef.(name.Tag); ok {
    48  		if o.noclobber {
    49  			logs.Progress.Printf("Checking existing tag %v", tag)
    50  			head, err := puller.Head(o.ctx, tag)
    51  			var terr *transport.Error
    52  			if errors.As(err, &terr) {
    53  				if terr.StatusCode != http.StatusNotFound && terr.StatusCode != http.StatusForbidden {
    54  					return err
    55  				}
    56  			} else if err != nil {
    57  				return err
    58  			}
    59  
    60  			if head != nil {
    61  				return fmt.Errorf("refusing to clobber existing tag %s@%s", tag, head.Digest)
    62  			}
    63  		}
    64  	}
    65  
    66  	pusher, err := remote.NewPusher(o.Remote...)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	logs.Progress.Printf("Copying from %v to %v", srcRef, dstRef)
    72  	desc, err := puller.Get(o.ctx, srcRef)
    73  	if err != nil {
    74  		return fmt.Errorf("fetching %q: %w", src, err)
    75  	}
    76  
    77  	if o.Platform == nil {
    78  		return pusher.Push(o.ctx, dstRef, desc)
    79  	}
    80  
    81  	// If platform is explicitly set, don't copy the whole index, just the appropriate image.
    82  	img, err := desc.Image()
    83  	if err != nil {
    84  		return err
    85  	}
    86  	return pusher.Push(o.ctx, dstRef, img)
    87  }
    88  
    89  // CopyRepository copies every tag from src to dst.
    90  func CopyRepository(src, dst string, opt ...Option) error {
    91  	o := makeOptions(opt...)
    92  
    93  	srcRepo, err := name.NewRepository(src, o.Name...)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	dstRepo, err := name.NewRepository(dst, o.Name...)
    99  	if err != nil {
   100  		return fmt.Errorf("parsing reference for %q: %w", dst, err)
   101  	}
   102  
   103  	puller, err := remote.NewPuller(o.Remote...)
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	ignoredTags := map[string]struct{}{}
   109  	if o.noclobber {
   110  		// TODO: It would be good to propagate noclobber down into remote so we can use Etags.
   111  		have, err := puller.List(o.ctx, dstRepo)
   112  		if err != nil {
   113  			var terr *transport.Error
   114  			if errors.As(err, &terr) {
   115  				// Some registries create repository on first push, so listing tags will fail.
   116  				// If we see 404 or 403, assume we failed because the repository hasn't been created yet.
   117  				if !(terr.StatusCode == http.StatusNotFound || terr.StatusCode == http.StatusForbidden) {
   118  					return err
   119  				}
   120  			} else {
   121  				return err
   122  			}
   123  		}
   124  		for _, tag := range have {
   125  			ignoredTags[tag] = struct{}{}
   126  		}
   127  	}
   128  
   129  	pusher, err := remote.NewPusher(o.Remote...)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	lister, err := puller.Lister(o.ctx, srcRepo)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	g, ctx := errgroup.WithContext(o.ctx)
   140  	g.SetLimit(o.jobs)
   141  
   142  	for lister.HasNext() {
   143  		tags, err := lister.Next(ctx)
   144  		if err != nil {
   145  			return err
   146  		}
   147  
   148  		for _, tag := range tags.Tags {
   149  			tag := tag
   150  
   151  			if o.noclobber {
   152  				if _, ok := ignoredTags[tag]; ok {
   153  					logs.Progress.Printf("Skipping %s due to no-clobber", tag)
   154  					continue
   155  				}
   156  			}
   157  
   158  			g.Go(func() error {
   159  				srcTag, err := name.ParseReference(src+":"+tag, o.Name...)
   160  				if err != nil {
   161  					return fmt.Errorf("failed to parse tag: %w", err)
   162  				}
   163  				dstTag, err := name.ParseReference(dst+":"+tag, o.Name...)
   164  				if err != nil {
   165  					return fmt.Errorf("failed to parse tag: %w", err)
   166  				}
   167  
   168  				logs.Progress.Printf("Fetching %s", srcTag)
   169  				desc, err := puller.Get(ctx, srcTag)
   170  				if err != nil {
   171  					return err
   172  				}
   173  
   174  				logs.Progress.Printf("Pushing %s", dstTag)
   175  				return pusher.Push(ctx, dstTag, desc)
   176  			})
   177  		}
   178  	}
   179  
   180  	return g.Wait()
   181  }
   182  

View as plain text