github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/engine/lifecycletest/provider_test.go (about) 1 //nolint:goconst 2 package lifecycletest 3 4 import ( 5 "sync" 6 "testing" 7 8 "github.com/blang/semver" 9 "github.com/gofrs/uuid" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 13 . "github.com/pulumi/pulumi/pkg/v3/engine" 14 "github.com/pulumi/pulumi/pkg/v3/resource/deploy" 15 "github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest" 16 "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" 17 "github.com/pulumi/pulumi/sdk/v3/go/common/display" 18 "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 19 "github.com/pulumi/pulumi/sdk/v3/go/common/resource/config" 20 "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" 21 "github.com/pulumi/pulumi/sdk/v3/go/common/util/result" 22 "github.com/pulumi/pulumi/sdk/v3/go/common/workspace" 23 ) 24 25 func TestSingleResourceDefaultProviderLifecycle(t *testing.T) { 26 t.Parallel() 27 28 loaders := []*deploytest.ProviderLoader{ 29 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 30 return &deploytest.Provider{}, nil 31 }), 32 } 33 34 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 35 _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true) 36 assert.NoError(t, err) 37 return nil 38 }) 39 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 40 41 p := &TestPlan{ 42 Options: UpdateOptions{Host: host}, 43 Steps: MakeBasicLifecycleSteps(t, 2), 44 } 45 p.Run(t, nil) 46 } 47 48 func TestSingleResourceExplicitProviderLifecycle(t *testing.T) { 49 t.Parallel() 50 51 loaders := []*deploytest.ProviderLoader{ 52 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 53 return &deploytest.Provider{}, nil 54 }), 55 } 56 57 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 58 provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true) 59 assert.NoError(t, err) 60 61 if provID == "" { 62 provID = providers.UnknownID 63 } 64 65 provRef, err := providers.NewReference(provURN, provID) 66 assert.NoError(t, err) 67 68 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 69 Provider: provRef.String(), 70 }) 71 assert.NoError(t, err) 72 73 return nil 74 }) 75 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 76 77 p := &TestPlan{ 78 Options: UpdateOptions{Host: host}, 79 Steps: MakeBasicLifecycleSteps(t, 2), 80 } 81 p.Run(t, nil) 82 } 83 84 func TestSingleResourceDefaultProviderUpgrade(t *testing.T) { 85 t.Parallel() 86 87 loaders := []*deploytest.ProviderLoader{ 88 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 89 return &deploytest.Provider{}, nil 90 }), 91 } 92 93 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 94 _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true) 95 assert.NoError(t, err) 96 return nil 97 }) 98 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 99 100 p := &TestPlan{ 101 Options: UpdateOptions{Host: host}, 102 } 103 104 provURN := p.NewProviderURN("pkgA", "default", "") 105 resURN := p.NewURN("pkgA:m:typA", "resA", "") 106 107 // Create an old snapshot with an existing copy of the single resource and no providers. 108 old := &deploy.Snapshot{ 109 Resources: []*resource.State{{ 110 Type: resURN.Type(), 111 URN: resURN, 112 Custom: true, 113 ID: "0", 114 Inputs: resource.PropertyMap{}, 115 Outputs: resource.PropertyMap{}, 116 }}, 117 } 118 119 isRefresh := false 120 validate := func(project workspace.Project, target deploy.Target, entries JournalEntries, 121 _ []Event, res result.Result) result.Result { 122 123 // Should see only sames: the default provider should be injected into the old state before the update 124 // runs. 125 for _, entry := range entries { 126 switch urn := entry.Step.URN(); urn { 127 case provURN, resURN: 128 expect := deploy.OpSame 129 if isRefresh { 130 expect = deploy.OpRefresh 131 } 132 assert.Equal(t, expect, entry.Step.Op()) 133 default: 134 t.Fatalf("unexpected resource %v", urn) 135 } 136 } 137 snap, err := entries.Snap(target.Snapshot) 138 require.NoError(t, err) 139 assert.Len(t, snap.Resources, 2) 140 return res 141 } 142 143 // Run a single update step using the base snapshot. 144 p.Steps = []TestStep{{Op: Update, Validate: validate}} 145 p.Run(t, old) 146 147 // Run a single refresh step using the base snapshot. 148 isRefresh = true 149 p.Steps = []TestStep{{Op: Refresh, Validate: validate}} 150 p.Run(t, old) 151 152 // Run a single destroy step using the base snapshot. 153 isRefresh = false 154 p.Steps = []TestStep{{ 155 Op: Destroy, 156 Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries, 157 _ []Event, res result.Result) result.Result { 158 159 // Should see two deletes: the default provider should be injected into the old state before the update 160 // runs. 161 deleted := make(map[resource.URN]bool) 162 for _, entry := range entries { 163 switch urn := entry.Step.URN(); urn { 164 case provURN, resURN: 165 deleted[urn] = true 166 assert.Equal(t, deploy.OpDelete, entry.Step.Op()) 167 default: 168 t.Fatalf("unexpected resource %v", urn) 169 } 170 } 171 assert.Len(t, deleted, 2) 172 snap, err := entries.Snap(target.Snapshot) 173 require.NoError(t, err) 174 assert.Len(t, snap.Resources, 0) 175 return res 176 }, 177 }} 178 p.Run(t, old) 179 180 // Run a partial lifecycle using the base snapshot, skipping the initial update step. 181 p.Steps = MakeBasicLifecycleSteps(t, 2)[1:] 182 p.Run(t, old) 183 } 184 185 func TestSingleResourceDefaultProviderReplace(t *testing.T) { 186 t.Parallel() 187 188 loaders := []*deploytest.ProviderLoader{ 189 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 190 return &deploytest.Provider{ 191 DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap, 192 ignoreChanges []string) (plugin.DiffResult, error) { 193 194 // Always require replacement. 195 keys := []resource.PropertyKey{} 196 for k := range news { 197 keys = append(keys, k) 198 } 199 return plugin.DiffResult{ReplaceKeys: keys}, nil 200 }, 201 }, nil 202 }), 203 } 204 205 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 206 _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true) 207 assert.NoError(t, err) 208 return nil 209 }) 210 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 211 212 p := &TestPlan{ 213 Options: UpdateOptions{Host: host}, 214 Config: config.Map{ 215 config.MustMakeKey("pkgA", "foo"): config.NewValue("bar"), 216 }, 217 } 218 219 // Build a basic lifecycle. 220 steps := MakeBasicLifecycleSteps(t, 2) 221 222 // Run the lifecycle through its no-op update+refresh. 223 p.Steps = steps[:4] 224 snap := p.Run(t, nil) 225 226 // Change the config and run an update. We expect everything to require replacement. 227 p.Config[config.MustMakeKey("pkgA", "foo")] = config.NewValue("baz") 228 p.Steps = []TestStep{{ 229 Op: Update, 230 Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries, 231 _ []Event, res result.Result) result.Result { 232 233 provURN := p.NewProviderURN("pkgA", "default", "") 234 resURN := p.NewURN("pkgA:m:typA", "resA", "") 235 236 // Look for replace steps on the provider and the resource. 237 replacedProvider, replacedResource := false, false 238 for _, entry := range entries { 239 if entry.Kind != JournalEntrySuccess || entry.Step.Op() != deploy.OpDeleteReplaced { 240 continue 241 } 242 243 switch urn := entry.Step.URN(); urn { 244 case provURN: 245 replacedProvider = true 246 case resURN: 247 replacedResource = true 248 default: 249 t.Fatalf("unexpected resource %v", urn) 250 } 251 } 252 assert.True(t, replacedProvider) 253 assert.True(t, replacedResource) 254 255 return res 256 }, 257 }} 258 259 snap = p.Run(t, snap) 260 261 // Resume the lifecycle with another no-op update. 262 p.Steps = steps[2:] 263 p.Run(t, snap) 264 } 265 266 func TestSingleResourceExplicitProviderReplace(t *testing.T) { 267 t.Parallel() 268 269 loaders := []*deploytest.ProviderLoader{ 270 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 271 return &deploytest.Provider{ 272 DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap, 273 ignoreChanges []string) (plugin.DiffResult, error) { 274 // Always require replacement. 275 keys := []resource.PropertyKey{} 276 for k := range news { 277 keys = append(keys, k) 278 } 279 return plugin.DiffResult{ReplaceKeys: keys}, nil 280 }, 281 }, nil 282 }), 283 } 284 285 providerInputs := resource.PropertyMap{ 286 resource.PropertyKey("foo"): resource.NewStringProperty("bar"), 287 } 288 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 289 provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true, 290 deploytest.ResourceOptions{Inputs: providerInputs}) 291 assert.NoError(t, err) 292 293 if provID == "" { 294 provID = providers.UnknownID 295 } 296 297 provRef, err := providers.NewReference(provURN, provID) 298 assert.NoError(t, err) 299 300 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 301 Provider: provRef.String(), 302 }) 303 assert.NoError(t, err) 304 305 return nil 306 }) 307 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 308 309 p := &TestPlan{ 310 Options: UpdateOptions{Host: host}, 311 } 312 313 // Build a basic lifecycle. 314 steps := MakeBasicLifecycleSteps(t, 2) 315 316 // Run the lifecycle through its no-op update+refresh. 317 p.Steps = steps[:4] 318 snap := p.Run(t, nil) 319 320 // Change the config and run an update. We expect everything to require replacement. 321 providerInputs[resource.PropertyKey("foo")] = resource.NewStringProperty("baz") 322 p.Steps = []TestStep{{ 323 Op: Update, 324 Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries, 325 _ []Event, res result.Result) result.Result { 326 327 provURN := p.NewProviderURN("pkgA", "provA", "") 328 resURN := p.NewURN("pkgA:m:typA", "resA", "") 329 330 // Look for replace steps on the provider and the resource. 331 replacedProvider, replacedResource := false, false 332 for _, entry := range entries { 333 if entry.Kind != JournalEntrySuccess || entry.Step.Op() != deploy.OpDeleteReplaced { 334 continue 335 } 336 337 switch urn := entry.Step.URN(); urn { 338 case provURN: 339 replacedProvider = true 340 case resURN: 341 replacedResource = true 342 default: 343 t.Fatalf("unexpected resource %v", urn) 344 } 345 } 346 assert.True(t, replacedProvider) 347 assert.True(t, replacedResource) 348 349 return res 350 }, 351 }} 352 snap = p.Run(t, snap) 353 354 // Resume the lifecycle with another no-op update. 355 p.Steps = steps[2:] 356 p.Run(t, snap) 357 } 358 359 type configurableProvider struct { 360 id string 361 replace bool 362 creates *sync.Map 363 deletes *sync.Map 364 } 365 366 func (p *configurableProvider) configure(news resource.PropertyMap) error { 367 p.id = news["id"].StringValue() 368 return nil 369 } 370 371 func (p *configurableProvider) create(urn resource.URN, inputs resource.PropertyMap, timeout float64, 372 preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) { 373 374 uid, err := uuid.NewV4() 375 if err != nil { 376 return "", nil, resource.StatusUnknown, err 377 } 378 id := resource.ID(uid.String()) 379 380 p.creates.Store(id, p.id) 381 return id, inputs, resource.StatusOK, nil 382 } 383 384 func (p *configurableProvider) delete(urn resource.URN, id resource.ID, olds resource.PropertyMap, 385 timeout float64) (resource.Status, error) { 386 p.deletes.Store(id, p.id) 387 return resource.StatusOK, nil 388 } 389 390 // TestSingleResourceExplicitProviderAliasUpdateDelete verifies that providers respect aliases during updates, and 391 // that the correct instance of an explicit provider is used to delete a removed resource. 392 func TestSingleResourceExplicitProviderAliasUpdateDelete(t *testing.T) { 393 t.Parallel() 394 395 var creates, deletes sync.Map 396 397 loaders := []*deploytest.ProviderLoader{ 398 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 399 configurable := &configurableProvider{ 400 creates: &creates, 401 deletes: &deletes, 402 } 403 404 return &deploytest.Provider{ 405 DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap, 406 ignoreChanges []string) (plugin.DiffResult, error) { 407 return plugin.DiffResult{}, nil 408 }, 409 ConfigureF: configurable.configure, 410 CreateF: configurable.create, 411 DeleteF: configurable.delete, 412 }, nil 413 }), 414 } 415 416 providerInputs := resource.PropertyMap{ 417 resource.PropertyKey("id"): resource.NewStringProperty("first"), 418 } 419 providerName := "provA" 420 aliases := []resource.URN{} 421 registerResource := true 422 var resourceID resource.ID 423 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 424 provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), providerName, true, 425 deploytest.ResourceOptions{ 426 Inputs: providerInputs, 427 AliasURNs: aliases, 428 }) 429 assert.NoError(t, err) 430 431 if provID == "" { 432 provID = providers.UnknownID 433 } 434 435 provRef, err := providers.NewReference(provURN, provID) 436 assert.NoError(t, err) 437 438 if registerResource { 439 _, resourceID, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 440 Provider: provRef.String(), 441 }) 442 assert.NoError(t, err) 443 } 444 445 return nil 446 }) 447 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 448 449 p := &TestPlan{ 450 Options: UpdateOptions{Host: host}, 451 } 452 453 // Build a basic lifecycle. 454 steps := MakeBasicLifecycleSteps(t, 2) 455 456 // Run the lifecycle through its initial update+refresh. 457 p.Steps = steps[:4] 458 snap := p.Run(t, nil) 459 460 // Add a provider alias to the original URN. 461 aliases = []resource.URN{ 462 p.NewProviderURN("pkgA", "provA", ""), 463 } 464 // Change the provider name and configuration and remove the resource. This will cause an Update for the provider 465 // and a Delete for the resource. The updated provider instance should be used to perform the delete. 466 providerName = "provB" 467 providerInputs[resource.PropertyKey("id")] = resource.NewStringProperty("second") 468 registerResource = false 469 470 p.Steps = []TestStep{{Op: Update}} 471 _ = p.Run(t, snap) 472 473 // Check the identity of the provider that performed the delete. 474 deleterID, ok := deletes.Load(resourceID) 475 require.True(t, ok) 476 assert.Equal(t, "second", deleterID) 477 } 478 479 // TestSingleResourceExplicitProviderAliasReplace verifies that providers respect aliases, 480 // and propagate replaces as a result of an aliased provider diff. 481 func TestSingleResourceExplicitProviderAliasReplace(t *testing.T) { 482 t.Parallel() 483 484 var creates, deletes sync.Map 485 486 loaders := []*deploytest.ProviderLoader{ 487 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 488 configurable := &configurableProvider{ 489 replace: true, 490 creates: &creates, 491 deletes: &deletes, 492 } 493 494 return &deploytest.Provider{ 495 DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap, 496 ignoreChanges []string) (plugin.DiffResult, error) { 497 keys := []resource.PropertyKey{} 498 for k := range news { 499 keys = append(keys, k) 500 } 501 return plugin.DiffResult{ReplaceKeys: keys}, nil 502 }, 503 ConfigureF: configurable.configure, 504 CreateF: configurable.create, 505 DeleteF: configurable.delete, 506 }, nil 507 }), 508 } 509 510 providerInputs := resource.PropertyMap{ 511 resource.PropertyKey("id"): resource.NewStringProperty("first"), 512 } 513 providerName := "provA" 514 aliases := []resource.URN{} 515 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 516 provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), providerName, true, 517 deploytest.ResourceOptions{ 518 Inputs: providerInputs, 519 AliasURNs: aliases, 520 }) 521 assert.NoError(t, err) 522 523 if provID == "" { 524 provID = providers.UnknownID 525 } 526 527 provRef, err := providers.NewReference(provURN, provID) 528 assert.NoError(t, err) 529 530 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 531 Provider: provRef.String(), 532 }) 533 assert.NoError(t, err) 534 535 return nil 536 }) 537 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 538 539 p := &TestPlan{ 540 Options: UpdateOptions{Host: host}, 541 } 542 543 // Build a basic lifecycle. 544 steps := MakeBasicLifecycleSteps(t, 2) 545 546 // Run the lifecycle through its no-op update+refresh. 547 p.Steps = steps[:4] 548 snap := p.Run(t, nil) 549 550 // add a provider alias to the original URN 551 aliases = []resource.URN{ 552 p.NewProviderURN("pkgA", "provA", ""), 553 } 554 // change the provider name 555 providerName = "provB" 556 // run an update expecting no-op respecting the aliases. 557 p.Steps = []TestStep{{ 558 Op: Update, 559 Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries, 560 _ []Event, res result.Result) result.Result { 561 for _, entry := range entries { 562 if entry.Step.Op() != deploy.OpSame { 563 t.Fatalf("update should contain no changes: %v", entry.Step.URN()) 564 } 565 } 566 return res 567 }, 568 }} 569 snap = p.Run(t, snap) 570 571 // Change the config and run an update maintaining the alias. We expect everything to require replacement. 572 providerInputs[resource.PropertyKey("id")] = resource.NewStringProperty("second") 573 p.Steps = []TestStep{{ 574 Op: Update, 575 Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries, 576 _ []Event, res result.Result) result.Result { 577 578 provURN := p.NewProviderURN("pkgA", providerName, "") 579 resURN := p.NewURN("pkgA:m:typA", "resA", "") 580 581 // Find the delete and create IDs for the resource. 582 var createdID, deletedID resource.ID 583 584 // Look for replace steps on the provider and the resource. 585 replacedProvider, replacedResource := false, false 586 for _, entry := range entries { 587 op := entry.Step.Op() 588 589 if entry.Step.URN() == resURN { 590 switch op { 591 case deploy.OpCreateReplacement: 592 createdID = entry.Step.New().ID 593 case deploy.OpDeleteReplaced: 594 deletedID = entry.Step.Old().ID 595 } 596 } 597 598 if entry.Kind != JournalEntrySuccess || op != deploy.OpDeleteReplaced { 599 continue 600 } 601 602 switch urn := entry.Step.URN(); urn { 603 case provURN: 604 replacedProvider = true 605 case resURN: 606 replacedResource = true 607 default: 608 t.Fatalf("unexpected resource %v", urn) 609 } 610 } 611 assert.True(t, replacedProvider) 612 assert.True(t, replacedResource) 613 614 // Check the identities of the providers that performed the create and delete. 615 // 616 // For a replacement, the newly-created provider should be used to create the new resource, and the original 617 // provider should be used to delete the old resource. 618 creatorID, ok := creates.Load(createdID) 619 require.True(t, ok) 620 assert.Equal(t, "second", creatorID) 621 622 deleterID, ok := deletes.Load(deletedID) 623 require.True(t, ok) 624 assert.Equal(t, "first", deleterID) 625 626 return res 627 }, 628 }} 629 snap = p.Run(t, snap) 630 631 // Resume the lifecycle with another no-op update. 632 p.Steps = steps[2:] 633 p.Run(t, snap) 634 } 635 636 func TestSingleResourceExplicitProviderDeleteBeforeReplace(t *testing.T) { 637 t.Parallel() 638 639 loaders := []*deploytest.ProviderLoader{ 640 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 641 return &deploytest.Provider{ 642 DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap, 643 ignoreChanges []string) (plugin.DiffResult, error) { 644 // Always require replacement. 645 keys := []resource.PropertyKey{} 646 for k := range news { 647 keys = append(keys, k) 648 } 649 return plugin.DiffResult{ReplaceKeys: keys, DeleteBeforeReplace: true}, nil 650 }, 651 }, nil 652 }), 653 } 654 655 providerInputs := resource.PropertyMap{ 656 resource.PropertyKey("foo"): resource.NewStringProperty("bar"), 657 } 658 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 659 provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true, 660 deploytest.ResourceOptions{Inputs: providerInputs}) 661 assert.NoError(t, err) 662 663 if provID == "" { 664 provID = providers.UnknownID 665 } 666 667 provRef, err := providers.NewReference(provURN, provID) 668 assert.NoError(t, err) 669 670 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 671 Provider: provRef.String(), 672 }) 673 assert.NoError(t, err) 674 675 return nil 676 }) 677 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 678 679 p := &TestPlan{ 680 Options: UpdateOptions{Host: host}, 681 } 682 683 // Build a basic lifecycle. 684 steps := MakeBasicLifecycleSteps(t, 2) 685 686 // Run the lifecycle through its no-op update+refresh. 687 p.Steps = steps[:4] 688 snap := p.Run(t, nil) 689 690 // Change the config and run an update. We expect everything to require replacement. 691 providerInputs[resource.PropertyKey("foo")] = resource.NewStringProperty("baz") 692 p.Steps = []TestStep{{ 693 Op: Update, 694 Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries, 695 _ []Event, res result.Result) result.Result { 696 697 provURN := p.NewProviderURN("pkgA", "provA", "") 698 resURN := p.NewURN("pkgA:m:typA", "resA", "") 699 700 // Look for replace steps on the provider and the resource. 701 createdProvider, createdResource := false, false 702 deletedProvider, deletedResource := false, false 703 for _, entry := range entries { 704 if entry.Kind != JournalEntrySuccess { 705 continue 706 } 707 708 switch urn := entry.Step.URN(); urn { 709 case provURN: 710 if entry.Step.Op() == deploy.OpDeleteReplaced { 711 assert.False(t, createdProvider) 712 assert.False(t, createdResource) 713 assert.True(t, deletedResource) 714 deletedProvider = true 715 } else if entry.Step.Op() == deploy.OpCreateReplacement { 716 assert.True(t, deletedProvider) 717 assert.True(t, deletedResource) 718 assert.False(t, createdResource) 719 createdProvider = true 720 } 721 case resURN: 722 if entry.Step.Op() == deploy.OpDeleteReplaced { 723 assert.False(t, deletedProvider) 724 assert.False(t, deletedResource) 725 deletedResource = true 726 } else if entry.Step.Op() == deploy.OpCreateReplacement { 727 assert.True(t, deletedProvider) 728 assert.True(t, deletedResource) 729 assert.True(t, createdProvider) 730 createdResource = true 731 } 732 default: 733 t.Fatalf("unexpected resource %v", urn) 734 } 735 } 736 assert.True(t, deletedProvider) 737 assert.True(t, deletedResource) 738 739 return res 740 }, 741 }} 742 snap = p.Run(t, snap) 743 744 // Resume the lifecycle with another no-op update. 745 p.Steps = steps[2:] 746 p.Run(t, snap) 747 } 748 749 // TestDefaultProviderDiff tests that the engine can gracefully recover whenever a resource's default provider changes 750 // and there is no diff in the provider's inputs. 751 func TestDefaultProviderDiff(t *testing.T) { 752 t.Parallel() 753 754 const resName, resBName = "resA", "resB" 755 loaders := []*deploytest.ProviderLoader{ 756 deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.10"), func() (plugin.Provider, error) { 757 return &deploytest.Provider{}, nil 758 }), 759 deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.11"), func() (plugin.Provider, error) { 760 return &deploytest.Provider{}, nil 761 }), 762 deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.12"), func() (plugin.Provider, error) { 763 return &deploytest.Provider{}, nil 764 }), 765 } 766 767 runProgram := func(base *deploy.Snapshot, versionA, versionB string, expectedStep display.StepOp) *deploy.Snapshot { 768 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 769 _, _, _, err := monitor.RegisterResource("pkgA:m:typA", resName, true, deploytest.ResourceOptions{ 770 Version: versionA, 771 }) 772 assert.NoError(t, err) 773 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", resBName, true, deploytest.ResourceOptions{ 774 Version: versionB, 775 }) 776 assert.NoError(t, err) 777 return nil 778 }) 779 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 780 p := &TestPlan{ 781 Options: UpdateOptions{Host: host}, 782 Steps: []TestStep{ 783 { 784 Op: Update, 785 Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries, 786 events []Event, res result.Result) result.Result { 787 for _, entry := range entries { 788 if entry.Kind != JournalEntrySuccess { 789 continue 790 } 791 792 switch entry.Step.URN().Name().String() { 793 case resName, resBName: 794 assert.Equal(t, expectedStep, entry.Step.Op()) 795 } 796 } 797 return res 798 }, 799 }, 800 }, 801 } 802 return p.Run(t, base) 803 } 804 805 // This test simulates the upgrade scenario of old-style default providers to new-style versioned default providers. 806 // 807 // The first update creates a stack using a language host that does not report a version to the engine. As a result, 808 // the engine makes up a default provider for "pkgA" and calls it "default". It then creates the two resources that 809 // we are creating and associates them with the default provider. 810 snap := runProgram(nil, "", "", deploy.OpCreate) 811 for _, res := range snap.Resources { 812 switch { 813 case providers.IsDefaultProvider(res.URN): 814 assert.Equal(t, "default", res.URN.Name().String()) 815 case res.URN.Name().String() == resName || res.URN.Name().String() == resBName: 816 provRef, err := providers.ParseReference(res.Provider) 817 assert.NoError(t, err) 818 assert.Equal(t, "default", provRef.URN().Name().String()) 819 } 820 } 821 822 // The second update switches to a language host that does report a version to the engine. As a result, the engine 823 // uses this version to make a new provider, with a different URN, and uses that provider to operate on resA and 824 // resB. 825 // 826 // Despite switching out the provider, the engine should still generate a Same step for resA. It is vital that the 827 // engine gracefully react to changes in the default provider in this manner. See pulumi/pulumi#2753 for what 828 // happens when it doesn't. 829 snap = runProgram(snap, "0.17.10", "0.17.10", deploy.OpSame) 830 for _, res := range snap.Resources { 831 switch { 832 case providers.IsDefaultProvider(res.URN): 833 assert.Equal(t, "default_0_17_10", res.URN.Name().String()) 834 case res.URN.Name().String() == resName || res.URN.Name().String() == resBName: 835 provRef, err := providers.ParseReference(res.Provider) 836 assert.NoError(t, err) 837 assert.Equal(t, "default_0_17_10", provRef.URN().Name().String()) 838 } 839 } 840 841 // The third update changes the version that the language host reports to the engine. This simulates a scenario in 842 // which a user updates their SDK to a new version of a provider package. In order to simulate side-by-side 843 // packages with different versions, this update requests distinct package versions for resA and resB. 844 snap = runProgram(snap, "0.17.11", "0.17.12", deploy.OpSame) 845 for _, res := range snap.Resources { 846 switch { 847 case providers.IsDefaultProvider(res.URN): 848 assert.True(t, res.URN.Name().String() == "default_0_17_11" || res.URN.Name().String() == "default_0_17_12") 849 case res.URN.Name().String() == resName: 850 provRef, err := providers.ParseReference(res.Provider) 851 assert.NoError(t, err) 852 assert.Equal(t, "default_0_17_11", provRef.URN().Name().String()) 853 case res.URN.Name().String() == resBName: 854 provRef, err := providers.ParseReference(res.Provider) 855 assert.NoError(t, err) 856 assert.Equal(t, "default_0_17_12", provRef.URN().Name().String()) 857 } 858 } 859 } 860 861 // TestDefaultProviderDiffReplacement tests that, when replacing a default provider for a resource, the engine will 862 // replace the resource if DiffConfig on the new provider returns a diff for the provider's new state. 863 func TestDefaultProviderDiffReplacement(t *testing.T) { 864 t.Parallel() 865 866 const resName, resBName = "resA", "resB" 867 loaders := []*deploytest.ProviderLoader{ 868 deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.10"), func() (plugin.Provider, error) { 869 return &deploytest.Provider{ 870 // This implementation of DiffConfig always requests replacement. 871 DiffConfigF: func(_ resource.URN, olds, news resource.PropertyMap, 872 ignoreChanges []string) (plugin.DiffResult, error) { 873 874 keys := []resource.PropertyKey{} 875 for k := range news { 876 keys = append(keys, k) 877 } 878 return plugin.DiffResult{ 879 Changes: plugin.DiffSome, 880 ReplaceKeys: keys, 881 }, nil 882 }, 883 }, nil 884 }), 885 deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.11"), func() (plugin.Provider, error) { 886 return &deploytest.Provider{}, nil 887 }), 888 } 889 890 runProgram := func(base *deploy.Snapshot, versionA, versionB string, 891 expectedSteps ...display.StepOp) *deploy.Snapshot { 892 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 893 _, _, _, err := monitor.RegisterResource("pkgA:m:typA", resName, true, deploytest.ResourceOptions{ 894 Version: versionA, 895 }) 896 assert.NoError(t, err) 897 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", resBName, true, deploytest.ResourceOptions{ 898 Version: versionB, 899 }) 900 assert.NoError(t, err) 901 return nil 902 }) 903 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 904 p := &TestPlan{ 905 Options: UpdateOptions{Host: host}, 906 Steps: []TestStep{ 907 { 908 Op: Update, 909 Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries, 910 events []Event, res result.Result) result.Result { 911 for _, entry := range entries { 912 if entry.Kind != JournalEntrySuccess { 913 continue 914 } 915 916 switch entry.Step.URN().Name().String() { 917 case resName: 918 assert.Subset(t, expectedSteps, []display.StepOp{entry.Step.Op()}) 919 case resBName: 920 assert.Subset(t, 921 []display.StepOp{deploy.OpCreate, deploy.OpSame}, []display.StepOp{entry.Step.Op()}) 922 } 923 } 924 return res 925 }, 926 }, 927 }, 928 } 929 return p.Run(t, base) 930 } 931 932 // This test simulates the upgrade scenario of default providers, except that the requested upgrade results in the 933 // provider getting replaced. Because of this, the engine should decide to replace resA. It should not decide to 934 // replace resB, as its change does not require replacement. 935 snap := runProgram(nil, "", "", deploy.OpCreate) 936 for _, res := range snap.Resources { 937 switch { 938 case providers.IsDefaultProvider(res.URN): 939 assert.Equal(t, "default", res.URN.Name().String()) 940 case res.URN.Name().String() == resName || res.URN.Name().String() == resBName: 941 provRef, err := providers.ParseReference(res.Provider) 942 assert.NoError(t, err) 943 assert.Equal(t, "default", provRef.URN().Name().String()) 944 } 945 } 946 947 // Upon update, now that the language host is sending a version, DiffConfig reports that there's a diff between the 948 // old and new provider and so we must replace resA. 949 snap = runProgram(snap, "0.17.10", "0.17.11", deploy.OpCreateReplacement, deploy.OpReplace, deploy.OpDeleteReplaced) 950 for _, res := range snap.Resources { 951 switch { 952 case providers.IsDefaultProvider(res.URN): 953 assert.True(t, res.URN.Name().String() == "default_0_17_10" || res.URN.Name().String() == "default_0_17_11") 954 case res.URN.Name().String() == resName: 955 provRef, err := providers.ParseReference(res.Provider) 956 assert.NoError(t, err) 957 assert.Equal(t, "default_0_17_10", provRef.URN().Name().String()) 958 case res.URN.Name().String() == resBName: 959 provRef, err := providers.ParseReference(res.Provider) 960 assert.NoError(t, err) 961 assert.Equal(t, "default_0_17_11", provRef.URN().Name().String()) 962 } 963 } 964 } 965 966 func TestProviderVersionDefault(t *testing.T) { 967 t.Parallel() 968 969 version := "" 970 loaders := []*deploytest.ProviderLoader{ 971 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 972 version = "1.0.0" 973 return &deploytest.Provider{}, nil 974 }), 975 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.5.0"), func() (plugin.Provider, error) { 976 version = "1.5.0" 977 return &deploytest.Provider{}, nil 978 }), 979 } 980 981 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 982 provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true) 983 assert.NoError(t, err) 984 985 if provID == "" { 986 provID = providers.UnknownID 987 } 988 989 provRef, err := providers.NewReference(provURN, provID) 990 assert.NoError(t, err) 991 992 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 993 Provider: provRef.String(), 994 }) 995 assert.NoError(t, err) 996 997 return nil 998 }) 999 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 1000 1001 p := &TestPlan{ 1002 Options: UpdateOptions{Host: host}, 1003 Steps: MakeBasicLifecycleSteps(t, 2), 1004 } 1005 p.Run(t, nil) 1006 1007 assert.Equal(t, "1.5.0", version) 1008 } 1009 1010 func TestProviderVersionOption(t *testing.T) { 1011 t.Parallel() 1012 1013 version := "" 1014 loaders := []*deploytest.ProviderLoader{ 1015 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 1016 version = "1.0.0" 1017 return &deploytest.Provider{}, nil 1018 }), 1019 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.5.0"), func() (plugin.Provider, error) { 1020 version = "1.5.0" 1021 return &deploytest.Provider{}, nil 1022 }), 1023 } 1024 1025 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 1026 provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true, 1027 deploytest.ResourceOptions{ 1028 Version: "1.0.0", 1029 }) 1030 assert.NoError(t, err) 1031 1032 if provID == "" { 1033 provID = providers.UnknownID 1034 } 1035 1036 provRef, err := providers.NewReference(provURN, provID) 1037 assert.NoError(t, err) 1038 1039 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 1040 Provider: provRef.String(), 1041 }) 1042 assert.NoError(t, err) 1043 1044 return nil 1045 }) 1046 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 1047 1048 p := &TestPlan{ 1049 Options: UpdateOptions{Host: host}, 1050 Steps: MakeBasicLifecycleSteps(t, 2), 1051 } 1052 p.Run(t, nil) 1053 1054 assert.Equal(t, "1.0.0", version) 1055 } 1056 1057 func TestProviderVersionInput(t *testing.T) { 1058 t.Parallel() 1059 1060 version := "" 1061 loaders := []*deploytest.ProviderLoader{ 1062 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 1063 version = "1.0.0" 1064 return &deploytest.Provider{}, nil 1065 }), 1066 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.5.0"), func() (plugin.Provider, error) { 1067 version = "1.5.0" 1068 return &deploytest.Provider{}, nil 1069 }), 1070 } 1071 1072 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 1073 provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true, 1074 deploytest.ResourceOptions{ 1075 Inputs: resource.PropertyMap{ 1076 "version": resource.NewStringProperty("1.0.0"), 1077 }, 1078 }) 1079 assert.NoError(t, err) 1080 1081 if provID == "" { 1082 provID = providers.UnknownID 1083 } 1084 1085 provRef, err := providers.NewReference(provURN, provID) 1086 assert.NoError(t, err) 1087 1088 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 1089 Provider: provRef.String(), 1090 }) 1091 assert.NoError(t, err) 1092 1093 return nil 1094 }) 1095 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 1096 1097 p := &TestPlan{ 1098 Options: UpdateOptions{Host: host}, 1099 Steps: MakeBasicLifecycleSteps(t, 2), 1100 } 1101 p.Run(t, nil) 1102 1103 assert.Equal(t, "1.0.0", version) 1104 } 1105 1106 func TestProviderVersionInputAndOption(t *testing.T) { 1107 t.Parallel() 1108 1109 version := "" 1110 loaders := []*deploytest.ProviderLoader{ 1111 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 1112 version = "1.0.0" 1113 return &deploytest.Provider{}, nil 1114 }), 1115 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.5.0"), func() (plugin.Provider, error) { 1116 version = "1.5.0" 1117 return &deploytest.Provider{}, nil 1118 }), 1119 } 1120 1121 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 1122 provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true, 1123 deploytest.ResourceOptions{ 1124 Inputs: resource.PropertyMap{ 1125 "version": resource.NewStringProperty("1.5.0"), 1126 }, 1127 Version: "1.0.0", 1128 }) 1129 assert.NoError(t, err) 1130 1131 if provID == "" { 1132 provID = providers.UnknownID 1133 } 1134 1135 provRef, err := providers.NewReference(provURN, provID) 1136 assert.NoError(t, err) 1137 1138 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 1139 Provider: provRef.String(), 1140 }) 1141 assert.NoError(t, err) 1142 1143 return nil 1144 }) 1145 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 1146 1147 p := &TestPlan{ 1148 Options: UpdateOptions{Host: host}, 1149 Steps: MakeBasicLifecycleSteps(t, 2), 1150 } 1151 p.Run(t, nil) 1152 1153 assert.Equal(t, "1.0.0", version) 1154 } 1155 1156 func TestPluginDownloadURLPassthrough(t *testing.T) { 1157 t.Parallel() 1158 1159 loaders := []*deploytest.ProviderLoader{ 1160 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 1161 return &deploytest.Provider{}, nil 1162 }), 1163 } 1164 1165 pkgAPluginDownloadURL := "get.pulumi.com/${VERSION}" 1166 pkgAType := providers.MakeProviderType("pkgA") 1167 1168 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 1169 provURN, provID, _, err := monitor.RegisterResource(pkgAType, "provA", true, deploytest.ResourceOptions{ 1170 PluginDownloadURL: pkgAPluginDownloadURL, 1171 }) 1172 assert.NoError(t, err) 1173 1174 if provID == "" { 1175 provID = providers.UnknownID 1176 } 1177 1178 provRef, err := providers.NewReference(provURN, provID) 1179 assert.NoError(t, err) 1180 1181 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 1182 Provider: provRef.String(), 1183 }) 1184 assert.NoError(t, err) 1185 1186 return nil 1187 }) 1188 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 1189 1190 steps := MakeBasicLifecycleSteps(t, 2) 1191 steps[0].ValidateAnd(func(project workspace.Project, target deploy.Target, entries JournalEntries, 1192 _ []Event, res result.Result) result.Result { 1193 1194 for _, e := range entries { 1195 r := e.Step.New() 1196 if r.Type == pkgAType && r.Inputs["pluginDownloadURL"].StringValue() != pkgAPluginDownloadURL { 1197 return result.Errorf("Found unexpected value %v", r.Inputs["pluginDownloadURL"]) 1198 } 1199 } 1200 return nil 1201 }) 1202 p := &TestPlan{ 1203 Options: UpdateOptions{Host: host}, 1204 Steps: steps, 1205 } 1206 p.Run(t, nil) 1207 } 1208 1209 // Check that creating a resource with pluginDownloadURL set will instantiate a default provider with 1210 // pluginDownloadURL set. 1211 func TestPluginDownloadURLDefaultProvider(t *testing.T) { 1212 t.Parallel() 1213 1214 loaders := []*deploytest.ProviderLoader{ 1215 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 1216 return &deploytest.Provider{}, nil 1217 }), 1218 } 1219 url := "get.pulumi.com" 1220 1221 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 1222 _, _, _, err := monitor.RegisterResource("pkgA::Foo", "foo", true, deploytest.ResourceOptions{ 1223 PluginDownloadURL: url, 1224 }) 1225 return err 1226 }) 1227 1228 snapshot := (&TestPlan{ 1229 Options: UpdateOptions{Host: deploytest.NewPluginHost(nil, nil, program, loaders...)}, 1230 // The first step is the update. We don't want the full lifecycle because we want to see the 1231 // created resources. 1232 Steps: MakeBasicLifecycleSteps(t, 2)[:1], 1233 }).Run(t, nil) 1234 1235 foundDefaultProvider := false 1236 for _, r := range snapshot.Resources { 1237 if providers.IsDefaultProvider(r.URN) { 1238 actualURL, err := providers.GetProviderDownloadURL(r.Inputs) 1239 assert.NoError(t, err) 1240 assert.Equal(t, url, actualURL) 1241 foundDefaultProvider = true 1242 } 1243 } 1244 assert.Truef(t, foundDefaultProvider, "Found resources: %#v", snapshot.Resources) 1245 } 1246 1247 func TestMultipleResourceDenyDefaultProviderLifecycle(t *testing.T) { 1248 t.Parallel() 1249 1250 cases := []struct { 1251 name string 1252 f deploytest.ProgramFunc 1253 disabled string 1254 expectFail bool 1255 }{ 1256 { 1257 name: "default-blocked", 1258 f: func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 1259 _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true) 1260 assert.NoError(t, err) 1261 _, _, _, err = monitor.RegisterResource("pkgB:m:typB", "resB", true) 1262 assert.NoError(t, err) 1263 1264 return nil 1265 }, 1266 disabled: `["pkgA"]`, 1267 expectFail: true, 1268 }, 1269 { 1270 name: "explicit-not-blocked", 1271 f: func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 1272 provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true) 1273 assert.NoError(t, err) 1274 provRef, err := providers.NewReference(provURN, provID) 1275 assert.NoError(t, err) 1276 1277 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 1278 Provider: provRef.String(), 1279 }) 1280 assert.NoError(t, err) 1281 1282 _, _, _, err = monitor.RegisterResource("pkgB:m:typB", "resB", true) 1283 assert.NoError(t, err) 1284 1285 return nil 1286 }, 1287 disabled: `["pkgA"]`, 1288 expectFail: false, 1289 }, 1290 { 1291 name: "wildcard", 1292 f: func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 1293 _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true) 1294 assert.NoError(t, err) 1295 _, _, _, err = monitor.RegisterResource("pkgB:m:typB", "resB", true) 1296 assert.NoError(t, err) 1297 1298 return nil 1299 }, 1300 disabled: `["*"]`, 1301 expectFail: true, 1302 }, 1303 } 1304 for _, tt := range cases { 1305 tt := tt 1306 t.Run(tt.name, func(t *testing.T) { 1307 t.Parallel() 1308 1309 loaders := []*deploytest.ProviderLoader{ 1310 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 1311 return &deploytest.Provider{}, nil 1312 }), 1313 deploytest.NewProviderLoader("pkgB", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 1314 return &deploytest.Provider{}, nil 1315 }), 1316 } 1317 1318 program := deploytest.NewLanguageRuntime(tt.f) 1319 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 1320 1321 c := config.Map{} 1322 k := config.MustMakeKey("pulumi", "disable-default-providers") 1323 c[k] = config.NewValue(tt.disabled) 1324 1325 expectedCreated := 4 1326 if tt.expectFail { 1327 expectedCreated = 0 1328 } 1329 update := MakeBasicLifecycleSteps(t, expectedCreated)[:1] 1330 update[0].ExpectFailure = tt.expectFail 1331 p := &TestPlan{ 1332 Options: UpdateOptions{Host: host}, 1333 Steps: update, 1334 Config: c, 1335 } 1336 p.Run(t, nil) 1337 }) 1338 } 1339 } 1340 1341 func TestProviderVersionAssignment(t *testing.T) { 1342 t.Parallel() 1343 1344 prog := func(opts ...deploytest.ResourceOptions) deploytest.ProgramFunc { 1345 1346 return func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 1347 _, _, _, err := monitor.RegisterResource("pkgA:r:typA", "resA", true, opts...) 1348 if err != nil { 1349 return err 1350 } 1351 _, _, _, err = monitor.RegisterResource("pulumi:providers:pkgA", "provA", true, opts...) 1352 if err != nil { 1353 return err 1354 } 1355 return nil 1356 } 1357 } 1358 1359 cases := []struct { 1360 name string 1361 plugins []workspace.PluginSpec 1362 snapshot *deploy.Snapshot 1363 validate func(t *testing.T, r *resource.State) 1364 versions []string 1365 prog deploytest.ProgramFunc 1366 }{ 1367 { 1368 name: "empty", 1369 versions: []string{"1.0.0"}, 1370 validate: func(*testing.T, *resource.State) {}, 1371 prog: prog(), 1372 }, 1373 { 1374 name: "default-version", 1375 versions: []string{"1.0.0", "1.1.0"}, 1376 plugins: []workspace.PluginSpec{{ 1377 Name: "pkgA", 1378 Version: &semver.Version{Major: 1, Minor: 1}, 1379 PluginDownloadURL: "example.com/default", 1380 Kind: workspace.ResourcePlugin, 1381 }}, 1382 validate: func(t *testing.T, r *resource.State) { 1383 if providers.IsProviderType(r.Type) && !providers.IsDefaultProvider(r.URN) { 1384 assert.Equal(t, r.Inputs["version"].StringValue(), "1.1.0") 1385 assert.Equal(t, r.Inputs["pluginDownloadURL"].StringValue(), "example.com/default") 1386 } 1387 }, 1388 prog: prog(), 1389 }, 1390 { 1391 name: "specified-provider", 1392 versions: []string{"1.0.0", "1.1.0"}, 1393 plugins: []workspace.PluginSpec{{ 1394 Name: "pkgA", 1395 Version: &semver.Version{Major: 1, Minor: 1}, 1396 Kind: workspace.ResourcePlugin, 1397 }}, 1398 validate: func(t *testing.T, r *resource.State) { 1399 if providers.IsProviderType(r.Type) && !providers.IsDefaultProvider(r.URN) { 1400 _, hasVersion := r.Inputs["version"] 1401 assert.False(t, hasVersion) 1402 assert.Equal(t, r.Inputs["pluginDownloadURL"].StringValue(), "example.com/download") 1403 } 1404 }, 1405 prog: prog(deploytest.ResourceOptions{PluginDownloadURL: "example.com/download"}), 1406 }, 1407 { 1408 name: "higher-in-snapshot", 1409 versions: []string{"1.3.0", "1.1.0"}, 1410 prog: prog(), 1411 plugins: []workspace.PluginSpec{{ 1412 Name: "pkgA", 1413 Version: &semver.Version{Major: 1, Minor: 1}, 1414 Kind: workspace.ResourcePlugin, 1415 }}, 1416 snapshot: &deploy.Snapshot{ 1417 Resources: []*resource.State{ 1418 { 1419 Type: "providers:pulumi:pkgA", 1420 URN: "this:is:a:urn::ofaei", 1421 Inputs: map[resource.PropertyKey]resource.PropertyValue{ 1422 "version": resource.NewPropertyValue("1.3.0"), 1423 }, 1424 }, 1425 }, 1426 }, 1427 validate: func(t *testing.T, r *resource.State) { 1428 if providers.IsProviderType(r.Type) && !providers.IsDefaultProvider(r.URN) { 1429 assert.Equal(t, r.Inputs["version"].StringValue(), "1.1.0") 1430 } 1431 }, 1432 }, 1433 } 1434 for _, c := range cases { 1435 c := c 1436 t.Run(c.name, func(t *testing.T) { 1437 t.Parallel() 1438 program := deploytest.NewLanguageRuntime(c.prog, c.plugins...) 1439 loaders := []*deploytest.ProviderLoader{} 1440 for _, v := range c.versions { 1441 loaders = append(loaders, 1442 deploytest.NewProviderLoader("pkgA", semver.MustParse(v), func() (plugin.Provider, error) { 1443 return &deploytest.Provider{}, nil 1444 })) 1445 } 1446 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 1447 1448 update := []TestStep{{Op: Update, Validate: func( 1449 project workspace.Project, target deploy.Target, entries JournalEntries, 1450 events []Event, res result.Result) result.Result { 1451 snap, err := entries.Snap(target.Snapshot) 1452 require.NoError(t, err) 1453 assert.Len(t, snap.Resources, 3) 1454 for _, r := range snap.Resources { 1455 c.validate(t, r) 1456 } 1457 return nil 1458 }}} 1459 1460 p := &TestPlan{ 1461 Options: UpdateOptions{Host: host}, 1462 Steps: update, 1463 } 1464 p.Run(t, &deploy.Snapshot{}) 1465 }) 1466 } 1467 }