Skip to content

Conversation

@kichristensen
Copy link
Contributor

What does this change

Adds three new commands for managing Porter's configuration file:

  • porter config show [--output FORMAT] - Display current config
  • porter config edit - Edit config in $EDITOR
  • porter config set KEY VALUE - Set individual config values

Examples

Show config (displays defaults if no config exists):

$ porter config show
allow-docker-host-access = false
build-driver = "buildkit"
verbosity = "info"
...

$ porter config show -o json
{
  "build-driver": "buildkit",
  "runtime-driver": "docker",
  ...
}

Set individual values:

$ porter config set verbosity debug
Config saved to /home/user/.porter/config.toml
Set verbosity = debug

$ porter config set logs.level info
Config saved to /home/user/.porter/config.toml
Set logs.level = info

Edit in your preferred editor:

$ porter config edit
# Opens config.toml in $EDITOR

Features

  • Multi-format support: toml (default), yaml, json
  • Format preservation: modifications preserve existing file format
  • Format conversion: show -o converts between formats
  • Type-safe value setting with automatic conversion (string, bool, int, nested fields)
  • Domain validation with helpful error messages
  • Creates default config if none exists

What issue does it fix

Closes #1077

Notes for the reviewer

Key design decisions:

  • Reflection-based field discovery (no manual field mappings)
  • Tag-based validation using validate:"oneof=..." struct tags
  • Format detection from file extension
  • Preserves existing format for set/edit, allows conversion with show

Checklist

  • Did you write tests?
  • Did you write documentation? (help text with examples)
  • 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 requested a review from a team as a code owner December 27, 2025 21:27
@kichristensen kichristensen marked this pull request as draft December 27, 2025 21:27
@kichristensen kichristensen marked this pull request as ready for review December 27, 2025 21:51
@kichristensen kichristensen changed the title feat: Add config management commands (show, edit, set) Add config management commands (show, edit, set) Dec 28, 2025
@kichristensen kichristensen self-assigned this Dec 28, 2025
Copy link
Member

@dgannon991 dgannon991 left a comment

Choose a reason for hiding this comment

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

Additional tests AI recommend adding;

  • Attempt to set a non-primitive field (slice/map/struct) and assert a descriptive error.
  • Attempt to set an exported field whose underlying type is a named numeric/enum to ensure SetString/SetInt works for named types.
  • Tests that simulate FileSystem failures for SaveConfig and CreateDefaultConfig.
  • An integration-style test for porter.SetConfig path that exercises loading existing file + saving changes (you already have this partly, but exercise the CLI path end-to-end).
  • Test case-insensitive inputs if you decide to normalize values (or add docs that values are exact).

field := t.Field(i)

