Skip to content

fix(config): resolve secrets only for selected context#3565

Open
SergioChan wants to merge 2 commits intogetporter:mainfrom
SergioChan:fix-context-secret-scope-3558
Open

fix(config): resolve secrets only for selected context#3565
SergioChan wants to merge 2 commits intogetporter:mainfrom
SergioChan:fix-context-secret-scope-3558

Conversation

@SergioChan
Copy link
Copy Markdown

What does this change

  • Scopes template variable discovery to the selected context's config: block in multi-context config files.
  • Prevents secret resolution from traversing ${secret.*} references that belong to other contexts.
  • Adds a regression test proving loading current-context: local does not attempt to resolve secrets that are only referenced under prod.

This makes porter config show and normal config loading work when contexts use different secret stores.

What issue does it fix

Closes #3558

Notes for the reviewer

  • The fix is intentionally minimal: legacy flat config behavior is unchanged.
  • For multi-context files, cfg.templateVariables is now recomputed from the selected context map so second-pass secret resolution only evaluates relevant keys.

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! 🙇♀️

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

Scopes config template-variable discovery (and therefore second-pass ${secret.*} resolution) to only the selected context in multi-context config files, preventing secret lookups for other contexts during normal config loading.

Changes:

  • Recomputes cfg.templateVariables from the extracted selected context config map in multi-context configs.
  • Adds listTemplateVariablesForContext to discover template variables within a context-only YAML subtree.
  • Adds a regression test ensuring current-context: local does not attempt to resolve secrets referenced only under another context.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
pkg/config/loader.go Re-scopes template variable discovery to the selected context to avoid cross-context secret resolution.
pkg/config/loader_test.go Adds a regression test ensuring secrets from unselected contexts are not resolved.

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

Comment on lines +290 to +297
return nil
}

engine := liquid.NewEngine()
engine.Delims("${", "}", "${%", "%}")
tmpl, err := engine.ParseTemplate(cfgContents)
if err != nil {
return nil
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

listTemplateVariablesForContext silently returns nil when yaml.Marshal or ParseTemplate fails. If that happens, loadFinalPass will be skipped and any ${secret.*} in the selected context will remain rendered as empty from the first pass, leading to a subtly broken config without an error. Consider returning an error from listTemplateVariablesForContext (and propagating it from LoadFromViper) or at least logging/handling the failure explicitly so config loading fails fast when template-variable discovery can’t run.

Suggested change
return nil
}
engine := liquid.NewEngine()
engine.Delims("${", "}", "${%", "%}")
tmpl, err := engine.ParseTemplate(cfgContents)
if err != nil {
return nil
// Log the error and return an empty slice so callers don't silently skip the final pass.
fmt.Printf("error marshaling context config for template variable discovery: %v\n", err)
return []string{}
}
engine := liquid.NewEngine()
engine.Delims("${", "}", "${%", "%}")
tmpl, err := engine.ParseTemplate(cfgContents)
if err != nil {
// Log the error and return an empty slice so callers don't silently skip the final pass.
fmt.Printf("error parsing context template for template variable discovery: %v\n", err)
return []string{}

Copilot uses AI. Check for mistakes.
@SergioChan SergioChan force-pushed the fix-context-secret-scope-3558 branch from a0b2fb9 to 0d94259 Compare March 9, 2026 09:27
@SergioChan
Copy link
Copy Markdown
Author

Pushed a follow-up commit with DCO sign-off so the DCO check should pass now.

No code changes were introduced in this update (commit was amended to add Signed-off-by).

@kichristensen
Copy link
Copy Markdown
Contributor

@SergioChan Thanks for the PR, could you please create an integration test for this too. I have been working on #3561, and found a lot of gotchas that is hard to validate in a unit test.

Signed-off-by: sergiochan <yuheng9211@qq.com>
@SergioChan
Copy link
Copy Markdown
Author

Thanks for the review — good call.

I added an integration test to cover this behavior:

  • tests/integration/config_test.go
  • TestMultiContextConfig_OnlyResolvesSelectedContextSecrets

What it verifies:

  • With current-context: local, a ${secret.*} reference that exists only under another context (prod) is not resolved.
  • The test fails if secret resolution is attempted for the unselected context.

Commit pushed to this PR branch:

  • f9e25374 (test(integration): cover selected-context secret resolution)

Quick local validation performed:

  • go test -tags=integration ./tests/integration -run TestMultiContextConfig_OnlyResolvesSelectedContextSecrets -count=1 -c

Happy to adjust the test style/scope if you'd prefer a different integration path.

@SergioChan
Copy link
Copy Markdown
Author

Thanks for calling that out — I added an integration test in commit f9e2537 to cover this path (tests/integration/config_test.go).

The new case exercises selected-context secret resolution behavior so the regression is validated beyond unit-level coverage.

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

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


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

Comment on lines +288 to +298
cfgContents, err := yaml.Marshal(contextConfigMap)
if err != nil {
return nil
}

engine := liquid.NewEngine()
engine.Delims("${", "}", "${%", "%}")
tmpl, err := engine.ParseTemplate(cfgContents)
if err != nil {
return nil
}
require.NoError(t, err)
assert.Equal(t, "filesystem", c.Data.DefaultSecretsPlugin)
}


p.Config.DataLoader = config.LoadFromFilesystem()

_, err := p.Config.Load(ctx, func(ctx context.Context, secretKey string) (string, error) {
@kichristensen
Copy link
Copy Markdown
Contributor

@SergioChan Hi, will you have time to take a look at the Copilot comments.
As mentioned I have also been working on a fix for this in #3561, but you implementation is simpler and I would prefer the simple version, I just don't know if it will continue be simpler, when taking into account all the details.

@SergioChan
Copy link
Copy Markdown
Author

Yep — I can take this on.

I’ll go through the Copilot comments carefully and post a focused update so we can keep this implementation simple (and avoid drifting toward #3561 complexity unless needed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Difficult to mix secret plugins/sources in new config contexts

3 participants