Skip to content

Commit

Permalink
[dotnet] Unknowns for previews involving an uninitialized provider (#…
Browse files Browse the repository at this point in the history
…2957)

<!--Thanks for your contribution. See [CONTRIBUTING](CONTRIBUTING.md)
    for Pulumi's contribution guidelines.

    Help us merge your changes more quickly by adding more details such
    as labels, milestones, and reviewers.-->

### Proposed changes

This PR fixes the "panic" that occurs when an uninitialized provider is
used with the yaml/kustomize resources. In that situation, the invoke
calls aren't performed and the engine simply returns an uninitialized
result. This causes a problem in the callers because the methods on
`ImmutableArray` mustn't be used when the value is uninitialized
(`IsDefault` is true).

### Example
The example given in #2741 easily repros the issue. With this fix, the
behavior improves:

```
Diagnostics:
  kubernetes:yaml:ConfigFile (guestbook):
    warning: Required input properties have unknown values. Preview is incomplete.

Outputs:
    FrontendIp: output<string>
```

### Testing
Includes integration tests involving an uninitialized provider with a
`ConfigFile` or `Directory`.

### Related issues (optional)

<!--Refer to related PRs or issues: #1234, or 'Fixes #1234' or 'Closes
#1234'.
Or link to full URLs to issues or pull requests in other GitHub
repositories. -->

Closes #2741
  • Loading branch information
EronWright committed Apr 17, 2024
1 parent ee61f79 commit 6eb4495
Show file tree
Hide file tree
Showing 17 changed files with 244 additions and 38 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## Unreleased
- [dotnet] Unknowns for previews involving an uninitialized provider (https://github.com/pulumi/pulumi-kubernetes/pull/2957)

## 4.10.0 (April 11, 2024)

- ConfigGroup V2 (https://github.com/pulumi/pulumi-kubernetes/pull/2844)
Expand Down
25 changes: 13 additions & 12 deletions provider/pkg/gen/dotnet-templates/helm/v3/Invokes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,19 @@ internal static class Invokes
/// Invoke the resource provider to fetch a Helm Chart, expand it into YAML, and return the corresponding objects.
/// </summary>
internal static Output<ImmutableArray<ImmutableDictionary<string, object>>> HelmTemplate(HelmTemplateArgs args,
InvokeOptions? options = null)
=> Output.Create(Deployment.Instance.InvokeAsync<HelmTemplateResult>("kubernetes:helm:template", args,
options.WithDefaults())).Apply(r =>
{
/// Invoke on an unconfigured provider results in an empty response.
/// TODO Change this based on how https://github.com/pulumi/pulumi/issues/10209
/// is addressed.
if (r.Result.IsDefaultOrEmpty) {
return ImmutableArray.Create<ImmutableDictionary<string,object>>();
}
return r.Result;
});
InvokeOptions? options = null) {
Output<ImmutableArray<ImmutableDictionary<string, object>>> Convert(HelmTemplateResult r) {
var a = r.Result;
if (a.IsDefault) {
Pulumi.Log.Warn("The provider is not fully configured. Preview is incomplete.", options?.Parent);
return Pulumi.Utilities.OutputUtilities.CreateUnknown(a);
}
return Pulumi.Output.Create(a);
}

return Output.Create(Deployment.Instance.InvokeAsync<HelmTemplateResult>("kubernetes:helm:template", args,
options.WithDefaults())).Apply(Convert);
}
}

internal class HelmTemplateArgs : InvokeArgs
Expand Down
16 changes: 13 additions & 3 deletions provider/pkg/gen/dotnet-templates/kustomize/Invokes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,19 @@ internal static class Invokes
/// Invoke the resource provider to process a kustomization.
/// </summary>
internal static Output<ImmutableArray<ImmutableDictionary<string, object>>> KustomizeDirectory(KustomizeDirectoryArgs args,
InvokeOptions? options = null)
=> Output.Create(Deployment.Instance.InvokeAsync<KustomizeDirectoryResult>("kubernetes:kustomize:directory", args,
options.WithDefaults())).Apply(r => r.Result.ToImmutableArray());
InvokeOptions? options = null) {
Output<ImmutableArray<ImmutableDictionary<string, object>>> Convert(KustomizeDirectoryResult r) {
var a = r.Result;
if (a.IsDefault) {
Pulumi.Log.Warn("The provider is not fully configured. Preview is incomplete.", options?.Parent);
return Pulumi.Utilities.OutputUtilities.CreateUnknown(a);
}
return Pulumi.Output.Create(a);
}

return Output.Create(Deployment.Instance.InvokeAsync<KustomizeDirectoryResult>("kubernetes:kustomize:directory", args,
options.WithDefaults())).Apply(Convert);
}
}

