pulumi/pkg/resource/deploy/source.go
Pat Gavlin 137fd54f1c
Propagate inputs to outputs during preview. (#3327)
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 introduce new APIs in the Node and Python SDKs
that work with unknown values in a first-class fashion:
- 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.
2019-11-11 12:09:34 -08:00

131 lines
5 KiB
Go

// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package deploy
import (
"context"
"io"
pbempty "github.com/golang/protobuf/ptypes/empty"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/result"
pulumirpc "github.com/pulumi/pulumi/sdk/proto/go"
)
// A ProviderSource allows a Source to lookup provider plugins.
type ProviderSource interface {
// GetProvider fetches the provider plugin for the given reference.
GetProvider(ref providers.Reference) (plugin.Provider, bool)
}
// A Source can generate a new set of resources that the planner will process accordingly.
type Source interface {
io.Closer
// Project returns the package name of the Pulumi project we are obtaining resources from.
Project() tokens.PackageName
// Info returns a serializable payload that can be used to stamp snapshots for future reconciliation.
Info() interface{}
// Iterate begins iterating the source. Error is non-nil upon failure; otherwise, a valid iterator is returned.
Iterate(ctx context.Context, opts Options, providers ProviderSource) (SourceIterator, result.Result)
}
// A SourceIterator enumerates the list of resources that a source has to offer and tracks associated state.
type SourceIterator interface {
io.Closer
// Next returns the next event from the source.
Next() (SourceEvent, result.Result)
}
// SourceResourceMonitor directs resource operations from the `Source` to various resource
// providers.
type SourceResourceMonitor interface {
// NOTE: This interface does not implement pulumirpc.ResourceMonitorClient because the eval and
// query implementations of `Source` do not implement precisely the same signatures.
Address() string
Cancel() error
Invoke(ctx context.Context, req *pulumirpc.InvokeRequest) (*pulumirpc.InvokeResponse, error)
ReadResource(ctx context.Context,
req *pulumirpc.ReadResourceRequest) (*pulumirpc.ReadResourceResponse, error)
RegisterResource(ctx context.Context,
req *pulumirpc.RegisterResourceRequest) (*pulumirpc.RegisterResourceResponse, error)
RegisterResourceOutputs(ctx context.Context,
req *pulumirpc.RegisterResourceOutputsRequest) (*pbempty.Empty, error)
}
// SourceEvent is an event associated with the enumeration of a plan. It is an intent expressed by the source
// program, and it is the responsibility of the engine to make it so.
type SourceEvent interface {
event()
}
// RegisterResourceEvent is a step that asks the engine to provision a resource.
type RegisterResourceEvent interface {
SourceEvent
// Goal returns the goal state for the resource object that was allocated by the program.
Goal() *resource.Goal
// Done indicates that we are done with this step. It must be called to perform cleanup associated with the step.
Done(result *RegisterResult)
}
// RegisterResult is the state of the resource after it has been registered.
type RegisterResult struct {
State *resource.State // the resource state.
}
// RegisterResourceOutputsEvent is an event that asks the engine to complete the provisioning of a resource.
type RegisterResourceOutputsEvent interface {
SourceEvent
// URN is the resource URN that this completion applies to.
URN() resource.URN
// Outputs returns a property map of output properties to add to a resource before completing.
Outputs() resource.PropertyMap
// Done indicates that we are done with this step. It must be called to perform cleanup associated with the step.
Done()
}
// ReadResourceEvent is an event that asks the engine to read the state of a resource that already exists.
type ReadResourceEvent interface {
SourceEvent
// ID is the requested ID of this read.
ID() resource.ID
// Name is the requested name of this read.
Name() tokens.QName
// Type is type of the resource being read.
Type() tokens.Type
// Provider is a reference to the provider instance to use for this read.
Provider() string
// Parent is the parent resource of the resource being read.
Parent() resource.URN
// Properties is the property bag that will be passed to Read as search parameters.
Properties() resource.PropertyMap
// Dependencies returns the list of URNs upon which this read depends.
Dependencies() []resource.URN
// Done indicates that we are done with this event.
Done(result *ReadResult)
// The names of any additional outputs that should be treated as secrets.
AdditionalSecretOutputs() []resource.PropertyKey
}
type ReadResult struct {
State *resource.State
}