Add replaceOnChanges resource option (#7226)

Adds a new resource option to force replacement when certain properties report changes, even if the resource provider itself does not require a replacement.

Fixes #6753.

Co-authored-by: Levi Blackstone <levi@pulumi.com>
This commit is contained in:
Luke Hoban 2021-07-02 05:32:08 +10:00 committed by GitHub
parent 2781cf03fe
commit eb32039013
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 998 additions and 347 deletions

View file

@ -1,6 +1,7 @@
### Improvements
- [sdk] - Add `replaceOnChanges` resource option.
[#7226](https://github.com/pulumi/pulumi/pull/722
### Bug Fixes

View file

@ -722,3 +722,81 @@ func TestProviderInheritanceGolangLifecycle(t *testing.T) {
}
p.Run(t, nil)
}
// This test validates the wiring of the ReplaceOnChanges prop in the go SDK.
// It doesn't attempt to validate underlying behavior.
func TestReplaceOnChangesGolangLifecycle(t *testing.T) {
var expectedReplaceOnChanges []string
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
return &deploytest.Provider{
CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
return "created-id", news, resource.StatusOK, nil
},
ReadF: func(urn resource.URN, id resource.ID,
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
},
DiffF: func(urn resource.URN, id resource.ID,
olds, news resource.PropertyMap, replaceOnChanges []string) (plugin.DiffResult, error) {
// just verify that the ReplaceOnChanges prop made it through
assert.Equal(t, expectedReplaceOnChanges, replaceOnChanges)
return plugin.DiffResult{}, nil
},
}, nil
}),
}
setupAndRunProgram := func(replaceOnChanges []string) *deploy.Snapshot {
program := deploytest.NewLanguageRuntime(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
Project: info.Project,
Stack: info.Stack,
Parallel: info.Parallel,
DryRun: info.DryRun,
MonitorAddr: info.MonitorAddress,
})
assert.NoError(t, err)
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
var res pulumi.CustomResourceState
err := ctx.RegisterResource("pkgA:m:typA", "resA", nil, &res, pulumi.ReplaceOnChanges(replaceOnChanges))
assert.NoError(t, err)
return nil
})
})
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{Host: host},
Steps: []TestStep{
{
Op: Update,
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
events []Event, res result.Result) result.Result {
for _, event := range events {
if event.Type == ResourcePreEvent {
payload := event.Payload().(ResourcePreEventPayload)
assert.Equal(t, []deploy.StepOp{deploy.OpCreate}, []deploy.StepOp{payload.Metadata.Op})
}
}
return res
},
},
},
}
return p.Run(t, nil)
}
// replace on changes specified
replaceOnChanges := []string{"b"}
setupAndRunProgram(replaceOnChanges)
// replace on changes empty
replaceOnChanges = []string{}
setupAndRunProgram(replaceOnChanges)
}

View file

@ -942,6 +942,158 @@ func TestSingleResourceIgnoreChanges(t *testing.T) {
}, []string{"a", "b"}, []deploy.StepOp{deploy.OpUpdate})
}
func objectDiffToDetailedDiff(prefix string, d *resource.ObjectDiff) map[string]plugin.PropertyDiff {
ret := map[string]plugin.PropertyDiff{}
for k, vd := range d.Updates {
var nestedPrefix string
if prefix == "" {
nestedPrefix = string(k)
} else {
nestedPrefix = fmt.Sprintf("%s.%s", prefix, string(k))
}
for kk, pd := range valueDiffToDetailedDiff(nestedPrefix, vd) {
ret[kk] = pd
}
}
return ret
}
func arrayDiffToDetailedDiff(prefix string, d *resource.ArrayDiff) map[string]plugin.PropertyDiff {
ret := map[string]plugin.PropertyDiff{}
for i, vd := range d.Updates {
for kk, pd := range valueDiffToDetailedDiff(fmt.Sprintf("%s[%d]", prefix, i), vd) {
ret[kk] = pd
}
}
return ret
}
func valueDiffToDetailedDiff(prefix string, vd resource.ValueDiff) map[string]plugin.PropertyDiff {
ret := map[string]plugin.PropertyDiff{}
if vd.Object != nil {
for kk, pd := range objectDiffToDetailedDiff(prefix, vd.Object) {
ret[kk] = pd
}
} else if vd.Array != nil {
for kk, pd := range arrayDiffToDetailedDiff(prefix, vd.Array) {
ret[kk] = pd
}
} else {
ret[prefix] = plugin.PropertyDiff{Kind: plugin.DiffUpdate}
}
return ret
}
func TestReplaceOnChanges(t *testing.T) {
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, ignoreChanges []string) (plugin.DiffResult, error) {
diff := olds.Diff(news)
if diff == nil {
return plugin.DiffResult{Changes: plugin.DiffNone}, nil
}
detailedDiff := objectDiffToDetailedDiff("", diff)
var changedKeys []resource.PropertyKey
for _, k := range diff.Keys() {
if diff.Changed(k) {
changedKeys = append(changedKeys, k)
}
}
return plugin.DiffResult{
Changes: plugin.DiffSome,
ChangedKeys: changedKeys,
DetailedDiff: detailedDiff,
}, nil
},
UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
return news, resource.StatusOK, nil
},
CreateF: func(urn resource.URN, inputs resource.PropertyMap, timeout float64,
preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
return resource.ID("id123"), inputs, resource.StatusOK, nil
},
}, nil
}),
}
updateProgramWithProps := func(snap *deploy.Snapshot, props resource.PropertyMap, replaceOnChanges []string,
allowedOps []deploy.StepOp) *deploy.Snapshot {
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
Inputs: props,
ReplaceOnChanges: replaceOnChanges,
})
assert.NoError(t, err)
return nil
})
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{Host: host},
Steps: []TestStep{
{
Op: Update,
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
events []Event, res result.Result) result.Result {
for _, event := range events {
if event.Type == ResourcePreEvent {
payload := event.Payload().(ResourcePreEventPayload)
assert.Subset(t, allowedOps, []deploy.StepOp{payload.Metadata.Op})
}
}
return res
},
},
},
}
return p.Run(t, snap)
}
snap := updateProgramWithProps(nil, resource.NewPropertyMapFromMap(map[string]interface{}{
"a": 1,
"b": map[string]interface{}{
"c": "foo",
},
}), []string{"a", "b.c"}, []deploy.StepOp{deploy.OpCreate})
// Ensure that a change to a replaceOnChange property results in an OpReplace
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
"a": 2,
"b": map[string]interface{}{
"c": "foo",
},
}), []string{"a"}, []deploy.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
// Ensure that a change to a nested replaceOnChange property results in an OpReplace
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
"a": 2,
"b": map[string]interface{}{
"c": "bar",
},
}), []string{"b.c"}, []deploy.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
// Ensure that a change to any property of a "*" replaceOnChange results in an OpReplace
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
"a": 3,
"b": map[string]interface{}{
"c": "baz",
},
}), []string{"*"}, []deploy.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
// Ensure that a change to an non-replaceOnChange property results in an OpUpdate
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
"a": 4,
"b": map[string]interface{}{
"c": "qux",
},
}), nil, []deploy.StepOp{deploy.OpUpdate})
_ = snap
}
// Resource is an abstract representation of a resource graph
type Resource struct {
t tokens.Type

View file

@ -95,6 +95,7 @@ type ResourceOptions struct {
DeleteBeforeReplace *bool
Version string
IgnoreChanges []string
ReplaceOnChanges []string
Aliases []resource.URN
ImportID resource.ID
CustomTimeouts *resource.CustomTimeouts
@ -185,6 +186,7 @@ func (rm *ResourceMonitor) RegisterResource(t tokens.Type, name string, custom b
CustomTimeouts: &timeouts,
SupportsPartialValues: supportsPartialValues,
Remote: opts.Remote,
ReplaceOnChanges: opts.ReplaceOnChanges,
}
// submit request

View file

@ -305,7 +305,7 @@ func (d *defaultProviders) newRegisterDefaultProviderEvent(
event := &registerResourceEvent{
goal: resource.NewGoal(
providers.MakeProviderType(req.Package()),
req.Name(), true, inputs, "", false, nil, "", nil, nil, nil, nil, nil, nil, "", nil),
req.Name(), true, inputs, "", false, nil, "", nil, nil, nil, nil, nil, nil, "", nil, nil),
done: done,
}
return event, done, nil
@ -843,6 +843,7 @@ func (rm *resmon) RegisterResource(ctx context.Context,
protect := req.GetProtect()
deleteBeforeReplaceValue := req.GetDeleteBeforeReplace()
ignoreChanges := req.GetIgnoreChanges()
replaceOnChanges := req.GetReplaceOnChanges()
id := resource.ID(req.GetImportId())
customTimeouts := req.GetCustomTimeouts()
@ -967,9 +968,9 @@ 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, deleteBeforeReplace=%v, ignoreChanges=%v, aliases=%v, customTimeouts=%v, "+
"providers=%v",
"providers=%v, replaceOnChanges=%v",
t, name, custom, len(props), parent, protect, providerRef, dependencies, deleteBeforeReplace, ignoreChanges,
aliases, timeouts, providerRefs)
aliases, timeouts, providerRefs, replaceOnChanges)
// If this is a remote component, fetch its provider and issue the construct call. Otherwise, register the resource.
var result *RegisterResult
@ -1006,7 +1007,7 @@ func (rm *resmon) RegisterResource(ctx context.Context,
step := &registerResourceEvent{
goal: resource.NewGoal(t, name, custom, props, parent, protect, dependencies,
providerRef.String(), nil, propertyDependencies, deleteBeforeReplace, ignoreChanges,
additionalSecretOutputs, aliases, id, &timeouts),
additionalSecretOutputs, aliases, id, &timeouts, replaceOnChanges),
done: make(chan *RegisterResult),
}

View file

@ -149,16 +149,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, nil, nil, nil, "", nil),
nil, "", []string{}, nil, nil, nil, nil, nil, "", nil, nil),
},
// 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, nil, nil, nil, nil, "", nil),
providerARef.String(), []string{}, nil, nil, nil, nil, nil, "", nil, nil),
},
&testRegEvent{
goal: resource.NewGoal("pkgA:index:typA", "res2", true, resource.PropertyMap{}, componentURN, false, nil,
providerARef.String(), []string{}, nil, nil, nil, nil, nil, "", nil),
providerARef.String(), []string{}, nil, nil, nil, nil, nil, "", nil, nil),
},
// Register two more providers.
newProviderEvent("pkgA", "providerB", nil, ""),
@ -166,11 +166,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, nil, nil, nil, nil, "", nil),
providerBRef.String(), []string{}, nil, nil, nil, nil, nil, "", nil, nil),
},
&testRegEvent{
goal: resource.NewGoal("pkgB:index:typC", "res4", true, resource.PropertyMap{}, "", false, nil,
providerCRef.String(), []string{}, nil, nil, nil, nil, nil, "", nil),
providerCRef.String(), []string{}, nil, nil, nil, nil, nil, "", nil, nil),
},
}
@ -233,25 +233,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, nil, nil, nil, "", nil),
nil, "", []string{}, nil, nil, nil, nil, nil, "", nil, nil),
},
// Register a couple resources from package A.
&testRegEvent{
goal: resource.NewGoal("pkgA:m:typA", "res1", true, resource.PropertyMap{},
componentURN, false, nil, "", []string{}, nil, nil, nil, nil, nil, "", nil),
componentURN, false, nil, "", []string{}, nil, nil, nil, nil, nil, "", nil, nil),
},
&testRegEvent{
goal: resource.NewGoal("pkgA:m:typA", "res2", true, resource.PropertyMap{},
componentURN, false, nil, "", []string{}, nil, nil, nil, nil, nil, "", nil),
componentURN, false, nil, "", []string{}, nil, nil, nil, nil, nil, "", nil, nil),
},
// Register a few resources from other packages.
&testRegEvent{
goal: resource.NewGoal("pkgB:m:typB", "res3", true, resource.PropertyMap{}, "", false,
nil, "", []string{}, nil, nil, nil, nil, nil, "", nil),
nil, "", []string{}, nil, nil, nil, nil, nil, "", nil, nil),
},
&testRegEvent{
goal: resource.NewGoal("pkgB:m:typC", "res4", true, resource.PropertyMap{}, "", false,
nil, "", []string{}, nil, nil, nil, nil, nil, "", nil),
nil, "", []string{}, nil, nil, nil, nil, nil, "", nil, nil),
},
}

View file