// Check toml, yaml, and json tags
for _, tagKey := range []string{"toml", "yaml", "json"} {
Copy link
Member

Choose a reason for hiding this comment

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

Does this mean if we miss an annotation in a config file, it won't work here? (E.G. I provide TOML + YAML, but the user requests JSON)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If for example we something like this, where the JSON annotation is missing:

type Data struct {
  BuilDriver string `toml:"build-driver" yaml:"build-driver"
}

This function will find anyway because it loops through all of them, the requested format is not considered here. It currently works because I assume that the value will be same across all formats.
The annotation is used during marshaling to the requested format, meaning it must there for each supported configuration format.
For this specific function it will only "fail" if the field annotation differs between formats, which is a little confusing.
All input is appreciated, I'm not 100% sure what I think myself.

Copy link
Member

Choose a reason for hiding this comment

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

Ah OK, that makes a lot more sense. Is there any form of validation we could add for the values being the same across the different format types?

@kichristensen
Copy link
Contributor Author

Additional tests AI recommend adding;

* Attempt to set a non-primitive field (slice/map/struct) and assert a descriptive error.

* Attempt to set an exported field whose underlying type is a named numeric/enum to ensure SetString/SetInt works for named types.

* Tests that simulate FileSystem failures for SaveConfig and CreateDefaultConfig.

* An integration-style test for porter.SetConfig path that exercises loading existing file + saving changes (you already have this partly, but exercise the CLI path end-to-end).

* Test case-insensitive inputs if you decide to normalize values (or add docs that values are exact).

Added tests for the first two points, they were already handled, but not explicitly tested.
Will look into the remaining 3 bullets

Implement Phase 1: config writer foundation with persistence layer
for saving Porter config files.

New functionality:
- GetConfigPath: locate existing config or default to config.toml
- DetectConfigFormat: extract format from file extension
- SaveConfig: marshal and write config to disk
- CreateDefaultConfig: create new config with defaults

Changes:
- Add toml/yaml/json tags to config structs for proper marshaling
- Support all formats: toml, yaml, yml, json
- 17 comprehensive unit tests, all passing

Related to getporter#1077

Signed-off-by: Kim Christensen <[email protected]>
Implement reflection-based config value setter with dot notation
support and domain validation via struct tags.

Features:
- Dynamic field discovery using toml/yaml/json tags
- Unlimited nesting depth (e.g., logs.level, telemetry.protocol)
- Type conversion for string, bool, int, uint, float
- Domain validation via validate:"oneof=..." tags
- No external dependencies (custom tag parser)

Files:
- pkg/config/setter.go: SetConfigValue and ValidateConfigValue
- pkg/config/setter_test.go: 33 tests covering mechanisms
- pkg/config/datastore.go: Added validate tags to constrained fields
- pkg/config/logs.go: Added validate tags to LogConfig/TelemetryConfig

Validated fields include verbosity, runtime-driver, build-driver,
schema-check, logs.level, telemetry.protocol/compression.

All 56 config package tests pass.

Signed-off-by: Kim Christensen <[email protected]>
Implement `porter config show` command to display current Porter
configuration in multiple formats (toml, json, yaml).

- Add ConfigShowOptions with format validation
- Add ShowConfig() to read config from file or show defaults
- Add CLI command with -o/--output flag
- Add comprehensive unit tests (24 test cases)
- Auto-generate command documentation

Phase 3 of config commands implementation (getporter#1077)

Signed-off-by: Kim Christensen <[email protected]>
Implement `porter config edit` command to edit Porter configuration
in user's default editor.

- Add ConfigEditOptions with validation
- Add EditConfig() to open config in $EDITOR
- Create default config if none exists
- Validate syntax before saving changes
- Preserve existing file format (toml/yaml/json)
- Add unit tests for validation and default creation

Phase 4 of config commands implementation (getporter#1077)

Signed-off-by: Kim Christensen <[email protected]>
Signed-off-by: Kim Christensen <[email protected]>
Add porter config set KEY VALUE command to set individual
config values programmatically. Command creates default
config if none exists, preserves file format (toml/yaml/json),
validates values, and supports nested fields via dot notation.

Signed-off-by: Kim Christensen <[email protected]>
Add guard check in findFieldByTagWithMeta to validate input is a
struct before calling NumField(). Prevents panic when function is
called with non-struct types.

Add comprehensive test coverage for string, int, bool, slice, and
map types to verify guard behavior.

Signed-off-by: Kim Christensen <[email protected]>
Add comprehensive test coverage for reviewer feedback:

1. Non-primitive field handling - validates descriptive errors when
   attempting to set slice/map fields
2. Named type support - verifies SetString/SetInt/SetBool work
   correctly with named types (e.g., type LogLevel string)

Tests use mock struct with named int/bool types since actual config
only has named string (LogLevel).

Signed-off-by: Kim Christensen <[email protected]>
Accept case-insensitive input for oneof-validated fields
(e.g., "DEBUG", "Docker", "Info") and normalize to correct
casing from validation tags.

Uses strings.EqualFold for comparison and integrates
normalization into validation flow to avoid duplicate path
traversal.

Signed-off-by: Kim Christensen <[email protected]>
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.

CLI: Porter config management commands

2 participants