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 }