Skip to content

Skip porter-state for modifies: false custom actions#3582

Open
kichristensen wants to merge 8 commits into
getporter:mainfrom
kichristensen:issue3579
Open

Skip porter-state for modifies: false custom actions#3582
kichristensen wants to merge 8 commits into
getporter:mainfrom
kichristensen:issue3579

Conversation

@kichristensen
Copy link
Copy Markdown
Contributor

@kichristensen kichristensen commented Apr 5, 2026

What does this change

Custom actions declared with `modifies: false` (e.g. `dry-run`) would silently overwrite the installation's `porter-state` whenever they produced outputs. This happened because `SaveOperationResult()` persisted all outputs unconditionally, regardless of the action's `modifies` flag.

This change adds a guard in `SaveOperationResult()`: if the action has `Modifies: false`, internal outputs such as `porter-state` are skipped during persistence. `Finalize()` still packs the state bag unconditionally so the runtime output file is produced, but the persistence layer discards it for non-modifying actions. For built-in actions (`install`, `upgrade`, `uninstall`) and `modifies: true` actions the existing behavior is preserved.

Run records and user-defined outputs for `modifies: false` actions continue to be persisted — this is correct per the Porter docs, which state that outputs are recorded when they explicitly apply to an action via `applyTo`.

What issue does it fix

Closes #3579

Notes for the reviewer

  • No changes to `ShouldRecord()` — its `hasOutput` OR-term is intentional. The Porter docs confirm that outputs are recorded when they explicitly apply to an action via `applyTo`. State isolation is a separate concern enforced in `SaveOperationResult()`.
  • The guard uses `Run.ActionModifies()` and `ExtendedBundle.IsInternalOutput()` to identify outputs that must not be persisted for non-modifying actions.

Checklist

  • Did you write tests?
  • Did you write documentation?
  • Did you change porter.yaml or a storage document record? Update the corresponding schema file.
  • If this is your first pull request, please add your name to the bottom of our Contributors list. Thank you for making Porter better! 🙇‍♀️

@kichristensen kichristensen changed the title fix: skip porter-state for modifies:false actions Skip porter-state for modifies: false custom actions Apr 5, 2026
When a custom action has modifies: false, Finalize() now skips
packStateBag(), preventing porter-state from being written back
to the installation. Run records and user-defined outputs are
still persisted as expected.

Fixes getporter#3579

Signed-off-by: Kim Christensen <kimworking@gmail.com>
Add test case for stateful, modifies:false action with an applyTo
output to document that recording runs/outputs is correct behavior
per Porter docs. State isolation is enforced in Finalize(), not
ShouldRecord().

Signed-off-by: Kim Christensen <kimworking@gmail.com>
Add test bundle and TestInvoke_ModifiesFalse_DoesNotPersistState to
assert that invoking a modifies:false action leaves porter-state
unchanged, while still recording the run and saving user outputs.

Signed-off-by: Kim Christensen <kimworking@gmail.com>
The previous fix skipped packStateBag() inside the invocation
image, but the CNAB driver requires all outputs with no default
to be present — so it failed with "required output porter-state
is missing and has no default".

Move the guard to SaveOperationResult on the host side: when the
action has modifies:false, skip persisting internal outputs
(porter-state) to the installation's output record while still
allowing packStateBag() to write the file for the driver.

Signed-off-by: Kim Christensen <kimworking@gmail.com>
@schristoff
Copy link
Copy Markdown
Member

I feel like we should be modifying "ShouldRecord()" to execute properly in this area by having a bool checked here, what do you think?

@kichristensen
Copy link
Copy Markdown
Contributor Author

I feel like we should be modifying "ShouldRecord()" to execute properly in this area by having a bool checked here, what do you think?

@schristoff If I understand you correctly. I don't think there is anything wrong in ShouldRecord(), the documentation explicitly states that modifies: false can still record a run and the outputs. But state shouldn't be updated when a action has modifies: false.
What do you think?

@schristoff
Copy link
Copy Markdown
Member

Yeah, I agree with that. I believe we should be leveraging that bool more to disregard writing the state, if that makes sense.

Signed-off-by: Kim Christensen <kimworking@gmail.com>
Remove duplicate GetAction().Modifies lookup in
SaveOperationResult by adding ActionModifies() on
Run. ShouldRecord() and SaveOperationResult now use
the same bool source.

Signed-off-by: Kim Christensen <kimworking@gmail.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prevents modifies:false custom actions from overwriting persisted Porter state while still allowing their run records and user-defined outputs to be saved.

Changes:

  • Adds Run.ActionModifies() and reuses it in ShouldRecord().
  • Filters internal outputs such as porter-state during SaveOperationResult() for non-modifying actions.
  • Adds unit and integration coverage plus a test bundle for the modifies:false state-isolation scenario.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg/cnab/provider/action.go Skips persisting internal outputs for non-modifying actions.
pkg/cnab/provider/action_test.go Adds tests for saving/skipping porter-state based on action modification behavior.
pkg/storage/run.go Adds reusable action modification helper and applies it to run recording logic.
pkg/storage/run_test.go Adds tests for ActionModifies() and output-driven run recording.
pkg/runtime/runtime_manifest_test.go Adds tests documenting that finalization still writes the state output file.
tests/integration/invoke_test.go Adds an end-to-end regression test for modifies:false state persistence.
tests/integration/testdata/bundles/bundle-with-modifies-false-action/porter.yaml Adds a fixture bundle with state, a modifies:false action, and an action-scoped output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/storage/run_test.go Outdated
Comment thread pkg/cnab/provider/action.go
@kichristensen kichristensen requested a review from schristoff May 17, 2026 20:11
Point to SaveOperationResult(), not Finalize(), which
was never modified.

Signed-off-by: Kim Christensen <kimworking@gmail.com>
@kichristensen kichristensen marked this pull request as ready for review May 17, 2026 20:22
@kichristensen kichristensen requested a review from a team as a code owner May 17, 2026 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Custom actions with modifies: false persist porter-state back to installation

3 participants