internal class KustomizeDirectoryArgs : InvokeArgs
Expand Down
16 changes: 13 additions & 3 deletions provider/pkg/gen/dotnet-templates/yaml/Invokes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,19 @@ internal static class Invokes
/// Invoke the resource provider to decode a YAML string.
/// </summary>
internal static Output<ImmutableArray<ImmutableDictionary<string, object>>> YamlDecode(YamlDecodeArgs args,
InvokeOptions? options = null)
=> Output.Create(Deployment.Instance.InvokeAsync<YamlDecodeResult>("kubernetes:yaml:decode", args,
options.WithDefaults())).Apply(r => r.Result.ToImmutableArray());
InvokeOptions? options = null) {
Output<ImmutableArray<ImmutableDictionary<string, object>>> Convert(YamlDecodeResult r) {
var a = r.Result;
if (a.IsDefault) {
Pulumi.Log.Warn("The provider is not fully configured. Preview is incomplete.", options?.Parent);
return Pulumi.Utilities.OutputUtilities.CreateUnknown(a);
}
return Pulumi.Output.Create(a);
}

return Output.Create(Deployment.Instance.InvokeAsync<YamlDecodeResult>("kubernetes:yaml:decode", args,
options.WithDefaults())).Apply(Convert);
}
}

internal class YamlDecodeArgs : InvokeArgs
Expand Down
25 changes: 13 additions & 12 deletions sdk/dotnet/Helm/V3/Invokes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,19 @@ internal static class Invokes
/// Invoke the resource provider to fetch a Helm Chart, expand it into YAML, and return the corresponding objects.
/// </summary>
internal static Output<ImmutableArray<ImmutableDictionary<string, object>>> HelmTemplate(HelmTemplateArgs args,
InvokeOptions? options = null)
=> Output.Create(Deployment.Instance.InvokeAsync<HelmTemplateResult>("kubernetes:helm:template", args,
options.WithDefaults())).Apply(r =>
{
/// Invoke on an unconfigured provider results in an empty response.
/// TODO Change this based on how https://github.com/pulumi/pulumi/issues/10209
/// is addressed.
if (r.Result.IsDefaultOrEmpty) {
return ImmutableArray.Create<ImmutableDictionary<string,object>>();
}
return r.Result;
});
InvokeOptions? options = null) {
Output<ImmutableArray<ImmutableDictionary<string, object>>> Convert(HelmTemplateResult r) {
var a = r.Result;
if (a.IsDefault) {
Pulumi.Log.Warn("The provider is not fully configured. Preview is incomplete.", options?.Parent);
return Pulumi.Utilities.OutputUtilities.CreateUnknown(a);
}
return Pulumi.Output.Create(a);
}

return Output.Create(Deployment.Instance.InvokeAsync<HelmTemplateResult>("kubernetes:helm:template", args,
options.WithDefaults())).Apply(Convert);
}
}

internal class HelmTemplateArgs : InvokeArgs
Expand Down
16 changes: 13 additions & 3 deletions sdk/dotnet/Kustomize/Invokes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,19 @@ internal static class Invokes
/// Invoke the resource provider to process a kustomization.
/// </summary>
internal static Output<ImmutableArray<ImmutableDictionary<string, object>>> KustomizeDirectory(KustomizeDirectoryArgs args,
InvokeOptions? options = null)
=> Output.Create(Deployment.Instance.InvokeAsync<KustomizeDirectoryResult>("kubernetes:kustomize:directory", args,
options.WithDefaults())).Apply(r => r.Result.ToImmutableArray());
InvokeOptions? options = null) {
Output<ImmutableArray<ImmutableDictionary<string, object>>> Convert(KustomizeDirectoryResult r) {
var a = r.Result;
if (a.IsDefault) {
Pulumi.Log.Warn("The provider is not fully configured. Preview is incomplete.", options?.Parent);
return Pulumi.Utilities.OutputUtilities.CreateUnknown(a);
}
return Pulumi.Output.Create(a);
}

return Output.Create(Deployment.Instance.InvokeAsync<KustomizeDirectoryResult>("kubernetes:kustomize:directory", args,
options.WithDefaults())).Apply(Convert);
}
}

