Skip to content

Support user config file#203

Open
mclasmeier wants to merge 13 commits into
mainfrom
mc/user-config
Open

Support user config file#203
mclasmeier wants to merge 13 commits into
mainfrom
mc/user-config

Conversation

@mclasmeier
Copy link
Copy Markdown
Collaborator

@mclasmeier mclasmeier commented Jun 4, 2026

Load user defaults from ~/.config/roxie/config.yaml (or $XDG_CONFIG_HOME/roxie/config.yaml) before command execution. The user config overrides hardcoded defaults and is itself overridden by CLI flags.

  • New internal/xdg package for resolving config and cache directories
  • Migrate image cache from ~/.roxie.image_cache to ~/.cache/roxie/image_cache
  • Consolidate logger: single globalLogger created in main(), shared across all commands
  • Apply user defaults before command execution
  • To make the applying of user defaults (specifically false booleans) reliable, this PR also replaces remaining booleans in the config structs with pointers.

Important use-case: Allow colleagues who are used to certain workflows to configure roxie according to their needs. Specifically thinking about:

central:
  namespace: stackrox
  pauseReconciliation: true
securedCluster:
  namespace: stackrox
  pauseReconciliation: true

So they don't need to keep repeating roxie deploy --pause-reconciliation --single-namespace.

Summary by CodeRabbit

  • New Features

    • Per-user default configuration file support to persist settings across runs.
  • Improvements

    • Centralized startup with a shared logger for more consistent output.
    • Optional feature flags now distinguish unset/true/false for clearer enable/disable behavior and stricter validation.
    • Safer image cache initialization and explicit resolution of cache/config paths.
  • Tests

    • Added/updated tests for user defaults and image cache behavior.

@mclasmeier mclasmeier added the ishur-review AI Hackathon test label label Jun 4, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds XDG user config/cache helpers, centralizes a package-level logger and applies per-user YAML defaults at startup, converts several deploy booleans to pointer-backed fields with Set/Enabled accessors, updates imagecache.New to return errors, and updates commands and tests to use the new APIs.

Changes

User Config Infrastructure and Configuration Refactoring

Layer / File(s) Summary
XDG directory helpers
internal/paths/paths.go
New paths module provides UserConfigPath(), configDir(), and CacheDir() to locate per-user configuration and cache directories under the roxie app subfolder with XDG-style resolution and error propagation.
ImageCache error handling and XDG integration
internal/imagecache/imagecache.go, internal/imagecache/imagecache_test.go, internal/deployer/deployer.go
imagecache.New now returns (*ImageCache, error) and uses paths.CacheDir() when no cache file is specified. Deployer.New() handles constructor errors and imagecache tests updated to assert require.NoError on creation.
Config structs with optional pointer-backed boolean fields
internal/deployer/config.go
Convert RoxieConfig.KonfluxImages, OperatorConfig.SkipDeployment and DeployViaOlm, and CentralConfig.PauseReconciliation and EarlyReadiness (and SecuredCluster equivalents) from bool to *bool; add corresponding Set()/Enabled() helpers. Set DefaultCentralConfig.EarlyReadiness and DefaultSecuredClusterConfig.EarlyReadiness to new(true).
Centralized logger initialization and user config defaults
cmd/main.go
Introduce package-level globalLogger, move deploySettings to var block, refactor main() to call run(), and add tryApplyUserDefaults() to read and mergo-merge per-user YAML into deploySettings when present.
All commands updated to use global logger
cmd/deploy.go, cmd/env.go, cmd/teardown.go
Commands now use the shared globalLogger instead of constructing new logger instances. cmd/deploy.go also changes sharedNamespace to a const. Removed unused logger imports where applicable.
Deploy command flag handlers and validation
cmd/deploy.go
Flag handlers now assign pointer-backed booleans via new(...). runDeploy uses Central.EarlyReadinessEnabled() for gating. configureConfig defaults port-forwarding to new(true) when exposure is not set. deployValidate uses SkipDeploymentEnabled(), DeployViaOlmEnabled(), and KonfluxImagesEnabled() for validations.
Operator deployment and Konflux logic
internal/deployer/deploy_via_operator.go, internal/deployer/operator.go, internal/deployer/deployer.go
Operator deployment logic now consults SkipDeploymentEnabled() and DeployViaOlmEnabled() for mode decisions; deployCentralOperator/deploySecuredClusterOperator use PauseReconciliationEnabled(); Konflux-related gates use KonfluxImagesEnabled() and summary methods use DeployViaOlmEnabled().
Deploy command tests for user defaults and config accessors
cmd/deploy_test.go
Updated imports to include mergo, logger, paths, yaml.v3. TestNewDeployCmd_Flags assertions updated to use ...Enabled() helpers. Added TestApplyUserDefaults to validate tryApplyUserDefaults behavior including missing config, merges, overrides, and invalid YAML error handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • stackrox/roxie#201: Both PRs touch cmd/deploy.go early-readiness evaluation; this PR changes gating to use new config accessors.

