Omit unknowns in resources in stack outputs during preview. (#3427)

If a stack output includes a `Resource`, we will as of a recent change
always show the output diff, but this diff will potentially include
unknowns, leading to spurious output like:

```
+ namePrefix : output<string>
```

These changes supress these diffs by adding a special key to the POJO
we generate for resources *during preview only* that indicates that the
POJO represents a Pulumi resource, then stripping all adds of unknown
values from diffs for objects marked with that key.

Fixes #3314.
This commit is contained in:
Pat Gavlin 2019-10-30 11:36:03 -07:00 committed by GitHub
parent 9bf688338c
commit c383810bf8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 87 additions and 3 deletions

View file

@ -13,6 +13,9 @@ CHANGELOG
imported by passing the `--force` flag.
[#3422](https://github.com/pulumi/pulumi/pull/3422)
- Omit unknowns in resources in stack outputs during preview.
[#3427](https://github.com/pulumi/pulumi/pull/3427)
## 1.4.0 (2019-10-24)
- `FileAsset` in the Python SDK now accepts anything implementing `os.PathLike` in addition to `str`.

View file

@ -238,6 +238,64 @@ func PrintObject(
}
}
func massageStackPreviewAdd(p resource.PropertyValue) {
switch {
case p.IsArray():
for _, v := range p.ArrayValue() {
massageStackPreviewAdd(v)
}
case p.IsObject():
delete(p.ObjectValue(), "@isPulumiResource")
for _, v := range p.ObjectValue() {
massageStackPreviewAdd(v)
}
}
}
func massageStackPreviewDiff(diff resource.ValueDiff, inResource bool) {
switch {
case diff.Array != nil:
for _, p := range diff.Array.Adds {
massageStackPreviewAdd(p)
}
for _, d := range diff.Array.Updates {
massageStackPreviewDiff(d, inResource)
}
case diff.Object != nil:
massageStackPreviewOutputDiff(diff.Object, inResource)
}
}
// massageStackPreviewOutputDiff removes any adds of unknown values nested inside Pulumi resources present in a stack's
// outputs.
func massageStackPreviewOutputDiff(diff *resource.ObjectDiff, inResource bool) {
if diff == nil {
return
}
_, isResource := diff.Adds["@isPulumiResource"]
if isResource {
delete(diff.Adds, "@isPulumiResource")
for k, v := range diff.Adds {
if v.IsComputed() {
delete(diff.Adds, k)
}
}
}
for _, p := range diff.Adds {
massageStackPreviewAdd(p)
}
for k, d := range diff.Updates {
if isResource && d.New.IsComputed() && !shouldPrintPropertyValue(d.Old, false) {
delete(diff.Updates, k)
} else {
massageStackPreviewDiff(d, inResource)
}
}
}
// GetResourceOutputsPropertiesString prints only those properties that either differ from the input properties or, if
// there is an old snapshot of the resource, differ from the prior old snapshot's output properties.
func GetResourceOutputsPropertiesString(
@ -298,6 +356,12 @@ func GetResourceOutputsPropertiesString(
var outputDiff *resource.ObjectDiff
if step.Old != nil && step.Old.Outputs != nil {
outputDiff = step.Old.Outputs.Diff(outs, IsInternalPropertyKey)
// If this is the root stack type, we want to strip out any nested resource outputs that are not known if
// they have no corresponding output in the old state.
if planning && step.URN.Type() == resource.RootStackType {
massageStackPreviewOutputDiff(outputDiff, false)
}
}
// If we asked to not show-sames, and no outputs changed then don't show anything at all here.

View file

@ -16,7 +16,7 @@ import * as asset from "../asset";
import { getProject, getStack } from "../metadata";
import { Inputs, Output, output, secret } from "../output";
import { ComponentResource, Resource, ResourceTransformation } from "../resource";
import { getRootResource, isQueryMode, setRootResource } from "./settings";
import { getRootResource, isDryRun, isQueryMode, setRootResource } from "./settings";
/**
* rootPulumiStackTypeName is the type name that should be used to construct the root component in the tree of Pulumi
@ -176,7 +176,13 @@ async function massageComplex(prop: any, objectStack: any[]): Promise<any> {
if (Resource.isInstance(prop)) {
// Emit a resource as a normal pojo. But filter out all our internal properties so that
// they don't clutter the display/checkpoint with values not relevant to the application.
return serializeAllKeys(n => !n.startsWith("__"));
//
// In preview only, we mark the POJO with "@isPulumiResource" to indicate that it is derived
// from a resource. This allows the engine to perform resource-specific filtering of unknowns
// from output diffs during a preview. This filtering is not necessary during an update because
// all property values are known.
const pojo = await serializeAllKeys(n => !n.startsWith("__"));
return !isDryRun() ? pojo : { ...pojo, "@isPulumiResource": true };
}
if (prop instanceof Array) {

View file

@ -21,7 +21,7 @@ from inspect import isawaitable
from typing import Callable, Any, Dict, List
from ..resource import ComponentResource, Resource, ResourceTransformation
from .settings import get_project, get_stack, get_root_resource, set_root_resource
from .settings import get_project, get_stack, get_root_resource, is_dry_run, set_root_resource
from .rpc_manager import RPC_MANAGER
from .. import log
@ -149,6 +149,17 @@ def massage(attr: Any, seen: List[Any]):
if isawaitable(attr):
return Output.from_input(attr).apply(lambda v: massage(v, seen))
if isinstance(attr, Resource):
result = massage(attr.__dict__, seen)
# In preview only, we mark the result with "@isPulumiResource" to indicate that it is derived
# from a resource. This allows the engine to perform resource-specific filtering of unknowns
# from output diffs during a preview. This filtering is not necessary during an update because
# all property values are known.
if is_dry_run():
result["@isPulumiResource"] = True
return result
if hasattr(attr, "__dict__"):
# recurse on the dictionary itself. It will be handled above.
return massage(attr.__dict__, seen)