internal class KustomizeDirectoryArgs : InvokeArgs
Expand Down
16 changes: 13 additions & 3 deletions sdk/dotnet/Yaml/Invokes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,19 @@ internal static class Invokes
/// Invoke the resource provider to decode a YAML string.
/// </summary>
internal static Output<ImmutableArray<ImmutableDictionary<string, object>>> YamlDecode(YamlDecodeArgs args,
InvokeOptions? options = null)
=> Output.Create(Deployment.Instance.InvokeAsync<YamlDecodeResult>("kubernetes:yaml:decode", args,
options.WithDefaults())).Apply(r => r.Result.ToImmutableArray());
InvokeOptions? options = null) {
Output<ImmutableArray<ImmutableDictionary<string, object>>> Convert(YamlDecodeResult r) {
var a = r.Result;
if (a.IsDefault) {
Pulumi.Log.Warn("The provider is not fully configured. Preview is incomplete.", options?.Parent);
return Pulumi.Utilities.OutputUtilities.CreateUnknown(a);
}
return Pulumi.Output.Create(a);
}

return Output.Create(Deployment.Instance.InvokeAsync<YamlDecodeResult>("kubernetes:yaml:decode", args,
options.WithDefaults())).Apply(Convert);
}
}

internal class YamlDecodeArgs : InvokeArgs
Expand Down
54 changes: 52 additions & 2 deletions tests/sdk/dotnet/dotnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,23 @@ func TestDotnet_YamlLocal(t *testing.T) {
integration.ProgramTest(t, &test)
}

func TestDotnet_YamlUninitializedProvider(t *testing.T) {
test := baseOptions.With(integration.ProgramTestOptions{
Dir: "yaml-uninitialized-provider",
Quick: false,
SkipPreview: false,
SkipExportImport: true,
SkipEmptyPreviewUpdate: true,
SkipUpdate: true,
SkipRefresh: true,
ExpectRefreshChanges: true,
AllowEmptyPreviewChanges: true,
})
integration.ProgramTest(t, &test)

// FUTURE: verify that the stack outputs include 'serviceUid' and has an unknown value.
}

