pulumi/pkg/engine/lifecycletest
Will Jones 2aad59df18
Spot skipped-create dependencies even when inputs don't change (#17633)
`Create`s happen in response to new resources being present in a Pulumi
program but not in Pulumi's state snapshot. For instance, take the
following TypeScript program:

```typescript
const b = new Resource("b", { x: 2 })
const c = new Resource("c", { x: 3 }, { deletedWith: b })
```

When asked to run this program on an empty state with no arguments,
Pulumi will issue two `Create` calls -- one for `b`, and one for `c`.
The call for `c` will *depend on* `b`'s due to the need for
`deletedWith` to refer to `b`'s URN.

If instead of passing no arguments we ask Pulumi to perform a *targeted*
operation that *only targets `c`*, Pulumi will throw an error and refuse
to execute the program. This is because `c`'s `Create` would depend on
`b`'s `Create`, but since `b` has not been targeted, its `Create` will
not happen and thus `c`'s call cannot be built. Internally, we call
`b`'s omission a "skipped create", and keep track of these so that we
can trigger the error above appropriately.

So far, so good. Now, consider that we have executed the above program
with no targets, so that Pulumi's state contains both `b` and `c`, with
`c` depending on `b` via a `deletedWith` link. We now add the missing
`a` to the program and modify `b` to depend on it:

```typescript
const a = new Resource("a", { x: 1 })
const b = new Resource("b", { x: 2 }, { deletedWith: a })
const c = new Resource("c", { x: 3 }, { deletedWith: b })
```

If we were to run the program with no targets, we would expect a state
in which all of `a`, `b` and `c` existed. `c` would depend on `b` (as it
did before) and `b` would have a new dependency on `a`. Lovely.

Of course, we are not about to run the program with no targets. We are
instead interested in *targeting `b` only*. When we do, the following
sequence of events will unfold:

* `a`, not existing in the state already and not being targeted, will
yield a skipped create.
* `b` depends on a skipped create. However, its *inputs* have not
changed (`x` was `2` before, and it's still `2` after). Consequently,
`b` will have yielded a `SameStep`. We *incorrectly* assume this means
that skipped creates are not a problem (`SameStep`s mean no changes,
right?) and allow the deployment to proceed.
* We write the new `b` to the state, with a dependency on the skipped
create, yielding a snapshot integrity error.
* 💥

We might ask "why assume that `SameStep`s are safe?". From looking at
the existing test cases, it seems likely that this was designed to cover
the case where a skipped create is depended on *by another skipped
create* (which is internally represented as a `SameStep`) -- in such
cases, we don't need to error and the deployment can proceed. However,
this bug shows that there are cases where `SameStep` does not imply
skipped create. This commit thus tightens up the logic appropriately,
checking explicitly for `SameStep`s whose `IsSkippedCreate` method
returns true.
2024-10-30 16:17:30 +00:00
..
framework Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
fuzzing Support generating random providers in lifecycle tests (#17626) 2024-10-29 17:51:29 +00:00
testdata Spot skipped-create dependencies even when inputs don't change (#17633) 2024-10-30 16:17:30 +00:00
README.md Document testing in `pulumi/pulumi` (#17161) 2024-09-05 11:51:32 +00:00
alias_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
analyzer_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
continue_on_error_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
delete_before_replace_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
golang_sdk_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
import_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
loader_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
parameterized_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
pending_delete_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
pending_replace_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
provider_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
pulumi_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
refresh_legacy_diff_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
refresh_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
resource_reference_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
retain_on_delete_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
source_query_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
step_generator_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
target_test.go Spot skipped-create dependencies even when inputs don't change (#17633) 2024-10-30 16:17:30 +00:00
transformation_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00
update_plan_test.go Factor out the lifecycle testing framework (#17584) 2024-10-28 11:58:59 +00:00

README.md

(lifecycle-tests)=

Lifecycle tests

Lifecycle tests exercise the Pulumi engine and serve as a specification for the behaviours and interactions of the various features that define the lifecycle of a Pulumi program. This includes, but is not limited to:

  • The operation(s) being executed (up, preview, etc.) and the options passed to that operation (--target, --target-dependents, etc.).
  • The programs being executed -- their resources, invocations, and the various options that might be associated with them (parent, retainOnDelete, etc.).
  • The state of the program before and after operations are executed.

How and when to use