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

     1  package porter
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime"
     9  	"testing"
    10  	"time"
    11  
    12  	"get.porter.sh/porter/pkg/portercontext"
    13  	"get.porter.sh/porter/pkg/printer"
    14  	"get.porter.sh/porter/pkg/storage"
    15  	"get.porter.sh/porter/pkg/test"
    16  	"get.porter.sh/porter/pkg/yaml"
    17  	"get.porter.sh/porter/tests"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  func TestGenerateNoName(t *testing.T) {
    23  	p := NewTestPorter(t)
    24  	defer p.Close()
    25  	ctx := context.Background()
    26  
    27  	p.TestConfig.TestContext.AddTestFile("testdata/bundle.json", "/bundle.json")
    28  
    29  	opts := CredentialOptions{
    30  		Silent: true,
    31  	}
    32  	opts.CNABFile = "/bundle.json"
    33  	err := opts.Validate(ctx, nil, p.Porter)
    34  	require.NoError(t, err, "Validate failed")
    35  
    36  	err = p.GenerateCredentials(ctx, opts)
    37  	require.NoError(t, err, "no error should have existed")
    38  
    39  	creds, err := p.Credentials.GetCredentialSet(ctx, "", "porter-hello")
    40  	require.NoError(t, err, "expected credential to have been generated")
    41  	var zero time.Time
    42  	assert.True(t, zero.Before(creds.Status.Created), "expected Credentials.Created to be set")
    43  	assert.True(t, creds.Status.Created.Equal(creds.Status.Modified), "expected Credentials.Created to be initialized to Credentials.Modified")
    44  }
    45  
    46  func TestGenerateNameProvided(t *testing.T) {
    47  	p := NewTestPorter(t)
    48  	defer p.Close()
    49  	ctx := context.Background()
    50  
    51  	p.TestConfig.TestContext.AddTestFile("testdata/bundle.json", "/bundle.json")
    52  
    53  	opts := CredentialOptions{
    54  		Silent: true,
    55  	}
    56  	opts.Namespace = "dev"
    57  	opts.Name = "kool-kred"
    58  	opts.Labels = []string{"env=dev"}
    59  	opts.CNABFile = "/bundle.json"
    60  	err := opts.Validate(ctx, nil, p.Porter)
    61  	require.NoError(t, err, "Validate failed")
    62  
    63  	err = p.GenerateCredentials(ctx, opts)
    64  	require.NoError(t, err, "no error should have existed")
    65  	creds, err := p.Credentials.GetCredentialSet(ctx, opts.Namespace, "kool-kred")
    66  	require.NoError(t, err, "expected credential to have been generated")
    67  	assert.Equal(t, map[string]string{"env": "dev"}, creds.Labels)
    68  }
    69  
    70  func TestGenerateBadNameProvided(t *testing.T) {
    71  	p := NewTestPorter(t)
    72  	defer p.Close()
    73  	ctx := context.Background()
    74  
    75  	p.TestConfig.TestContext.AddTestFile("testdata/bundle.json", "/bundle.json")
    76  
    77  	opts := CredentialOptions{
    78  		Silent: true,
    79  	}
    80  	opts.Name = "this.isabadname"
    81  	opts.CNABFile = "/bundle.json"
    82  	err := opts.Validate(ctx, nil, p.Porter)
    83  	require.NoError(t, err, "Validate failed")
    84  
    85  	err = p.GenerateCredentials(ctx, opts)
    86  	require.Error(t, err, "name is invalid, we should have had an error")
    87  	_, err = p.Credentials.GetCredentialSet(ctx, "", "this.isabadname")
    88  	require.Error(t, err, "expected credential to not exist")
    89  }
    90  
    91  type CredentialsListTest struct {
    92  	name       string
    93  	format     printer.Format
    94  	wantOutput string
    95  	errorMsg   string
    96  }
    97  
    98  func TestCredentialsList_None(t *testing.T) {
    99  	ctx := context.Background()
   100  
   101  	testcases := []CredentialsListTest{
   102  		{
   103  			name:     "invalid format",
   104  			format:   "wingdings",
   105  			errorMsg: "invalid format: wingdings",
   106  		},
   107  		{
   108  			name:       "json",
   109  			format:     printer.FormatJson,
   110  			wantOutput: "testdata/credentials/list-output.json",
   111  			errorMsg:   "",
   112  		},
   113  		{
   114  			name:       "yaml",
   115  			format:     printer.FormatYaml,
   116  			wantOutput: "testdata/credentials/list-output.yaml",
   117  			errorMsg:   "",
   118  		},
   119  		{
   120  			name:       "plaintext",
   121  			format:     printer.FormatPlaintext,
   122  			wantOutput: "testdata/credentials/list-output.txt",
   123  			errorMsg:   "",
   124  		},
   125  	}
   126  
   127  	for _, tc := range testcases {
   128  		t.Run(tc.name, func(t *testing.T) {
   129  			p := NewTestPorter(t)
   130  			defer p.Close()
   131  
   132  			listOpts := ListOptions{}
   133  			listOpts.Format = tc.format
   134  			err := p.PrintCredentials(ctx, listOpts)
   135  			if tc.errorMsg != "" {
   136  				require.Equal(t, err.Error(), tc.errorMsg)
   137  			} else {
   138  				require.NoError(t, err, "no error should have existed")
   139  				gotOutput := p.TestConfig.TestContext.GetOutput()
   140  				test.CompareGoldenFile(t, tc.wantOutput, gotOutput)
   141  			}
   142  		})
   143  	}
   144  }
   145  
   146  func TestPorter_PrintCredentials(t *testing.T) {
   147  	ctx := context.Background()
   148  
   149  	testcases := []CredentialsListTest{
   150  		{
   151  			name:       "json",
   152  			format:     printer.FormatJson,
   153  			wantOutput: "testdata/credentials/show-output.json",
   154  		},
   155  		{
   156  			name:       "yaml",
   157  			format:     printer.FormatYaml,
   158  			wantOutput: "testdata/credentials/show-output.yaml",
   159  		},
   160  		{
   161  			name:       "plaintext",
   162  			format:     printer.FormatPlaintext,
   163  			wantOutput: "testdata/credentials/show-output.txt",
   164  		},
   165  	}
   166  
   167  	for _, tc := range testcases {
   168  		t.Run(tc.name, func(t *testing.T) {
   169  			p := NewTestPorter(t)
   170  			defer p.Close()
   171  
   172  			p.TestCredentials.AddTestCredentialsDirectory("testdata/test-creds")
   173  
   174  			listOpts := ListOptions{}
   175  			listOpts.Namespace = "dev"
   176  			listOpts.Format = tc.format
   177  			err := p.PrintCredentials(ctx, listOpts)
   178  			require.NoError(t, err)
   179  
   180  			gotOutput := p.TestConfig.TestContext.GetOutput()
   181  			test.CompareGoldenFile(t, tc.wantOutput, gotOutput)
   182  		})
   183  	}
   184  }
   185  
   186  // Test filtering
   187  func TestPorter_ListCredentials(t *testing.T) {
   188  	p := NewTestPorter(t)
   189  	defer p.Close()
   190  
   191  	ctx := context.Background()
   192  	require.NoError(t, p.TestCredentials.InsertCredentialSet(ctx, storage.NewCredentialSet("", "shared-mysql")))
   193  	require.NoError(t, p.TestCredentials.InsertCredentialSet(ctx, storage.NewCredentialSet("dev", "carolyn-wordpress")))
   194  	require.NoError(t, p.TestCredentials.InsertCredentialSet(ctx, storage.NewCredentialSet("dev", "vaughn-wordpress")))
   195  	require.NoError(t, p.TestCredentials.InsertCredentialSet(ctx, storage.NewCredentialSet("test", "staging-wordpress")))
   196  	require.NoError(t, p.TestCredentials.InsertCredentialSet(ctx, storage.NewCredentialSet("test", "iat-wordpress")))
   197  	require.NoError(t, p.TestCredentials.InsertCredentialSet(ctx, storage.NewCredentialSet("test", "shared-mysql")))
   198  
   199  	t.Run("all-namespaces", func(t *testing.T) {
   200  		opts := ListOptions{AllNamespaces: true}
   201  		results, err := p.ListCredentials(ctx, opts)
   202  		require.NoError(t, err)
   203  		assert.Len(t, results, 6)
   204  	})
   205  
   206  	t.Run("local namespace", func(t *testing.T) {
   207  		opts := ListOptions{Namespace: "dev"}
   208  		results, err := p.ListCredentials(ctx, opts)
   209  		require.NoError(t, err)
   210  		assert.Len(t, results, 2)
   211  
   212  		opts = ListOptions{Namespace: "test"}
   213  		results, err = p.ListCredentials(ctx, opts)
   214  		require.NoError(t, err)
   215  		assert.Len(t, results, 3)
   216  	})
   217  
   218  	t.Run("global namespace", func(t *testing.T) {
   219  		opts := ListOptions{Namespace: ""}
   220  		results, err := p.ListCredentials(ctx, opts)
   221  		require.NoError(t, err)
   222  		assert.Len(t, results, 1)
   223  	})
   224  }
   225  
   226  func TestShowCredential_NotFound(t *testing.T) {
   227  	p := NewTestPorter(t)
   228  	defer p.Close()
   229  
   230  	opts := CredentialShowOptions{
   231  		PrintOptions: printer.PrintOptions{
   232  			Format: printer.FormatPlaintext,
   233  		},
   234  		Name: "non-existent-cred",
   235  	}
   236  
   237  	err := p.ShowCredential(context.Background(), opts)
   238  	assert.ErrorIs(t, err, storage.ErrNotFound{})
   239  }
   240  
   241  func TestShowCredential_Found(t *testing.T) {
   242  	type CredentialShowTest struct {
   243  		name               string
   244  		format             printer.Format
   245  		expectedOutputFile string
   246  	}
   247  
   248  	testcases := []CredentialShowTest{
   249  		{
   250  			name:               "json",
   251  			format:             printer.FormatJson,
   252  			expectedOutputFile: "testdata/credentials/kool-kreds.json",
   253  		},
   254  		{
   255  			name:               "yaml",
   256  			format:             printer.FormatYaml,
   257  			expectedOutputFile: "testdata/credentials/kool-kreds.yaml",
   258  		},
   259  		{
   260  			name:               "table",
   261  			format:             printer.FormatPlaintext,
   262  			expectedOutputFile: "testdata/credentials/kool-kreds.txt",
   263  		},
   264  	}
   265  
   266  	for _, tc := range testcases {
   267  		t.Run(tc.name, func(t *testing.T) {
   268  			p := NewTestPorter(t)
   269  			defer p.Close()
   270  
   271  			opts := CredentialShowOptions{
   272  				PrintOptions: printer.PrintOptions{
   273  					Format: tc.format,
   274  				},
   275  				Name:      "kool-kreds",
   276  				Namespace: "dev",
   277  			}
   278  
   279  			p.TestCredentials.AddTestCredentialsDirectory("testdata/test-creds")
   280  
   281  			err := p.ShowCredential(context.Background(), opts)
   282  			require.NoError(t, err, "an error should not have occurred")
   283  			gotOutput := p.TestConfig.TestContext.GetOutput()
   284  			test.CompareGoldenFile(t, tc.expectedOutputFile, gotOutput)
   285  		})
   286  	}
   287  }
   288  
   289  func TestShowCredential_PreserveCase(t *testing.T) {
   290  	opts := CredentialShowOptions{}
   291  	opts.RawFormat = string(printer.FormatPlaintext)
   292  
   293  	err := opts.Validate([]string{"porter-hello"})
   294  	require.NoError(t, err, "Validate failed")
   295  	assert.Equal(t, "porter-hello", opts.Name, "Validate should preserve the credential set name case")
   296  }
   297  
   298  func TestCredentialsEdit(t *testing.T) {
   299  	p := NewTestPorter(t)
   300  	defer p.Close()
   301  
   302  	p.Setenv("SHELL", "bash")
   303  	p.Setenv("EDITOR", "vi")
   304  	p.Setenv(test.ExpectedCommandEnv, "bash -c vi "+filepath.Join(os.TempDir(), "porter-kool-kreds.yaml"))
   305  
   306  	if runtime.GOOS == "windows" {
   307  		p.Setenv("SHELL", "cmd")
   308  		p.Setenv("EDITOR", "notepad")
   309  		p.Setenv(test.ExpectedCommandEnv, "cmd /C notepad "+filepath.Join(os.TempDir(), "porter-kool-kreds.yaml"))
   310  	}
   311  
   312  	opts := CredentialEditOptions{Namespace: "dev", Name: "kool-kreds"}
   313  
   314  	p.TestCredentials.AddTestCredentialsDirectory("testdata/test-creds")
   315  	err := p.EditCredential(context.Background(), opts)
   316  	require.NoError(t, err, "no error should have existed")
   317  }
   318  
   319  func TestCredentialsEditEditorPathWithArgument(t *testing.T) {
   320  	p := NewTestPorter(t)
   321  	defer p.Close()
   322  
   323  	p.Setenv("SHELL", "something")
   324  	p.Setenv("EDITOR", "C:\\Program Files\\Visual Studio Code\\code.exe --wait")
   325  	p.Setenv(test.ExpectedCommandEnv, fmt.Sprintf("something -c C:\\Program Files\\Visual Studio Code\\code.exe --wait %s", filepath.Join(os.TempDir(), "porter-kool-kreds.yaml")))
   326  	if runtime.GOOS == "windows" {
   327  		p.Setenv(test.ExpectedCommandEnv, fmt.Sprintf("something /C C:\\Program Files\\Visual Studio Code\\code.exe --wait %s", filepath.Join(os.TempDir(), "porter-kool-kreds.yaml")))
   328  	}
   329  	opts := CredentialEditOptions{Namespace: "dev", Name: "kool-kreds"}
   330  
   331  	p.TestCredentials.AddTestCredentialsDirectory("testdata/test-creds")
   332  	err := p.EditCredential(context.Background(), opts)
   333  	require.NoError(t, err, "no error should have existed")
   334  }
   335  
   336  func TestCredentialsDelete(t *testing.T) {
   337  	ctx := context.Background()
   338  
   339  	testcases := []struct {
   340  		name       string
   341  		credName   string
   342  		wantStderr string
   343  	}{{
   344  		name:     "delete",
   345  		credName: "kool-kreds",
   346  	}, {
   347  		name:       "error",
   348  		credName:   "noop-kreds",
   349  		wantStderr: "Credential Set not found",
   350  	}}
   351  
   352  	for _, tc := range testcases {
   353  		t.Run(tc.name, func(t *testing.T) {
   354  			p := NewTestPorter(t)
   355  			defer p.Close()
   356  
   357  			p.TestCredentials.AddTestCredentialsDirectory("testdata/test-creds")
   358  
   359  			opts := CredentialDeleteOptions{Namespace: "dev", Name: tc.credName}
   360  			err := p.DeleteCredential(ctx, opts)
   361  			require.NoError(t, err, "no error should have existed")
   362  
   363  			_, err = p.TestCredentials.GetCredentialSet(ctx, "", tc.credName)
   364  			assert.ErrorIs(t, err, storage.ErrNotFound{})
   365  		})
   366  	}
   367  }
   368  
   369  func TestApplyOptions_Validate(t *testing.T) {
   370  	t.Run("no file specified", func(t *testing.T) {
   371  		tc := portercontext.NewTestContext(t)
   372  		opts := ApplyOptions{}
   373  		err := opts.Validate(tc.Context, nil)
   374  		require.EqualError(t, err, "a file argument is required")
   375  	})
   376  
   377  	t.Run("one file specified", func(t *testing.T) {
   378  		tc := portercontext.NewTestContext(t)
   379  		tc.AddTestFileFromRoot("tests/testdata/creds/mybuns.yaml", "mybuns.yaml")
   380  		opts := ApplyOptions{}
   381  		err := opts.Validate(tc.Context, []string{"mybuns.yaml"})
   382  		require.NoError(t, err)
   383  		assert.Equal(t, "mybuns.yaml", opts.File)
   384  	})
   385  
   386  	t.Run("missing file specified", func(t *testing.T) {
   387  		tc := portercontext.NewTestContext(t)
   388  		opts := ApplyOptions{}
   389  		err := opts.Validate(tc.Context, []string{"mybuns.yaml"})
   390  		require.Error(t, err)
   391  		require.Contains(t, err.Error(), "invalid file argument")
   392  	})
   393  
   394  	t.Run("two files specified", func(t *testing.T) {
   395  		tc := portercontext.NewTestContext(t)
   396  		opts := ApplyOptions{}
   397  		err := opts.Validate(tc.Context, []string{"mybuns.yaml", "yourbuns.yaml"})
   398  		require.Error(t, err)
   399  		require.Contains(t, err.Error(), "only one file argument may be specified")
   400  	})
   401  
   402  }
   403  
   404  func TestCredentialsCreateOptions_Validate(t *testing.T) {
   405  	testcases := []struct {
   406  		name       string
   407  		args       []string
   408  		outputType string
   409  		wantErr    string
   410  	}{
   411  		{
   412  			name:       "no fileName defined",
   413  			args:       []string{},
   414  			outputType: "",
   415  			wantErr:    "",
   416  		},
   417  		{
   418  			name:       "two positional arguments",
   419  			args:       []string{"credential-set1", "credential-set2"},
   420  			outputType: "",
   421  			wantErr:    "only one positional argument may be specified",
   422  		},
   423  		{
   424  			name:       "no file format defined from file extension or output flag",
   425  			args:       []string{"credential-set"},
   426  			outputType: "",
   427  			wantErr:    "could not detect the file format from the file extension (.txt). Specify the format with --output",
   428  		},
   429  		{
   430  			name:       "different file format",
   431  			args:       []string{"credential-set.json"},
   432  			outputType: "yaml",
   433  			wantErr:    "",
   434  		},
   435  		{
   436  			name:       "format from output flag",
   437  			args:       []string{"creds"},
   438  			outputType: "json",
   439  			wantErr:    "",
   440  		},
   441  		{
   442  			name:       "format from file extension",
   443  			args:       []string{"credential-set.yml"},
   444  			outputType: "",
   445  			wantErr:    "",
   446  		},
   447  	}
   448  
   449  	for _, tc := range testcases {
   450  		t.Run(tc.name, func(t *testing.T) {
   451  			opts := CredentialCreateOptions{OutputType: tc.outputType}
   452  			err := opts.Validate(tc.args)
   453  			if tc.wantErr == "" {
   454  				require.NoError(t, err, "no error should have existed")
   455  				return
   456  			}
   457  			assert.Contains(t, err.Error(), tc.wantErr)
   458  		})
   459  	}
   460  }
   461  
   462  func TestCredentialsCreate(t *testing.T) {
   463  	testcases := []struct {
   464  		name       string
   465  		fileName   string
   466  		outputType string
   467  		wantErr    string
   468  	}{
   469  		{
   470  			name:       "valid input: no input defined, will output yaml format to stdout",
   471  			fileName:   "",
   472  			outputType: "",
   473  			wantErr:    "",
   474  		},
   475  		{
   476  			name:       "valid input: output to stdout with format json",
   477  			fileName:   "",
   478  			outputType: "json",
   479  			wantErr:    "",
   480  		},
   481  		{
   482  			name:       "valid input: file format from fileName",
   483  			fileName:   "fileName.json",
   484  			outputType: "",
   485  			wantErr:    "",
   486  		},
   487  		{
   488  			name:       "valid input: file format from outputType",
   489  			fileName:   "fileName",
   490  			outputType: "json",
   491  			wantErr:    "",
   492  		},
   493  		{
   494  			name:       "valid input: different file format from fileName and outputType",
   495  			fileName:   "fileName.yaml",
   496  			outputType: "json",
   497  			wantErr:    "",
   498  		},
   499  		{
   500  			name:       "valid input: same file format in fileName and outputType",
   501  			fileName:   "fileName.json",
   502  			outputType: "json",
   503  			wantErr:    "",
   504  		},
   505  		{
   506  			name:       "invalid input: invalid file format from fileName",
   507  			fileName:   "fileName.txt",
   508  			outputType: "",
   509  			wantErr:    fmt.Sprintf("unsupported format %s. Supported formats are: yaml and json", "txt"),
   510  		},
   511  		{
   512  			name:       "invalid input: invalid file format from outputType",
   513  			fileName:   "fileName",
   514  			outputType: "txt",
   515  			wantErr:    fmt.Sprintf("unsupported format %s. Supported formats are: yaml and json", "txt"),
   516  		},
   517  	}
   518  
   519  	for _, tc := range testcases {
   520  		t.Run(tc.name, func(t *testing.T) {
   521  			ctx := context.Background()
   522  			p := NewTestPorter(t)
   523  			defer p.Close()
   524  
   525  			opts := CredentialCreateOptions{FileName: tc.fileName, OutputType: tc.outputType}
   526  			err := p.CreateCredential(ctx, opts)
   527  			if tc.wantErr == "" {
   528  				require.NoError(t, err, "no error should have existed")
   529  				return
   530  			}
   531  			assert.Contains(t, err.Error(), tc.wantErr)
   532  		})
   533  	}
   534  }
   535  
   536  func TestPorter_CredentialsApply(t *testing.T) {
   537  	t.Run("invalid schemaType", func(t *testing.T) {
   538  		// Make sure that we are validating the display credential set values, and not just the underlying stored credential set
   539  		ctx := context.Background()
   540  		p := NewTestPorter(t)
   541  		defer p.Close()
   542  
   543  		p.AddTestFile("testdata/credentials/kool-kreds.yaml", "kool-kreds.yaml")
   544  		p.TestConfig.TestContext.EditYaml("kool-kreds.yaml", func(yq *yaml.Editor) error {
   545  			return yq.SetValue("schemaType", "invalidthing")
   546  		})
   547  
   548  		opts := ApplyOptions{
   549  			Namespace: "altns",
   550  			File:      "kool-kreds.yaml",
   551  		}
   552  		err := p.CredentialsApply(ctx, opts)
   553  		tests.RequireErrorContains(t, err, "invalid schemaType")
   554  	})
   555  
   556  	t.Run("valid credential set", func(t *testing.T) {
   557  		ctx := context.Background()
   558  		p := NewTestPorter(t)
   559  		defer p.Close()
   560  
   561  		p.AddTestFile("testdata/credentials/kool-kreds.yaml", "kool-kreds.yaml")
   562  		p.TestConfig.TestContext.EditYaml("kool-kreds.yaml", func(yq *yaml.Editor) error {
   563  			return yq.DeleteNode("namespace")
   564  		})
   565  
   566  		opts := ApplyOptions{
   567  			Namespace: "altns", // Import into this namespace since one isn't set in the file (we removed it above)
   568  			File:      "kool-kreds.yaml",
   569  		}
   570  		err := p.CredentialsApply(ctx, opts)
   571  		require.NoError(t, err, "CredentialsApply failed")
   572  
   573  		cs, err := p.Credentials.GetCredentialSet(ctx, "altns", "kool-kreds")
   574  		require.NoError(t, err, "Failed to retrieve applied credential set")
   575  
   576  		assert.Equal(t, "kool-kreds", cs.Name, "unexpected credential set name")
   577  		require.Len(t, cs.Credentials, 4, "expected 4 credentials in the set")
   578  		assert.Equal(t, "kool-config", cs.Credentials[0].Name, "expected the kool-config credential mapping defined")
   579  		assert.Equal(t, "path", cs.Credentials[0].Source.Strategy, "unexpected credential source")
   580  		assert.Equal(t, "/path/to/kool-config", cs.Credentials[0].Source.Hint, "unexpected credential mapping value")
   581  	})
   582  }