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")
							 | 
						||
| 
								 | 
							
								}
							 |