Is it possible to create stack outputs dynamically...
# dotnet
p
Is it possible to create stack outputs dynamically? We would like the infrastructure to be entirely declarative, so the necessity of having to imperatively assign to output properties within the stack is problematic. Basically we would need something like this:
Copy code
public sealed class AzureAlertStack : Stack
{
    public AzureAlertStack()
    {
        var alertId = CreateAlert(...);
        RegisterOutput("AlertId", alertId);
    }
}
instead of
Copy code
public sealed class AzureAlertStack : Stack
{
    [Output] public Output<string> AlertId { get; set; }

    public AzureAlertStack()
    {
        AlertId = CreateAlert(...);
    }
}
p
Maybe
this.RegisterOutputs(IDictionary<string, object?> )
(plural)
p
Yeah, I tried that, but got this error:
b
I believe the property outputs are only when you derive from Pulumi.Stack, as a way to make it simpler. You could just not derive from pulumi stack and let pulumi handle that in the background Then your root component resource could use register outputs
I believe you’re seeing that error because pulumi calls register outputs automatically on Pulumi.Stack so it is being called twice.
t
p
Thanks @tall-librarian-49374, returning the dictionary seems to work.
@tall-librarian-49374 It's working non-deterministically...here I ran
pulumi up
twice. The first time, it showed me the resources to be created in the preview, but then actually did nothing. The second time, it didn't show any resources to be created in the preview, but then actually created them.
t
Could you share your code?
p
Copy code
public sealed class AzureFeaturePulumiAlertStack : Stack
{
    public static Dictionary<string, Output<string>> Run()
    {
        var resources = new AzureFeaturePulumiAlerts();
        var stackReferences = GetStackReferences(resources.AllMetricAlerts.Values);

        var metricAlertOutputs = resources.AllMetricAlerts.ToDictionary(
            p => p.Key,
            p => CreateMetricAlert(p.Value.Resource, stackReferences)
        );

        return metricAlertOutputs.ToDictionary(
            p => p.Key,
            p => p.Value.Apply(a => a.Id)
        );
    }

    private static DataCube2<string, string, Output<object>> GetStackReferences(IEnumerable<MewsResource<MetricAlert>> alerts)
    {
        var dependencies = alerts.Select(a => (StackName: a.Resource.TargetResource.StackName, OutputName: a.Resource.TargetResource.OutputName)).Distinct();
        return dependencies.ToDataCube(
            t => t.StackName,
            t => t.OutputName,
            t => new StackReference($"mews-admin/Mews.Server.Environments/{t.StackName}").GetOutput(t.OutputName)
        );
    }

    private static Output<Pulumi.AzureNative.Insights.MetricAlert> CreateMetricAlert(MetricAlert alert, DataCube2<string, string, Output<object>> stackReferences)
    {
        var targetResourceId = stackReferences.Get(alert.TargetResource.StackName, alert.TargetResource.OutputName).Get(_ => new Exception($"The stack reference for {alert.TargetResource.StackName}/{alert.TargetResource.OutputName} does not exist."));
        return targetResourceId.Apply(id => CreateMetricAlert(alert, id.ToString()));
    }

    private static Pulumi.AzureNative.Insights.MetricAlert CreateMetricAlert(MetricAlert alert, string scopeId)
    {
        return new Pulumi.AzureNative.Insights.MetricAlert(...);
    }
}

public class Program
{
    public static Task<int> Main()
    {
        var stackName = GetStackName();
        if (stackName.StartsWith("AzureFeaturePulumiAlertStack"))
        {
            return Deployment.RunAsync(() => AzureFeaturePulumiAlertStack.Run());
        }

        throw new ApplicationException("No cases matched the stack name.");
    }
}
It looks like it sometimes waits for the resources to be created and sometimes it doesn't. I didn't notice this problem when I was using the standard way of assigning to
Output<string>
properties directly.
t
I think you are using a wrong overload of `RunAsync`:
RunAsync(Action action)
Your dictionary is not used. You should use
RunAsync(Func<IDictionary<string, object?>> func)
instead
Change your
Run
to be
IDictionary<string, object> Run()
(btw, no need to derive from
Stack
in this case)
p
Looks good Mikhail, thanks! I still wonder why it was working intermittently before.
t
Because your callbacks where not awaited by the engine so there was a race condition
👌 1
w
So is there no way to derive from
Stack
where it's instantiated via the service provider (
PulumiFn.Create(ServiceProvider, info.StackType)
) and call
RegisterOutputs
?
Copy code
stdout: Updating (pharos/alpha)

