diff --git a/pkg/engine/plan.go b/pkg/engine/plan.go index a31ff5cc5..63b1d2cbc 100644 --- a/pkg/engine/plan.go +++ b/pkg/engine/plan.go @@ -70,7 +70,7 @@ func plan(info *planContext, opts deployOptions) (*planResult, error) { } // Generate a plan; this API handles all interesting cases (create, update, delete). - plan := deploy.NewPlan(ctx, target, target.Snapshot, source, analyzers) + plan := deploy.NewPlan(ctx, target, target.Snapshot, source, analyzers, opts.DryRun) return &planResult{ Ctx: ctx, Info: info, diff --git a/pkg/resource/deploy/plan.go b/pkg/resource/deploy/plan.go index 257a54d3a..5658348b0 100644 --- a/pkg/resource/deploy/plan.go +++ b/pkg/resource/deploy/plan.go @@ -23,6 +23,7 @@ type Plan struct { olds map[resource.URN]*resource.State // a map of all old resources. source Source // the source of new resources. analyzers []tokens.QName // the analyzers to run during this plan's generation. + preview bool // true if this plan is to be previewed rather than applied. } // NewPlan creates a new deployment plan from a resource snapshot plus a package to evaluate. @@ -34,7 +35,9 @@ type Plan struct { // // Note that a plan uses internal concurrency and parallelism in various ways, so it must be closed if for some reason // a plan isn't carried out to its final conclusion. This will result in cancelation and reclamation of OS resources. -func NewPlan(ctx *plugin.Context, target *Target, prev *Snapshot, source Source, analyzers []tokens.QName) *Plan { +func NewPlan(ctx *plugin.Context, target *Target, prev *Snapshot, source Source, analyzers []tokens.QName, + preview bool) *Plan { + contract.Assert(ctx != nil) contract.Assert(target != nil) contract.Assert(source != nil) @@ -61,6 +64,7 @@ func NewPlan(ctx *plugin.Context, target *Target, prev *Snapshot, source Source, olds: olds, source: source, analyzers: analyzers, + preview: preview, } } diff --git a/pkg/resource/deploy/plan_apply.go b/pkg/resource/deploy/plan_apply.go index f9c1d5187..a39fae5a9 100644 --- a/pkg/resource/deploy/plan_apply.go +++ b/pkg/resource/deploy/plan_apply.go @@ -296,11 +296,14 @@ func (iter *PlanIterator) makeRegisterResouceSteps(e RegisterResourceEvent) ([]S } } + // We only allow unknown property values to be exposed to the provider if we are performing a preview. + allowUnknowns := iter.p.preview + // Ensure the provider is okay with this resource and fetch the inputs to pass to subsequent methods. news, inputs := new.Inputs, new.Inputs if prov != nil { var failures []plugin.CheckFailure - inputs, failures, err = prov.Check(urn, olds, news) + inputs, failures, err = prov.Check(urn, olds, news, allowUnknowns) if err != nil { return nil, err } else if iter.issueCheckErrors(new, urn, failures) { @@ -354,7 +357,7 @@ func (iter *PlanIterator) makeRegisterResouceSteps(e RegisterResourceEvent) ([]S // The properties changed; we need to figure out whether to do an update or replacement. var diff plugin.DiffResult if prov != nil { - if diff, err = prov.Diff(urn, old.ID, oldState, inputs); err != nil { + if diff, err = prov.Diff(urn, old.ID, oldState, inputs, allowUnknowns); err != nil { return nil, err } } @@ -367,7 +370,7 @@ func (iter *PlanIterator) makeRegisterResouceSteps(e RegisterResourceEvent) ([]S // had assumed that we were going to carry them over from the old resource, which is no longer true. if prov != nil { var failures []plugin.CheckFailure - inputs, failures, err = prov.Check(urn, nil, news) + inputs, failures, err = prov.Check(urn, nil, news, allowUnknowns) if err != nil { return nil, err } else if iter.issueCheckErrors(new, urn, failures) { diff --git a/pkg/resource/deploy/plan_test.go b/pkg/resource/deploy/plan_test.go index bd3528187..d1dd2cedc 100644 --- a/pkg/resource/deploy/plan_test.go +++ b/pkg/resource/deploy/plan_test.go @@ -24,7 +24,7 @@ func TestNullPlan(t *testing.T) { assert.Nil(t, err) targ := &Target{Name: tokens.QName("null")} prev := NewSnapshot(targ.Name, Manifest{}, nil) - plan := NewPlan(ctx, targ, prev, NullSource, nil) + plan := NewPlan(ctx, targ, prev, NullSource, nil, false) iter, err := plan.Start(Options{}) assert.Nil(t, err) assert.NotNil(t, iter) @@ -45,7 +45,7 @@ func TestErrorPlan(t *testing.T) { assert.Nil(t, err) targ := &Target{Name: tokens.QName("errs")} prev := NewSnapshot(targ.Name, Manifest{}, nil) - plan := NewPlan(ctx, targ, prev, &errorSource{err: errors.New("ITERATE"), duringIterate: true}, nil) + plan := NewPlan(ctx, targ, prev, &errorSource{err: errors.New("ITERATE"), duringIterate: true}, nil, false) iter, err := plan.Start(Options{}) assert.Nil(t, iter) assert.NotNil(t, err) @@ -60,7 +60,7 @@ func TestErrorPlan(t *testing.T) { assert.Nil(t, err) targ := &Target{Name: tokens.QName("errs")} prev := NewSnapshot(targ.Name, Manifest{}, nil) - plan := NewPlan(ctx, targ, prev, &errorSource{err: errors.New("NEXT"), duringIterate: false}, nil) + plan := NewPlan(ctx, targ, prev, &errorSource{err: errors.New("NEXT"), duringIterate: false}, nil, false) iter, err := plan.Start(Options{}) assert.Nil(t, err) assert.NotNil(t, iter) @@ -216,7 +216,7 @@ func TestBasicCRUDPlan(t *testing.T) { source := NewFixedSource(pkgname, []SourceEvent{newStateA, newStateB, newStateC}) // Next up, create a plan from the new and old, and validate its shape. - plan := NewPlan(ctx, targ, oldsnap, source, nil) + plan := NewPlan(ctx, targ, oldsnap, source, nil, false) // Next, validate the steps and ensure that we see all of the expected ones. Note that there aren't any // dependencies between the steps, so we must validate it in a way that's insensitive of order. @@ -398,7 +398,7 @@ func (prov *testProvider) Configure(vars map[tokens.ModuleMember]string) error { return prov.config(vars) } func (prov *testProvider) Check(urn resource.URN, - olds, news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) { + olds, news resource.PropertyMap, _ bool) (resource.PropertyMap, []plugin.CheckFailure, error) { return prov.check(urn, olds, news) } func (prov *testProvider) Create(urn resource.URN, props resource.PropertyMap) (resource.ID, @@ -406,7 +406,7 @@ func (prov *testProvider) Create(urn resource.URN, props resource.PropertyMap) ( return prov.create(urn, props) } func (prov *testProvider) Diff(urn resource.URN, id resource.ID, - olds resource.PropertyMap, news resource.PropertyMap) (plugin.DiffResult, error) { + olds resource.PropertyMap, news resource.PropertyMap, _ bool) (plugin.DiffResult, error) { return prov.diff(urn, id, olds, news) } func (prov *testProvider) Update(urn resource.URN, id resource.ID, diff --git a/pkg/resource/plugin/provider.go b/pkg/resource/plugin/provider.go index 6df4a818d..df57b1447 100644 --- a/pkg/resource/plugin/provider.go +++ b/pkg/resource/plugin/provider.go @@ -28,9 +28,11 @@ type Provider interface { Configure(vars map[tokens.ModuleMember]string) error // Check validates that the given property bag is valid for a resource of the given type and returns the inputs // that should be passed to successive calls to Diff, Create, or Update for this resource. - Check(urn resource.URN, olds, news resource.PropertyMap) (resource.PropertyMap, []CheckFailure, error) + Check(urn resource.URN, olds, news resource.PropertyMap, + allowUnknowns bool) (resource.PropertyMap, []CheckFailure, error) // Diff checks what impacts a hypothetical update will have on the resource's properties. - Diff(urn resource.URN, id resource.ID, olds resource.PropertyMap, news resource.PropertyMap) (DiffResult, error) + Diff(urn resource.URN, id resource.ID, olds resource.PropertyMap, news resource.PropertyMap, + allowUnknowns bool) (DiffResult, error) // Create allocates a new instance of the provided resource and returns its unique resource.ID. Create(urn resource.URN, news resource.PropertyMap) (resource.ID, resource.PropertyMap, resource.Status, error) // Update updates an existing resource with new values. diff --git a/pkg/resource/plugin/provider_plugin.go b/pkg/resource/plugin/provider_plugin.go index db0e631e8..a671195f7 100644 --- a/pkg/resource/plugin/provider_plugin.go +++ b/pkg/resource/plugin/provider_plugin.go @@ -71,16 +71,18 @@ func (p *provider) Configure(vars map[tokens.ModuleMember]string) error { // Check validates that the given property bag is valid for a resource of the given type. func (p *provider) Check(urn resource.URN, - olds, news resource.PropertyMap) (resource.PropertyMap, []CheckFailure, error) { + olds, news resource.PropertyMap, allowUnknowns bool) (resource.PropertyMap, []CheckFailure, error) { label := fmt.Sprintf("%s.Check(%s)", p.label(), urn) glog.V(7).Infof("%s executing (#olds=%d,#news=%d", label, len(olds), len(news)) - molds, err := MarshalProperties(olds, MarshalOptions{Label: fmt.Sprintf("%s.olds", label)}) + molds, err := MarshalProperties(olds, MarshalOptions{Label: fmt.Sprintf("%s.olds", label), + KeepUnknowns: allowUnknowns}) if err != nil { return nil, nil, err } - mnews, err := MarshalProperties(news, MarshalOptions{Label: fmt.Sprintf("%s.news", label)}) + mnews, err := MarshalProperties(news, MarshalOptions{Label: fmt.Sprintf("%s.news", label), + KeepUnknowns: allowUnknowns}) if err != nil { return nil, nil, err } @@ -99,7 +101,7 @@ func (p *provider) Check(urn resource.URN, var inputs resource.PropertyMap if ins := resp.GetInputs(); ins != nil { inputs, err = UnmarshalProperties(ins, MarshalOptions{ - Label: fmt.Sprintf("%s.inputs", label), RejectUnknowns: true}) + Label: fmt.Sprintf("%s.inputs", label), KeepUnknowns: allowUnknowns, RejectUnknowns: !allowUnknowns}) if err != nil { return nil, nil, err } @@ -117,7 +119,7 @@ func (p *provider) Check(urn resource.URN, // Diff checks what impacts a hypothetical update will have on the resource's properties. func (p *provider) Diff(urn resource.URN, id resource.ID, - olds resource.PropertyMap, news resource.PropertyMap) (DiffResult, error) { + olds resource.PropertyMap, news resource.PropertyMap, allowUnknowns bool) (DiffResult, error) { contract.Assert(urn != "") contract.Assert(id != "") contract.Assert(news != nil) @@ -127,11 +129,12 @@ func (p *provider) Diff(urn resource.URN, id resource.ID, glog.V(7).Infof("%s: executing (#olds=%d,#news=%d)", label, len(olds), len(news)) molds, err := MarshalProperties(olds, MarshalOptions{ - Label: fmt.Sprintf("%s.olds", label), ElideAssetContents: true}) + Label: fmt.Sprintf("%s.olds", label), ElideAssetContents: true, KeepUnknowns: allowUnknowns}) if err != nil { return DiffResult{}, err } - mnews, err := MarshalProperties(news, MarshalOptions{Label: fmt.Sprintf("%s.news", label)}) + mnews, err := MarshalProperties(news, MarshalOptions{Label: fmt.Sprintf("%s.news", label), + KeepUnknowns: allowUnknowns}) if err != nil { return DiffResult{}, err }