@ -1,4 +1,4 @@
// Copyright 2016-2018, Pulumi Corporation.
// Copyright 2016-2021, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -524,7 +524,15 @@ func (sg *stepGenerator) generateStepsFromDiff(
"unrecognized diff state for %s: %d", urn, diff.Changes)
}
// If there were changes, check for a replacement vs. an in-place update.
hasInitErrors := len(old.InitErrors) > 0
// Update the diff to apply any replaceOnChanges annotations and to include initErrors in the diff.
diff, err = sg.applyReplaceOnChanges(diff, goal.ReplaceOnChanges, hasInitErrors)
if err != nil {
return nil, result.FromError(err)
}
// If there were changes check for a replacement vs. an in-place update.
if diff.Changes == plugin.DiffSome {
if diff.Replace() {
// If the goal state specified an ID, issue an error: the replacement will change the ID, and is
@ -557,8 +565,8 @@ func (sg *stepGenerator) generateStepsFromDiff(
}
if logging.V(7) {
logging.V(7).Infof("Planner decided to replace '%v' (oldprops=%v inputs=%v)",
urn, oldInputs, new.Inputs)
logging.V(7).Infof("Planner decided to replace '%v' (oldprops=%v inputs=%v replaceKeys=%v)",
urn, oldInputs, new.Inputs, diff.ReplaceKeys)
}
// We have two approaches to performing replacements:
@ -638,7 +646,7 @@ func (sg *stepGenerator) generateStepsFromDiff(
// If we fell through, it's an update.
sg.updates[urn] = true
if logging.V(7) {
logging.V(7).Infof("Planner decided to update '%v' (oldprops=%v inputs=%v", urn, oldInputs, new.Inputs)
logging.V(7).Infof("Planner decided to update '%v' (oldprops=%v inputs=%v)", urn, oldInputs, new.Inputs)
}
return []Step{
NewUpdateStep(sg.deployment, event, old, new, diff.StableKeys, diff.ChangedKeys, diff.DetailedDiff,
@ -648,11 +656,12 @@ func (sg *stepGenerator) generateStepsFromDiff(
// If resource was unchanged, but there were initialization errors, generate an empty update
// step to attempt to "continue" awaiting initialization.
if len(old.InitErrors) > 0 {
if hasInitErrors {
sg.updates[urn] = true
return []Step{NewUpdateStep(sg.deployment, event, old, new, diff.StableKeys, nil, nil, nil)}, nil
}
// Else there are no changes needed
return nil, nil
}
@ -1180,6 +1189,101 @@ func (sg *stepGenerator) getProviderResource(urn resource.URN, provider string)
return result
}
// initErrorSpecialKey is a special property key used to indicate that a diff is due to
// initialization errors existing in the old state instead of due to a specific property
// diff between old and new states.
const initErrorSpecialKey = "#initerror"
// applyReplaceOnChanges adjusts a DiffResult returned from a provider to apply the ReplaceOnChange
// settings in the desired state and init errors from the previous state.
func (sg *stepGenerator) applyReplaceOnChanges(diff plugin.DiffResult,
replaceOnChanges []string, hasInitErrors bool) (plugin.DiffResult, error) {
// No further work is necessary for DiffNone unless init errors are present.
if diff.Changes != plugin.DiffSome && !hasInitErrors {
return diff, nil
}
var replaceOnChangePaths []resource.PropertyPath
for _, p := range replaceOnChanges {
path, err := resource.ParsePropertyPath(p)
if err != nil {
return diff, err
}
replaceOnChangePaths = append(replaceOnChangePaths, path)
}
// Calculate the new DetailedDiff
modifiedDiff := map[string]plugin.PropertyDiff{}
for p, v := range diff.DetailedDiff {
diffPath, err := resource.ParsePropertyPath(p)
if err != nil {
return diff, err
}
changeToReplace := false
for _, replaceOnChangePath := range replaceOnChangePaths {
if replaceOnChangePath.Contains(diffPath) {
changeToReplace = true
break
}
}
if changeToReplace {
v = v.ToReplace()
}
modifiedDiff[p] = v
}
// Calculate the new ReplaceKeys
modifiedReplaceKeysMap := map[resource.PropertyKey]struct{}{}
for _, k := range diff.ReplaceKeys {
modifiedReplaceKeysMap[k] = struct{}{}
}
for _, k := range diff.ChangedKeys {
for _, replaceOnChangePath := range replaceOnChangePaths {
keyPath, err := resource.ParsePropertyPath(string(k))
if err != nil {
continue
}
if replaceOnChangePath.Contains(keyPath) {
modifiedReplaceKeysMap[k] = struct{}{}
}
}
}
var modifiedReplaceKeys []resource.PropertyKey
for k := range modifiedReplaceKeysMap {
modifiedReplaceKeys = append(modifiedReplaceKeys, k)
}
// Add init errors to modified diff results
modifiedChanges := diff.Changes
if hasInitErrors {
for _, replaceOnChangePath := range replaceOnChangePaths {
initErrPath, err := resource.ParsePropertyPath(initErrorSpecialKey)
if err != nil {
continue
}
if replaceOnChangePath.Contains(initErrPath) {
modifiedReplaceKeys = append(modifiedReplaceKeys, initErrorSpecialKey)
modifiedDiff[initErrorSpecialKey] = plugin.PropertyDiff{
Kind: plugin.DiffUpdateReplace,
InputDiff: false,
}
// If an init error is present on a path that causes replacement, then trigger a replacement.
modifiedChanges = plugin.DiffSome
}
}
}
return plugin.DiffResult{
DetailedDiff: modifiedDiff,
ReplaceKeys: modifiedReplaceKeys,
ChangedKeys: diff.ChangedKeys,
Changes: modifiedChanges,
DeleteBeforeReplace: diff.DeleteBeforeReplace,
StableKeys: diff.StableKeys,
}, nil
}
type dependentReplace struct {
res *resource.State
keys []resource.PropertyKey

View file

@ -0,0 +1,2 @@
Pulumi.ResourceOptions.ReplaceOnChanges.get -> System.Collections.Generic.List<string>
Pulumi.ResourceOptions.ReplaceOnChanges.set -> void

View file

@ -1,4 +1,4 @@
// Copyright 2016-2019, Pulumi Corporation
// Copyright 2016-2021, Pulumi Corporation
using System.Collections.Generic;
@ -92,6 +92,20 @@ namespace Pulumi
/// </summary>
public string? Urn { get; set; }
private List<string>? _replaceOnChanges;
/// <summary>
/// Changes to any of these property paths will force a replacement. If this list
/// includes `"*"`, changes to any properties will force a replacement. Initialization
/// errors from previous deployments will require replacement instead of update only if
/// `"*"` is passed.
/// </summary>
public List<string> ReplaceOnChanges
{
get => _replaceOnChanges ??= new List<string>();
set => _replaceOnChanges = value;
}
internal abstract ResourceOptions Clone();
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2016-2019, Pulumi Corporation
// Copyright 2016-2021, Pulumi Corporation
using System.Collections.Generic;
using System.Linq;
@ -18,6 +18,7 @@ namespace Pulumi
IgnoreChanges = options.IgnoreChanges.ToList(),
Protect = options.Protect,
Provider = options.Provider,
ReplaceOnChanges = options.ReplaceOnChanges.ToList(),
ResourceTransformations = options.ResourceTransformations.ToList(),
Urn = options.Urn,
Version = options.Version

View file

@ -1,4 +1,4 @@
// Copyright 2016-2019, Pulumi Corporation
// Copyright 2016-2021, Pulumi Corporation
namespace Pulumi
{
@ -17,7 +17,8 @@ namespace Pulumi
options1.IgnoreChanges.AddRange(options2.IgnoreChanges);
options1.ResourceTransformations.AddRange(options2.ResourceTransformations);
options1.Aliases.AddRange(options2.Aliases);
options1.ReplaceOnChanges.AddRange(options2.ReplaceOnChanges);
options1.DependsOn = options1.DependsOn.Concat(options2.DependsOn);
}
}

View file

@ -155,6 +155,28 @@ func (d DiffKind) IsReplace() bool {
}
}
// AsReplace converts a DiffKind into the equivalent replacement if it not already
// a replacement.
func (d DiffKind) AsReplace() DiffKind {
switch d {
case DiffAdd:
return DiffAddReplace
case DiffAddReplace:
return DiffAddReplace
case DiffDelete:
return DiffDeleteReplace
case DiffDeleteReplace:
return DiffDeleteReplace
case DiffUpdate:
return DiffUpdateReplace
case DiffUpdateReplace:
return DiffUpdateReplace
default:
contract.Failf("Unknown diff kind %v", int(d))
return DiffUpdateReplace
}
}
const (
// DiffAdd indicates that the property was added.
DiffAdd DiffKind = 0
@ -176,6 +198,15 @@ type PropertyDiff struct {
InputDiff bool // True if this is a diff between old and new inputs rather than old state and new inputs.
}
// ToReplace converts the kind of a PropertyDiff into the equivalent replacement if it not already
// a replacement.
func (p PropertyDiff) ToReplace() PropertyDiff {
return PropertyDiff{
InputDiff: p.InputDiff,
Kind: p.Kind.AsReplace(),
}
}
// DiffResult indicates whether an operation should replace or update an existing resource.
type DiffResult struct {
Changes DiffChanges // true if this diff represents a changed resource.

View file

@ -37,13 +37,15 @@ type Goal struct {
Aliases []URN // additional URNs that should be aliased to this resource.
ID ID // the expected ID of the resource, if any.
CustomTimeouts CustomTimeouts // an optional config object for resource options
ReplaceOnChanges []string // a list of property paths that if changed should force a 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, deleteBeforeReplace *bool, ignoreChanges []string,
additionalSecretOutputs []PropertyKey, aliases []URN, id ID, customTimeouts *CustomTimeouts) *Goal {
additionalSecretOutputs []PropertyKey, aliases []URN, id ID, customTimeouts *CustomTimeouts,
replaceOnChanges []string) *Goal {
g := &Goal{
Type: t,
@ -61,6 +63,7 @@ func NewGoal(t tokens.Type, name tokens.QName, custom bool, props PropertyMap,
AdditionalSecretOutputs: additionalSecretOutputs,
Aliases: aliases,
ID: id,
ReplaceOnChanges: replaceOnChanges,
}
if customTimeouts != nil {

View file

@ -610,6 +610,7 @@ func (ctx *Context) registerResource(
AdditionalSecretOutputs: inputs.additionalSecretOutputs,
Version: inputs.version,
Remote: remote,
ReplaceOnChanges: inputs.replaceOnChanges,
})
if err != nil {
logging.V(9).Infof("RegisterResource(%s, %s): error: %v", t, name, err)
@ -931,6 +932,7 @@ type resourceInputs struct {
aliases []string
additionalSecretOutputs []string
version string
replaceOnChanges []string
}
// prepareResourceInputs prepares the inputs for a resource operation, shared between read and register.

View file

@ -1,4 +1,4 @@
// Copyright 2016-2020, Pulumi Corporation.
// Copyright 2016-2021, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -205,6 +205,10 @@ type resourceOptions struct {
Provider ProviderResource
// Providers is an optional map of package to provider resource for a component resource.
Providers map[string]ProviderResource
// ReplaceOnChanges will force a replacement when any of these property paths are set. If this list includes `"*"`,
// changes to any properties will force a replacement. Initialization errors from previous deployments will
// require replacement instead of update only if `"*"` is passed.
ReplaceOnChanges []string
// Transformations is an optional list of transformations to apply to this resource during construction.
// The transformations are applied in order, and are applied prior to transformation and to parents
// walking from the resource up to the stack.
@ -367,6 +371,15 @@ func Providers(o ...ProviderResource) ResourceOption {
return ProviderMap(m)
}
// ReplaceOnChanges will force a replacement when any of these property paths are set. If this list includes `"*"`,
// changes to any properties will force a replacement. Initialization errors from previous deployments will
// require replacement instead of update only if `"*"` is passed.
func ReplaceOnChanges(o []string) ResourceOption {
return resourceOption(func(ro *resourceOptions) {
ro.ReplaceOnChanges = append(ro.ReplaceOnChanges, o...)
})
}
// Timeouts is an optional configuration block used for CRUD operations
func Timeouts(o *CustomTimeouts) ResourceOption {
return resourceOption(func(ro *resourceOptions) {

View file

@ -1,3 +1,17 @@
// Copyright 2016-2021, 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 pulumi
import (
@ -280,3 +294,26 @@ func assertTransformations(t *testing.T, t1 []ResourceTransformation, t2 []Resou
assert.Equal(t, p1, p2)
}
}
func TestResourceOptionMergingReplaceOnChanges(t *testing.T) {
// ReplaceOnChanges arrays are always appended together
i1 := "a"
i2 := "b"
i3 := "c"
// two singleton options
opts := merge(ReplaceOnChanges([]string{i1}), ReplaceOnChanges([]string{i2}))
assert.Equal(t, []string{i1, i2}, opts.ReplaceOnChanges)
// nil i1
opts = merge(ReplaceOnChanges(nil), ReplaceOnChanges([]string{i2}))
assert.Equal(t, []string{i2}, opts.ReplaceOnChanges)
// nil i2
opts = merge(ReplaceOnChanges([]string{i1}), ReplaceOnChanges(nil))
assert.Equal(t, []string{i1}, opts.ReplaceOnChanges)
// multivalue arrays
opts = merge(ReplaceOnChanges([]string{i1, i2}), ReplaceOnChanges([]string{i2, i3}))
assert.Equal(t, []string{i1, i2, i2, i3}, opts.ReplaceOnChanges)
}

View file

@ -1230,7 +1230,7 @@ proto.pulumirpc.ReadResourceResponse.prototype.hasProperties = function() {
* @private {!Array<number>}
* @const
*/
proto.pulumirpc.RegisterResourceRequest.repeatedFields_ = [7,12,14,15];
proto.pulumirpc.RegisterResourceRequest.repeatedFields_ = [7,12,14,15,23];
@ -1284,7 +1284,8 @@ proto.pulumirpc.RegisterResourceRequest.toObject = function(includeInstance, msg
supportspartialvalues: jspb.Message.getBooleanFieldWithDefault(msg, 19, false),
remote: jspb.Message.getBooleanFieldWithDefault(msg, 20, false),
acceptresources: jspb.Message.getBooleanFieldWithDefault(msg, 21, false),
providersMap: (f = msg.getProvidersMap()) ? f.toObject(includeInstance, undefined) : []
providersMap: (f = msg.getProvidersMap()) ? f.toObject(includeInstance, undefined) : [],
replaceonchangesList: (f = jspb.Message.getRepeatedField(msg, 23)) == null ? undefined : f
};
if (includeInstance) {
@ -1415,6 +1416,10 @@ proto.pulumirpc.RegisterResourceRequest.deserializeBinaryFromReader = function(m
jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readString, null, "", "");
});
break;
case 23:
var value = /** @type {string} */ (reader.readString());
msg.addReplaceonchanges(value);
break;
default:
reader.skipField();
break;
@ -1594,6 +1599,13 @@ proto.pulumirpc.RegisterResourceRequest.serializeBinaryToWriter = function(messa
if (f && f.getLength() > 0) {
f.serializeBinary(22, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeString);
}
f = message.getReplaceonchangesList();
if (f.length > 0) {
writer.writeRepeatedString(
23,
f
);
}
};
@ -2461,6 +2473,43 @@ proto.pulumirpc.RegisterResourceRequest.prototype.clearProvidersMap = function()
return this;};
/**
* repeated string replaceOnChanges = 23;
* @return {!Array<string>}
*/
proto.pulumirpc.RegisterResourceRequest.prototype.getReplaceonchangesList = function() {
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 23));
};
/**
* @param {!Array<string>} value
* @return {!proto.pulumirpc.RegisterResourceRequest} returns this
*/
proto.pulumirpc.RegisterResourceRequest.prototype.setReplaceonchangesList = function(value) {
return jspb.Message.setField(this, 23, value || []);
};
/**
* @param {string} value
* @param {number=} opt_index
* @return {!proto.pulumirpc.RegisterResourceRequest} returns this
*/
proto.pulumirpc.RegisterResourceRequest.prototype.addReplaceonchanges = function(value, opt_index) {
return jspb.Message.addToRepeatedField(this, 23, value, opt_index);
};
/**
* Clears the list making it empty but non-null.
* @return {!proto.pulumirpc.RegisterResourceRequest} returns this
*/
proto.pulumirpc.RegisterResourceRequest.prototype.clearReplaceonchangesList = function() {
return this.setReplaceonchangesList([]);
};
/**
* List of repeated fields within this message type.

View file

@ -491,6 +491,12 @@ export interface ResourceOptions {
* Ignore changes to any of the specified properties.
*/
ignoreChanges?: string[];
/**
* Changes to any of these property paths will force a replacement. If this list includes `"*"`, changes to any
* properties will force a replacement. Initialization errors from previous deployments will require replacement
* instead of update only if `"*"` is passed.
*/
replaceOnChanges?: string[];
/**
* An optional version, corresponding to the version of the provider plugin that should be used when operating on
* this resource. This version overrides the version information inferred from the current package and should

View file

@ -316,6 +316,7 @@ export function registerResource(res: Resource, t: string, name: string, custom:
req.setImportid(resop.import || "");
req.setSupportspartialvalues(true);
req.setRemote(remote);
req.setReplaceonchangesList(opts.replaceOnChanges || []);
const customTimeouts = new resproto.RegisterResourceRequest.CustomTimeouts();
if (opts.customTimeouts != null) {

View file

@ -0,0 +1,11 @@
// This tests the replaceOnChanges ResourceOption.
let pulumi = require("../../../../../");
class MyResource extends pulumi.CustomResource {
constructor(name, opts) {
super("test:index:MyResource", name, {}, opts);
}
}
new MyResource("testResource", { replaceOnChanges: ["foo"] });

View file

@ -53,7 +53,10 @@ interface RunCase {
};
registerResource?: (ctx: any, dryrun: boolean, t: string, name: string, res: any, dependencies?: string[],
custom?: boolean, protect?: boolean, parent?: string, provider?: string,
propertyDeps?: any, ignoreChanges?: string[], version?: string, importID?: string) => { urn: URN | undefined, id: ID | undefined, props: any | undefined };
propertyDeps?: any, ignoreChanges?: string[], version?: string, importID?: string,
replaceOnChanges?: string[]) => {
urn: URN | undefined, id: ID | undefined, props: any | undefined,
};
registerResourceOutputs?: (ctx: any, dryrun: boolean, urn: URN,
t: string, name: string, res: any, outputs: any | undefined) => void;
log?: (ctx: any, severity: any, message: string, urn: URN, streamId: number) => void;
@ -808,7 +811,9 @@ describe("rpc", () => {
expectResourceCount: 3,
registerResource: (ctx: any, dryrun: boolean, t: string, name: string, res: any, dependencies?: string[],
custom?: boolean, protect?: boolean, parent?: string, provider?: string,
propertyDeps?: any, ignoreChanges?: string[], version?: string) => {
propertyDeps?: any, ignoreChanges?: string[], version?: string, importID?: string,
replaceOnChanges?: string[],
) => {
switch (name) {
case "testResource":
assert.strictEqual("0.19.1", version);
@ -1157,6 +1162,25 @@ describe("rpc", () => {
};
},
},
"replace_on_changes": {
program: path.join(base, "066.replace_on_changes"),
expectResourceCount: 1,
registerResource: (
ctx: any, dryrun: boolean, t: string, name: string, res: any, dependencies?: string[],
custom?: boolean, protect?: boolean, parent?: string, provider?: string,
propertyDeps?: any, ignoreChanges?: string[], version?: string, importID?: string,
replaceOnChanges?: string[],
) => {
if (name === "testResource") {
assert.deepStrictEqual(replaceOnChanges, ["foo"]);
}
return {
urn: makeUrn(t, name),
id: name,
props: {},
};
},
},
};
for (const casename of Object.keys(cases)) {
@ -1224,6 +1248,7 @@ describe("rpc", () => {
const parent: string = req.getParent();
const provider: string = req.getProvider();
const ignoreChanges: string[] = req.getIgnorechangesList().sort();
const replaceOnChanges: string[] = req.getReplaceonchangesList().sort();
const propertyDeps: any = Array.from(req.getPropertydependenciesMap().entries())
.reduce((o: any, [key, value]: any) => {
return { ...o, [key]: value.getUrnsList().sort() };
@ -1231,7 +1256,7 @@ describe("rpc", () => {
const version: string = req.getVersion();
const importID: string = req.getImportid();
const { urn, id, props } = opts.registerResource(ctx, dryrun, t, name, res, deps,
custom, protect, parent, provider, propertyDeps, ignoreChanges, version, importID);
custom, protect, parent, provider, propertyDeps, ignoreChanges, version, importID, replaceOnChanges);
resp.setUrn(urn);
resp.setId(id);
resp.setObject(gstruct.Struct.fromJavaScript(props));

View file

@ -306,6 +306,7 @@ type RegisterResourceRequest struct {
Remote bool `protobuf:"varint,20,opt,name=remote,proto3" json:"remote,omitempty"`
AcceptResources bool `protobuf:"varint,21,opt,name=acceptResources,proto3" json:"acceptResources,omitempty"`
Providers map[string]string `protobuf:"bytes,22,rep,name=providers,proto3" json:"providers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
ReplaceOnChanges []string `protobuf:"bytes,23,rep,name=replaceOnChanges,proto3" json:"replaceOnChanges,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -490,6 +491,13 @@ func (m *RegisterResourceRequest) GetProviders() map[string]string {
return nil
}
func (m *RegisterResourceRequest) GetReplaceOnChanges() []string {
if m != nil {
return m.ReplaceOnChanges
}
return nil
}
// PropertyDependencies describes the resources that a particular property depends on.
type RegisterResourceRequest_PropertyDependencies struct {
Urns []string `protobuf:"bytes,1,rep,name=urns,proto3" json:"urns,omitempty"`
@ -784,70 +792,71 @@ func init() {
func init() { proto.RegisterFile("resource.proto", fileDescriptor_d1b72f771c35e3b8) }
var fileDescriptor_d1b72f771c35e3b8 = []byte{
// 995 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x57, 0x4f, 0x6f, 0x1b, 0x45,
0x14, 0x8f, 0xed, 0xd4, 0xb1, 0x5f, 0x52, 0x27, 0x4c, 0x52, 0x67, 0xba, 0xa0, 0x10, 0x16, 0x0e,
0x86, 0x83, 0xd3, 0x04, 0xa4, 0xa6, 0xa8, 0x80, 0x44, 0x5a, 0x50, 0x0f, 0xa5, 0x65, 0x83, 0x10,
0x20, 0x81, 0x34, 0xd9, 0x7d, 0x49, 0x97, 0xac, 0x77, 0xb6, 0x33, 0xb3, 0x91, 0x7c, 0x83, 0xef,
0xc1, 0x85, 0xaf, 0x82, 0xf8, 0x60, 0x68, 0x66, 0x76, 0xdc, 0x5d, 0xef, 0x3a, 0x71, 0xca, 0x6d,
0xdf, 0x7f, 0xcf, 0xef, 0xfd, 0xde, 0x9b, 0x31, 0x0c, 0x04, 0x4a, 0x9e, 0x8b, 0x10, 0xc7, 0x99,
0xe0, 0x8a, 0x93, 0x7e, 0x96, 0x27, 0xf9, 0x24, 0x16, 0x59, 0xe8, 0xbd, 0x7b, 0xc1, 0xf9, 0x45,
0x82, 0x07, 0xc6, 0x70, 0x96, 0x9f, 0x1f, 0xe0, 0x24, 0x53, 0x53, 0xeb, 0xe7, 0xbd, 0x37, 0x6f,
0x94, 0x4a, 0xe4, 0xa1, 0x2a, 0xac, 0x83, 0x4c, 0xf0, 0xab, 0x38, 0x42, 0x61, 0x65, 0x7f, 0x04,
0xc3, 0xd3, 0x3c, 0xcb, 0xb8, 0x50, 0xf2, 0x1b, 0x64, 0x2a, 0x17, 0x18, 0xe0, 0xeb, 0x1c, 0xa5,
0x22, 0x03, 0x68, 0xc7, 0x11, 0x6d, 0xed, 0xb7, 0x46, 0xfd, 0xa0, 0x1d, 0x47, 0xfe, 0x23, 0xd8,
0xad, 0x79, 0xca, 0x8c, 0xa7, 0x12, 0xc9, 0x1e, 0xc0, 0x2b, 0x26, 0x0b, 0xab, 0x09, 0xe9, 0x05,
0x25, 0x8d, 0xff, 0x57, 0x07, 0xb6, 0x03, 0x64, 0x51, 0x50, 0x9c, 0x68, 0x41, 0x09, 0x42, 0x60,
0x55, 0x4d, 0x33, 0xa4, 0x6d, 0xa3, 0x31, 0xdf, 0x5a, 0x97, 0xb2, 0x09, 0xd2, 0x8e, 0xd5, 0xe9,
0x6f, 0x32, 0x84, 0x6e, 0xc6, 0x04, 0xa6, 0x8a, 0xae, 0x1a, 0x6d, 0x21, 0x91, 0x87, 0x00, 0x99,
0xe0, 0x19, 0x0a, 0x15, 0xa3, 0xa4, 0x77, 0xf6, 0x5b, 0xa3, 0xf5, 0xa3, 0xdd, 0xb1, 0xc5, 0x63,
0xec, 0xf0, 0x18, 0x9f, 0x1a, 0x3c, 0x82, 0x92, 0x2b, 0xf1, 0x61, 0x23, 0xc2, 0x0c, 0xd3, 0x08,
0xd3, 0x50, 0x87, 0x76, 0xf7, 0x3b, 0xa3, 0x7e, 0x50, 0xd1, 0x11, 0x0f, 0x7a, 0x0e, 0x3b, 0xba,
0x66, 0xca, 0xce, 0x64, 0x42, 0x61, 0xed, 0x0a, 0x85, 0x8c, 0x79, 0x4a, 0x7b, 0xc6, 0xe4, 0x44,
0xf2, 0x11, 0xdc, 0x65, 0x61, 0x88, 0x99, 0x3a, 0xc5, 0x50, 0xa0, 0x92, 0xb4, 0x6f, 0xd0, 0xa9,
0x2a, 0xc9, 0x31, 0xec, 0xb2, 0x28, 0x8a, 0x55, 0xcc, 0x53, 0x96, 0x58, 0xe5, 0x8b, 0x5c, 0x65,
0xb9, 0x92, 0x14, 0xcc, 0x4f, 0x59, 0x64, 0xd6, 0x95, 0x59, 0x12, 0x33, 0x89, 0x92, 0xae, 0x1b,
0x4f, 0x27, 0x92, 0x11, 0x6c, 0xda, 0x22, 0x0e, 0x75, 0x49, 0x37, 0x4c, 0xed, 0x79, 0xb5, 0xcf,
0x60, 0xa7, 0xda, 0x9d, 0xa2, 0xad, 0x5b, 0xd0, 0xc9, 0x45, 0x5a, 0xf4, 0x47, 0x7f, 0xce, 0x01,
0xdc, 0x5e, 0x1a, 0x60, 0xff, 0x6f, 0x80, 0xdd, 0x00, 0x2f, 0x62, 0xa9, 0x50, 0xcc, 0xb3, 0xc0,
0x75, 0xbd, 0xd5, 0xd0, 0xf5, 0x76, 0x63, 0xd7, 0x3b, 0x95, 0xae, 0x0f, 0xa1, 0x1b, 0xe6, 0x52,
0xf1, 0x89, 0x61, 0x43, 0x2f, 0x28, 0x24, 0x72, 0x00, 0x5d, 0x7e, 0xf6, 0x3b, 0x86, 0xea, 0x26,
0x26, 0x14, 0x6e, 0x1a, 0x4b, 0x6d, 0xd2, 0x11, 0x5d, 0x93, 0xc9, 0x89, 0x35, 0x7e, 0xac, 0xdd,
0xc0, 0x8f, 0xde, 0x1c, 0x3f, 0x32, 0xd8, 0x29, 0xc0, 0x98, 0x3e, 0x29, 0xe7, 0xe9, 0xef, 0x77,
0x46, 0xeb, 0x47, 0x8f, 0xc7, 0xb3, 0xd1, 0x1e, 0x2f, 0x00, 0x69, 0xfc, 0xb2, 0x21, 0xfc, 0x69,
0xaa, 0xc4, 0x34, 0x68, 0xcc, 0x4c, 0x1e, 0xc0, 0x76, 0x84, 0x09, 0x2a, 0xfc, 0x1a, 0xcf, 0xb9,
0x1e, 0xd5, 0x2c, 0x61, 0x21, 0x52, 0x30, 0xe7, 0x6a, 0x32, 0x95, 0x39, 0xbc, 0x5e, 0xe3, 0x70,
0x7c, 0x91, 0x72, 0x81, 0x27, 0xaf, 0x58, 0x7a, 0x61, 0x78, 0xa4, 0x8f, 0x5f, 0x55, 0xd6, 0x99,
0x7e, 0xf7, 0x96, 0x4c, 0x1f, 0x2c, 0xcd, 0xf4, 0xcd, 0x2a, 0xd3, 0x3d, 0xe8, 0xc5, 0x13, 0xbd,
0x68, 0x9e, 0x45, 0x74, 0xcb, 0x22, 0xef, 0x64, 0xf2, 0x33, 0x0c, 0x2c, 0x1d, 0x7e, 0x88, 0x27,
0xc8, 0x75, 0x99, 0x77, 0x0c, 0x19, 0x0e, 0x97, 0xc0, 0xfc, 0xa4, 0x12, 0x18, 0xcc, 0x25, 0x22,
0x5f, 0x82, 0xd7, 0x80, 0xe3, 0x13, 0x3c, 0x8f, 0x53, 0x8c, 0x28, 0x31, 0xa7, 0xbf, 0xc6, 0x83,
0x7c, 0x06, 0xf7, 0x64, 0xb1, 0x50, 0x5f, 0x32, 0xa1, 0x62, 0x96, 0xfc, 0xc8, 0x92, 0x1c, 0x25,
0xdd, 0x36, 0xa1, 0xcd, 0x46, 0xcd, 0x76, 0x81, 0x13, 0xae, 0x90, 0xee, 0x58, 0xb6, 0x5b, 0xa9,
0x69, 0xdc, 0xef, 0x35, 0x8e, 0x3b, 0x79, 0x01, 0x7d, 0x47, 0x4c, 0x49, 0x87, 0x86, 0x81, 0x87,
0xcb, 0x31, 0xd0, 0xc6, 0x58, 0xda, 0xbd, 0xc9, 0xe1, 0x7d, 0x02, 0x3b, 0x4d, 0xf4, 0xd4, 0x43,
0x9c, 0x8b, 0x54, 0xd2, 0x96, 0x69, 0x97, 0xf9, 0xf6, 0x7e, 0x82, 0x41, 0x15, 0x56, 0x33, 0xbe,
0x02, 0x99, 0x72, 0x0b, 0xa0, 0x90, 0xb4, 0x3e, 0xcf, 0x22, 0xad, 0xb7, 0x4b, 0xa0, 0x90, 0xb4,
0xde, 0x82, 0xea, 0xd6, 0x80, 0x95, 0xbc, 0x3f, 0x5a, 0x70, 0x7f, 0xe1, 0x94, 0xe8, 0x5d, 0x76,
0x89, 0x53, 0xb7, 0xcb, 0x2e, 0x71, 0x4a, 0x9e, 0xc3, 0x9d, 0x2b, 0x0d, 0x69, 0xb1, 0xc6, 0x1e,
0xbe, 0xe5, 0x10, 0x06, 0x36, 0xcb, 0xe7, 0xed, 0xe3, 0x96, 0xf7, 0x18, 0x06, 0x55, 0x94, 0x1a,
0xca, 0xee, 0x94, 0xcb, 0xf6, 0x4b, 0xd1, 0xfe, 0x3f, 0x1d, 0xa0, 0xf5, 0xca, 0x0b, 0x77, 0xb1,
0xbd, 0x3c, 0xdb, 0xb3, 0xcb, 0xf3, 0xcd, 0xba, 0xeb, 0x2c, 0xb7, 0xee, 0x86, 0xd0, 0x95, 0x8a,
0x9d, 0x25, 0xe8, 0xf6, 0xa6, 0x95, 0xf4, 0xa0, 0xd9, 0x2f, 0x7d, 0x85, 0x9a, 0x41, 0x2b, 0x44,
0xf2, 0x7a, 0xc1, 0x1a, 0xeb, 0x1a, 0x12, 0x7d, 0x71, 0x2d, 0x82, 0xf6, 0x1c, 0xb7, 0xdd, 0x63,
0xb7, 0xe2, 0xd6, 0x9f, 0xb7, 0x64, 0xc0, 0x77, 0x55, 0x06, 0x1c, 0xbf, 0xed, 0xef, 0x2f, 0x37,
0x11, 0x61, 0x6f, 0x3e, 0xb6, 0x58, 0x60, 0xee, 0xba, 0xab, 0x77, 0xf2, 0x10, 0xd6, 0x78, 0xb1,
0x03, 0x6f, 0xb8, 0x52, 0x9d, 0xdf, 0xd1, 0xbf, 0xab, 0xb0, 0xe9, 0xf2, 0x3f, 0xe7, 0x69, 0xac,
0xb8, 0x20, 0xbf, 0xc0, 0xe6, 0xdc, 0x03, 0x8d, 0x7c, 0x50, 0x3a, 0x52, 0xf3, 0x33, 0xcf, 0xf3,
0xaf, 0x73, 0xb1, 0x87, 0xf6, 0x57, 0xc8, 0x57, 0xd0, 0x7d, 0x96, 0x5e, 0xf1, 0x4b, 0x24, 0xb4,
0xe4, 0x6f, 0x55, 0x2e, 0xd3, 0xfd, 0x06, 0xcb, 0x2c, 0xc1, 0xb7, 0xb0, 0x71, 0xaa, 0x04, 0xb2,
0xc9, 0xff, 0x4a, 0xf3, 0xa0, 0x45, 0x1e, 0xc1, 0xea, 0x09, 0x4b, 0x12, 0x32, 0x2c, 0xb9, 0x69,
0x85, 0x0b, 0xdf, 0xad, 0xe9, 0x67, 0xbf, 0xe1, 0x7b, 0xd8, 0x28, 0xbf, 0x73, 0xc8, 0x5e, 0xa5,
0xe1, 0xb5, 0xe7, 0xa9, 0xf7, 0xfe, 0x42, 0xfb, 0x2c, 0xe5, 0xaf, 0xb0, 0x35, 0xdf, 0x6e, 0xe2,
0xdf, 0xbc, 0x49, 0xbc, 0x0f, 0x97, 0xe0, 0x9a, 0xbf, 0x42, 0x7e, 0xab, 0xbf, 0x9a, 0xdc, 0x75,
0xf8, 0xf1, 0x35, 0x19, 0xaa, 0x8c, 0xf3, 0x86, 0x35, 0x3a, 0x3d, 0xd5, 0xff, 0x17, 0xfc, 0x95,
0xb3, 0xae, 0xd1, 0x7c, 0xfa, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x1e, 0x91, 0x5b, 0x6c,
0x0c, 0x00, 0x00,
// 1010 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x57, 0x5f, 0x6f, 0x1b, 0x45,
0x10, 0x8f, 0xed, 0xd4, 0xb1, 0x27, 0xa9, 0x13, 0x36, 0xa9, 0xbd, 0x3d, 0x50, 0x08, 0x07, 0x0f,
0xa6, 0x0f, 0x4e, 0x13, 0x90, 0x9a, 0xa2, 0x02, 0x12, 0x69, 0x41, 0x7d, 0x28, 0x29, 0x17, 0x84,
0x00, 0x09, 0xa4, 0x8d, 0x6f, 0x92, 0x1e, 0x39, 0xdf, 0x5e, 0x77, 0xf7, 0x22, 0xf9, 0x0d, 0xbe,
0x07, 0x9f, 0x06, 0xf1, 0xca, 0x77, 0x42, 0xbb, 0x7b, 0xeb, 0xde, 0xf9, 0xce, 0x89, 0xd3, 0xbe,
0xdd, 0xfc, 0xe6, 0x9f, 0x77, 0xe6, 0x37, 0xb3, 0x6b, 0xe8, 0x09, 0x94, 0x3c, 0x13, 0x63, 0x1c,
0xa5, 0x82, 0x2b, 0x4e, 0xba, 0x69, 0x16, 0x67, 0x93, 0x48, 0xa4, 0x63, 0xef, 0xfd, 0x0b, 0xce,
0x2f, 0x62, 0xdc, 0x37, 0x8a, 0xb3, 0xec, 0x7c, 0x1f, 0x27, 0xa9, 0x9a, 0x5a, 0x3b, 0xef, 0x83,
0x79, 0xa5, 0x54, 0x22, 0x1b, 0xab, 0x5c, 0xdb, 0x4b, 0x05, 0xbf, 0x8a, 0x42, 0x14, 0x56, 0xf6,
0x87, 0xd0, 0x3f, 0xcd, 0xd2, 0x94, 0x0b, 0x25, 0xbf, 0x45, 0xa6, 0x32, 0x81, 0x01, 0xbe, 0xce,
0x50, 0x2a, 0xd2, 0x83, 0x66, 0x14, 0xd2, 0xc6, 0x5e, 0x63, 0xd8, 0x0d, 0x9a, 0x51, 0xe8, 0x3f,
0x86, 0x41, 0xc5, 0x52, 0xa6, 0x3c, 0x91, 0x48, 0x76, 0x01, 0x5e, 0x31, 0x99, 0x6b, 0x8d, 0x4b,
0x27, 0x28, 0x20, 0xfe, 0xdf, 0x2d, 0xd8, 0x0e, 0x90, 0x85, 0x41, 0x7e, 0xa2, 0x05, 0x29, 0x08,
0x81, 0x55, 0x35, 0x4d, 0x91, 0x36, 0x0d, 0x62, 0xbe, 0x35, 0x96, 0xb0, 0x09, 0xd2, 0x96, 0xc5,
0xf4, 0x37, 0xe9, 0x43, 0x3b, 0x65, 0x02, 0x13, 0x45, 0x57, 0x0d, 0x9a, 0x4b, 0xe4, 0x11, 0x40,
0x2a, 0x78, 0x8a, 0x42, 0x45, 0x28, 0xe9, 0x9d, 0xbd, 0xc6, 0x70, 0xfd, 0x70, 0x30, 0xb2, 0xf5,
0x18, 0xb9, 0x7a, 0x8c, 0x4e, 0x4d, 0x3d, 0x82, 0x82, 0x29, 0xf1, 0x61, 0x23, 0xc4, 0x14, 0x93,
0x10, 0x93, 0xb1, 0x76, 0x6d, 0xef, 0xb5, 0x86, 0xdd, 0xa0, 0x84, 0x11, 0x0f, 0x3a, 0xae, 0x76,
0x74, 0xcd, 0xa4, 0x9d, 0xc9, 0x84, 0xc2, 0xda, 0x15, 0x0a, 0x19, 0xf1, 0x84, 0x76, 0x8c, 0xca,
0x89, 0xe4, 0x13, 0xb8, 0xcb, 0xc6, 0x63, 0x4c, 0xd5, 0x29, 0x8e, 0x05, 0x2a, 0x49, 0xbb, 0xa6,
0x3a, 0x65, 0x90, 0x1c, 0xc1, 0x80, 0x85, 0x61, 0xa4, 0x22, 0x9e, 0xb0, 0xd8, 0x82, 0x27, 0x99,
0x4a, 0x33, 0x25, 0x29, 0x98, 0x9f, 0xb2, 0x48, 0xad, 0x33, 0xb3, 0x38, 0x62, 0x12, 0x25, 0x5d,
0x37, 0x96, 0x4e, 0x24, 0x43, 0xd8, 0xb4, 0x49, 0x5c, 0xd5, 0x25, 0xdd, 0x30, 0xb9, 0xe7, 0x61,
0x9f, 0xc1, 0x4e, 0xb9, 0x3b, 0x79, 0x5b, 0xb7, 0xa0, 0x95, 0x89, 0x24, 0xef, 0x8f, 0xfe, 0x9c,
0x2b, 0x70, 0x73, 0xe9, 0x02, 0xfb, 0xff, 0x01, 0x0c, 0x02, 0xbc, 0x88, 0xa4, 0x42, 0x31, 0xcf,
0x02, 0xd7, 0xf5, 0x46, 0x4d, 0xd7, 0x9b, 0xb5, 0x5d, 0x6f, 0x95, 0xba, 0xde, 0x87, 0xf6, 0x38,
0x93, 0x8a, 0x4f, 0x0c, 0x1b, 0x3a, 0x41, 0x2e, 0x91, 0x7d, 0x68, 0xf3, 0xb3, 0x3f, 0x70, 0xac,
0x6e, 0x62, 0x42, 0x6e, 0xa6, 0x6b, 0xa9, 0x55, 0xda, 0xa3, 0x6d, 0x22, 0x39, 0xb1, 0xc2, 0x8f,
0xb5, 0x1b, 0xf8, 0xd1, 0x99, 0xe3, 0x47, 0x0a, 0x3b, 0x79, 0x31, 0xa6, 0x4f, 0x8b, 0x71, 0xba,
0x7b, 0xad, 0xe1, 0xfa, 0xe1, 0x93, 0xd1, 0x6c, 0xb4, 0x47, 0x0b, 0x8a, 0x34, 0x7a, 0x59, 0xe3,
0xfe, 0x2c, 0x51, 0x62, 0x1a, 0xd4, 0x46, 0x26, 0x0f, 0x61, 0x3b, 0xc4, 0x18, 0x15, 0x7e, 0x83,
0xe7, 0x5c, 0x8f, 0x6a, 0x1a, 0xb3, 0x31, 0x52, 0x30, 0xe7, 0xaa, 0x53, 0x15, 0x39, 0xbc, 0x5e,
0xe1, 0x70, 0x74, 0x91, 0x70, 0x81, 0xc7, 0xaf, 0x58, 0x72, 0x61, 0x78, 0xa4, 0x8f, 0x5f, 0x06,
0xab, 0x4c, 0xbf, 0x7b, 0x4b, 0xa6, 0xf7, 0x96, 0x66, 0xfa, 0x66, 0x99, 0xe9, 0x1e, 0x74, 0xa2,
0x89, 0x5e, 0x34, 0xcf, 0x43, 0xba, 0x65, 0x2b, 0xef, 0x64, 0xf2, 0x0b, 0xf4, 0x2c, 0x1d, 0x7e,
0x8c, 0x26, 0xc8, 0x75, 0x9a, 0xf7, 0x0c, 0x19, 0x0e, 0x96, 0xa8, 0xf9, 0x71, 0xc9, 0x31, 0x98,
0x0b, 0x44, 0xbe, 0x02, 0xaf, 0xa6, 0x8e, 0x4f, 0xf1, 0x3c, 0x4a, 0x30, 0xa4, 0xc4, 0x9c, 0xfe,
0x1a, 0x0b, 0xf2, 0x39, 0xdc, 0x93, 0xf9, 0x42, 0x7d, 0xc9, 0x84, 0x8a, 0x58, 0xfc, 0x13, 0x8b,
0x33, 0x94, 0x74, 0xdb, 0xb8, 0xd6, 0x2b, 0x35, 0xdb, 0x05, 0x4e, 0xb8, 0x42, 0xba, 0x63, 0xd9,
0x6e, 0xa5, 0xba, 0x71, 0xbf, 0x57, 0x3b, 0xee, 0xe4, 0x04, 0xba, 0x8e, 0x98, 0x92, 0xf6, 0x0d,
0x03, 0x0f, 0x96, 0x63, 0xa0, 0xf5, 0xb1, 0xb4, 0x7b, 0x13, 0x83, 0x3c, 0x80, 0x2d, 0x61, 0x8f,
0x76, 0x92, 0x38, 0x8a, 0x0c, 0x4c, 0x8b, 0x2a, 0xb8, 0xf7, 0x00, 0x76, 0xea, 0xa8, 0xac, 0x07,
0x3e, 0x13, 0x89, 0xa4, 0x0d, 0xe3, 0x67, 0xbe, 0xbd, 0x9f, 0xa1, 0x57, 0x6e, 0x81, 0x19, 0x75,
0x81, 0x4c, 0xb9, 0x65, 0x91, 0x4b, 0x1a, 0xcf, 0xd2, 0x50, 0xe3, 0x76, 0x61, 0xe4, 0x92, 0xc6,
0x6d, 0x03, 0xdc, 0xca, 0xb0, 0x92, 0xf7, 0x67, 0x03, 0xee, 0x2f, 0x9c, 0x28, 0xbd, 0xf7, 0x2e,
0x71, 0xea, 0xf6, 0xde, 0x25, 0x4e, 0xc9, 0x0b, 0xb8, 0x73, 0xa5, 0xcb, 0x9f, 0xaf, 0xbc, 0x47,
0x6f, 0x39, 0xb0, 0x81, 0x8d, 0xf2, 0x45, 0xf3, 0xa8, 0xe1, 0x3d, 0x81, 0x5e, 0xb9, 0xa2, 0x35,
0x69, 0x77, 0x8a, 0x69, 0xbb, 0x05, 0x6f, 0xff, 0x9f, 0x16, 0xd0, 0x6a, 0xe6, 0x85, 0x7b, 0xdb,
0x5e, 0xb4, 0xcd, 0xd9, 0x45, 0xfb, 0x66, 0x35, 0xb6, 0x96, 0x5b, 0x8d, 0x7d, 0x68, 0x4b, 0xc5,
0xce, 0x62, 0x74, 0x3b, 0xd6, 0x4a, 0x7a, 0x28, 0xed, 0x97, 0xbe, 0x6e, 0xcd, 0x50, 0xe6, 0x22,
0x79, 0xbd, 0x60, 0xe5, 0xb5, 0x0d, 0xe1, 0xbe, 0xbc, 0xb6, 0x82, 0xf6, 0x1c, 0xb7, 0xdd, 0x79,
0xb7, 0xe2, 0xd6, 0x5f, 0xb7, 0x64, 0xc0, 0xf7, 0x65, 0x06, 0x1c, 0xbd, 0xed, 0xef, 0x2f, 0x36,
0x11, 0x61, 0x77, 0xde, 0x37, 0x5f, 0x76, 0xee, 0x6a, 0xac, 0x76, 0xf2, 0x00, 0xd6, 0x78, 0xbe,
0x2f, 0x6f, 0xb8, 0x7e, 0x9d, 0xdd, 0xe1, 0xbf, 0xab, 0xb0, 0xe9, 0xe2, 0xbf, 0xe0, 0x49, 0xa4,
0xb8, 0x20, 0xbf, 0xc2, 0xe6, 0xdc, 0x63, 0x8e, 0x7c, 0x54, 0x38, 0x52, 0xfd, 0x93, 0xd0, 0xf3,
0xaf, 0x33, 0xb1, 0x87, 0xf6, 0x57, 0xc8, 0xd7, 0xd0, 0x7e, 0x9e, 0x5c, 0xf1, 0x4b, 0x24, 0xb4,
0x60, 0x6f, 0x21, 0x17, 0xe9, 0x7e, 0x8d, 0x66, 0x16, 0xe0, 0x3b, 0xd8, 0x38, 0x55, 0x02, 0xd9,
0xe4, 0x9d, 0xc2, 0x3c, 0x6c, 0x90, 0xc7, 0xb0, 0x7a, 0xcc, 0xe2, 0x98, 0xf4, 0x0b, 0x66, 0x1a,
0x70, 0xee, 0x83, 0x0a, 0x3e, 0xfb, 0x0d, 0x3f, 0xc0, 0x46, 0xf1, 0x4d, 0x44, 0x76, 0x4b, 0x0d,
0xaf, 0x3c, 0x65, 0xbd, 0x0f, 0x17, 0xea, 0x67, 0x21, 0x7f, 0x83, 0xad, 0xf9, 0x76, 0x13, 0xff,
0xe6, 0x4d, 0xe2, 0x7d, 0xbc, 0x04, 0xd7, 0xfc, 0x15, 0xf2, 0x7b, 0xf5, 0x85, 0xe5, 0xae, 0xce,
0x4f, 0xaf, 0x89, 0x50, 0x66, 0x9c, 0xd7, 0xaf, 0xd0, 0xe9, 0x99, 0xfe, 0x6f, 0xe1, 0xaf, 0x9c,
0xb5, 0x0d, 0xf2, 0xd9, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0xf1, 0x1f, 0xaf, 0x2d, 0x98, 0x0c,
0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.

View file

@ -104,6 +104,7 @@ message RegisterResourceRequest {
bool remote = 20; // true if the resource is a plugin-managed component resource.
bool acceptResources = 21; // when true operations should return resource references as strongly typed.
map<string, string> providers = 22; // an optional reference to the provider map to manage this resource's CRUD operations.
repeated string replaceOnChanges = 23; // a list of properties that if changed should force a replacement.
}
// RegisterResourceResponse is returned by the engine after a resource has finished being initialized. It includes the

View file

@ -381,6 +381,13 @@ class ResourceOptions:
The URN of a previously-registered resource of this type to read from the engine.
"""
replace_on_changes: Optional[List[str]]
"""
Changes to any of these property paths will force a replacement. If this list includes `"*"`, changes
to any properties will force a replacement. Initialization errors from previous deployments will
require replacement instead of update only if `"*"` is passed.
"""
# pylint: disable=redefined-builtin
def __init__(self,
parent: Optional['Resource'] = None,
@ -397,7 +404,8 @@ class ResourceOptions:
import_: Optional[str] = None,
custom_timeouts: Optional['CustomTimeouts'] = None,
transformations: Optional[List[ResourceTransformation]] = None,
urn: Optional[str] = None) -> None:
urn: Optional[str] = None,
replace_on_changes: Optional[List[str]] = None) -> None:
"""
:param Optional[Resource] parent: If provided, the currently-constructing resource should be the child of
the provided parent resource.
@ -430,6 +438,9 @@ class ResourceOptions:
:param Optional[List[ResourceTransformation]] transformations: If provided, a list of transformations to apply
to this resource during construction.
:param Optional[str] urn: The URN of a previously-registered resource of this type to read from the engine.
:param Optional[List[str]] replace_on_changes: Changes to any of these property paths will force a replacement.
If this list includes `"*"`, changes to any properties will force a replacement. Initialization errors
from previous deployments will require replacement instead of update only if `"*"` is passed.
"""
# Expose 'merge' again this this object, but this time as an instance method.
@ -452,6 +463,7 @@ class ResourceOptions:
self.import_ = import_
self.transformations = transformations
self.urn = urn
self.replace_on_changes = replace_on_changes
if depends_on is not None:
for dep in depends_on:
@ -511,6 +523,7 @@ class ResourceOptions:
dest.providers = _merge_lists(dest.providers, source.providers)
dest.depends_on = _merge_lists(dest.depends_on, source.depends_on)
dest.ignore_changes = _merge_lists(dest.ignore_changes, source.ignore_changes)
dest.replace_on_changes = _merge_lists(dest.replace_on_changes, source.replace_on_changes)
dest.aliases = _merge_lists(dest.aliases, source.aliases)
dest.additional_secret_outputs = _merge_lists(dest.additional_secret_outputs, source.additional_secret_outputs)
dest.transformations = _merge_lists(dest.transformations, source.transformations)

View file

@ -21,7 +21,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\"$\n\x16SupportsFeatureRequest\x12\n\n\x02id\x18\x01 \x01(\t\"-\n\x17SupportsFeatureResponse\x12\x12\n\nhasSupport\x18\x01 \x01(\x08\"\x95\x02\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\x12\x0f\n\x07version\x18\x08 \x01(\t\x12\x15\n\racceptSecrets\x18\t \x01(\x08\x12\x1f\n\x17\x61\x64\x64itionalSecretOutputs\x18\n \x03(\t\x12\x0f\n\x07\x61liases\x18\x0b \x03(\t\x12\x17\n\x0f\x61\x63\x63\x65ptResources\x18\x0c \x01(\x08\"P\n\x14ReadResourceResponse\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"\xc0\x07\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\x12\x0f\n\x07version\x18\x0b \x01(\t\x12\x15\n\rignoreChanges\x18\x0c \x03(\t\x12\x15\n\racceptSecrets\x18\r \x01(\x08\x12\x1f\n\x17\x61\x64\x64itionalSecretOutputs\x18\x0e \x03(\t\x12\x0f\n\x07\x61liases\x18\x0f \x03(\t\x12\x10\n\x08importId\x18\x10 \x01(\t\x12I\n\x0e\x63ustomTimeouts\x18\x11 \x01(\x0b\x32\x31.pulumirpc.RegisterResourceRequest.CustomTimeouts\x12\"\n\x1a\x64\x65leteBeforeReplaceDefined\x18\x12 \x01(\x08\x12\x1d\n\x15supportsPartialValues\x18\x13 \x01(\x08\x12\x0e\n\x06remote\x18\x14 \x01(\x08\x12\x17\n\x0f\x61\x63\x63\x65ptResources\x18\x15 \x01(\x08\x12\x44\n\tproviders\x18\x16 \x03(\x0b\x32\x31.pulumirpc.RegisterResourceRequest.ProvidersEntry\x1a$\n\x14PropertyDependencies\x12\x0c\n\x04urns\x18\x01 \x03(\t\x1a@\n\x0e\x43ustomTimeouts\x12\x0e\n\x06\x63reate\x18\x01 \x01(\t\x12\x0e\n\x06update\x18\x02 \x01(\t\x12\x0e\n\x06\x64\x65lete\x18\x03 \x01(\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\x1a\x30\n\x0eProvidersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xf7\x02\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\x12[\n\x14propertyDependencies\x18\x06 \x03(\x0b\x32=.pulumirpc.RegisterResourceResponse.PropertyDependenciesEntry\x1a$\n\x14PropertyDependencies\x12\x0c\n\x04urns\x18\x01 \x03(\t\x1au\n\x19PropertyDependenciesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12G\n\x05value\x18\x02 \x01(\x0b\x32\x38.pulumirpc.RegisterResourceResponse.PropertyDependencies:\x02\x38\x01\"W\n\x1eRegisterResourceOutputsRequest\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12(\n\x07outputs\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct2\xc4\x04\n\x0fResourceMonitor\x12Z\n\x0fSupportsFeature\x12!.pulumirpc.SupportsFeatureRequest\x1a\".pulumirpc.SupportsFeatureResponse\"\x00\x12?\n\x06Invoke\x12\x18.pulumirpc.InvokeRequest\x1a\x19.pulumirpc.InvokeResponse\"\x00\x12G\n\x0cStreamInvoke\x12\x18.pulumirpc.InvokeRequest\x1a\x19.pulumirpc.InvokeResponse\"\x00\x30\x01\x12\x39\n\x04\x43\x61ll\x12\x16.pulumirpc.CallRequest\x1a\x17.pulumirpc.CallResponse\"\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\"$\n\x16SupportsFeatureRequest\x12\n\n\x02id\x18\x01 \x01(\t\"-\n\x17SupportsFeatureResponse\x12\x12\n\nhasSupport\x18\x01 \x01(\x08\"\x95\x02\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\x12\x0f\n\x07version\x18\x08 \x01(\t\x12\x15\n\racceptSecrets\x18\t \x01(\x08\x12\x1f\n\x17\x61\x64\x64itionalSecretOutputs\x18\n \x03(\t\x12\x0f\n\x07\x61liases\x18\x0b \x03(\t\x12\x17\n\x0f\x61\x63\x63\x65ptResources\x18\x0c \x01(\x08\"P\n\x14ReadResourceResponse\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"\xda\x07\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\x12\x0f\n\x07version\x18\x0b \x01(\t\x12\x15\n\rignoreChanges\x18\x0c \x03(\t\x12\x15\n\racceptSecrets\x18\r \x01(\x08\x12\x1f\n\x17\x61\x64\x64itionalSecretOutputs\x18\x0e \x03(\t\x12\x0f\n\x07\x61liases\x18\x0f \x03(\t\x12\x10\n\x08importId\x18\x10 \x01(\t\x12I\n\x0e\x63ustomTimeouts\x18\x11 \x01(\x0b\x32\x31.pulumirpc.RegisterResourceRequest.CustomTimeouts\x12\"\n\x1a\x64\x65leteBeforeReplaceDefined\x18\x12 \x01(\x08\x12\x1d\n\x15supportsPartialValues\x18\x13 \x01(\x08\x12\x0e\n\x06remote\x18\x14 \x01(\x08\x12\x17\n\x0f\x61\x63\x63\x65ptResources\x18\x15 \x01(\x08\x12\x44\n\tproviders\x18\x16 \x03(\x0b\x32\x31.pulumirpc.RegisterResourceRequest.ProvidersEntry\x12\x18\n\x10replaceOnChanges\x18\x17 \x03(\t\x1a$\n\x14PropertyDependencies\x12\x0c\n\x04urns\x18\x01 \x03(\t\x1a@\n\x0e\x43ustomTimeouts\x12\x0e\n\x06\x63reate\x18\x01 \x01(\t\x12\x0e\n\x06update\x18\x02 \x01(\t\x12\x0e\n\x06\x64\x65lete\x18\x03 \x01(\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\x1a\x30\n\x0eProvidersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xf7\x02\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\x12[\n\x14propertyDependencies\x18\x06 \x03(\x0b\x32=.pulumirpc.RegisterResourceResponse.PropertyDependenciesEntry\x1a$\n\x14PropertyDependencies\x12\x0c\n\x04urns\x18\x01 \x03(\t\x1au\n\x19PropertyDependenciesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12G\n\x05value\x18\x02 \x01(\x0b\x32\x38.pulumirpc.RegisterResourceResponse.PropertyDependencies:\x02\x38\x01\"W\n\x1eRegisterResourceOutputsRequest\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12(\n\x07outputs\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct2\xc4\x04\n\x0fResourceMonitor\x12Z\n\x0fSupportsFeature\x12!.pulumirpc.SupportsFeatureRequest\x1a\".pulumirpc.SupportsFeatureResponse\"\x00\x12?\n\x06Invoke\x12\x18.pulumirpc.InvokeRequest\x1a\x19.pulumirpc.InvokeResponse\"\x00\x12G\n\x0cStreamInvoke\x12\x18.pulumirpc.InvokeRequest\x1a\x19.pulumirpc.InvokeResponse\"\x00\x30\x01\x12\x39\n\x04\x43\x61ll\x12\x16.pulumirpc.CallRequest\x1a\x17.pulumirpc.CallResponse\"\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,])
@ -262,8 +262,8 @@ _REGISTERRESOURCEREQUEST_PROPERTYDEPENDENCIES = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1242,
serialized_end=1278,
serialized_start=1268,
serialized_end=1304,
)
_REGISTERRESOURCEREQUEST_CUSTOMTIMEOUTS = _descriptor.Descriptor(
@ -306,8 +306,8 @@ _REGISTERRESOURCEREQUEST_CUSTOMTIMEOUTS = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1280,
serialized_end=1344,
serialized_start=1306,
serialized_end=1370,
)
_REGISTERRESOURCEREQUEST_PROPERTYDEPENDENCIESENTRY = _descriptor.Descriptor(
@ -343,8 +343,8 @@ _REGISTERRESOURCEREQUEST_PROPERTYDEPENDENCIESENTRY = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1346,
serialized_end=1462,
serialized_start=1372,
serialized_end=1488,
)
_REGISTERRESOURCEREQUEST_PROVIDERSENTRY = _descriptor.Descriptor(
@ -380,8 +380,8 @@ _REGISTERRESOURCEREQUEST_PROVIDERSENTRY = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1464,
serialized_end=1512,
serialized_start=1490,
serialized_end=1538,
)
_REGISTERRESOURCEREQUEST = _descriptor.Descriptor(
@ -545,6 +545,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='replaceOnChanges', full_name='pulumirpc.RegisterResourceRequest.replaceOnChanges', index=22,
number=23, type=9, cpp_type=9, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR),
],
extensions=[
],
@ -558,7 +565,7 @@ _REGISTERRESOURCEREQUEST = _descriptor.Descriptor(
oneofs=[
],
serialized_start=552,
serialized_end=1512,
serialized_end=1538,
)
@ -588,8 +595,8 @@ _REGISTERRESOURCERESPONSE_PROPERTYDEPENDENCIES = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1242,
serialized_end=1278,
serialized_start=1268,
serialized_end=1304,
)
_REGISTERRESOURCERESPONSE_PROPERTYDEPENDENCIESENTRY = _descriptor.Descriptor(
@ -625,8 +632,8 @@ _REGISTERRESOURCERESPONSE_PROPERTYDEPENDENCIESENTRY = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1773,
serialized_end=1890,
serialized_start=1799,
serialized_end=1916,
)
_REGISTERRESOURCERESPONSE = _descriptor.Descriptor(
@ -690,8 +697,8 @@ _REGISTERRESOURCERESPONSE = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1515,
serialized_end=1890,
serialized_start=1541,
serialized_end=1916,
)
@ -728,8 +735,8 @@ _REGISTERRESOURCEOUTPUTSREQUEST = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1892,
serialized_end=1979,
serialized_start=1918,
serialized_end=2005,
)
_READRESOURCEREQUEST.fields_by_name['properties'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT
@ -866,8 +873,8 @@ _RESOURCEMONITOR = _descriptor.ServiceDescriptor(
file=DESCRIPTOR,
index=0,
serialized_options=None,
serialized_start=1982,
serialized_end=2562,
serialized_start=2008,
serialized_end=2588,
methods=[
_descriptor.MethodDescriptor(
name='SupportsFeature',

View file

@ -288,6 +288,19 @@ def _translate_additional_secret_outputs(res: 'Resource',
return additional_secret_outputs
def _translate_replace_on_changes(res: 'Resource',
typ: Optional[type],
replace_on_changes: Optional[List[str]]) -> Optional[List[str]]:
if replace_on_changes is not None:
if typ is not None:
# If `typ` is specified, use its type/name metadata for translation.
input_names = _types.input_type_py_to_pulumi_names(typ)
replace_on_changes = list(map(lambda k: input_names.get(k) or k, replace_on_changes))
elif res.translate_input_property is not None:
replace_on_changes = list(map(res.translate_input_property, replace_on_changes))
return replace_on_changes
def read_resource(res: 'CustomResource',
ty: str,
name: str,
@ -439,6 +452,7 @@ def register_resource(res: 'Resource',
ignore_changes = _translate_ignore_changes(res, typ, opts.ignore_changes)
additional_secret_outputs = _translate_additional_secret_outputs(res, typ, opts.additional_secret_outputs)
replace_on_changes = _translate_replace_on_changes(res, typ, opts.replace_on_changes)
# Translate the CustomTimeouts object.
custom_timeouts = None
@ -487,6 +501,7 @@ def register_resource(res: 'Resource',
aliases=resolver.aliases,
supportsPartialValues=True,
remote=remote,
replaceOnChanges=replace_on_changes,
)
from ..resource import create_urn # pylint: disable=import-outside-toplevel

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -16,26 +16,25 @@ from pulumi.asset import FileAsset, StringAsset, RemoteAsset
from ..util import LanghostTest
class AssetTest(LanghostTest):
def test_asset(self):
self.run_test(
program=path.join(self.base_path(), "asset"),
expected_resource_count=4)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual(ty, "test:index:MyResource")
if name == "file":
self.assertIsInstance(resource["asset"], FileAsset)
self.assertEqual(path.normpath(resource["asset"].path), "testfile.txt")
self.assertIsInstance(_resource["asset"], FileAsset)
self.assertEqual(path.normpath(_resource["asset"].path), "testfile.txt")
elif name == "string":
self.assertIsInstance(resource["asset"], StringAsset)
self.assertEqual(resource["asset"].text, "its a string")
self.assertIsInstance(_resource["asset"], StringAsset)
self.assertEqual(_resource["asset"].text, "its a string")
elif name == "remote":
self.assertIsInstance(resource["asset"], RemoteAsset)
self.assertEqual(resource["asset"].uri, "https://pulumi.com")
self.assertIsInstance(_resource["asset"], RemoteAsset)
self.assertEqual(_resource["asset"].uri, "https://pulumi.com")
else:
self.fail("unexpected resource name: " + name)
return {

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -30,12 +30,12 @@ class ChainedFailureTest(LanghostTest):
expected_error="Program exited with non-zero exit code: 1",
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, res, _deps,
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
if ty == "test:index:ResourceA":
self.assertEqual(name, "resourceA")
self.assertDictEqual(res, {"inprop": 777})
self.assertDictEqual(_resource, {"inprop": 777})
return {
"urn": self.make_urn(ty, name),
"id": name,
@ -45,5 +45,5 @@ class ChainedFailureTest(LanghostTest):
}
if ty == "test:index:ResourceB":
self.fail(f"we should never have gotten here! {res}")
self.fail(f"we should never have gotten here! {_resource}")
self.fail(f"unknown resource type: {ty}")

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -29,10 +29,10 @@ class ComponentResourceListOfProvidersTest(LanghostTest):
program=path.join(self.base_path(), "component_resource_list_of_providers"),
expected_resource_count=240)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, custom, protect, provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
if custom and not ty.startswith("pulumi:providers:"):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
if _custom and not ty.startswith("pulumi:providers:"):
expect_protect = False
expect_provider_name = ""
@ -59,7 +59,7 @@ class ComponentResourceListOfProvidersTest(LanghostTest):
# "provider" is a provider reference. To get the provider name (technically its ID, but this test
# uses names as IDs), get the first string before the double-colon.
provider_name = provider.split("::")[-1]
provider_name = _provider.split("::")[-1]
self.assertEqual(f"{name}.protect: {protect}", f"{name}.protect: {expect_protect}")
self.assertEqual(f"{name}.provider: {provider_name}", f"{name}.provider: {expect_provider_name}")

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -29,10 +29,10 @@ class ComponentResourceSingleProviderTest(LanghostTest):
program=path.join(self.base_path(), "component_resource_single_provider"),
expected_resource_count=240)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, custom, protect, provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
if custom and not ty.startswith("pulumi:providers:"):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
if _custom and not ty.startswith("pulumi:providers:"):
expect_protect = False
expect_provider_name = ""
@ -59,7 +59,7 @@ class ComponentResourceSingleProviderTest(LanghostTest):
# "provider" is a provider reference. To get the provider name (technically its ID, but this test
# uses names as IDs), get the first string before the double-colon.
provider_name = provider.split("::")[-1]
provider_name = _provider.split("::")[-1]
self.assertEqual(f"{name}.protect: {protect}", f"{name}.protect: {expect_protect}")
self.assertEqual(f"{name}.provider: {provider_name}", f"{name}.provider: {expect_provider_name}")

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -27,9 +27,9 @@ class ConfigTest(LanghostTest):
},
expected_resource_count=1)
def register_resource(self, ctx, dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual("test:index:MyResource", ty)
self.assertEqual("myname", name)
return {

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -19,16 +19,16 @@ class DeleteBeforeReplaceTest(LanghostTest):
"""
Tests that DBRed resources correctly pass the "DBR" boolean to the engine.
"""
def test_protect(self):
def test_delete_before_replace(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,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual("foo", name)
self.assertTrue(delete_before_replace)
self.assertTrue(_delete_before_replace)
return {
"urn": self.make_urn(ty, name)
}

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -28,13 +28,13 @@ class FirstClassProviderTest(LanghostTest):
program=path.join(self.base_path(), "first_class_provider"),
expected_resource_count=2)
def register_resource(self, _ctx, _dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
if name == "testprov":
# Provider resource.
self.assertEqual("pulumi:providers:test", ty)
self.assertEqual("", provider)
self.assertEqual("", _provider)
self.prov_urn = self.make_urn(ty, name)
self.prov_id = "testid"
return {
@ -50,10 +50,9 @@ class FirstClassProviderTest(LanghostTest):
# The provider reference is created by concatenating the URN and ID of the referenced provider.
# The language host is responsible for doing this, since the engine will parse this identifier.
self.assertEqual(f"{self.prov_urn}::{self.prov_id}", provider)
self.assertEqual(f"{self.prov_urn}::{self.prov_id}", _provider)
return {
"urn": self.make_urn(ty, name)
}
self.fail(f"unexpected resource: {name} ({ty})")
return None

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -29,7 +29,6 @@ class TestFirstClassProviderInvoke(LanghostTest):
program=path.join(self.base_path(), "first_class_provider_invoke"),
expected_resource_count=4)
def invoke(self, _ctx, token, args, provider, _version):
# MyFunction explicitly receives a provider reference.
if token == "test:index:MyFunction":
@ -52,9 +51,9 @@ class TestFirstClassProviderInvoke(LanghostTest):
"value": args["value"] + 1
}
def register_resource(self, _ctx, _dry_run, ty, name, resource, _deps,
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
if name == "testprov":
self.assertEqual("pulumi:providers:test", ty)
self.prov_urn = self.make_urn(ty, name)
@ -66,11 +65,11 @@ class TestFirstClassProviderInvoke(LanghostTest):
if name == "resourceA":
self.assertEqual("test:index:MyResource", ty)
self.assertEqual(resource["value"], 9001)
self.assertEqual(_resource["value"], 9001)
return {
"urn": self.make_urn(ty, name),
"id": name,
"object": resource,
"object": _resource,
}
if name == "resourceB":
@ -81,11 +80,11 @@ class TestFirstClassProviderInvoke(LanghostTest):
if name == "resourceC":
self.assertEqual("test:index:MyResource", ty)
self.assertEqual(resource["value"], 42)
self.assertEqual(_resource["value"], 42)
return {
"urn": self.make_urn(ty, name),
"id": name,
"object": resource,
"object": _resource,
}
self.fail(f"unexpected resource: {name}")

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -30,15 +30,15 @@ class FirstClassProviderUnknown(LanghostTest):
program=path.join(self.base_path(), "first_class_provider_unknown"),
expected_resource_count=2)
def register_resource(self, _ctx, dry_run, ty, name, resource, _deps,
_parent, _custom, _protect, provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
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
# and resources referencing this resource will need to use the unknown sentinel to do so.
self.prov_urn = self.make_urn(ty, name)
if dry_run:
if _dry_run:
return {
"urn": self.prov_urn
}
@ -51,16 +51,16 @@ class FirstClassProviderUnknown(LanghostTest):
if name == "res":
self.assertEqual("test:index:MyResource", ty)
if dry_run:
if _dry_run:
# During a preview, the ID of the pulumi:providers:test resource is unknown.
self.assertEqual(f"{self.prov_urn}::{rpc.UNKNOWN}", provider)
self.assertEqual(f"{self.prov_urn}::{rpc.UNKNOWN}", _provider)
else:
# Otherwise, it's known to be exactly the above provider's ID.
self.assertEqual(f"{self.prov_urn}::{self.prov_id}", provider)
self.assertEqual(f"{self.prov_urn}::{self.prov_id}", _provider)
return {
"urn": self.make_urn(ty, name),
"id": name,
"object": resource
"object": _resource
}
self.fail(f"unknown resource: {name} ({ty})")

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -26,12 +26,12 @@ class TestFutureFailure(LanghostTest):
self.assertEqual("test:index:MyFunction", token)
return [], {}
def register_resource(self, _ctx, _dry_run, ty, name, resource, _deps,
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual("test:index:MyResource", ty)
return {
"urn": self.make_urn(ty, name),
"id": name,
"object": resource,
"object": _resource,
}

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -25,17 +25,17 @@ class FutureInputTest(LanghostTest):
program=path.join(self.base_path(), "future_input"),
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, resource, _deps,
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual(ty, "test:index:FileResource")
self.assertEqual(name, "file")
self.assertDictEqual(resource, {
self.assertDictEqual(_resource, {
"contents": "here's a file"
})
return {
"urn": self.make_urn(ty, name),
"id": name,
"object": resource
"object": _resource
}

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -24,15 +24,15 @@ class TestIgnoreChanges(LanghostTest):
program=path.join(self.base_path(), "ignore_changes"),
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, resource, _deps,
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
# Note that here we expect to receive `ignoredProperty`, even though the user provided `ignored_property`.
self.assertListEqual(ignore_changes, ["ignoredProperty", "ignored_property_other"])
self.assertListEqual(_ignore_changes, ["ignoredProperty", "ignored_property_other"])
return {
"urn": self.make_urn(ty, name),
"id": name,
"object": resource
"object": _resource
}

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -29,10 +29,10 @@ class InheritDefaultsTest(LanghostTest):
program=path.join(self.base_path(), "inherit_defaults"),
expected_resource_count=240)
def register_resource(self, _ctx, _dry_run, ty, name, _resource,
_dependencies, _parent, custom, protect, provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
if custom and not ty.startswith("pulumi:providers:"):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
if _custom and not ty.startswith("pulumi:providers:"):
expect_protect = False
expect_provider_name = ""
@ -59,7 +59,7 @@ class InheritDefaultsTest(LanghostTest):
# "provider" is a provider reference. To get the provider name (technically its ID, but this test
# uses names as IDs), get the first string before the double-colon.
provider_name = provider.split("::")[-1]
provider_name = _provider.split("::")[-1]
self.assertEqual(f"{name}.protect: {protect}", f"{name}.protect: {expect_protect}")
self.assertEqual(f"{name}.provider: {provider_name}", f"{name}.provider: {expect_provider_name}")

View file

@ -21,9 +21,9 @@ class InheritanceTranslationTest(LanghostTest):
program=path.join(self.base_path(), "inheritance_translation"),
expected_resource_count=4)
def register_resource(self, ctx, dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual("test:index:MyResource", ty)
return {
"urn": self.make_urn(ty, name),

View file

@ -21,9 +21,9 @@ class InheritanceTypesTest(LanghostTest):
program=path.join(self.base_path(), "inheritance_types"),
expected_resource_count=1)
def register_resource(self, ctx, dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual("test:index:MyResource", ty)
return {
"urn": self.make_urn(ty, name),

View file

@ -23,9 +23,9 @@ class InputTypeMismatchTest(LanghostTest):
program=path.join(self.base_path(), "input_type_mismatch"),
expected_resource_count=2)
def register_resource(self, ctx, dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual("test:index:MyResource", ty)
policy = _resource["policy"]

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -23,13 +23,13 @@ class InvalidPropertyDependencyTest(LanghostTest):
expected_error="Program exited with non-zero exit code: 1",
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_dependencies, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual(ty, "test:index:MyResource")
if name == "resA":
self.assertListEqual(_dependencies, [])
self.assertDictEqual(_property_dependencies, {})
self.assertDictEqual(_property_deps, {})
else:
self.fail(f"unexpected resource: {name} ({ty})")

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -33,16 +33,16 @@ class TestInvoke(LanghostTest):
"value": args["value"] + 1
}
def register_resource(self, _ctx, _dry_run, ty, name, resource, _deps,
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual("test:index:MyResource", ty)
self.assertEqual(resource["value"], 42)
self.assertEqual(_resource["value"], 42)
return {
"urn": self.make_urn(ty, name),
"id": name,
"object": resource,
"object": _resource,
}

View file

@ -1,4 +1,4 @@
# Copyright 2016-2020, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -42,25 +42,24 @@ class TestInvoke(LanghostTest):
else:
self.fail(f"unexpected token {token}")
def register_resource(self, _ctx, _dry_run, ty, name, resource, _deps,
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
if name == "resourceA":
self.assertEqual({
"first_value": "hellohello",
"second_value": 43,
}, resource)
}, _resource)
elif name == "resourceB":
self.assertEqual({
"first_value": "worldworld",
"second_value": 101,
}, resource)
}, _resource)
else:
self.fail(f"unknown resource: {name}")
return {
"urn": self.make_urn(ty, name),
"id": name,
"object": resource,
"object": _resource,
}

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -20,15 +20,16 @@ from ..util import LanghostTest
long_string = "a" * 1024 * 1024 * 5
class LargeResourceTest(LanghostTest):
def test_large_resource(self):
self.run_test(
program=path.join(self.base_path(), "large_resource"),
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual(ty, "test:index:MyLargeStringResource")
self.assertEqual(name, "testResource1")

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -26,12 +26,12 @@ class TestMarshalFailure(LanghostTest):
self.assertEqual("test:index:MyFunction", token)
return [], {}
def register_resource(self, _ctx, _dry_run, ty, name, resource, _deps,
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual("test:index:MyResource", ty)
return {
"urn": self.make_urn(ty, name),
"id": name,
"object": resource,
"object": _resource,
}

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -22,16 +22,16 @@ class OneComplexResourceTest(LanghostTest):
program=path.join(self.base_path(), "one_complex_resource"),
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual(ty, "test:index:MyResource")
self.assertEqual(name, "testres")
self.assertEqual(resource["falseprop"], False)
self.assertEqual(resource["trueprop"], True)
self.assertEqual(resource["intprop"], 42)
self.assertListEqual(resource["listprop"], [1, 2, "string", False])
self.assertDictEqual(resource["mapprop"], {
self.assertEqual(_resource["falseprop"], False)
self.assertEqual(_resource["trueprop"], True)
self.assertEqual(_resource["intprop"], 42)
self.assertListEqual(_resource["listprop"], [1, 2, "string", False])
self.assertDictEqual(_resource["mapprop"], {
"foo": ["bar", "baz"]
})

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -21,9 +21,9 @@ class OneResourceTest(LanghostTest):
program=path.join(self.base_path(), "one_resource"),
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual(ty, "test:index:MyResource")
self.assertEqual(name, "testResource1")
return {

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -23,9 +23,9 @@ class OutputAllTest(LanghostTest):
program=path.join(self.base_path(), "output_all"),
expected_resource_count=4)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
number = 0
if name == "testResource1":
self.assertEqual(ty, "test:index:MyResource")
@ -37,14 +37,14 @@ class OutputAllTest(LanghostTest):
self.assertEqual(ty, "test:index:FinalResource")
# The source program uses Output.apply to merge outputs from the above two resources.
# The 5 is produced by adding 2 and 3 in the source program.
self.assertEqual(resource["number"], 5)
number = resource["number"]
self.assertEqual(_resource["number"], 5)
number = _resource["number"]
elif name == "testResource4":
self.assertEqual(ty, "test:index:FinalResource")
# The source program uses Output.apply to merge outputs from the above two resources.
# The 5 is produced by adding 2 and 3 in the source program.
self.assertEqual(resource["number"], 5)
number = resource["number"]
self.assertEqual(_resource["number"], 5)
number = _resource["number"]
return {
"urn": self.make_urn(ty, name),
"id": name,

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -21,9 +21,9 @@ class OutputNestedTest(LanghostTest):
program=path.join(self.base_path(), "output_nested"),
expected_resource_count=3)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
nested_numbers = None
if name == "testResource1":
self.assertEqual(ty, "test:index:MyResource")
@ -46,8 +46,8 @@ class OutputNestedTest(LanghostTest):
# The source program uses Output.apply to merge outputs from the above two resources.
# The 10 is produced by adding 9 and 1 in the source program, derived from nested properties of the
# testResource1 nested_numbers property.
self.assertEqual(resource["sum"], 10)
nested_numbers = resource["sum"]
self.assertEqual(_resource["sum"], 10)
nested_numbers = _resource["sum"]
return {
"urn": self.make_urn(ty, name),
"id": name,

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -24,19 +24,19 @@ class PreviewTest(LanghostTest):
program=path.join(self.base_path(), "preview"),
expected_resource_count=1)
def register_resource(self, _ctx, dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual(ty, "test:index:MyResource")
self.assertEqual(name, "foo")
if dry_run:
if _dry_run:
self.assertDictEqual({
"is_preview": True
}, resource)
}, _resource)
else:
self.assertDictEqual({
"is_preview": False
}, resource)
}, _resource)
return {
"urn": self.make_urn(ty, name),
}

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -22,33 +22,33 @@ class PropertyDependenciesTest(LanghostTest):
program=path.join(self.base_path(), "property_dependencies"),
expected_resource_count=5)
def register_resource(self, _ctx, _dry_run, ty, name, resource,
_dependencies, _parent, _custom, _protect, _provider, _property_dependencies, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual(ty, "test:index:MyResource")
if name == "resA":
self.assertListEqual(_dependencies, [])
self.assertDictEqual(_property_dependencies, {})
self.assertDictEqual(_property_deps, {})
elif name == "resB":
self.assertListEqual(_dependencies, [ "resA" ])
self.assertDictEqual(_property_dependencies, {})
self.assertDictEqual(_property_deps, {})
elif name == "resC":
self.assertListEqual(_dependencies, [ "resA", "resB" ])
self.assertDictEqual(_property_dependencies, {
self.assertDictEqual(_property_deps, {
"propA": [ "resA" ],
"propB": [ "resB" ],
"propC": [],
})
elif name == "resD":
self.assertListEqual(_dependencies, [ "resA", "resB", "resC" ])
self.assertDictEqual(_property_dependencies, {
self.assertDictEqual(_property_deps, {
"propA": [ "resA", "resB" ],
"propB": [ "resC" ],
"propC": [],
})
elif name == "resE":
self.assertListEqual(_dependencies, [ "resA", "resB", "resC", "resD" ])
self.assertDictEqual(_property_dependencies, {
self.assertDictEqual(_property_deps, {
"propA": [ "resC" ],
"propB": [ "resA", "resB" ],
"propC": [],

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -25,9 +25,9 @@ class PropertyRenamingTest(LanghostTest):
program=path.join(self.base_path(), "property_renaming"),
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, res, _deps,
_parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
# 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.
@ -35,12 +35,12 @@ class PropertyRenamingTest(LanghostTest):
# to translate them back to snake case.
self.assertEqual("test:index:TranslatedResource", ty)
self.assertEqual("res", name)
self.assertIn("engineProp", res)
self.assertEqual("some string", res["engineProp"])
self.assertIn("recursiveProp", res)
self.assertIn("engineProp", _resource)
self.assertEqual("some string", _resource["engineProp"])
self.assertIn("recursiveProp", _resource)
self.assertDictEqual({
"recursiveKey": "value"
}, res["recursiveProp"])
}, _resource["recursiveProp"])
return {
"urn": self.make_urn(ty, name),
"id": name,

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -24,9 +24,9 @@ class ProtectTest(LanghostTest):
program=path.join(self.base_path(), "protect"),
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, _resource,
_dependencies, _parent, _custom, protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual("foo", name)
self.assertTrue(protect)
return {

View file

@ -1,4 +1,4 @@
# Copyright 2016-2019, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -21,16 +21,16 @@ class ReadTest(LanghostTest):
program=path.join(self.base_path(), "read"),
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual(ty, "test:index:MyResource")
self.assertEqual(name, "foo2")
return {
"urn": self.make_urn(ty, name),
}
def read_resource(self, ctx, ty, name, id, parent, state, dependencies, provider, version):
def read_resource(self, ctx, ty, name, _id, parent, state, dependencies, provider, version):
if name == "foo":
self.assertDictEqual(state, {
"a": "bar",
@ -40,14 +40,14 @@ class ReadTest(LanghostTest):
}
})
self.assertEqual(ty, "test:read:resource")
self.assertEqual(id, "myresourceid")
self.assertEqual(_id, "myresourceid")
self.assertEqual(version, "0.17.9")
elif name == "foo-with-parent":
self.assertDictEqual(state, {
"state": "foo",
})
self.assertEqual(ty, "test:read:resource")
self.assertEqual(id, "myresourceid2")
self.assertEqual(_id, "myresourceid2")
self.assertEqual(parent, self.make_urn("test:index:MyResource", "foo2"))
self.assertEqual(version, "0.17.9")
return {

View file

@ -0,0 +1,13 @@
# Copyright 2016-2021, 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,23 @@
# Copyright 2016-2021, 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, InvokeOptions
from pulumi.runtime import invoke
class MyResource(CustomResource):
def __init__(self, name, opts=None):
CustomResource.__init__(self, "test:index:MyResource", name, opts=opts)
res = MyResource("testResource", opts=ResourceOptions(replace_on_changes=["a", "b"]))

View file

@ -0,0 +1,37 @@
# Copyright 2016-2021, 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 TestReplaceOnChanges(LanghostTest):
"""
Tests that Pulumi resources can accept replace_on_changes resource options.
"""
def test_replace_on_changes(self):
self.run_test(
program=path.join(self.base_path(), "replace_on_changes"),
expected_resource_count=1)
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
print(f'register_resource args: {locals()}')
self.assertEqual("testResource", name)
self.assertListEqual(_replace_on_changes, ["a", "b"])
return {
"urn": self.make_urn(ty, name),
}

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -21,7 +21,7 @@ class UnhandledExceptionTest(LanghostTest):
program=path.join(self.base_path(), "resource_op_bad_inputs"),
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, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
raise Exception("oh no")

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -21,7 +21,7 @@ class UnhandledExceptionTest(LanghostTest):
program=path.join(self.base_path(), "resource_op_fail"),
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, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
raise Exception("oh no")

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -29,16 +29,16 @@ class ResourceThensTest(LanghostTest):
program=path.join(self.base_path(), "resource_thens"),
expected_resource_count=2)
def register_resource(self, _ctx, dry_run, ty, name, res, deps,
_parent, custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
if ty == "test:index:ResourceA":
self.assertEqual(name, "resourceA")
self.assertDictEqual(res, {"inprop": 777, "inprop_2": 42})
self.assertDictEqual(_resource, {"inprop": 777, "inprop_2": 42})
urn = self.make_urn(ty, name)
res_id = ""
props = {}
if not dry_run:
if not _dry_run:
res_id = name
props["outprop"] = "output yeah"
@ -50,22 +50,22 @@ class ResourceThensTest(LanghostTest):
if ty == "test:index:ResourceB":
self.assertEqual(name, "resourceB")
self.assertListEqual(deps, ["test:index:ResourceA::resourceA"])
if dry_run:
self.assertDictEqual(res, {
self.assertListEqual(_dependencies, ["test:index:ResourceA::resourceA"])
if _dry_run:
self.assertDictEqual(_resource, {
# other_in is unknown, so it is not in the dictionary.
# other_out is unknown, so it is not in the dictionary.
# other_id is also unknown so it is not in the dictionary
})
else:
self.assertDictEqual(res, {
self.assertDictEqual(_resource, {
"other_in": 777,
"other_out": "output yeah",
"other_id": "resourceA",
})
res_id = ""
if not dry_run:
if not _dry_run:
res_id = name
return {

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -24,11 +24,11 @@ class TenResourcesTest(LanghostTest):
program=path.join(self.base_path(), "ten_resources"),
expected_resource_count=10)
def register_resource(self, ctx, dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect, _provider, _property_deps, _delete_before_replace,
_ignore_changes, _version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
self.assertEqual("test:index:MyResource", ty)
if not dry_run:
if not _dry_run:
self.assertIsNone(
self.seen.get(name),
"Got multiple resources with the same name: " + name)

View file

@ -1,4 +1,4 @@
# Copyright 2016-2020, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -22,9 +22,9 @@ class TestTypes(LanghostTest):
program=path.join(self.base_path(), "types"),
expected_resource_count=16)
def register_resource(self, ctx, dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
if name in ["testres", "testres2", "testres3", "testres4"]:
self.assertIn("additional", _resource)
self.assertEqual({

View file

@ -98,16 +98,17 @@ class LanghostMockResourceMonitor(proto.ResourceMonitorServicer):
ignore_changes = sorted(list(request.ignoreChanges))
version = request.version
import_ = request.importId
replace_on_changes = sorted(list(request.replaceOnChanges))
property_dependencies = {}
for key, value in request.propertyDependencies.items():
property_dependencies[key] = sorted(list(value.urns))
outs = {}
if type_ != "pulumi:pulumi:Stack":
rrsig = signature(self.langhost_test.register_resource)
args = [context, self.dryrun, type_, name, props, deps, parent, custom, protect, provider, property_dependencies, delete_before_replace, ignore_changes, version, import_]
outs = self.langhost_test.register_resource(*args[0:len(rrsig.parameters)])
outs = self.langhost_test.register_resource(
context, self.dryrun, type_, name, props, deps, parent, custom, protect, provider,
property_dependencies, delete_before_replace, ignore_changes, version, import_, replace_on_changes,
)
if outs.get("urn"):
urn = outs["urn"]
self.registrations[urn] = {
@ -246,7 +247,7 @@ class LanghostTest(unittest.TestCase):
monitor.server.stop(0)
def invoke(self, _ctx, _token, _args, _provider):
def invoke(self, _ctx, token, args, provider, _version):
"""
Method corresponding to the `Invoke` resource monitor RPC call.
Override for custom behavior or assertions.
@ -256,7 +257,7 @@ class LanghostTest(unittest.TestCase):
"""
return ([], {})
def read_resource(self, _ctx, _type, _name, _id, _parent, _state):
def read_resource(self, ctx, ty, name, _id, parent, state, dependencies, provider, version):
"""
Method corresponding to the `ReadResource` resource monitor RPC call.
Override for custom behavior or assertions.
@ -265,8 +266,9 @@ class LanghostTest(unittest.TestCase):
"""
return {}
def register_resource(self, _ctx, _dry_run, _type, _name, _resource,
_dependencies, _parent, _custom, _provider, _property_deps, _delete_before_replace, _import):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
"""
Method corresponding to the `RegisterResource` resource monitor RPC call.
Override for custom behavior or assertions.

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# Copyright 2016-2021, Pulumi Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -21,15 +21,15 @@ class TestVersions(LanghostTest):
program=path.join(self.base_path(), "versions"),
expected_resource_count=3)
def register_resource(self, ctx, dry_run, ty, name, _resource,
_dependencies, _parent, _custom, _protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, version):
def register_resource(self, _ctx, _dry_run, ty, name, _resource, _dependencies, _parent, _custom, protect,
_provider, _property_deps, _delete_before_replace, _ignore_changes, _version, _import,
_replace_on_changes):
if name == "testres":
self.assertEqual(version, "0.19.1")
self.assertEqual(_version, "0.19.1")
elif name == "testres2":
self.assertEqual(version, "0.19.2")
self.assertEqual(_version, "0.19.2")
elif name == "testres3":
self.assertEqual(version, "")
self.assertEqual(_version, "")
else:
self.fail(f"unknown resource: {name}")
return {