mirror of https://github.com/pulumi/pulumi.git
1264 lines
42 KiB
Go
1264 lines
42 KiB
Go
// Copyright 2020-2024, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package lifecycletest
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/blang/semver"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
. "github.com/pulumi/pulumi/pkg/v3/engine" //nolint:revive
|
|
lt "github.com/pulumi/pulumi/pkg/v3/engine/lifecycletest/framework"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
|
)
|
|
|
|
func TestImportOption(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
readInputs := resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
}
|
|
readOutputs := resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"out": resource.NewNumberProperty(41),
|
|
}
|
|
|
|
// For imports we expect inputs and state to be nil, but when we change to do a read they should both be set to the
|
|
// resource inputs.
|
|
var expectedInputs, expectedState resource.PropertyMap
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
DiffF: func(_ context.Context, req plugin.DiffRequest) (plugin.DiffResult, error) {
|
|
if req.OldOutputs["foo"].DeepEquals(req.NewInputs["foo"]) {
|
|
return plugin.DiffResult{Changes: plugin.DiffNone}, nil
|
|
}
|
|
|
|
diffKind := plugin.DiffUpdate
|
|
if req.NewInputs["foo"].IsString() && req.NewInputs["foo"].StringValue() == "replace" {
|
|
diffKind = plugin.DiffUpdateReplace
|
|
}
|
|
|
|
return plugin.DiffResult{
|
|
Changes: plugin.DiffSome,
|
|
DetailedDiff: map[string]plugin.PropertyDiff{
|
|
"foo": {Kind: diffKind},
|
|
},
|
|
}, nil
|
|
},
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
ReadF: func(_ context.Context, req plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
assert.Equal(t, expectedInputs, req.Inputs)
|
|
assert.Equal(t, expectedState, req.State)
|
|
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: readInputs,
|
|
Outputs: readOutputs,
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
readID, importID, inputs := resource.ID(""), resource.ID("id"), resource.PropertyMap{}
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
var err error
|
|
if readID != "" {
|
|
_, _, err = monitor.ReadResource("pkgA:m:typA", "resA", readID, "", inputs, "", "", "", "")
|
|
} else {
|
|
_, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: inputs,
|
|
ImportID: importID,
|
|
})
|
|
}
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
}
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
// Run the initial update. The import should fail due to a mismatch in inputs between the program and the
|
|
// actual resource state.
|
|
project := p.GetProject()
|
|
_, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
require.ErrorContains(t, err, "step application failed: inputs to import do not match the existing resource")
|
|
|
|
// Run a second update after fixing the inputs. The import should succeed.
|
|
inputs["foo"] = resource.NewStringProperty("bar")
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient,
|
|
func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, err error) error {
|
|
for _, entry := range entries {
|
|
switch urn := entry.Step.URN(); urn {
|
|
case provURN:
|
|
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
|
|
case resURN:
|
|
assert.Equal(t, deploy.OpImport, entry.Step.Op())
|
|
default:
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
}
|
|
}
|
|
return err
|
|
}, "1")
|
|
assert.NoError(t, err)
|
|
assert.Len(t, snap.Resources, 2)
|
|
assert.Equal(t, readInputs, snap.Resources[1].Inputs)
|
|
assert.Equal(t, readOutputs, snap.Resources[1].Outputs)
|
|
|
|
// Now, run another update. The update should succeed and there should be no diffs.
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient,
|
|
func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, err error) error {
|
|
for _, entry := range entries {
|
|
switch urn := entry.Step.URN(); urn {
|
|
case provURN, resURN:
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
default:
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
}
|
|
}
|
|
return err
|
|
}, "2")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, readInputs, snap.Resources[1].Inputs)
|
|
assert.Equal(t, readOutputs, snap.Resources[1].Outputs)
|
|
|
|
// Change a property value and run a third update. The update should succeed.
|
|
inputs["foo"] = resource.NewStringProperty("rab")
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient,
|
|
func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, err error) error {
|
|
for _, entry := range entries {
|
|
switch urn := entry.Step.URN(); urn {
|
|
case provURN:
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
case resURN:
|
|
assert.Equal(t, deploy.OpUpdate, entry.Step.Op())
|
|
default:
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
}
|
|
}
|
|
return err
|
|
}, "3")
|
|
assert.NoError(t, err)
|
|
// This should call update not read, which just returns the passed inputs as outputs.
|
|
assert.Equal(t, inputs, snap.Resources[1].Inputs)
|
|
assert.Equal(t, inputs, snap.Resources[1].Outputs)
|
|
|
|
// Change the property value s.t. the resource requires replacement. The update should fail.
|
|
inputs["foo"] = resource.NewStringProperty("replace")
|
|
_, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "4")
|
|
assert.ErrorContains(t, err, "reviously-imported resources that still specify an ID may not be replaced")
|
|
|
|
// Finally, destroy the stack. The `Delete` function should be called.
|
|
_, err = lt.TestOp(Destroy).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient,
|
|
func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, err error) error {
|
|
for _, entry := range entries {
|
|
switch urn := entry.Step.URN(); urn {
|
|
case provURN, resURN:
|
|
assert.Equal(t, deploy.OpDelete, entry.Step.Op())
|
|
default:
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
}
|
|
}
|
|
return err
|
|
}, "5")
|
|
assert.NoError(t, err)
|
|
|
|
// Now clear the ID to import and run an initial update to create a resource that we will import-replace.
|
|
importID, inputs["foo"] = "", resource.NewStringProperty("bar")
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient,
|
|
func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, err error) error {
|
|
for _, entry := range entries {
|
|
switch urn := entry.Step.URN(); urn {
|
|
case provURN, resURN:
|
|
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
|
|
default:
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
}
|
|
}
|
|
return err
|
|
}, "6")
|
|
assert.NoError(t, err)
|
|
assert.Len(t, snap.Resources, 2)
|
|
// This will have just called create which returns the inputs as outputs.
|
|
assert.Equal(t, inputs, snap.Resources[1].Inputs)
|
|
assert.Equal(t, inputs, snap.Resources[1].Outputs)
|
|
|
|
// Set the import ID to the same ID as the existing resource and run an update. This should produce no changes.
|
|
for _, r := range snap.Resources {
|
|
if r.URN == resURN {
|
|
importID = r.ID
|
|
}
|
|
}
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient,
|
|
func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, err error) error {
|
|
for _, entry := range entries {
|
|
switch urn := entry.Step.URN(); urn {
|
|
case provURN, resURN:
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
default:
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
}
|
|
}
|
|
return err
|
|
}, "7")
|
|
assert.NoError(t, err)
|
|
// This will have 'same'd so the inputs and outputs will be the same as the lat run with create.
|
|
assert.Equal(t, inputs, snap.Resources[1].Inputs)
|
|
assert.Equal(t, inputs, snap.Resources[1].Outputs)
|
|
|
|
// Then set the import ID and run another update. The update should succeed and should show an import-replace and
|
|
// a delete-replaced.
|
|
importID = "id"
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient,
|
|
func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, err error) error {
|
|
for _, entry := range entries {
|
|
switch urn := entry.Step.URN(); urn {
|
|
case provURN:
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
case resURN:
|
|
switch entry.Step.Op() {
|
|
case deploy.OpReplace, deploy.OpImportReplacement:
|
|
assert.Equal(t, importID, entry.Step.New().ID)
|
|
case deploy.OpDeleteReplaced:
|
|
assert.NotEqual(t, importID, entry.Step.Old().ID)
|
|
}
|
|
default:
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
}
|
|
}
|
|
return err
|
|
}, "8")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, readInputs, snap.Resources[1].Inputs)
|
|
assert.Equal(t, readOutputs, snap.Resources[1].Outputs)
|
|
|
|
// Change the program to read a resource rather than creating one.
|
|
readID = "id"
|
|
expectedInputs, expectedState = inputs, inputs
|
|
snap, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient,
|
|
func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, err error) error {
|
|
for _, entry := range entries {
|
|
switch urn := entry.Step.URN(); urn {
|
|
case provURN:
|
|
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
|
|
case resURN:
|
|
assert.Equal(t, deploy.OpRead, entry.Step.Op())
|
|
default:
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
}
|
|
}
|
|
return err
|
|
}, "9")
|
|
assert.NoError(t, err)
|
|
assert.Len(t, snap.Resources, 2)
|
|
assert.Equal(t, readInputs, snap.Resources[1].Inputs)
|
|
assert.Equal(t, readOutputs, snap.Resources[1].Outputs)
|
|
|
|
// Now have the program import the resource. We should see an import-replace and a read-discard.
|
|
readID, importID = "", readID
|
|
expectedInputs, expectedState = nil, nil
|
|
_, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient,
|
|
func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, err error) error {
|
|
for _, entry := range entries {
|
|
switch urn := entry.Step.URN(); urn {
|
|
case provURN:
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
case resURN:
|
|
switch entry.Step.Op() {
|
|
case deploy.OpReplace, deploy.OpImportReplacement:
|
|
assert.Equal(t, importID, entry.Step.New().ID)
|
|
case deploy.OpDiscardReplaced:
|
|
assert.Equal(t, importID, entry.Step.Old().ID)
|
|
}
|
|
default:
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
}
|
|
}
|
|
return err
|
|
}, "10")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, readInputs, snap.Resources[1].Inputs)
|
|
assert.Equal(t, readOutputs, snap.Resources[1].Outputs)
|
|
}
|
|
|
|
// TestImportWithDifferingImportIdentifierFormat tests importing a resource that has a different format of identifier
|
|
// for the import input than for the ID property, ensuring that a second update does not result in a replace.
|
|
func TestImportWithDifferingImportIdentifierFormat(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
DiffF: func(_ context.Context, req plugin.DiffRequest) (plugin.DiffResult, error) {
|
|
if req.OldOutputs["foo"].DeepEquals(req.NewInputs["foo"]) {
|
|
return plugin.DiffResult{Changes: plugin.DiffNone}, nil
|
|
}
|
|
|
|
return plugin.DiffResult{
|
|
Changes: plugin.DiffSome,
|
|
DetailedDiff: map[string]plugin.PropertyDiff{
|
|
"foo": {Kind: plugin.DiffUpdate},
|
|
},
|
|
}, nil
|
|
},
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
// This ID is deliberately not the same as the ID used to import.
|
|
ID: "id",
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
},
|
|
Outputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
},
|
|
// The import ID is deliberately not the same as the ID returned from Read.
|
|
ImportID: resource.ID("import-id"),
|
|
})
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
}
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
// Run the initial update. The import should succeed.
|
|
project := p.GetProject()
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient,
|
|
func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, err error) error {
|
|
for _, entry := range entries {
|
|
switch urn := entry.Step.URN(); urn {
|
|
case provURN:
|
|
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
|
|
case resURN:
|
|
assert.Equal(t, deploy.OpImport, entry.Step.Op())
|
|
default:
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
}
|
|
}
|
|
return err
|
|
}, "0")
|
|
assert.NoError(t, err)
|
|
assert.Len(t, snap.Resources, 2)
|
|
|
|
// Now, run another update. The update should succeed and there should be no diffs.
|
|
_, err = lt.TestOp(Update).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient,
|
|
func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, err error) error {
|
|
for _, entry := range entries {
|
|
switch urn := entry.Step.URN(); urn {
|
|
case provURN, resURN:
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
default:
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
}
|
|
}
|
|
return err
|
|
}, "1")
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestImportUpdatedID(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := <.TestPlan{}
|
|
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
importID := resource.ID("myID")
|
|
actualID := resource.ID("myNewID")
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
ID: actualID,
|
|
Outputs: resource.PropertyMap{},
|
|
Inputs: resource.PropertyMap{},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
resp, err := monitor.RegisterResource("pkgA:m:typA", "resA", false, deploytest.ResourceOptions{
|
|
ImportID: importID,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, actualID, resp.ID)
|
|
return nil
|
|
})
|
|
p.Options.HostF = deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
p.Options.T = t
|
|
|
|
p.Steps = []lt.TestStep{{Op: Refresh, SkipPreview: true}}
|
|
|
|
// Refresh requires at least one resource in order to proceed.
|
|
stackURN := resource.URN("urn:pulumi:stack::stack::pulumi:pulumi:Stack::foo")
|
|
stackResource := newResource(
|
|
stackURN,
|
|
"",
|
|
"foo",
|
|
"",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
false,
|
|
)
|
|
snap := p.Run(t, &deploy.Snapshot{Resources: []*resource.State{stackResource}})
|
|
|
|
require.NotEmpty(t, snap.Resources)
|
|
|
|
for _, resource := range snap.Resources {
|
|
switch urn := resource.URN; urn {
|
|
case provURN, stackURN:
|
|
// continue
|
|
case resURN:
|
|
assert.Equal(t, actualID, resource.ID)
|
|
default:
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
}
|
|
}
|
|
}
|
|
|
|
const importSchema = `{
|
|
"version": "0.0.1",
|
|
"name": "pkgA",
|
|
"resources": {
|
|
"pkgA:m:typA": {
|
|
"inputProperties": {
|
|
"foo": {
|
|
"type": "string"
|
|
},
|
|
"frob": {
|
|
"type": "number"
|
|
}
|
|
},
|
|
"requiredInputs": [
|
|
"frob"
|
|
],
|
|
"properties": {
|
|
"foo": {
|
|
"type": "string"
|
|
},
|
|
"frob": {
|
|
"type": "number"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
func diffImportResource(
|
|
_ context.Context,
|
|
req plugin.DiffRequest,
|
|
) (plugin.DiffResult, error) {
|
|
if req.OldOutputs["foo"].DeepEquals(req.NewInputs["foo"]) && req.OldOutputs["frob"].DeepEquals(req.NewInputs["frob"]) {
|
|
return plugin.DiffResult{Changes: plugin.DiffNone}, nil
|
|
}
|
|
|
|
detailedDiff := make(map[string]plugin.PropertyDiff)
|
|
if !req.OldOutputs["foo"].DeepEquals(req.NewInputs["foo"]) {
|
|
detailedDiff["foo"] = plugin.PropertyDiff{Kind: plugin.DiffUpdate}
|
|
}
|
|
if !req.OldOutputs["frob"].DeepEquals(req.NewInputs["frob"]) {
|
|
detailedDiff["frob"] = plugin.PropertyDiff{Kind: plugin.DiffUpdate}
|
|
}
|
|
|
|
return plugin.DiffResult{
|
|
Changes: plugin.DiffSome,
|
|
DetailedDiff: detailedDiff,
|
|
}, nil
|
|
}
|
|
|
|
func TestImportPlan(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
readInputs := resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
}
|
|
readOutputs := resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
}
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
GetSchemaF: func(context.Context, plugin.GetSchemaRequest) (plugin.GetSchemaResponse, error) {
|
|
return plugin.GetSchemaResponse{Schema: []byte(importSchema)}, nil
|
|
},
|
|
DiffF: diffImportResource,
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
ID: "actual-id",
|
|
Inputs: readInputs,
|
|
Outputs: readOutputs,
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{})
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
}
|
|
|
|
// Run the initial update.
|
|
project := p.GetProject()
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NoError(t, err)
|
|
|
|
// Run an import.
|
|
snap, err = lt.ImportOp([]deploy.Import{{
|
|
Type: "pkgA:m:typA",
|
|
Name: "resB",
|
|
ID: "imported-id",
|
|
}}).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, snap.Resources, 4)
|
|
|
|
// Import should save the ID, inputs and outputs
|
|
assert.Equal(t, resource.ID("actual-id"), snap.Resources[3].ID)
|
|
assert.Equal(t, readInputs, snap.Resources[3].Inputs)
|
|
assert.Equal(t, readOutputs, snap.Resources[3].Outputs)
|
|
|
|
// Import should set Created and Modified timestamps on state.
|
|
for _, r := range snap.Resources {
|
|
assert.NotNil(t, r.Created)
|
|
assert.NotNil(t, r.Modified)
|
|
}
|
|
}
|
|
|
|
func TestImportIgnoreChanges(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
DiffF: diffImportResource,
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
Outputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("foo"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
ImportID: "import-id",
|
|
IgnoreChanges: []string{"foo"},
|
|
})
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
}
|
|
|
|
project := p.GetProject()
|
|
snap, err := lt.TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Len(t, snap.Resources, 2)
|
|
assert.Equal(t, resource.NewStringProperty("bar"), snap.Resources[1].Outputs["foo"])
|
|
}
|
|
|
|
func TestImportPlanExistingImport(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
GetSchemaF: func(context.Context, plugin.GetSchemaRequest) (plugin.GetSchemaResponse, error) {
|
|
return plugin.GetSchemaResponse{Schema: []byte(importSchema)}, nil
|
|
},
|
|
DiffF: diffImportResource,
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
Outputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
resp, err := monitor.RegisterResource("pulumi:pulumi:Stack", "test", false)
|
|
require.NoError(t, err)
|
|
|
|
_, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
ImportID: "imported-id",
|
|
Parent: resp.URN,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
err = monitor.RegisterResourceOutputs(resp.URN, resource.PropertyMap{})
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
}
|
|
|
|
// Run the initial update.
|
|
project := p.GetProject()
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NoError(t, err)
|
|
|
|
// Run an import with a different ID. This should fail.
|
|
_, err = lt.ImportOp([]deploy.Import{{
|
|
Type: "pkgA:m:typA",
|
|
Name: "resA",
|
|
ID: "imported-id-2",
|
|
}}).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
assert.Error(t, err)
|
|
|
|
// Run an import with a matching ID. This should succeed and do nothing.
|
|
snap, err = lt.ImportOp([]deploy.Import{{
|
|
Type: "pkgA:m:typA",
|
|
Name: "resA",
|
|
ID: "imported-id",
|
|
}}).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient,
|
|
func(_ workspace.Project, _ deploy.Target, entries JournalEntries, _ []Event, _ error) error {
|
|
for _, e := range entries {
|
|
assert.Equal(t, deploy.OpSame, e.Step.Op())
|
|
}
|
|
return nil
|
|
}, "2")
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, snap.Resources, 3)
|
|
}
|
|
|
|
func TestImportPlanEmptyState(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
GetSchemaF: func(context.Context, plugin.GetSchemaRequest) (plugin.GetSchemaResponse, error) {
|
|
return plugin.GetSchemaResponse{Schema: []byte(importSchema)}, nil
|
|
},
|
|
DiffF: diffImportResource,
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
Outputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
programF := deploytest.NewLanguageRuntimeF(nil)
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
}
|
|
|
|
// Run the initial import.
|
|
project := p.GetProject()
|
|
snap, err := lt.ImportOp([]deploy.Import{{
|
|
Type: "pkgA:m:typA",
|
|
Name: "resB",
|
|
ID: "imported-id",
|
|
}}).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, snap.Resources, 3)
|
|
}
|
|
|
|
func TestImportPlanSpecificProvider(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
GetSchemaF: func(context.Context, plugin.GetSchemaRequest) (plugin.GetSchemaResponse, error) {
|
|
return plugin.GetSchemaResponse{Schema: []byte(importSchema)}, nil
|
|
},
|
|
DiffF: diffImportResource,
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
Outputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pulumi:providers:pkgA", "provA", true)
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
}
|
|
|
|
// Run the initial update.
|
|
project := p.GetProject()
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NoError(t, err)
|
|
|
|
snap, err = lt.ImportOp([]deploy.Import{{
|
|
Type: "pkgA:m:typA",
|
|
Name: "resB",
|
|
ID: "imported-id",
|
|
Provider: p.NewProviderURN("pkgA", "provA", ""),
|
|
}}).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, snap.Resources, 3)
|
|
}
|
|
|
|
func TestImportPlanSpecificProperties(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
GetSchemaF: func(context.Context, plugin.GetSchemaRequest) (plugin.GetSchemaResponse, error) {
|
|
return plugin.GetSchemaResponse{Schema: []byte(importSchema)}, nil
|
|
},
|
|
DiffF: diffImportResource,
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
"baz": resource.NewNumberProperty(2),
|
|
},
|
|
Outputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
"baz": resource.NewNumberProperty(2),
|
|
},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
CheckF: func(
|
|
_ context.Context,
|
|
req plugin.CheckRequest,
|
|
) (plugin.CheckResponse, error) {
|
|
// Error unless "foo" and "frob" are in news
|
|
|
|
if _, has := req.News["foo"]; !has {
|
|
return plugin.CheckResponse{}, errors.New("Need foo")
|
|
}
|
|
|
|
if _, has := req.News["frob"]; !has {
|
|
return plugin.CheckResponse{}, errors.New("Need frob")
|
|
}
|
|
|
|
return plugin.CheckResponse{Properties: req.News}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pulumi:providers:pkgA", "provA", true)
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
}
|
|
|
|
// Run the initial update.
|
|
project := p.GetProject()
|
|
snap, err := lt.TestOp(Update).RunStep(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil, "0")
|
|
assert.NoError(t, err)
|
|
|
|
// Import specifying to use just foo and frob
|
|
snap, err = lt.ImportOp([]deploy.Import{{
|
|
Type: "pkgA:m:typA",
|
|
Name: "resB",
|
|
ID: "imported-id",
|
|
Provider: p.NewProviderURN("pkgA", "provA", ""),
|
|
Properties: []string{"foo", "frob"},
|
|
}}).RunStep(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil, "1")
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, snap.Resources, 3)
|
|
|
|
// We should still have the baz output but will be missing its input
|
|
assert.Equal(t, resource.NewNumberProperty(2), snap.Resources[2].Outputs["baz"])
|
|
assert.NotContains(t, snap.Resources[2].Inputs, "baz")
|
|
}
|
|
|
|
// Test that we can import one resource, then import another resource with the first one as a parent.
|
|
func TestImportIntoParent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
GetSchemaF: func(context.Context, plugin.GetSchemaRequest) (plugin.GetSchemaResponse, error) {
|
|
return plugin.GetSchemaResponse{Schema: []byte(importSchema)}, nil
|
|
},
|
|
DiffF: diffImportResource,
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
Properties: req.Properties,
|
|
Status: resource.StatusUnknown,
|
|
}, errors.New("not implemented")
|
|
},
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
Outputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
programF := deploytest.NewLanguageRuntimeF(nil)
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
}
|
|
|
|
// Run the initial import.
|
|
project := p.GetProject()
|
|
snap, err := lt.ImportOp([]deploy.Import{
|
|
{
|
|
Type: "pkgA:m:typA",
|
|
Name: "resB",
|
|
ID: "imported-idB",
|
|
Parent: p.NewURN("pkgA:m:typA", "resA", ""),
|
|
},
|
|
{
|
|
Type: "pkgA:m:typA",
|
|
Name: "resA",
|
|
ID: "imported-idA",
|
|
},
|
|
}).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, snap.Resources, 4)
|
|
}
|
|
|
|
func TestImportComponent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
GetSchemaF: func(context.Context, plugin.GetSchemaRequest) (plugin.GetSchemaResponse, error) {
|
|
return plugin.GetSchemaResponse{Schema: []byte(importSchema)}, nil
|
|
},
|
|
DiffF: diffImportResource,
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{Status: resource.StatusUnknown}, errors.New("not implemented")
|
|
},
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
Outputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
programF := deploytest.NewLanguageRuntimeF(nil)
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
}
|
|
|
|
// Run the initial import.
|
|
project := p.GetProject()
|
|
snap, err := lt.ImportOp([]deploy.Import{
|
|
{
|
|
Type: "my-component",
|
|
Name: "comp",
|
|
Component: true,
|
|
},
|
|
{
|
|
Type: "pkgA:m:typA",
|
|
Name: "resB",
|
|
ID: "imported-id",
|
|
Parent: p.NewURN("my-component", "comp", ""),
|
|
},
|
|
}).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, snap.Resources, 4)
|
|
|
|
// Ensure that the resource 2 is the component.
|
|
comp := snap.Resources[2]
|
|
assert.Equal(t, resource.URN("urn:pulumi:test::test::my-component::comp"), comp.URN)
|
|
// Ensure it's marked as a component.
|
|
assert.False(t, comp.Custom, "expected component resource to not be marked as custom")
|
|
|
|
// Ensure resource 3 is the custom resource
|
|
custom := snap.Resources[3]
|
|
assert.Equal(t, resource.URN("urn:pulumi:test::test::my-component$pkgA:m:typA::resB"), custom.URN)
|
|
// Ensure it's marked as custom.
|
|
assert.True(t, custom.Custom, "expected custom resource to be marked as custom")
|
|
}
|
|
|
|
func TestImportRemoteComponent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("mlc", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{}, nil
|
|
}),
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
GetSchemaF: func(context.Context, plugin.GetSchemaRequest) (plugin.GetSchemaResponse, error) {
|
|
return plugin.GetSchemaResponse{Schema: []byte(importSchema)}, nil
|
|
},
|
|
DiffF: diffImportResource,
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{Status: resource.StatusUnknown}, errors.New("not implemented")
|
|
},
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
Outputs: resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
programF := deploytest.NewLanguageRuntimeF(nil)
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
}
|
|
|
|
// Run the initial import.
|
|
project := p.GetProject()
|
|
snap, err := lt.ImportOp([]deploy.Import{
|
|
{
|
|
Type: "mlc:index:Component",
|
|
Name: "comp",
|
|
Component: true,
|
|
Remote: true,
|
|
},
|
|
{
|
|
Type: "pkgA:m:typA",
|
|
Name: "resB",
|
|
ID: "imported-id",
|
|
Parent: p.NewURN("mlc:index:Component", "comp", ""),
|
|
},
|
|
}).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Len(t, snap.Resources, 5)
|
|
|
|
// Ensure that the resource 3 is the component.
|
|
comp := snap.Resources[3]
|
|
assert.Equal(t, resource.URN("urn:pulumi:test::test::mlc:index:Component::comp"), comp.URN)
|
|
// Ensure it's marked as a component.
|
|
assert.False(t, comp.Custom, "expected component resource to not be marked as custom")
|
|
|
|
// Ensure resource 4 is the custom resource
|
|
custom := snap.Resources[4]
|
|
assert.Equal(t, resource.URN("urn:pulumi:test::test::mlc:index:Component$pkgA:m:typA::resB"), custom.URN)
|
|
// Ensure it's marked as custom.
|
|
assert.True(t, custom.Custom, "expected custom resource to be marked as custom")
|
|
}
|
|
|
|
// Regression test for https://github.com/pulumi/pulumi-gcp/issues/1900
|
|
// Check that if a provider normalizes an input we don't display that as a diff.
|
|
func TestImportInputDiff(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
upInputs := resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("barz"),
|
|
}
|
|
readInputs := resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
}
|
|
readOutputs := resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
"frob": resource.NewNumberProperty(1),
|
|
}
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
GetSchemaF: func(context.Context, plugin.GetSchemaRequest) (plugin.GetSchemaResponse, error) {
|
|
return plugin.GetSchemaResponse{Schema: []byte(importSchema)}, nil
|
|
},
|
|
DiffF: func(context.Context, plugin.DiffRequest) (plugin.DiffResult, error) {
|
|
return plugin.DiffResult{
|
|
Changes: plugin.DiffNone,
|
|
StableKeys: []resource.PropertyKey{
|
|
"foo",
|
|
},
|
|
}, nil
|
|
},
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
ID: "actual-id",
|
|
Inputs: readInputs,
|
|
Outputs: readOutputs,
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
_, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
Inputs: upInputs,
|
|
})
|
|
assert.NoError(t, err)
|
|
return nil
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
}
|
|
project := p.GetProject()
|
|
|
|
// Run an import.
|
|
snap, err := lt.ImportOp([]deploy.Import{{
|
|
Type: "pkgA:m:typA",
|
|
Name: "resA",
|
|
ID: "imported-id",
|
|
}}).RunStep(
|
|
project, p.GetTarget(t, nil), p.Options, false, p.BackendClient,
|
|
func(project workspace.Project, target deploy.Target, entries JournalEntries, events []Event, err error) error {
|
|
lt.AssertDisplay(t, events, filepath.Join("testdata", "output", t.Name(), "import"))
|
|
return err
|
|
}, "0")
|
|
assert.NoError(t, err)
|
|
// 3 because Import magic's up a Stack resource.
|
|
assert.Len(t, snap.Resources, 3)
|
|
|
|
// Import should save the ID, inputs and outputs
|
|
assert.Equal(t, resource.ID("actual-id"), snap.Resources[2].ID)
|
|
assert.Equal(t, readInputs, snap.Resources[2].Inputs)
|
|
assert.Equal(t, readOutputs, snap.Resources[2].Outputs)
|
|
|
|
// Run an update that changes the input but the provider normalizes it.
|
|
snap, err = lt.TestOp(Update).RunStep(
|
|
project, p.GetTarget(t, snap), p.Options, false, p.BackendClient,
|
|
func(project workspace.Project, target deploy.Target, entries JournalEntries, events []Event, err error) error {
|
|
lt.AssertDisplay(t, events, filepath.Join("testdata", "output", t.Name(), "up"))
|
|
return err
|
|
}, "1")
|
|
assert.NoError(t, err)
|
|
|
|
// Update should have updated to the new inputs from the program.
|
|
assert.Equal(t, resource.ID("actual-id"), snap.Resources[1].ID)
|
|
assert.Equal(t, upInputs, snap.Resources[1].Inputs)
|
|
assert.Equal(t, readOutputs, snap.Resources[1].Outputs)
|
|
}
|