Validate outputs don't change

This commit is contained in:
Fraser Waters 2021-11-23 13:26:11 +00:00
parent 76f3e938aa
commit 202c0f0ae4
3 changed files with 73 additions and 5 deletions

View file

@ -3379,3 +3379,50 @@ func TestPlannedUpdateChangedStack(t *testing.T) {
})
assert.Equal(t, expected, snap.Resources[1].Outputs)
}
func TestPlannedOutputChanges(t *testing.T) {
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
return &deploytest.Provider{
CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
return resource.ID("created-id-" + urn.Name()), news, resource.StatusOK, nil
},
}, nil
}),
}
outs := resource.NewPropertyMapFromMap(map[string]interface{}{
"foo": "bar",
"frob": "baz",
})
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
urn, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{})
assert.NoError(t, err)
monitor.RegisterResourceOutputs(urn, outs)
return nil
})
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{Host: host},
}
project := p.GetProject()
// Create an initial plan to create resA and the outputs
plan, res := TestOp(Update).Plan(project, p.GetTarget(t, nil), p.Options, p.BackendClient, nil)
assert.NotNil(t, plan)
assert.Nil(t, res)
// Now change the runtime to not return property "frob", this should error
outs = resource.NewPropertyMapFromMap(map[string]interface{}{
"foo": "bar",
})
p.Options.Plan = ClonePlan(t, plan)
snap, res := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
assert.NotNil(t, snap)
assert.NotNil(t, res)
}

View file

@ -385,8 +385,7 @@ func (ex *deploymentExecutor) handleSingleEvent(event SourceEvent) result.Result
steps, res = ex.stepGen.GenerateReadSteps(e)
case RegisterResourceOutputsEvent:
logging.V(4).Infof("deploymentExecutor.handleSingleEvent(...): received register resource outputs")
ex.stepExec.ExecuteRegisterResourceOutputs(e)
return nil
return ex.stepExec.ExecuteRegisterResourceOutputs(e)
}
if res != nil {

View file

@ -25,6 +25,7 @@ import (
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
)
const (
@ -147,7 +148,7 @@ func (se *stepExecutor) ExecuteParallel(antichain antichain) completionToken {
}
// ExecuteRegisterResourceOutputs services a RegisterResourceOutputsEvent synchronously on the calling goroutine.
func (se *stepExecutor) ExecuteRegisterResourceOutputs(e RegisterResourceOutputsEvent) {
func (se *stepExecutor) ExecuteRegisterResourceOutputs(e RegisterResourceOutputsEvent) result.Result {
// Look up the final state in the pending registration list.
urn := e.URN()
value, has := se.pendingNews.Load(urn)
@ -160,7 +161,27 @@ func (se *stepExecutor) ExecuteRegisterResourceOutputs(e RegisterResourceOutputs
outs := e.Outputs()
se.log(synchronousWorkerID,
"registered resource outputs %s: old=#%d, new=#%d", urn, len(reg.New().Outputs), len(outs))
reg.New().Outputs = e.Outputs()
reg.New().Outputs = outs
// If a plan is present check that these outputs match what we recorded before
if se.deployment.plan != nil {
resourcePlan, ok := se.deployment.plan[urn]
if !ok {
return result.FromError(fmt.Errorf("no plan for resource %v", urn))
} else {
if diffs, has := resourcePlan.Outputs.DiffIncludeUnknowns(outs); has {
return result.FromError(fmt.Errorf("resource violates plan: %v", diffs))
}
}
}
// Save these new outputs to the plan
if resourcePlan, ok := se.deployment.newPlans.get(urn); ok {
resourcePlan.Outputs = outs
} else {
return result.FromError(fmt.Errorf("this should already have a plan from when we called register resources"))
}
// If there is an event subscription for finishing the resource, execute them.
if e := se.opts.Events; e != nil {
if eventerr := e.OnResourceOutputs(reg); eventerr != nil {
@ -176,10 +197,11 @@ func (se *stepExecutor) ExecuteRegisterResourceOutputs(e RegisterResourceOutputs
diagMsg := diag.RawMessage(reg.URN(), outErr.Error())
se.deployment.Diag().Errorf(diagMsg)
se.cancelDueToError()
return
return nil
}
}
e.Done()
return nil
}
// Errored returns whether or not this step executor saw a step whose execution ended in failure.