Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix dependency handling #179

Open
wants to merge 14 commits into
base: t0yv0/dependencies-baseline
Choose a base branch
from
Open

Conversation

t0yv0
Copy link
Member

@t0yv0 t0yv0 commented Mar 5, 2025

This change fixes dependency handling. The fix is coarse-grained but important for correctly ordering deletes. The implementation is currently lacking capabilities to track dependencies precisely through the TF black box.

What this does:

  • if dependencies are passed as inputs to a module instance, ModuleState Custom Resource now depends on all these dependencies. This means, in particular, that tofu destroy will now have to complete before the dependencies start getting destroyed themselves. This fixes issues such as Dependency information lost for RDS module depending on AWS resources #234

  • if Pulumi resources depend on any of the module's outputs, they now also will depend on all the dependencies of the module's inputs, they should order their destroy operations accordingly. They also depend on the Component resource itself, and I believe depending on a Component is equivalent in Pulumi to depending on all its children, which should mean they transitively depend on ModuleState as well.

How this works: unfortunately plumbing is not very friendly at the moment for preserving first-class Output values and they tend to get lost. The fixes remedy that just enough to grab these Output values in key locations. Then the code harvests all dependencies from first-class Output values and re-applies them to every output returned from Construct to broadcast them. A test is added that asserts on Pulumi statefile shapes to verify these dependencies.

Fixes #151
Fixes #234

Questions:

  • Does the code need to process ConstructRequest.{Dependencies, PropertyDependencies} or it is obsoleted by first-class Output values?
  • Do we need to copy up the URN of ModuleState resource and add it to the broadcast of module outputs, so that resources that consume the outputs of the Component resource not only depend on the Component and indirectly on ModuleState because it is the child of the Component, but also directly depend on the ModuleState?
  • Do dependencies work through first-class providers? That might need further work around Configure; Test: dependencies through explicit provider configuration #187 is tracking

@@ -62,6 +66,11 @@ func TestUnmarhsalPropertiesThreadThroughRegisterResource(t *testing.T) {
Element: resource.NewStringProperty(""),
}),
},
inputsReceived: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unknowns get normalized to Outputs. That's interesting.

@@ -70,15 +79,20 @@ func TestUnmarhsalPropertiesThreadThroughRegisterResource(t *testing.T) {
Element: resource.NewStringProperty("SECRET"),
}),
},
inputsReceived: resource.PropertyMap{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Secrets get normalized to outputs as well.

},
{
name: "array",
inputs: resource.PropertyMap{
"foo": resource.NewArrayProperty([]resource.PropertyValue{
resource.NewStringProperty("foo"),
resource.NewSecretProperty(&resource.Secret{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just simplify this since secrets get normalized to outputs, avoid them.

@@ -87,9 +101,74 @@ func TestUnmarhsalPropertiesThreadThroughRegisterResource(t *testing.T) {
inputs: resource.PropertyMap{
"foo": resource.NewObjectProperty(resource.PropertyMap{
"p1": resource.NewStringProperty("foo"),
"p2": resource.NewSecretProperty(&resource.Secret{
Element: resource.NewStringProperty("SECRET"),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

@t0yv0
Copy link
Member Author

t0yv0 commented Mar 20, 2025

This change is part of the following stack:

Change managed by git-spice.

@t0yv0 t0yv0 changed the base branch from main to t0yv0/dependencies-baseline March 20, 2025 21:03
@t0yv0 t0yv0 requested a review from guineveresaenger March 20, 2025 21:04
@t0yv0 t0yv0 requested a review from corymhall March 20, 2025 21:43
@t0yv0
Copy link
Member Author

t0yv0 commented Mar 20, 2025

@justinvp somehow can't tag you as reviewer, strange GH features.

@t0yv0 t0yv0 changed the title Fix first-class output turnaround Fix dependency handling Mar 20, 2025
@t0yv0 t0yv0 marked this pull request as ready for review March 20, 2025 22:47
@t0yv0 t0yv0 requested a review from justinvp March 20, 2025 22:48
Copy link
Contributor

@guineveresaenger guineveresaenger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've verified that this indeed fixes #234.

Do we have confidence that turning Unknowns and Secrets into OutputProperty is safe?

@t0yv0 t0yv0 requested a review from guineveresaenger March 20, 2025 23:24
@t0yv0
Copy link
Member Author

t0yv0 commented Mar 20, 2025

Do we have confidence that turning Unknowns and Secrets into OutputProperty is safe?

Those are semantically equivalent. Also, secretes + unknowns should be under test in this repo AFAIK, though maybe something somewhere is lacking in rigor.

Copy link
Contributor

@guineveresaenger guineveresaenger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really appreciate the comments here, thank you for letting me nit pick them.

Copy link
Member

@justinvp justinvp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Seems worth merging as-is.

To answer some of your questions...


Does the code need to process ConstructRequest.{Dependencies, PropertyDependencies} or it is obsoleted by first-class Output values?

Just a note that "PropertyDependencies" is actually called inputDependencies on ConstructRequest, which I assume is what you mean. Yes, it is obsoleted by first-class Output values. Since you're handling that now, you can ignore ConstructRequest.InputDependencies.

Dependencies represents the ResourceOptions.DependsOn. You will want to handle it. You have this code:

return pulumiprovider.Construct(ctx, req, s.hostClient.EngineConn(), func(
ctx *pulumi.Context, typ, name string,
_ pulumiprovider.ConstructInputs,
_ pulumi.ResourceOption,
) (*pulumiprovider.ConstructResult, error) {

The ResourceOption being passed into the callback (currently ignored) will have the DependsOn filled in for you based on the ConstructRequest.Dependencies, via https://github.com/pulumi/pulumi/blob/6bb0e44f1bb9119435d9ec02217f5ebba9c4b9f3/sdk/go/pulumi/provider.go#L117-L119

You're going to want to think about passing that ResourceOption to your NewModuleComponentResource inside Construct, so the actual component gets the resource options that the user specified. I think it's a matter of passing it as the last arg of NewModuleComponentResource.


if Pulumi resources depend on any of the module's outputs, they now also will depend on all the dependencies of the module's inputs

Do we need to copy up the URN of ModuleState resource and add it to the broadcast of module outputs, so that resources that consume the outputs of the Component resource not only depend on the Component and indirectly on ModuleState because it is the child of the Component, but also directly depend on the ModuleState?

It may be sufficient to only have the module's outputs depend on ModuleState, if ModuleState depends on all the inputs. Then you might not have to propagate all the input dependencies to the module outputs.

@t0yv0
Copy link
Member Author

t0yv0 commented Mar 21, 2025

Test non-deterministic re-orders the deps sigh. I'll fix up.

      	resource.PropertyKey("max"): {
        		urn.URN("urn:pulumi:test::ts-dep-tester::randmod:index:Module::myrandmod"),
        		urn.URN("urn:pulumi:test::ts-dep-tester::random:index/randomInteger:RandomInteger::seed"),
        		urn.URN("urn:pulumi:test::ts-dep-tester::randmod:index:Module::myrandmod"),
        	},

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support Output in UnmarshalPropertyValue
3 participants