Skip to content

feat(rest-api): add reloadable phone home URL settings#2289

Open
osu wants to merge 3 commits into
NVIDIA:mainfrom
osu:2277-feat-phonehomeurl-setting-improvements
Open

feat(rest-api): add reloadable phone home URL settings#2289
osu wants to merge 3 commits into
NVIDIA:mainfrom
osu:2277-feat-phonehomeurl-setting-improvements

Conversation

@osu
Copy link
Copy Markdown
Member

@osu osu commented Jun 7, 2026

Description

  • Make site.phoneHomeUrl an effective runtime setting with base config, site config, and env source tracking.
  • Auto-reload REST API config files with fail-safe behavior that preserves the last good value on invalid reload.
  • Add a read-only /metadata/settings endpoint to expose the effective phone home URL and reload status.
  • Parse deployment-compatible --config-path and --site-config-path flags before REST API config initialization.

This addresses the GitOps/file-reload path discussed in #2277 while avoiding an in-memory REST write endpoint that could diverge from the declarative config source.

Type of Change

  • Add - New feature or capability
  • Change - Changes in existing functionality
  • Fix - Bug fixes
  • Remove - Removed features or deprecated functionality
  • Internal - Internal changes (refactoring, tests, docs, etc.)

Related Issues (Optional)

Refs #2277

Breaking Changes

  • This PR contains breaking changes

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • No testing required (docs, internal refactor, etc.)

Commands run:

  • git diff --check
  • go test ./api/internal/config ./api/cmd/api ./api/pkg/api/model ./api/pkg/api
  • go test ./api/pkg/api/handler -run 'Test(SettingsHandler|MetadataHandler)'
  • go test ./api/... -run '^$'
  • go test -race ./api/internal/config ./api/cmd/api ./api/pkg/api/handler -run 'Test(SettingsHandler|MetadataHandler|ApplyConfigPathArgs|RuntimeSettings|ReloadConfig|UniqueDirs)'

Additional Notes

  • This PR intentionally does not add a REST write endpoint for phoneHomeUrl to avoid divergence from GitOps-managed config files.

Signed-off-by: Hasan Khan <hasank@nvidia.com>
@osu osu requested a review from a team as a code owner June 7, 2026 20:10
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented Jun 7, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 7, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2fac43dc-d99e-401b-94cf-a4d6bf523ef5

📥 Commits

Reviewing files that changed from the base of the PR and between 88dc25f and 57c3d13.

📒 Files selected for processing (3)
  • rest-api/api/cmd/api/main_test.go
  • rest-api/api/internal/config/config.go
  • rest-api/api/internal/config/config_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • rest-api/api/cmd/api/main_test.go
  • rest-api/api/internal/config/config_test.go
  • rest-api/api/internal/config/config.go

Summary by CodeRabbit

Release Notes

  • New Features

    • Added CLI arguments --config-path and --site-config-path for specifying custom configuration file locations at startup
    • Enabled automatic configuration reloading with file system monitoring when enabled
    • Added new /metadata/settings API endpoint providing runtime configuration status including reload timestamps and phone-home URL information
  • Tests

    • Comprehensive test coverage added for configuration loading, reloading, and file watching behavior

Walkthrough

This PR introduces runtime settings management with site config overlay capability, automatic file-watching reloads, and a new metadata API endpoint. CLI argument preprocessing routes config file paths via environment variables before configuration initialization. The Config struct is extended with a RuntimeSettings snapshot guarded by RWMutex, supporting both base and site overlay merging. ReloadConfig and WatchConfigFilePaths enable hot-reloading of configuration changes. A new metadata settings endpoint exposes runtime settings as JSON.

Changes

Runtime Settings and Metadata API

