pulumi / pulumi-dotnet

.NET support for Pulumi
Apache License 2.0
26 stars 22 forks source link

[sdk/dotnet] Cloudflare record creation throws `NullReferenceException` #79

Open haythem opened 1 year ago

haythem commented 1 year ago

What happened?

Hello,

This particular behavior occurs only when setting a Cloudflare record with a value from DigitalOcean reserved ip output.

Steps to reproduce

  1. Create a DigitalOcean droplet
  2. Create a DigitalOcean reserved IP and attach the droplet to it
  3. Create a Cloudflare record using the reserved IP

var Droplet = new DigitalOcean.Droplet(name, new DigitalOcean.DropletArgs
{
    Name = name,
    Image = image,
    Region = region,
    Size = size,
    GracefulShutdown = true,
    DropletAgent = false,
    SshKeys = { sshKey.Fingerprint },
    VpcUuid = vpc.Id,
    Tags = tags,
    UserData = userData
});

// Reserved IP
var ReservedIp = new DigitalOcean.ReservedIp("reserved-ip", new DigitalOcean.ReservedIpArgs
{
    Region = Droplet.Region,
    DropletId = Droplet.Id.Apply(int.Parse)
});

var Zone = Output.Create(Cloudflare.GetZone.InvokeAsync(new Cloudflare.GetZoneArgs
{
    Name = "mydomain.com"
}));

var ApiRecord = new Cloudflare.Record("api-record", new Cloudflare.RecordArgs
{
    Name = "api",
    ZoneId = Zone.Apply(x => x.Id),
    Type = "A",
    Value = ReservedIp.IpAddress,
    Ttl = 3600,
    AllowOverwrite = true
});

Changing the value of the record to Droplet.Ipv4Address solves the problem.

Expected Behavior

Create the Cloudflare record with the reserved ip output.

Actual Behavior

An exception is thrown. Sometimes the pulumi up command hangs (see screenshot below) for 3 to 5 minutes before throwing the error.

image

Exception Thrown

Diagnostics:
  pulumi:pulumi:Stack (octopush-production):
    error: Running program 'D:\Projects\GitHub\OctoPush-Infrastructure\src\Cloud\bin\Debug\net6.0\Cloud.dll' failed with an unhandled exception:
    System.NullReferenceException: Object reference not set to an instance of an object.
       at Task<HashSet<string>> Pulumi.Deployment.GetAllTransitivelyReferencedResourceUrnsAsync(HashSet<Resource> resources)+(Resource r) => { }
       at bool System.Linq.Enumerable+WhereSelectEnumerableIterator<TSource, TResult>.MoveNext()
       at Task<TResult[]> System.Threading.Tasks.Task.WhenAll<TResult>(IEnumerable<Task<TResult>> tasks)
       at async Task<HashSet<string>> Pulumi.Deployment.GetAllTransitivelyReferencedResourceUrnsAsync(HashSet<Resource> resources)
       at async Task<PrepareResult> Pulumi.Deployment.PrepareResourceAsync(string label, Resource res, bool custom, bool remote, ResourceArgs args, ResourceOptions options)
       at async Task<(string urn, string id, Struct data, ImmutableDictionary<string, ImmutableHashSet<Resource>> dependencies)> Pulumi.Deployment.RegisterResourceAsync(Resource resource, bool remote, Func<string, Resource> newDependency, ResourceArgs args, ResourceOptions options)
       at async Task<(string urn, string id, Struct data, ImmutableDictionary<string, ImmutableHashSet<Resource>> dependencies)> Pulumi.Deployment.ReadOrRegisterResourceAsync(Resource resource, bool remote, Func<string, Resource> newDependency, ResourceArgs args, ResourceOptions options)
       at async Task Pulumi.Deployment.CompleteResourceAsync(Resource resource, bool remote, Func<string, Resource> newDependency, ResourceArgs args, ResourceOptions options, ImmutableDictionary<string, IOutputCompletionSource> completionSources)

Output of pulumi about

CLI
Version      3.51.1
Go Version   go1.19.4
Go Compiler  gc

Plugins
NAME          VERSION
azure-native  1.92.0
cloudflare    4.15.0
digitalocean  4.16.0
dotnet        unknown

Host
OS       Microsoft Windows 11 Enterprise
Version  10.0.22000 Build 22000
Arch     x86_64

This project is written in dotnet: executable='C:\Program Files\dotnet\dotnet.exe' version='7.0.102'

Additional context

No response

Contributing

Vote on this issue by adding a 👍 reaction. To contribute a fix for this issue, leave a comment (and link to your pull request, if you've opened one already).

Zaid-Ajaj commented 1 year ago

Hi @haythem thanks for filing the issue! This one is interesting. It looks like the following is failing with NRE

var tasks = transitivelyReachableCustomResources.Select(r => r.Urn.GetValueAsync(whenUnknown: ""));
var urns = await Task.WhenAll(tasks).ConfigureAwait(false);

Which suggests that r.Urn is null for one of the elements in transitivelyReachableCustomResources. A potential fix is

var tasks = transitivelyReachableCustomResources.Select(resource => 
    resource.Urn == null 
    ? Task.FromResult("") 
    : resource.Urn.GetValueAsync(whenUnknown: "")
);

var urns = await Task.WhenAll(tasks).ConfigureAwait(false);

that guards against null but the real question is why Urn is null in the first place. cc @Frassle

Frassle commented 1 year ago

Yeh Urn should never be null... we should track that down and fix it rather than changing transitivelyReachableCustomResources to handle null.