Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[engine] Clear pending operations with refresh. #8435

Merged
merged 10 commits into from Mar 25, 2022
3 changes: 3 additions & 0 deletions CHANGELOG_PENDING.md
@@ -1,5 +1,8 @@
### Improvements

- Clear pending operations during `pulumi refresh` or `pulumi up -r`.
[#8435](https://github.com/pulumi/pulumi/pull/8435)

### Bug Fixes

- [codegen/typescript] - Respect default values in Pulumi object types.
Expand Down
67 changes: 67 additions & 0 deletions pkg/engine/lifeycletest/pulumi_test.go
Expand Up @@ -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{
Expand Down
4 changes: 0 additions & 4 deletions pkg/resource/deploy/deployment.go
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions pkg/resource/deploy/deployment_executor.go
Expand Up @@ -146,6 +146,8 @@ func (ex *deploymentExecutor) Execute(callerCtx context.Context, opts Options, p
if opts.RefreshOnly {
return nil
}
} else if ex.deployment.prev != nil && 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'.
Expand Down
13 changes: 1 addition & 12 deletions pkg/resource/deploy/deployment_test.go
Expand Up @@ -43,16 +43,5 @@ func TestPendingOperationsDeployment(t *testing.T) {
})

_, err := NewDeployment(&plugin.Context{}, &Target{}, snap, &fixedSource{}, nil, false, nil)
if !assert.Error(t, err) {
t.FailNow()
}

invalidErr, ok := err.(PlanPendingOperationsError)
if !assert.True(t, ok) {
t.FailNow()
}

assert.Len(t, invalidErr.Operations, 1)
assert.Equal(t, resourceB.URN, invalidErr.Operations[0].Resource.URN)
assert.Equal(t, resource.OperationTypeCreating, invalidErr.Operations[0].Type)
assert.NoError(t, err)
}