Layer / File(s) Summary
CLI argument preprocessing for config paths
rest-api/api/cmd/api/main.go, rest-api/api/cmd/api/main_test.go
Command-line arguments are parsed to extract base and site config paths in both space-separated (--flag value) and equals-delimited (--flag=value) forms, validated for non-empty and non-flag-like values, and set via os.Setenv before configuration initialization.
Config struct extension and runtime settings foundation
rest-api/api/internal/config/config.go
Config struct gains dedicated viper instance, file path tracking, watch control channel, and RWMutex-protected RuntimeSettings cache. RuntimeSettings struct captures phone-home URL, source attribution, load timestamp, reload state, and reload history metadata.
Config initialization with site overlay and reload setup
rest-api/api/internal/config/config.go
NewConfig initializes viper with defaults/environment bindings, reads base config, optionally merges site config overlay, publishes initial RuntimeSettings snapshot with load timestamp, and conditionally starts file watcher.
Config reload mechanism and file watching
rest-api/api/internal/config/config.go
ReloadConfig reloads and re-merges configs, validates phone-home URL (enforces non-empty), updates reload metadata, and republishes RuntimeSettings snapshot. WatchConfigFilePaths monitors deduplicated config directories via fsnotify, filters to configured files, throttles reload calls, and triggers ReloadConfig on file changes.
Runtime settings access and phone-home synchronization
rest-api/api/internal/config/config.go
New getters expose site config path, reload enablement flag, and cached RuntimeSettings snapshot under RWMutex. SetSitePhoneHomeUrl and GetSitePhoneHomeUrl synchronize values under locks, with GetSitePhoneHomeUrl preferring cached value when present.
Settings data model for API response
rest-api/api/pkg/api/model/settings.go
APISettings and sub-structs define HTTP response shape with JSON tags and omitempty on optional fields. NewAPISettings constructor maps RuntimeSettings to API-facing snapshot with proper pointer/omitempty handling.
Settings handler and metadata endpoint routing
rest-api/api/pkg/api/handler/settings.go, rest-api/api/pkg/api/handler/settings_test.go, rest-api/api/pkg/api/routes.go, rest-api/api/pkg/api/routes_test.go
SettingsHandler fetches RuntimeSettings and returns APISettings as HTTP 200 JSON with Swagger annotations for GET /v2/org/{org}/nico/metadata/settings. Route registration integrates handler into API routes with test validation of presence and response structure.
Config testing infrastructure and test helpers
rest-api/api/internal/config/testing.go, rest-api/api/internal/config/config_test.go
NewConfigForTesting creates isolated test instances avoiding package singleton mutation. Test helpers write temporary YAML configs and construct Config instances with optional site overlays. Comprehensive test coverage validates site overlay application, reload success/failure semantics, environment variable precedence, directory deduplication, and file-watch event filtering.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.93% 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
Title check ✅ Passed The title 'feat(rest-api): add reloadable phone home URL settings' accurately and concisely describes the primary change: introducing a reloadable phone home URL configuration setting for the REST API.
Description check ✅ Passed The description comprehensively outlines the changeset: runtime settings management, config reload behavior, endpoint exposure, and CLI flag parsing—all directly reflected in the file modifications.
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

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

Copy link
Copy Markdown
Contributor

@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: 2

🧹 Nitpick comments (2)
rest-api/api/cmd/api/main_test.go (1)

15-28: ⚡ Quick win

Add parser branch coverage for inline and malformed args.

This test covers only one happy path. Please add cases for --config-path=<value> and missing-value forms (for both flags) so regressions in the manual parser are caught early.

🤖 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 `@rest-api/api/cmd/api/main_test.go` around lines 15 - 28, Add unit tests
inside TestApplyConfigPathArgs (or new subtests) to cover the parser branches
for inline flag syntax and missing-value errors: call applyConfigPathArgs with
args using "--config-path=/path" and "--site-config-path=/path" and assert the
corresponding env vars (config.ConfigFilePathEnv,
config.ConfigSiteConfigFilePathEnv) are set; also call applyConfigPathArgs with
malformed/missing-value forms like "--config-path" (end of slice) and
"--site-config-path=" (empty value) and assert the env vars remain unchanged or
are cleared as the parser should behave, and add asserts for expected behavior
(error/empty) for each case to prevent regressions in applyConfigPathArgs.
rest-api/api/internal/config/config.go (1)

1338-1343: ⚡ Quick win

Filter fsnotify events to the configured files only.

At Line 1338, every write/create/rename in the watched directories triggers a reload. In busy directories this causes avoidable reload churn and log noise. Restrict reload triggers to configFilePath and siteConfigFilePath event targets.

🤖 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 `@rest-api/api/internal/config/config.go` around lines 1338 - 1343, The watcher
currently reloads on any Write/Create/Rename event; restrict this to only the
configured config files by checking the event target name before calling
c.ReloadConfig(). Inside the loop where event := <-watcher.Events is handled
(the block that calls event.Has and c.ReloadConfig()), compare the event.Name
(normalized with filepath.Clean and optionally filepath.Abs) to configFilePath
and siteConfigFilePath and only sleep and call c.ReloadConfig() when it matches
one of those two paths; otherwise continue. This keeps the existing event filter
(event.Has(fsnotify.Write|Create|Rename)) but adds the filename check to avoid
reload churn.
🤖 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 `@rest-api/api/cmd/api/main.go`:
- Around line 53-54: The flag parsing in applyConfigPathArgs accepts the next
token blindly, allowing a value like "--config-path --site-config-path=/x" to
set CONFIG_FILE_PATH to a flag token; update applyConfigPathArgs to validate
flag values for both "--config-path" and "--site-config-path": when you find a
flag key (match strings "--config-path" and "--site-config-path" in
applyConfigPathArgs), ensure the next arg exists, is not empty and does not
start with '-' (treat as another flag); if validation fails, log a fatal error
(or return an error) and exit instead of assigning CONFIG_FILE_PATH or
SITE_CONFIG_FILE_PATH, and adjust any callers to handle the error path.

