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  }