[engine] Clear pending operations with refresh.
Just what it says on the tin. This is implemented by moving the check for pending operations in the last statefile into the deployment executor and making it conditional on whether or not a refresh is being performed (either via `pulumi refresh` or `pulumi up -r`). Because pending operations are not carried over from the base statefile, this has the effect of clearing pending operations if a refresh is performed. Fixes #4265.
This commit is contained in:
parent
3e2f36548e
commit
e07c2c2c21
|
@ -538,6 +538,73 @@ func TestPreviewWithPendingOperations(t *testing.T) {
|
|||
assert.EqualError(t, res.Error(), deploy.PlanPendingOperationsError{}.Error())
|
||||
}
|
||||
|
||||
// Tests that a refresh works for a stack with pending operations.
|
||||
func TestRefreshWithPendingOperations(t *testing.T) {
|
||||
p := &TestPlan{}
|
||||
|
||||
const resType = "pkgA:m:typA"
|
||||
urnA := p.NewURN(resType, "resA", "")
|
||||
|
||||
newResource := func(urn resource.URN, id resource.ID, delete bool, dependencies ...resource.URN) *resource.State {
|
||||
return &resource.State{
|
||||
Type: urn.Type(),
|
||||
URN: urn,
|
||||
Custom: true,
|
||||
Delete: delete,
|
||||
ID: id,
|
||||
Inputs: resource.PropertyMap{},
|
||||
Outputs: resource.PropertyMap{},
|
||||
Dependencies: dependencies,
|
||||
}
|
||||
}
|
||||
|
||||
old := &deploy.Snapshot{
|
||||
PendingOperations: []resource.Operation{{
|
||||
Resource: newResource(urnA, "0", false),
|
||||
Type: resource.OperationTypeUpdating,
|
||||
}},
|
||||
Resources: []*resource.State{
|
||||
newResource(urnA, "0", false),
|
||||
},
|
||||
}
|
||||
|
||||
loaders := []*deploytest.ProviderLoader{
|
||||
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
||||
return &deploytest.Provider{}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
||||
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
||||
assert.NoError(t, err)
|
||||
return nil
|
||||
})
|
||||
|
||||
op := TestOp(Update)
|
||||
options := UpdateOptions{Host: deploytest.NewPluginHost(nil, nil, program, loaders...)}
|
||||
project, target := p.GetProject(), p.GetTarget(old)
|
||||
|
||||
// Without refreshing, an update should fail.
|
||||
_, res := op.Run(project, target, options, false, nil, nil)
|
||||
assertIsErrorOrBailResult(t, res)
|
||||
assert.EqualError(t, res.Error(), deploy.PlanPendingOperationsError{}.Error())
|
||||
|
||||
// With a refresh, the update should succeed.
|
||||
withRefresh := options
|
||||
withRefresh.Refresh = true
|
||||
new, res := op.Run(project, target, withRefresh, false, nil, nil)
|
||||
assert.Nil(t, res)
|
||||
assert.Len(t, new.PendingOperations, 0)
|
||||
|
||||
// Similarly, the update should succeed if performed after a separate refresh.
|
||||
new, res = TestOp(Refresh).Run(project, target, options, false, nil, nil)
|
||||
assert.Nil(t, res)
|
||||
assert.Len(t, new.PendingOperations, 0)
|
||||
|
||||
_, res = op.Run(project, p.GetTarget(new), options, false, nil, nil)
|
||||
assert.Nil(t, res)
|
||||
}
|
||||
|
||||
// Tests that a failed partial update causes the engine to persist the resource's old inputs and new outputs.
|
||||
func TestUpdatePartialFailure(t *testing.T) {
|
||||
loaders := []*deploytest.ProviderLoader{
|
||||
|
|
|
@ -270,10 +270,6 @@ func buildResourceMap(prev *Snapshot, preview bool) ([]*resource.State, map[reso
|
|||
return nil, olds, nil
|
||||
}
|
||||
|
||||
if prev.PendingOperations != nil && !preview {
|
||||
return nil, nil, PlanPendingOperationsError{prev.PendingOperations}
|
||||
}
|
||||
|
||||
for _, oldres := range prev.Resources {
|
||||
// Ignore resources that are pending deletion; these should not be recorded in the LUT.
|
||||
if oldres.Delete {
|
||||
|
|
|
@ -146,6 +146,8 @@ func (ex *deploymentExecutor) Execute(callerCtx context.Context, opts Options, p
|
|||
if opts.RefreshOnly {
|
||||
return nil
|
||||
}
|
||||
} else if len(ex.deployment.prev.PendingOperations) != 0 && !preview {
|
||||
return result.FromError(PlanPendingOperationsError{ex.deployment.prev.PendingOperations})
|
||||
}
|
||||
|
||||
// The set of -t targets provided on the command line. 'nil' means 'update everything'.
|
||||
|
|
Loading…
Reference in a new issue