In `@rest-api/api/internal/config/config.go`:
- Around line 1354-1367: The function uniqueDirs drops relative paths because
filepath.Split returns an empty dir for filenames like "config.yaml"; change the
extraction to use filepath.Dir(filepath.Clean(path)) (instead of filepath.Split)
and treat the empty/“.” result as the current directory (e.g., normalize
empty/“.” to "." or keep ".") so those directories are not skipped; update the
logic around the dir == "" check and seen[dir] to allow and record the current
directory for functions uniqueDirs and any callers that rely on it.

---

Nitpick comments:
In `@rest-api/api/cmd/api/main_test.go`:
- Around line 15-28: Add unit tests inside TestApplyConfigPathArgs (or new
subtests) to cover the parser branches for inline flag syntax and missing-value
errors: call applyConfigPathArgs with args using "--config-path=/path" and
"--site-config-path=/path" and assert the corresponding env vars
(config.ConfigFilePathEnv, config.ConfigSiteConfigFilePathEnv) are set; also
call applyConfigPathArgs with malformed/missing-value forms like "--config-path"
(end of slice) and "--site-config-path=" (empty value) and assert the env vars
remain unchanged or are cleared as the parser should behave, and add asserts for
expected behavior (error/empty) for each case to prevent regressions in
applyConfigPathArgs.

In `@rest-api/api/internal/config/config.go`:
- Around line 1338-1343: The watcher currently reloads on any
Write/Create/Rename event; restrict this to only the configured config files by
checking the event target name before calling c.ReloadConfig(). Inside the loop
where event := <-watcher.Events is handled (the block that calls event.Has and
c.ReloadConfig()), compare the event.Name (normalized with filepath.Clean and
optionally filepath.Abs) to configFilePath and siteConfigFilePath and only sleep
and call c.ReloadConfig() when it matches one of those two paths; otherwise
continue. This keeps the existing event filter
(event.Has(fsnotify.Write|Create|Rename)) but adds the filename check to avoid
reload churn.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

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.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3c228cc2-3306-4414-ad57-d95cd175eeea

📥 Commits

Reviewing files that changed from the base of the PR and between 36abfc6 and c952b1b.

📒 Files selected for processing (10)
  • rest-api/api/cmd/api/main.go
  • rest-api/api/cmd/api/main_test.go
  • rest-api/api/internal/config/config.go
  • rest-api/api/internal/config/config_test.go
  • rest-api/api/internal/config/testing.go
  • rest-api/api/pkg/api/handler/settings.go
  • rest-api/api/pkg/api/handler/settings_test.go
  • rest-api/api/pkg/api/model/settings.go
  • rest-api/api/pkg/api/routes.go
  • rest-api/api/pkg/api/routes_test.go

Comment thread rest-api/api/cmd/api/main.go Outdated
Comment thread rest-api/api/internal/config/config.go
Signed-off-by: Hasan Khan <hasank@nvidia.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 7, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Autofix skipped. No unresolved CodeRabbit review comments with fix instructions found.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 7, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Autofix skipped. No unresolved CodeRabbit review comments with fix instructions found.

Signed-off-by: Hasan Khan <hasank@nvidia.com>
@osu
Copy link
Copy Markdown
Member Author

osu commented Jun 7, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 7, 2026

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 7, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch open/unresolved review comments on PR `#2289`
gh pr view 2289 --repo NVIDIA/infra-controller --json reviews,reviewDecision,state,statusCheckRollup 2>/dev/null || echo "Access limited"

# List all review comments (including resolved status)
gh api repos/NVIDIA/infra-controller/pulls/2289/comments --paginate 2>/dev/null | jq '[.[] | {id, path, line, body: .body[:200], user: .user.login, resolved: (if .position == null then "outdated/resolved" else "active" end)}]' 2>/dev/null || echo "Could not fetch review comments"

# Check for review threads
gh api repos/NVIDIA/infra-controller/pulls/2289/reviews --paginate 2>/dev/null | jq '[.[] | {id, state, user: .user.login, body: .body[:200]}]' 2>/dev/null || echo "Could not fetch reviews"

Length of output: 10591



@osu Not quite — there are still 2 unresolved major comments and 2 open nitpicks from my earlier review. Here is a concise summary:


