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 }