func TestDotnet_Helm(t *testing.T) {
test := baseOptions.With(integration.ProgramTestOptions{
Dir: filepath.Join("helm", "step1"),
Expand Down Expand Up @@ -262,6 +279,23 @@ func TestDotnet_Kustomize(t *testing.T) {
integration.ProgramTest(t, &test)
}

func TestDotnet_Kustomize_UninitializedProvider(t *testing.T) {
test := baseOptions.With(integration.ProgramTestOptions{
Dir: "kustomize-uninitialized-provider",
Quick: false,
SkipPreview: false,
SkipExportImport: true,
SkipEmptyPreviewUpdate: true,
SkipUpdate: true,
SkipRefresh: true,
ExpectRefreshChanges: true,
AllowEmptyPreviewChanges: true,
})
integration.ProgramTest(t, &test)

// FUTURE: verify that the stack outputs include 'serviceUid' and has an unknown value.
}

func TestDotnet_Secrets(t *testing.T) {
secretMessage := "secret message for testing"

Expand Down Expand Up @@ -311,10 +345,10 @@ func TestDotnet_ServerSideApply(t *testing.T) {
integration.ProgramTest(t, &test)
}

// TestOptionPropagation tests the handling of resource options by the various compoonent resources.
// TestDotnet_OptionPropagation tests the handling of resource options by the various compoonent resources.
// Component resources are responsible for implementing option propagation logic when creating
// child resources.
func TestOptionPropagation(t *testing.T) {
func TestDotnet_OptionPropagation(t *testing.T) {
g := NewWithT(t)
format.MaxLength = 0
format.MaxDepth = 5
Expand Down Expand Up @@ -803,3 +837,19 @@ func TestOptionPropagation(t *testing.T) {
err = pt.TestPreviewUpdateAndEdits()
require.NoError(t, err)
}

func TestYamlUninitializedProvider(t *testing.T) {
test := baseOptions.With(integration.ProgramTestOptions{
Dir: "yaml-uninitialized-provider",
Quick: false,
SkipPreview: false,
SkipExportImport: true,
SkipUpdate: true,
SkipRefresh: true,
ExpectRefreshChanges: true,
AllowEmptyPreviewChanges: true,
})
integration.ProgramTest(t, &test)

// FUTURE: verify that the stack outputs include 'serviceUid' and has an unknown value.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

</Project>
35 changes: 35 additions & 0 deletions tests/sdk/dotnet/kustomize-uninitialized-provider/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Kubernetes = Pulumi.Kubernetes;
using Kustomize = Pulumi.Kubernetes.Kustomize;
using CoreV1 = Pulumi.Kubernetes.Core.V1;
using Pulumi;
using System.Collections.Generic;
using System.IO;

return await Deployment.RunAsync(() =>
{
// Create an uninitialized provider
var provider = new Kubernetes.Provider("provider", new()
{
KubeConfig = Pulumi.Utilities.OutputUtilities.CreateUnknown(""),
});
// Create resources using Directory (and for which Invoke is skipped)
var directory = new Kustomize.Directory("directory",
new Kustomize.DirectoryArgs
{
Directory = "kustomize",
},
new ComponentResourceOptions
{
Provider = provider
});
// Lookup the registered service, to exercise the 'resources' output property.
// During preview, we expect the stack outputs to be unknown.
var service = directory.GetResource<CoreV1.Service>("kustomize-uninitialized-provider");
return new Dictionary<string, object?>
{
["serviceUid"] = service.Apply(svc => svc.Metadata.Apply(meta => meta.Uid)),
};
});
3 changes: 3 additions & 0 deletions tests/sdk/dotnet/kustomize-uninitialized-provider/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: dotnet_kustomize_uninitialized_provider
description: A test of the kustomize resoures with an uninitialized provider
runtime: dotnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
commonLabels:
app: hello

resources:
- manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
kind: Service
metadata:
name: kustomize-uninitialized-provider
spec:
type: ExternalName
externalName: localhost
35 changes: 35 additions & 0 deletions tests/sdk/dotnet/yaml-uninitialized-provider/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Kubernetes = Pulumi.Kubernetes;
using Yaml = Pulumi.Kubernetes.Yaml;
using CoreV1 = Pulumi.Kubernetes.Core.V1;
using Pulumi;
using System.Collections.Generic;
using System.IO;

return await Deployment.RunAsync(() =>
{
// Create an uninitialized provider
var provider = new Kubernetes.Provider("provider", new()
{
KubeConfig = Pulumi.Utilities.OutputUtilities.CreateUnknown(""),
});
// Create resources using ConfigFile (and for which Invoke is skipped)
var manifest = new Yaml.ConfigFile("manifest",
new Yaml.ConfigFileArgs
{
File = "manifest.yaml",
},
new ComponentResourceOptions
{
Provider = provider
});
// Lookup the registered service, to exercise the 'resources' output property.
// During preview, we expect the stack outputs to be unknown.
var service = manifest.GetResource<CoreV1.Service>("yaml-uninitialized-provider");
return new Dictionary<string, object?>
{
["serviceUid"] = service.Apply(svc => svc.Metadata.Apply(meta => meta.Uid)),
};
});
3 changes: 3 additions & 0 deletions tests/sdk/dotnet/yaml-uninitialized-provider/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: dotnet_yaml_uninitialized_provider
description: A test of the yaml resoures with an uninitialized provider
runtime: dotnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

</Project>
7 changes: 7 additions & 0 deletions tests/sdk/dotnet/yaml-uninitialized-provider/manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
kind: Service
metadata:
name: yaml-uninitialized-provider
spec:
type: ExternalName
externalName: localhost

0 comments on commit 6eb4495

Please sign in to comment.