Fix null exceptions when reading unknown outputs (#7762)
* Fix null exceptions when reading unknown outputs * Fix test compilation * Add a test reproducing the actual problem * Revert the change in behavior that was not clearny an improvement * Unique resource UUID * Add a CHANGELOG entry
This commit is contained in:
parent
065ae27b91
commit
2223c6b8b9
|
@ -2,3 +2,6 @@
|
|||
|
||||
### Bug Fixes
|
||||
|
||||
- [sdk/dotnet] - Fix an exception when passing an unknown `Output` to
|
||||
the `DependsOn` resource option.
|
||||
[#7762](https://github.com/pulumi/pulumi/pull/7762)
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit;
|
||||
|
||||
using Pulumi.Testing;
|
||||
using Pulumi.Tests.Mocks;
|
||||
|
||||
namespace Pulumi.Tests
|
||||
{
|
||||
public class DeploymentResourceDependencyGatheringTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task DeploysResourcesWithUnknownDependsOn()
|
||||
{
|
||||
var deployResult = await Deployment.TryTestAsync<DeploysResourcesWithUnknownDependsOnStack>(
|
||||
new EmptyMocks(isPreview: true),
|
||||
new TestOptions()
|
||||
{
|
||||
IsPreview = true,
|
||||
});
|
||||
Assert.Null(deployResult.Exception);
|
||||
}
|
||||
|
||||
class DeploysResourcesWithUnknownDependsOnStack : Stack
|
||||
{
|
||||
public DeploysResourcesWithUnknownDependsOnStack()
|
||||
{
|
||||
new MyCustomResource("r1", null, new CustomResourceOptions()
|
||||
{
|
||||
DependsOn = Output<Resource[]>.CreateUnknown(new Resource[]{}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MyArgs : ResourceArgs
|
||||
{
|
||||
}
|
||||
|
||||
[ResourceType("test:DeploymentResourceDependencyGatheringTests:resource", null)]
|
||||
private class MyCustomResource : CustomResource
|
||||
{
|
||||
public MyCustomResource(string name, MyArgs? args, CustomResourceOptions? options = null)
|
||||
: base("test:DeploymentResourceDependencyGatheringTests:resource", name, args ?? new MyArgs(), options)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyMocks : IMocks
|
||||
{
|
||||
public bool IsPreview { get; private set; }
|
||||
|
||||
public EmptyMocks(bool isPreview)
|
||||
{
|
||||
this.IsPreview = isPreview;
|
||||
}
|
||||
|
||||
public Task<object> CallAsync(MockCallArgs args)
|
||||
{
|
||||
return Task.FromResult<object>(args);
|
||||
}
|
||||
|
||||
public Task<(string? id, object state)> NewResourceAsync(MockResourceArgs args)
|
||||
{
|
||||
if (args.Type == "test:DeploymentResourceDependencyGatheringTests:resource")
|
||||
{
|
||||
return Task.FromResult<(string?, object)>((this.IsPreview ? null : "id",
|
||||
new Dictionary<string, object>()));
|
||||
}
|
||||
throw new Exception($"Unknown resource {args.Type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ namespace Pulumi.Tests
|
|||
Assert.IsType<RunException>(deployResult.Exception!);
|
||||
Assert.Contains("Deliberate test error", deployResult.Exception!.Message);
|
||||
var stack = (TerminatesEarlyOnExceptionStack)deployResult.Resources[0];
|
||||
Assert.False(stack.SlowOutput.GetValueAsync().IsCompleted);
|
||||
Assert.False(stack.SlowOutput.GetValueAsync(whenUnknown: default!).IsCompleted);
|
||||
}
|
||||
|
||||
class TerminatesEarlyOnExceptionStack : Stack
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace Pulumi.Tests.Mocks
|
|||
var instance = resources.OfType<Instance>().FirstOrDefault();
|
||||
Assert.NotNull(instance);
|
||||
|
||||
var ip = await instance!.PublicIp.GetValueAsync();
|
||||
var ip = await instance!.PublicIp.GetValueAsync(whenUnknown: default!);
|
||||
Assert.Equal("203.0.113.12", ip);
|
||||
}
|
||||
|
||||
|
@ -48,10 +48,10 @@ namespace Pulumi.Tests.Mocks
|
|||
var myCustom = resources.OfType<MyCustom>().FirstOrDefault();
|
||||
Assert.NotNull(myCustom);
|
||||
|
||||
var instance = await myCustom!.Instance.GetValueAsync();
|
||||
var instance = await myCustom!.Instance.GetValueAsync(whenUnknown: default!);
|
||||
Assert.IsType<Instance>(instance);
|
||||
|
||||
var ip = await instance.PublicIp.GetValueAsync();
|
||||
var ip = await instance.PublicIp.GetValueAsync(whenUnknown: default!);
|
||||
Assert.Equal("203.0.113.12", ip);
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ namespace Pulumi.Tests.Mocks
|
|||
var stack = resources.OfType<MyStack>().FirstOrDefault();
|
||||
Assert.NotNull(stack);
|
||||
|
||||
var ip = await stack!.PublicIp.GetValueAsync();
|
||||
var ip = await stack!.PublicIp.GetValueAsync(whenUnknown: default!);
|
||||
Assert.Equal("203.0.113.12", ip);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016-2019, Pulumi Corporation
|
||||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
@ -147,7 +147,8 @@ namespace Pulumi
|
|||
|
||||
public async IAsyncEnumerator<Input<T>> GetAsyncEnumerator(CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await _outputValue.GetValueAsync().ConfigureAwait(false);
|
||||
var data = await _outputValue.GetValueAsync(whenUnknown: ImmutableArray<T>.Empty)
|
||||
.ConfigureAwait(false);
|
||||
foreach (var value in data)
|
||||
{
|
||||
yield return value;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
// Copyright 2016-2019, Pulumi Corporation
|
||||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Pulumi
|
||||
|
@ -64,7 +65,7 @@ namespace Pulumi
|
|||
|
||||
/// <summary>
|
||||
/// Merge two instances of <see cref="InputMap{V}"/>. Returns a new <see cref="InputMap{V}"/>
|
||||
/// without modifying any of the arguments.
|
||||
/// without modifying any of the arguments.
|
||||
/// <para/>If both maps contain the same key, the value from the second map takes over.
|
||||
/// </summary>
|
||||
/// <param name="first">The first <see cref="InputMap{V}"/>. Has lower priority in case of
|
||||
|
@ -113,7 +114,8 @@ namespace Pulumi
|
|||
|
||||
public async IAsyncEnumerator<Input<KeyValuePair<string, V>>> GetAsyncEnumerator(CancellationToken cancellationToken)
|
||||
{
|
||||
var data = await _outputValue.GetValueAsync().ConfigureAwait(false);
|
||||
var data = await _outputValue.GetValueAsync(whenUnknown: ImmutableDictionary<string, V>.Empty)
|
||||
.ConfigureAwait(false);
|
||||
foreach (var value in data)
|
||||
{
|
||||
yield return value;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016-2019, Pulumi Corporation
|
||||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -148,10 +148,10 @@ namespace Pulumi
|
|||
}
|
||||
}
|
||||
|
||||
internal async Task<T> GetValueAsync()
|
||||
internal async Task<T> GetValueAsync(T whenUnknown)
|
||||
{
|
||||
var data = await DataTask.ConfigureAwait(false);
|
||||
return data.Value;
|
||||
return data.IsKnown ? data.Value : whenUnknown;
|
||||
}
|
||||
|
||||
async Task<ImmutableHashSet<Resource>> IOutput.GetResourcesAsync()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation
|
||||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
|
@ -9,7 +9,7 @@ namespace Pulumi.Utilities
|
|||
/// <summary>
|
||||
/// Allows extracting some internal insights about an instance of
|
||||
/// <see cref="Output{T}"/>.
|
||||
///
|
||||
///
|
||||
/// Danger: these utilities are intended for use in test and
|
||||
/// debugging scenarios. In normal Pulumi programs, please
|
||||
/// consider using `.Apply` instead to chain `Output{T}`
|
||||
|
@ -61,7 +61,7 @@ namespace Pulumi.Utilities
|
|||
/// </summary>
|
||||
/// <param name="output">The <see cref="Output{T}"/> to evaluate.</param>
|
||||
public static Task<T> GetValueAsync<T>(Output<T> output)
|
||||
=> output.GetValueAsync();
|
||||
=> output.GetValueAsync(whenUnknown: default!);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a set of resources that the given output depends on.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016-2019, Pulumi Corporation
|
||||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
@ -141,7 +141,7 @@ namespace Pulumi
|
|||
{
|
||||
try
|
||||
{
|
||||
return await resource.Urn.GetValueAsync().ConfigureAwait(false);
|
||||
return await resource.Urn.GetValueAsync(whenUnknown: "").ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace Pulumi
|
|||
// If no parent was provided, parent to the root resource.
|
||||
LogExcessive($"Getting parent urn: t={type}, name={name}, custom={custom}, remote={remote}");
|
||||
var parentUrn = options.Parent != null
|
||||
? await options.Parent.Urn.GetValueAsync().ConfigureAwait(false)
|
||||
? await options.Parent.Urn.GetValueAsync(whenUnknown: default!).ConfigureAwait(false)
|
||||
: await GetRootResourceAsync(type).ConfigureAwait(false);
|
||||
LogExcessive($"Got parent urn: t={type}, name={name}, custom={custom}, remote={remote}");
|
||||
|
||||
|
@ -97,8 +97,8 @@ namespace Pulumi
|
|||
var uniqueAliases = new HashSet<string>();
|
||||
foreach (var alias in res._aliases)
|
||||
{
|
||||
var aliasVal = await alias.ToOutput().GetValueAsync().ConfigureAwait(false);
|
||||
if (uniqueAliases.Add(aliasVal))
|
||||
var aliasVal = await alias.ToOutput().GetValueAsync(whenUnknown: "").ConfigureAwait(false);
|
||||
if (aliasVal != "" && uniqueAliases.Add(aliasVal))
|
||||
{
|
||||
aliases.Add(aliasVal);
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ namespace Pulumi
|
|||
}
|
||||
|
||||
private static Task<ImmutableArray<Resource>> GatherExplicitDependenciesAsync(InputList<Resource> resources)
|
||||
=> resources.ToOutput().GetValueAsync();
|
||||
=> resources.ToOutput().GetValueAsync(whenUnknown: ImmutableArray<Resource>.Empty);
|
||||
|
||||
private static async Task<HashSet<string>> GetAllTransitivelyReferencedResourceUrnsAsync(
|
||||
HashSet<Resource> resources)
|
||||
|
@ -159,9 +159,9 @@ namespace Pulumi
|
|||
default: return false; // Unreachable
|
||||
}
|
||||
});
|
||||
var tasks = transitivelyReachableCustomResources.Select(r => r.Urn.GetValueAsync());
|
||||
var tasks = transitivelyReachableCustomResources.Select(r => r.Urn.GetValueAsync(whenUnknown: ""));
|
||||
var urns = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
return new HashSet<string>(urns);
|
||||
return new HashSet<string>(urns.Where(urn => !string.IsNullOrEmpty(urn)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016-2019, Pulumi Corporation
|
||||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
|
@ -46,16 +46,16 @@ namespace Pulumi
|
|||
"pulumi:pulumi:getResource",
|
||||
new GetResourceInvokeArgs {Urn = options.Urn},
|
||||
new InvokeOptions()).ConfigureAwait(false);
|
||||
|
||||
|
||||
var urn = result.Fields["urn"].StringValue;
|
||||
var id = result.Fields["id"].StringValue;
|
||||
var state = result.Fields["state"].StructValue;
|
||||
return (urn, id, state, ImmutableDictionary<string, ImmutableHashSet<Resource>>.Empty);
|
||||
}
|
||||
|
||||
|
||||
if (options.Id != null)
|
||||
{
|
||||
var id = await options.Id.ToOutput().GetValueAsync().ConfigureAwait(false);
|
||||
var id = await options.Id.ToOutput().GetValueAsync(whenUnknown: "").ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
{
|
||||
if (!(resource is CustomResource))
|
||||
|
@ -74,7 +74,7 @@ namespace Pulumi
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="ReadOrRegisterResourceAsync"/> then completes all the
|
||||
/// Calls <see cref="ReadOrRegisterResourceAsync"/> then completes all the
|
||||
/// <see cref="IOutputCompletionSource"/> sources on the <paramref name="resource"/> with
|
||||
/// the results of it.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016-2019, Pulumi Corporation
|
||||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -25,8 +25,8 @@ namespace Pulumi
|
|||
|
||||
// The registration could very well still be taking place, so we will need to wait for its URN.
|
||||
// Additionally, the output properties might have come from other resources, so we must await those too.
|
||||
var urn = await resource.Urn.GetValueAsync().ConfigureAwait(false);
|
||||
var props = await outputs.GetValueAsync().ConfigureAwait(false);
|
||||
var urn = await resource.Urn.GetValueAsync(whenUnknown: default!).ConfigureAwait(false);
|
||||
var props = await outputs.GetValueAsync(whenUnknown: default!).ConfigureAwait(false);
|
||||
|
||||
var serialized = await SerializeAllPropertiesAsync(
|
||||
opLabel, props, await MonitorSupportsResourceReferences().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016-2019, Pulumi Corporation
|
||||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -38,7 +38,7 @@ namespace Pulumi
|
|||
|
||||
private async Task<string> SetRootResourceWorkerAsync(Stack stack)
|
||||
{
|
||||
var resUrn = await stack.Urn.GetValueAsync().ConfigureAwait(false);
|
||||
var resUrn = await stack.Urn.GetValueAsync(whenUnknown: default!).ConfigureAwait(false);
|
||||
await this.Engine.SetRootResourceAsync(new SetRootResourceRequest
|
||||
{
|
||||
Urn = resUrn,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016-2019, Pulumi Corporation
|
||||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Pulumi.Serialization;
|
||||
|
@ -53,8 +53,8 @@ namespace Pulumi
|
|||
|
||||
if (provider._registrationId == null)
|
||||
{
|
||||
var providerUrn = await provider.Urn.GetValueAsync().ConfigureAwait(false);
|
||||
var providerId = await provider.Id.GetValueAsync().ConfigureAwait(false);
|
||||
var providerUrn = await provider.Urn.GetValueAsync(whenUnknown: default!).ConfigureAwait(false);
|
||||
var providerId = await provider.Id.GetValueAsync(whenUnknown: default!).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(providerId))
|
||||
{
|
||||
providerId = Constants.UnknownValue;
|
||||
|
|
Loading…
Reference in a new issue