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

     1  package cnab
     2  
     3  import (
     4  	"testing"
     5  
     6  	depsv1ext "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v1"
     7  	"get.porter.sh/porter/pkg/portercontext"
     8  	porterschema "get.porter.sh/porter/pkg/schema"
     9  	"github.com/cnabio/cnab-go/bundle"
    10  	"github.com/cnabio/cnab-go/bundle/definition"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func TestExtendedBundle_IsPorterBundle(t *testing.T) {
    16  	t.Run("made by porter", func(t *testing.T) {
    17  		b := NewBundle(bundle.Bundle{
    18  			Custom: map[string]interface{}{
    19  				"sh.porter": struct{}{},
    20  			},
    21  		})
    22  
    23  		assert.True(t, b.IsPorterBundle())
    24  	})
    25  
    26  	t.Run("third party bundle", func(t *testing.T) {
    27  		b := ExtendedBundle{}
    28  
    29  		assert.False(t, b.IsPorterBundle())
    30  	})
    31  }
    32  
    33  func TestExtendedBundle_IsFileType(t *testing.T) {
    34  	stringDef := &definition.Schema{
    35  		Type: "string",
    36  	}
    37  	fileDef := &definition.Schema{
    38  		Type:            "string",
    39  		ContentEncoding: "base64",
    40  	}
    41  	bun := NewBundle(bundle.Bundle{
    42  		RequiredExtensions: []string{
    43  			FileParameterExtensionKey,
    44  		},
    45  		Definitions: definition.Definitions{
    46  			"string": stringDef,
    47  			"file":   fileDef,
    48  		},
    49  		Parameters: map[string]bundle.Parameter{
    50  			"debug": {
    51  				Definition: "string",
    52  				Required:   true,
    53  			},
    54  			"tfstate": {
    55  				Definition: "file",
    56  			},
    57  		},
    58  	})
    59  
    60  	assert.False(t, bun.IsFileType(stringDef), "strings should not be flagged as files")
    61  	assert.True(t, bun.IsFileType(fileDef), "strings+base64 with the file-parameters extension should be categorized as files")
    62  
    63  	// Ensure we honor the custom extension
    64  	bun.RequiredExtensions = nil
    65  	assert.False(t, bun.IsFileType(fileDef), "don't categorize as file type when the extension is missing")
    66  
    67  	// Ensure we work with old bundles before the extension was created
    68  	bun.Custom = map[string]interface{}{
    69  		"sh.porter": struct{}{},
    70  	}
    71  
    72  	assert.True(t, bun.IsFileType(fileDef), "categorize string+base64 in old porter bundles should be categorized as files")
    73  }
    74  
    75  func TestExtendedBundle_IsInternalParameter(t *testing.T) {
    76  	bun := NewBundle(bundle.Bundle{
    77  		Definitions: definition.Definitions{
    78  			"foo": &definition.Schema{
    79  				Type: "string",
    80  			},
    81  			"porter-debug": &definition.Schema{
    82  				Type:    "string",
    83  				Comment: PorterInternal,
    84  			},
    85  		},
    86  		Parameters: map[string]bundle.Parameter{
    87  			"foo": {
    88  				Definition: "foo",
    89  			},
    90  			"baz": {
    91  				Definition: "baz",
    92  			},
    93  			"porter-debug": {
    94  				Definition: "porter-debug",
    95  			},
    96  		},
    97  	})
    98  
    99  	t.Run("empty bundle", func(t *testing.T) {
   100  		b := ExtendedBundle{}
   101  		require.False(t, b.IsInternalParameter("foo"))
   102  	})
   103  
   104  	t.Run("param does not exist", func(t *testing.T) {
   105  		require.False(t, bun.IsInternalParameter("bar"))
   106  	})
   107  
   108  	t.Run("definition does not exist", func(t *testing.T) {
   109  		require.False(t, bun.IsInternalParameter("baz"))
   110  	})
   111  
   112  	t.Run("is not internal", func(t *testing.T) {
   113  		require.False(t, bun.IsInternalParameter("foo"))
   114  	})
   115  
   116  	t.Run("is internal", func(t *testing.T) {
   117  		require.True(t, bun.IsInternalParameter("porter-debug"))
   118  	})
   119  }
   120  
   121  func TestExtendedBundle_IsSensitiveParameter(t *testing.T) {
   122  	sensitive := true
   123  	bun := NewBundle(bundle.Bundle{
   124  		Definitions: definition.Definitions{
   125  			"foo": &definition.Schema{
   126  				Type:      "string",
   127  				WriteOnly: &sensitive,
   128  			},
   129  			"porter-debug": &definition.Schema{
   130  				Type:    "string",
   131  				Comment: PorterInternal,
   132  			},
   133  		},
   134  		Parameters: map[string]bundle.Parameter{
   135  			"foo": {
   136  				Definition: "foo",
   137  			},
   138  			"baz": {
   139  				Definition: "baz",
   140  			},
   141  			"porter-debug": {
   142  				Definition: "porter-debug",
   143  			},
   144  		},
   145  	})
   146  
   147  	t.Run("empty bundle", func(t *testing.T) {
   148  		b := ExtendedBundle{}
   149  		require.False(t, b.IsSensitiveParameter("foo"))
   150  	})
   151  
   152  	t.Run("param does not exist", func(t *testing.T) {
   153  		require.False(t, bun.IsSensitiveParameter("bar"))
   154  	})
   155  
   156  	t.Run("definition does not exist", func(t *testing.T) {
   157  		require.False(t, bun.IsSensitiveParameter("baz"))
   158  	})
   159  
   160  	t.Run("is not sensitive", func(t *testing.T) {
   161  		require.False(t, bun.IsSensitiveParameter("porter-debug"))
   162  	})
   163  
   164  	t.Run("is sensitive", func(t *testing.T) {
   165  		require.True(t, bun.IsSensitiveParameter("foo"))
   166  	})
   167  }
   168  
   169  func TestExtendedBundle_GetReferencedRegistries(t *testing.T) {
   170  	t.Run("bundle image in different registry", func(t *testing.T) {
   171  		// Make sure we are looking at the images and the bundle image
   172  		b := NewBundle(bundle.Bundle{
   173  			InvocationImages: []bundle.InvocationImage{
   174  				{BaseImage: bundle.BaseImage{Image: "docker.io/example/mybuns:abc123"}},
   175  			},
   176  			Images: map[string]bundle.Image{
   177  				"nginx": {BaseImage: bundle.BaseImage{Image: "quay.io/library/nginx:latest"}},
   178  				"redis": {BaseImage: bundle.BaseImage{Image: "quay.io/library/redis:latest"}},
   179  				"helm":  {BaseImage: bundle.BaseImage{Image: "ghcr.io/library/helm:latest"}},
   180  			},
   181  		})
   182  
   183  		regs, err := b.GetReferencedRegistries()
   184  		require.NoError(t, err, "GetReferencedRegistries failed")
   185  		wantRegs := []string{"docker.io", "ghcr.io", "quay.io"}
   186  		require.Equal(t, wantRegs, regs, "unexpected registries identified in the bundle")
   187  	})
   188  
   189  	t.Run("bundle image in same registry", func(t *testing.T) {
   190  		// Make sure that we don't generate duplicate registry entries
   191  		b := NewBundle(bundle.Bundle{
   192  			InvocationImages: []bundle.InvocationImage{
   193  				{BaseImage: bundle.BaseImage{Image: "ghcr.io/example/mybuns:abc123"}},
   194  			},
   195  			Images: map[string]bundle.Image{
   196  				"nginx": {BaseImage: bundle.BaseImage{Image: "quay.io/library/nginx:latest"}},
   197  				"redis": {BaseImage: bundle.BaseImage{Image: "quay.io/library/redis:latest"}},
   198  				"helm":  {BaseImage: bundle.BaseImage{Image: "ghcr.io/library/helm:latest"}},
   199  			},
   200  		})
   201  
   202  		regs, err := b.GetReferencedRegistries()
   203  		require.NoError(t, err, "GetReferencedRegistries failed")
   204  		wantRegs := []string{"ghcr.io", "quay.io"}
   205  		require.Equal(t, wantRegs, regs, "unexpected registries identified in the bundle")
   206  	})
   207  }
   208  
   209  func TestValidate(t *testing.T) {
   210  	testcases := []struct {
   211  		name       string
   212  		version    string
   213  		strategy   porterschema.CheckStrategy
   214  		hasWarning bool
   215  		wantErr    string
   216  	}{
   217  		{name: "older version", strategy: porterschema.CheckStrategyExact, version: "1.0.0"},
   218  		{name: "current version", strategy: porterschema.CheckStrategyExact, version: "1.2.0"},
   219  		{name: "unsupported version", strategy: porterschema.CheckStrategyExact, version: "1.3.0", wantErr: "invalid"},
   220  		{name: "custom version check strategy", strategy: porterschema.CheckStrategyMajor, version: "1.1.1", hasWarning: true, wantErr: "WARNING"},
   221  	}
   222  
   223  	cxt := portercontext.NewTestContext(t)
   224  	defer cxt.Close()
   225  
   226  	for _, tc := range testcases {
   227  		t.Run(tc.name, func(t *testing.T) {
   228  			b := NewBundle(bundle.Bundle{
   229  				SchemaVersion: SchemaVersion(tc.version),
   230  				InvocationImages: []bundle.InvocationImage{
   231  					{BaseImage: bundle.BaseImage{}},
   232  				},
   233  			})
   234  
   235  			err := b.Validate(cxt.Context, tc.strategy)
   236  			if tc.wantErr != "" && !tc.hasWarning {
   237  				require.ErrorContains(t, err, tc.wantErr)
   238  				return
   239  			}
   240  			require.NoError(t, err)
   241  			require.Contains(t, cxt.GetError(), tc.wantErr)
   242  		})
   243  	}
   244  }
   245  
   246  func TestExtendedBundle_ResolveDependencies(t *testing.T) {
   247  	t.Parallel()
   248  
   249  	bun := NewBundle(bundle.Bundle{
   250  		Custom: map[string]interface{}{
   251  			DependenciesV1ExtensionKey: depsv1ext.Dependencies{
   252  				Requires: map[string]depsv1ext.Dependency{
   253  					"mysql": {
   254  						Bundle: "getporter/mysql:5.7",
   255  					},
   256  					"nginx": {
   257  						Bundle: "localhost:5000/nginx:1.19",
   258  					},
   259  				},
   260  			},
   261  		},
   262  	})
   263  
   264  	eb := ExtendedBundle{}
   265  	locks, err := eb.ResolveDependencies(bun)
   266  	require.NoError(t, err)
   267  	require.Len(t, locks, 2)
   268  
   269  	var mysql DependencyLock
   270  	var nginx DependencyLock
   271  	for _, lock := range locks {
   272  		switch lock.Alias {
   273  		case "mysql":
   274  			mysql = lock
   275  		case "nginx":
   276  			nginx = lock
   277  		}
   278  	}
   279  
   280  	assert.Equal(t, "getporter/mysql:5.7", mysql.Reference)
   281  	assert.Equal(t, "localhost:5000/nginx:1.19", nginx.Reference)
   282  }
   283  
   284  func TestExtendedBundle_ResolveVersion(t *testing.T) {
   285  	t.Parallel()
   286  
   287  	testcases := []struct {
   288  		name        string
   289  		dep         depsv1ext.Dependency
   290  		wantVersion string
   291  		wantError   string
   292  	}{
   293  		{name: "pinned version",
   294  			dep:         depsv1ext.Dependency{Bundle: "mysql:5.7"},
   295  			wantVersion: "5.7"},
   296  		{name: "unimplemented range",
   297  			dep:       depsv1ext.Dependency{Bundle: "mysql", Version: &depsv1ext.DependencyVersion{Ranges: []string{"1 - 1.5"}}},
   298  			wantError: "not implemented"},
   299  		{name: "default tag to latest",
   300  			dep:         depsv1ext.Dependency{Bundle: "getporterci/porter-test-only-latest"},
   301  			wantVersion: "latest"},
   302  		{name: "no default tag",
   303  			dep:       depsv1ext.Dependency{Bundle: "getporterci/porter-test-no-default-tag"},
   304  			wantError: "no tag was specified"},
   305  		{name: "default tag to highest semver",
   306  			dep:         depsv1ext.Dependency{Bundle: "getporterci/porter-test-with-versions", Version: &depsv1ext.DependencyVersion{Ranges: nil, AllowPrereleases: true}},
   307  			wantVersion: "v1.3-beta1"},
   308  		{name: "default tag to highest semver, explicitly excluding prereleases",
   309  			dep:         depsv1ext.Dependency{Bundle: "getporterci/porter-test-with-versions", Version: &depsv1ext.DependencyVersion{Ranges: nil, AllowPrereleases: false}},
   310  			wantVersion: "v1.2"},
   311  		{name: "default tag to highest semver, excluding prereleases by default",
   312  			dep:         depsv1ext.Dependency{Bundle: "getporterci/porter-test-with-versions"},
   313  			wantVersion: "v1.2"},
   314  	}
   315  
   316  	for _, tc := range testcases {
   317  		tc := tc
   318  		t.Run(tc.name, func(t *testing.T) {
   319  			t.Parallel()
   320  
   321  			eb := ExtendedBundle{}
   322  			version, err := eb.ResolveVersion("mysql", tc.dep)
   323  			if tc.wantError != "" {
   324  				require.Error(t, err, "ResolveVersion should have returned an error")
   325  				assert.Contains(t, err.Error(), tc.wantError)
   326  			} else {
   327  				require.NoError(t, err, "ResolveVersion should not have returned an error")
   328  
   329  				assert.Equal(t, tc.wantVersion, version.Tag(), "incorrect version resolved")
   330  			}
   331  		})
   332  	}
   333  }