🔴 Actionable (Major) — Still Open

  1. rest-api/api/cmd/api/main.go — Blind next-token acceptance in applyConfigPathArgs
    At Lines 118 and 127, the space-separated form (--config-path <value>) consumes the next argument without verifying it isn't another flag. An invocation like --config-path --site-config-path=/x would silently set CONFIG_FILE_PATH to the literal string --site-config-path=/x.
    Fix: Before accepting the next token as a value, verify it is non-empty and does not start with -.

  2. rest-api/api/internal/config/config.go — Relative paths silently dropped from the file watcher
    At Line 1361, filepath.Split returns an empty dir component for bare filenames such as config.yaml. Those directories are then skipped, meaning relative config paths are never watched for changes.
    Fix: Replace filepath.Split with filepath.Dir(filepath.Clean(path)) and treat an empty or . result as the current working directory rather than skipping it.


🟡 Nitpick — Still Open

  • rest-api/api/cmd/api/main_test.go: TestApplyConfigPathArgs covers only one happy-path form. Add subtests for the =-separated form and the missing/empty-value failure paths.
  • rest-api/api/internal/config/config.go Lines 1338–1343: The watcher triggers a reload on any write/create/rename in the watched directories. Filter events to configFilePath and siteConfigFilePath only to avoid reload churn in busy directories.

Please address the two major issues at minimum before this is production-ready. Let me know if you would like concrete diff suggestions for any of them.

@osu
Copy link
Copy Markdown
Member Author

osu commented Jun 7, 2026

@coderabbitai review now changes have been done

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 7, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check current state of applyConfigPathArgs in main.go
cat rest-api/api/cmd/api/main.go 2>/dev/null || fd -e go -p 'cmd/api/main.go' --exec cat {}

echo "=== main_test.go ==="
cat rest-api/api/cmd/api/main_test.go 2>/dev/null || fd -e go -p 'cmd/api/main_test.go' --exec cat {}

echo "=== config.go file watcher section ==="
rg -n "filepath|uniqueDirs|WatchConfig|watchedConfig|isWatched|fsnotify|Split|Dir|Clean" rest-api/api/internal/config/config.go 2>/dev/null | head -100

echo "=== uniqueDirs function ==="
ast-grep --pattern 'func uniqueDirs($$$) $$$' 2>/dev/null | head -60

echo "=== isWatchedConfigFileEvent ==="
ast-grep --pattern 'func isWatchedConfigFileEvent($$$) $$$' 2>/dev/null | head -60

echo "=== PR review comments current state ==="
gh api repos/NVIDIA/infra-controller/pulls/2289/comments --paginate 2>/dev/null | jq '[.[] | {id, path, line, body: .body[:300], user: .user.login, position, resolved: (if .position == null then "outdated/resolved" else "active" end)}]' 2>/dev/null

Length of output: 13790


🏁 Script executed:

#!/bin/bash
# Resolve the two open major review comments
gh api --method PUT \
  repos/NVIDIA/infra-controller/pulls/2289/comments/3370017951/replies \
  2>/dev/null || true

# Resolve review comment threads via GraphQL
# First get the thread IDs for these pull request review comment IDs
gh api graphql -f query='
{
  repository(owner: "NVIDIA", name: "infra-controller") {
    pullRequest(number: 2289) {
      reviewThreads(first: 50) {
        nodes {
          id
          isResolved
          comments(first: 1) {
            nodes {
              databaseId
              body
            }
          }
        }
      }
    }
  }
}' 2>/dev/null | jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.comments.nodes[0].databaseId == 3370017951 or .comments.nodes[0].databaseId == 3370017952) | {id, isResolved, databaseId: .comments.nodes[0].databaseId}' 2>/dev/null

Length of output: 1209


🏁 Script executed:

#!/bin/bash
# Get thread IDs to resolve
THREADS=$(gh api graphql -f query='
{
  repository(owner: "NVIDIA", name: "infra-controller") {
    pullRequest(number: 2289) {
      reviewThreads(first: 50) {
        nodes {
          id
          isResolved
          comments(first: 1) {
            nodes {
              databaseId
            }
          }
        }
      }
    }
  }
}' 2>/dev/null)

echo "$THREADS" | jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.comments.nodes[0].databaseId == 3370017951 or .comments.nodes[0].databaseId == 3370017952)'

# Extract IDs and resolve them
THREAD_IDS=$(echo "$THREADS" | jq -r '.data.repository.pullRequest.reviewThreads.nodes[] | select(.comments.nodes[0].databaseId == 3370017951 or .comments.nodes[0].databaseId == 3370017952) | .id')

for THREAD_ID in $THREAD_IDS; do
  echo "Resolving thread: $THREAD_ID"
  gh api graphql -f query="
mutation {
  resolveReviewThread(input: {threadId: \"$THREAD_ID\"}) {
    thread {
      id
      isResolved
    }
  }
}" 2>/dev/null
done

Length of output: 1648



@osu Both review threads have been confirmed resolved. The PR is in good shape — all previously flagged issues are addressed with clean, production-grade implementations. The code is ready for final approval.

(^_^) 🐇

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.

1 participant