|
package oci |
|
|
|
import ( |
|
"context" |
|
"errors" |
|
"fmt" |
|
"io" |
|
"net/http" |
|
"runtime" |
|
"strings" |
|
"syscall" |
|
"time" |
|
|
|
"github.com/containerd/containerd/archive" |
|
registrytypes "github.com/docker/docker/api/types/registry" |
|
"github.com/google/go-containerregistry/pkg/authn" |
|
"github.com/google/go-containerregistry/pkg/logs" |
|
"github.com/google/go-containerregistry/pkg/name" |
|
v1 "github.com/google/go-containerregistry/pkg/v1" |
|
"github.com/google/go-containerregistry/pkg/v1/mutate" |
|
"github.com/google/go-containerregistry/pkg/v1/remote" |
|
"github.com/google/go-containerregistry/pkg/v1/remote/transport" |
|
) |
|
|
|
|
|
type staticAuth struct { |
|
auth *registrytypes.AuthConfig |
|
} |
|
|
|
func (s staticAuth) Authorization() (*authn.AuthConfig, error) { |
|
if s.auth == nil { |
|
return nil, nil |
|
} |
|
return &authn.AuthConfig{ |
|
Username: s.auth.Username, |
|
Password: s.auth.Password, |
|
Auth: s.auth.Auth, |
|
IdentityToken: s.auth.IdentityToken, |
|
RegistryToken: s.auth.RegistryToken, |
|
}, nil |
|
} |
|
|
|
var defaultRetryBackoff = remote.Backoff{ |
|
Duration: 1.0 * time.Second, |
|
Factor: 3.0, |
|
Jitter: 0.1, |
|
Steps: 3, |
|
} |
|
|
|
var defaultRetryPredicate = func(err error) bool { |
|
if err == nil { |
|
return false |
|
} |
|
|
|
if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) || errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET) || strings.Contains(err.Error(), "connection refused") { |
|
logs.Warn.Printf("retrying %v", err) |
|
return true |
|
} |
|
return false |
|
} |
|
|
|
|
|
func ExtractOCIImage(img v1.Image, targetDestination string) error { |
|
reader := mutate.Extract(img) |
|
|
|
_, err := archive.Apply(context.Background(), targetDestination, reader, archive.WithNoSameOwner()) |
|
|
|
return err |
|
} |
|
|
|
func ParseImageParts(image string) (tag, repository, dstimage string) { |
|
tag = "latest" |
|
repository = "library" |
|
if strings.Contains(image, ":") { |
|
parts := strings.Split(image, ":") |
|
image = parts[0] |
|
tag = parts[1] |
|
} |
|
if strings.Contains("/", image) { |
|
parts := strings.Split(image, "/") |
|
repository = parts[0] |
|
image = parts[1] |
|
} |
|
dstimage = image |
|
return tag, repository, image |
|
} |
|
|
|
|
|
|
|
|
|
func GetImage(targetImage, targetPlatform string, auth *registrytypes.AuthConfig, t http.RoundTripper) (v1.Image, error) { |
|
var platform *v1.Platform |
|
var image v1.Image |
|
var err error |
|
|
|
if targetPlatform != "" { |
|
platform, err = v1.ParsePlatform(targetPlatform) |
|
if err != nil { |
|
return image, err |
|
} |
|
} else { |
|
platform, err = v1.ParsePlatform(fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)) |
|
if err != nil { |
|
return image, err |
|
} |
|
} |
|
|
|
ref, err := name.ParseReference(targetImage) |
|
if err != nil { |
|
return image, err |
|
} |
|
|
|
if t == nil { |
|
t = http.DefaultTransport |
|
} |
|
|
|
tr := transport.NewRetry(t, |
|
transport.WithRetryBackoff(defaultRetryBackoff), |
|
transport.WithRetryPredicate(defaultRetryPredicate), |
|
) |
|
|
|
opts := []remote.Option{ |
|
remote.WithTransport(tr), |
|
remote.WithPlatform(*platform), |
|
} |
|
if auth != nil { |
|
opts = append(opts, remote.WithAuth(staticAuth{auth})) |
|
} else { |
|
opts = append(opts, remote.WithAuthFromKeychain(authn.DefaultKeychain)) |
|
} |
|
|
|
image, err = remote.Image(ref, opts...) |
|
|
|
return image, err |
|
} |
|
|
|
func GetOCIImageSize(targetImage, targetPlatform string, auth *registrytypes.AuthConfig, t http.RoundTripper) (int64, error) { |
|
var size int64 |
|
var img v1.Image |
|
var err error |
|
|
|
img, err = GetImage(targetImage, targetPlatform, auth, t) |
|
if err != nil { |
|
return size, err |
|
} |
|
layers, _ := img.Layers() |
|
for _, layer := range layers { |
|
s, _ := layer.Size() |
|
size += s |
|
} |
|
|
|
return size, nil |
|
} |
|
|