Add support for explicit delete-before-replace (#2415)

These changes add a new flag to the various `ResourceOptions` types that
indicates that a resource should be deleted before it is replaced, even
if the provider does not require this behavior. The usual
delete-before-replace cascade semantics apply.

Fixes #1620.
This commit is contained in:
Pat Gavlin 2019-01-31 14:27:53 -08:00 committed by GitHub
parent 128afe3323
commit 6e90ab0341
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 487 additions and 151 deletions

View file

@ -20,6 +20,10 @@ Diagnostics:
We appologize for the regression. (fixes [pulumi/pulumi#2414](https://github.com/pulumi/pulumi/issues/2414))
### Improvements
- Individual resources may now be explicitly marked as requiring delete-before-replace behavior. This can be used e.g. to handle explicitly-named resources that may not be able to be replaced in the usual manner.
## 0.16.13 (Released January 31th, 2019)
### Major Changes

View file

@ -191,6 +191,34 @@ func (j *Journal) Snap(base *deploy.Snapshot) *deploy.Snapshot {
return deploy.NewSnapshot(manifest, resources, operations)
}
func (j *Journal) SuccessfulSteps() []deploy.Step {
var steps []deploy.Step
for _, entry := range j.Entries {
if entry.Kind == JournalEntrySuccess {
steps = append(steps, entry.Step)
}
}
return steps
}
type StepSummary struct {
Op deploy.StepOp
URN resource.URN
}
func AssertSameSteps(t *testing.T, expected []StepSummary, actual []deploy.Step) bool {
assert.Equal(t, len(expected), len(actual))
for _, exp := range expected {
act := actual[0]
actual = actual[1:]
if !assert.Equal(t, exp.Op, act.Op()) || !assert.Equal(t, exp.URN, act.URN()) {
return false
}
}
return true
}
func newJournal() *Journal {
j := &Journal{
events: make(chan JournalEntry),
@ -512,7 +540,7 @@ func TestSingleResourceDefaultProviderLifecycle(t *testing.T) {
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "",
resource.PropertyMap{}, nil)
resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
return nil
})
@ -534,7 +562,7 @@ func TestSingleResourceExplicitProviderLifecycle(t *testing.T) {
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true, "",
false, nil, "", resource.PropertyMap{}, nil)
false, nil, "", resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
if provID == "" {
@ -545,7 +573,7 @@ func TestSingleResourceExplicitProviderLifecycle(t *testing.T) {
assert.NoError(t, err)
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, provRef.String(),
resource.PropertyMap{}, nil)
resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
return nil
@ -568,7 +596,7 @@ func TestSingleResourceDefaultProviderUpgrade(t *testing.T) {
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "",
resource.PropertyMap{}, nil)
resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
return nil
})
@ -669,7 +697,7 @@ func TestSingleResourceDefaultProviderReplace(t *testing.T) {
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "",
resource.PropertyMap{}, nil)
resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
return nil
})
@ -748,7 +776,7 @@ func TestSingleResourceExplicitProviderReplace(t *testing.T) {
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true, "",
false, nil, "", providerInputs, nil)
false, nil, "", providerInputs, nil, false)
assert.NoError(t, err)
if provID == "" {
@ -759,7 +787,7 @@ func TestSingleResourceExplicitProviderReplace(t *testing.T) {
assert.NoError(t, err)
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, provRef.String(),
resource.PropertyMap{}, nil)
resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
return nil
@ -835,7 +863,7 @@ func TestSingleResourceExplicitProviderDeleteBeforeReplace(t *testing.T) {
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true, "",
false, nil, "", providerInputs, nil)
false, nil, "", providerInputs, nil, false)
assert.NoError(t, err)
if provID == "" {
@ -846,7 +874,7 @@ func TestSingleResourceExplicitProviderDeleteBeforeReplace(t *testing.T) {
assert.NoError(t, err)
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, provRef.String(),
resource.PropertyMap{}, nil)
resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
return nil
@ -936,7 +964,7 @@ func TestSingleResourceDiffUnavailable(t *testing.T) {
inputs := resource.PropertyMap{}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "", inputs, nil)
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "", inputs, nil, false)
assert.NoError(t, err)
return nil
})
@ -1129,19 +1157,19 @@ func TestParallelRefresh(t *testing.T) {
// it.
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
resA, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "",
resource.PropertyMap{}, nil)
resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
resB, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resB", true, "", false, []resource.URN{resA}, "",
resource.PropertyMap{}, nil)
resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
resC, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resC", true, "", false, []resource.URN{resB}, "",
resource.PropertyMap{}, nil)
resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resD", true, "", false, []resource.URN{resC}, "",
resource.PropertyMap{}, nil)
resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
return nil
@ -1255,7 +1283,7 @@ func TestRefreshInitFailure(t *testing.T) {
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "",
resource.PropertyMap{}, nil)
resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
return nil
})
@ -1342,7 +1370,7 @@ func TestCheckFailureRecord(t *testing.T) {
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "", nil, nil)
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "", nil, nil, false)
assert.Error(t, err)
return err
})
@ -1390,7 +1418,7 @@ func TestCheckFailureInvalidPropertyRecord(t *testing.T) {
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "", nil, nil)
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "", nil, nil, false)
assert.Error(t, err)
return err
})
@ -1445,7 +1473,7 @@ func TestRefreshWithDelete(t *testing.T) {
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "", nil, nil)
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "", nil, nil, false)
assert.NoError(t, err)
return err
})
@ -1938,7 +1966,8 @@ func TestBadResourceType(t *testing.T) {
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, mon *deploytest.ResourceMonitor) error {
_, _, _, err := mon.RegisterResource("very:bad", "resA", true, "", false, nil, "", resource.PropertyMap{}, nil)
_, _, _, err := mon.RegisterResource(
"very:bad", "resA", true, "", false, nil, "", resource.PropertyMap{}, nil, false)
assert.Error(t, err)
rpcerr, ok := rpcerror.FromError(err)
assert.True(t, ok)
@ -1954,11 +1983,11 @@ func TestBadResourceType(t *testing.T) {
// Component resources may have any format type.
_, _, _, noErr := mon.RegisterResource(
"a:component", "resB", false /* custom */, "", false, nil, "", resource.PropertyMap{}, nil)
"a:component", "resB", false /* custom */, "", false, nil, "", resource.PropertyMap{}, nil, false)
assert.NoError(t, noErr)
_, _, _, noErr = mon.RegisterResource(
"singlename", "resC", false /* custom */, "", false, nil, "", resource.PropertyMap{}, nil)
"singlename", "resC", false /* custom */, "", false, nil, "", resource.PropertyMap{}, nil, false)
assert.NoError(t, noErr)
return err
@ -2022,7 +2051,7 @@ func TestProviderCancellation(t *testing.T) {
for i := 0; i < resourceCount; i++ {
go func(idx int) {
_, _, _, errors[idx] = monitor.RegisterResource("pkgA:m:typA", fmt.Sprintf("res%d", idx), true, "",
false, nil, "", resource.PropertyMap{}, nil)
false, nil, "", resource.PropertyMap{}, nil, false)
resources.Done()
}(i)
}
@ -2087,7 +2116,7 @@ func TestPreviewWithPendingOperations(t *testing.T) {
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "",
resource.PropertyMap{}, nil)
resource.PropertyMap{}, nil, false)
assert.NoError(t, err)
return nil
})
@ -2132,7 +2161,7 @@ func TestUpdatePartialFailure(t *testing.T) {
_, _, _, err := mon.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "",
resource.NewPropertyMapFromMap(map[string]interface{}{
"input_prop": "new inputs",
}), nil)
}), nil, false)
return err
})
@ -2202,7 +2231,7 @@ func TestStackReference(t *testing.T) {
_, _, state, err := mon.RegisterResource("pulumi:pulumi:StackReference", "other", true, "", false, nil, "",
resource.NewPropertyMapFromMap(map[string]interface{}{
"name": "other",
}), nil)
}), nil, false)
assert.NoError(t, err)
if !info.DryRun {
assert.Equal(t, "bar", state["outputs"].ObjectValue()["foo"].StringValue())
@ -2273,7 +2302,7 @@ func TestStackReference(t *testing.T) {
_, _, _, err := mon.RegisterResource("pulumi:pulumi:StackReference", "other", true, "", false, nil, "",
resource.NewPropertyMapFromMap(map[string]interface{}{
"name": "rehto",
}), nil)
}), nil, false)
assert.Error(t, err)
return err
})
@ -2291,7 +2320,7 @@ func TestStackReference(t *testing.T) {
resource.NewPropertyMapFromMap(map[string]interface{}{
"name": "other",
"foo": "bar",
}), nil)
}), nil, false)
assert.Error(t, err)
return err
})
@ -2522,7 +2551,7 @@ func TestDeleteBeforeReplace(t *testing.T) {
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
register := func(urn resource.URN, provider string, inputs resource.PropertyMap) resource.ID {
_, id, _, err := monitor.RegisterResource(urn.Type(), string(urn.Name()), true, "", false, nil, provider,
inputs, nil)
inputs, nil, false)
assert.NoError(t, err)
return id
}
@ -2595,7 +2624,7 @@ func TestPropertyDependenciesAdapter(t *testing.T) {
dependencies []resource.URN) resource.URN {
urn, _, _, err := monitor.RegisterResource(resType, name, true, "", false, dependencies, "", inputs,
inputDeps)
inputDeps, false)
assert.NoError(t, err)
return urn
@ -2644,3 +2673,146 @@ func TestPropertyDependenciesAdapter(t *testing.T) {
}
}
}
func TestExplicitDeleteBeforeReplace(t *testing.T) {
p := &TestPlan{}
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
return &deploytest.Provider{
DiffF: func(urn resource.URN, id resource.ID,
olds, news resource.PropertyMap) (plugin.DiffResult, error) {
if !olds["A"].DeepEquals(news["A"]) {
return plugin.DiffResult{ReplaceKeys: []resource.PropertyKey{"A"}}, nil
}
return plugin.DiffResult{}, nil
},
}, nil
}),
}
const resType = "pkgA:index:typ"
inputsA := resource.NewPropertyMapFromMap(map[string]interface{}{"A": "foo"})
dbrA := false
inputsB := resource.NewPropertyMapFromMap(map[string]interface{}{"A": "foo"})
var provURN, urnA, urnB resource.URN
var provID resource.ID
var err error
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
provURN, provID, _, err = monitor.RegisterResource(
providers.MakeProviderType("pkgA"), "provA", true, "", false, nil, "", nil, nil, false)
assert.NoError(t, err)
if provID == "" {
provID = providers.UnknownID
}
provRef, err := providers.NewReference(provURN, provID)
assert.NoError(t, err)
provA := provRef.String()
urnA, _, _, err = monitor.RegisterResource(resType, "resA", true, "", false, nil, provA, inputsA, nil, dbrA)
assert.NoError(t, err)
inputDepsB := map[resource.PropertyKey][]resource.URN{"A": {urnA}}
urnB, _, _, err = monitor.RegisterResource(resType, "resB", true, "", false, []resource.URN{urnA}, provA,
inputsB, inputDepsB, false)
assert.NoError(t, err)
return nil
})
p.Options.host = deploytest.NewPluginHost(nil, nil, program, loaders...)
p.Steps = []TestStep{{Op: Update}}
snap := p.Run(t, nil)
// Change the value of resA.A. Only resA should be replaced, and the replacement should be create-before-delete.
inputsA["A"] = resource.NewStringProperty("bar")
p.Steps = []TestStep{{
Op: Update,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, evts []Event, err error) error {
assert.NoError(t, err)
AssertSameSteps(t, []StepSummary{
{Op: deploy.OpSame, URN: provURN},
{Op: deploy.OpCreateReplacement, URN: urnA},
{Op: deploy.OpReplace, URN: urnA},
{Op: deploy.OpSame, URN: urnB},
{Op: deploy.OpDeleteReplaced, URN: urnA},
}, j.SuccessfulSteps())
return err
},
}}
snap = p.Run(t, snap)
// Change the registration of resA such that it requires delete-before-replace and change the value of resA.A. Both
// resA and resB should be replaced, and the replacements should be delete-before-replace.
dbrA, inputsA["A"] = true, resource.NewStringProperty("baz")
p.Steps = []TestStep{{
Op: Update,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, evts []Event, err error) error {
assert.NoError(t, err)
AssertSameSteps(t, []StepSummary{
{Op: deploy.OpSame, URN: provURN},
{Op: deploy.OpDeleteReplaced, URN: urnB},
{Op: deploy.OpDeleteReplaced, URN: urnA},
{Op: deploy.OpReplace, URN: urnA},
{Op: deploy.OpCreateReplacement, URN: urnA},
{Op: deploy.OpReplace, URN: urnB},
{Op: deploy.OpCreateReplacement, URN: urnB},
}, j.SuccessfulSteps())
return err
},
}}
snap = p.Run(t, snap)
// Change the value of resB.A. Only resB should be replaced, and the replacement should be create-before-delete.
inputsB["A"] = resource.NewStringProperty("qux")
p.Steps = []TestStep{{
Op: Update,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, evts []Event, err error) error {
assert.NoError(t, err)
AssertSameSteps(t, []StepSummary{
{Op: deploy.OpSame, URN: provURN},
{Op: deploy.OpSame, URN: urnA},
{Op: deploy.OpCreateReplacement, URN: urnB},
{Op: deploy.OpReplace, URN: urnB},
{Op: deploy.OpDeleteReplaced, URN: urnB},
}, j.SuccessfulSteps())
return err
},
}}
snap = p.Run(t, snap)
// Change the registration of resA such that it no longer requires delete-before-replace and change the value of
// resA.A. Only resA should be replaced, and the replacement should be delete-before-replace.
dbrA, inputsA["A"] = false, resource.NewStringProperty("zam")
p.Steps = []TestStep{{
Op: Update,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, evts []Event, err error) error {
assert.NoError(t, err)
AssertSameSteps(t, []StepSummary{
{Op: deploy.OpSame, URN: provURN},
{Op: deploy.OpCreateReplacement, URN: urnA},
{Op: deploy.OpReplace, URN: urnA},
{Op: deploy.OpSame, URN: urnB},
{Op: deploy.OpDeleteReplaced, URN: urnA},
}, j.SuccessfulSteps())
return err
},
}}
p.Run(t, snap)
}

