2019-12-22 20:47:29 +03:00
|
|
|
package mirror
|
|
|
|
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
|
|
// Licensed under the Apache License 2.0.
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
2021-02-24 00:55:53 +03:00
|
|
|
"time"
|
2019-12-22 20:47:29 +03:00
|
|
|
|
2020-10-31 21:40:30 +03:00
|
|
|
"github.com/containers/image/v5/copy"
|
|
|
|
"github.com/containers/image/v5/docker"
|
|
|
|
"github.com/containers/image/v5/signature"
|
|
|
|
"github.com/containers/image/v5/types"
|
2019-12-22 20:47:29 +03:00
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
2020-02-24 17:12:19 +03:00
|
|
|
func Copy(ctx context.Context, dstreference, srcreference string, dstauth, srcauth *types.DockerAuthConfig) error {
|
2019-12-22 20:47:29 +03:00
|
|
|
policyctx, err := signature.NewPolicyContext(&signature.Policy{
|
|
|
|
Default: signature.PolicyRequirements{
|
|
|
|
signature.NewPRInsecureAcceptAnything(),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
src, err := docker.ParseReference("//" + srcreference)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
dst, err := docker.ParseReference("//" + dstreference)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = copy.Image(ctx, policyctx, dst, src, ©.Options{
|
|
|
|
SourceCtx: &types.SystemContext{
|
|
|
|
DockerAuthConfig: srcauth,
|
|
|
|
},
|
|
|
|
DestinationCtx: &types.SystemContext{
|
|
|
|
DockerAuthConfig: dstauth,
|
|
|
|
},
|
2022-10-31 07:19:50 +03:00
|
|
|
// Images that we mirror shouldn't change, so we can use the
|
|
|
|
// optimisation that checks if the source and destination manifests are
|
|
|
|
// equal before attempting to push it (and sending no blobs because
|
|
|
|
// they're all already there)
|
|
|
|
OptimizeDestinationImageAlreadyExists: true,
|
2019-12-22 20:47:29 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-06-11 21:25:28 +03:00
|
|
|
// This will return repo and image name, preserving path
|
|
|
|
// Ex:
|
|
|
|
// repo destrepo.io
|
|
|
|
// reference azurecr.io/some/path/to/image:tag
|
|
|
|
// returns: destrepo.io/some/path/to/image:tag
|
2020-02-24 17:12:19 +03:00
|
|
|
func Dest(repo, reference string) string {
|
2019-12-22 20:47:29 +03:00
|
|
|
return repo + reference[strings.IndexByte(reference, '/'):]
|
|
|
|
}
|
|
|
|
|
2021-06-11 21:25:28 +03:00
|
|
|
// This will return repo / image name.
|
|
|
|
// Ex:
|
|
|
|
// repo destrepo.io
|
|
|
|
// reference azurecr.io/some/path/to/image:tag
|
|
|
|
// returns: destrepo.io/image:tag
|
|
|
|
func DestLastIndex(repo, reference string) string {
|
|
|
|
return repo + reference[strings.LastIndex(reference, "/"):]
|
|
|
|
}
|
|
|
|
|
2019-12-22 20:47:29 +03:00
|
|
|
func Mirror(ctx context.Context, log *logrus.Entry, dstrepo, srcrelease string, dstauth, srcauth *types.DockerAuthConfig) error {
|
|
|
|
log.Printf("reading imagestream from %s", srcrelease)
|
|
|
|
is, err := getReleaseImageStream(ctx, srcrelease, srcauth)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
type work struct {
|
|
|
|
tag string
|
|
|
|
dstreference string
|
|
|
|
srcreference string
|
|
|
|
dstauth *types.DockerAuthConfig
|
|
|
|
srcauth *types.DockerAuthConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
ch := make(chan *work)
|
|
|
|
wg := &sync.WaitGroup{}
|
|
|
|
var errorOccurred atomic.Value
|
|
|
|
errorOccurred.Store(false)
|
|
|
|
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
for w := range ch {
|
|
|
|
log.Printf("mirroring %s", w.tag)
|
2020-03-13 03:00:42 +03:00
|
|
|
var err error
|
2021-02-24 00:55:53 +03:00
|
|
|
for retry := 0; retry < 6; retry++ {
|
2020-03-13 03:00:42 +03:00
|
|
|
err = Copy(ctx, w.dstreference, w.srcreference, w.dstauth, w.srcauth)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
2021-02-24 00:55:53 +03:00
|
|
|
time.Sleep(10 * time.Second)
|
2020-03-13 03:00:42 +03:00
|
|
|
}
|
2019-12-22 20:47:29 +03:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("%s: %s\n", w.tag, err)
|
|
|
|
errorOccurred.Store(true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
wg.Done()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("mirroring %d image(s)", len(is.Spec.Tags)+1)
|
|
|
|
|
|
|
|
ch <- &work{
|
|
|
|
tag: "release",
|
2020-02-24 17:12:19 +03:00
|
|
|
dstreference: Dest(dstrepo, srcrelease),
|
2019-12-22 20:47:29 +03:00
|
|
|
srcreference: srcrelease,
|
|
|
|
dstauth: dstauth,
|
|
|
|
srcauth: srcauth,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tag := range is.Spec.Tags {
|
|
|
|
ch <- &work{
|
|
|
|
tag: tag.Name,
|
2020-02-24 17:12:19 +03:00
|
|
|
dstreference: Dest(dstrepo, tag.From.Name),
|
2019-12-22 20:47:29 +03:00
|
|
|
srcreference: tag.From.Name,
|
|
|
|
dstauth: dstauth,
|
|
|
|
srcauth: srcauth,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
close(ch)
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
if errorOccurred.Load().(bool) {
|
|
|
|
return fmt.Errorf("an error occurred")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|