mirror of https://github.com/pulumi/pulumi.git
1420 lines
42 KiB
Go
1420 lines
42 KiB
Go
// Copyright 2016-2023, 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 deploy
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/display"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest"
|
|
"github.com/pulumi/pulumi/pkg/v3/util/gsync"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
|
|
"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/resource/urn"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/testing/diagtest"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestRawPrefix(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
op display.StepOp
|
|
want string
|
|
}{
|
|
{name: "Same", op: OpSame, want: " "},
|
|
{name: "Create", op: OpCreate, want: "+ "},
|
|
{name: "Delete", op: OpDelete, want: "- "},
|
|
{name: "Update", op: OpUpdate, want: "~ "},
|
|
{name: "Replace", op: OpReplace, want: "+-"},
|
|
{name: "CreateReplacement", op: OpCreateReplacement, want: "++"},
|
|
{name: "DeleteReplaced", op: OpDeleteReplaced, want: "--"},
|
|
{name: "Read", op: OpRead, want: "> "},
|
|
{name: "ReadReplacement", op: OpReadReplacement, want: ">>"},
|
|
{name: "Refresh", op: OpRefresh, want: "~ "},
|
|
{name: "ReadDiscard", op: OpReadDiscard, want: "< "},
|
|
{name: "DiscardReplaced", op: OpDiscardReplaced, want: "<<"},
|
|
{name: "Import", op: OpImport, want: "= "},
|
|
{name: "ImportReplacement", op: OpImportReplacement, want: "=>"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Equal(t, tt.want, RawPrefix(tt.op))
|
|
})
|
|
}
|
|
t.Run("panics on unknown", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Panics(t, func() {
|
|
RawPrefix("not-a-real-operation")
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestPastTense(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
op display.StepOp
|
|
want string
|
|
}{
|
|
{"Same", OpSame, "samed"},
|
|
{"Create", OpCreate, "created"},
|
|
{"Replace", OpReplace, "replaced"},
|
|
{"Update", OpUpdate, "updated"},
|
|
|
|
// TODO(dixler) consider fixing this.
|
|
{"CreateReplacement", OpCreateReplacement, "create-replacementd"},
|
|
{"ReadReplacement", OpReadReplacement, "read-replacementd"},
|
|
|
|
{"Refresh", OpRefresh, "refreshed"},
|
|
{"Read", OpRead, "read"},
|
|
{"ReadDiscard", OpReadDiscard, "discarded"},
|
|
{"DiscardReplaced", OpDiscardReplaced, "discarded"},
|
|
{"Delete", OpDelete, "deleted"},
|
|
{"DeleteReplaced", OpDeleteReplaced, "deleted"},
|
|
{"Import", OpImport, "imported"},
|
|
{"ImportReplacement", OpImportReplacement, "imported"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Equal(t, tt.want, PastTense(tt.op))
|
|
})
|
|
}
|
|
t.Run("panics on unknown", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Panics(t, func() {
|
|
PastTense("not-a-real-operation")
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestSameStep(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Apply", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("bad provider state for resource", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &SameStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
old: &resource.State{
|
|
URN: "urn:pulumi:stack::project::type::foo",
|
|
},
|
|
new: &resource.State{
|
|
URN: "urn:pulumi:stack::project::type::foo",
|
|
Type: "pulumi:providers:some-provider",
|
|
},
|
|
}
|
|
_, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "bad provider state for resource")
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestCreateStep(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Apply", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("custom", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("error getting provider", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &CreateStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
new: &resource.State{
|
|
Custom: true,
|
|
// Use denydefaultprovider ID to ensure failure.
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:aws::default_5_42_0::denydefaultprovider",
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "Default provider for 'default_5_42_0' disabled.")
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
t.Run("error in create", func(t *testing.T) {
|
|
t.Parallel()
|
|
expectedErr := errors.New("expected error")
|
|
var createCalled bool
|
|
s := &CreateStep{
|
|
new: &resource.State{
|
|
Custom: true,
|
|
},
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
provider: &deploytest.Provider{
|
|
CreateF: func(context.Context, plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
createCalled = true
|
|
return plugin.CreateResponse{}, expectedErr
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorIs(t, err, expectedErr)
|
|
assert.True(t, createCalled)
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
t.Run("handle InitError", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &CreateStep{
|
|
new: &resource.State{
|
|
Custom: true,
|
|
},
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
provider: &deploytest.Provider{
|
|
CreateF: func(context.Context, plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
Status: resource.StatusPartialFailure,
|
|
}, &plugin.InitError{
|
|
Reasons: []string{
|
|
"intentional error",
|
|
},
|
|
}
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "intentional error")
|
|
assert.Len(t, s.new.InitErrors, 1)
|
|
assert.Equal(t, resource.StatusPartialFailure, status)
|
|
})
|
|
t.Run("error create no ID", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &CreateStep{
|
|
new: &resource.State{
|
|
Custom: true,
|
|
},
|
|
deployment: &Deployment{
|
|
opts: &Options{},
|
|
},
|
|
provider: &deploytest.Provider{
|
|
CreateF: func(context.Context, plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{}, nil
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "provider did not return an ID from Create")
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestDeleteStep(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("isDeletedWith", func(t *testing.T) {
|
|
t.Parallel()
|
|
otherDeletions := map[resource.URN]bool{
|
|
"false-key": false,
|
|
"true-key": true,
|
|
}
|
|
assert.False(t, isDeletedWith("", otherDeletions))
|
|
assert.False(t, isDeletedWith("does-not-exist", otherDeletions))
|
|
assert.False(t, isDeletedWith("false-key", otherDeletions))
|
|
assert.True(t, isDeletedWith("true-key", otherDeletions))
|
|
})
|
|
t.Run("Apply", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("custom", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("error getting provider", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &DeleteStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: false,
|
|
},
|
|
},
|
|
old: &resource.State{
|
|
Custom: true,
|
|
// Use denydefaultprovider ID to ensure failure.
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:aws::default_5_42_0::denydefaultprovider",
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "Default provider for 'default_5_42_0' disabled.")
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestRemovePendingReplaceStep(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("NewRemovePendingReplaceStep", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("panics on old=nil", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Panics(t, func() {
|
|
NewRemovePendingReplaceStep(nil, nil)
|
|
})
|
|
})
|
|
t.Run("panics if not old.PendingReplacement", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Panics(t, func() {
|
|
NewRemovePendingReplaceStep(nil, &resource.State{
|
|
PendingReplacement: false,
|
|
})
|
|
})
|
|
})
|
|
})
|
|
t.Run("Op", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := NewRemovePendingReplaceStep(nil, &resource.State{
|
|
PendingReplacement: true,
|
|
})
|
|
assert.Equal(t, OpRemovePendingReplace, s.Op())
|
|
})
|
|
t.Run("Deployment", func(t *testing.T) {
|
|
t.Parallel()
|
|
d := &Deployment{}
|
|
s := NewRemovePendingReplaceStep(d, &resource.State{
|
|
Type: "expected-value",
|
|
PendingReplacement: true,
|
|
})
|
|
assert.Equal(t, d, s.Deployment())
|
|
})
|
|
t.Run("Type", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := NewRemovePendingReplaceStep(nil, &resource.State{
|
|
Type: "expected-value",
|
|
PendingReplacement: true,
|
|
})
|
|
assert.Equal(t, tokens.Type("expected-value"), s.Type())
|
|
})
|
|
t.Run("Provider", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := NewRemovePendingReplaceStep(nil, &resource.State{
|
|
Provider: "expected-value",
|
|
PendingReplacement: true,
|
|
})
|
|
assert.Equal(t, "expected-value", s.Provider())
|
|
})
|
|
t.Run("URN", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := NewRemovePendingReplaceStep(nil, &resource.State{
|
|
URN: "expected-value",
|
|
PendingReplacement: true,
|
|
})
|
|
assert.Equal(t, resource.URN("expected-value"), s.URN())
|
|
})
|
|
t.Run("Old", func(t *testing.T) {
|
|
t.Parallel()
|
|
old := &resource.State{
|
|
URN: "expected-value",
|
|
PendingReplacement: true,
|
|
}
|
|
s := NewRemovePendingReplaceStep(nil, old)
|
|
assert.Equal(t, old, s.Old())
|
|
})
|
|
t.Run("New", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := NewRemovePendingReplaceStep(nil, &resource.State{
|
|
PendingReplacement: true,
|
|
})
|
|
assert.Equal(t, (*resource.State)(nil), s.New())
|
|
})
|
|
t.Run("Res", func(t *testing.T) {
|
|
t.Parallel()
|
|
old := &resource.State{
|
|
PendingReplacement: true,
|
|
}
|
|
s := NewRemovePendingReplaceStep(nil, old)
|
|
assert.Equal(t, old, s.Res())
|
|
})
|
|
t.Run("Logical", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := NewRemovePendingReplaceStep(nil, &resource.State{
|
|
PendingReplacement: true,
|
|
})
|
|
assert.False(t, s.Logical())
|
|
})
|
|
t.Run("Apply", func(t *testing.T) {
|
|
t.Parallel()
|
|
d := &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
}
|
|
|
|
s := NewRemovePendingReplaceStep(d, &resource.State{
|
|
PendingReplacement: true,
|
|
})
|
|
status, _, err := s.Apply()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
}
|
|
|
|
func TestUpdateStep(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Apply", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("error getting provider", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &UpdateStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
old: &resource.State{},
|
|
new: &resource.State{
|
|
Custom: true,
|
|
// Use denydefaultprovider ID to ensure failure.
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:aws::default_5_42_0::denydefaultprovider",
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "Default provider for 'default_5_42_0' disabled.")
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
t.Run("failure in provider", func(t *testing.T) {
|
|
t.Parallel()
|
|
expectedErr := errors.New("expected error")
|
|
s := &UpdateStep{
|
|
old: &resource.State{},
|
|
new: &resource.State{
|
|
Custom: true,
|
|
// Use denydefaultprovider ID to ensure failure.
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:aws::default_5_42_0::denydefaultprovider",
|
|
},
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
provider: &deploytest.Provider{
|
|
UpdateF: func(context.Context, plugin.UpdateRequest) (plugin.UpdateResponse, error) {
|
|
return plugin.UpdateResponse{}, expectedErr
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorIs(t, err, expectedErr)
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
t.Run("partial failure in provider", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &UpdateStep{
|
|
old: &resource.State{},
|
|
new: &resource.State{
|
|
Custom: true,
|
|
// Use denydefaultprovider ID to ensure failure.
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:aws::default_5_42_0::denydefaultprovider",
|
|
},
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
provider: &deploytest.Provider{
|
|
UpdateF: func(context.Context, plugin.UpdateRequest) (plugin.UpdateResponse, error) {
|
|
return plugin.UpdateResponse{
|
|
Properties: resource.PropertyMap{
|
|
"key": resource.NewStringProperty("expected-value"),
|
|
},
|
|
Status: resource.StatusPartialFailure,
|
|
}, &plugin.InitError{
|
|
Reasons: []string{
|
|
"intentional error",
|
|
},
|
|
}
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "intentional error")
|
|
assert.Equal(t, resource.StatusPartialFailure, status)
|
|
|
|
// News should be updated.
|
|
assert.Len(t, s.new.InitErrors, 1)
|
|
assert.Equal(t, resource.PropertyMap{
|
|
"key": resource.NewStringProperty("expected-value"),
|
|
}, s.new.Outputs)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestReplaceStep(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Deployment", func(t *testing.T) {
|
|
t.Parallel()
|
|
d := &Deployment{}
|
|
s := ReplaceStep{deployment: d}
|
|
assert.Equal(t, d, s.Deployment())
|
|
})
|
|
}
|
|
|
|
func TestReadStep(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Apply", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("error getting provider", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &ReadStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
old: &resource.State{},
|
|
new: &resource.State{
|
|
Custom: true,
|
|
// Use denydefaultprovider ID to ensure failure.
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:aws::default_5_42_0::denydefaultprovider",
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "Default provider for 'default_5_42_0' disabled.")
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
t.Run("failure in provider", func(t *testing.T) {
|
|
t.Parallel()
|
|
expectedErr := errors.New("expected error")
|
|
s := &ReadStep{
|
|
old: &resource.State{},
|
|
new: &resource.State{
|
|
URN: "some-urn",
|
|
ID: "some-id",
|
|
Custom: true,
|
|
// Use denydefaultprovider ID to ensure failure.
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:aws::default_5_42_0::denydefaultprovider",
|
|
},
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
provider: &deploytest.Provider{
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{}, expectedErr
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorIs(t, err, expectedErr)
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
t.Run("partial failure in provider", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &ReadStep{
|
|
old: &resource.State{},
|
|
new: &resource.State{
|
|
URN: "some-urn",
|
|
ID: "some-id",
|
|
Custom: true,
|
|
// Use denydefaultprovider ID to ensure failure.
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:aws::default_5_42_0::denydefaultprovider",
|
|
},
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
provider: &deploytest.Provider{
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
ID: "new-id",
|
|
Inputs: resource.PropertyMap{
|
|
"inputs-key": resource.NewStringProperty("expected-value"),
|
|
},
|
|
Outputs: resource.PropertyMap{
|
|
"outputs-key": resource.NewStringProperty("expected-value"),
|
|
},
|
|
},
|
|
Status: resource.StatusPartialFailure,
|
|
}, &plugin.InitError{
|
|
Reasons: []string{
|
|
"intentional error",
|
|
},
|
|
}
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "intentional error")
|
|
assert.Equal(t, resource.StatusPartialFailure, status)
|
|
|
|
// News should be updated.
|
|
assert.Len(t, s.new.InitErrors, 1)
|
|
assert.Equal(t, (resource.PropertyMap)(nil), s.new.Inputs)
|
|
assert.Equal(t, resource.PropertyMap{
|
|
"outputs-key": resource.NewStringProperty("expected-value"),
|
|
}, s.new.Outputs)
|
|
assert.Equal(t, resource.ID("new-id"), s.new.ID)
|
|
})
|
|
t.Run("unknown id", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &ReadStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
new: &resource.State{
|
|
ID: plugin.UnknownStringValue,
|
|
},
|
|
provider: &deploytest.Provider{
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
panic("should not be called")
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
// News should be updated.
|
|
assert.Equal(t, resource.PropertyMap{}, s.new.Outputs)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestRefreshStepPatterns(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
inputs resource.PropertyMap
|
|
outputs resource.PropertyMap
|
|
readInputs resource.PropertyMap
|
|
readOutputs resource.PropertyMap
|
|
diffResult plugin.DiffResult
|
|
expectedDetailedDiff map[string]plugin.PropertyDiff
|
|
ignoreChanges []string
|
|
}{
|
|
{
|
|
name: "tfbridge 'computed' property changed",
|
|
inputs: resource.PropertyMap{},
|
|
outputs: resource.PropertyMap{
|
|
"etag": resource.NewStringProperty("abc"),
|
|
},
|
|
readInputs: resource.PropertyMap{},
|
|
readOutputs: resource.PropertyMap{
|
|
"etag": resource.NewStringProperty("def"),
|
|
},
|
|
diffResult: plugin.DiffResult{
|
|
// Diff newInputs, newOutputs, oldInputs
|
|
Changes: plugin.DiffNone,
|
|
DetailedDiff: map[string]plugin.PropertyDiff{},
|
|
},
|
|
expectedDetailedDiff: map[string]plugin.PropertyDiff{},
|
|
},
|
|
{
|
|
// Note: this is probably a case where the TF provider has a bug, a pure in property
|
|
// really shouldn't change, but this is common in TF providers.
|
|
name: "tfbridge 'required' property changed",
|
|
inputs: resource.PropertyMap{
|
|
"title": resource.NewStringProperty("test"),
|
|
},
|
|
outputs: resource.PropertyMap{
|
|
"title": resource.NewStringProperty("test"),
|
|
},
|
|
readInputs: resource.PropertyMap{
|
|
"title": resource.NewStringProperty("testtesttest"),
|
|
},
|
|
readOutputs: resource.PropertyMap{
|
|
"title": resource.NewStringProperty("testtesttest"),
|
|
},
|
|
diffResult: plugin.DiffResult{
|
|
// Diff newInputs, newOutputs, oldInputs
|
|
Changes: plugin.DiffSome,
|
|
DetailedDiff: map[string]plugin.PropertyDiff{
|
|
"title": {Kind: plugin.DiffUpdate},
|
|
},
|
|
},
|
|
expectedDetailedDiff: map[string]plugin.PropertyDiff{
|
|
"title": {Kind: plugin.DiffUpdate},
|
|
},
|
|
},
|
|
{
|
|
// Note: this is probably a case where the TF provider has a bug, a pure in property
|
|
// really shouldn't change, but this is common in TF providers.
|
|
name: "tfbridge 'required' property changed w/ ignoreChanges",
|
|
ignoreChanges: []string{"title"},
|
|
inputs: resource.PropertyMap{
|
|
"title": resource.NewStringProperty("test"),
|
|
},
|
|
outputs: resource.PropertyMap{
|
|
"title": resource.NewStringProperty("test"),
|
|
},
|
|
readInputs: resource.PropertyMap{
|
|
"title": resource.NewStringProperty("testtesttest"),
|
|
},
|
|
readOutputs: resource.PropertyMap{
|
|
"title": resource.NewStringProperty("testtesttest"),
|
|
},
|
|
diffResult: plugin.DiffResult{
|
|
// Diff newInputs, newOutputs, oldInputs
|
|
Changes: plugin.DiffNone,
|
|
DetailedDiff: map[string]plugin.PropertyDiff{},
|
|
},
|
|
expectedDetailedDiff: map[string]plugin.PropertyDiff{},
|
|
},
|
|
{
|
|
// Note: this is probably a case where the TF provider has a bug, a pure in property
|
|
// really shouldn't change, but this is common in TF providers.
|
|
name: "tfbridge 'optional' property changed",
|
|
inputs: resource.PropertyMap{},
|
|
outputs: resource.PropertyMap{
|
|
"body": resource.NewStringProperty(""),
|
|
},
|
|
readInputs: resource.PropertyMap{
|
|
// Pretty sure its a bug in tfbridge that it doesn't populate the new value
|
|
// into inputs for an `optional` property. But that's what it does so
|
|
// testing against the current behaviour.
|
|
},
|
|
readOutputs: resource.PropertyMap{
|
|
"body": resource.NewStringProperty("bodybodybody"),
|
|
},
|
|
diffResult: plugin.DiffResult{
|
|
// Diff newInputs, newOutputs, oldInputs
|
|
Changes: plugin.DiffSome,
|
|
DetailedDiff: map[string]plugin.PropertyDiff{
|
|
"body": {Kind: plugin.DiffDelete},
|
|
},
|
|
},
|
|
expectedDetailedDiff: map[string]plugin.PropertyDiff{
|
|
"body": {Kind: plugin.DiffAdd},
|
|
},
|
|
},
|
|
{
|
|
// Note: this is probably a case where the TF provider has a bug, a pure in property
|
|
// really shouldn't change, but this is common in TF providers.
|
|
name: "tfbridge 'optional' property changed w/ ignoreChanges",
|
|
ignoreChanges: []string{"body"},
|
|
inputs: resource.PropertyMap{},
|
|
outputs: resource.PropertyMap{
|
|
"body": resource.NewStringProperty(""),
|
|
},
|
|
readInputs: resource.PropertyMap{
|
|
// Pretty sure its a bug in tfbridge that it doesn't populate the new value
|
|
// into inputs for an `optional` property. But that's what it does so
|
|
// testing against the current behaviour.
|
|
},
|
|
readOutputs: resource.PropertyMap{
|
|
"body": resource.NewStringProperty("bodybodybody"),
|
|
},
|
|
diffResult: plugin.DiffResult{
|
|
// Diff newInputs, newOutputs, oldInputs
|
|
Changes: plugin.DiffNone,
|
|
DetailedDiff: map[string]plugin.PropertyDiff{},
|
|
},
|
|
expectedDetailedDiff: map[string]plugin.PropertyDiff{},
|
|
},
|
|
{
|
|
name: "tfbridge 'optional+computed' property element added",
|
|
inputs: resource.PropertyMap{},
|
|
outputs: resource.PropertyMap{
|
|
"tags": resource.NewObjectProperty(resource.PropertyMap{}),
|
|
},
|
|
readInputs: resource.PropertyMap{},
|
|
readOutputs: resource.PropertyMap{
|
|
"tags": resource.NewObjectProperty((resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
})),
|
|
},
|
|
diffResult: plugin.DiffResult{
|
|
// Diff newInputs, newOutputs, oldInputs
|
|
Changes: plugin.DiffSome,
|
|
DetailedDiff: map[string]plugin.PropertyDiff{
|
|
"tags": {Kind: plugin.DiffUpdate},
|
|
"tags.foo": {Kind: plugin.DiffDelete},
|
|
},
|
|
},
|
|
expectedDetailedDiff: map[string]plugin.PropertyDiff{
|
|
"tags": {Kind: plugin.DiffUpdate},
|
|
"tags.foo": {Kind: plugin.DiffAdd},
|
|
},
|
|
},
|
|
{
|
|
name: "tfbridge 'optional+computed' property element changed",
|
|
inputs: resource.PropertyMap{
|
|
"tags": resource.NewObjectProperty(resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
}),
|
|
},
|
|
outputs: resource.PropertyMap{
|
|
"tags": resource.NewObjectProperty(resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("bar"),
|
|
}),
|
|
},
|
|
readInputs: resource.PropertyMap{
|
|
"tags": resource.NewObjectProperty((resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("baz"),
|
|
})),
|
|
},
|
|
readOutputs: resource.PropertyMap{
|
|
"tags": resource.NewObjectProperty((resource.PropertyMap{
|
|
"foo": resource.NewStringProperty("baz"),
|
|
})),
|
|
},
|
|
diffResult: plugin.DiffResult{
|
|
// Diff newInputs, newOutputs, oldInputs
|
|
Changes: plugin.DiffSome,
|
|
DetailedDiff: map[string]plugin.PropertyDiff{
|
|
"tags.foo": {Kind: plugin.DiffUpdate},
|
|
},
|
|
},
|
|
expectedDetailedDiff: map[string]plugin.PropertyDiff{
|
|
"tags.foo": {Kind: plugin.DiffUpdate},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
s := &RefreshStep{
|
|
old: &resource.State{
|
|
URN: "some-urn",
|
|
ID: "some-id",
|
|
Type: "some-type",
|
|
Custom: true,
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:aws::default_5_42_0::denydefaultprovider",
|
|
Inputs: tc.inputs,
|
|
Outputs: tc.outputs,
|
|
},
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
provider: &deploytest.Provider{
|
|
ReadF: func(_ context.Context, req plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
ID: req.ID,
|
|
Inputs: tc.readInputs,
|
|
Outputs: tc.readOutputs,
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
DiffF: func(context.Context, plugin.DiffRequest) (plugin.DiffResponse, error) {
|
|
return tc.diffResult, nil
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.Equal(t, s.diff.DetailedDiff, tc.expectedDetailedDiff)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
}
|
|
}
|
|
|
|
func TestRefreshStep(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Apply", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("error getting provider", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &RefreshStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
old: &resource.State{
|
|
Custom: true,
|
|
// Use denydefaultprovider ID to ensure failure.
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:aws::default_5_42_0::denydefaultprovider",
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "Default provider for 'default_5_42_0' disabled.")
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
t.Run("failure in provider", func(t *testing.T) {
|
|
t.Parallel()
|
|
expectedErr := errors.New("expected error")
|
|
s := &RefreshStep{
|
|
old: &resource.State{
|
|
URN: "some-urn",
|
|
ID: "some-id",
|
|
Custom: true,
|
|
// Use denydefaultprovider ID to ensure failure.
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:aws::default_5_42_0::denydefaultprovider",
|
|
},
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
provider: &deploytest.Provider{
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{}, expectedErr
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorIs(t, err, expectedErr)
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
t.Run("partial failure in provider", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &RefreshStep{
|
|
old: &resource.State{
|
|
URN: "some-urn",
|
|
ID: "some-id",
|
|
Type: "some-type",
|
|
Custom: true,
|
|
// Use denydefaultprovider ID to ensure failure.
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:aws::default_5_42_0::denydefaultprovider",
|
|
},
|
|
deployment: &Deployment{
|
|
ctx: &plugin.Context{Diag: &deploytest.NoopSink{}},
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
},
|
|
provider: &deploytest.Provider{
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
ID: "new-id",
|
|
Inputs: resource.PropertyMap{
|
|
"inputs-key": resource.NewStringProperty("expected-value"),
|
|
},
|
|
Outputs: resource.PropertyMap{
|
|
"outputs-key": resource.NewStringProperty("expected-value"),
|
|
},
|
|
},
|
|
Status: resource.StatusPartialFailure,
|
|
}, &plugin.InitError{
|
|
Reasons: []string{
|
|
"intentional error",
|
|
},
|
|
}
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.NoError(t, err, "InitError should be discarded")
|
|
assert.Equal(t, resource.StatusPartialFailure, status)
|
|
|
|
// News should be updated.
|
|
assert.Len(t, s.new.InitErrors, 1)
|
|
assert.Equal(t, resource.PropertyMap{
|
|
"outputs-key": resource.NewStringProperty("expected-value"),
|
|
}, s.new.Outputs)
|
|
assert.Equal(t, resource.ID("new-id"), s.new.ID)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestImportStep(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Apply", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("missing parent", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &ImportStep{
|
|
planned: true,
|
|
new: &resource.State{
|
|
Parent: "urn:pulumi:stack::project::foo:bar:Bar::name",
|
|
},
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
olds: map[resource.URN]*resource.State{},
|
|
news: &gsync.Map[urn.URN, *resource.State]{},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "unknown parent")
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
t.Run("getProvider error", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &ImportStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
olds: map[resource.URN]*resource.State{},
|
|
news: &gsync.Map[urn.URN, *resource.State]{},
|
|
},
|
|
new: &resource.State{
|
|
URN: "urn:pulumi:stack::project::foo:bar:Bar::name",
|
|
ID: "some-id",
|
|
Custom: true,
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "bad provider reference")
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
t.Run("provider read error", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("error", func(t *testing.T) {
|
|
t.Parallel()
|
|
expectedErr := errors.New("expected error")
|
|
s := &ImportStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
olds: map[resource.URN]*resource.State{},
|
|
news: &gsync.Map[urn.URN, *resource.State]{},
|
|
},
|
|
new: &resource.State{
|
|
URN: "urn:pulumi:stack::project::foo:bar:Bar::name",
|
|
ID: "some-id",
|
|
Custom: true,
|
|
},
|
|
provider: &deploytest.Provider{
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{}, expectedErr
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorIs(t, err, expectedErr)
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
t.Run("init error", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &ImportStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
olds: map[resource.URN]*resource.State{},
|
|
news: &gsync.Map[urn.URN, *resource.State]{},
|
|
},
|
|
new: &resource.State{
|
|
URN: "urn:pulumi:stack::project::foo:bar:Bar::name",
|
|
ID: "some-id",
|
|
Custom: true,
|
|
},
|
|
provider: &deploytest.Provider{
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{}, &plugin.InitError{
|
|
Reasons: []string{
|
|
"intentional error",
|
|
},
|
|
}
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.Error(t, err)
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
assert.Len(t, s.new.InitErrors, 1)
|
|
})
|
|
t.Run("resource does not exist", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &ImportStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
olds: map[resource.URN]*resource.State{},
|
|
news: &gsync.Map[urn.URN, *resource.State]{},
|
|
},
|
|
new: &resource.State{
|
|
URN: "urn:pulumi:stack::project::foo:bar:Bar::name",
|
|
ID: "some-id",
|
|
Custom: true,
|
|
},
|
|
provider: &deploytest.Provider{
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{}, nil
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "does not exist")
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
t.Run("provider does not support importing resources", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &ImportStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
olds: map[resource.URN]*resource.State{},
|
|
news: &gsync.Map[urn.URN, *resource.State]{},
|
|
},
|
|
new: &resource.State{
|
|
URN: "urn:pulumi:stack::project::foo:bar:Bar::name",
|
|
ID: "some-id",
|
|
Custom: true,
|
|
},
|
|
provider: &deploytest.Provider{
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Outputs: resource.PropertyMap{},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
},
|
|
}
|
|
status, _, err := s.Apply()
|
|
assert.ErrorContains(t, err, "provider does not support importing resources")
|
|
assert.Equal(t, resource.StatusOK, status)
|
|
})
|
|
})
|
|
t.Run("provider check error", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("error", func(t *testing.T) {
|
|
t.Parallel()
|
|
expectedErr := errors.New("expected error")
|
|
var readCalled bool
|
|
var checkCalled bool
|
|
s := &ImportStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
olds: map[resource.URN]*resource.State{},
|
|
news: &gsync.Map[urn.URN, *resource.State]{},
|
|
},
|
|
new: &resource.State{
|
|
URN: "urn:pulumi:stack::project::foo:bar:Bar::name",
|
|
ID: "some-id",
|
|
Type: "foo:bar:Bar",
|
|
Custom: true,
|
|
},
|
|
randomSeed: []byte{},
|
|
provider: &deploytest.Provider{
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
readCalled = true
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Outputs: resource.PropertyMap{},
|
|
Inputs: resource.PropertyMap{},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
CheckF: func(context.Context, plugin.CheckRequest) (plugin.CheckResponse, error) {
|
|
checkCalled = true
|
|
return plugin.CheckResponse{}, expectedErr
|
|
},
|
|
},
|
|
}
|
|
_, _, err := s.Apply()
|
|
assert.ErrorIs(t, err, expectedErr)
|
|
assert.True(t, readCalled)
|
|
assert.True(t, checkCalled)
|
|
})
|
|
t.Run("failures", func(t *testing.T) {
|
|
t.Parallel()
|
|
var readCalled bool
|
|
var checkCalled bool
|
|
s := &ImportStep{
|
|
deployment: &Deployment{
|
|
ctx: &plugin.Context{
|
|
Diag: &deploytest.NoopSink{},
|
|
},
|
|
opts: &Options{
|
|
DryRun: true,
|
|
},
|
|
olds: map[resource.URN]*resource.State{},
|
|
news: &gsync.Map[urn.URN, *resource.State]{},
|
|
},
|
|
new: &resource.State{
|
|
URN: "urn:pulumi:stack::project::foo:bar:Bar::name",
|
|
ID: "some-id",
|
|
Type: "foo:bar:Bar",
|
|
Custom: true,
|
|
Parent: "urn:pulumi:stack::project::pulumi:pulumi:Stack::name",
|
|
Provider: "urn:pulumi:stack::project::pulumi:providers:provider::name::uuid",
|
|
},
|
|
planned: true,
|
|
randomSeed: []byte{},
|
|
provider: &deploytest.Provider{
|
|
ReadF: func(context.Context, plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
readCalled = true
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Outputs: resource.PropertyMap{},
|
|
Inputs: resource.PropertyMap{},
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
CheckF: func(context.Context, plugin.CheckRequest) (plugin.CheckResponse, error) {
|
|
checkCalled = true
|
|
return plugin.CheckResponse{
|
|
Properties: resource.PropertyMap{},
|
|
Failures: []plugin.CheckFailure{
|
|
{
|
|
Reason: "intentional failure",
|
|
},
|
|
},
|
|
}, nil
|
|
},
|
|
},
|
|
}
|
|
_, _, err := s.Apply()
|
|
assert.NoError(t, err)
|
|
assert.True(t, readCalled)
|
|
assert.True(t, checkCalled)
|
|
})
|
|
})
|
|
t.Run("preview: resource input diff found -> no error, 4 diag msg", func(t *testing.T) {
|
|
t.Parallel()
|
|
var diffCalled bool
|
|
var stderrbuff, stdoutbuff bytes.Buffer
|
|
ctx, _ := plugin.NewContext(
|
|
diagtest.MockSink(&stdoutbuff, &stderrbuff), // The diagnostics sink to use for messages.
|
|
diagtest.MockSink(&stdoutbuff, &stderrbuff), // The diagnostics sink to use for status messages.
|
|
nil, // the host that can be used to fetch providers.
|
|
nil, // configSource
|
|
t.TempDir(), // the working directory to spawn all plugins in.
|
|
nil, // runtimeOptions
|
|
false, // disableProviderPreview
|
|
nil,
|
|
)
|
|
s := &ImportStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true, // DryRun true denotes preview
|
|
},
|
|
olds: map[resource.URN]*resource.State{},
|
|
news: &gsync.Map[urn.URN, *resource.State]{},
|
|
ctx: ctx,
|
|
},
|
|
new: &resource.State{
|
|
URN: "urn:pulumi:stack::project::foo:bar:Bar::name",
|
|
ID: "some-id",
|
|
Type: "foo:bar:Bar",
|
|
Custom: true,
|
|
},
|
|
randomSeed: []byte{},
|
|
provider: &deploytest.Provider{
|
|
DiffF: func(context.Context, plugin.DiffRequest) (plugin.DiffResponse, error) {
|
|
diffCalled = true
|
|
return plugin.DiffResponse{
|
|
Changes: plugin.DiffSome,
|
|
ChangedKeys: []resource.PropertyKey{
|
|
resource.PropertyKey("non-matching-input-1"),
|
|
resource.PropertyKey("non-matching-input-2"),
|
|
},
|
|
}, nil
|
|
},
|
|
},
|
|
}
|
|
_, _, err := s.Apply()
|
|
assert.NoError(t, err)
|
|
assert.True(t, diffCalled)
|
|
assert.Contains(t, stderrbuff.String(), "[non-matching-input-1 non-matching-input-2]")
|
|
})
|
|
t.Run("preview: no resource input diff found -> no error, no msg", func(t *testing.T) {
|
|
t.Parallel()
|
|
var stderrbuff, stdoutbuff bytes.Buffer
|
|
ctx, _ := plugin.NewContext(
|
|
diagtest.MockSink(&stdoutbuff, &stderrbuff), // The diagnostics sink to use for messages.
|
|
diagtest.MockSink(&stdoutbuff, &stderrbuff), // The diagnostics sink to use for status messages.
|
|
nil, // the host that can be used to fetch providers.
|
|
nil, // configSource
|
|
t.TempDir(), // the working directory to spawn all plugins in.
|
|
nil, // runtimeOptions
|
|
false, // disableProviderPreview
|
|
nil,
|
|
)
|
|
s := &ImportStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: true, // DryRun true denotes preview
|
|
},
|
|
olds: map[resource.URN]*resource.State{},
|
|
news: &gsync.Map[urn.URN, *resource.State]{},
|
|
ctx: ctx,
|
|
},
|
|
new: &resource.State{
|
|
URN: "urn:pulumi:stack::project::foo:bar:Bar::name",
|
|
ID: "some-id",
|
|
Type: "foo:bar:Bar",
|
|
Custom: true,
|
|
},
|
|
randomSeed: []byte{},
|
|
provider: &deploytest.Provider{},
|
|
}
|
|
_, _, err := s.Apply()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 0, len(stderrbuff.String()))
|
|
})
|
|
t.Run("up: resource input diff found -> error, no msg", func(t *testing.T) {
|
|
t.Parallel()
|
|
var diffCalled bool
|
|
var stderrbuff, stdoutbuff bytes.Buffer
|
|
ctx, _ := plugin.NewContext(
|
|
diagtest.MockSink(&stdoutbuff, &stderrbuff), // The diagnostics sink to use for messages.
|
|
diagtest.MockSink(&stdoutbuff, &stderrbuff), // The diagnostics sink to use for status messages.
|
|
nil, // the host that can be used to fetch providers.
|
|
nil, // configSource
|
|
t.TempDir(), // the working directory to spawn all plugins in.
|
|
nil, // runtimeOptions
|
|
false, // disableProviderPreview
|
|
nil,
|
|
)
|
|
s := &ImportStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: false, // DryRun false denotes up
|
|
},
|
|
olds: map[resource.URN]*resource.State{},
|
|
news: &gsync.Map[urn.URN, *resource.State]{},
|
|
ctx: ctx,
|
|
},
|
|
new: &resource.State{
|
|
URN: "urn:pulumi:stack::project::foo:bar:Bar::name",
|
|
ID: "some-id",
|
|
Type: "foo:bar:Bar",
|
|
Custom: true,
|
|
},
|
|
randomSeed: []byte{},
|
|
provider: &deploytest.Provider{
|
|
DiffF: func(context.Context, plugin.DiffRequest) (plugin.DiffResponse, error) {
|
|
diffCalled = true
|
|
return plugin.DiffResponse{
|
|
Changes: plugin.DiffSome,
|
|
ChangedKeys: []resource.PropertyKey{
|
|
resource.PropertyKey("non-matching-input-1"),
|
|
resource.PropertyKey("non-matching-input-2"),
|
|
},
|
|
}, nil
|
|
},
|
|
},
|
|
}
|
|
_, _, err := s.Apply()
|
|
assert.Error(t, err)
|
|
assert.True(t, diffCalled)
|
|
assert.Equal(t, 0, len(stderrbuff.String()))
|
|
assert.ErrorContains(t, err, "[non-matching-input-1 non-matching-input-2]")
|
|
})
|
|
t.Run("up: no resource input diff found", func(t *testing.T) {
|
|
t.Parallel()
|
|
var stderrbuff, stdoutbuff bytes.Buffer
|
|
ctx, _ := plugin.NewContext(
|
|
diagtest.MockSink(&stdoutbuff, &stderrbuff), // The diagnostics sink to use for messages.
|
|
diagtest.MockSink(&stdoutbuff, &stderrbuff), // The diagnostics sink to use for status messages.
|
|
nil, // the host that can be used to fetch providers.
|
|
nil, // configSource
|
|
t.TempDir(), // the working directory to spawn all plugins in.
|
|
nil, // runtimeOptions
|
|
false, // disableProviderPreview
|
|
nil,
|
|
)
|
|
s := &ImportStep{
|
|
deployment: &Deployment{
|
|
opts: &Options{
|
|
DryRun: false, // DryRun false denotes up
|
|
},
|
|
olds: map[resource.URN]*resource.State{},
|
|
news: &gsync.Map[urn.URN, *resource.State]{},
|
|
ctx: ctx,
|
|
},
|
|
new: &resource.State{
|
|
URN: "urn:pulumi:stack::project::foo:bar:Bar::name",
|
|
ID: "some-id",
|
|
Type: "foo:bar:Bar",
|
|
Custom: true,
|
|
},
|
|
randomSeed: []byte{},
|
|
provider: &deploytest.Provider{},
|
|
}
|
|
_, _, err := s.Apply()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 0, len(stderrbuff.String()))
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestGetProvider(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("ensure default is not selected", func(t *testing.T) {
|
|
t.Parallel()
|
|
s := &CreateStep{
|
|
new: &resource.State{
|
|
Provider: "invalid-provider",
|
|
},
|
|
}
|
|
prov, err := getProvider(s, s.provider)
|
|
assert.Nil(t, prov)
|
|
assert.ErrorContains(t, err, "bad provider reference")
|
|
})
|
|
t.Run("ensure default is not selected", func(t *testing.T) {
|
|
t.Parallel()
|
|
expectedProvider := &deploytest.Provider{}
|
|
s := &CreateStep{
|
|
provider: expectedProvider,
|
|
new: &resource.State{
|
|
Provider: "invalid-provider",
|
|
},
|
|
}
|
|
prov, err := getProvider(s, s.provider)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedProvider, prov)
|
|
})
|
|
}
|
|
|
|
func TestSuffix(t *testing.T) {
|
|
t.Parallel()
|
|
for op, expectation := range map[display.StepOp]string{
|
|
OpSame: "",
|
|
OpCreate: "",
|
|
OpDelete: "",
|
|
OpDeleteReplaced: "",
|
|
OpRead: "",
|
|
OpReadDiscard: "",
|
|
OpDiscardReplaced: "",
|
|
OpRemovePendingReplace: "",
|
|
OpImport: "",
|
|
"not-a-real-step-op": "",
|
|
|
|
OpCreateReplacement: colors.Reset,
|
|
OpUpdate: colors.Reset,
|
|
OpReplace: colors.Reset,
|
|
OpReadReplacement: colors.Reset,
|
|
OpRefresh: colors.Reset,
|
|
OpImportReplacement: colors.Reset,
|
|
} {
|
|
assert.Equal(t, expectation, Suffix(op))
|
|
}
|
|
}
|