Suggested reviewers

  • AlexVulaj
  • davdhacs

Poem

🐰 I nibble paths beneath the tree,

YAML crumbs guide settings to me,
pointers hide a true or nil,
one shared log hums a steady trill,
defaults merged, the app hops free.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Support user config file' directly and clearly describes the main feature addition: enabling the application to load and apply user configuration from a file, which is the primary objective of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mc/user-config

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mclasmeier mclasmeier force-pushed the mc/user-config branch 2 times, most recently from 97ae030 to a58fe13 Compare June 4, 2026 16:05
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/deployer/deployer.go (1)

230-245: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clean up tempDir before returning on image cache init failure.

If imagecache.New(...) fails, New(...) returns immediately and leaves the temp directory created earlier on disk. Repeated failures will leak directories.

Suggested fix
 	d.dockerAuth = dockerauth.New(log)
 	d.imageCache, err = imagecache.New(log, "", 20)
 	if err != nil {
-		return nil, err
+		_ = os.RemoveAll(tempDir)
+		return nil, fmt.Errorf("failed to initialize image cache: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/deployer/deployer.go` around lines 230 - 245, The tempDir created
before initializing the Deployer is not removed if imagecache.New(...) fails,
leaking a directory; update the New/constructor path in
internal/deployer/deployer.go (around tempDir, Deployer struct init, and the
imageCache assignment where imagecache.New is called) to remove the tempDir on
error (e.g., call os.RemoveAll(tempDir)) before returning the error so the
temporary directory is cleaned up on failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/deploy_test.go`:
- Around line 271-272: The tests currently only set HOME via t.Setenv("HOME",
t.TempDir()), but XDG_CONFIG_HOME can still redirect xdg.UserConfigPath();
update each test instance that sets HOME (the t.Setenv("HOME", t.TempDir())
calls) to also set XDG_CONFIG_HOME to the same temp dir (e.g., add
t.Setenv("XDG_CONFIG_HOME", t.TempDir()) or reuse the same temp dir variable) so
path resolution is deterministic; apply the same change to the other occurrence
noted in the file.

In `@cmd/main.go`:
- Around line 52-55: Do not silently return nil when xdg.UserConfigPath() fails;
instead propagate the error (wrap it) or at minimum log a warning so caller
knows user-config couldn't be resolved. Replace the current branch that does "if
err != nil { return nil }" with code that returns a wrapped error like
fmt.Errorf("resolve user config path: %w", err) (or calls processLogger.Warn/
log.Printf with the error and continues), and ensure the containing function
signature and callers accept/handle the returned error; update imports (fmt or
log) as needed.
- Around line 23-33: globalLogger is declared but only initialized in main(),
causing nil-pointer panics in command handlers like runDeploy and runTeardown
when those functions are invoked outside main(); fix by ensuring globalLogger is
initialized at package load or via a lazy accessor: either set globalLogger =
logger.New() at declaration time (replace the nil pointer declaration) or
implement a getGlobalLogger() that checks if globalLogger==nil and sets it to
logger.New() before returning; update runDeploy/runTeardown to call
getGlobalLogger() (or rely on the package-level initialized globalLogger) so
they never dereference a nil logger.

---

Outside diff comments:
In `@internal/deployer/deployer.go`:
- Around line 230-245: The tempDir created before initializing the Deployer is
not removed if imagecache.New(...) fails, leaking a directory; update the
New/constructor path in internal/deployer/deployer.go (around tempDir, Deployer
struct init, and the imageCache assignment where imagecache.New is called) to
remove the tempDir on error (e.g., call os.RemoveAll(tempDir)) before returning
the error so the temporary directory is cleaned up on failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: 9a4b55c3-3288-4a61-b134-df6f99d4da73

📥 Commits

Reviewing files that changed from the base of the PR and between 76f4de3 and 4e9865f.

📒 Files selected for processing (10)
  • .github/workflows/docker-build.yml
  • cmd/deploy.go
  • cmd/deploy_test.go
  • cmd/env.go
  • cmd/main.go
  • cmd/teardown.go
  • internal/deployer/deployer.go
  • internal/imagecache/imagecache.go
  • internal/imagecache/imagecache_test.go
  • internal/xdg/xdg.go
💤 Files with no reviewable changes (1)
  • .github/workflows/docker-build.yml

Comment thread cmd/deploy_test.go Outdated
Comment thread cmd/main.go Outdated
Comment thread cmd/main.go Outdated
@mclasmeier mclasmeier force-pushed the mc/user-config branch 6 times, most recently from c996323 to 88cd1f0 Compare June 4, 2026 16:51
@mclasmeier mclasmeier removed the ishur-review AI Hackathon test label label Jun 7, 2026
@mclasmeier mclasmeier marked this pull request as draft June 7, 2026 20:56
@mclasmeier mclasmeier added the ishur-review AI Hackathon test label label Jun 7, 2026
@mclasmeier mclasmeier marked this pull request as ready for review June 8, 2026 05:50
@mclasmeier mclasmeier requested a review from porridge June 8, 2026 05:50
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/paths/paths.go (1)

30-36: ⚡ Quick win

Consider wrapping the error for consistency and better debugging context.

CacheDir() returns the raw error from os.UserCacheDir() without context, while UserConfigPath() wraps its error with "retrieving user config path". Adding similar wrapping here would improve error messages when cache directory resolution fails.

♻️ Suggested improvement
 func CacheDir() (string, error) {
 	dir, err := os.UserCacheDir()
 	if err != nil {
-		return "", err
+		return "", fmt.Errorf("retrieving cache directory: %w", err)
 	}
 	return filepath.Join(dir, appName), nil
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/paths/paths.go` around lines 30 - 36, CacheDir() currently returns
the raw error from os.UserCacheDir(); change it to wrap the error with
contextual text (e.g., "retrieving user cache path") before returning so it
matches UserConfigPath()'s behavior and gives better debugging context — update
the return in CacheDir() to wrap err (using fmt.Errorf(...%w...) or your
project's error-wrap helper) while keeping the final filepath.Join(dir, appName)
result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/deployer/deployer.go`:
- Around line 242-245: When imagecache.New(log, "", 20) fails, the tempDir
created earlier (variable tempDir) is left behind; update the initialization in
NewDeployer (or the constructor where tempDir is created) to call
os.RemoveAll(tempDir) before returning the error from imagecache.New so the
temporary directory is cleaned up when imagecache.New returns an error; ensure
you reference the same tempDir variable and remove it prior to returning nil,
err so callers don't need to rely on deployer.Cleanup().

---

Nitpick comments:
In `@internal/paths/paths.go`:
- Around line 30-36: CacheDir() currently returns the raw error from
os.UserCacheDir(); change it to wrap the error with contextual text (e.g.,
"retrieving user cache path") before returning so it matches UserConfigPath()'s
behavior and gives better debugging context — update the return in CacheDir() to
wrap err (using fmt.Errorf(...%w...) or your project's error-wrap helper) while
keeping the final filepath.Join(dir, appName) result.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: f3e2d8d0-8ddb-417d-bac7-fc9b5d7f3e12

📥 Commits

Reviewing files that changed from the base of the PR and between 4e9865f and 4226439.

📒 Files selected for processing (12)
  • cmd/deploy.go
  • cmd/deploy_test.go
  • cmd/env.go
  • cmd/main.go
  • cmd/teardown.go
  • internal/deployer/config.go
  • internal/deployer/deploy_via_operator.go
  • internal/deployer/deployer.go
  • internal/deployer/operator.go
  • internal/imagecache/imagecache.go
  • internal/imagecache/imagecache_test.go
  • internal/paths/paths.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/imagecache/imagecache_test.go
  • cmd/teardown.go
  • cmd/env.go
  • internal/imagecache/imagecache.go
  • cmd/main.go

Comment thread internal/deployer/deployer.go Outdated
@mclasmeier
Copy link
Copy Markdown
Collaborator Author

@coderabbitai, addressed your nitpick

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 8, 2026

Tip

For best results, initiate chat on the files or code changes.

@mclasmeier Thanks for addressing it! 🐇


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@mclasmeier mclasmeier requested a review from AlexVulaj June 8, 2026 10:23
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/deployer/config.go (2)

118-131: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

new(true) is invalid Go syntax—code will not compile.

The builtin new() takes a type argument, not a value. new(true) will fail with: true is not a type. To create a *bool pointing to true, use a helper function or the k8s.io/utils/ptr package.

🐛 Proposed fix using a helper function

Add a helper at the top of the file:

func boolPtr(b bool) *bool {
	return &b
}

Then apply this diff:

 func DefaultCentralConfig() CentralConfig {
 	return CentralConfig{
 		DeployTimeout:  DefaultCentralWaitTimeout,
 		Namespace:      "acs-central",
-		EarlyReadiness: new(true),
+		EarlyReadiness: boolPtr(true),
 		Spec: map[string]interface{}{

Alternatively, if k8s.io/utils/ptr is already a dependency:

import "k8s.io/utils/ptr"
// ...
EarlyReadiness: ptr.To(true),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/deployer/config.go` around lines 118 - 131, The EarlyReadiness field
in DefaultCentralConfig is using invalid Go syntax `new(true)`; replace that
with a pointer-to-bool value (e.g., add a helper func boolPtr(b bool) *bool {
return &b } and set EarlyReadiness: boolPtr(true)) or, if available, use the k8s
helper and import "k8s.io/utils/ptr" and set EarlyReadiness: ptr.To(true);
update DefaultCentralConfig accordingly so it compiles.

251-259: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Same new(true) compile error as in DefaultCentralConfig.

Apply the same fix here using the helper function.

🐛 Proposed fix
 func DefaultSecuredClusterConfig() SecuredClusterConfig {
 	return SecuredClusterConfig{
 		DeployTimeout:  DefaultSecuredClusterWaitTimeout,
 		Namespace:      "acs-sensor",
-		EarlyReadiness: new(true),
+		EarlyReadiness: boolPtr(true),
 		Spec:           make(map[string]interface{}),
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/deployer/config.go` around lines 251 - 259,
DefaultSecuredClusterConfig is using new(true) for the EarlyReadiness field
which causes the same compile error as before; replace the new(true) expression
in the DefaultSecuredClusterConfig function (the EarlyReadiness field) with the
boolean pointer helper used in DefaultCentralConfig (the same helper function
you used there) so EarlyReadiness is set via that helper function call instead
of new(true).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/deployer/config.go`:
- Around line 118-131: The EarlyReadiness field in DefaultCentralConfig is using
invalid Go syntax `new(true)`; replace that with a pointer-to-bool value (e.g.,
add a helper func boolPtr(b bool) *bool { return &b } and set EarlyReadiness:
boolPtr(true)) or, if available, use the k8s helper and import
"k8s.io/utils/ptr" and set EarlyReadiness: ptr.To(true); update
DefaultCentralConfig accordingly so it compiles.
- Around line 251-259: DefaultSecuredClusterConfig is using new(true) for the
EarlyReadiness field which causes the same compile error as before; replace the
new(true) expression in the DefaultSecuredClusterConfig function (the
EarlyReadiness field) with the boolean pointer helper used in
DefaultCentralConfig (the same helper function you used there) so EarlyReadiness
is set via that helper function call instead of new(true).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: 11c109a6-282c-46ab-91b8-8801a22e4397

📥 Commits

Reviewing files that changed from the base of the PR and between a7e7df5 and 83ae551.

📒 Files selected for processing (4)
  • cmd/deploy.go
  • cmd/deploy_test.go
  • internal/deployer/config.go
  • internal/deployer/deploy_via_operator.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/deployer/deploy_via_operator.go
  • cmd/deploy_test.go
  • cmd/deploy.go

@mclasmeier
Copy link
Copy Markdown
Collaborator Author

@coderabbitai, what you are claiming there wrt to new(true) and ptr.To(), that seems to be outdated. Check out what recent Go versions (I believe >= 1.26) can do with new() nowadays.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 8, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🌐 Web query:

Go 1.26 new() builtin accept value argument pointer golang

Length of output: 8128



@mclasmeier You're absolutely right, and I apologize for the noise. Go 1.26 extended new() to accept an expression (not just a type), so new(true) allocates a *bool initialized to true — exactly equivalent to ptr.To(true) but without needing a helper. From the Go 1.26 release notes:

The built-in new function, which creates a new variable, now allows its operand to be an expression, specifying the initial value of the variable.

So new(true) is the clean, idiomatic Go 1.26+ way to create a pointer to a boolean value. No external helper needed. My earlier comment was wrong in that context. 🐇


✏️ Learnings added
Learnt from: mclasmeier
URL: https://github.com/stackrox/roxie/pull/203

Timestamp: 2026-06-08T11:49:08.937Z
Learning: In Go 1.26+, the built-in `new()` function accepts an expression argument (not just a type). For example, `new(true)` allocates a `*bool` initialized to `true`, making helper functions like `ptr.To(true)` unnecessary. Do not flag `new(<value>)` usage as incorrect in repositories targeting Go 1.26 or later.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Moritz Clasmeier added 12 commits June 8, 2026 14:36
Introduce function for applying user defaults.
Otherwise overwriting of bool settings with a user-config would only
work in one direction: from false to true. By using pointers we can
reliably detect "is this field set or not" and overwrite.

Adjusted call sites for new bool ptrs.
We use this for temporarily saving just the result from the translation
of command line args to YAML patches.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ishur-review AI Hackathon test label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant