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

Propagate inputs to outputs during preview. #3245

Merged
merged 4 commits into from
Sep 30, 2019
Merged

Conversation

pgavlin
Copy link
Member

@pgavlin pgavlin commented Sep 18, 2019

These changes restore a more-correct version of the behavior that was
disabled with #3014. The original implementation of this behavior was
done in the SDKs, which do not have access to the complete inputs for a
resource (in particular, default values filled in by the provider during
Check are not exposed to the SDK). This lack of information meant that
the resolved output values could disagree with the typings present in
a provider SDK. Exacerbating this problem was the fact that unknown
values were dropped entirely, causing undefined values to appear in
unexpected places.

By doing this in the engine and allowing unknown values to be
represented in a first-class manner in the SDK, we can attack both of
these issues.

Although this behavior is not strictly consistent with respect to the
resource model--in an update, a resource's output properties will come
from its provider and may differ from its input properties--this
behavior was present in the product for a fairly long time without
significant issues. In the future, we may be able to improve the
accuracy of resource outputs during a preview by allowing the provider
to dry-run CRUD operations and return partially-known values where
possible.

These changes also allow the Node and Python SDKs to work with unknown values
in a first-class fashion by introducing a handful of new APIs:

  • A new parameter to the apply function that indicates that the
    callback should be run even if the result of the apply contains
    unknown values
  • containsUnknowns and isUnknown, which return true if a value
    either contains nested unknown values or is exactly an unknown value
  • The Unknown type, which represents unknown values

The primary use case for these APIs is to allow nested, properties with
known values to be accessed via the lifted property accessor even when
the containing property is not fully know. A common example of this
pattern is the metadata.name property of a Kubernetes Namespace
object: while other properties of the metadata bag may be unknown,
name is often known. These APIs allow ns.metadata.name to return a
known value in this case.

In order to avoid exposing downlevel SDKs to unknown values--a change
which could break user code by exposing it to unexpected values--a
language SDK must indicate whether or not it supports first-class
unknown values as part of each RegisterResourceRequest.

These changes also allow us to avoid breaking user code with the new
behavior introduced by the prior commit.

Fixes #3190.

@pgavlin
Copy link
Member Author

pgavlin commented Sep 18, 2019

@lukehoban most of these changes are from #3216. The input-propagation changes are all in step.go: https://github.com/pulumi/pulumi/pull/3245/files#diff-674141a8d712fdb099f5fa5e79270d6c

I'll add tests and update the commit message & PR comment tomorrow.

@pgavlin pgavlin marked this pull request as ready for review September 19, 2019 21:55
@pgavlin
Copy link
Member Author

pgavlin commented Sep 19, 2019

Working on test coverage now.

@pgavlin
Copy link
Member Author

pgavlin commented Sep 20, 2019

Okay, unit tests are in. Integration tests for Python and Node are next.

@@ -208,6 +208,8 @@ func (s *CreateStep) Apply(preview bool) (resource.Status, StepCompleteFunc, err
s.new.ID = id
s.new.Outputs = outs
}
} else {
s.new.Outputs = s.new.Inputs
}
Copy link
Contributor

Choose a reason for hiding this comment

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

could we flip the 'if' we're in. i'm a big fan of if case simpleStuff else really complex stuff versus if !case complex stuff else really simple stuff.


const result1 = output1.foo;
assert.equal(await result1.isKnown, true);
assert.equal(await result1.promise(), "foo");
Copy link
Contributor

Choose a reason for hiding this comment

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

awesome.

"""

def contains_unknowns(val: Any) -> bool:
return rpc.contains_unknowns(val)
Copy link
Contributor

Choose a reason for hiding this comment

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

todo: review the python side.

@CyrusNajmabadi
Copy link
Contributor

done with initial pass. still have to review python. zero impl concerns. all my stuff is about doc'ing api/impl for the best long term health of this complex part of the sdk.

// runWithUnknowns requests that `func` is run even if `isKnown` resolves to `false`. This is used to allow
// callers to process fully- or partially-unknown values and return a known result. the output proxy takes
// advantage of this to allow proxied property accesses to return known values even if other properties of
// the containing object are unknown.
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@CyrusNajmabadi
Copy link
Contributor

Everything looks good (though i haven't reviewed python). You can move forward if you want, or you can wait on me to fully sign off once i check that too.

Copy link
Member

@lukehoban lukehoban left a comment

Choose a reason for hiding this comment

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

LGTM.

There's more change here than I expected - and I'm having some trouble grasping the full set of potential impact this could have to make sure we've done the appropriate testing. But all the changes look right in isolation. I think we should try to land and make sure we get this exposed to more real world validation in master over the next week.

pkg/resource/deploy/deploytest/resourcemonitor.go Outdated Show resolved Hide resolved
sdk/nodejs/output.ts Show resolved Hide resolved
sdk/nodejs/output.ts Outdated Show resolved Hide resolved
sdk/nodejs/output.ts Show resolved Hide resolved
pgavlin and others added 4 commits September 30, 2019 10:07
These changes restore a more-correct version of the behavior that was
disabled with #3014. The original implementation of this behavior was
done in the SDKs, which do not have access to the complete inputs for a
resource (in particular, default values filled in by the provider during
`Check` are not exposed to the SDK). This lack of information meant that
the resolved output values could disagree with the typings present in
a provider SDK. Exacerbating this problem was the fact that unknown
values were dropped entirely, causing `undefined` values to appear in
unexpected places.

By doing this in the engine and allowing unknown values to be
represented in a first-class manner in the SDK, we can attack both of
these issues.

Although this behavior is not _strictly_ consistent with respect to the
resource model--in an update, a resource's output properties will come
from its provider and may differ from its input properties--this
behavior was present in the product for a fairly long time without
significant issues. In the future, we may be able to improve the
accuracy of resource outputs during a preview by allowing the provider
to dry-run CRUD operations and return partially-known values where
possible.

Fixes #3190.
These changes allow the Node and Python SDKs to work with unknown values
in a first-class fashion by introducing a handful of new APIs:
- A new parameter to the `apply` function that indicates that the
  callback should be run even if the result of the apply contains
  unknown values
- `containsUnknowns` and `isUnknown`, which return true if a value
  either contains nested unknown values or is exactly an unknown value
- The `Unknown` type, which represents unknown values

The primary use case for these APIs is to allow nested, properties with
known values to be accessed via the lifted property accessor even when
the containing property is not fully know. A common example of this
pattern is the `metadata.name` property of a Kubernetes `Namespace`
object: while other properties of the `metadata` bag may be unknown,
`name` is often known. These APIs allow `ns.metadata.name` to return a
known value in this case.

In order to avoid exposing downlevel SDKs to unknown values--a change
which could break user code by exposing it to unexpected values--a
language SDK must indicate whether or not it supports first-class
unknown values as part of each `RegisterResourceRequest`.

These changes also allow us to avoid breaking user code with the new
behavior introduced by the prior commit.
Co-Authored-By: CyrusNajmabadi <cyrus.najmabadi@gmail.com>
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.

Previews do not reflect the resulting update
3 participants