View Live: <https://app.pulumi.com/pharos/aws-eks/alpha/updates/43>


 +  pulumi:pulumi:Stack aws-eks-alpha creating
 +  pulumi:providers:aws alpha-aws creating
 +  pulumi:providers:aws alpha-aws created
 +  aws:iam:Role alpha-k8s-full-access creating

stderr: panic: fatal: An assertion has failed: cannot complete a resource
'urn:pulumi:alpha::aws-eks::pulumi:pulumi:Stack::aws-eks-alpha' whose registration isn't pending

goroutine 69 [running]:
<http://github.com/pulumi/pulumi/sdk/v3/go/common/util/contract.failfast(...)|github.com/pulumi/pulumi/sdk/v3/go/common/util/contract.failfast(...)>
        D:/a/pulumi/pulumi/src/github.com/pulumi/pulumi/sdk/go/common/util/contract/failfast.go:23
<http://github.com/pulumi/pulumi/sdk/v3/go/common/util/contract.Assertf(0xc00021c100|github.com/pulumi/pulumi/sdk/v3/go/common/util/contract.Assertf(0xc00021c100>, 0x221fa93, 0x40, 0xc000577948, 0x1, 0x1)
        D:/a/pulumi/pulumi/src/github.com/pulumi/pulumi/sdk/go/common/util/contract/assert.go:33 +0x1ad
<http://github.com/pulumi/pulumi/pkg/v3/resource/deploy.(*stepExecutor).ExecuteRegisterResourceOutputs(0xc000366b00|github.com/pulumi/pulumi/pkg/v3/resource/deploy.(*stepExecutor).ExecuteRegisterResourceOutputs(0xc000366b00>,
0x21256188c30, 0xc0012fe360)
        D:/a/pulumi/pulumi/src/github.com/pulumi/pulumi/pkg/resource/deploy/step_executor.go:154 +0x165
<http://github.com/pulumi/pulumi/pkg/v3/resource/deploy.(*deploymentExecutor).handleSingleEvent(0xc0004ae918|github.com/pulumi/pulumi/pkg/v3/resource/deploy.(*deploymentExecutor).handleSingleEvent(0xc0004ae918>, 0x25272c0,
0xc0012fe360, 0x0, 0x2)
        D:/a/pulumi/pulumi/src/github.com/pulumi/pulumi/pkg/resource/deploy/deployment_executor.go:378 +0x318
<http://github.com/pulumi/pulumi/pkg/v3/resource/deploy.(*deploymentExecutor).Execute.func3(0xc0008c1c80|github.com/pulumi/pulumi/pkg/v3/resource/deploy.(*deploymentExecutor).Execute.func3(0xc0008c1c80>, 0xc0004ae918,
0xc000b9dc10, 0x2555b70, 0xc0009e7180, 0x0, 0x0, 0x2555c18, 0xc000a9e270, 0x0, ...)
        D:/a/pulumi/pulumi/src/github.com/pulumi/pulumi/pkg/resource/deploy/deployment_executor.go:242 +0x215
<http://github.com/pulumi/pulumi/pkg/v3/resource/deploy.(*deploymentExecutor).Execute(0xc0004ae918|github.com/pulumi/pulumi/pkg/v3/resource/deploy.(*deploymentExecutor).Execute(0xc0004ae918>, 0x2555c18, 0xc000a9e270,
0x21255bd6318, 0xc0001e54a0, 0x7fffffff, 0x0, 0x31e1f80, 0x0, 0x0, ...)
        D:/a/pulumi/pulumi/src/github.com/pulumi/pulumi/pkg/resource/deploy/deployment_executor.go:258 +0x805
