Skip to content

backend: kubeconfig: Stop treating empty path and missing file as load errors#5731

Open
govindup63 wants to merge 1 commit into
kubernetes-sigs:mainfrom
govindup63:fix/4724-incluster-kubeconfig-log-noise
Open

backend: kubeconfig: Stop treating empty path and missing file as load errors#5731
govindup63 wants to merge 1 commit into
kubernetes-sigs:mainfrom
govindup63:fix/4724-incluster-kubeconfig-log-noise

Conversation

@govindup63
Copy link
Copy Markdown
Contributor

Summary

Two long-standing issues report the same root cause: a default in-cluster Headlamp deployment writes two ERROR lines to its pod log on every startup, even when nothing is wrong.

{"level":"error","source":".../backend/cmd/headlamp.go","line":496,
 "error":"error loading kubeconfig files: error reading kubeconfig file: open : no such file or directory",
 "message":"loading kubeconfig"}
{"level":"error","source":".../backend/cmd/headlamp.go","line":516,
 "error":"error loading kubeconfig files: error reading kubeconfig file: open /home/headlamp/.config/Headlamp/kubeconfigs/config: no such file or directory",
 "message":"loading dynamic kubeconfig"}

The first line comes from LoadAndStoreKubeConfigs(store, "", ...) because in-cluster mode leaves KubeConfigPath empty (the service account token is the actual credential). The second comes from LoadAndStoreKubeConfigs(store, ~/.config/Headlamp/kubeconfigs/config, ...): this file is owned by Headlamp itself and is absent on a fresh pod until the user adds the first dynamic cluster. Both situations are normal but were both treated as ERROR.

Related Issues

Issue: #4724
Issue: #4401

Changes

  • backend/pkg/kubeconfig/kubeconfig.go::LoadContextsFromFile
    • If the path is empty, return (nil, nil, nil). There is nothing to read.
    • If os.ReadFile fails with errors.Is(err, fs.ErrNotExist), return (nil, nil, nil). The file simply isn't there.
    • All other read errors (permission denied, malformed file, etc.) keep their existing behavior.
    • Bonus: switch the existing wrap from fmt.Errorf("...: %v", err) to ... %w, so callers can use errors.Is on the chain.
  • backend/pkg/kubeconfig/kubeconfig_test.go
    • Rename the existing invalid_file subtests in TestLoadAndStoreKubeConfigs and TestLoadContextsFromKubeConfigFile to missing_file, and flip their assertion to NoError. The old name was misleading because the test was passing a path to a file that does not exist on disk, not a malformed kubeconfig.
    • Add a new empty_path subtest to each of the two test groups, covering the in-cluster scenario directly.

The fix cascades through every caller: createHeadlampHandler at the two startup load sites (cmd/headlamp.go:522 and :541), and the two reload paths in pkg/kubeconfig/watcher.go. None of those call sites needed to change.

Steps to Test

  1. cd backend && go test ./pkg/kubeconfig/ -run 'TestLoadAndStoreKubeConfigs|TestLoadContextsFromKubeConfigFile' -v. All subtests pass, including the new missing_file and empty_path cases.
  2. Revert only kubeconfig.go and rerun: both missing_file and empty_path subtests fail with exactly the error from the reporters, demonstrating the regression test catches the bug.
  3. cd backend && go test ./pkg/kubeconfig/... ./cmd/... -race. Clean.
  4. cd backend && go build ./... && go vet ./pkg/kubeconfig/ ./cmd/. Clean.

I also ran the full backend suite. One race finding in pkg/telemetry/TestResponseWriterHijack_SucceedsWithUnderlyingHijacker exists on main independent of this change (git stash && go test ./pkg/telemetry/... still fails) and is out of scope here.

Notes for the Reviewer

  • This intentionally trades off "loud about every missing kubeconfig" for "quiet about the common, expected cases". A user who passes --kubeconfig=/typo/path will no longer see an ERROR line for that typo, only the (already existing) absence of contexts in the UI. If you'd prefer an INFO-level "skipping nonexistent kubeconfig path: ..." log at the call sites in createHeadlampHandler, I'm happy to add it.
  • The %v%w swap is harmless on its own (the message string is identical) and gives callers an errors.Is/errors.As foothold if they ever need to distinguish error kinds in the future.

…d errors

LoadContextsFromFile previously returned

    error loading kubeconfig files: error reading kubeconfig file:
    open : no such file or directory

whenever the caller passed an empty path, and

    error loading kubeconfig files: error reading kubeconfig file:
    open .../kubeconfigs/config: no such file or directory

whenever the kubeconfig file simply did not exist on disk. The default
in-cluster mode passes "" for the static kubeconfig (the service
account token is the actual credential), and the dynamic-cluster
persistence file lives at ~/.config/Headlamp/kubeconfigs/config which
is absent on a fresh in-cluster pod until the user adds the first
dynamic cluster. Both situations are normal, yet createHeadlampHandler
logged them at ERROR level on every startup, polluting pod logs.

Treat both as no-ops in LoadContextsFromFile:

  - Empty path: nothing to read, return (nil, nil, nil).
  - Path resolves to a file that does not exist
    (errors.Is(err, fs.ErrNotExist)): return (nil, nil, nil).
  - Other read errors (permission denied, etc.): unchanged.

Also switch the existing fmt.Errorf from %v to %w so callers can use
errors.Is on the wrapped error in the future.

Update the existing invalid_file subtests in TestLoadAndStoreKubeConfigs
and TestLoadContextsFromKubeConfigFile to reflect the new contract,
and add empty_path regression subtests.

Issue: kubernetes-sigs#4724
Issue: kubernetes-sigs#4401
Signed-off-by: Govind Pandey <govindup63@gmail.com>
@k8s-ci-robot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: govindup63
Once this PR has been reviewed and has the lgtm label, please assign yolossn for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels May 18, 2026
@k8s-ci-robot k8s-ci-robot requested review from ashu8912 and sniok May 18, 2026 00:07
@illume illume requested a review from Copilot May 18, 2026 07:09
Copy link
Copy Markdown
Contributor

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

Eliminates two spurious ERROR log lines emitted on every in-cluster Headlamp startup by treating an empty kubeconfig path and a non-existent kubeconfig file as a no-op rather than a load failure. The change is confined to LoadContextsFromFile, with existing callers (startup loaders in cmd/headlamp.go and the watcher) inheriting the new behavior unchanged.

Changes:

  • In LoadContextsFromFile, short-circuit when the path is empty and ignore fs.ErrNotExist, returning (nil, nil, nil).
  • Switch the read-error wrap from %v to %w to allow errors.Is/As on the chain.
  • Rename the misnamed invalid_file subtests to missing_file, flip them to NoError, and add new empty_path subtests in kubeconfig_test.go.

Reviewed changes

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

File Description
backend/pkg/kubeconfig/kubeconfig.go Skip empty paths and treat missing kubeconfig files as no-op; use %w wrap.
backend/pkg/kubeconfig/kubeconfig_test.go Rename/flip invalid_file to missing_file and add empty_path regression tests.

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

Labels

cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/M Denotes a PR that changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants