pulumi/pkg/engine/query.go
Pat Gavlin 249140242e
Add support for provider-side preview. (#5443)
These changes add support for provider-side previews of create and
update operations, which allows resource providers to supply output
property values for resources that are being created or updated during a
preview.

If a plugin supports provider-side preview, its create/update methods
will be invoked during previews with the `preview` property set to true.
It is the responsibility of the provider to fill in any output
properties that are known before returning. It is a best practice for
providers to only fill in property values that are guaranteed to be
identical if the preview were instead an update (i.e. only those output
properties whose values can be conclusively determined without
actually performing the create/update operation should be populated).
Providers that support previews must accept unknown values in their
create and update methods.

If a plugin does not support provider-side preview, the inputs to a
create or update operation will be propagated to the outputs as they are
today.

Fixes #4992.
2020-10-09 13:13:55 -07:00

174 lines
5.6 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 engine
import (
"context"
"github.com/opentracing/opentracing-go"
"github.com/pulumi/pulumi/pkg/v2/resource/deploy"
"github.com/pulumi/pulumi/sdk/v2/go/common/diag"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/fsutil"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/logging"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/result"
)
type QueryOptions struct {
Events eventEmitter // the channel to write events from the engine to.
Diag diag.Sink // the sink to use for diag'ing.
StatusDiag diag.Sink // the sink to use for diag'ing status messages.
host plugin.Host // the plugin host to use for this query.
pwd, main string
plugctx *plugin.Context
tracingSpan opentracing.Span
}
func Query(ctx *Context, q QueryInfo, opts UpdateOptions) result.Result {
contract.Require(q != nil, "update")
contract.Require(ctx != nil, "ctx")
defer func() { ctx.Events <- cancelEvent() }()
tracingSpan := func(opName string, parentSpan opentracing.SpanContext) opentracing.Span {
// Create a root span for the operation
opts := []opentracing.StartSpanOption{}
if opName != "" {
opts = append(opts, opentracing.Tag{Key: "operation", Value: opName})
}
if parentSpan != nil {
opts = append(opts, opentracing.ChildOf(parentSpan))
}
return opentracing.StartSpan("pulumi-query", opts...)
}("query", ctx.ParentSpan)
defer tracingSpan.Finish()
emitter, err := makeQueryEventEmitter(ctx.Events)
if err != nil {
return result.FromError(err)
}
defer emitter.Close()
// First, load the package metadata and the deployment target in preparation for executing the package's program
// and creating resources. This includes fetching its pwd and main overrides.
diag := newEventSink(emitter, false)
statusDiag := newEventSink(emitter, true)
proj := q.GetProject()
contract.Assert(proj != nil)
pwd, main, plugctx, err := ProjectInfoContext(&Projinfo{Proj: proj, Root: q.GetRoot()},
opts.host, nil, diag, statusDiag, false, tracingSpan)
if err != nil {
return result.FromError(err)
}
defer plugctx.Close()
return query(ctx, q, QueryOptions{
Events: emitter,
Diag: diag,
StatusDiag: statusDiag,
host: opts.host,
pwd: pwd,
main: main,
plugctx: plugctx,
tracingSpan: tracingSpan,
})
}
func newQuerySource(cancel context.Context, client deploy.BackendClient, q QueryInfo,
opts QueryOptions) (deploy.QuerySource, error) {
allPlugins, defaultProviderVersions, err := installPlugins(q.GetProject(), opts.pwd, opts.main,
nil, opts.plugctx)
if err != nil {
return nil, err
}
// Once we've installed all of the plugins we need, make sure that all analyzers and language plugins are
// loaded up and ready to go. Provider plugins are loaded lazily by the provider registry and thus don't
// need to be loaded here.
const kinds = plugin.LanguagePlugins
if err := ensurePluginsAreLoaded(opts.plugctx, allPlugins, kinds); err != nil {
return nil, err
}
if opts.tracingSpan != nil {
cancel = opentracing.ContextWithSpan(cancel, opts.tracingSpan)
}
// If that succeeded, create a new source that will perform interpretation of the compiled program.
// TODO[pulumi/pulumi#88]: we are passing `nil` as the arguments map; we need to allow a way to pass these.
return deploy.NewQuerySource(cancel, opts.plugctx, client, &deploy.EvalRunInfo{
Proj: q.GetProject(),
Pwd: opts.pwd,
Program: opts.main,
}, defaultProviderVersions, nil)
}
func query(ctx *Context, q QueryInfo, opts QueryOptions) result.Result {
// Make the current working directory the same as the program's, and restore it upon exit.
done, chErr := fsutil.Chdir(opts.plugctx.Pwd)
if chErr != nil {
return result.FromError(chErr)
}
defer done()
if res := runQuery(ctx, q, opts); res != nil {
if res.IsBail() {
return res
}
return result.Errorf("an error occurred while running the query: %v", res.Error())
}
return nil
}
func runQuery(cancelCtx *Context, q QueryInfo, opts QueryOptions) result.Result {
ctx, cancelFunc := context.WithCancel(context.Background())
contract.Ignore(cancelFunc)
src, err := newQuerySource(ctx, cancelCtx.BackendClient, q, opts)
if err != nil {
return result.FromError(err)
}
// Set up a goroutine that will signal cancellation to the plan's plugins if the caller context
// is cancelled.
go func() {
<-cancelCtx.Cancel.Canceled()
logging.V(4).Infof("engine.runQuery(...): signalling cancellation to providers...")
cancelFunc()
cancelErr := opts.plugctx.Host.SignalCancellation()
if cancelErr != nil {
logging.V(4).Infof("engine.runQuery(...): failed to signal cancellation to providers: %v", cancelErr)
}
}()
done := make(chan result.Result)
go func() {
done <- src.Wait()
}()
// Block until query completes.
select {
case <-cancelCtx.Cancel.Terminated():
return result.WrapIfNonNil(cancelCtx.Cancel.TerminateErr())
case res := <-done:
return res
}
}