<http://github.com/pulumi/pulumi/pkg/v3/resource/deploy.(*Deployment).Execute(...)|github.com/pulumi/pulumi/pkg/v3/resource/deploy.(*Deployment).Execute(...)>
        D:/a/pulumi/pulumi/src/github.com/pulumi/pulumi/pkg/resource/deploy/deployment.go:405
<http://github.com/pulumi/pulumi/pkg/v3/engine.(*deployment).run.func1(0x2560650|github.com/pulumi/pulumi/pkg/v3/engine.(*deployment).run.func1(0x2560650>, 0xc0001e54a0, 0xc000a80280, 0x2555c18,
0xc000a9e270, 0xc000a9e200, 0xc000b9db90, 0xc0008c17a0)
        D:/a/pulumi/pulumi/src/github.com/pulumi/pulumi/pkg/engine/deployment.go:252 +0x2a7
created by <http://github.com/pulumi/pulumi/pkg/v3/engine.(*deployment).run|github.com/pulumi/pulumi/pkg/v3/engine.(*deployment).run>
        D:/a/pulumi/pulumi/src/github.com/pulumi/pulumi/pkg/engine/deployment.go:237 +0x314


   at Pulumi.Automation.Commands.LocalPulumiCmd.RunAsyncInner(IEnumerable`1 args, String workingDir, IDictionary`2
additionalEnv, Action`1 onStandardOutput, Action`1 onStandardError, EventLogFile eventLogFile, CancellationToken
cancellationToken) in /_/sdk/dotnet/Pulumi.Automation/Commands/LocalPulumiCmd.cs:line 89
   at Pulumi.Automation.Commands.LocalPulumiCmd.RunAsync(IEnumerable`1 args, String workingDir, IDictionary`2
additionalEnv, Action`1 onStandardOutput, Action`1 onStandardError, Action`1 onEngineEvent, CancellationToken
cancellationToken) in /_/sdk/dotnet/Pulumi.Automation/Commands/LocalPulumiCmd.cs:line 45
   at Pulumi.Automation.Workspace.RunStackCommandAsync(String stackName, IEnumerable`1 args, Action`1 onStandardOutput,
Action`1 onStandardError, Action`1 onEngineEvent, CancellationToken cancellationToken) in
/_/sdk/dotnet/Pulumi.Automation/Workspace.cs:line 288
   at Pulumi.Automation.WorkspaceStack.RunCommandAsync(IEnumerable`1 args, Action`1 onStandardOutput, Action`1
onStandardError, Action`1 onEngineEvent, CancellationToken cancellationToken) in
/_/sdk/dotnet/Pulumi.Automation/WorkspaceStack.cs:line 627
   at Pulumi.Automation.WorkspaceStack.UpAsync(UpOptions options, CancellationToken cancellationToken) in
/_/sdk/dotnet/Pulumi.Automation/WorkspaceStack.cs:line 282
   at Pulumi.Automation.WorkspaceStack.UpAsync(UpOptions options, CancellationToken cancellationToken) in
/_/sdk/dotnet/Pulumi.Automation/WorkspaceStack.cs:line 300
   at Pharos.Gemini.DeployCommand.OnExecuteAsync(CommandContext context, Settings settings) in
D:\Devel\Mps\devops-gemini-pulumi\Gemini\DeployCommand.cs:line 89
   at Pharos.Gemini.AsyncCommandBase`1.ExecuteAsync(CommandContext context, TSettings settings) in
D:\Devel\Mps\devops-gemini-pulumi\Gemini\AsyncCommandBase.cs:line 22
D:\Devel\Mps\devops-gemini-pulumi\Gemini\bin\Debug\gemini.exe (process 15224) exited with code -1.
Is there a reason why this should not be allowed?
Ideally both places could register outputs, so could use properties and dynamic registration. Failing that, could let derived stack disable default property outputs registration somehow.
Thoughts, @tall-librarian-49374 or anyone else?
126 Views