840 lines
34 KiB
Go
840 lines
34 KiB
Go
package manifests
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd/platforms"
|
|
"github.com/containers/common/pkg/manifests"
|
|
cp "github.com/containers/image/v5/copy"
|
|
"github.com/containers/image/v5/directory"
|
|
"github.com/containers/image/v5/manifest"
|
|
"github.com/containers/image/v5/pkg/compression"
|
|
"github.com/containers/image/v5/signature"
|
|
"github.com/containers/image/v5/transports"
|
|
"github.com/containers/image/v5/transports/alltransports"
|
|
"github.com/containers/image/v5/types"
|
|
"github.com/containers/storage"
|
|
"github.com/containers/storage/pkg/unshare"
|
|
digest "github.com/opencontainers/go-digest"
|
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
_ List = &list{}
|
|
|
|
sys = &types.SystemContext{
|
|
SystemRegistriesConfPath: "../../tests/registries.conf",
|
|
SignaturePolicyPath: "../../tests/policy.json",
|
|
}
|
|
amd64sys = &types.SystemContext{ArchitectureChoice: "amd64"}
|
|
arm64sys = &types.SystemContext{ArchitectureChoice: "arm64"}
|
|
ppc64sys = &types.SystemContext{ArchitectureChoice: "ppc64le"}
|
|
)
|
|
|
|
type listPtr = *list
|
|
|
|
const (
|
|
listImageName = "foo"
|
|
|
|
otherListImage = "docker://k8s.gcr.io/pause:3.1"
|
|
otherListDigest = "sha256:f78411e19d84a252e53bff71a4407a5686c46983a2c2eeed83929b888179acea"
|
|
otherListAmd64Digest = "sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610"
|
|
otherListArm64Digest = "sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39"
|
|
otherListPpc64Digest = "sha256:bcf9771c0b505e68c65440474179592ffdfa98790eb54ffbf129969c5e429990"
|
|
otherListInstanceDigest = "docker://k8s.gcr.io/pause@sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39"
|
|
)
|
|
|
|
func TestSaveLoad(t *testing.T) {
|
|
if unshare.IsRootless() {
|
|
t.Skip("Test can only run as root")
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
storeOptions := storage.StoreOptions{
|
|
GraphRoot: filepath.Join(dir, "root"),
|
|
RunRoot: filepath.Join(dir, "runroot"),
|
|
GraphDriverName: "vfs",
|
|
}
|
|
store, err := storage.GetStore(storeOptions)
|
|
assert.NoError(t, err, "error opening store")
|
|
if store == nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
if _, err := store.Shutdown(true); err != nil {
|
|
assert.NoError(t, err, "error closing store")
|
|
}
|
|
}()
|
|
|
|
list := Create()
|
|
list.(listPtr).artifacts.Detached[otherListDigest] = "relative-path-names-are-messy" // set to check that this data is recorded
|
|
assert.NotNil(t, list, "Create() returned nil?")
|
|
|
|
image, err := list.SaveToImage(store, "", []string{listImageName}, manifest.DockerV2ListMediaType)
|
|
assert.NoError(t, err, "SaveToImage(1)")
|
|
locker, err := LockerForImage(store, image)
|
|
assert.NoError(t, err, "LockerForImage()")
|
|
locker.Lock()
|
|
defer locker.Unlock()
|
|
imageReused, err := list.SaveToImage(store, image, nil, manifest.DockerV2ListMediaType)
|
|
assert.NoError(t, err, "SaveToImage(2)")
|
|
|
|
_, list, err = LoadFromImage(store, image)
|
|
assert.NoError(t, err, "LoadFromImage(1)")
|
|
assert.NotNilf(t, list, "LoadFromImage(1)")
|
|
_, list, err = LoadFromImage(store, imageReused)
|
|
assert.NoError(t, err, "LoadFromImage(2)")
|
|
assert.NotNilf(t, list, "LoadFromImage(2)")
|
|
_, list, err = LoadFromImage(store, listImageName)
|
|
assert.NoError(t, err, "LoadFromImage(3)")
|
|
assert.NotNilf(t, list, "LoadFromImage(3)")
|
|
|
|
assert.Equal(t, list.(listPtr).artifacts.Detached[otherListDigest], "relative-path-names-are-messy") // check that this data is loaded
|
|
}
|
|
|
|
func TestAddRemove(t *testing.T) {
|
|
if unshare.IsRootless() {
|
|
t.Skip("Test can only run as root")
|
|
}
|
|
ctx := context.Background()
|
|
|
|
ref, err := alltransports.ParseImageName(otherListImage)
|
|
assert.NoError(t, err, "ParseImageName(%q)", otherListImage)
|
|
src, err := ref.NewImageSource(ctx, sys)
|
|
assert.NoError(t, err, "NewImageSource(%q)", otherListImage)
|
|
defer assert.NoError(t, src.Close(), "ImageSource.Close()")
|
|
m, _, err := src.GetManifest(ctx, nil)
|
|
assert.NoError(t, err, "ImageSource.GetManifest()")
|
|
assert.NoError(t, src.Close(), "ImageSource.GetManifest()")
|
|
listDigest, err := manifest.Digest(m)
|
|
assert.NoError(t, err, "manifest.Digest()")
|
|
assert.Equalf(t, listDigest.String(), otherListDigest, "digest for image %q changed?", otherListImage)
|
|
|
|
l, err := manifests.FromBlob(m)
|
|
assert.NoError(t, err, "manifests.FromBlob()")
|
|
assert.NotNilf(t, l, "manifests.FromBlob()")
|
|
assert.Equalf(t, len(l.Instances()), 5, "image %q had an arch added?", otherListImage)
|
|
|
|
list := Create()
|
|
instanceDigest, err := list.Add(ctx, amd64sys, ref, false)
|
|
assert.NoError(t, err, "list.Add(all=false)")
|
|
assert.Equal(t, instanceDigest.String(), otherListAmd64Digest)
|
|
assert.Equalf(t, len(list.Instances()), 1, "too many instances added")
|
|
|
|
list = Create()
|
|
instanceDigest, err = list.Add(ctx, arm64sys, ref, false)
|
|
assert.NoError(t, err, "list.Add(all=false)")
|
|
assert.Equal(t, instanceDigest.String(), otherListArm64Digest)
|
|
assert.Equalf(t, len(list.Instances()), 1, "too many instances added")
|
|
|
|
list = Create()
|
|
instanceDigest, err = list.Add(ctx, ppc64sys, ref, false)
|
|
assert.NoError(t, err, "list.Add(all=false)")
|
|
assert.Equal(t, instanceDigest.String(), otherListPpc64Digest)
|
|
assert.Equalf(t, len(list.Instances()), 1, "too many instances added")
|
|
|
|
_, err = list.Add(ctx, sys, ref, true)
|
|
assert.NoError(t, err, "list.Add(all=true)")
|
|
assert.Equalf(t, len(list.Instances()), 5, "too many instances added")
|
|
|
|
list = Create()
|
|
_, err = list.Add(ctx, sys, ref, true)
|
|
assert.NoError(t, err, "list.Add(all=true)")
|
|
assert.Equalf(t, len(list.Instances()), 5, "too many instances added", otherListImage)
|
|
|
|
for _, instance := range list.Instances() {
|
|
assert.NoErrorf(t, list.Remove(instance), "error removing instance %q", instance)
|
|
}
|
|
assert.Equalf(t, len(list.Instances()), 0, "should have removed all instances")
|
|
|
|
ref, err = alltransports.ParseImageName(otherListInstanceDigest)
|
|
assert.NoError(t, err, "ParseImageName(%q)", otherListInstanceDigest)
|
|
|
|
list = Create()
|
|
_, err = list.Add(ctx, sys, ref, false)
|
|
assert.NoError(t, err, "list.Add(all=false)")
|
|
assert.Equalf(t, len(list.Instances()), 1, "too many instances added", otherListInstanceDigest)
|
|
|
|
list = Create()
|
|
_, err = list.Add(ctx, sys, ref, true)
|
|
assert.NoError(t, err, "list.Add(all=true)")
|
|
assert.Equalf(t, len(list.Instances()), 1, "too many instances added", otherListInstanceDigest)
|
|
}
|
|
|
|
func TestAddArtifact(t *testing.T) {
|
|
if unshare.IsRootless() {
|
|
t.Skip("Test can only run as root")
|
|
}
|
|
ctx := context.Background()
|
|
dir := t.TempDir()
|
|
storeOptions := storage.StoreOptions{
|
|
GraphRoot: filepath.Join(dir, "root"),
|
|
RunRoot: filepath.Join(dir, "runroot"),
|
|
GraphDriverName: "vfs",
|
|
}
|
|
emptyConfigFile := filepath.Join(dir, "empty.json")
|
|
err := os.WriteFile(emptyConfigFile, []byte("{}"), 0o600)
|
|
assert.NoError(t, err, "error creating a mostly-empty file")
|
|
store, err := storage.GetStore(storeOptions)
|
|
assert.NoError(t, err, "error opening store")
|
|
if store == nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
if _, err := store.Shutdown(true); err != nil {
|
|
assert.NoError(t, err, "error closing store")
|
|
}
|
|
}()
|
|
subjectImageName := "oci-archive:" + filepath.Join("..", "testdata", "oci-name-only.tar.gz")
|
|
subjectReference, err := alltransports.ParseImageName(subjectImageName)
|
|
require.NoError(t, err)
|
|
testCombination := func(t *testing.T, file, fileMediaType string, manifestArtifactType *string, platform v1.Platform, configDescriptor *v1.Descriptor, configFile string, layerMediaType *string, annotations map[string]string, subjectReference types.ImageReference, excludeTitles bool) {
|
|
subjectName := "<nil>"
|
|
if subjectReference != nil {
|
|
subjectName = transports.ImageName(subjectReference)
|
|
}
|
|
configMediaType := "<nil>"
|
|
if configDescriptor != nil {
|
|
configMediaType = configDescriptor.MediaType
|
|
}
|
|
var platformStr string
|
|
if platform.OS != "" && platform.Architecture != "" {
|
|
platformStr = platforms.Format(platform)
|
|
}
|
|
manifestArtifactTypeStr := "<nil>"
|
|
if manifestArtifactType != nil {
|
|
manifestArtifactTypeStr = *manifestArtifactType
|
|
}
|
|
layerMediaTypeStr := "<nil>"
|
|
if layerMediaType != nil {
|
|
layerMediaTypeStr = *layerMediaType
|
|
}
|
|
annotationsStr := "<nil>"
|
|
if annotations != nil {
|
|
var annotationsSlice []string
|
|
for k, v := range annotations {
|
|
annotationsSlice = append(annotationsSlice, fmt.Sprintf("%q=%q", k, v))
|
|
}
|
|
sort.Strings(annotationsSlice)
|
|
annotationsStr = "{" + strings.Join(annotationsSlice, ",") + "}"
|
|
}
|
|
fileBasename := ""
|
|
if file != "" {
|
|
fileBasename = filepath.Base(file)
|
|
}
|
|
desc := fmt.Sprint("file=", fileBasename, ",fileMediaType=", fileMediaType, ",manifestArtifactType=", manifestArtifactTypeStr, ",platform=", platformStr, ",configMediaType=", configMediaType, ",configFile=", configFile, ",layerMediaType=", layerMediaTypeStr, ",annotations=", annotationsStr, ",subject=", subjectName, ",excludeTitles=", excludeTitles)
|
|
t.Run(desc, func(t *testing.T) {
|
|
// create the new index and add the file to it
|
|
options := AddArtifactOptions{
|
|
ManifestArtifactType: manifestArtifactType,
|
|
Platform: platform,
|
|
ConfigDescriptor: configDescriptor,
|
|
ConfigFile: configFile,
|
|
LayerMediaType: layerMediaType,
|
|
Annotations: annotations,
|
|
SubjectReference: subjectReference,
|
|
ExcludeTitles: excludeTitles,
|
|
}
|
|
list := Create()
|
|
var instanceDigest digest.Digest
|
|
if file != "" {
|
|
instanceDigest, err = list.AddArtifact(ctx, sys, options, file)
|
|
} else {
|
|
instanceDigest, err = list.AddArtifact(ctx, sys, options)
|
|
}
|
|
assert.NoErrorf(t, err, "list.AddArtifact(%#v)", options)
|
|
assert.Equal(t, 1, len(list.Instances()), "too many instances added")
|
|
// have to save it before we can create a reference to it
|
|
_, err = list.SaveToImage(store, "", nil, "")
|
|
require.NoError(t, err)
|
|
// get ready to copy it, sort of
|
|
ref, err := list.Reference(store, cp.CopyAllImages, nil)
|
|
require.NoError(t, err)
|
|
// fetch the manifest for the artifact that we just added to the index
|
|
src, err := ref.NewImageSource(ctx, &types.SystemContext{})
|
|
require.NoError(t, err)
|
|
defer src.Close()
|
|
manifestBytes, manifestType, err := src.GetManifest(ctx, &instanceDigest)
|
|
require.NoError(t, err)
|
|
// decode the artifact manifest
|
|
var m v1.Manifest
|
|
if annotations != nil {
|
|
m.Annotations = make(map[string]string)
|
|
}
|
|
err = json.Unmarshal(manifestBytes, &m)
|
|
require.NoError(t, err)
|
|
// check that the artifact manifest looks right
|
|
assert.Equal(t, v1.MediaTypeImageManifest, manifestType)
|
|
assert.Equal(t, v1.MediaTypeImageManifest, m.MediaType)
|
|
expectedManifestArtifactType := "application/vnd.unknown.artifact.v1"
|
|
if manifestArtifactType != nil {
|
|
expectedManifestArtifactType = *manifestArtifactType
|
|
}
|
|
assert.Equal(t, expectedManifestArtifactType, m.ArtifactType)
|
|
// check the config blob info
|
|
configFileSize := v1.DescriptorEmptyJSON.Size
|
|
configFileDigest := v1.DescriptorEmptyJSON.Digest
|
|
if configFile != "" {
|
|
f, err := os.Open(configFile)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { assert.NoError(t, f.Close()) })
|
|
st, err := f.Stat()
|
|
require.NoError(t, err)
|
|
configFileSize = st.Size()
|
|
digester := digest.Canonical.Digester()
|
|
_, err = io.Copy(digester.Hash(), f)
|
|
require.NoError(t, err)
|
|
configFileDigest = digester.Digest()
|
|
}
|
|
switch {
|
|
case configDescriptor != nil && configFile != "":
|
|
assert.Equal(t, configDescriptor.MediaType, m.Config.MediaType, "did not record expected media type for config with file")
|
|
assert.Equal(t, configFileDigest, m.Config.Digest, "did not record expected digest for config with file")
|
|
assert.Equal(t, configFileSize, m.Config.Size, "did not record expected size for config with file")
|
|
case configFile != "":
|
|
assert.Equal(t, v1.MediaTypeImageConfig, m.Config.MediaType, "did not record expected media type for config file")
|
|
assert.Equal(t, configFileDigest, m.Config.Digest, "did not record expected digest for config with file")
|
|
assert.Equal(t, configFileSize, m.Config.Size, "did not record expected size for config with file")
|
|
case configDescriptor != nil:
|
|
assert.Equal(t, configDescriptor.MediaType, m.Config.MediaType, "did not record expected mediaType for empty config")
|
|
assert.Equal(t, configDescriptor.Digest, m.Config.Digest, "did not record expected digest for empty config")
|
|
assert.Equal(t, configDescriptor.Size, m.Config.Size, "did not record expected digest for empty config")
|
|
default:
|
|
assert.Equal(t, v1.DescriptorEmptyJSON.MediaType, m.Config.MediaType, "did not record expected mediaType for empty config and no config file")
|
|
assert.Equal(t, v1.DescriptorEmptyJSON.Digest, m.Config.Digest, "did not record expected digest for empty config and no config file")
|
|
assert.Equal(t, v1.DescriptorEmptyJSON.Size, m.Config.Size, "did not record expected digest for empty config and no config file")
|
|
}
|
|
// if we had a file, it should be there as the "layer", otherwise it should be the empty descriptor
|
|
assert.Equal(t, 1, len(m.Layers), "expected only one layer")
|
|
if file == "" {
|
|
assert.Equal(t, v1.DescriptorEmptyJSON.MediaType, m.Layers[0].MediaType, "did not record empty JSON as layer")
|
|
assert.Equal(t, v1.DescriptorEmptyJSON.Digest, m.Layers[0].Digest, "did not record empty JSON as layer")
|
|
assert.Equal(t, v1.DescriptorEmptyJSON.Size, m.Layers[0].Size, "did not record empty JSON as layer")
|
|
} else {
|
|
// we need to have preserved its size
|
|
st, err := os.Stat(file)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, st.Size(), m.Layers[0].Size, "did not record size of file")
|
|
// did we set the type correctly?
|
|
expectedLayerMediaType := fileMediaType
|
|
if layerMediaType != nil {
|
|
expectedLayerMediaType = *layerMediaType
|
|
}
|
|
assert.Equal(t, expectedLayerMediaType, m.Layers[0].MediaType, "recorded MediaType for layer was wrong")
|
|
// did we set the digest correctly?
|
|
f, err := os.Open(file)
|
|
require.NoError(t, err)
|
|
defer f.Close()
|
|
digester := m.Layers[0].Digest.Algorithm().Digester()
|
|
_, err = io.Copy(digester.Hash(), f)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, digester.Digest().String(), m.Layers[0].Digest.String(), "recorded digest was wrong")
|
|
// did we add that annotation?
|
|
if excludeTitles && file != "" {
|
|
assert.Nil(t, m.Layers[0].Annotations, "expected no layer annotations")
|
|
} else {
|
|
assert.Equal(t, 1, len(m.Layers[0].Annotations), "expected a layer annotation")
|
|
assert.Equal(t, fileBasename, m.Layers[0].Annotations[v1.AnnotationTitle], "expected a title annotation")
|
|
}
|
|
}
|
|
// did we set the annotations?
|
|
assert.EqualValues(t, annotations, m.Annotations, "recorded annotations were wrong")
|
|
if subjectReference != nil {
|
|
// did we set the subject right?
|
|
subject, err := subjectReference.NewImageSource(ctx, &types.SystemContext{})
|
|
require.NoError(t, err)
|
|
defer subject.Close()
|
|
subjectManifestBytes, subjectManifestType, err := subject.GetManifest(ctx, nil)
|
|
require.NoError(t, err)
|
|
subjectManifestDigest, err := manifest.Digest(subjectManifestBytes)
|
|
require.NoError(t, err)
|
|
var s v1.Manifest
|
|
err = json.Unmarshal(subjectManifestBytes, &s)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, m.Subject.Digest, subjectManifestDigest)
|
|
assert.Equal(t, m.Subject.MediaType, subjectManifestType)
|
|
assert.Equal(t, int64(len(subjectManifestBytes)), m.Subject.Size)
|
|
}
|
|
})
|
|
}
|
|
for file, fileMediaType := range map[string]string{
|
|
"": v1.DescriptorEmptyJSON.MediaType,
|
|
filepath.Join("..", "testdata", "containers.conf"): "text/plain",
|
|
filepath.Join("..", "testdata", "oci-name-only.tar.gz"): "application/gzip",
|
|
filepath.Join("..", "..", "logos", "containers.png"): "image/png",
|
|
} {
|
|
defaultManifestArtifactType := "application/vnd.unknown.artifact.v1"
|
|
manifestArtifactType := &defaultManifestArtifactType
|
|
platform := v1.Platform{OS: runtime.GOOS, Architecture: runtime.GOARCH}
|
|
configDescriptor := &v1.DescriptorEmptyJSON
|
|
configFile := ""
|
|
emptyString := ""
|
|
layerMediaType := &emptyString
|
|
annotations := make(map[string]string)
|
|
excludeTitles := false
|
|
for _, manifestArtifactType := range []string{"(nil)", "", "application/vnd.unknown.artifact.v1"} {
|
|
manifestArtifactType := &manifestArtifactType
|
|
if *manifestArtifactType == "(nil)" {
|
|
manifestArtifactType = nil
|
|
}
|
|
testCombination(t, file, fileMediaType, manifestArtifactType, platform, configDescriptor, configFile, layerMediaType, annotations, subjectReference, excludeTitles)
|
|
}
|
|
for _, platform := range []v1.Platform{
|
|
{},
|
|
{
|
|
OS: runtime.GOOS,
|
|
Architecture: runtime.GOARCH,
|
|
},
|
|
} {
|
|
testCombination(t, file, fileMediaType, manifestArtifactType, platform, configDescriptor, configFile, layerMediaType, annotations, subjectReference, excludeTitles)
|
|
}
|
|
for _, configDescriptor := range []*v1.Descriptor{
|
|
nil,
|
|
{MediaType: "application/x-config", Size: 0, Digest: digest.Canonical.FromString("")},
|
|
{MediaType: v1.MediaTypeImageConfig, Size: 0, Digest: digest.Canonical.FromString("")},
|
|
&v1.DescriptorEmptyJSON,
|
|
} {
|
|
for _, configFile := range []string{
|
|
"",
|
|
emptyConfigFile,
|
|
} {
|
|
testCombination(t, file, fileMediaType, manifestArtifactType, platform, configDescriptor, configFile, layerMediaType, annotations, subjectReference, excludeTitles)
|
|
}
|
|
}
|
|
for _, layerMediaType := range []string{"(nil)", "", "text/plain", "application/octet-stream"} {
|
|
layerMediaType := &layerMediaType
|
|
if *layerMediaType == "(nil)" {
|
|
layerMediaType = nil
|
|
}
|
|
testCombination(t, file, fileMediaType, manifestArtifactType, platform, configDescriptor, configFile, layerMediaType, annotations, subjectReference, excludeTitles)
|
|
}
|
|
for _, annotations := range []map[string]string{
|
|
nil,
|
|
{},
|
|
{
|
|
"annotationA": "valueA",
|
|
},
|
|
{
|
|
"annotationB": "valueB",
|
|
"annotationC": "valueC",
|
|
},
|
|
} {
|
|
testCombination(t, file, fileMediaType, manifestArtifactType, platform, configDescriptor, configFile, layerMediaType, annotations, subjectReference, excludeTitles)
|
|
}
|
|
for _, subjectName := range []string{"", subjectImageName} {
|
|
var subjectReference types.ImageReference
|
|
if subjectName != "" {
|
|
var err error
|
|
subjectReference, err = alltransports.ParseImageName(subjectName)
|
|
require.NoError(t, err)
|
|
}
|
|
testCombination(t, file, fileMediaType, manifestArtifactType, platform, configDescriptor, configFile, layerMediaType, annotations, subjectReference, excludeTitles)
|
|
}
|
|
for _, excludeTitles := range []bool{false, true} {
|
|
testCombination(t, file, fileMediaType, manifestArtifactType, platform, configDescriptor, configFile, layerMediaType, annotations, subjectReference, excludeTitles)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReference(t *testing.T) {
|
|
if unshare.IsRootless() {
|
|
t.Skip("Test can only run as root")
|
|
}
|
|
ctx := context.Background()
|
|
|
|
dir := t.TempDir()
|
|
storeOptions := storage.StoreOptions{
|
|
GraphRoot: filepath.Join(dir, "root"),
|
|
RunRoot: filepath.Join(dir, "runroot"),
|
|
GraphDriverName: "vfs",
|
|
}
|
|
store, err := storage.GetStore(storeOptions)
|
|
assert.NoError(t, err, "error opening store")
|
|
if store == nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
if _, err := store.Shutdown(true); err != nil {
|
|
assert.NoError(t, err, "error closing store")
|
|
}
|
|
}()
|
|
|
|
policy, err := signature.NewPolicyFromFile(sys.SignaturePolicyPath)
|
|
assert.NoErrorf(t, err, "NewPolicyFromFile()")
|
|
policyContext, err := signature.NewPolicyContext(policy)
|
|
assert.NoErrorf(t, err, "NewPolicyContext()")
|
|
destRef, err := directory.NewReference(filepath.Join(dir, "directory"))
|
|
assert.NoErrorf(t, err, "NewReference()")
|
|
checkCopy := func(ref types.ImageReference, selection cp.ImageListSelection, instances []digest.Digest) error {
|
|
if ref == nil {
|
|
return errors.New("no reference")
|
|
}
|
|
copyOptions := &cp.Options{
|
|
ImageListSelection: selection,
|
|
SourceCtx: sys,
|
|
DestinationCtx: sys,
|
|
Instances: instances,
|
|
}
|
|
// t.Helper()
|
|
_, err := cp.Image(ctx, policyContext, destRef, ref, copyOptions)
|
|
return err
|
|
}
|
|
ref, err := alltransports.ParseImageName(otherListImage)
|
|
assert.NoErrorf(t, err, "ParseImageName(%q)", otherListImage)
|
|
|
|
list := Create()
|
|
_, err = list.Add(ctx, ppc64sys, ref, false)
|
|
assert.NoError(t, err, "list.Add(all=false)")
|
|
|
|
smallJSON := filepath.Join(dir, "small.json")
|
|
err = os.WriteFile(smallJSON, []byte(`{"slice":[1, 2, 3]}`), 0o600)
|
|
assert.NoError(t, err)
|
|
|
|
minimumJSON := filepath.Join(dir, "minimum.json")
|
|
err = os.WriteFile(minimumJSON, []byte("{}"), 0o600)
|
|
assert.NoError(t, err)
|
|
|
|
emptyJSON := filepath.Join(dir, "empty.json")
|
|
err = os.WriteFile(emptyJSON, []byte(""), 0o600)
|
|
assert.NoError(t, err)
|
|
|
|
artifactOptions := AddArtifactOptions{
|
|
ConfigFile: emptyJSON,
|
|
}
|
|
_, err = list.AddArtifact(ctx, &types.SystemContext{}, artifactOptions)
|
|
assert.NoErrorf(t, err, "list.AddArtifact(file=%s)", emptyJSON)
|
|
|
|
artifactOptions = AddArtifactOptions{
|
|
ConfigDescriptor: &v1.DescriptorEmptyJSON,
|
|
}
|
|
minimumArtifactDigest, err := list.AddArtifact(ctx, &types.SystemContext{}, artifactOptions, minimumJSON)
|
|
assert.NoError(t, err, "list.AddArtifact(file=%s)", minimumJSON)
|
|
|
|
artifactOptions = AddArtifactOptions{}
|
|
smallArtifactDigest, err := list.AddArtifact(ctx, &types.SystemContext{}, artifactOptions, smallJSON)
|
|
assert.NoError(t, err, "list.AddArtifact(file=%s)", smallJSON)
|
|
|
|
listRef, err := list.Reference(store, cp.CopyAllImages, nil)
|
|
assert.Error(t, err, "list.Reference(never saved)")
|
|
assert.Nilf(t, listRef, "list.Reference(never saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopyAllImages, nil)
|
|
assert.Error(t, err, "list.Reference(never saved)")
|
|
assert.Nilf(t, listRef, "list.Reference(never saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySystemImage, nil)
|
|
assert.Error(t, err, "list.Reference(never saved)")
|
|
assert.Nilf(t, listRef, "list.Reference(never saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest})
|
|
assert.Error(t, err, "list.Reference(never saved)")
|
|
assert.Nilf(t, listRef, "list.Reference(never saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest})
|
|
assert.Error(t, err, "list.Reference(never saved)")
|
|
assert.Nilf(t, listRef, "list.Reference(never saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest, minimumArtifactDigest})
|
|
assert.Error(t, err, "list.Reference(never saved)")
|
|
assert.Nilf(t, listRef, "list.Reference(never saved)")
|
|
err = checkCopy(listRef, cp.CopySpecificImages, []digest.Digest{minimumArtifactDigest})
|
|
assert.Error(t, err, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest, minimumArtifactDigest, smallArtifactDigest})
|
|
assert.Error(t, err, "list.Reference(never saved)")
|
|
assert.Nilf(t, listRef, "list.Reference(never saved)")
|
|
err = checkCopy(listRef, cp.CopySpecificImages, []digest.Digest{minimumArtifactDigest, smallArtifactDigest})
|
|
assert.Error(t, err, "list.Reference(saved)")
|
|
|
|
_, err = list.SaveToImage(store, "", []string{listImageName}, "")
|
|
assert.NoError(t, err, "SaveToImage")
|
|
|
|
listRef, err = list.Reference(store, cp.CopyAllImages, nil)
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySystemImage, nil)
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, nil)
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest, minimumArtifactDigest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
err = checkCopy(listRef, cp.CopySpecificImages, []digest.Digest{minimumArtifactDigest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest, minimumArtifactDigest, smallArtifactDigest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
err = checkCopy(listRef, cp.CopySpecificImages, []digest.Digest{minimumArtifactDigest, smallArtifactDigest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
|
|
_, err = list.Add(ctx, sys, ref, true)
|
|
assert.NoError(t, err, "list.Add(all=true)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopyAllImages, nil)
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
err = checkCopy(listRef, cp.CopyAllImages, nil)
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySystemImage, nil)
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
err = checkCopy(listRef, cp.CopySystemImage, nil)
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, nil)
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
err = checkCopy(listRef, cp.CopySpecificImages, nil)
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
err = checkCopy(listRef, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
err = checkCopy(listRef, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest, minimumArtifactDigest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
err = checkCopy(listRef, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest, minimumArtifactDigest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
|
|
listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest, minimumArtifactDigest, smallArtifactDigest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
assert.NotNilf(t, listRef, "list.Reference(saved)")
|
|
err = checkCopy(listRef, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest, minimumArtifactDigest, smallArtifactDigest})
|
|
assert.NoError(t, err, "list.Reference(saved)")
|
|
}
|
|
|
|
func TestPushManifest(t *testing.T) {
|
|
if unshare.IsRootless() {
|
|
t.Skip("Test can only run as root")
|
|
}
|
|
ctx := context.Background()
|
|
|
|
dir := t.TempDir()
|
|
storeOptions := storage.StoreOptions{
|
|
GraphRoot: filepath.Join(dir, "root"),
|
|
RunRoot: filepath.Join(dir, "runroot"),
|
|
GraphDriverName: "vfs",
|
|
}
|
|
store, err := storage.GetStore(storeOptions)
|
|
assert.NoError(t, err, "error opening store")
|
|
if store == nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
if _, err := store.Shutdown(true); err != nil {
|
|
assert.NoError(t, err, "error closing store")
|
|
}
|
|
}()
|
|
|
|
destRef, err := alltransports.ParseImageName(fmt.Sprintf("dir:%s", t.TempDir()))
|
|
assert.NoError(t, err, "ParseImageName()")
|
|
|
|
ref, err := alltransports.ParseImageName(otherListImage)
|
|
assert.NoErrorf(t, err, "ParseImageName(%q)", otherListImage)
|
|
|
|
list := Create()
|
|
_, err = list.Add(ctx, sys, ref, true)
|
|
assert.NoError(t, err, "list.Add(all=true)")
|
|
|
|
_, err = list.SaveToImage(store, "", []string{listImageName}, "")
|
|
assert.NoError(t, err, "SaveToImage")
|
|
|
|
options := PushOptions{
|
|
Store: store,
|
|
SystemContext: sys,
|
|
ImageListSelection: cp.CopyAllImages,
|
|
Instances: nil,
|
|
}
|
|
_, _, err = list.Push(ctx, destRef, options)
|
|
assert.NoError(t, err, "list.Push(all)")
|
|
|
|
options.ImageListSelection = cp.CopySystemImage
|
|
_, _, err = list.Push(ctx, destRef, options)
|
|
assert.NoError(t, err, "list.Push(local)")
|
|
|
|
options.ImageListSelection = cp.CopySpecificImages
|
|
_, _, err = list.Push(ctx, destRef, options)
|
|
assert.NoError(t, err, "list.Push(none specified)")
|
|
|
|
options.Instances = []digest.Digest{otherListAmd64Digest}
|
|
_, _, err = list.Push(ctx, destRef, options)
|
|
assert.NoError(t, err, "list.Push(one specified)")
|
|
|
|
options.Instances = append(options.Instances, otherListArm64Digest)
|
|
_, _, err = list.Push(ctx, destRef, options)
|
|
assert.NoError(t, err, "list.Push(two specified)")
|
|
|
|
options.Instances = append(options.Instances, otherListPpc64Digest)
|
|
_, _, err = list.Push(ctx, destRef, options)
|
|
assert.NoError(t, err, "list.Push(three specified)")
|
|
|
|
options.Instances = append(options.Instances, otherListDigest)
|
|
_, _, err = list.Push(ctx, destRef, options)
|
|
assert.NoError(t, err, "list.Push(four specified)")
|
|
|
|
bogusDestRef, err := alltransports.ParseImageName("docker://localhost/bogus/dest:latest")
|
|
assert.NoErrorf(t, err, "ParseImageName()")
|
|
|
|
var logBuffer bytes.Buffer
|
|
logBuffer = bytes.Buffer{}
|
|
logrus.SetOutput(&logBuffer)
|
|
maxRetry := uint(5)
|
|
delay := 3 * time.Second
|
|
options.MaxRetries = &maxRetry
|
|
_, _, err = list.Push(ctx, bogusDestRef, options)
|
|
assert.Error(t, err)
|
|
logString := logBuffer.String()
|
|
// Must show warning where libimage is going to retry 5 times with 1s delay
|
|
assert.Contains(t, logString, "Failed, retrying in 1s ... (1/5)", "warning not matched")
|
|
|
|
logBuffer = bytes.Buffer{}
|
|
logrus.SetOutput(&logBuffer)
|
|
options.RetryDelay = &delay
|
|
_, _, err = list.Push(ctx, bogusDestRef, options)
|
|
assert.Error(t, err)
|
|
logString = logBuffer.String()
|
|
// Must show warning where libimage is going to retry 5 times with 3s delay
|
|
assert.Contains(t, logString, "Failed, retrying in 3s ... (1/5)", "warning not matched")
|
|
|
|
options.AddCompression = []string{"zstd"}
|
|
options.ImageListSelection = cp.CopyAllImages
|
|
_, _, err = list.Push(ctx, destRef, options)
|
|
assert.NoError(t, err, "list.Push(with replication for zstd specified)")
|
|
|
|
options.ForceCompressionFormat = true
|
|
options.ImageListSelection = cp.CopyAllImages
|
|
options.SystemContext.CompressionFormat = &compression.Gzip
|
|
_, _, err = list.Push(ctx, destRef, options)
|
|
assert.NoError(t, err, "list.Push(with ForceCompressionFormat: true)")
|
|
}
|
|
|
|
func TestInstanceByImageAndFiles(t *testing.T) {
|
|
if unshare.IsRootless() {
|
|
t.Skip("Test can only run as root")
|
|
}
|
|
ctx := context.Background()
|
|
|
|
dir := t.TempDir()
|
|
storeOptions := storage.StoreOptions{
|
|
GraphRoot: filepath.Join(dir, "root"),
|
|
RunRoot: filepath.Join(dir, "runroot"),
|
|
GraphDriverName: "vfs",
|
|
}
|
|
store, err := storage.GetStore(storeOptions)
|
|
assert.NoError(t, err, "error opening store")
|
|
if store == nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
if _, err := store.Shutdown(true); err != nil {
|
|
assert.NoError(t, err, "error closing store")
|
|
}
|
|
}()
|
|
|
|
cconfig := filepath.Join("..", "testdata", "containers.conf")
|
|
absCconfig, err := filepath.Abs(cconfig)
|
|
assert.NoError(t, err)
|
|
gzipped := filepath.Join("..", "testdata", "oci-name-only.tar.gz")
|
|
absGzipped, err := filepath.Abs(gzipped)
|
|
assert.NoError(t, err)
|
|
pngfile := filepath.Join("..", "..", "logos", "containers.png")
|
|
absPngfile, err := filepath.Abs(pngfile)
|
|
assert.NoError(t, err)
|
|
|
|
list := Create()
|
|
options := AddArtifactOptions{}
|
|
firstInstanceDigest, err := list.AddArtifact(ctx, sys, options, cconfig, gzipped)
|
|
assert.NoError(t, err)
|
|
secondInstanceDigest, err := list.AddArtifact(ctx, sys, options, pngfile)
|
|
assert.NoError(t, err)
|
|
|
|
candidate, err := list.InstanceByFile(cconfig)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, firstInstanceDigest, candidate)
|
|
candidate, err = list.InstanceByFile(gzipped)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, firstInstanceDigest, candidate)
|
|
|
|
firstFiles, err := list.Files(firstInstanceDigest)
|
|
assert.NoError(t, err)
|
|
assert.ElementsMatch(t, []string{absCconfig, absGzipped}, firstFiles)
|
|
|
|
candidate, err = list.InstanceByFile(pngfile)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, secondInstanceDigest, candidate)
|
|
|
|
secondFiles, err := list.Files(secondInstanceDigest)
|
|
assert.NoError(t, err)
|
|
assert.ElementsMatch(t, []string{absPngfile}, secondFiles)
|
|
|
|
_, err = list.InstanceByFile("ha ha, fooled you")
|
|
assert.Error(t, err)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
|
|
otherDigest, err := digest.Parse(otherListDigest)
|
|
assert.NoError(t, err)
|
|
noFiles, err := list.Files(otherDigest)
|
|
assert.NoError(t, err)
|
|
assert.ElementsMatch(t, []string{}, noFiles)
|
|
}
|
|
|
|
// TestAddIndexOfArtifacts ensures that we don't fail to preserve artifactType
|
|
// fields in artifact manifests when added from one list to another.
|
|
func TestAddIndexOfArtifacts(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
absPath, err := filepath.Abs(filepath.Join("..", "..", "pkg", "manifests", "testdata", "artifacts", "index"))
|
|
require.NoError(t, err)
|
|
rawPath := "oci:" + absPath
|
|
ref, err := alltransports.ParseImageName(rawPath)
|
|
require.NoErrorf(t, err, "ParseImageName(%q)", rawPath)
|
|
|
|
cookedList := Create()
|
|
_, err = cookedList.Add(ctx, sys, ref, true)
|
|
assert.NoError(t, err, "list.Add()")
|
|
|
|
cooked := cookedList.OCIv1()
|
|
for _, instance := range cooked.Manifests {
|
|
assert.NotEmpty(t, instance.ArtifactType, "lost the artifactType field")
|
|
}
|
|
}
|