View file

@ -29,7 +29,8 @@ type ResourceMonitor struct {
func (rm *ResourceMonitor) RegisterResource(t tokens.Type, name string, custom bool, parent resource.URN, protect bool,
dependencies []resource.URN, provider string, inputs resource.PropertyMap,
propertyDeps map[resource.PropertyKey][]resource.URN) (resource.URN, resource.ID, resource.PropertyMap, error) {
propertyDeps map[resource.PropertyKey][]resource.URN,
deleteBeforeReplace bool) (resource.URN, resource.ID, resource.PropertyMap, error) {
// marshal inputs
ins, err := plugin.MarshalProperties(inputs, plugin.MarshalOptions{KeepUnknowns: true})
@ -65,6 +66,7 @@ func (rm *ResourceMonitor) RegisterResource(t tokens.Type, name string, custom b
Provider: provider,
Object: ins,
PropertyDependencies: inputDeps,
DeleteBeforeReplace: deleteBeforeReplace,
})
if err != nil {
return "", "", nil, err

View file

@ -256,7 +256,7 @@ func (d *defaultProviders) newRegisterDefaultProviderEvent(
// Create the result channel and the event.
done := make(chan *RegisterResult)
event := &registerResourceEvent{
goal: resource.NewGoal(providers.MakeProviderType(pkg), "default", true, inputs, "", false, nil, "", nil, nil),
goal: resource.NewGoal(providers.MakeProviderType(pkg), "default", true, inputs, "", false, nil, "", nil, nil, false),
done: done,
}
return event, done, nil
@ -569,6 +569,7 @@ func (rm *resmon) RegisterResource(ctx context.Context,
custom := req.GetCustom()
parent := resource.URN(req.GetParent())
protect := req.GetProtect()
deleteBeforeReplace := req.GetDeleteBeforeReplace()
var t tokens.Type
// Custom resources must have a three-part type so that we can 1) identify if they are providers and 2) retrieve the
@ -625,13 +626,13 @@ func (rm *resmon) RegisterResource(ctx context.Context,
logging.V(5).Infof(
"ResourceMonitor.RegisterResource received: t=%v, name=%v, custom=%v, #props=%v, parent=%v, protect=%v, "+
"provider=%v, deps=%v",
t, name, custom, len(props), parent, protect, provider, dependencies)
"provider=%v, deps=%v, deleteBeforeReplace=%v",
t, name, custom, len(props), parent, protect, provider, dependencies, deleteBeforeReplace)
// Send the goal state to the engine.
step := &registerResourceEvent{
goal: resource.NewGoal(t, name, custom, props, parent, protect, dependencies, provider, nil,
propertyDependencies),
propertyDependencies, deleteBeforeReplace),
done: make(chan *RegisterResult),
}

View file

@ -55,7 +55,7 @@ func fixedProgram(steps []RegisterResourceEvent) deploytest.ProgramFunc {
for _, s := range steps {
g := s.Goal()
urn, id, outs, err := resmon.RegisterResource(g.Type, string(g.Name), g.Custom, g.Parent, g.Protect,
g.Dependencies, g.Provider, g.Properties, g.PropertyDependencies)
g.Dependencies, g.Provider, g.Properties, g.PropertyDependencies, false)
if err != nil {
return err
}
@ -143,16 +143,16 @@ func TestRegisterNoDefaultProviders(t *testing.T) {
// Register a component resource.
&testRegEvent{
goal: resource.NewGoal(componentURN.Type(), componentURN.Name(), false, resource.PropertyMap{}, "", false,
nil, "", []string{}, nil),
nil, "", []string{}, nil, false),
},
// Register a couple resources using provider A.
&testRegEvent{
goal: resource.NewGoal("pkgA:index:typA", "res1", true, resource.PropertyMap{}, componentURN, false, nil,
providerARef.String(), []string{}, nil),
providerARef.String(), []string{}, nil, false),
},
&testRegEvent{
goal: resource.NewGoal("pkgA:index:typA", "res2", true, resource.PropertyMap{}, componentURN, false, nil,
providerARef.String(), []string{}, nil),
providerARef.String(), []string{}, nil, false),
},
// Register two more providers.
newProviderEvent("pkgA", "providerB", nil, ""),
@ -160,11 +160,11 @@ func TestRegisterNoDefaultProviders(t *testing.T) {
// Register a few resources that use the new providers.
&testRegEvent{
goal: resource.NewGoal("pkgB:index:typB", "res3", true, resource.PropertyMap{}, "", false, nil,
providerBRef.String(), []string{}, nil),
providerBRef.String(), []string{}, nil, false),
},
&testRegEvent{
goal: resource.NewGoal("pkgB:index:typC", "res4", true, resource.PropertyMap{}, "", false, nil,
providerCRef.String(), []string{}, nil),
providerCRef.String(), []string{}, nil, false),
},
}
@ -227,25 +227,25 @@ func TestRegisterDefaultProviders(t *testing.T) {
// Register a component resource.
&testRegEvent{
goal: resource.NewGoal(componentURN.Type(), componentURN.Name(), false, resource.PropertyMap{}, "", false,
nil, "", []string{}, nil),
nil, "", []string{}, nil, false),
},
// Register a couple resources from package A.
&testRegEvent{
goal: resource.NewGoal("pkgA:m:typA", "res1", true, resource.PropertyMap{},
componentURN, false, nil, "", []string{}, nil),
componentURN, false, nil, "", []string{}, nil, false),
},
&testRegEvent{
goal: resource.NewGoal("pkgA:m:typA", "res2", true, resource.PropertyMap{},
componentURN, false, nil, "", []string{}, nil),
componentURN, false, nil, "", []string{}, nil, false),
},
// Register a few resources from other packages.
&testRegEvent{
goal: resource.NewGoal("pkgB:m:typB", "res3", true, resource.PropertyMap{}, "", false,
nil, "", []string{}, nil),
nil, "", []string{}, nil, false),
},
&testRegEvent{
goal: resource.NewGoal("pkgB:m:typC", "res4", true, resource.PropertyMap{}, "", false,
nil, "", []string{}, nil),
nil, "", []string{}, nil, false),
},
}

