249140242e
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.
174 lines
5.6 KiB
Go
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
|
|
}
|
|
}
|