worried-city-86458
05/27/2021, 11:49 PMawait
an Output<T>
rather than use Output<T>.Apply
, how should I do that? Any examples?bored-activity-40468
05/28/2021, 12:13 AMworried-city-86458
05/28/2021, 12:25 AMTaskCompletionSource
only completes during up and not preview.
So I'd still have to be careful to avoid `await`ing it during preview.public static class OutputExtensions
{
public static Task<T> GetValueAsync<T>(this Output<T> output) =>
(Task<T>)typeof(Output<T>).GetMethod("GetValueAsync", BindingFlags.NonPublic)!.Invoke(output, Array.Empty<object?>())!;
}
What if those outputs never resolve and your await never completes? That sounds dangerous to me...Excuse my ignorance but how would that be different from doing
Output.Tuple.Apply
to render the template?
(Which is a lot more awkward to use, hence I'd much prefer to push the await to Scriban)tall-librarian-49374
05/28/2021, 6:38 AMApply
. The callback will just never be invoked. An await
will freeze forever.worried-city-86458
05/28/2021, 6:53 AMOutput.Tuple
will still await all the tuple items before calling apply.
So if the outputs never resolve, what's the difference between awaiting there vs in a Scriban function?
Scriban is async all the way down, so nothing should be "blocking".tall-librarian-49374
05/28/2021, 7:36 AMTupleHelperAsync
in that example, it all stays within outputs that know how to handle unknown values etc. I guess I should come up with a repro of a problematic case. It would involve an await in the main function.await Deployment.RunAsync(async () =>
{
var resourceGroup = new ResourceGroup("my-rg");
await resourceGroup.Name.GetValueAsync();
return new Dictionary<string, object?>
{
{ "test", "this will never run in the initial preview" }
};
});
worried-city-86458
05/28/2021, 9:40 AMreturn Output.Create(template.RenderAsync(context).AsTask());
tall-librarian-49374
05/28/2021, 9:44 AMawait Deployment.RunAsync(async () =>
{
return new Dictionary<string, object?>
{
{ "test", Output.Create(Task.Run(async () =>
{
var resourceGroup = new ResourceGroup("my-rg");
await resourceGroup.Name.GetValueAsync();
return "this will never run either";
})) }
};
});
worried-city-86458
05/28/2021, 10:15 AMtall-librarian-49374
05/28/2021, 11:55 AMawait Deployment.RunAsync(async () =>
{
return new Dictionary<string, object?>
{
{ "test", Output.Create(Task.Run(async () =>
{
var resourceGroup = new ResourceGroup("my-rg");
return Output.Tuple(resourceGroup.Name, resourceGroup.Location).Apply(v => v.Item1);
})) }
};
});
The preview will not hang (it won’t show an output).worried-city-86458
05/28/2021, 6:17 PMApply
have the gate in ApplyHelperAsync?Output.Create(template.RenderAsync(context).AsTask());
If I understand correctly, what's missing is another building block:
Output.Invoke(template.RenderAsync(context).AsTask());
tall-librarian-49374
05/28/2021, 7:25 PMOutput.Invoke
?worried-city-86458
05/28/2021, 7:32 PMOutput.Prune
would be more descriptive.
Then again maybe it could be baked into Output.Create
since it already knows about Task
.
As for what it is, I was thinking about a helper that "prunes task execution" during preview.
i.e. if it's got a known value, great, use that, otherwise like Apply
it should short circuit and return an unknown value.Apply
semantics but for the initial Create
await Deployment.RunAsync(async () =>
{
return new Dictionary<string, object?>
{
{ "test", Output.Prune(() => Output.Create(Task.Run(async () =>
{
var resourceGroup = new ResourceGroup("my-rg");
await resourceGroup.Name.GetValueAsync();
return "this will never run either ... but it won't hang preview";
}))) }
};
});
Prune
chains the Create
, the indirection is enough:
await Deployment.RunAsync(async () =>
{
return new Dictionary<string, object?>
{
{ "test", Output.Prune(() => Task.Run(async () =>
{
var resourceGroup = new ResourceGroup("my-rg");
await resourceGroup.Name.GetValueAsync();
return "this will never run either ... but it won't hang preview";
})) }
};
});
Create
.
But it probably needs to be more explicit that the Task
will be bypassed during preview.bored-activity-40468
05/28/2021, 7:53 PMtall-librarian-49374
05/28/2021, 9:55 PMworried-city-86458
05/28/2021, 11:41 PMOutput.Prune(() => template.RenderAsync(context).AsTask());
Output.Create(() => template.RenderAsync(context).AsTask());
Apply
semantics, I think it's this:
public static partial class Output
{
public static Output<T> Create<T>(Func<Task<T>> func)
=> Create(default(T)!).Apply(_ => func());
}
Apply
on an unknown output.tall-librarian-49374
05/29/2021, 6:08 AMOutput.All
your arguments and only then call template.RenderAsync
in the apply?
Q2: If Create(default(T)!).Apply(_ => func())
works for you, why do you need to add stuff to Pulumi core?SafeAwaitable
- it’s going to be hard to explain to folks what that is. It also defies the purpose of hiding GetValueAsync
in OutputUtilities
.worried-city-86458
05/29/2021, 6:16 AMOutput.All
and Output.Tuple
are very clumsy to use in comparison to what I can do otherwise using an anonymous type:
var yaml = RenderTemplate("AwsAuth.yaml", ReadResource, new { deployerRoleArn, nodeRoleArn, k8sFullAccessRoleArn, k8sReadOnlyRoleArn });
... and dealing with pulumi outputs in the template
A2: Create(default(T)!).Apply(_ => func())
didn't work - it would hang since the initial output was known
... the Apply
gate only works when the initial output is unknownSafeAwaitable
will skip awaiting the specified awaitable during preview.tall-librarian-49374
05/29/2021, 6:26 AMworried-city-86458
05/29/2021, 6:27 AMpreview
(or dry run)tall-librarian-49374
05/29/2021, 6:29 AMmake some things publicAll you need is
Unknown
, right?worried-city-86458
05/29/2021, 6:32 AMOutputUtilities.GetValueAsync
so I can directly await an output in my awaitable.Unknown
is used in a few other places so I could refactor those to use it too.tall-librarian-49374
05/29/2021, 6:37 AMUnknown
seems safer to me. I’ll take another look on Monday.worried-city-86458
05/29/2021, 8:24 PMOutput.CreateUnknown
which I now think is a better name