Skip to content

Commit 06b0b84

Browse files
committed
fix: skip porter-state on host for modifies:false
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>
1 parent aa51e36 commit 06b0b84

File tree

4 files changed

+113
-11
lines changed

4 files changed

+113
-11
lines changed

pkg/cnab/provider/action.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,22 @@ func (r *Runtime) SaveOperationResult(ctx context.Context, opResult driver.Opera
270270
bigerr = multierror.Append(bigerr, fmt.Errorf("error updating installation record for %s\n%#v: %w", installation, installation, err))
271271
}
272272

273+
extBun := cnab.ExtendedBundle{Bundle: run.Bundle}
274+
actionModifies := true
275+
if action, err := run.Bundle.GetAction(run.Action); err == nil {
276+
actionModifies = action.Modifies
277+
}
278+
273279
for outputName, outputValue := range opResult.Outputs {
280+
// porter-state tracks bundle-managed resource state. Skip persisting it
281+
// for modifies:false actions so that a read-only invoke cannot overwrite
282+
// the installation's state record.
283+
if !actionModifies && extBun.IsInternalOutput(outputName) {
284+
continue
285+
}
286+
274287
output := result.NewOutput(outputName, []byte(outputValue))
275-
output, err = r.sanitizer.CleanOutput(ctx, output, cnab.ExtendedBundle{Bundle: run.Bundle})
288+
output, err = r.sanitizer.CleanOutput(ctx, output, extBun)
276289
if err != nil {
277290
bigerr = multierror.Append(bigerr, fmt.Errorf("error sanitizing sensitive %s output for %s run of installation %s\n%#v: %w", output.Name, run.Action, installation, output, err))
278291
}

pkg/cnab/provider/action_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"testing"
1212

1313
"github.com/cnabio/cnab-go/bundle"
14+
"github.com/cnabio/cnab-go/bundle/definition"
1415
"github.com/cnabio/cnab-go/driver"
1516
"github.com/stretchr/testify/assert"
1617
"github.com/stretchr/testify/require"
@@ -73,3 +74,93 @@ func TestAddFiles(t *testing.T) {
7374
require.Contains(t, op.Files, config.ClaimFilepath, "The claim should have been injected into the bundle")
7475
test.CompareGoldenFile(t, "testdata/want-claim.json", op.Files[config.ClaimFilepath])
7576
}
77+
78+
func TestSaveOperationResult_ModifiesFalse_SkipsPorterState(t *testing.T) {
79+
t.Parallel()
80+
81+
ctx := context.Background()
82+
d := NewTestRuntime(t)
83+
defer d.Close()
84+
85+
instName := "mybuns"
86+
bun := bundle.Bundle{
87+
Actions: map[string]bundle.Action{
88+
"dry-run": {Modifies: false},
89+
},
90+
Outputs: map[string]bundle.Output{
91+
"porter-state": {Definition: "porter-state", Path: "/cnab/app/outputs/porter-state.tgz"},
92+
"user-output": {Definition: "user-output", Path: "/cnab/app/outputs/user-output"},
93+
},
94+
Definitions: map[string]*definition.Schema{
95+
"porter-state": {Comment: cnab.PorterInternal},
96+
"user-output": {Type: "string"},
97+
},
98+
}
99+
i := d.TestInstallations.CreateInstallation(storage.NewInstallation("", instName), d.TestInstallations.SetMutableInstallationValues)
100+
run := storage.NewRun("", instName)
101+
run.Bundle = bun
102+
run.Action = "dry-run"
103+
run = d.TestInstallations.CreateRun(run, d.TestInstallations.SetMutableRunValues)
104+
result := run.NewResult(cnab.StatusSucceeded)
105+
106+
opResult := driver.OperationResult{
107+
Outputs: map[string]string{
108+
"porter-state": "state-data",
109+
"user-output": "hello",
110+
},
111+
}
112+
113+
err := d.SaveOperationResult(ctx, opResult, i, run, result)
114+
require.NoError(t, err)
115+
116+
outputs, err := d.TestInstallations.GetOutputs(ctx, run.ID)
117+
require.NoError(t, err)
118+
119+
_, hasPorterState := outputs.GetByName("porter-state")
120+
assert.False(t, hasPorterState, "porter-state should not be saved for modifies:false actions")
121+
122+
_, hasUserOutput := outputs.GetByName("user-output")
123+
assert.True(t, hasUserOutput, "user-defined outputs should still be saved")
124+
}
125+
126+
func TestSaveOperationResult_ModifiesTrue_SavesPorterState(t *testing.T) {
127+
t.Parallel()
128+
129+
ctx := context.Background()
130+
d := NewTestRuntime(t)
131+
defer d.Close()
132+
133+
instName := "mybuns"
134+
bun := bundle.Bundle{
135+
Actions: map[string]bundle.Action{
136+
"rotate-creds": {Modifies: true},
137+
},
138+
Outputs: map[string]bundle.Output{
139+
"porter-state": {Definition: "porter-state", Path: "/cnab/app/outputs/porter-state.tgz"},
140+
},
141+
Definitions: map[string]*definition.Schema{
142+
"porter-state": {Comment: cnab.PorterInternal},
143+
},
144+
}
145+
i := d.TestInstallations.CreateInstallation(storage.NewInstallation("", instName), d.TestInstallations.SetMutableInstallationValues)
146+
run := storage.NewRun("", instName)
147+
run.Bundle = bun
148+
run.Action = "rotate-creds"
149+
run = d.TestInstallations.CreateRun(run, d.TestInstallations.SetMutableRunValues)
150+
result := run.NewResult(cnab.StatusSucceeded)
151+
152+
opResult := driver.OperationResult{
153+
Outputs: map[string]string{
154+
"porter-state": "state-data",
155+
},
156+
}
157+
158+
err := d.SaveOperationResult(ctx, opResult, i, run, result)
159+
require.NoError(t, err)
160+
161+
outputs, err := d.TestInstallations.GetOutputs(ctx, run.ID)
162+
require.NoError(t, err)
163+
164+
_, hasPorterState := outputs.GetByName("porter-state")
165+
assert.True(t, hasPorterState, "porter-state should be saved for modifies:true actions")
166+
}

pkg/runtime/runtime_manifest.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -760,14 +760,9 @@ func (m *RuntimeManifest) Finalize(ctx context.Context) error {
760760
bigErr = multierror.Append(bigErr, err)
761761
}
762762

763-
// Skip state persistence for modifies: false actions. Such actions declare
764-
// they do not change bundle-managed resources, so writing porter-state back
765-
// would contradict that contract.
766-
if action, err := m.bundle.GetAction(m.Action); err != nil || action.Modifies {
767-
// Always try to persist state, even when errors occur
768-
if err := m.packStateBag(ctx); err != nil {
769-
bigErr = multierror.Append(bigErr, err)
770-
}
763+
// Always try to persist state, even when errors occur
764+
if err := m.packStateBag(ctx); err != nil {
765+
bigErr = multierror.Append(bigErr, err)
771766
}
772767

773768
return bigErr.ErrorOrNil()

pkg/runtime/runtime_manifest_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,12 +1430,15 @@ install:
14301430
return rm, testConfig
14311431
}
14321432

1433-
t.Run("modifies false skips porter-state", func(t *testing.T) {
1433+
t.Run("modifies false still writes porter-state", func(t *testing.T) {
1434+
// packStateBag always runs so the CNAB driver can collect the output.
1435+
// Skipping the save on the host side is what prevents state from being
1436+
// persisted for modifies:false actions.
14341437
rm, testConfig := setup(t, "dry-run", false, false)
14351438
require.NoError(t, rm.Finalize(context.Background()))
14361439
exists, err := testConfig.FileSystem.Exists("/cnab/app/outputs/porter-state")
14371440
require.NoError(t, err)
1438-
assert.False(t, exists, "porter-state should not be written for modifies: false actions")
1441+
assert.True(t, exists, "porter-state must be written so the CNAB driver can collect it")
14391442
})
14401443

14411444
t.Run("modifies true writes porter-state", func(t *testing.T) {

0 commit comments

Comments
 (0)