169 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Go
		
	
	
	
| package buildah
 | |
| 
 | |
| import (
 | |
| 	"archive/tar"
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	cp "github.com/containers/image/v5/copy"
 | |
| 	"github.com/containers/image/v5/signature"
 | |
| 	imageStorage "github.com/containers/image/v5/storage"
 | |
| 	"github.com/containers/image/v5/transports/alltransports"
 | |
| 	"github.com/containers/storage"
 | |
| 	storageTypes "github.com/containers/storage/types"
 | |
| 	digest "github.com/opencontainers/go-digest"
 | |
| 	ispec "github.com/opencontainers/image-spec/specs-go"
 | |
| 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| type testRetryCopyImageWrappedStore struct {
 | |
| 	phantomImageID string
 | |
| 	storage.Store
 | |
| }
 | |
| 
 | |
| func (ts *testRetryCopyImageWrappedStore) CreateImage(id string, names []string, layer, metadata string, options *storage.ImageOptions) (*storage.Image, error) {
 | |
| 	if id == ts.phantomImageID {
 | |
| 		if img, err := ts.Store.Image(id); img != nil && err == nil {
 | |
| 			// i'm another thread somewhere
 | |
| 			if _, err := ts.Store.DeleteImage(id, true); err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return ts.Store.CreateImage(id, names, layer, metadata, options)
 | |
| }
 | |
| 
 | |
| func TestRetryCopyImage(t *testing.T) {
 | |
| 	ctx := context.TODO()
 | |
| 
 | |
| 	graphDriverName := os.Getenv("STORAGE_DRIVER")
 | |
| 	if graphDriverName == "" {
 | |
| 		graphDriverName = "vfs"
 | |
| 	}
 | |
| 	store, err := storage.GetStore(storageTypes.StoreOptions{
 | |
| 		RunRoot:         t.TempDir(),
 | |
| 		GraphRoot:       t.TempDir(),
 | |
| 		GraphDriverName: graphDriverName,
 | |
| 	})
 | |
| 	require.NoError(t, err, "initializing storage")
 | |
| 	t.Cleanup(func() { _, err := store.Shutdown(true); assert.NoError(t, err) })
 | |
| 
 | |
| 	// construct an "image" that can be pulled into local storage
 | |
| 	var layerBuffer bytes.Buffer
 | |
| 	tw := tar.NewWriter(&layerBuffer)
 | |
| 	err = tw.WriteHeader(&tar.Header{
 | |
| 		Name:     "rootfile",
 | |
| 		Typeflag: tar.TypeReg,
 | |
| 		Size:     1234,
 | |
| 	})
 | |
| 	require.NoError(t, err, "writing header for archive")
 | |
| 	_, err = tw.Write(make([]byte, 1234))
 | |
| 	require.NoError(t, err, "writing empty file to archive")
 | |
| 	require.NoError(t, tw.Close(), "finishing layer")
 | |
| 	layerDigest := digest.Canonical.FromBytes(layerBuffer.Bytes())
 | |
| 	imageConfig := v1.Image{
 | |
| 		RootFS: v1.RootFS{
 | |
| 			Type:    "layers",
 | |
| 			DiffIDs: []digest.Digest{layerDigest},
 | |
| 		},
 | |
| 	}
 | |
| 	imageConfigBytes, err := json.Marshal(&imageConfig)
 | |
| 	require.NoError(t, err, "marshalling image configuration blob")
 | |
| 	imageConfigDigest := digest.Canonical.FromBytes(imageConfigBytes)
 | |
| 	imageManifest := v1.Manifest{
 | |
| 		Versioned: ispec.Versioned{
 | |
| 			SchemaVersion: 2,
 | |
| 		},
 | |
| 		MediaType: v1.MediaTypeImageManifest,
 | |
| 		Config: v1.Descriptor{
 | |
| 			MediaType: v1.MediaTypeImageConfig,
 | |
| 			Size:      int64(len(imageConfigBytes)),
 | |
| 			Digest:    digest.FromBytes(imageConfigBytes),
 | |
| 		},
 | |
| 		Layers: []v1.Descriptor{
 | |
| 			{
 | |
| 				MediaType: v1.MediaTypeImageLayer,
 | |
| 				Size:      int64(layerBuffer.Len()),
 | |
| 				Digest:    layerDigest,
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 	imageManifestBytes, err := json.Marshal(&imageManifest)
 | |
| 	require.NoError(t, err, "marshalling image manifest")
 | |
| 	imageManifestDigest := digest.Canonical.FromBytes(imageManifestBytes)
 | |
| 
 | |
| 	// write it to an oci layout
 | |
| 	ociDir := t.TempDir()
 | |
| 	blobbyDir := filepath.Join(ociDir, "blobs")
 | |
| 	require.NoError(t, os.Mkdir(blobbyDir, 0o700))
 | |
| 	blobDir := filepath.Join(blobbyDir, layerDigest.Algorithm().String())
 | |
| 	require.NoError(t, os.Mkdir(blobDir, 0o700))
 | |
| 	require.NoError(t, os.WriteFile(filepath.Join(blobDir, layerDigest.Encoded()), layerBuffer.Bytes(), 0o600), "writing layer")
 | |
| 	require.NoError(t, os.WriteFile(filepath.Join(blobDir, imageConfigDigest.Encoded()), imageConfigBytes, 0o600), "writing image config")
 | |
| 	require.NoError(t, os.WriteFile(filepath.Join(blobDir, imageManifestDigest.Encoded()), imageManifestBytes, 0o600), "writing manifest")
 | |
| 	imageIndex := v1.Index{
 | |
| 		Versioned: ispec.Versioned{
 | |
| 			SchemaVersion: 2,
 | |
| 		},
 | |
| 		MediaType: v1.MediaTypeImageIndex,
 | |
| 		Manifests: []v1.Descriptor{
 | |
| 			{
 | |
| 				MediaType: v1.MediaTypeImageManifest,
 | |
| 				Digest:    imageManifestDigest,
 | |
| 				Size:      int64(len(imageManifestBytes)),
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 	imageIndexBytes, err := json.Marshal(&imageIndex)
 | |
| 	require.NoError(t, err, "marshalling image index")
 | |
| 	require.NoError(t, os.WriteFile(filepath.Join(ociDir, v1.ImageIndexFile), imageIndexBytes, 0o600), "writing image index")
 | |
| 	imageLayout := v1.ImageLayout{
 | |
| 		Version: v1.ImageLayoutVersion,
 | |
| 	}
 | |
| 	imageLayoutBytes, err := json.Marshal(&imageLayout)
 | |
| 	require.NoError(t, err, "marshalling image layout")
 | |
| 	require.NoError(t, os.WriteFile(filepath.Join(ociDir, v1.ImageLayoutFile), imageLayoutBytes, 0o600), "writing image layout")
 | |
| 
 | |
| 	// pull the image, twice, just to make sure nothing weird happens
 | |
| 	srcRef, err := alltransports.ParseImageName("oci:" + ociDir)
 | |
| 	require.NoError(t, err, "building reference to image layout")
 | |
| 	destRef, err := imageStorage.Transport.NewStoreReference(store, nil, imageConfigDigest.Encoded())
 | |
| 	require.NoError(t, err, "building reference to image in store")
 | |
| 	policy, err := signature.NewPolicyFromFile("tests/policy.json")
 | |
| 	require.NoError(t, err, "reading signature policy")
 | |
| 	policyContext, err := signature.NewPolicyContext(policy)
 | |
| 	require.NoError(t, err, "building policy context")
 | |
| 	t.Cleanup(func() {
 | |
| 		require.NoError(t, policyContext.Destroy(), "destroying policy context")
 | |
| 	})
 | |
| 	_, err = retryCopyImage(ctx, policyContext, destRef, srcRef, destRef, &cp.Options{}, 3, 1*time.Second)
 | |
| 	require.NoError(t, err, "copying image")
 | |
| 	_, err = retryCopyImage(ctx, policyContext, destRef, srcRef, destRef, &cp.Options{}, 3, 1*time.Second)
 | |
| 	require.NoError(t, err, "copying image")
 | |
| 
 | |
| 	// now make something weird happen
 | |
| 	wrappedStore := &testRetryCopyImageWrappedStore{
 | |
| 		phantomImageID: imageConfigDigest.Encoded(),
 | |
| 		Store:          store,
 | |
| 	}
 | |
| 	wrappedDestRef, err := imageStorage.Transport.NewStoreReference(wrappedStore, nil, imageConfigDigest.Encoded())
 | |
| 	require.NoError(t, err, "building wrapped reference")
 | |
| 
 | |
| 	// copy with retry-on-storage-layer-unknown = false: expect an error
 | |
| 	// (if it succeeds, either the test is broken, or we can remove this
 | |
| 	// case from the retry function)
 | |
| 	_, err = retryCopyImageWithOptions(ctx, policyContext, wrappedDestRef, srcRef, wrappedDestRef, &cp.Options{}, 3, 1*time.Second, false)
 | |
| 	require.ErrorIs(t, err, storage.ErrLayerUnknown, "copying image")
 | |
| 
 | |
| 	// copy with retry-on-storage-layer-unknown = true: expect no error
 | |
| 	_, err = retryCopyImageWithOptions(ctx, policyContext, wrappedDestRef, srcRef, wrappedDestRef, &cp.Options{}, 3, 1*time.Second, true)
 | |
| 	require.NoError(t, err, "copying image")
 | |
| }
 |