1
0
mirror of https://github.com/regclient/regclient.git synced 2025-04-18 22:44:00 +03:00
regclient/mod/mod_test.go
Brandon Mitchell 95c9152941
Chore: Modernize Go to the 1.22 specs
- Use "any" instead of an empty interface.
- Use range over an integer for for loops.
- Remove shadow variables in loops now that Go no longer reuses the variable.
- Use "slices.Contains", "slices.Delete", "slices.Equal", "slices.Index", "slices.SortFunc".
- Use "cmp.Or", "min", and "max".
- Use "fmt.Appendf" instead of "Sprintf" for generating a byte slice.
- Use "errors.Join" or "fmt.Errorf" with multiple "%w" for multiple errors.

Additionally, use modern regclient features:

- Use "ref.SetTag", "ref.SetDigest", and "ref.AddDigest".
- Call "regclient.ManifestGet" using "WithManifestDesc" instead of setting the digest on the reference.

Signed-off-by: Brandon Mitchell <git@bmitch.net>
2025-02-18 14:32:06 -05:00

1092 lines
24 KiB
Go

package mod
import (
"bytes"
"context"
"errors"
"fmt"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"regexp"
"testing"
"time"
"github.com/olareg/olareg"
oConfig "github.com/olareg/olareg/config"
"github.com/opencontainers/go-digest"
"github.com/regclient/regclient"
"github.com/regclient/regclient/config"
"github.com/regclient/regclient/internal/copyfs"
"github.com/regclient/regclient/pkg/archive"
"github.com/regclient/regclient/scheme/reg"
"github.com/regclient/regclient/types/errs"
"github.com/regclient/regclient/types/manifest"
"github.com/regclient/regclient/types/platform"
"github.com/regclient/regclient/types/ref"
)
func TestMod(t *testing.T) {
t.Parallel()
ctx := context.Background()
baseTime, err := time.Parse(time.RFC3339, "2020-01-01T00:00:00Z")
if err != nil {
t.Fatalf("failed to parse test time: %v", err)
}
oldTime, err := time.Parse(time.RFC3339, "1999-01-01T00:00:00Z")
if err != nil {
t.Fatalf("failed to parse test time: %v", err)
}
bDig := digest.FromString("digest for base image")
bRef, err := ref.New("base:latest")
if err != nil {
t.Fatalf("failed to parse base image: %v", err)
}
tarBytes, err := os.ReadFile("../testdata/layer.tar")
if err != nil {
t.Fatalf("failed to read testdata/layer.tar: %v", err)
}
bTrue := true
regSrc := olareg.New(oConfig.Config{
Storage: oConfig.ConfigStorage{
StoreType: oConfig.StoreMem,
RootDir: "../testdata",
ReadOnly: &bTrue,
},
})
tSrc := httptest.NewServer(regSrc)
tSrcURL, _ := url.Parse(tSrc.URL)
tSrcHost := tSrcURL.Host
regTgt := olareg.New(oConfig.Config{
Storage: oConfig.ConfigStorage{
StoreType: oConfig.StoreMem,
RootDir: "../testdata",
},
})
tTgt := httptest.NewServer(regTgt)
tTgtURL, _ := url.Parse(tTgt.URL)
tTgtHost := tTgtURL.Host
t.Cleanup(func() {
tSrc.Close()
_ = regSrc.Close()
tTgt.Close()
_ = regTgt.Close()
})
tempDir := t.TempDir()
err = copyfs.Copy(filepath.Join(tempDir, "testrepo"), "../testdata/testrepo")
if err != nil {
t.Fatalf("failed to setup tempDir: %v", err)
}
// create regclient
rcHosts := []config.Host{
{
Name: tSrcHost,
Hostname: tSrcHost,
TLS: config.TLSDisabled,
},
{
Name: tTgtHost,
Hostname: tTgtHost,
TLS: config.TLSDisabled,
},
{
Name: "registry.example.org",
Hostname: tSrcHost,
TLS: config.TLSDisabled,
},
}
delayInit, _ := time.ParseDuration("0.05s")
delayMax, _ := time.ParseDuration("0.10s")
rc := regclient.New(
regclient.WithConfigHost(rcHosts...),
regclient.WithRegOpts(reg.WithDelay(delayInit, delayMax)),
)
rTgt1, err := ref.New(tTgtHost + "/tgtrepo1:v1")
if err != nil {
t.Fatalf("failed to parse ref: %v", err)
}
rTgt2, err := ref.New(tTgtHost + "/tgtrepo2:v2")
if err != nil {
t.Fatalf("failed to parse ref: %v", err)
}
rTgt3, err := ref.New(tTgtHost + "/tgtrepo3:v3")
if err != nil {
t.Fatalf("failed to parse ref: %v", err)
}
rb1, err := ref.New("registry.example.org/testrepo:b1")
if err != nil {
t.Fatalf("failed to parse ref: %v", err)
}
rb2, err := ref.New("registry.example.org/testrepo:b2")
if err != nil {
t.Fatalf("failed to parse ref: %v", err)
}
rb3, err := ref.New("registry.example.org/testrepo:b3")
if err != nil {
t.Fatalf("failed to parse ref: %v", err)
}
// r1, err := ref.New("registry.example.org/testrepo:v1")
// if err != nil {
// t.Fatalf("failed to parse ref: %v", err)
// }
// r2, err := ref.New("registry.example.org/testrepo:v2")
// if err != nil {
// t.Fatalf("failed to parse ref: %v", err)
// }
r3, err := ref.New(tTgtHost + "/testrepo:v3")
if err != nil {
t.Fatalf("failed to parse ref: %v", err)
}
m3, err := rc.ManifestGet(ctx, r3)
if err != nil {
t.Fatalf("failed to retrieve v3 ref: %v", err)
}
pAMD, err := platform.Parse("linux/amd64")
if err != nil {
t.Fatalf("failed to parse platform: %v", err)
}
m3DescAmd, err := manifest.GetPlatformDesc(m3, &pAMD)
if err != nil {
t.Fatalf("failed to get amd64 descriptor: %v", err)
}
r3amd, err := ref.New(fmt.Sprintf("%s/testrepo@%s", tTgtHost, m3DescAmd.Digest.String()))
if err != nil {
t.Fatalf("failed to parse platform specific descriptor: %v", err)
}
plat, err := platform.Parse("linux/amd64/v3")
if err != nil {
t.Fatalf("failed to parse the platform: %v", err)
}
// define tests
tests := []struct {
name string
opts []Opts
ref string
wantErr error
wantSame bool // if the resulting image should be unchanged
}{
{
name: "To OCI",
opts: []Opts{
WithManifestToOCI(),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "To OCI Copy",
opts: []Opts{
WithManifestToOCI(),
WithRefTgt(rTgt1),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "To Docker Copy",
opts: []Opts{
WithManifestToDocker(),
WithRefTgt(rTgt1),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Docker To OCI",
opts: []Opts{
WithManifestToOCI(),
},
ref: rTgt1.CommonName(),
},
{
name: "To OCI Referrers",
opts: []Opts{
WithManifestToOCIReferrers(),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Add Annotation",
opts: []Opts{
WithAnnotation("test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Add Annotation All",
opts: []Opts{
WithAnnotation("[*]test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Add Annotation AMD64/ARM64",
opts: []Opts{
WithAnnotation("[linux/amd64,linux/arm64]test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Add Annotation Missing",
opts: []Opts{
WithAnnotation("[linux/i386,linux/s390x]test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Add Annotation Platform Parse Error",
opts: []Opts{
WithAnnotation("[linux/invalid.arch!]test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
wantErr: fmt.Errorf("failed to parse annotation platform linux/invalid.arch!: invalid platform component invalid.arch! in linux/invalid.arch!"),
},
{
name: "Delete Annotation",
opts: []Opts{
WithAnnotation("org.example.version", ""),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Delete Missing Annotation",
opts: []Opts{
WithAnnotation("[*]missing", ""),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Add Base Annotations",
opts: []Opts{
WithAnnotationOCIBase(bRef, bDig),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Add Label",
opts: []Opts{
WithLabel("test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Add Label to All",
opts: []Opts{
WithLabel("[*]test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Add Label AMD64/ARM64",
opts: []Opts{
WithLabel("[linux/amd64,linux/arm64]test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Add Label Missing",
opts: []Opts{
WithLabel("[linux/i386,linux/s390x]test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Add Label Platform Parse Error",
opts: []Opts{
WithLabel("[linux/invalid.arch!]test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
wantErr: fmt.Errorf("failed to parse label platform linux/invalid.arch!: invalid platform component invalid.arch! in linux/invalid.arch!"),
},
{
name: "Delete Label",
opts: []Opts{
WithLabel("version", ""),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Delete Missing Label",
opts: []Opts{
WithLabel("[*]missing", ""),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Label to Annotation",
opts: []Opts{
WithLabelToAnnotation(),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Add Env",
opts: []Opts{
WithEnv("test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Add Env to All",
opts: []Opts{
WithEnv("[*]test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Add Env AMD64/ARM64",
opts: []Opts{
WithEnv("[linux/amd64,linux/arm64]test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Add Env Missing",
opts: []Opts{
WithEnv("[linux/i386,linux/s390x]test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Add Env Platform Parse Error",
opts: []Opts{
WithEnv("[linux/invalid.arch!]test", "hello"),
},
ref: tTgtHost + "/testrepo:v1",
wantErr: fmt.Errorf("failed to parse env platform linux/invalid.arch!: invalid platform component invalid.arch! in linux/invalid.arch!"),
},
{
name: "Change Env",
opts: []Opts{
WithEnv("PATH", "/usr/sbin:/usr/bin:/sbin:/bin"),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Delete Env",
opts: []Opts{
WithEnv("PATH", ""),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Delete Missing Label",
opts: []Opts{
WithLabel("[*]missing", ""),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Unchanged Env",
opts: []Opts{
WithEnv("[linux/amd64]PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Time",
opts: []Opts{
WithConfigTimestampFromLabel("org.opencontainers.image.created"),
},
ref: tTgtHost + "/testrepo:v1",
wantErr: fmt.Errorf("label not found: org.opencontainers.image.created"),
},
{
name: "Config Digest sha256",
opts: []Opts{
WithConfigDigestAlgo(digest.SHA256),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Config Digest sha512 ocidir",
opts: []Opts{
WithConfigDigestAlgo(digest.SHA512),
},
ref: "ocidir://" + tempDir + "/testrepo:v1",
},
{
name: "Config Time",
opts: []Opts{
WithConfigTimestampMax(baseTime),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Config Time Artifact",
opts: []Opts{
WithConfigTimestampMax(baseTime),
},
ref: tTgtHost + "/testrepo:a1",
wantSame: true,
},
{
name: "Config Time Unchanged",
opts: []Opts{
WithConfigTimestampMax(time.Now()),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Config Time Missing Set",
opts: []Opts{
WithConfigTimestamp(OptTime{}),
},
ref: tTgtHost + "/testrepo:v1",
wantErr: fmt.Errorf("WithConfigTimestamp requires a time to set"),
},
{
name: "Config Time Set",
opts: []Opts{
WithConfigTimestamp(OptTime{
Set: baseTime,
}),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Config Time Base Ref",
opts: []Opts{
WithConfigTimestamp(OptTime{
Set: baseTime,
BaseRef: rb1,
}),
},
ref: tTgtHost + "/testrepo:v3",
},
{
name: "Config Time Base Count",
opts: []Opts{
WithConfigTimestamp(OptTime{
Set: baseTime,
BaseLayers: 1,
}),
},
ref: tTgtHost + "/testrepo:v3",
},
{
name: "Config Time Label Missing",
opts: []Opts{
WithConfigTimestamp(OptTime{
FromLabel: "org.opencontainers.image.created",
}),
},
ref: tTgtHost + "/testrepo:v1",
wantErr: fmt.Errorf("label not found: org.opencontainers.image.created"),
},
{
name: "Config Time Artifact",
opts: []Opts{
WithConfigTimestamp(OptTime{
Set: baseTime,
}),
},
ref: tTgtHost + "/testrepo:a1",
wantSame: true,
},
{
name: "Config Time After Unchanged",
opts: []Opts{
WithConfigTimestamp(OptTime{
Set: baseTime,
After: time.Now(),
}),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Config Platform",
opts: []Opts{
WithConfigPlatform(plat),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Digest sha256",
opts: []Opts{
WithDigestAlgo(digest.SHA256),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Digest sha512 ocidir",
opts: []Opts{
WithDigestAlgo(digest.SHA512),
},
ref: "ocidir://" + tempDir + "/testrepo:v1",
},
{
name: "Expose Port",
opts: []Opts{
WithExposeAdd("8080"),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Expose Port Delete Unchanged",
opts: []Opts{
WithExposeRm("8080"),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Expose Port Artifact",
opts: []Opts{
WithExposeAdd("8080"),
},
ref: tTgtHost + "/testrepo:a1",
wantSame: true,
},
{
name: "External layer remove unchanged",
opts: []Opts{
WithExternalURLsRm(),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Layer Add",
opts: []Opts{
WithLayerAddTar(bytes.NewReader(tarBytes), "", nil),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Layer Uncompressed",
opts: []Opts{
WithLayerCompression(archive.CompressNone),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Layer Compressed gzip",
opts: []Opts{
WithLayerCompression(archive.CompressGzip),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Layer Compressed zstd",
opts: []Opts{
WithLayerCompression(archive.CompressZstd),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Layer Digest sha256",
opts: []Opts{
WithLayerDigestAlgo(digest.SHA256),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Layer Digest sha512 ocidir",
opts: []Opts{
WithLayerDigestAlgo(digest.SHA512),
},
ref: "ocidir://" + tempDir + "/testrepo:v1",
},
// TODO(bmitch): enable when registry support is added
// {
// name: "Layer Digest sha512 registry",
// opts: []Opts{
// WithLayerDigestAlgo(digest.SHA512),
// },
// ref: tTgtHost + "/testrepo:v1",
// },
{
name: "Layer Reproducible",
opts: []Opts{
WithLayerReproducible(),
},
ref: tTgtHost + "/testrepo:v3",
wantSame: true,
},
{
name: "Layer Timestamp Missing Label",
opts: []Opts{
WithLayerTimestampFromLabel("missing"),
},
ref: tTgtHost + "/testrepo:v1",
wantErr: fmt.Errorf("label not found: missing"),
},
{
name: "Layer Timestamp",
opts: []Opts{
WithLayerTimestampMax(baseTime),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Layer Timestamp Unchanged",
opts: []Opts{
WithLayerTimestampMax(time.Now()),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Layer Timestamp Artifact",
opts: []Opts{
WithLayerTimestampMax(baseTime),
},
ref: tTgtHost + "/testrepo:a1",
wantSame: true,
},
{
name: "Layer Trim File",
opts: []Opts{
WithLayerStripFile("/layer2"),
},
ref: tTgtHost + "/testrepo:v3",
},
{
name: "Layer Trim File With Local Separator",
opts: []Opts{
WithLayerStripFile(string(filepath.Separator) + "layer2"),
},
ref: tTgtHost + "/testrepo:v3",
},
{
name: "Layer Timestamp Set Missing",
opts: []Opts{
WithLayerTimestamp(OptTime{}),
},
ref: tTgtHost + "/testrepo:v1",
wantErr: fmt.Errorf("WithLayerTimestamp requires a time to set"),
},
{
name: "Layer Timestamp Missing Label",
opts: []Opts{
WithLayerTimestamp(OptTime{
FromLabel: "missing",
}),
},
ref: tTgtHost + "/testrepo:v1",
wantErr: fmt.Errorf("label not found: missing"),
},
{
name: "Layer Timestamp",
opts: []Opts{
WithLayerTimestamp(OptTime{
Set: baseTime,
}),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Layer Timestamp After Unchanged",
opts: []Opts{
WithLayerTimestamp(OptTime{
Set: baseTime,
After: time.Now(),
}),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Layer Timestamp Base Ref",
opts: []Opts{
WithLayerTimestamp(OptTime{
Set: baseTime,
BaseRef: rb1,
}),
},
ref: tTgtHost + "/testrepo:v3",
},
{
name: "Layer Timestamp Base Ref Same",
opts: []Opts{
WithLayerTimestamp(OptTime{
Set: baseTime,
BaseRef: r3,
}),
},
ref: tTgtHost + "/testrepo:v3",
wantSame: true,
},
{
name: "Layer Timestamp Base Count Same",
opts: []Opts{
WithLayerTimestamp(OptTime{
Set: baseTime,
BaseLayers: 99,
}),
},
ref: tTgtHost + "/testrepo:v3",
wantSame: true,
},
{
name: "Layer Timestamp Base Count",
opts: []Opts{
WithLayerTimestamp(OptTime{
Set: baseTime,
BaseLayers: 1,
}),
},
ref: tTgtHost + "/testrepo:v3",
},
{
name: "Layer Timestamp Artifact",
opts: []Opts{
WithLayerTimestamp(OptTime{
Set: baseTime,
After: baseTime,
}),
},
ref: tTgtHost + "/testrepo:a1",
wantSame: true,
},
{
name: "Layer File Tar Time Max",
opts: []Opts{
WithFileTarTimeMax("/dir/layer.tar", baseTime),
},
ref: tTgtHost + "/testrepo:v3",
},
{
name: "Layer File Tar Time Max Unchanged",
opts: []Opts{
WithFileTarTimeMax("/dir/layer.tar", time.Now()),
},
ref: tTgtHost + "/testrepo:v3",
wantSame: true,
},
{
name: "Layer File Tar Time",
opts: []Opts{
WithFileTarTime("/dir/layer.tar", OptTime{
Set: baseTime,
BaseRef: rb1,
}),
},
ref: tTgtHost + "/testrepo:v3",
},
{
name: "Layer File Tar Time After",
opts: []Opts{
WithFileTarTime("/dir/layer.tar", OptTime{
Set: baseTime,
After: baseTime,
}),
},
ref: tTgtHost + "/testrepo:v3",
},
{
name: "Layer File Tar Time Same Base",
opts: []Opts{
WithFileTarTime("/dir/layer.tar", OptTime{
Set: baseTime,
BaseRef: r3,
}),
},
ref: tTgtHost + "/testrepo:v3",
wantSame: true,
},
{
name: "Layer Trim By Created RE",
opts: []Opts{
WithLayerRmCreatedBy(*regexp.MustCompile("^COPY layer2.txt /layer2")),
},
ref: tTgtHost + "/testrepo:v3",
},
{
name: "Layer Remove by index from Index",
opts: []Opts{
WithLayerRmIndex(1),
},
ref: tTgtHost + "/testrepo:v3",
wantErr: fmt.Errorf("remove layer by index requires v2 image manifest"),
},
{
name: "Layer Remove by index",
opts: []Opts{
WithLayerRmIndex(1),
},
ref: r3amd.CommonName(),
},
{
name: "Layer Remove by index missing",
opts: []Opts{
WithLayerRmIndex(10),
},
ref: r3amd.CommonName(),
wantErr: fmt.Errorf("layer not found"),
},
{
name: "Manifest Digest sha256",
opts: []Opts{
WithManifestDigestAlgo(digest.SHA256),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Manifest Digest sha512 ocidir",
opts: []Opts{
WithManifestDigestAlgo(digest.SHA512),
},
ref: "ocidir://" + tempDir + "/testrepo:v1",
},
{
name: "Add volume",
opts: []Opts{
WithVolumeAdd("/new"),
},
ref: tTgtHost + "/testrepo:v2",
},
{
name: "Add volume again",
opts: []Opts{
WithVolumeAdd("/volume"),
},
ref: tTgtHost + "/testrepo:v2",
wantSame: true,
},
{
name: "Rm volume",
opts: []Opts{
WithVolumeRm("/volume"),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Rm volume missing",
opts: []Opts{
WithVolumeRm("/test"),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Data field",
opts: []Opts{
WithData(2048),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Remove data config",
opts: []Opts{
WithData(0),
},
ref: tTgtHost + "/testrepo:a-example",
},
{
name: "Keep data config",
opts: []Opts{
WithData(4),
},
ref: tTgtHost + "/testrepo:a-example",
wantSame: true,
},
{
name: "Remove Command",
opts: []Opts{
WithConfigCmd([]string{}),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Set Command",
opts: []Opts{
WithConfigCmd([]string{"/app", "-v"}),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Set Command Shell",
opts: []Opts{
WithConfigCmd([]string{"/bin/sh", "-c", "/app -v"}),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Remove Entrypoint",
opts: []Opts{
WithConfigEntrypoint([]string{}),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Set Entrypoint",
opts: []Opts{
WithConfigEntrypoint([]string{"/app", "-v"}),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Build arg rm",
opts: []Opts{
WithBuildArgRm("arg_label", regexp.MustCompile("arg_for_[a-z]*")),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Build arg with value rm",
opts: []Opts{
WithBuildArgRm("arg_label", regexp.MustCompile("arg_for_[a-z]*")),
},
ref: tTgtHost + "/testrepo:v2",
},
{
name: "Build arg missing",
opts: []Opts{
WithBuildArgRm("no_such_arg", regexp.MustCompile("no_such_value")),
},
ref: tTgtHost + "/testrepo:v1",
wantSame: true,
},
{
name: "Rebase with annotations v2",
opts: []Opts{
WithRebase(),
WithRefTgt(rTgt2),
},
ref: tTgtHost + "/testrepo:v2",
},
{
name: "Rebase with annotations v3",
opts: []Opts{
WithRebase(),
WithRefTgt(rTgt2),
},
ref: tTgtHost + "/testrepo:v3",
},
{
name: "Rebase missing annotations",
opts: []Opts{
WithRebase(),
},
ref: tTgtHost + "/testrepo:v1",
wantErr: errs.ErrMissingAnnotation,
},
{
name: "Rebase refs",
opts: []Opts{
WithRebaseRefs(rb1, rb2),
WithRefTgt(rTgt2),
},
ref: tTgtHost + "/testrepo:v2",
},
{
name: "Rebase mismatch",
opts: []Opts{
WithRebaseRefs(rb2, rb3),
},
ref: tTgtHost + "/testrepo:v3",
wantErr: errs.ErrMismatch,
},
{
name: "Rebase mismatch",
opts: []Opts{
WithRebaseRefs(rb3, rb2),
},
ref: tTgtHost + "/testrepo:v3",
wantErr: errs.ErrMismatch,
},
{
name: "Rebase and backdate",
opts: []Opts{
WithRefTgt(rTgt3),
WithRebase(),
WithLayerTimestamp(OptTime{
Set: oldTime,
}),
},
ref: tTgtHost + "/testrepo:v2",
},
{
name: "Pull up labels and common annotations v1",
opts: []Opts{
WithManifestToOCIReferrers(),
WithLabelToAnnotation(),
WithAnnotationPromoteCommon(),
},
ref: tTgtHost + "/testrepo:v1",
},
{
name: "Setup Annotations v2",
opts: []Opts{
WithAnnotation("[*]common", "annotation on all images"),
WithAnnotation("[linux/amd64,linux/arm64,linux/arm/v7]child", "annotation on all child images"),
WithAnnotation("[linux/amd64]unique", "amd64"),
WithAnnotation("[linux/arm64]unique", "arm64"),
WithAnnotation("[linux/arm/v7]unique", "arm/v7"),
WithAnnotation("[linux/amd64]amd64only", "value for amd64"),
WithRefTgt(rTgt2),
},
ref: tTgtHost + "/testrepo:v2",
},
{
name: "Pull up common annotations v2",
opts: []Opts{
WithAnnotationPromoteCommon(),
},
ref: rTgt2.CommonName(),
},
}
// run tests
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rSrc, err := ref.New(tt.ref)
if err != nil {
t.Fatalf("failed creating ref: %v", err)
}
// run mod with opts
rMod, err := Apply(ctx, rc, rSrc, tt.opts...)
if tt.wantErr != nil {
if err == nil {
t.Errorf("ModImage did not fail")
} else if !errors.Is(err, tt.wantErr) && err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error, wanted %v, received %v", tt.wantErr, err)
}
return
} else if err != nil {
t.Fatalf("unexpected error: %v", err)
}
mSrc, err := rc.ManifestHead(ctx, rSrc, regclient.WithManifestRequireDigest())
if err != nil {
t.Fatalf("failed to get manifest from src: %v", err)
}
mTgt, err := rc.ManifestHead(ctx, rMod, regclient.WithManifestRequireDigest())
if err != nil {
t.Fatalf("failed to get manifest from mod \"%s\": %v", rMod.CommonName(), err)
}
if tt.wantSame {
if mSrc.GetDescriptor().Digest != mTgt.GetDescriptor().Digest {
t.Errorf("digest changed")
}
} else {
if mSrc.GetDescriptor().Digest == mTgt.GetDescriptor().Digest {
t.Errorf("digest did not change")
}
}
})
}
}