get.porter.sh/porter@v1.3.0/pkg/porter/publish_test.go (about)

     1  package porter
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"runtime"
     7  	"testing"
     8  	"time"
     9  
    10  	"get.porter.sh/porter/pkg"
    11  	"get.porter.sh/porter/pkg/cache"
    12  	"get.porter.sh/porter/pkg/cnab"
    13  	cnabtooci "get.porter.sh/porter/pkg/cnab/cnab-to-oci"
    14  	"get.porter.sh/porter/tests"
    15  	"github.com/cnabio/cnab-go/bundle"
    16  	"github.com/cnabio/cnab-to-oci/relocation"
    17  	"github.com/cnabio/image-relocation/pkg/image"
    18  	"github.com/cnabio/image-relocation/pkg/registry"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  func TestPublish_Validate_PorterYamlExists(t *testing.T) {
    24  	p := NewTestPorter(t)
    25  	defer p.Close()
    26  
    27  	p.TestConfig.TestContext.AddTestFile("testdata/porter.yaml", "porter.yaml")
    28  	opts := PublishOptions{}
    29  	err := opts.Validate(p.Config)
    30  	require.NoError(t, err, "validating should not have failed")
    31  }
    32  
    33  func TestPublish_Validate_PorterYamlDoesNotExist(t *testing.T) {
    34  	p := NewTestPorter(t)
    35  	defer p.Close()
    36  
    37  	opts := PublishOptions{}
    38  	err := opts.Validate(p.Config)
    39  	require.ErrorContains(
    40  		t,
    41  		err,
    42  		"could not find porter.yaml in the current directory",
    43  	)
    44  }
    45  
    46  func TestPublish_Validate_ArchivePath(t *testing.T) {
    47  	p := NewTestPorter(t)
    48  	defer p.Close()
    49  
    50  	opts := PublishOptions{
    51  		ArchiveFile: "mybuns.tgz",
    52  	}
    53  	err := opts.Validate(p.Config)
    54  	assert.ErrorContains(t, err, "file does not exist")
    55  
    56  	require.NoError(t, p.FileSystem.WriteFile("mybuns.tgz", []byte("mybuns"), pkg.FileModeWritable))
    57  	err = opts.Validate(p.Config)
    58  	assert.EqualError(t, err, "must provide a value for --reference of the form REGISTRY/bundle:tag")
    59  
    60  	opts.Reference = "myreg/mybuns:v0.1.0"
    61  	err = opts.Validate(p.Config)
    62  	require.NoError(t, err, "validating should not have failed")
    63  }
    64  
    65  func TestPublish_validateTag(t *testing.T) {
    66  	t.Run("tag is a Docker tag", func(t *testing.T) {
    67  		opts := PublishOptions{
    68  			Tag: "latest",
    69  		}
    70  		err := opts.validateTag()
    71  		assert.NoError(t, err)
    72  	})
    73  
    74  	t.Run("tag is a full bundle reference with '@'", func(t *testing.T) {
    75  		opts := PublishOptions{
    76  			Tag: "myregistry.com/mybuns:v0.1.0",
    77  		}
    78  		err := opts.validateTag()
    79  		assert.EqualError(t, err, "the --tag flag has been updated to designate just the Docker tag portion of the bundle reference; use --reference for the full bundle reference instead")
    80  	})
    81  
    82  	t.Run("tag is a full bundle reference with ':'", func(t *testing.T) {
    83  		opts := PublishOptions{
    84  			Tag: "myregistry.com/mybuns@abcde1234",
    85  		}
    86  		err := opts.validateTag()
    87  		assert.EqualError(t, err, "the --tag flag has been updated to designate just the Docker tag portion of the bundle reference; use --reference for the full bundle reference instead")
    88  	})
    89  }
    90  
    91  func TestPublish_getNewImageNameFromBundleReference(t *testing.T) {
    92  	t.Run("has registry and org", func(t *testing.T) {
    93  		newInvImgName, err := getNewImageNameFromBundleReference("localhost:5000/myorg/apache-installer", "example.com/neworg/apache:v0.1.0")
    94  		require.NoError(t, err, "getNewImageNameFromBundleReference failed")
    95  		assert.Equal(t, "example.com/neworg/apache:porter-83e8daf2fa98c1232fd8477a16eb8d0c", newInvImgName.String())
    96  	})
    97  
    98  	t.Run("has registry and org, bundle tag has subdomain", func(t *testing.T) {
    99  		newInvImgName, err := getNewImageNameFromBundleReference("localhost:5000/myorg/apache-installer", "example.com/neworg/bundles/apache:v0.1.0")
   100  		require.NoError(t, err, "getNewImageNameFromBundleReference failed")
   101  		assert.Equal(t, "example.com/neworg/bundles/apache:porter-83e8daf2fa98c1232fd8477a16eb8d0c", newInvImgName.String())
   102  	})
   103  
   104  	t.Run("has registry, org and subdomain, bundle tag has subdomain", func(t *testing.T) {
   105  		newInvImgName, err := getNewImageNameFromBundleReference("localhost:5000/myorg/myimgs/apache-installer", "example.com/neworg/bundles/apache:v0.1.0")
   106  		require.NoError(t, err, "getNewImageNameFromBundleReference failed")
   107  		assert.Equal(t, "example.com/neworg/bundles/apache:porter-e18bca98afc244c5d7a568be2cf6885f", newInvImgName.String())
   108  	})
   109  
   110  	t.Run("has registry, no org", func(t *testing.T) {
   111  		newInvImgName, err := getNewImageNameFromBundleReference("localhost:5000/apache-installer", "example.com/neworg/apache:v0.1.0")
   112  		require.NoError(t, err, "getNewImageNameFromBundleReference failed")
   113  		assert.Equal(t, "example.com/neworg/apache:porter-2125d4f796f345561b13ec13a1f08e2d", newInvImgName.String())
   114  	})
   115  
   116  	t.Run("no registry, has org", func(t *testing.T) {
   117  		newInvImgName, err := getNewImageNameFromBundleReference("myorg/apache-installer", "example.com/anotherorg/apache:v0.1.0")
   118  		require.NoError(t, err, "getNewImageNameFromBundleReference failed")
   119  		assert.Equal(t, "example.com/anotherorg/apache:porter-05885277937850e552535b74f7fc28a5", newInvImgName.String())
   120  	})
   121  
   122  	t.Run("org repeated in registry name", func(t *testing.T) {
   123  		newInvImgName, err := getNewImageNameFromBundleReference("getporter/whalesayd", "getporter.azurecr.io/neworg/whalegap:v0.1.0")
   124  		require.NoError(t, err, "getNewImageNameFromBundleReference failed")
   125  		assert.Equal(t, "getporter.azurecr.io/neworg/whalegap:porter-5cfeb864c54c7211a83a7d2ec5caaeb1", newInvImgName.String())
   126  	})
   127  
   128  	t.Run("org repeated in image name", func(t *testing.T) {
   129  		newInvImgName, err := getNewImageNameFromBundleReference("getporter/getporter-hello-installer", "test.azurecr.io/neworg/hello:v0.1.0")
   130  		require.NoError(t, err, "getNewImageNameFromBundleReference failed")
   131  		assert.Equal(t, "test.azurecr.io/neworg/hello:porter-5f484237ec91b98a63dd55846fb317ef", newInvImgName.String())
   132  	})
   133  
   134  	t.Run("src has no org, dst has no org", func(t *testing.T) {
   135  		newInvImgName, err := getNewImageNameFromBundleReference("apache", "example.com/apache:v0.1.0")
   136  		require.NoError(t, err, "getNewImageNameFromBundleReference failed")
   137  		assert.Equal(t, "example.com/apache:porter-b6efd606d118d0f62066e31419ff04cc", newInvImgName.String())
   138  	})
   139  
   140  	t.Run("src has no org, dst has org", func(t *testing.T) {
   141  		newInvImgName, err := getNewImageNameFromBundleReference("apache", "example.com/neworg/apache:v0.1.0")
   142  		require.NoError(t, err, "getNewImageNameFromBundleReference failed")
   143  		assert.Equal(t, "example.com/neworg/apache:porter-b6efd606d118d0f62066e31419ff04cc", newInvImgName.String())
   144  	})
   145  
   146  	t.Run("src has registry, dst has no registry (implicit docker.io)", func(t *testing.T) {
   147  		newInvImgName, err := getNewImageNameFromBundleReference("oldregistry.com/apache", "neworg/apache:v0.1.0")
   148  		require.NoError(t, err, "getNewImageNameFromBundleReference failed")
   149  		assert.Equal(t, "docker.io/neworg/apache:porter-e8d04c0fd60dc2f793d2a865b899ca64", newInvImgName.String())
   150  	})
   151  }
   152  
   153  func TestPublish_RelocateImage(t *testing.T) {
   154  	p := NewTestPorter(t)
   155  	defer p.Close()
   156  
   157  	originImg := "myorg/myinvimg"
   158  	tag := "myneworg/mynewbuns"
   159  	digest, err := image.NewDigest("sha256:6b5a28ccbb76f12ce771a23757880c6083234255c5ba191fca1c5db1f71c1687")
   160  	require.NoError(t, err, "should have successfully created a digest")
   161  
   162  	testcases := []struct {
   163  		description   string
   164  		relocationMap relocation.ImageRelocationMap
   165  		layout        registry.Layout
   166  		wantErr       error
   167  	}{
   168  		{description: "has relocation mapping defined", relocationMap: relocation.ImageRelocationMap{"myorg/myinvimg": "private/myinvimg"}, layout: mockRegistryLayout{expectedDigest: digest}},
   169  		{description: "empty relocation map", relocationMap: relocation.ImageRelocationMap{}, layout: mockRegistryLayout{expectedDigest: digest}},
   170  		{description: "failed to update", relocationMap: relocation.ImageRelocationMap{"myorg/myinvimg": "private/myinvimg"}, layout: mockRegistryLayout{hasError: true}, wantErr: errors.New("unable to push updated image")},
   171  	}
   172  
   173  	for _, tc := range testcases {
   174  		tc := tc
   175  		t.Run(tc.description, func(t *testing.T) {
   176  			newMap, err := p.relocateImage(tc.relocationMap, tc.layout, originImg, tag)
   177  			if tc.wantErr != nil {
   178  				require.ErrorContains(t, err, tc.wantErr.Error())
   179  				return
   180  			}
   181  			require.Equal(t, tag+"@sha256:6b5a28ccbb76f12ce771a23757880c6083234255c5ba191fca1c5db1f71c1687", newMap[originImg])
   182  		})
   183  	}
   184  }
   185  
   186  type mockRegistryLayout struct {
   187  	hasError       bool
   188  	expectedDigest image.Digest
   189  }
   190  
   191  func (m mockRegistryLayout) Add(name image.Name) (image.Digest, error) {
   192  	return image.EmptyDigest, nil
   193  }
   194  
   195  func (m mockRegistryLayout) Push(digest image.Digest, name image.Name) error {
   196  	if m.hasError {
   197  		return errors.New("failed to add image")
   198  	}
   199  	return nil
   200  }
   201  
   202  func (m mockRegistryLayout) Find(n image.Name) (image.Digest, error) {
   203  	return m.expectedDigest, nil
   204  }
   205  
   206  func TestPublish_RefreshCachedBundle(t *testing.T) {
   207  	p := NewTestPorter(t)
   208  	defer p.Close()
   209  
   210  	bundleRef := cnab.BundleReference{
   211  		Reference:  cnab.MustParseOCIReference("myreg/mybuns"),
   212  		Definition: cnab.NewBundle(bundle.Bundle{Name: "myreg/mybuns"}),
   213  	}
   214  
   215  	// No-Op; bundle does not yet exist in cache
   216  	err := p.refreshCachedBundle(bundleRef)
   217  	require.NoError(t, err, "should have not errored out if bundle does not yet exist in cache")
   218  
   219  	// Save bundle in cache
   220  	cachedBundle, err := p.Cache.StoreBundle(bundleRef)
   221  	require.NoError(t, err, "should have successfully stored bundle")
   222  
   223  	// Get file mod time
   224  	file, err := p.FileSystem.Stat(cachedBundle.BundlePath)
   225  	require.NoError(t, err)
   226  	origBunPathTime := file.ModTime()
   227  
   228  	if runtime.GOOS == "windows" {
   229  		// see https://github.com/getporter/porter/issues/2858
   230  		time.Sleep(5 * time.Millisecond)
   231  	}
   232  
   233  	// Should refresh cache
   234  	err = p.refreshCachedBundle(bundleRef)
   235  	require.NoError(t, err, "should have successfully updated the cache")
   236  
   237  	// Get file mod time
   238  	file, err = p.FileSystem.Stat(cachedBundle.BundlePath)
   239  	require.NoError(t, err)
   240  	updatedBunPathTime := file.ModTime()
   241  
   242  	// Verify mod times differ
   243  	require.NotEqual(t, updatedBunPathTime, origBunPathTime,
   244  		"bundle.json file should have an updated mod time per cache refresh")
   245  }
   246  
   247  func TestPublish_RefreshCachedBundle_OnlyWarning(t *testing.T) {
   248  	p := NewTestPorter(t)
   249  	defer p.Close()
   250  
   251  	bundleRef := cnab.BundleReference{
   252  		Reference:  cnab.MustParseOCIReference("myreg/mybuns"),
   253  		Definition: cnab.NewBundle(bundle.Bundle{Name: "myreg/mybuns"}),
   254  	}
   255  
   256  	p.TestCache.FindBundleMock = func(ref cnab.OCIReference) (cachedBundle cache.CachedBundle, found bool, err error) {
   257  		// force the bundle to be found
   258  		return cache.CachedBundle{}, true, nil
   259  	}
   260  	p.TestCache.StoreBundleMock = func(bundleRef cnab.BundleReference) (cachedBundle cache.CachedBundle, err error) {
   261  		// sabotage the bundle refresh
   262  		return cache.CachedBundle{}, errors.New("error trying to store bundle")
   263  	}
   264  
   265  	err := p.refreshCachedBundle(bundleRef)
   266  	require.NoError(t, err, "should have not errored out even if cache.StoreBundle does")
   267  
   268  	gotStderr := p.TestConfig.TestContext.GetError()
   269  	require.Equal(t, "warning: unable to update cache for bundle myreg/mybuns: error trying to store bundle\n", gotStderr)
   270  }
   271  
   272  func TestPublish_RewriteImageWithDigest(t *testing.T) {
   273  	// change from our temporary tag for the bundle image to using ONLY the digest
   274  	p := NewTestPorter(t)
   275  	defer p.Close()
   276  
   277  	digestedImg, err := p.rewriteImageWithDigest("example/mybuns:temp-tag", "sha256:6b5a28ccbb76f12ce771a23757880c6083234255c5ba191fca1c5db1f71c1687")
   278  	require.NoError(t, err)
   279  	assert.Equal(t, "example/mybuns@sha256:6b5a28ccbb76f12ce771a23757880c6083234255c5ba191fca1c5db1f71c1687", digestedImg)
   280  }
   281  
   282  func TestPublish_ForceOverwrite(t *testing.T) {
   283  	t.Parallel()
   284  
   285  	testcases := []struct {
   286  		name    string
   287  		exists  bool
   288  		force   bool
   289  		wantErr string
   290  	}{
   291  		{name: "bundle doesn't exist, force not set", exists: false, force: false, wantErr: ""},
   292  		{name: "bundle exists, force not set", exists: true, force: false, wantErr: "already exists in the destination registry"},
   293  		{name: "bundle exists, force set", exists: true, force: true},
   294  	}
   295  
   296  	for _, tc := range testcases {
   297  		tc := tc
   298  
   299  		t.Run(tc.name, func(t *testing.T) {
   300  			t.Parallel()
   301  
   302  			ctx := context.Background()
   303  			p := NewTestPorter(t)
   304  			defer p.Close()
   305  
   306  			// Set up that the destination already exists
   307  			p.TestRegistry.MockGetBundleMetadata = func(ctx context.Context, ref cnab.OCIReference, opts cnabtooci.RegistryOptions) (cnabtooci.BundleMetadata, error) {
   308  				if tc.exists {
   309  					return cnabtooci.BundleMetadata{}, nil
   310  				}
   311  				return cnabtooci.BundleMetadata{}, cnabtooci.ErrNotFound{Reference: ref}
   312  			}
   313  
   314  			p.TestConfig.TestContext.AddTestDirectoryFromRoot("tests/testdata/mybuns", p.BundleDir)
   315  
   316  			opts := PublishOptions{}
   317  			opts.Force = tc.force
   318  
   319  			err := opts.Validate(p.Config)
   320  			require.NoError(t, err)
   321  
   322  			err = p.Publish(ctx, opts)
   323  
   324  			if tc.wantErr == "" {
   325  				require.NoError(t, err, "Publish failed")
   326  			} else {
   327  				tests.RequireErrorContains(t, err, tc.wantErr)
   328  			}
   329  		})
   330  	}
   331  }