diff --git a/changelog/pending/20221129--engine--fix-an-assert-for-resources-being-replaced-but-also-pending-deletion.yaml b/changelog/pending/20221129--engine--fix-an-assert-for-resources-being-replaced-but-also-pending-deletion.yaml
new file mode 100644
index 000000000000..fc9b0531a32f
--- /dev/null
+++ b/changelog/pending/20221129--engine--fix-an-assert-for-resources-being-replaced-but-also-pending-deletion.yaml
@@ -0,0 +1,4 @@
+changes:
+- type: fix
+ scope: engine
+ description: Fix an assert for resources being replaced but also pending deletion.
diff --git a/changelog/pending/20221206--pkg--fixes-codegen-python-nonstring-secrets.yaml b/changelog/pending/20221206--pkg--fixes-codegen-python-nonstring-secrets.yaml
new file mode 100644
index 000000000000..2faf39de7052
--- /dev/null
+++ b/changelog/pending/20221206--pkg--fixes-codegen-python-nonstring-secrets.yaml
@@ -0,0 +1,4 @@
+changes:
+- type: fix
+ scope: pkg
+ description: Fixes codegen/python generation of non-string secrets in provider properties
diff --git a/pkg/codegen/python/gen.go b/pkg/codegen/python/gen.go
index 3dcbe76973ee..c5e0d860f826 100644
--- a/pkg/codegen/python/gen.go
+++ b/pkg/codegen/python/gen.go
@@ -1301,11 +1301,17 @@ func (mod *modContext) genResource(res *schema.Resource) (string, error) {
// If this resource is a provider then, regardless of the schema of the underlying provider
// type, we must project all properties as strings. For all properties that are not strings,
// we'll marshal them to JSON and use the JSON string as a string input.
+ handledSecret := false
if res.IsProvider && !isStringType(prop.Type) {
- arg = fmt.Sprintf("pulumi.Output.from_input(%s).apply(pulumi.runtime.to_json) if %s is not None else None", arg, arg)
+ if prop.Secret {
+ arg = fmt.Sprintf("pulumi.Output.secret(%s).apply(pulumi.runtime.to_json) if %s is not None else None", arg, arg)
+ handledSecret = true
+ } else {
+ arg = fmt.Sprintf("pulumi.Output.from_input(%s).apply(pulumi.runtime.to_json) if %s is not None else None", arg, arg)
+ }
}
name := PyName(prop.Name)
- if prop.Secret {
+ if prop.Secret && !handledSecret {
fmt.Fprintf(w, " __props__.__dict__[%[1]q] = None if %[2]s is None else pulumi.Output.secret(%[2]s)\n", name, arg)
} else {
fmt.Fprintf(w, " __props__.__dict__[%q] = %s\n", name, arg)
diff --git a/pkg/codegen/testing/test/sdk_driver.go b/pkg/codegen/testing/test/sdk_driver.go
index a105126bb887..398563c5ae96 100644
--- a/pkg/codegen/testing/test/sdk_driver.go
+++ b/pkg/codegen/testing/test/sdk_driver.go
@@ -151,9 +151,10 @@ var PulumiPulumiSDKTests = []*SDKTest{
Description: "Simple schema encoded using YAML",
},
{
- Directory: "provider-config-schema",
- Description: "Simple provider config schema",
- SkipCompileCheck: codegen.NewStringSet(dotnet),
+ Directory: "provider-config-schema",
+ Description: "Simple provider config schema",
+ // For golang skip check, see https://github.com/pulumi/pulumi/issues/11567
+ SkipCompileCheck: codegen.NewStringSet(dotnet, golang),
},
{
Directory: "replace-on-change",
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/docs/_index.md b/pkg/codegen/testing/test/testdata/provider-config-schema/docs/_index.md
index c4b5d9778cee..9b2a218610f0 100644
--- a/pkg/codegen/testing/test/testdata/provider-config-schema/docs/_index.md
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/docs/_index.md
@@ -1,6 +1,6 @@
---
title: "configstation"
-title_tag: "configstation.configstation"
+title_tag: "configstation Package"
meta_desc: ""
layout: api
no_edit_this_page: true
@@ -11,6 +11,11 @@ no_edit_this_page: true
+
Modules
+
+
Resources
- Provider
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/docs/codegen-manifest.json b/pkg/codegen/testing/test/testdata/provider-config-schema/docs/codegen-manifest.json
index baedc6ed3fc6..80106aa37f0a 100644
--- a/pkg/codegen/testing/test/testdata/provider-config-schema/docs/codegen-manifest.json
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/docs/codegen-manifest.json
@@ -1,6 +1,7 @@
{
"emittedFiles": [
"_index.md",
+ "config/_index.md",
"funcwithalloptionalinputs/_index.md",
"provider/_index.md"
]
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/docs/config/_index.md b/pkg/codegen/testing/test/testdata/provider-config-schema/docs/config/_index.md
new file mode 100644
index 000000000000..b6d90fe00ee3
--- /dev/null
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/docs/config/_index.md
@@ -0,0 +1,23 @@
+---
+title: "config"
+title_tag: "configstation.config"
+meta_desc: "Explore the resources and functions of the configstation.config module."
+layout: api
+no_edit_this_page: true
+---
+
+
+
+
+Explore the resources and functions of the configstation.config module.
+
+Package Details
+
+ - Repository
+
+ - License
+
+ - Version
+ - 0.0.1
+
+
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/docs/provider/_index.md b/pkg/codegen/testing/test/testdata/provider-config-schema/docs/provider/_index.md
index 29b28485a2f4..a8ea7c5bf53e 100644
--- a/pkg/codegen/testing/test/testdata/provider-config-schema/docs/provider/_index.md
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/docs/provider/_index.md
@@ -32,7 +32,8 @@ no_edit_this_page: true
@@ -243,6 +253,15 @@ The Provider resource accepts the following [input](/docs/intro/concepts/inputs-
string | Color
this is a relaxed string enum which can also be set via env var It can also be sourced from the following environment variable: FAVE_COLOR
+-
+
+SecretSandwiches
+
+
+ SandwichArgs
+
+ Super duper secret sandwiches.
@@ -258,6 +277,15 @@ The Provider resource accepts the following [input](/docs/intro/concepts/inputs-
String | Color
this is a relaxed string enum which can also be set via env var It can also be sourced from the following environment variable: FAVE_COLOR
+-
+
+secretSandwiches
+
+
+ List<SandwichArgs>
+
+ Super duper secret sandwiches.
@@ -273,6 +301,15 @@ The Provider resource accepts the following [input](/docs/intro/concepts/inputs-
string | Color
this is a relaxed string enum which can also be set via env var It can also be sourced from the following environment variable: FAVE_COLOR
+-
+
+secretSandwiches
+
+
+ configSandwichArgs[]
+
+ Super duper secret sandwiches.
@@ -288,6 +325,15 @@ The Provider resource accepts the following [input](/docs/intro/concepts/inputs-
str | Color
this is a relaxed string enum which can also be set via env var It can also be sourced from the following environment variable: FAVE_COLOR
+-
+
+secret_sandwiches
+
+
+ SandwichArgs]
+
+ Super duper secret sandwiches.
@@ -303,6 +349,15 @@ The Provider resource accepts the following [input](/docs/intro/concepts/inputs-
String | "blue" | "red"
this is a relaxed string enum which can also be set via env var It can also be sourced from the following environment variable: FAVE_COLOR
+-
+
+secretSandwiches
+
+
+ List<Property Map>
+
+ Super duper secret sandwiches.
@@ -464,6 +519,140 @@ All [input](#inputs) properties are implicitly available as output properties. A
+Sandwich
+
+
+
+-
+
+Bread
+
+
+ string
+
+ -
+
+Veggies
+
+
+ List<string>
+
+
+
+
+
+
+
+-
+
+Bread
+
+
+ string
+
+ -
+
+Veggies
+
+
+ []string
+
+
+
+
+
+
+
+-
+
+bread
+
+
+ String
+
+ -
+
+veggies
+
+
+ List<String>
+
+
+
+
+
+
+
+-
+
+bread
+
+
+ string
+
+ -
+
+veggies
+
+
+ string[]
+
+
+
+
+
+
+
+-
+
+bread
+
+
+ str
+
+ -
+
+veggies
+
+
+ Sequence[str]
+
+
+
+
+
+
+
+-
+
+bread
+
+
+ String
+
+ -
+
+veggies
+
+
+ List<String>
+
+
+
+
+
Package Details
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/dotnet/Configstation/Provider.cs b/pkg/codegen/testing/test/testdata/provider-config-schema/dotnet/Configstation/Provider.cs
index 6b035382daca..299087eeb568 100644
--- a/pkg/codegen/testing/test/testdata/provider-config-schema/dotnet/Configstation/Provider.cs
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/dotnet/Configstation/Provider.cs
@@ -46,6 +46,22 @@ public sealed class ProviderArgs : global::Pulumi.ResourceArgs
[Input("favoriteColor", json: true)]
public InputUnion? FavoriteColor { get; set; }
+ [Input("secretSandwiches", json: true)]
+ private InputList? _secretSandwiches;
+
+ ///
+ /// Super duper secret sandwiches.
+ ///
+ public InputList SecretSandwiches
+ {
+ get => _secretSandwiches ?? (_secretSandwiches = new InputList());
+ set
+ {
+ var emptySecret = Output.CreateSecret(ImmutableArray.Create());
+ _secretSandwiches = Output.All(value, emptySecret).Apply(v => v[0]);
+ }
+ }
+
public ProviderArgs()
{
FavoriteColor = Utilities.GetEnv("FAVE_COLOR");
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/go/configstation/config/pulumiTypes.go b/pkg/codegen/testing/test/testdata/provider-config-schema/go/configstation/config/pulumiTypes.go
index 91017be23970..70a1600a5b74 100644
--- a/pkg/codegen/testing/test/testdata/provider-config-schema/go/configstation/config/pulumiTypes.go
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/go/configstation/config/pulumiTypes.go
@@ -43,6 +43,31 @@ func (i SandwichArgs) ToSandwichOutputWithContext(ctx context.Context) SandwichO
return pulumi.ToOutputWithContext(ctx, i).(SandwichOutput)
}
+// SandwichArrayInput is an input type that accepts SandwichArray and SandwichArrayOutput values.
+// You can construct a concrete instance of `SandwichArrayInput` via:
+//
+// SandwichArray{ SandwichArgs{...} }
+type SandwichArrayInput interface {
+ pulumi.Input
+
+ ToSandwichArrayOutput() SandwichArrayOutput
+ ToSandwichArrayOutputWithContext(context.Context) SandwichArrayOutput
+}
+
+type SandwichArray []SandwichInput
+
+func (SandwichArray) ElementType() reflect.Type {
+ return reflect.TypeOf((*[]Sandwich)(nil)).Elem()
+}
+
+func (i SandwichArray) ToSandwichArrayOutput() SandwichArrayOutput {
+ return i.ToSandwichArrayOutputWithContext(context.Background())
+}
+
+func (i SandwichArray) ToSandwichArrayOutputWithContext(ctx context.Context) SandwichArrayOutput {
+ return pulumi.ToOutputWithContext(ctx, i).(SandwichArrayOutput)
+}
+
type SandwichOutput struct{ *pulumi.OutputState }
func (SandwichOutput) ElementType() reflect.Type {
@@ -65,7 +90,29 @@ func (o SandwichOutput) Veggies() pulumi.StringArrayOutput {
return o.ApplyT(func(v Sandwich) []string { return v.Veggies }).(pulumi.StringArrayOutput)
}
+type SandwichArrayOutput struct{ *pulumi.OutputState }
+
+func (SandwichArrayOutput) ElementType() reflect.Type {
+ return reflect.TypeOf((*[]Sandwich)(nil)).Elem()
+}
+
+func (o SandwichArrayOutput) ToSandwichArrayOutput() SandwichArrayOutput {
+ return o
+}
+
+func (o SandwichArrayOutput) ToSandwichArrayOutputWithContext(ctx context.Context) SandwichArrayOutput {
+ return o
+}
+
+func (o SandwichArrayOutput) Index(i pulumi.IntInput) SandwichOutput {
+ return pulumi.All(o, i).ApplyT(func(vs []interface{}) Sandwich {
+ return vs[0].([]Sandwich)[vs[1].(int)]
+ }).(SandwichOutput)
+}
+
func init() {
pulumi.RegisterInputType(reflect.TypeOf((*SandwichInput)(nil)).Elem(), SandwichArgs{})
+ pulumi.RegisterInputType(reflect.TypeOf((*SandwichArrayInput)(nil)).Elem(), SandwichArray{})
pulumi.RegisterOutputType(SandwichOutput{})
+ pulumi.RegisterOutputType(SandwichArrayOutput{})
}
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/go/configstation/provider.go b/pkg/codegen/testing/test/testdata/provider-config-schema/go/configstation/provider.go
index 546fff9507d2..31aaa998f1d6 100644
--- a/pkg/codegen/testing/test/testdata/provider-config-schema/go/configstation/provider.go
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/go/configstation/provider.go
@@ -7,6 +7,7 @@ import (
"context"
"reflect"
+ "config"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
@@ -24,6 +25,9 @@ func NewProvider(ctx *pulumi.Context,
if isZero(args.FavoriteColor) {
args.FavoriteColor = pulumi.StringPtr(getEnvOrDefault("", nil, "FAVE_COLOR").(string))
}
+ if args.SecretSandwiches != nil {
+ args.SecretSandwiches = pulumi.ToSecret(args.SecretSandwiches).(config.SandwichArrayOutput)
+ }
var resource Provider
err := ctx.RegisterResource("pulumi:providers:configstation", name, args, &resource, opts...)
if err != nil {
@@ -35,12 +39,16 @@ func NewProvider(ctx *pulumi.Context,
type providerArgs struct {
// this is a relaxed string enum which can also be set via env var
FavoriteColor *string `pulumi:"favoriteColor"`
+ // Super duper secret sandwiches.
+ SecretSandwiches []config.Sandwich `pulumi:"secretSandwiches"`
}
// The set of arguments for constructing a Provider resource.
type ProviderArgs struct {
// this is a relaxed string enum which can also be set via env var
FavoriteColor pulumi.StringPtrInput
+ // Super duper secret sandwiches.
+ SecretSandwiches config.SandwichArrayInput
}
func (ProviderArgs) ElementType() reflect.Type {
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/nodejs/provider.ts b/pkg/codegen/testing/test/testdata/provider-config-schema/nodejs/provider.ts
index 64c963a487f2..3f09cc7bd995 100644
--- a/pkg/codegen/testing/test/testdata/provider-config-schema/nodejs/provider.ts
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/nodejs/provider.ts
@@ -35,6 +35,7 @@ export class Provider extends pulumi.ProviderResource {
opts = opts || {};
{
resourceInputs["favoriteColor"] = (args ? args.favoriteColor : undefined) ?? utilities.getEnv("FAVE_COLOR");
+ resourceInputs["secretSandwiches"] = pulumi.output(args?.secretSandwiches ? pulumi.secret(args.secretSandwiches) : undefined).apply(JSON.stringify);
}
opts = pulumi.mergeOptions(utilities.resourceOptsDefaults(), opts);
super(Provider.__pulumiType, name, resourceInputs, opts);
@@ -49,4 +50,8 @@ export interface ProviderArgs {
* this is a relaxed string enum which can also be set via env var
*/
favoriteColor?: pulumi.Input;
+ /**
+ * Super duper secret sandwiches.
+ */
+ secretSandwiches?: pulumi.Input[]>;
}
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/nodejs/types/input.ts b/pkg/codegen/testing/test/testdata/provider-config-schema/nodejs/types/input.ts
index a43cbaa66807..678384267e74 100644
--- a/pkg/codegen/testing/test/testdata/provider-config-schema/nodejs/types/input.ts
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/nodejs/types/input.ts
@@ -7,4 +7,8 @@ import * as outputs from "../types/output";
import * as enums from "../types/enums";
export namespace config {
+ export interface SandwichArgs {
+ bread?: pulumi.Input;
+ veggies?: pulumi.Input[]>;
+ }
}
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/python/codegen-manifest.json b/pkg/codegen/testing/test/testdata/provider-config-schema/python/codegen-manifest.json
index 5ab532a947f3..4a914edca058 100644
--- a/pkg/codegen/testing/test/testdata/provider-config-schema/python/codegen-manifest.json
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/python/codegen-manifest.json
@@ -6,6 +6,7 @@
"pulumi_configstation/_utilities.py",
"pulumi_configstation/config/__init__.py",
"pulumi_configstation/config/__init__.pyi",
+ "pulumi_configstation/config/_inputs.py",
"pulumi_configstation/config/outputs.py",
"pulumi_configstation/config/vars.py",
"pulumi_configstation/func_with_all_optional_inputs.py",
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/python/pulumi_configstation/config/_inputs.py b/pkg/codegen/testing/test/testdata/provider-config-schema/python/pulumi_configstation/config/_inputs.py
new file mode 100644
index 000000000000..075a0cebffa0
--- /dev/null
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/python/pulumi_configstation/config/_inputs.py
@@ -0,0 +1,44 @@
+# coding=utf-8
+# *** WARNING: this file was generated by test. ***
+# *** Do not edit by hand unless you're certain you know what you are doing! ***
+
+import copy
+import warnings
+import pulumi
+import pulumi.runtime
+from typing import Any, Mapping, Optional, Sequence, Union, overload
+from .. import _utilities
+
+__all__ = [
+ 'SandwichArgs',
+]
+
+@pulumi.input_type
+class SandwichArgs:
+ def __init__(__self__, *,
+ bread: Optional[pulumi.Input[str]] = None,
+ veggies: Optional[pulumi.Input[Sequence[pulumi.Input[str]]]] = None):
+ if bread is not None:
+ pulumi.set(__self__, "bread", bread)
+ if veggies is not None:
+ pulumi.set(__self__, "veggies", veggies)
+
+ @property
+ @pulumi.getter
+ def bread(self) -> Optional[pulumi.Input[str]]:
+ return pulumi.get(self, "bread")
+
+ @bread.setter
+ def bread(self, value: Optional[pulumi.Input[str]]):
+ pulumi.set(self, "bread", value)
+
+ @property
+ @pulumi.getter
+ def veggies(self) -> Optional[pulumi.Input[Sequence[pulumi.Input[str]]]]:
+ return pulumi.get(self, "veggies")
+
+ @veggies.setter
+ def veggies(self, value: Optional[pulumi.Input[Sequence[pulumi.Input[str]]]]):
+ pulumi.set(self, "veggies", value)
+
+
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/python/pulumi_configstation/provider.py b/pkg/codegen/testing/test/testdata/provider-config-schema/python/pulumi_configstation/provider.py
index 72c5a61a8411..fe5b8c9b3b42 100644
--- a/pkg/codegen/testing/test/testdata/provider-config-schema/python/pulumi_configstation/provider.py
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/python/pulumi_configstation/provider.py
@@ -8,6 +8,7 @@
import pulumi.runtime
from typing import Any, Mapping, Optional, Sequence, Union, overload
from . import _utilities
+from . import config as _config
from ._enums import *
__all__ = ['ProviderArgs', 'Provider']
@@ -15,15 +16,19 @@
@pulumi.input_type
class ProviderArgs:
def __init__(__self__, *,
- favorite_color: Optional[pulumi.Input[Union[str, 'Color']]] = None):
+ favorite_color: Optional[pulumi.Input[Union[str, 'Color']]] = None,
+ secret_sandwiches: Optional[pulumi.Input[Sequence[pulumi.Input['_config.SandwichArgs']]]] = None):
"""
The set of arguments for constructing a Provider resource.
:param pulumi.Input[Union[str, 'Color']] favorite_color: this is a relaxed string enum which can also be set via env var
+ :param pulumi.Input[Sequence[pulumi.Input['_config.SandwichArgs']]] secret_sandwiches: Super duper secret sandwiches.
"""
if favorite_color is None:
favorite_color = _utilities.get_env('FAVE_COLOR')
if favorite_color is not None:
pulumi.set(__self__, "favorite_color", favorite_color)
+ if secret_sandwiches is not None:
+ pulumi.set(__self__, "secret_sandwiches", secret_sandwiches)
@property
@pulumi.getter(name="favoriteColor")
@@ -37,6 +42,18 @@ def favorite_color(self) -> Optional[pulumi.Input[Union[str, 'Color']]]:
def favorite_color(self, value: Optional[pulumi.Input[Union[str, 'Color']]]):
pulumi.set(self, "favorite_color", value)
+ @property
+ @pulumi.getter(name="secretSandwiches")
+ def secret_sandwiches(self) -> Optional[pulumi.Input[Sequence[pulumi.Input['_config.SandwichArgs']]]]:
+ """
+ Super duper secret sandwiches.
+ """
+ return pulumi.get(self, "secret_sandwiches")
+
+ @secret_sandwiches.setter
+ def secret_sandwiches(self, value: Optional[pulumi.Input[Sequence[pulumi.Input['_config.SandwichArgs']]]]):
+ pulumi.set(self, "secret_sandwiches", value)
+
class Provider(pulumi.ProviderResource):
@overload
@@ -44,12 +61,14 @@ def __init__(__self__,
resource_name: str,
opts: Optional[pulumi.ResourceOptions] = None,
favorite_color: Optional[pulumi.Input[Union[str, 'Color']]] = None,
+ secret_sandwiches: Optional[pulumi.Input[Sequence[pulumi.Input[pulumi.InputType['_config.SandwichArgs']]]]] = None,
__props__=None):
"""
Create a Configstation resource with the given unique name, props, and options.
:param str resource_name: The name of the resource.
:param pulumi.ResourceOptions opts: Options for the resource.
:param pulumi.Input[Union[str, 'Color']] favorite_color: this is a relaxed string enum which can also be set via env var
+ :param pulumi.Input[Sequence[pulumi.Input[pulumi.InputType['_config.SandwichArgs']]]] secret_sandwiches: Super duper secret sandwiches.
"""
...
@overload
@@ -75,6 +94,7 @@ def _internal_init(__self__,
resource_name: str,
opts: Optional[pulumi.ResourceOptions] = None,
favorite_color: Optional[pulumi.Input[Union[str, 'Color']]] = None,
+ secret_sandwiches: Optional[pulumi.Input[Sequence[pulumi.Input[pulumi.InputType['_config.SandwichArgs']]]]] = None,
__props__=None):
opts = pulumi.ResourceOptions.merge(_utilities.get_resource_opts_defaults(), opts)
if not isinstance(opts, pulumi.ResourceOptions):
@@ -87,6 +107,7 @@ def _internal_init(__self__,
if favorite_color is None:
favorite_color = _utilities.get_env('FAVE_COLOR')
__props__.__dict__["favorite_color"] = pulumi.Output.from_input(favorite_color).apply(pulumi.runtime.to_json) if favorite_color is not None else None
+ __props__.__dict__["secret_sandwiches"] = pulumi.Output.secret(secret_sandwiches).apply(pulumi.runtime.to_json) if secret_sandwiches is not None else None
super(Provider, __self__).__init__(
'configstation',
resource_name,
diff --git a/pkg/codegen/testing/test/testdata/provider-config-schema/schema.json b/pkg/codegen/testing/test/testdata/provider-config-schema/schema.json
index ce0597a467d2..41c2eab10fe8 100644
--- a/pkg/codegen/testing/test/testdata/provider-config-schema/schema.json
+++ b/pkg/codegen/testing/test/testdata/provider-config-schema/schema.json
@@ -16,6 +16,14 @@
"defaultInfo": {
"environment": ["FAVE_COLOR"]
}
+ },
+ "secretSandwiches": {
+ "type": "array",
+ "items": {
+ "$ref": "#/types/configstation:config:sandwich"
+ },
+ "description": "Super duper secret sandwiches.\n",
+ "secret": true
}
}
},
diff --git a/pkg/codegen/testing/test/testdata/types.json b/pkg/codegen/testing/test/testdata/types.json
index 42f3fb153be9..1a0c511e73eb 100644
--- a/pkg/codegen/testing/test/testdata/types.json
+++ b/pkg/codegen/testing/test/testdata/types.json
@@ -4,6 +4,9 @@
"meta": {
"moduleFormat": "(.*)"
},
+ "language": {
+ "nodejs": {}
+ },
"config": {},
"types": {
"typetests::object": {
diff --git a/pkg/engine/lifecycletest/pulumi_test.go b/pkg/engine/lifecycletest/pulumi_test.go
index b7daacbde0d1..33d4214f7572 100644
--- a/pkg/engine/lifecycletest/pulumi_test.go
+++ b/pkg/engine/lifecycletest/pulumi_test.go
@@ -4437,3 +4437,173 @@ func TestDuplicatesDueToAliases(t *testing.T) {
// Because we made the B first that's what should end up in the state file
assert.Equal(t, resource.URN("urn:pulumi:test::test::pkgA:m:typA::resB"), snap.Resources[1].URN)
}
+
+func TestPendingDeleteReplacement(t *testing.T) {
+ // Test for https://github.com/pulumi/pulumi/issues/11391, check that if we
+ // try to replace a resource via delete before replace, but fail to delete
+ // it, then rerun that we don't error.
+
+ t.Parallel()
+
+ cloudID := 0
+ cloudState := map[resource.ID]resource.PropertyMap{}
+
+ failDeletionOfTypB := true
+
+ 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) {
+
+ id := resource.ID("")
+ if !preview {
+ id = resource.ID(fmt.Sprintf("%d", cloudID))
+ cloudID = cloudID + 1
+ cloudState[id] = news
+ }
+ return id, news, resource.StatusOK, nil
+ },
+ DeleteF: func(urn resource.URN,
+ id resource.ID, olds resource.PropertyMap, timeout float64) (resource.Status, error) {
+ // Fail if anything in cloud state still points to us
+ for _, res := range cloudState {
+ for _, v := range res {
+ if v.IsString() && v.StringValue() == string(id) {
+ return resource.StatusOK, fmt.Errorf("Can not delete %s", id)
+ }
+ }
+ }
+
+ if strings.Contains(string(urn), "typB") && failDeletionOfTypB {
+ return resource.StatusOK, fmt.Errorf("Could not delete typB")
+ }
+
+ delete(cloudState, id)
+ return resource.StatusOK, nil
+ },
+ DiffF: func(urn resource.URN,
+ id resource.ID, olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
+ if strings.Contains(string(urn), "typA") {
+ if !olds["foo"].DeepEquals(news["foo"]) {
+ return plugin.DiffResult{
+ Changes: plugin.DiffSome,
+ ReplaceKeys: []resource.PropertyKey{"foo"},
+ DetailedDiff: map[string]plugin.PropertyDiff{
+ "foo": {
+ Kind: plugin.DiffUpdateReplace,
+ InputDiff: true,
+ },
+ },
+ DeleteBeforeReplace: true,
+ }, nil
+ }
+ }
+ if strings.Contains(string(urn), "typB") {
+ if !olds["parent"].DeepEquals(news["parent"]) {
+ return plugin.DiffResult{
+ Changes: plugin.DiffSome,
+ ReplaceKeys: []resource.PropertyKey{"parent"},
+ DetailedDiff: map[string]plugin.PropertyDiff{
+ "parent": {
+ Kind: plugin.DiffUpdateReplace,
+ InputDiff: true,
+ },
+ },
+ DeleteBeforeReplace: false,
+ }, nil
+ }
+ if !olds["frob"].DeepEquals(news["frob"]) {
+ return plugin.DiffResult{
+ Changes: plugin.DiffSome,
+ ReplaceKeys: []resource.PropertyKey{"frob"},
+ DetailedDiff: map[string]plugin.PropertyDiff{
+ "frob": {
+ Kind: plugin.DiffUpdateReplace,
+ InputDiff: true,
+ },
+ },
+ DeleteBeforeReplace: false,
+ }, nil
+ }
+ }
+
+ return plugin.DiffResult{}, nil
+ },
+ UpdateF: func(urn resource.URN,
+ id resource.ID, olds, news resource.PropertyMap, timeout float64,
+ ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
+ assert.Fail(t, "Didn't expect update to be called")
+ return nil, resource.StatusOK, nil
+ },
+ }, nil
+ }, deploytest.WithoutGrpc),
+ }
+
+ insA := resource.NewPropertyMapFromMap(map[string]interface{}{
+ "foo": "bar",
+ })
+ inB := "active"
+ program := deploytest.NewLanguageRuntime(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
+ urnA, idA, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
+ Inputs: insA,
+ })
+ assert.NoError(t, err)
+
+ _, _, _, err = monitor.RegisterResource("pkgA:m:typB", "resB", true, deploytest.ResourceOptions{
+ Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
+ "parent": idA,
+ "frob": inB,
+ }),
+ PropertyDeps: map[resource.PropertyKey][]resource.URN{
+ "parent": {urnA},
+ },
+ Dependencies: []resource.URN{urnA},
+ })
+ assert.NoError(t, err)
+
+ return nil
+ })
+ host := deploytest.NewPluginHost(nil, nil, program, loaders...)
+
+ p := &TestPlan{
+ Options: UpdateOptions{Host: host},
+ }
+
+ project := p.GetProject()
+
+ // Run an update to create the resources
+ snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
+ assert.Nil(t, res)
+ assert.NotNil(t, snap)
+ assert.Len(t, snap.Resources, 3)
+
+ // Trigger a replacement of B but fail to delete it
+ inB = "inactive"
+ snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
+ // Assert that this fails, we should have two B's one marked to delete
+ assert.NotNil(t, res)
+ assert.NotNil(t, snap)
+ assert.Len(t, snap.Resources, 4)
+ assert.Equal(t, snap.Resources[1].Type, tokens.Type("pkgA:m:typA"))
+ assert.False(t, snap.Resources[1].Delete)
+ assert.Equal(t, snap.Resources[2].Type, tokens.Type("pkgA:m:typB"))
+ assert.False(t, snap.Resources[2].Delete)
+ assert.Equal(t, snap.Resources[3].Type, tokens.Type("pkgA:m:typB"))
+ assert.True(t, snap.Resources[3].Delete)
+
+ // Now trigger a replacment of A, which will also trigger B to replace
+ insA = resource.NewPropertyMapFromMap(map[string]interface{}{
+ "foo": "baz",
+ })
+ failDeletionOfTypB = false
+ snap, res = TestOp(Update).Run(project, p.GetTarget(t, snap), p.Options, false, p.BackendClient, nil)
+ // Assert this is ok, we should have just one A and B
+ assert.Nil(t, res)
+ assert.NotNil(t, snap)
+ assert.Len(t, snap.Resources, 3)
+ assert.Equal(t, snap.Resources[1].Type, tokens.Type("pkgA:m:typA"))
+ assert.False(t, snap.Resources[1].Delete)
+ assert.Equal(t, snap.Resources[2].Type, tokens.Type("pkgA:m:typB"))
+ assert.False(t, snap.Resources[2].Delete)
+}
diff --git a/pkg/resource/deploy/step_generator.go b/pkg/resource/deploy/step_generator.go
index cfe63eb3ca79..35112b5d9e08 100644
--- a/pkg/resource/deploy/step_generator.go
+++ b/pkg/resource/deploy/step_generator.go
@@ -973,7 +973,7 @@ func (sg *stepGenerator) generateStepsFromDiff(
dependentResource := toReplace[i].res
// If we already deleted this resource due to some other DBR, don't do it again.
- if sg.deletes[dependentResource.URN] {
+ if sg.pendingDeletes[dependentResource] {
continue
}
@@ -992,10 +992,16 @@ func (sg *stepGenerator) generateStepsFromDiff(
logging.V(7).Infof("Planner decided to delete '%v' due to dependence on condemned resource '%v'",
dependentResource.URN, urn)
- steps = append(steps, NewDeleteReplacementStep(sg.deployment, sg.deletes, dependentResource, true))
+ // This resource might already be pending-delete
+ if dependentResource.Delete {
+ steps = append(steps, NewDeleteStep(sg.deployment, sg.deletes, dependentResource))
+ } else {
+ steps = append(steps, NewDeleteReplacementStep(sg.deployment, sg.deletes, dependentResource, true))
+ }
// Mark the condemned resource as deleted. We won't know until later in the deployment whether
// or not we're going to be replacing this resource.
sg.deletes[dependentResource.URN] = true
+ sg.pendingDeletes[dependentResource] = true
}
}