get.porter.sh/porter@v1.3.0/tests/smoke/airgap_test.go (about) 1 //go:build smoke 2 3 package smoke 4 5 import ( 6 "encoding/json" 7 "fmt" 8 "path/filepath" 9 "strings" 10 "testing" 11 12 "get.porter.sh/porter/pkg/yaml" 13 "get.porter.sh/porter/tests/testdata" 14 "get.porter.sh/porter/tests/tester" 15 "github.com/cnabio/cnab-go/bundle/loader" 16 "github.com/cnabio/cnab-go/packager" 17 "github.com/cnabio/cnab-to-oci/relocation" 18 "github.com/stretchr/testify/require" 19 "github.com/uwu-tools/magex/shx" 20 ) 21 22 // Validate that we can move a bundle into an aigraped environment 23 // and that it works without referencing the old environment/images. 24 // This also validates a lot of our insecure/unsecured registry configurations. 25 func TestAirgappedEnvironment(t *testing.T) { 26 testcases := []struct { 27 name string 28 useTLS bool 29 useAlias bool 30 insecure bool 31 }{ 32 // Validate we "just work" with an unsecured registry on localhost, just like docker does, without specifying --insecure-registry 33 {name: "plain http, no alias", useTLS: false, useAlias: false, insecure: false}, 34 // Validate we can connect to plain http when we can't detect that it's loopback/localhost as long as --insecure-registry is specified 35 // We do not support docker's extra automagic where it resolves the host and treats it like localhost. You have to specify --insecure-registry with a custom hostname 36 {name: "plain http, use alias", useTLS: false, useAlias: true, insecure: true}, 37 // Validate that --insecure-registry works with self-signed certificates 38 {name: "untrusted tls, no alias", useTLS: false, useAlias: true, insecure: true}, 39 } 40 for _, tc := range testcases { 41 tc := tc 42 t.Run(tc.name, func(t *testing.T) { 43 insecureFlag := fmt.Sprintf("--insecure-registry=%t", tc.insecure) 44 45 test, err := tester.NewTest(t) 46 defer test.Close() 47 require.NoError(t, err, "test setup failed") 48 test.Chdir(test.TestDir) 49 50 // Start a temporary insecure test registry 51 reg1 := test.StartTestRegistry(tester.TestRegistryOptions{UseTLS: tc.useTLS, UseAlias: tc.useAlias}) 52 t.Cleanup(reg1.Close) 53 54 // Publish referenced image to the insecure registry 55 // This helps test that we can publish a bundle that references images from multiple registries 56 // and that we properly apply --insecure-registry to those registries (and not just the registry to which we are pushing) 57 referencedImg := "carolynvs/whalesayd@sha256:8b92b7269f59e3ed824e811a1ff1ee64f0d44c0218efefada57a4bebc2d7ef6f" 58 localRegRepo := fmt.Sprintf("%s/whalesayd", reg1) 59 localRegRef := localRegRepo + ":latest" 60 require.NoError(t, shx.RunV("docker", "pull", referencedImg)) 61 require.NoError(t, shx.RunV("docker", "tag", referencedImg, localRegRef)) 62 output, err := shx.OutputV("docker", "push", localRegRef) 63 require.NoError(t, err) 64 digest := getDigestFromDockerOutput(test.T, output) 65 localRefWithDigest := fmt.Sprintf("%s@%s", localRegRepo, digest) 66 67 // Start a second insecure test registry 68 reg2 := test.StartTestRegistry(tester.TestRegistryOptions{UseTLS: tc.useTLS, UseAlias: tc.useAlias}) 69 t.Cleanup(reg2.Close) 70 71 // Edit the bundle so that it's referencing the image on the temporary registry 72 // make sure the referenced image is not in local image cache 73 shx.RunV("docker", "rmi", localRegRef) 74 err = shx.Copy(filepath.Join(test.RepoRoot, fmt.Sprintf("tests/testdata/%s/*", testdata.MyBuns)), test.TestDir) 75 require.NoError(t, err, "failed to copy test bundle") 76 test.EditYaml(filepath.Join(test.TestDir, "porter.yaml"), func(yq *yaml.Editor) error { 77 // Remove the bundle's dependencies to simplify installation 78 if err := yq.DeleteNode("dependencies"); err != nil { 79 return err 80 } 81 82 // Reference our copy of the whalesayd image 83 return yq.SetValue("images.whalesayd.repository", fmt.Sprintf("%s/whalesayd", reg1)) 84 }) 85 86 // Build the test bundle separate from publish so we best validate that we aren't pulling referenced images during build anymore 87 test.RequirePorter("build", insecureFlag) 88 89 // Validate that the referenced bundle is not in the local docker cache and that build did not pull it 90 err = shx.RunE("docker", "image", "inspect", localRefWithDigest) 91 if err == nil { 92 t.Fatalf("expected %s to not be in the local docker cache after building the bundle. Build should no longer pull referenced images.", localRefWithDigest) 93 } 94 95 // Publish a test bundle that references the image from the temp registry, and push to another insecure registry 96 _, output = test.RequirePorter("publish", "--registry", reg2.String(), insecureFlag, "--verbosity=debug") 97 98 // Validate that we aren't getting a rebuild since we are building immediately before publish 99 // I am using --verbosity=debug above so that we can see the reason why a rebuild was triggered 100 // which helps with troubleshooting if/when a regression occurs. 101 require.NotContains(t, output, "Building bundle", "expected a rebuild to not happen because we built before publishing") 102 103 // Stop the original registry, this ensures that we are relying 100% on the copy of the bundle in the second registry 104 reg1.Close() 105 106 // 107 // Try out the two ways to move a bundle between registries: 108 // 1. Copy the bundle from one registry to the other directly 109 // 110 origRef := fmt.Sprintf("%s/%s:%s", reg2, testdata.MyBuns, "v0.1.2") 111 newRef := fmt.Sprintf("%s/%s-second:%s", reg2, testdata.MyBuns, "v0.2.0") 112 test.RequirePorter("copy", "--source", origRef, "--destination", newRef, insecureFlag) 113 114 // 115 // 2. Use archive + publish to copy the bundle from one registry to the other 116 // 117 archiveFilePath := filepath.Join(test.TestDir, "archive-test.tgz") 118 test.RequirePorter("archive", archiveFilePath, "--reference", origRef, insecureFlag) 119 relocMap := getRelocationMap(test, archiveFilePath) 120 require.Equal(test.T, fmt.Sprintf("%s/mybuns@sha256:499f71eec2e3bd78f26c268bbf5b2a65f73b96216fac4a89b86b5ebf115527b6", reg2), relocMap[localRefWithDigest], "expected the relocation entry for the image to be the new published location") 121 122 // Publish from the archived bundle to a new repository on the second registry 123 // Specify --force since we are overwriting the tag pushed to during the last copy 124 test.RequirePorter("publish", "--archive", archiveFilePath, "-r", newRef, insecureFlag, "--force") 125 archiveFilePath2 := filepath.Join(test.TestDir, "archive-test2.tgz") 126 127 // Archive from the new location on the second registry 128 test.RequirePorter("archive", archiveFilePath2, "--reference", newRef, insecureFlag) 129 relocMap2 := getRelocationMap(test, archiveFilePath2) 130 require.Equal(test.T, fmt.Sprintf("%s/mybuns-second@sha256:499f71eec2e3bd78f26c268bbf5b2a65f73b96216fac4a89b86b5ebf115527b6", reg2), relocMap2[localRefWithDigest], "expected the relocation entry for the image to be the new published location") 131 132 // Validate that we can pull the bundle from the new location 133 test.RequirePorter("explain", newRef) 134 135 // Validate that we can install from the new location 136 test.ApplyTestBundlePrerequisites() 137 test.RequirePorter("install", "-r", newRef, insecureFlag, "-c=mybuns", "-p=mybuns") 138 }) 139 } 140 } 141 142 func getRelocationMap(test tester.Tester, archiveFilePath string) relocation.ImageRelocationMap { 143 l := loader.NewLoader() 144 imp := packager.NewImporter(archiveFilePath, test.TestDir, l) 145 err := imp.Import() 146 require.NoError(test.T, err, "opening archive failed") 147 148 _, err = test.TestContext.FileSystem.Stat(filepath.Join(test.TestDir, strings.TrimSuffix(filepath.Base(archiveFilePath), ".tgz"), "bundle.json")) 149 require.NoError(test.T, err) 150 relocMapBytes, err := test.TestContext.FileSystem.ReadFile(filepath.Join(test.TestDir, strings.TrimSuffix(filepath.Base(archiveFilePath), ".tgz"), "relocation-mapping.json")) 151 require.NoError(test.T, err) 152 153 // make sure the relocation map contains the expected image 154 relocMap := relocation.ImageRelocationMap{} 155 require.NoError(test.T, json.Unmarshal(relocMapBytes, &relocMap)) 156 return relocMap 157 } 158 159 func getDigestFromDockerOutput(t *testing.T, output string) string { 160 _, after, found := strings.Cut(output, "digest: ") 161 require.True(t, found) 162 results := strings.Split(after, " ") 163 require.Greater(t, len(results), 1) 164 require.Contains(t, results[0], "sha256") 165 166 return results[0] 167 }