View file

@ -322,7 +322,7 @@ func (sg *stepGenerator) GenerateSteps(event RegisterResourceEvent) ([]Step, *re
//
// The provider is responsible for requesting which of these two modes to use.
if diff.DeleteBeforeReplace {
if diff.DeleteBeforeReplace || goal.DeleteBeforeReplace {
logging.V(7).Infof("Planner decided to delete-before-replacement for resource '%v'", urn)
contract.Assert(sg.plan.depGraph != nil)

View file

@ -31,12 +31,13 @@ type Goal struct {
Provider string // the provider to use for this resource.
InitErrors []string // errors encountered as we attempted to initialize the resource.
PropertyDependencies map[PropertyKey][]URN // the set of dependencies that affect each property.
DeleteBeforeReplace bool // true if this resource should be deleted prior to replacement.
}
// NewGoal allocates a new resource goal state.
func NewGoal(t tokens.Type, name tokens.QName, custom bool, props PropertyMap,
parent URN, protect bool, dependencies []URN, provider string, initErrors []string,
propertyDependencies map[PropertyKey][]URN) *Goal {
propertyDependencies map[PropertyKey][]URN, deleteBeforeReplace bool) *Goal {
return &Goal{
Type: t,
@ -49,5 +50,6 @@ func NewGoal(t tokens.Type, name tokens.QName, custom bool, props PropertyMap,
Provider: provider,
InitErrors: initErrors,
PropertyDependencies: propertyDependencies,
DeleteBeforeReplace: deleteBeforeReplace,
}
}

View file

@ -292,6 +292,7 @@ func (ctx *Context) RegisterResource(
Dependencies: inputs.deps,
Provider: inputs.provider,
PropertyDependencies: inputs.rpcPropertyDeps,
DeleteBeforeReplace: inputs.deleteBeforeReplace,
})
if err != nil {
glog.V(9).Infof("RegisterResource(%s, %s): error: %v", t, name, err)
@ -404,19 +405,20 @@ func (outputs *resourceOutputs) resolve(dryrun bool, err error, inputs map[strin
// resourceInputs reflects all of the inputs necessary to perform core resource RPC operations.
type resourceInputs struct {
parent string
deps []string
protect bool
provider string
rpcProps *structpb.Struct
rpcPropertyDeps map[string]*pulumirpc.RegisterResourceRequest_PropertyDependencies
parent string
deps []string
protect bool
provider string
rpcProps *structpb.Struct
rpcPropertyDeps map[string]*pulumirpc.RegisterResourceRequest_PropertyDependencies
deleteBeforeReplace bool
}
// prepareResourceInputs prepares the inputs for a resource operation, shared between read and register.
func (ctx *Context) prepareResourceInputs(props map[string]interface{}, opts ...ResourceOpt) (*resourceInputs, error) {
// Get the parent and dependency URNs from the options, in addition to the protection bit. If there wasn't an
// explicit parent, and a root stack resource exists, we will automatically parent to that.
parent, optDeps, protect, provider, err := ctx.getOpts(opts...)
parent, optDeps, protect, provider, deleteBeforeReplace, err := ctx.getOpts(opts...)
if err != nil {
return nil, errors.Wrap(err, "resolving options")
}
@ -457,12 +459,13 @@ func (ctx *Context) prepareResourceInputs(props map[string]interface{}, opts ...
sort.Strings(deps)
return &resourceInputs{
parent: string(parent),
deps: deps,
protect: protect,
provider: provider,
rpcProps: rpcProps,
rpcPropertyDeps: rpcPropertyDeps,
parent: string(parent),
deps: deps,
protect: protect,
provider: provider,
rpcProps: rpcProps,
rpcPropertyDeps: rpcPropertyDeps,
deleteBeforeReplace: deleteBeforeReplace,
}, nil
}
@ -474,11 +477,12 @@ type resourceOutput struct {
// getOpts returns a set of resource options from an array of them. This includes the parent URN, any dependency URNs,
// a boolean indicating whether the resource is to be protected, and the URN and ID of the resource's provider, if any.
func (ctx *Context) getOpts(opts ...ResourceOpt) (URN, []URN, bool, string, error) {
func (ctx *Context) getOpts(opts ...ResourceOpt) (URN, []URN, bool, string, bool, error) {
var parent Resource
var deps []Resource
var protect bool
var provider ProviderResource
var deleteBeforeReplace bool
for _, opt := range opts {
if parent == nil && opt.Parent != nil {
parent = opt.Parent
@ -492,6 +496,9 @@ func (ctx *Context) getOpts(opts ...ResourceOpt) (URN, []URN, bool, string, erro
if provider == nil && opt.Provider != nil {
provider = opt.Provider
}
if !deleteBeforeReplace && opt.DeleteBeforeReplace {
deleteBeforeReplace = true
}
}
var parentURN URN
@ -500,7 +507,7 @@ func (ctx *Context) getOpts(opts ...ResourceOpt) (URN, []URN, bool, string, erro
} else {
urn, err := parent.URN().Value()
if err != nil {
return "", nil, false, "", err
return "", nil, false, "", false, err
}
parentURN = urn
}
@ -511,7 +518,7 @@ func (ctx *Context) getOpts(opts ...ResourceOpt) (URN, []URN, bool, string, erro
for i, r := range deps {
urn, err := r.URN().Value()
if err != nil {
return "", nil, false, "", err
return "", nil, false, "", false, err
}
depURNs[i] = urn
}
@ -521,12 +528,12 @@ func (ctx *Context) getOpts(opts ...ResourceOpt) (URN, []URN, bool, string, erro
if provider != nil {
pr, err := ctx.resolveProviderReference(provider)
if err != nil {
return "", nil, false, "", err
return "", nil, false, "", false, err
}
providerRef = pr
}
return parentURN, depURNs, protect, providerRef, nil
return parentURN, depURNs, protect, providerRef, false, nil
}
func (ctx *Context) resolveProviderReference(provider ProviderResource) (string, error) {

View file

@ -60,6 +60,8 @@ type ResourceOpt struct {
Protect bool
// Provider is an optional provider resource to use for this resource's CRUD operations.
Provider ProviderResource
// DeleteBeforeReplace, when set to true, ensures that this resource is deleted prior to replacement.
DeleteBeforeReplace bool
}
// InvokeOpt contains optional settings that control an invoke's behavior.

View file

@ -610,7 +610,8 @@ proto.pulumirpc.RegisterResourceRequest.toObject = function(includeInstance, msg
protect: jspb.Message.getFieldWithDefault(msg, 6, false),
dependenciesList: jspb.Message.getRepeatedField(msg, 7),
provider: jspb.Message.getFieldWithDefault(msg, 8, ""),
propertydependenciesMap: (f = msg.getPropertydependenciesMap()) ? f.toObject(includeInstance, proto.pulumirpc.RegisterResourceRequest.PropertyDependencies.toObject) : []
propertydependenciesMap: (f = msg.getPropertydependenciesMap()) ? f.toObject(includeInstance, proto.pulumirpc.RegisterResourceRequest.PropertyDependencies.toObject) : [],
deletebeforereplace: jspb.Message.getFieldWithDefault(msg, 10, false)
};
if (includeInstance) {
@ -686,6 +687,10 @@ proto.pulumirpc.RegisterResourceRequest.deserializeBinaryFromReader = function(m
jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readMessage, proto.pulumirpc.RegisterResourceRequest.PropertyDependencies.deserializeBinaryFromReader);
});
break;
case 10:
var value = /** @type {boolean} */ (reader.readBool());
msg.setDeletebeforereplace(value);
break;
default:
reader.skipField();
break;
@ -776,6 +781,13 @@ proto.pulumirpc.RegisterResourceRequest.serializeBinaryToWriter = function(messa
if (f && f.getLength() > 0) {
f.serializeBinary(9, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeMessage, proto.pulumirpc.RegisterResourceRequest.PropertyDependencies.serializeBinaryToWriter);
}
f = message.getDeletebeforereplace();
if (f) {
writer.writeBool(
10,
f
);
}
};
@ -1113,6 +1125,23 @@ proto.pulumirpc.RegisterResourceRequest.prototype.clearPropertydependenciesMap =
};
/**
* optional bool deleteBeforeReplace = 10;
* Note that Boolean fields may be set to 0/1 when serialized from a Java server.
* You should avoid comparisons like {@code val === true/false} in those cases.
* @return {boolean}
*/
proto.pulumirpc.RegisterResourceRequest.prototype.getDeletebeforereplace = function() {
return /** @type {boolean} */ (jspb.Message.getFieldWithDefault(this, 10, false));
};
/** @param {boolean} value */
proto.pulumirpc.RegisterResourceRequest.prototype.setDeletebeforereplace = function(value) {
jspb.Message.setProto3BooleanField(this, 10, value);
};
/**
* Generated by JsPbCodeGenerator.

View file

@ -169,6 +169,12 @@ export interface CustomResourceOptions extends ResourceOptions {
* provider bag (see also ComponentResourceOptions.providers).
*/
provider?: ProviderResource;
/**
* When set to true, deleteBeforeReplace indicates that this resource should be deleted before its replacement
* is created when replacement is necessary.
*/
deleteBeforeReplace?: boolean;
}
/**

View file

@ -137,6 +137,7 @@ export function registerResource(res: Resource, t: string, name: string, custom:
req.setProtect(opts.protect);
req.setProvider(resop.providerRef);
req.setDependenciesList(Array.from(resop.dependencies));
req.setDeletebeforereplace((<any>opts).deleteBeforeReplace || false);
const propertyDependencies = req.getPropertydependenciesMap();
for (const key of Object.keys(resop.propertyDependencies)) {

View file

@ -43,7 +43,7 @@ func (m *ReadResourceRequest) Reset() { *m = ReadResourceRequest{} }
func (m *ReadResourceRequest) String() string { return proto.CompactTextString(m) }
func (*ReadResourceRequest) ProtoMessage() {}
func (*ReadResourceRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_resource_814328246bd928d7, []int{0}
return fileDescriptor_resource_03e51d5764cd9ae8, []int{0}
}
func (m *ReadResourceRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReadResourceRequest.Unmarshal(m, b)
@ -125,7 +125,7 @@ func (m *ReadResourceResponse) Reset() { *m = ReadResourceResponse{} }
func (m *ReadResourceResponse) String() string { return proto.CompactTextString(m) }
func (*ReadResourceResponse) ProtoMessage() {}
func (*ReadResourceResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_resource_814328246bd928d7, []int{1}
return fileDescriptor_resource_03e51d5764cd9ae8, []int{1}
}
func (m *ReadResourceResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReadResourceResponse.Unmarshal(m, b)
@ -170,6 +170,7 @@ type RegisterResourceRequest struct {
Dependencies []string `protobuf:"bytes,7,rep,name=dependencies" json:"dependencies,omitempty"`
Provider string `protobuf:"bytes,8,opt,name=provider" json:"provider,omitempty"`
PropertyDependencies map[string]*RegisterResourceRequest_PropertyDependencies `protobuf:"bytes,9,rep,name=propertyDependencies" json:"propertyDependencies,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
DeleteBeforeReplace bool `protobuf:"varint,10,opt,name=deleteBeforeReplace" json:"deleteBeforeReplace,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -179,7 +180,7 @@ func (m *RegisterResourceRequest) Reset() { *m = RegisterResourceRequest
func (m *RegisterResourceRequest) String() string { return proto.CompactTextString(m) }
func (*RegisterResourceRequest) ProtoMessage() {}
func (*RegisterResourceRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_resource_814328246bd928d7, []int{2}
return fileDescriptor_resource_03e51d5764cd9ae8, []int{2}
}
func (m *RegisterResourceRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RegisterResourceRequest.Unmarshal(m, b)
@ -262,6 +263,13 @@ func (m *RegisterResourceRequest) GetPropertyDependencies() map[string]*Register
return nil
}
func (m *RegisterResourceRequest) GetDeleteBeforeReplace() bool {
if m != nil {
return m.DeleteBeforeReplace
}
return false
}
// PropertyDependencies describes the resources that a particular property depends on.
type RegisterResourceRequest_PropertyDependencies struct {
Urns []string `protobuf:"bytes,1,rep,name=urns" json:"urns,omitempty"`
@ -278,7 +286,7 @@ func (m *RegisterResourceRequest_PropertyDependencies) String() string {
}
func (*RegisterResourceRequest_PropertyDependencies) ProtoMessage() {}
func (*RegisterResourceRequest_PropertyDependencies) Descriptor() ([]byte, []int) {
return fileDescriptor_resource_814328246bd928d7, []int{2, 0}
return fileDescriptor_resource_03e51d5764cd9ae8, []int{2, 0}
}
func (m *RegisterResourceRequest_PropertyDependencies) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RegisterResourceRequest_PropertyDependencies.Unmarshal(m, b)
@ -322,7 +330,7 @@ func (m *RegisterResourceResponse) Reset() { *m = RegisterResourceRespon
func (m *RegisterResourceResponse) String() string { return proto.CompactTextString(m) }
func (*RegisterResourceResponse) ProtoMessage() {}
func (*RegisterResourceResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_resource_814328246bd928d7, []int{3}
return fileDescriptor_resource_03e51d5764cd9ae8, []int{3}
}
func (m *RegisterResourceResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RegisterResourceResponse.Unmarshal(m, b)
@ -390,7 +398,7 @@ func (m *RegisterResourceOutputsRequest) Reset() { *m = RegisterResource
func (m *RegisterResourceOutputsRequest) String() string { return proto.CompactTextString(m) }
func (*RegisterResourceOutputsRequest) ProtoMessage() {}
func (*RegisterResourceOutputsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_resource_814328246bd928d7, []int{4}
return fileDescriptor_resource_03e51d5764cd9ae8, []int{4}
}
func (m *RegisterResourceOutputsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RegisterResourceOutputsRequest.Unmarshal(m, b)
@ -605,45 +613,46 @@ var _ResourceMonitor_serviceDesc = grpc.ServiceDesc{
Metadata: "resource.proto",
}
func init() { proto.RegisterFile("resource.proto", fileDescriptor_resource_814328246bd928d7) }
func init() { proto.RegisterFile("resource.proto", fileDescriptor_resource_03e51d5764cd9ae8) }
var fileDescriptor_resource_814328246bd928d7 = []byte{
// 577 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x95, 0xcd, 0x6e, 0xd3, 0x40,
0x10, 0xc7, 0x6b, 0xa7, 0x75, 0xda, 0x69, 0x15, 0xaa, 0x25, 0x6a, 0x5d, 0x83, 0x4a, 0x65, 0x2e,
0xc0, 0xc1, 0x11, 0xe1, 0x50, 0x84, 0x90, 0xb8, 0xd0, 0x03, 0x87, 0x0a, 0x30, 0x67, 0x90, 0x1c,
0x67, 0x88, 0x4c, 0x13, 0xef, 0xb2, 0x1f, 0x91, 0x72, 0xe3, 0x45, 0x10, 0x6f, 0xc6, 0x89, 0x07,
0x61, 0xd7, 0x6b, 0x87, 0x38, 0x76, 0x9a, 0x8a, 0x53, 0xe6, 0x6b, 0xff, 0x3b, 0xfb, 0xdb, 0x59,
0x07, 0x7a, 0x1c, 0x05, 0x55, 0x3c, 0xc5, 0x88, 0x71, 0x2a, 0x29, 0x39, 0x60, 0x6a, 0xaa, 0x66,
0x19, 0x67, 0x69, 0xf0, 0x60, 0x42, 0xe9, 0x64, 0x8a, 0x83, 0x22, 0x31, 0x52, 0x5f, 0x07, 0x38,
0x63, 0x72, 0x61, 0xeb, 0x82, 0x87, 0xeb, 0x49, 0x21, 0xb9, 0x4a, 0x65, 0x99, 0xed, 0xe9, 0x9f,
0x79, 0x36, 0x46, 0x6e, 0xfd, 0xf0, 0xb7, 0x03, 0xf7, 0x63, 0x4c, 0xc6, 0x71, 0xb9, 0x59, 0x8c,
0xdf, 0x15, 0x0a, 0x49, 0x7a, 0xe0, 0x66, 0x63, 0xdf, 0xb9, 0x70, 0x9e, 0x1c, 0xc4, 0xda, 0x22,
0x04, 0x76, 0xe5, 0x82, 0xa1, 0xef, 0x16, 0x91, 0xc2, 0x36, 0xb1, 0x3c, 0x99, 0xa1, 0xdf, 0xb1,
0x31, 0x63, 0x93, 0x13, 0xf0, 0x58, 0xc2, 0x31, 0x97, 0xfe, 0x6e, 0x11, 0x2d, 0x3d, 0x72, 0x09,
0xa0, 0x37, 0x64, 0xc8, 0x65, 0x86, 0xc2, 0xdf, 0xd3, 0xb9, 0xc3, 0xe1, 0x69, 0x64, 0x5b, 0x8d,
0xaa, 0x56, 0xa3, 0x4f, 0x45, 0xab, 0xf1, 0x4a, 0x29, 0x09, 0xe1, 0x68, 0x8c, 0x0c, 0xf3, 0x31,
0xe6, 0xa9, 0x59, 0xea, 0x5d, 0x74, 0xb4, 0x6c, 0x2d, 0x46, 0x02, 0xd8, 0xaf, 0x8e, 0xe5, 0x77,
0x8b, 0x6d, 0x97, 0x7e, 0x98, 0x40, 0xbf, 0x7e, 0x3e, 0xc1, 0x68, 0x2e, 0x90, 0x1c, 0x43, 0x47,
0xf1, 0xbc, 0x3c, 0xa1, 0x31, 0xd7, 0x5a, 0x74, 0xef, 0xdc, 0x62, 0xf8, 0x73, 0x17, 0x4e, 0x63,
0x9c, 0x64, 0x42, 0x22, 0x5f, 0xe7, 0x58, 0x71, 0x73, 0x5a, 0xb8, 0xb9, 0xad, 0xdc, 0x3a, 0x35,
0x6e, 0x3a, 0x9e, 0x2a, 0x21, 0xe9, 0xac, 0xe0, 0xb9, 0x1f, 0x97, 0x1e, 0x19, 0x80, 0x47, 0x47,
0xdf, 0x30, 0x95, 0xdb, 0x58, 0x96, 0x65, 0xc4, 0x87, 0xae, 0x49, 0x99, 0x15, 0x5e, 0xa1, 0x54,
0xb9, 0x0d, 0xc2, 0xdd, 0x2d, 0x84, 0xf7, 0xeb, 0x84, 0x09, 0x83, 0x7e, 0x09, 0x63, 0xf1, 0x76,
0x55, 0xe7, 0x40, 0xeb, 0x1c, 0x0e, 0x5f, 0x47, 0xcb, 0xb9, 0x8d, 0x36, 0x40, 0x8a, 0x3e, 0xb4,
0x2c, 0xbf, 0xca, 0x25, 0x5f, 0xc4, 0xad, 0xca, 0xc1, 0x33, 0xe8, 0xb7, 0x2d, 0x31, 0x60, 0xf5,
0x45, 0x0a, 0x0d, 0xdb, 0x9c, 0xa0, 0xb0, 0x83, 0x1f, 0x0e, 0x9c, 0x6d, 0xd4, 0x37, 0x53, 0x70,
0x83, 0x8b, 0x6a, 0x0a, 0xb4, 0x49, 0xae, 0x61, 0x6f, 0x9e, 0x4c, 0x15, 0x96, 0x03, 0x70, 0xf9,
0x9f, 0xed, 0xc7, 0x56, 0xe5, 0x95, 0xfb, 0xd2, 0x09, 0x7f, 0x39, 0xe0, 0x37, 0xd7, 0x6e, 0x9c,
0x43, 0xfb, 0xf4, 0xdc, 0xe5, 0xd3, 0xfb, 0x77, 0xd5, 0x9d, 0xbb, 0x5d, 0xb5, 0x9e, 0x19, 0x21,
0x93, 0xd1, 0x14, 0xab, 0x99, 0xb1, 0x9e, 0x19, 0x01, 0x6b, 0x99, 0x07, 0x68, 0x08, 0x55, 0x6e,
0x88, 0x70, 0xbe, 0xde, 0xe0, 0x7b, 0x25, 0x99, 0x92, 0xa2, 0x9a, 0xe3, 0x66, 0x9b, 0xcf, 0xa1,
0x4b, 0x6d, 0xcd, 0xb6, 0xb7, 0x52, 0xd5, 0x0d, 0xff, 0xb8, 0x70, 0xaf, 0xd2, 0xbf, 0xa6, 0x79,
0x26, 0x29, 0x27, 0x6f, 0xc0, 0x7b, 0x97, 0xcf, 0xe9, 0x8d, 0x6e, 0x6f, 0x05, 0xb5, 0x0d, 0x95,
0x9b, 0x07, 0x67, 0x2d, 0x19, 0x8b, 0x2f, 0xdc, 0x21, 0x1f, 0xe1, 0x68, 0xf5, 0x81, 0x93, 0xf3,
0xda, 0x8d, 0x35, 0xbe, 0x6c, 0xc1, 0xa3, 0x8d, 0xf9, 0xa5, 0xe4, 0x67, 0x38, 0x5e, 0xc7, 0x41,
0xc2, 0xed, 0x83, 0x10, 0x3c, 0xbe, 0xb5, 0x66, 0x29, 0xff, 0xa5, 0xf9, 0xb9, 0x28, 0x69, 0x93,
0xa7, 0xb7, 0x28, 0xd4, 0x6f, 0x24, 0x38, 0x69, 0xe0, 0xbe, 0x32, 0xff, 0x02, 0xe1, 0xce, 0xc8,
0x2b, 0x22, 0x2f, 0xfe, 0x06, 0x00, 0x00, 0xff, 0xff, 0x12, 0x2a, 0xc4, 0xa0, 0x42, 0x06, 0x00,
0x00,
var fileDescriptor_resource_03e51d5764cd9ae8 = []byte{
// 601 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0xcd, 0x6e, 0xd3, 0x40,
0x10, 0xae, 0x9d, 0xd6, 0x49, 0xa6, 0x55, 0xa8, 0xb6, 0x51, 0xeb, 0x1a, 0x54, 0x2a, 0x73, 0x01,
0x0e, 0x0e, 0x84, 0x43, 0x11, 0x42, 0x42, 0x42, 0xf4, 0xc0, 0xa1, 0x02, 0xcc, 0x19, 0x24, 0xc7,
0x9e, 0x46, 0xa6, 0x8e, 0x77, 0x59, 0xaf, 0x23, 0xe5, 0xc6, 0x9b, 0xf0, 0x48, 0xbc, 0x01, 0x27,
0x1e, 0x84, 0x5d, 0xaf, 0x1d, 0xe2, 0xd8, 0x69, 0x2a, 0x4e, 0x99, 0xbf, 0xfd, 0x76, 0xe6, 0x9b,
0x6f, 0x1d, 0x18, 0x70, 0xcc, 0x68, 0xce, 0x43, 0xf4, 0x18, 0xa7, 0x82, 0x92, 0x3e, 0xcb, 0x93,
0x7c, 0x16, 0x73, 0x16, 0x3a, 0xf7, 0xa7, 0x94, 0x4e, 0x13, 0x1c, 0x15, 0x89, 0x49, 0x7e, 0x3d,
0xc2, 0x19, 0x13, 0x0b, 0x5d, 0xe7, 0x3c, 0x58, 0x4f, 0x66, 0x82, 0xe7, 0xa1, 0x28, 0xb3, 0x03,
0xf9, 0x33, 0x8f, 0x23, 0xe4, 0xda, 0x77, 0x7f, 0x1b, 0x70, 0xe4, 0x63, 0x10, 0xf9, 0xe5, 0x65,
0x3e, 0x7e, 0xcf, 0x31, 0x13, 0x64, 0x00, 0x66, 0x1c, 0xd9, 0xc6, 0xb9, 0xf1, 0xb8, 0xef, 0x4b,
0x8b, 0x10, 0xd8, 0x15, 0x0b, 0x86, 0xb6, 0x59, 0x44, 0x0a, 0x5b, 0xc5, 0xd2, 0x60, 0x86, 0x76,
0x47, 0xc7, 0x94, 0x4d, 0x8e, 0xc1, 0x62, 0x01, 0xc7, 0x54, 0xd8, 0xbb, 0x45, 0xb4, 0xf4, 0xc8,
0x05, 0x80, 0xbc, 0x90, 0x21, 0x17, 0x31, 0x66, 0xf6, 0x9e, 0xcc, 0xed, 0x8f, 0x4f, 0x3c, 0xdd,
0xaa, 0x57, 0xb5, 0xea, 0x7d, 0x2e, 0x5a, 0xf5, 0x57, 0x4a, 0x89, 0x0b, 0x07, 0x11, 0x32, 0x4c,
0x23, 0x4c, 0x43, 0x75, 0xd4, 0x3a, 0xef, 0x48, 0xd8, 0x5a, 0x8c, 0x38, 0xd0, 0xab, 0xc6, 0xb2,
0xbb, 0xc5, 0xb5, 0x4b, 0xdf, 0x0d, 0x60, 0x58, 0x9f, 0x2f, 0x63, 0x34, 0xcd, 0x90, 0x1c, 0x42,
0x27, 0xe7, 0x69, 0x39, 0xa1, 0x32, 0xd7, 0x5a, 0x34, 0xef, 0xdc, 0xa2, 0xfb, 0x6b, 0x17, 0x4e,
0x7c, 0x9c, 0xc6, 0x99, 0x40, 0xbe, 0xce, 0x63, 0xc5, 0x9b, 0xd1, 0xc2, 0x9b, 0xd9, 0xca, 0x5b,
0xa7, 0xc6, 0x9b, 0x8c, 0x87, 0x79, 0x26, 0xe8, 0xac, 0xe0, 0xb3, 0xe7, 0x97, 0x1e, 0x19, 0x81,
0x45, 0x27, 0xdf, 0x30, 0x14, 0xdb, 0xb8, 0x2c, 0xcb, 0x88, 0x0d, 0x5d, 0x95, 0x52, 0x27, 0xac,
0x02, 0xa9, 0x72, 0x1b, 0x0c, 0x77, 0xb7, 0x30, 0xdc, 0xab, 0x33, 0x4c, 0x18, 0x0c, 0x4b, 0x32,
0x16, 0xef, 0x56, 0x71, 0xfa, 0x12, 0x67, 0x7f, 0xfc, 0xda, 0x5b, 0xea, 0xd6, 0xdb, 0x40, 0x92,
0xf7, 0xb1, 0xe5, 0xf8, 0x65, 0x2a, 0xf8, 0xc2, 0x6f, 0x45, 0x26, 0xcf, 0xe0, 0x28, 0xc2, 0x04,
0x05, 0xbe, 0xc5, 0x6b, 0xca, 0x25, 0x0c, 0x4b, 0x82, 0x10, 0x6d, 0x28, 0xe6, 0x6a, 0x4b, 0x39,
0x4f, 0x61, 0xd8, 0x76, 0x89, 0x5a, 0x85, 0x5c, 0x7d, 0x26, 0xd7, 0xa3, 0x66, 0x2e, 0x6c, 0xe7,
0x87, 0x01, 0xa7, 0x1b, 0x3b, 0x52, 0xba, 0xb9, 0xc1, 0x45, 0xa5, 0x1b, 0x69, 0x92, 0x2b, 0xd8,
0x9b, 0x07, 0x49, 0x8e, 0xa5, 0x64, 0x2e, 0xfe, 0x73, 0x60, 0x5f, 0xa3, 0xbc, 0x32, 0x5f, 0x1a,
0xee, 0x4f, 0x03, 0xec, 0xe6, 0xd9, 0x8d, 0xca, 0xd5, 0x8f, 0xd5, 0x5c, 0x3e, 0xd6, 0x7f, 0xe2,
0xe8, 0xdc, 0x4d, 0x1c, 0x52, 0x65, 0x99, 0x08, 0x26, 0x09, 0x56, 0x2a, 0xd3, 0x9e, 0x12, 0x8d,
0xb6, 0xd4, 0x93, 0x55, 0x0c, 0x55, 0xae, 0x8b, 0x70, 0xb6, 0xde, 0xe0, 0x87, 0x5c, 0xb0, 0x5c,
0x64, 0x95, 0xf2, 0x9b, 0x6d, 0x3e, 0x87, 0x2e, 0xd5, 0x35, 0xdb, 0x5e, 0x57, 0x55, 0x37, 0xfe,
0x63, 0xc2, 0xbd, 0x0a, 0xff, 0x8a, 0xa6, 0xb1, 0xa0, 0x9c, 0xbc, 0x01, 0xeb, 0x7d, 0x3a, 0xa7,
0x37, 0xb2, 0xbd, 0x15, 0xaa, 0x75, 0xa8, 0xbc, 0xdc, 0x39, 0x6d, 0xc9, 0x68, 0xfa, 0xdc, 0x1d,
0xf2, 0x09, 0x0e, 0x56, 0x3f, 0x09, 0xe4, 0xac, 0xb6, 0xb1, 0xc6, 0xb7, 0xd0, 0x79, 0xb8, 0x31,
0xbf, 0x84, 0xfc, 0x02, 0x87, 0xeb, 0x74, 0x10, 0x77, 0xbb, 0x10, 0x9c, 0x47, 0xb7, 0xd6, 0x2c,
0xe1, 0xbf, 0x36, 0x3f, 0x30, 0x25, 0xdb, 0xe4, 0xc9, 0x2d, 0x08, 0xf5, 0x8d, 0x38, 0xc7, 0x0d,
0xba, 0x2f, 0xd5, 0xff, 0x86, 0xbb, 0x33, 0xb1, 0x8a, 0xc8, 0x8b, 0xbf, 0x01, 0x00, 0x00, 0xff,
0xff, 0x00, 0xdf, 0xec, 0x9d, 0x74, 0x06, 0x00, 0x00,
}

View file

@ -66,8 +66,8 @@ message RegisterResourceRequest {
bool protect = 6; // true if the resource should be marked protected.
repeated string dependencies = 7; // a list of URNs that this resource depends on, as observed by the language host.
string provider = 8; // an optional reference to the provider to manage this resource's CRUD operations.
map<string, PropertyDependencies> propertyDependencies = 9; // a map from property keys to the dependencies of the property.
bool deleteBeforeReplace = 10; // true if this resource should be deleted before replacement.
}
// RegisterResourceResponse is returned by the engine after a resource has finished being initialized. It includes the

View file

@ -43,6 +43,11 @@ class ResourceOptions:
If provided and True, this resource is not allowed to be deleted.
"""
delete_before_replace: Optional[bool]
"""
If provided and True, this resource must be deleted before it is replaced.
"""
provider: Optional['ProviderResource']
"""
An optional provider to use for this resource's CRUD operations. If no provider is supplied, the default
@ -61,7 +66,8 @@ class ResourceOptions:
depends_on: Optional[List['Resource']] = None,
protect: Optional[bool] = None,
provider: Optional['ProviderResource'] = None,
providers: Optional[Mapping[str, 'ProviderResource']] = None) -> None:
providers: Optional[Mapping[str, 'ProviderResource']] = None,
delete_before_replace: Optional[bool] = None) -> None:
"""
:param Optional[Resource] parent: If provided, the currently-constructing resource should be the child of
the provided parent resource.
@ -73,13 +79,14 @@ class ResourceOptions:
provider is pulled from the parent's provider bag.
:param Optional[Mapping[str,ProviderResource]] providers: An optional set of providers to use for child resources. Keyed
by package name (e.g. "aws")
:param Optional[bool] delete_before_replace: If provided and True, this resource must be deleted before it is replaced.
"""
self.parent = parent
self.depends_on = depends_on
self.protect = protect
self.provider = provider
self.providers = providers
self.delete_before_replace = delete_before_replace
class Resource:
"""

View file

@ -22,7 +22,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='pulumirpc',
syntax='proto3',
serialized_options=None,
serialized_pb=_b('\n\x0eresource.proto\x12\tpulumirpc\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x0eprovider.proto\"\xa2\x01\n\x13ReadResourceRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0e\n\x06parent\x18\x04 \x01(\t\x12+\n\nproperties\x18\x05 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x14\n\x0c\x64\x65pendencies\x18\x06 \x03(\t\x12\x10\n\x08provider\x18\x07 \x01(\t\"P\n\x14ReadResourceResponse\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"\xaf\x03\n\x17RegisterResourceRequest\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06parent\x18\x03 \x01(\t\x12\x0e\n\x06\x63ustom\x18\x04 \x01(\x08\x12\'\n\x06object\x18\x05 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0f\n\x07protect\x18\x06 \x01(\x08\x12\x14\n\x0c\x64\x65pendencies\x18\x07 \x03(\t\x12\x10\n\x08provider\x18\x08 \x01(\t\x12Z\n\x14propertyDependencies\x18\t \x03(\x0b\x32<.pulumirpc.RegisterResourceRequest.PropertyDependenciesEntry\x1a$\n\x14PropertyDependencies\x12\x0c\n\x04urns\x18\x01 \x03(\t\x1at\n\x19PropertyDependenciesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x46\n\x05value\x18\x02 \x01(\x0b\x32\x37.pulumirpc.RegisterResourceRequest.PropertyDependencies:\x02\x38\x01\"}\n\x18RegisterResourceResponse\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\'\n\x06object\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0e\n\x06stable\x18\x04 \x01(\x08\x12\x0f\n\x07stables\x18\x05 \x03(\t\"W\n\x1eRegisterResourceOutputsRequest\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12(\n\x07outputs\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct2\xe4\x02\n\x0fResourceMonitor\x12?\n\x06Invoke\x12\x18.pulumirpc.InvokeRequest\x1a\x19.pulumirpc.InvokeResponse\"\x00\x12Q\n\x0cReadResource\x12\x1e.pulumirpc.ReadResourceRequest\x1a\x1f.pulumirpc.ReadResourceResponse\"\x00\x12]\n\x10RegisterResource\x12\".pulumirpc.RegisterResourceRequest\x1a#.pulumirpc.RegisterResourceResponse\"\x00\x12^\n\x17RegisterResourceOutputs\x12).pulumirpc.RegisterResourceOutputsRequest\x1a\x16.google.protobuf.Empty\"\x00\x62\x06proto3')
serialized_pb=_b('\n\x0eresource.proto\x12\tpulumirpc\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x0eprovider.proto\"\xa2\x01\n\x13ReadResourceRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0e\n\x06parent\x18\x04 \x01(\t\x12+\n\nproperties\x18\x05 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x14\n\x0c\x64\x65pendencies\x18\x06 \x03(\t\x12\x10\n\x08provider\x18\x07 \x01(\t\"P\n\x14ReadResourceResponse\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"\xcc\x03\n\x17RegisterResourceRequest\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06parent\x18\x03 \x01(\t\x12\x0e\n\x06\x63ustom\x18\x04 \x01(\x08\x12\'\n\x06object\x18\x05 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0f\n\x07protect\x18\x06 \x01(\x08\x12\x14\n\x0c\x64\x65pendencies\x18\x07 \x03(\t\x12\x10\n\x08provider\x18\x08 \x01(\t\x12Z\n\x14propertyDependencies\x18\t \x03(\x0b\x32<.pulumirpc.RegisterResourceRequest.PropertyDependenciesEntry\x12\x1b\n\x13\x64\x65leteBeforeReplace\x18\n \x01(\x08\x1a$\n\x14PropertyDependencies\x12\x0c\n\x04urns\x18\x01 \x03(\t\x1at\n\x19PropertyDependenciesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x46\n\x05value\x18\x02 \x01(\x0b\x32\x37.pulumirpc.RegisterResourceRequest.PropertyDependencies:\x02\x38\x01\"}\n\x18RegisterResourceResponse\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\'\n\x06object\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0e\n\x06stable\x18\x04 \x01(\x08\x12\x0f\n\x07stables\x18\x05 \x03(\t\"W\n\x1eRegisterResourceOutputsRequest\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12(\n\x07outputs\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct2\xe4\x02\n\x0fResourceMonitor\x12?\n\x06Invoke\x12\x18.pulumirpc.InvokeRequest\x1a\x19.pulumirpc.InvokeResponse\"\x00\x12Q\n\x0cReadResource\x12\x1e.pulumirpc.ReadResourceRequest\x1a\x1f.pulumirpc.ReadResourceResponse\"\x00\x12]\n\x10RegisterResource\x12\".pulumirpc.RegisterResourceRequest\x1a#.pulumirpc.RegisterResourceResponse\"\x00\x12^\n\x17RegisterResourceOutputs\x12).pulumirpc.RegisterResourceOutputsRequest\x1a\x16.google.protobuf.Empty\"\x00\x62\x06proto3')
,
dependencies=[google_dot_protobuf_dot_empty__pb2.DESCRIPTOR,google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,provider__pb2.DESCRIPTOR,])
@ -166,8 +166,8 @@ _REGISTERRESOURCEREQUEST_PROPERTYDEPENDENCIES = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=629,
serialized_end=665,
serialized_start=658,
serialized_end=694,
)
_REGISTERRESOURCEREQUEST_PROPERTYDEPENDENCIESENTRY = _descriptor.Descriptor(
@ -203,8 +203,8 @@ _REGISTERRESOURCEREQUEST_PROPERTYDEPENDENCIESENTRY = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=667,
serialized_end=783,
serialized_start=696,
serialized_end=812,
)
_REGISTERRESOURCEREQUEST = _descriptor.Descriptor(
@ -277,6 +277,13 @@ _REGISTERRESOURCEREQUEST = _descriptor.Descriptor(
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='deleteBeforeReplace', full_name='pulumirpc.RegisterResourceRequest.deleteBeforeReplace', index=9,
number=10, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@ -290,7 +297,7 @@ _REGISTERRESOURCEREQUEST = _descriptor.Descriptor(
oneofs=[
],
serialized_start=352,
serialized_end=783,
serialized_end=812,
)
@ -348,8 +355,8 @@ _REGISTERRESOURCERESPONSE = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=785,
serialized_end=910,
serialized_start=814,
serialized_end=939,
)
@ -386,8 +393,8 @@ _REGISTERRESOURCEOUTPUTSREQUEST = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=912,
serialized_end=999,
serialized_start=941,
serialized_end=1028,
)
_READRESOURCEREQUEST.fields_by_name['properties'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT
@ -466,8 +473,8 @@ _RESOURCEMONITOR = _descriptor.ServiceDescriptor(
file=DESCRIPTOR,
index=0,
serialized_options=None,
serialized_start=1002,
serialized_end=1358,
serialized_start=1031,
serialized_end=1387,
methods=[
_descriptor.MethodDescriptor(
name='Invoke',

View file

@ -182,7 +182,8 @@ def register_resource(res: 'Resource', ty: str, name: str, custom: bool, props:
protect=opts.protect,
provider=resolver.provider_ref,
dependencies=resolver.dependencies,
propertyDependencies=property_dependencies
propertyDependencies=property_dependencies,
deleteBeforeReplace=opts.delete_before_replace
)
def do_rpc_call():

View file

@ -24,7 +24,7 @@ class AssetTest(LanghostTest):
expected_resource_count=3)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps):
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
self.assertEqual(ty, "test:index:MyResource")
if name == "file":
self.assertIsInstance(resource["asset"], FileAsset)

View file

@ -31,7 +31,7 @@ class ChainedFailureTest(LanghostTest):
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, res, _deps,
_parent, _custom, _protect, _provider, _property_deps):
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
if ty == "test:index:ResourceA":
self.assertEqual(name, "resourceA")
self.assertDictEqual(res, {"inprop": 777})

View file

@ -28,7 +28,7 @@ class ConfigTest(LanghostTest):
expected_resource_count=1)
def register_resource(self, ctx, dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps):
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
self.assertEqual("test:index:MyResource", ty)
self.assertEqual("myname", name)
return {

View file

@ -0,0 +1,13 @@
# Copyright 2016-2018, 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.

View file

@ -0,0 +1,20 @@
# Copyright 2016-2018, 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.
from pulumi import CustomResource, ResourceOptions
class MyResource(CustomResource):
def __init__(self, name, opts=None):
CustomResource.__init__(self, "test:index:MyResource", name, opts=opts)
res = MyResource("foo", opts=ResourceOptions(delete_before_replace=True))

View file

@ -0,0 +1,33 @@
# Copyright 2016-2018, 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.
from os import path
from ..util import LanghostTest
class DeleteBeforeReplaceTest(LanghostTest):
"""
Tests that DBRed resources correctly pass the "DBR" boolean to the engine.
"""
def test_protect(self):
self.run_test(
program=path.join(self.base_path(), "delete_before_replace"),
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, delete_before_replace):
self.assertEqual("foo", name)
self.assertTrue(delete_before_replace)
return {
"urn": self.make_urn(ty, name)
}

View file

@ -29,7 +29,7 @@ class FirstClassProviderTest(LanghostTest):
expected_resource_count=2)
def register_resource(self, _ctx, _dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, provider, _property_deps):
_dependencies, _parent, _custom, _protect, provider, _property_deps, _delete_before_replace):
if name == "testprov":
# Provider resource.
self.assertEqual("pulumi:providers:test", ty)

View file

@ -53,7 +53,7 @@ class TestFirstClassProviderInvoke(LanghostTest):
}
def register_resource(self, _ctx, _dry_run, ty, name, resource, _deps,
_parent, _custom, _protect, _provider, _property_deps):
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
if name == "testprov":
self.assertEqual("pulumi:providers:test", ty)
self.prov_urn = self.make_urn(ty, name)

View file

@ -31,7 +31,7 @@ class FirstClassProviderUnknown(LanghostTest):
expected_resource_count=2)
def register_resource(self, _ctx, dry_run, ty, name, resource, _deps,
_parent, _custom, _protect, provider, _property_deps):
_parent, _custom, _protect, provider, _property_deps, _delete_before_replace):
if name == "testprov":
self.assertEqual("pulumi:providers:test", ty)
# Only provide an ID when doing an update. When doing a preview the ID will be unknown

View file

@ -26,7 +26,7 @@ class FutureInputTest(LanghostTest):
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, resource, _deps,
_parent, _custom, _protect, _provider, _property_deps):
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
self.assertEqual(ty, "test:index:FileResource")
self.assertEqual(name, "file")
self.assertDictEqual(resource, {

View file

@ -30,7 +30,7 @@ class InheritDefaultsTest(LanghostTest):
expected_resource_count=240)
def register_resource(self, _ctx, _dry_run, ty, name, _resource,
_dependencies, _parent, custom, protect, provider, _property_deps):
_dependencies, _parent, custom, protect, provider, _property_deps, _delete_before_replace):
if custom and not ty.startswith("pulumi:providers:"):
expect_protect = False
expect_provider_name = ""

View file

@ -33,7 +33,7 @@ class TestInvoke(LanghostTest):
}
def register_resource(self, _ctx, _dry_run, ty, name, resource, _deps,
_parent, _custom, _protect, _provider, _property_deps):
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
self.assertEqual("test:index:MyResource", ty)
self.assertEqual("resourceA", name)
self.assertEqual(resource["value"], 42)

View file

@ -23,7 +23,7 @@ class OneComplexResourceTest(LanghostTest):
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps):
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
self.assertEqual(ty, "test:index:MyResource")
self.assertEqual(name, "testres")
self.assertEqual(resource["falseprop"], False)

View file

@ -22,7 +22,7 @@ class OneResourceTest(LanghostTest):
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps):
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
self.assertEqual(ty, "test:index:MyResource")
self.assertEqual(name, "testResource1")
return {

View file

@ -24,7 +24,7 @@ class OutputAllTest(LanghostTest):
expected_resource_count=3)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps):
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
number = 0
if name == "testResource1":
self.assertEqual(ty, "test:index:MyResource")

View file

@ -22,7 +22,7 @@ class OutputNestedTest(LanghostTest):
expected_resource_count=3)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps):
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
nested_numbers = None
if name == "testResource1":
self.assertEqual(ty, "test:index:MyResource")

View file

@ -25,7 +25,7 @@ class PreviewTest(LanghostTest):
expected_resource_count=1)
def register_resource(self, _ctx, dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps):
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
self.assertEqual(ty, "test:index:MyResource")
self.assertEqual(name, "foo")
if dry_run:

View file

@ -23,7 +23,7 @@ class PropertyDependenciesTest(LanghostTest):
expected_resource_count=5)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_dependencies):
_dependencies, _parent, _custom, _protect, _provider, _property_dependencies, _delete_before_replace):
self.assertEqual(ty, "test:index:MyResource")
if name == "resA":
self.assertListEqual(_dependencies, [])

View file

@ -26,7 +26,7 @@ class PropertyRenamingTest(LanghostTest):
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, res, _deps,
_parent, _custom, _protect, _provider, _property_deps):
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
# Test:
# 1. Everything that we receive from the running program is in camel-case. The engine never sees
# the pre-translated names of the input properties.

View file

@ -25,7 +25,7 @@ class ProtectTest(LanghostTest):
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, _resource,
_dependencies, _parent, _custom, protect, _provider, _property_deps):
_dependencies, _parent, _custom, protect, _provider, _property_deps, _delete_before_replace):
self.assertEqual("foo", name)
self.assertTrue(protect)
return {

View file

@ -22,5 +22,5 @@ class UnhandledExceptionTest(LanghostTest):
expected_error="Program exited with non-zero exit code: 1")
def register_resource(self, _ctx, _dry_run, _ty, _name, _resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps):
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
raise Exception("oh no")

View file

@ -30,7 +30,7 @@ class ResourceThensTest(LanghostTest):
expected_resource_count=2)
def register_resource(self, _ctx, dry_run, ty, name, res, deps,
_parent, custom, _protect, _provider, _property_deps):
_parent, custom, _protect, _provider, _property_deps, _delete_before_replace):
if ty == "test:index:ResourceA":
self.assertEqual(name, "resourceA")
self.assertDictEqual(res, {"inprop": 777})

View file

@ -25,7 +25,7 @@ class TenResourcesTest(LanghostTest):
expected_resource_count=10)
def register_resource(self, ctx, dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps):
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace):
self.assertEqual("test:index:MyResource", ty)
if not dry_run:
self.assertIsNone(

View file

@ -19,6 +19,7 @@ import asyncio
import unittest
from collections import namedtuple
from concurrent import futures
from inspect import signature
import logging
import subprocess
from os import path
@ -87,6 +88,7 @@ class LanghostMockResourceMonitor(proto.ResourceMonitorServicer):
custom = request.custom
protect = request.protect
provider = request.provider
delete_before_replace = request.deleteBeforeReplace
property_dependencies = {}
for key, value in request.propertyDependencies.items():
@ -95,7 +97,7 @@ class LanghostMockResourceMonitor(proto.ResourceMonitorServicer):
outs = {}
if type_ != "pulumi:pulumi:Stack":
outs = self.langhost_test.register_resource(
context, self.dryrun, type_, name, props, deps, parent, custom, protect, provider, property_dependencies)
context, self.dryrun, type_, name, props, deps, parent, custom, protect, provider, property_dependencies, delete_before_replace)
if outs.get("urn"):
urn = outs["urn"]
self.registrations[urn] = {

View file

@ -32,6 +32,10 @@ func TestDeleteBeforeCreate(t *testing.T) {
Dir: "step5",
Additive: true,
},
{
Dir: "step6",
Additive: true,
},
},
})
}

View file

@ -18,7 +18,7 @@ export class Provider implements dynamic.ResourceProvider {
return {
changes: true,
replaces: ["state"],
deleteBeforeReplace: true,
deleteBeforeReplace: news.noDBR ? false : true,
};
}
@ -46,7 +46,7 @@ export class Resource extends pulumi.dynamic.Resource {
public state: pulumi.Output<number>;
public noReplace?: pulumi.Output<number>;
constructor(name: string, props: ResourceProps, opts?: pulumi.ResourceOptions) {
constructor(name: string, props: ResourceProps, opts?: pulumi.CustomResourceOptions) {
super(Provider.instance, name, props, opts);
}
}
@ -55,4 +55,5 @@ export interface ResourceProps {
readonly uniqueKey?: pulumi.Input<number>;
readonly state: pulumi.Input<number>;
readonly noReplace?: pulumi.Input<number>;
readonly noDBR?: pulumi.Input<boolean>;
}

View file

@ -0,0 +1,13 @@
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
import * as pulumi from "@pulumi/pulumi";
import { Resource } from "./resource";
// Base should not be delete-before-replaced, but should still be replaced.
const a = new Resource("base", { uniqueKey: 1, state: 42, noDBR: true });
// Base-2 should not be delete-before-replaced, but should still be replaced.
const b = new Resource("base-2", { uniqueKey: 2, state: 42, noDBR: true });
// Dependent should be delete-before-replaced.
const c = new Resource("dependent", { state: pulumi.all([a.state, b.state]).apply(([astate, bstate]) => astate + bstate), noDBR: true }, { deleteBeforeReplace: true });