github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/engine/lifecycletest/import_test.go (about) 1 package lifecycletest 2 3 import ( 4 "errors" 5 "testing" 6 7 "github.com/blang/semver" 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 11 . "github.com/pulumi/pulumi/pkg/v3/engine" 12 "github.com/pulumi/pulumi/pkg/v3/resource/deploy" 13 "github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest" 14 "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 15 "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" 16 "github.com/pulumi/pulumi/sdk/v3/go/common/util/result" 17 "github.com/pulumi/pulumi/sdk/v3/go/common/workspace" 18 ) 19 20 func TestImportOption(t *testing.T) { 21 t.Parallel() 22 23 loaders := []*deploytest.ProviderLoader{ 24 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 25 return &deploytest.Provider{ 26 DiffF: func(urn resource.URN, id resource.ID, 27 olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) { 28 29 if olds["foo"].DeepEquals(news["foo"]) { 30 return plugin.DiffResult{Changes: plugin.DiffNone}, nil 31 } 32 33 diffKind := plugin.DiffUpdate 34 if news["foo"].IsString() && news["foo"].StringValue() == "replace" { 35 diffKind = plugin.DiffUpdateReplace 36 } 37 38 return plugin.DiffResult{ 39 Changes: plugin.DiffSome, 40 DetailedDiff: map[string]plugin.PropertyDiff{ 41 "foo": {Kind: diffKind}, 42 }, 43 }, nil 44 }, 45 CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64, 46 preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) { 47 48 return "created-id", news, resource.StatusOK, nil 49 }, 50 ReadF: func(urn resource.URN, id resource.ID, 51 inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) { 52 53 return plugin.ReadResult{ 54 Inputs: resource.PropertyMap{ 55 "foo": resource.NewStringProperty("bar"), 56 }, 57 Outputs: resource.PropertyMap{ 58 "foo": resource.NewStringProperty("bar"), 59 }, 60 }, resource.StatusOK, nil 61 }, 62 }, nil 63 }), 64 } 65 66 readID, importID, inputs := resource.ID(""), resource.ID("id"), resource.PropertyMap{} 67 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 68 var err error 69 if readID != "" { 70 _, _, err = monitor.ReadResource("pkgA:m:typA", "resA", readID, "", resource.PropertyMap{}, "", "") 71 } else { 72 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 73 Inputs: inputs, 74 ImportID: importID, 75 }) 76 } 77 assert.NoError(t, err) 78 return nil 79 }) 80 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 81 82 p := &TestPlan{ 83 Options: UpdateOptions{Host: host}, 84 } 85 provURN := p.NewProviderURN("pkgA", "default", "") 86 resURN := p.NewURN("pkgA:m:typA", "resA", "") 87 88 // Run the initial update. The import should fail due to a mismatch in inputs between the program and the 89 // actual resource state. 90 project := p.GetProject() 91 _, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil) 92 assert.NotNil(t, res) 93 94 // Run a second update after fixing the inputs. The import should succeed. 95 inputs["foo"] = resource.NewStringProperty("bar") 96 snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, 97 func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, res result.Result) result.Result { 98 for _, entry := range entries { 99 switch urn := entry.Step.URN(); urn { 100 case provURN: 101 assert.Equal(t, deploy.OpCreate, entry.Step.Op()) 102 case resURN: 103 assert.Equal(t, deploy.OpImport, entry.Step.Op()) 104 default: 105 t.Fatalf("unexpected resource %v", urn) 106 } 107 } 108 return res 109 }) 110 assert.Nil(t, res) 111 assert.Len(t, snap.Resources, 2) 112 113 // Now, run another update. The update should succeed and there should be no diffs. 114 snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, 115 func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, res result.Result) result.Result { 116 for _, entry := range entries { 117 switch urn := entry.Step.URN(); urn { 118 case provURN, resURN: 119 assert.Equal(t, deploy.OpSame, entry.Step.Op()) 120 default: 121 t.Fatalf("unexpected resource %v", urn) 122 } 123 } 124 return res 125 }) 126 assert.Nil(t, res) 127 128 // Change a property value and run a third update. The update should succeed. 129 inputs["foo"] = resource.NewStringProperty("rab") 130 snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, 131 func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, res result.Result) result.Result { 132 for _, entry := range entries { 133 switch urn := entry.Step.URN(); urn { 134 case provURN: 135 assert.Equal(t, deploy.OpSame, entry.Step.Op()) 136 case resURN: 137 assert.Equal(t, deploy.OpUpdate, entry.Step.Op()) 138 default: 139 t.Fatalf("unexpected resource %v", urn) 140 } 141 } 142 return res 143 }) 144 assert.Nil(t, res) 145 146 // Change the property value s.t. the resource requires replacement. The update should fail. 147 inputs["foo"] = resource.NewStringProperty("replace") 148 _, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil) 149 assert.NotNil(t, res) 150 151 // Finally, destroy the stack. The `Delete` function should be called. 152 _, res = TestOp(Destroy).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, 153 func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, res result.Result) result.Result { 154 for _, entry := range entries { 155 switch urn := entry.Step.URN(); urn { 156 case provURN, resURN: 157 assert.Equal(t, deploy.OpDelete, entry.Step.Op()) 158 default: 159 t.Fatalf("unexpected resource %v", urn) 160 } 161 } 162 return res 163 }) 164 assert.Nil(t, res) 165 166 // Now clear the ID to import and run an initial update to create a resource that we will import-replace. 167 importID, inputs["foo"] = "", resource.NewStringProperty("bar") 168 snap, res = TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, 169 func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, res result.Result) result.Result { 170 for _, entry := range entries { 171 switch urn := entry.Step.URN(); urn { 172 case provURN, resURN: 173 assert.Equal(t, deploy.OpCreate, entry.Step.Op()) 174 default: 175 t.Fatalf("unexpected resource %v", urn) 176 } 177 } 178 return res 179 }) 180 assert.Nil(t, res) 181 assert.Len(t, snap.Resources, 2) 182 183 // Set the import ID to the same ID as the existing resource and run an update. This should produce no changes. 184 for _, r := range snap.Resources { 185 if r.URN == resURN { 186 importID = r.ID 187 } 188 } 189 snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, 190 func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, res result.Result) result.Result { 191 for _, entry := range entries { 192 switch urn := entry.Step.URN(); urn { 193 case provURN, resURN: 194 assert.Equal(t, deploy.OpSame, entry.Step.Op()) 195 default: 196 t.Fatalf("unexpected resource %v", urn) 197 } 198 } 199 return res 200 }) 201 assert.Nil(t, res) 202 203 // Then set the import ID and run another update. The update should succeed and should show an import-replace and 204 // a delete-replaced. 205 importID = "id" 206 _, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, 207 func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, res result.Result) result.Result { 208 for _, entry := range entries { 209 switch urn := entry.Step.URN(); urn { 210 case provURN: 211 assert.Equal(t, deploy.OpSame, entry.Step.Op()) 212 case resURN: 213 switch entry.Step.Op() { 214 case deploy.OpReplace, deploy.OpImportReplacement: 215 assert.Equal(t, importID, entry.Step.New().ID) 216 case deploy.OpDeleteReplaced: 217 assert.NotEqual(t, importID, entry.Step.Old().ID) 218 } 219 default: 220 t.Fatalf("unexpected resource %v", urn) 221 } 222 } 223 return res 224 }) 225 assert.Nil(t, res) 226 227 // Change the program to read a resource rather than creating one. 228 readID = "id" 229 snap, res = TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, 230 func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, res result.Result) result.Result { 231 for _, entry := range entries { 232 switch urn := entry.Step.URN(); urn { 233 case provURN: 234 assert.Equal(t, deploy.OpCreate, entry.Step.Op()) 235 case resURN: 236 assert.Equal(t, deploy.OpRead, entry.Step.Op()) 237 default: 238 t.Fatalf("unexpected resource %v", urn) 239 } 240 } 241 return res 242 }) 243 assert.Nil(t, res) 244 assert.Len(t, snap.Resources, 2) 245 246 // Now have the program import the resource. We should see an import-replace and a read-discard. 247 readID, importID = "", readID 248 _, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, 249 func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, res result.Result) result.Result { 250 for _, entry := range entries { 251 switch urn := entry.Step.URN(); urn { 252 case provURN: 253 assert.Equal(t, deploy.OpSame, entry.Step.Op()) 254 case resURN: 255 switch entry.Step.Op() { 256 case deploy.OpReplace, deploy.OpImportReplacement: 257 assert.Equal(t, importID, entry.Step.New().ID) 258 case deploy.OpDiscardReplaced: 259 assert.Equal(t, importID, entry.Step.Old().ID) 260 } 261 default: 262 t.Fatalf("unexpected resource %v", urn) 263 } 264 } 265 return res 266 }) 267 assert.Nil(t, res) 268 } 269 270 // TestImportWithDifferingImportIdentifierFormat tests importing a resource that has a different format of identifier 271 // for the import input than for the ID property, ensuring that a second update does not result in a replace. 272 func TestImportWithDifferingImportIdentifierFormat(t *testing.T) { 273 t.Parallel() 274 275 loaders := []*deploytest.ProviderLoader{ 276 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 277 return &deploytest.Provider{ 278 DiffF: func(urn resource.URN, id resource.ID, 279 olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) { 280 281 if olds["foo"].DeepEquals(news["foo"]) { 282 return plugin.DiffResult{Changes: plugin.DiffNone}, nil 283 } 284 285 return plugin.DiffResult{ 286 Changes: plugin.DiffSome, 287 DetailedDiff: map[string]plugin.PropertyDiff{ 288 "foo": {Kind: plugin.DiffUpdate}, 289 }, 290 }, nil 291 }, 292 CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64, 293 preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) { 294 295 return "created-id", news, resource.StatusOK, nil 296 }, 297 ReadF: func(urn resource.URN, id resource.ID, 298 inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) { 299 300 return plugin.ReadResult{ 301 // This ID is deliberately not the same as the ID used to import. 302 ID: "id", 303 Inputs: resource.PropertyMap{ 304 "foo": resource.NewStringProperty("bar"), 305 }, 306 Outputs: resource.PropertyMap{ 307 "foo": resource.NewStringProperty("bar"), 308 }, 309 }, resource.StatusOK, nil 310 }, 311 }, nil 312 }), 313 } 314 315 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 316 _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 317 Inputs: resource.PropertyMap{ 318 "foo": resource.NewStringProperty("bar"), 319 }, 320 // The import ID is deliberately not the same as the ID returned from Read. 321 ImportID: resource.ID("import-id"), 322 }) 323 assert.NoError(t, err) 324 return nil 325 }) 326 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 327 328 p := &TestPlan{ 329 Options: UpdateOptions{Host: host}, 330 } 331 provURN := p.NewProviderURN("pkgA", "default", "") 332 resURN := p.NewURN("pkgA:m:typA", "resA", "") 333 334 // Run the initial update. The import should succeed. 335 project := p.GetProject() 336 snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, 337 func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, res result.Result) result.Result { 338 for _, entry := range entries { 339 switch urn := entry.Step.URN(); urn { 340 case provURN: 341 assert.Equal(t, deploy.OpCreate, entry.Step.Op()) 342 case resURN: 343 assert.Equal(t, deploy.OpImport, entry.Step.Op()) 344 default: 345 t.Fatalf("unexpected resource %v", urn) 346 } 347 } 348 return res 349 }) 350 assert.Nil(t, res) 351 assert.Len(t, snap.Resources, 2) 352 353 // Now, run another update. The update should succeed and there should be no diffs. 354 _, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, 355 func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, res result.Result) result.Result { 356 for _, entry := range entries { 357 switch urn := entry.Step.URN(); urn { 358 case provURN, resURN: 359 assert.Equal(t, deploy.OpSame, entry.Step.Op()) 360 default: 361 t.Fatalf("unexpected resource %v", urn) 362 } 363 } 364 return res 365 }) 366 assert.Nil(t, res) 367 } 368 369 func TestImportUpdatedID(t *testing.T) { 370 t.Parallel() 371 372 p := &TestPlan{} 373 374 provURN := p.NewProviderURN("pkgA", "default", "") 375 resURN := p.NewURN("pkgA:m:typA", "resA", "") 376 importID := resource.ID("myID") 377 actualID := resource.ID("myNewID") 378 379 loaders := []*deploytest.ProviderLoader{ 380 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 381 return &deploytest.Provider{ 382 ReadF: func( 383 urn resource.URN, id resource.ID, inputs, state resource.PropertyMap, 384 ) (plugin.ReadResult, resource.Status, error) { 385 return plugin.ReadResult{ 386 ID: actualID, 387 Outputs: resource.PropertyMap{}, 388 Inputs: resource.PropertyMap{}, 389 }, resource.StatusOK, nil 390 }, 391 }, nil 392 }), 393 } 394 395 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 396 _, id, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", false, deploytest.ResourceOptions{ 397 ImportID: importID, 398 }) 399 assert.NoError(t, err) 400 assert.Equal(t, actualID, id) 401 return nil 402 }) 403 p.Options.Host = deploytest.NewPluginHost(nil, nil, program, loaders...) 404 405 p.Steps = []TestStep{{Op: Refresh, SkipPreview: true}} 406 snap := p.Run(t, nil) 407 408 for _, resource := range snap.Resources { 409 switch urn := resource.URN; urn { 410 case provURN: 411 // break 412 case resURN: 413 assert.Equal(t, actualID, resource.ID) 414 default: 415 t.Fatalf("unexpected resource %v", urn) 416 } 417 } 418 } 419 420 const importSchema = `{ 421 "version": "0.0.1", 422 "name": "pkgA", 423 "resources": { 424 "pkgA:m:typA": { 425 "inputProperties": { 426 "foo": { 427 "type": "string" 428 }, 429 "frob": { 430 "type": "number" 431 } 432 }, 433 "requiredInputs": [ 434 "frob" 435 ], 436 "properties": { 437 "foo": { 438 "type": "string" 439 }, 440 "frob": { 441 "type": "number" 442 } 443 } 444 } 445 } 446 }` 447 448 func diffImportResource(urn resource.URN, id resource.ID, 449 olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) { 450 451 if olds["foo"].DeepEquals(news["foo"]) && olds["frob"].DeepEquals(news["frob"]) { 452 return plugin.DiffResult{Changes: plugin.DiffNone}, nil 453 } 454 455 detailedDiff := make(map[string]plugin.PropertyDiff) 456 if !olds["foo"].DeepEquals(news["foo"]) { 457 detailedDiff["foo"] = plugin.PropertyDiff{Kind: plugin.DiffUpdate} 458 } 459 if !olds["frob"].DeepEquals(news["frob"]) { 460 detailedDiff["frob"] = plugin.PropertyDiff{Kind: plugin.DiffUpdate} 461 } 462 463 return plugin.DiffResult{ 464 Changes: plugin.DiffSome, 465 DetailedDiff: detailedDiff, 466 }, nil 467 } 468 469 func TestImportPlan(t *testing.T) { 470 t.Parallel() 471 472 loaders := []*deploytest.ProviderLoader{ 473 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 474 return &deploytest.Provider{ 475 GetSchemaF: func(version int) ([]byte, error) { 476 return []byte(importSchema), nil 477 }, 478 DiffF: diffImportResource, 479 CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64, 480 preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) { 481 482 return "created-id", news, resource.StatusOK, nil 483 }, 484 ReadF: func(urn resource.URN, id resource.ID, 485 inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) { 486 487 return plugin.ReadResult{ 488 Inputs: resource.PropertyMap{ 489 "foo": resource.NewStringProperty("bar"), 490 "frob": resource.NewNumberProperty(1), 491 }, 492 Outputs: resource.PropertyMap{ 493 "foo": resource.NewStringProperty("bar"), 494 "frob": resource.NewNumberProperty(1), 495 }, 496 }, resource.StatusOK, nil 497 }, 498 }, nil 499 }), 500 } 501 502 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 503 _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{}) 504 assert.NoError(t, err) 505 return nil 506 }) 507 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 508 509 p := &TestPlan{ 510 Options: UpdateOptions{Host: host}, 511 } 512 513 // Run the initial update. 514 project := p.GetProject() 515 snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil) 516 assert.Nil(t, res) 517 518 // Run an import. 519 snap, res = ImportOp([]deploy.Import{{ 520 Type: "pkgA:m:typA", 521 Name: "resB", 522 ID: "imported-id", 523 }}).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil) 524 525 assert.Nil(t, res) 526 assert.Len(t, snap.Resources, 4) 527 } 528 529 func TestImportIgnoreChanges(t *testing.T) { 530 t.Parallel() 531 532 loaders := []*deploytest.ProviderLoader{ 533 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 534 return &deploytest.Provider{ 535 DiffF: diffImportResource, 536 CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64, 537 preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) { 538 539 return "created-id", news, resource.StatusOK, nil 540 }, 541 ReadF: func(urn resource.URN, id resource.ID, 542 inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) { 543 544 return plugin.ReadResult{ 545 Inputs: resource.PropertyMap{ 546 "foo": resource.NewStringProperty("bar"), 547 "frob": resource.NewNumberProperty(1), 548 }, 549 Outputs: resource.PropertyMap{ 550 "foo": resource.NewStringProperty("bar"), 551 "frob": resource.NewNumberProperty(1), 552 }, 553 }, resource.StatusOK, nil 554 }, 555 }, nil 556 }), 557 } 558 559 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 560 _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 561 Inputs: resource.PropertyMap{ 562 "foo": resource.NewStringProperty("foo"), 563 "frob": resource.NewNumberProperty(1), 564 }, 565 ImportID: "import-id", 566 IgnoreChanges: []string{"foo"}, 567 }) 568 assert.NoError(t, err) 569 return nil 570 }) 571 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 572 573 p := &TestPlan{ 574 Options: UpdateOptions{Host: host}, 575 } 576 577 project := p.GetProject() 578 snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil) 579 assert.Nil(t, res) 580 581 assert.Len(t, snap.Resources, 2) 582 assert.Equal(t, resource.NewStringProperty("bar"), snap.Resources[1].Outputs["foo"]) 583 } 584 585 func TestImportPlanExistingImport(t *testing.T) { 586 t.Parallel() 587 588 loaders := []*deploytest.ProviderLoader{ 589 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 590 return &deploytest.Provider{ 591 GetSchemaF: func(version int) ([]byte, error) { 592 return []byte(importSchema), nil 593 }, 594 DiffF: diffImportResource, 595 CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64, 596 preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) { 597 return "created-id", news, resource.StatusOK, nil 598 }, 599 ReadF: func(urn resource.URN, id resource.ID, 600 inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) { 601 602 return plugin.ReadResult{ 603 Inputs: resource.PropertyMap{ 604 "foo": resource.NewStringProperty("bar"), 605 "frob": resource.NewNumberProperty(1), 606 }, 607 Outputs: resource.PropertyMap{ 608 "foo": resource.NewStringProperty("bar"), 609 "frob": resource.NewNumberProperty(1), 610 }, 611 }, resource.StatusOK, nil 612 }, 613 }, nil 614 }), 615 } 616 617 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 618 stackURN, _, _, err := monitor.RegisterResource("pulumi:pulumi:Stack", "test", false) 619 require.NoError(t, err) 620 621 _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{ 622 Inputs: resource.PropertyMap{ 623 "foo": resource.NewStringProperty("bar"), 624 "frob": resource.NewNumberProperty(1), 625 }, 626 ImportID: "imported-id", 627 Parent: stackURN, 628 }) 629 require.NoError(t, err) 630 631 err = monitor.RegisterResourceOutputs(stackURN, resource.PropertyMap{}) 632 assert.NoError(t, err) 633 return nil 634 }) 635 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 636 637 p := &TestPlan{ 638 Options: UpdateOptions{Host: host}, 639 } 640 641 // Run the initial update. 642 project := p.GetProject() 643 snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil) 644 assert.Nil(t, res) 645 646 // Run an import with a different ID. This should fail. 647 _, res = ImportOp([]deploy.Import{{ 648 Type: "pkgA:m:typA", 649 Name: "resA", 650 ID: "imported-id-2", 651 }}).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil) 652 assert.NotNil(t, res) 653 654 // Run an import with a matching ID. This should succeed and do nothing. 655 snap, res = ImportOp([]deploy.Import{{ 656 Type: "pkgA:m:typA", 657 Name: "resA", 658 ID: "imported-id", 659 }}).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, 660 func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, _ result.Result) result.Result { 661 for _, e := range entries { 662 assert.Equal(t, e.Step.Op(), deploy.OpSame) 663 } 664 return nil 665 }) 666 667 assert.Nil(t, res) 668 assert.Len(t, snap.Resources, 3) 669 } 670 671 func TestImportPlanEmptyState(t *testing.T) { 672 t.Parallel() 673 674 loaders := []*deploytest.ProviderLoader{ 675 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 676 return &deploytest.Provider{ 677 GetSchemaF: func(version int) ([]byte, error) { 678 return []byte(importSchema), nil 679 }, 680 DiffF: diffImportResource, 681 CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64, 682 preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) { 683 return "created-id", news, resource.StatusOK, nil 684 }, 685 ReadF: func(urn resource.URN, id resource.ID, 686 inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) { 687 688 return plugin.ReadResult{ 689 Inputs: resource.PropertyMap{ 690 "foo": resource.NewStringProperty("bar"), 691 "frob": resource.NewNumberProperty(1), 692 }, 693 Outputs: resource.PropertyMap{ 694 "foo": resource.NewStringProperty("bar"), 695 "frob": resource.NewNumberProperty(1), 696 }, 697 }, resource.StatusOK, nil 698 }, 699 }, nil 700 }), 701 } 702 program := deploytest.NewLanguageRuntime(nil) 703 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 704 705 p := &TestPlan{ 706 Options: UpdateOptions{Host: host}, 707 } 708 709 // Run the initial import. 710 project := p.GetProject() 711 snap, res := ImportOp([]deploy.Import{{ 712 Type: "pkgA:m:typA", 713 Name: "resB", 714 ID: "imported-id", 715 }}).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil) 716 717 assert.Nil(t, res) 718 assert.Len(t, snap.Resources, 3) 719 } 720 721 func TestImportPlanSpecificProvider(t *testing.T) { 722 t.Parallel() 723 724 loaders := []*deploytest.ProviderLoader{ 725 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 726 return &deploytest.Provider{ 727 GetSchemaF: func(version int) ([]byte, error) { 728 return []byte(importSchema), nil 729 }, 730 DiffF: diffImportResource, 731 CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64, 732 preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) { 733 return "created-id", news, resource.StatusOK, nil 734 }, 735 ReadF: func(urn resource.URN, id resource.ID, 736 inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) { 737 738 return plugin.ReadResult{ 739 Inputs: resource.PropertyMap{ 740 "foo": resource.NewStringProperty("bar"), 741 "frob": resource.NewNumberProperty(1), 742 }, 743 Outputs: resource.PropertyMap{ 744 "foo": resource.NewStringProperty("bar"), 745 "frob": resource.NewNumberProperty(1), 746 }, 747 }, resource.StatusOK, nil 748 }, 749 }, nil 750 }), 751 } 752 753 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 754 _, _, _, err := monitor.RegisterResource("pulumi:providers:pkgA", "provA", true) 755 assert.NoError(t, err) 756 return nil 757 }) 758 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 759 760 p := &TestPlan{ 761 Options: UpdateOptions{Host: host}, 762 } 763 764 // Run the initial update. 765 project := p.GetProject() 766 snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil) 767 assert.Nil(t, res) 768 769 snap, res = ImportOp([]deploy.Import{{ 770 Type: "pkgA:m:typA", 771 Name: "resB", 772 ID: "imported-id", 773 Provider: p.NewProviderURN("pkgA", "provA", ""), 774 }}).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil) 775 776 assert.Nil(t, res) 777 assert.Len(t, snap.Resources, 3) 778 } 779 780 func TestImportPlanSpecificProperties(t *testing.T) { 781 t.Parallel() 782 783 loaders := []*deploytest.ProviderLoader{ 784 deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { 785 return &deploytest.Provider{ 786 GetSchemaF: func(version int) ([]byte, error) { 787 return []byte(importSchema), nil 788 }, 789 DiffF: diffImportResource, 790 CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64, 791 preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) { 792 return "created-id", news, resource.StatusOK, nil 793 }, 794 ReadF: func(urn resource.URN, id resource.ID, 795 inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) { 796 797 return plugin.ReadResult{ 798 Inputs: resource.PropertyMap{ 799 "foo": resource.NewStringProperty("bar"), 800 "frob": resource.NewNumberProperty(1), 801 "baz": resource.NewNumberProperty(2), 802 }, 803 Outputs: resource.PropertyMap{ 804 "foo": resource.NewStringProperty("bar"), 805 "frob": resource.NewNumberProperty(1), 806 "baz": resource.NewNumberProperty(2), 807 }, 808 }, resource.StatusOK, nil 809 }, 810 CheckF: func( 811 urn resource.URN, olds, news resource.PropertyMap, 812 randomSeed []byte) (resource.PropertyMap, []plugin.CheckFailure, error) { 813 // Error unless "foo" and "frob" are in news 814 815 if _, has := news["foo"]; !has { 816 return nil, nil, errors.New("Need foo") 817 } 818 819 if _, has := news["frob"]; !has { 820 return nil, nil, errors.New("Need frob") 821 } 822 823 return news, nil, nil 824 }, 825 }, nil 826 }), 827 } 828 829 program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { 830 _, _, _, err := monitor.RegisterResource("pulumi:providers:pkgA", "provA", true) 831 assert.NoError(t, err) 832 return nil 833 }) 834 host := deploytest.NewPluginHost(nil, nil, program, loaders...) 835 836 p := &TestPlan{ 837 Options: UpdateOptions{Host: host}, 838 } 839 840 // Run the initial update. 841 project := p.GetProject() 842 snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil) 843 assert.Nil(t, res) 844 845 // Import specifying to use just foo and frob 846 snap, res = ImportOp([]deploy.Import{{ 847 Type: "pkgA:m:typA", 848 Name: "resB", 849 ID: "imported-id", 850 Provider: p.NewProviderURN("pkgA", "provA", ""), 851 Properties: []string{"foo", "frob"}, 852 }}).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil) 853 854 assert.Nil(t, res) 855 assert.Len(t, snap.Resources, 3) 856 857 // We should still have the baz output but will be missing its input 858 assert.Equal(t, resource.NewNumberProperty(2), snap.Resources[2].Outputs["baz"]) 859 assert.NotContains(t, snap.Resources[2].Inputs, "baz") 860 }