Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 54
feat(cli): add flags to override policy inputs (replace, not append)#3338
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
Merged
javirln
merged 6 commits into
chainloop-dev:main
from
javirln:javier/pfm-6906-cli-add-a-flag-to-override-policy-inputs-replace-not-appendAug 10, 2026
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
3378e79
feat(cli): add flags to override policy inputs (replace, not append)
javirln e2ca596
fix(cli): make scoped policy-input precedence deterministic
javirln e4401c9
fix(cli): rank provider-qualified policy-input scopes above bare names
javirln 31a3b62
feat(cli): add reserved --append flag, drop --policy-input-from-file-…
javirln 4ccb7f6
refactor(cli): drop the no-op runtime warning on --append
javirln 5dbf491
refactor(policies): simplify runtime-override name collection
javirln File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -101,16 +101,16 @@ func NewAttestationAdd(cfg *AttestationAddOpts) (*AttestationAdd, error) { | ||
| var ErrAttestationNotInitialized = errors.New("attestation not yet initialized") | ||
| func (action *AttestationAdd) Run(ctx context.Context, attestationID, materialName, materialValue, materialType string, annotations map[string]string, policyInputFiles []*PolicyInputFromFile) ([]*AttestationStatusMaterial, error) { | ||
| func (action *AttestationAdd) Run(ctx context.Context, attestationID, materialName, materialValue, materialType string, annotations map[string]string, policyInputFiles []*PolicyInputFromFile, policyInputs []*PolicyInput) ([]*AttestationStatusMaterial, error) { | ||
| // initialize the crafter. If attestation-id is provided we assume the attestation is performed using remote state | ||
| crafter, err := newCrafter(&newCrafterStateOpts{enableRemoteState: (attestationID != ""), localStatePath: action.localStatePath}, action.CPConnection, action.opts...) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to load crafter: %w", err) | ||
| } | ||
| // Resolve runtime policy inputs from the provided files before adding the | ||
| // material, so a malformed file aborts the add early. | ||
| runtimeInputs, err := buildRuntimeInputs(policyInputFiles) | ||
| // Resolve runtime policy inputs from the provided files and inline values | ||
| // before adding the material, so a malformed file aborts the add early. | ||
| runtimeInputs, err := buildRuntimeInputs(policyInputFiles, policyInputs) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| @@ -163,7 +163,7 @@ func (action *AttestationAdd) Run(ctx context.Context, attestationID, materialNa | ||
| // The runtime inputs still apply to every exploded material's policy | ||
| // evaluation (they flow through addOpts); only the per-input EVIDENCE | ||
| // materials are not recorded on the explode path. | ||
| action.Logger.Warn().Msg("--policy-input-from-file values apply to policy evaluation but are not recorded as evidence materials when expanding an archive") | ||
| action.Logger.Warn().Msg("policyinput files apply to policy evaluation but are not recorded as evidence materials when expanding an archive") | ||
| } | ||
| limits := materials.ArchiveLimits{MaxEntries: action.maxExtractEntries, MaxTotalSize: action.maxExtractSize} | ||
| // AddMaterialsFromArchive also records the source archive as an EVIDENCE | ||
| @@ -249,7 +249,8 @@ func shouldExplode(materialType, value string) (materials.ArchiveFormat, error) | ||
| // returns nil when there are none. Defined at package scope so it can name the | ||
| // crafter package type (the Run method shadows it with a local variable). | ||
| func runtimeInputAddOpts(runtimeInputs *policies.RuntimeInputs) []crafter.AddOpt { | ||
| if runtimeInputs == nil || (len(runtimeInputs.Global) == 0 && len(runtimeInputs.Scoped) == 0) { | ||
| if runtimeInputs == nil || (len(runtimeInputs.Global) == 0 && len(runtimeInputs.Scoped) == 0 && | ||
| len(runtimeInputs.GlobalOverride) == 0 && len(runtimeInputs.ScopedOverride) == 0) { | ||
| return nil | ||
| } | ||
| return []crafter.AddOpt{crafter.WithRuntimeInputs(runtimeInputs)} | ||
| @@ -262,43 +263,71 @@ func withSourceArchiveEvidence(opts []crafter.AddOpt) []crafter.AddOpt { | ||
| return append(opts, crafter.WithSourceArchiveEvidence()) | ||
| } | ||
| // buildRuntimeInputs reads each policy input file and returns the extracted | ||
| // values grouped for the policy engine: unscoped entries under Global and | ||
| // policy-scoped entries under Scoped[policy]. Values are newline-joined and | ||
| // accumulated via policies.MergeRuntimeInputs so repeated inputs merge using the | ||
| // same multi-value encoding the engine expects (it splits inputs back on | ||
| // newlines and commas). As with contract-declared arguments, individual values | ||
| // must not embed those delimiters; path globs, the intended use, never do. | ||
| func buildRuntimeInputs(policyInputFiles []*PolicyInputFromFile) (*policies.RuntimeInputs, error) { | ||
| if len(policyInputFiles) == 0 { | ||
| // buildRuntimeInputs reads each policy input file and combines it with the | ||
| // inline --policy-input values, returning them grouped for the policy engine. | ||
| // File inputs (--policy-input-from-file) go under Global/Scoped and are | ||
| // newline-joined via policies.MergeRuntimeInputs so repeated inputs merge using | ||
| // the multi-value encoding the engine expects (it splits inputs back on newlines | ||
| // and commas). Inline values (--policy-input) go under GlobalOverride/ScopedOverride | ||
| // and replace the contract value instead of appending, keeping a scalar override | ||
| // a scalar. As with contract-declared arguments, individual append values must | ||
| // not embed those delimiters; path globs, the intended use, never do. | ||
| func buildRuntimeInputs(policyInputFiles []*PolicyInputFromFile, policyInputs []*PolicyInput) (*policies.RuntimeInputs, error) { | ||
| if len(policyInputFiles) == 0 && len(policyInputs) == 0 { | ||
| return nil, nil | ||
| } | ||
| ri := &policies.RuntimeInputs{ | ||
| Global: map[string]string{}, | ||
| Scoped: map[string]map[string]string{}, | ||
| Global: map[string]string{}, | ||
| Scoped: map[string]map[string]string{}, | ||
| GlobalOverride: map[string]string{}, | ||
| ScopedOverride: map[string]map[string]string{}, | ||
| } | ||
| for _, pif := range policyInputFiles { | ||
| values, err := ExtractColumnValues(pif.File, pif.Column) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("extracting %q from %q: %w", pif.Column, pif.File, err) | ||
| } | ||
| // Unscoped entries go to Global; policy-scoped entries to their own | ||
| // Scoped[policy] map. Because global and scoped values live in separate | ||
| // maps, they never collide here even when they share an input name; | ||
| // forPolicy is what later merges a policy's scoped values over Global. | ||
| add := map[string]string{pif.Input: strings.Join(values, "\n")} | ||
| if pif.Policy == "" { | ||
| ri.Global = policies.MergeRuntimeInputs(ri.Global, add) | ||
| } else { | ||
| ri.Scoped[pif.Policy] = policies.MergeRuntimeInputs(ri.Scoped[pif.Policy], add) | ||
| } | ||
| addAppendInput(ri, pif.Policy, pif.Input, strings.Join(values, "\n")) | ||
| } | ||
| for _, pi := range policyInputs { | ||
| addOverrideInput(ri, pi.Policy, pi.Input, pi.Value) | ||
| } | ||
| return ri, nil | ||
| } | ||
| // addAppendInput accumulates an append-mode runtime input. Unscoped entries go | ||
| // to Global; policy-scoped entries to their own Scoped[policy] map. Because | ||
| // global and scoped values live in separate maps they never collide here even | ||
| // when they share an input name; forPolicy is what later merges a policy's | ||
| // scoped values over Global. | ||
| func addAppendInput(ri *policies.RuntimeInputs, policy, input, value string) { | ||
| add := map[string]string{input: value} | ||
| if policy == "" { | ||
| ri.Global = policies.MergeRuntimeInputs(ri.Global, add) | ||
| } else { | ||
| ri.Scoped[policy] = policies.MergeRuntimeInputs(ri.Scoped[policy], add) | ||
| } | ||
| } | ||
| // addOverrideInput records a replace-mode runtime input. When the same key is | ||
| // supplied more than once the last value wins, since a replace — unlike an | ||
| // append — has no meaningful accumulation. | ||
| func addOverrideInput(ri *policies.RuntimeInputs, policy, input, value string) { | ||
| if policy == "" { | ||
| ri.GlobalOverride[input] = value | ||
| return | ||
| } | ||
| if ri.ScopedOverride[policy] == nil { | ||
| ri.ScopedOverride[policy] = map[string]string{} | ||
| } | ||
| ri.ScopedOverride[policy][input] = value | ||
javirln marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| // addPolicyInputEvidence adds each policy input file as an EVIDENCE material, | ||
| // cross-linked with the evaluated material in both directions via the | ||
| // chainloop.material.references annotation: each evidence material points at the | ||
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.