Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/cmd/headlamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const (
type clientConfig struct {
Clusters []Cluster `json:"clusters"`
IsDynamicClusterEnabled bool `json:"isDynamicClusterEnabled"`
PrometheusEndpoint string `json:"prometheusEndpoint"`
}

type OauthConfig struct {
Expand Down Expand Up @@ -1750,7 +1751,7 @@ func parseClusterFromKubeConfig(kubeConfigs []string) ([]Cluster, []error) {
func (c *HeadlampConfig) getConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

clientConfig := clientConfig{c.getClusters(), c.EnableDynamicClusters}
clientConfig := clientConfig{c.getClusters(), c.EnableDynamicClusters, c.PrometheusEndpoint}

Comment on lines +1754 to 1755
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

This code references c.PrometheusEndpoint, but HeadlampConfig embeds headlampconfig.HeadlampConfig/HeadlampCFG, which currently do not include a PrometheusEndpoint field. This will fail to compile until the config structs are updated to carry this value (or the code is changed to retrieve it from the correct place).

Copilot uses AI. Check for mistakes.
if err := json.NewEncoder(w).Encode(&clientConfig); err != nil {
logger.Log(logger.LevelError, nil, err, "encoding config")
Expand Down
1 change: 1 addition & 0 deletions backend/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func buildHeadlampCFG(conf *config.Config, kubeConfigStore kubeconfig.ContextSto
TLSCertPath: conf.TLSCertPath,
TLSKeyPath: conf.TLSKeyPath,
SessionTTL: conf.SessionTTL,
PrometheusEndpoint: *conf.PrometheusEndpoint,
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

PrometheusEndpoint is being set on headlampconfig.HeadlampCFG, but that struct currently does not define this field (backend/pkg/headlampconfig/headlampConfig.go). As-is, this change will not compile; add the field to HeadlampCFG (and wire it through wherever HeadlampCFG is constructed) or remove this assignment.

Suggested change
PrometheusEndpoint: *conf.PrometheusEndpoint,

Copilot uses AI. Check for mistakes.
}
}

Expand Down
2 changes: 1 addition & 1 deletion backend/cmd/stateless.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func (c *HeadlampConfig) parseKubeConfig(w http.ResponseWriter, r *http.Request)
return
}

clientConfig := clientConfig{contexts, c.EnableDynamicClusters}
clientConfig := clientConfig{contexts, c.EnableDynamicClusters, c.PrometheusEndpoint}
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

This code references c.PrometheusEndpoint, but the embedded backend config structs don’t currently define this field. Until the config structs are updated to include PrometheusEndpoint, this change will not compile.

Suggested change
clientConfig := clientConfig{contexts, c.EnableDynamicClusters, c.PrometheusEndpoint}
clientConfig := clientConfig{contexts, c.EnableDynamicClusters, ""}

Copilot uses AI. Check for mistakes.

if err := json.NewEncoder(w).Encode(&clientConfig); err != nil {
logger.Log(logger.LevelError, nil, err, "encoding config")
Expand Down
16 changes: 14 additions & 2 deletions backend/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ type Config struct {
StdoutTraceEnabled *bool `koanf:"stdout-trace-enabled"`
SamplingRate *float64 `koanf:"sampling-rate"`
// TLS config
TLSCertPath string `koanf:"tls-cert-path"`
TLSKeyPath string `koanf:"tls-key-path"`
TLSCertPath string `koanf:"tls-cert-path"`
TLSKeyPath string `koanf:"tls-key-path"`
PrometheusEndpoint *string `koanf:"prometheus-endpoint"`
}

func (c *Config) Validate() error {
Expand Down Expand Up @@ -144,6 +145,16 @@ func (c *Config) Validate() error {
}
}

return c.validatePrometheusEndpoint()
}

func (c *Config) validatePrometheusEndpoint() error {
if c.PrometheusEndpoint != nil && *c.PrometheusEndpoint != "" {
if !strings.HasPrefix(*c.PrometheusEndpoint, "http://") && !strings.HasPrefix(*c.PrometheusEndpoint, "https://") {
return errors.New("prometheus-endpoint must start with http:// or https://")
}
}
Comment on lines +148 to +156
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

New validation logic for prometheus-endpoint is added, but there are no config parsing/validation tests covering valid/invalid values (e.g. http://, https://, missing scheme). Adding tests alongside existing TestParseErrors cases would prevent regressions.

Copilot uses AI. Check for mistakes.

return nil
}

Expand Down Expand Up @@ -448,6 +459,7 @@ func addGeneralFlags(f *flag.FlagSet) {
f.Uint("port", defaultPort, "Port to listen from")
f.String("proxy-urls", "", "Allow proxy requests to specified URLs")
f.Bool("enable-helm", false, "Enable Helm operations")
f.String("prometheus-endpoint", "", "Prometheus endpoint for the cluster")
}
Comment on lines 459 to 463
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

The new --prometheus-endpoint flag/env var is not covered by the existing config parsing tests (backend/pkg/config/config_test.go). Add a ParseFlags/ParseWithEnv test to ensure the value is correctly read and propagated (including the empty/unset case).

Copilot uses AI. Check for mistakes.

func addOIDCFlags(f *flag.FlagSet) {
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/headlampconfig/headlampConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,5 @@ type HeadlampCFG struct {
TLSCertPath string
TLSKeyPath string
SessionTTL int
PrometheusEndpoint string
}
53 changes: 46 additions & 7 deletions backend/pkg/kubeconfig/kubeconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"path/filepath"
"testing"

"github.com/kubernetes-sigs/headlamp/backend/pkg/config"
"github.com/kubernetes-sigs/headlamp/backend/pkg/kubeconfig"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -314,12 +313,55 @@ func createTempKubeconfig(t *testing.T, content string) string {
return tempFile.Name()
}

// setupMockK8sServer creates a test HTTP server and a temporary kubeconfig pointing to it.
// Returns the server, temp kubeconfig path, and a cleanup function.
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

The helper comment says it returns a cleanup function, but the function only returns the server and temp kubeconfig path. Either update the comment to match what is returned, or return a cleanup func (e.g. to close the server and remove the temp file) to keep the test setup self-contained.

Suggested change
// Returns the server, temp kubeconfig path, and a cleanup function.
// It returns the server and the path to the temporary kubeconfig file.

Copilot uses AI. Check for mistakes.
func setupMockK8sServer(t *testing.T) (*httptest.Server, string) {
t.Helper()

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/version" {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"major": "1", "minor": "20"}`))

return
}

w.WriteHeader(http.StatusNotFound)
}))

kubeConfigContent := fmt.Sprintf(`apiVersion: v1
clusters:
- cluster:
server: %s
insecure-skip-tls-verify: true
name: minikube
contexts:
- context:
cluster: minikube
namespace: default
user: minikube
name: minikube
current-context: minikube
kind: Config
users:
- name: minikube
user:
token: test-token
`, server.URL)

tempFile := createTempKubeconfig(t, kubeConfigContent)

return server, tempFile
}

func TestContext(t *testing.T) {
kubeConfigFile := config.GetDefaultKubeConfigPath()
server, tempFile := setupMockK8sServer(t)
defer server.Close()
defer os.Remove(tempFile)

configStore := kubeconfig.NewContextStore()

err := kubeconfig.LoadAndStoreKubeConfigs(configStore, kubeConfigFile, kubeconfig.KubeConfig, nil)
err := kubeconfig.LoadAndStoreKubeConfigs(configStore, tempFile, kubeconfig.KubeConfig, nil)
require.NoError(t, err)

testContext, err := configStore.GetContext("minikube")
Expand All @@ -333,18 +375,15 @@ func TestContext(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, restConf)

// Test proxy request handler

request, err := http.NewRequestWithContext(context.Background(), "GET", "/version", nil)
require.NoError(t, err)

rr := httptest.NewRecorder()

err = testContext.ProxyRequest(rr, request)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rr.Code)

t.Logf("Proxy request Response: %s", rr.Body.String())
assert.Equal(t, http.StatusOK, rr.Code)
assert.Contains(t, rr.Body.String(), "major")
assert.Contains(t, rr.Body.String(), "minor")
}
Expand Down
3 changes: 3 additions & 0 deletions charts/headlamp/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ spec:
{{- with .Values.config.baseURL }}
- "-base-url={{ . }}"
{{- end }}
{{- with .Values.config.prometheus.endpoint }}
- "-prometheus-endpoint={{ . | quote }}"
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

The Helm template wraps the endpoint with quote inside an already-quoted arg string, which will render as -prometheus-endpoint="http://..." (literal quotes included). This will cause the backend flag value to include quotes and fail the http(s):// prefix validation. Remove the | quote filter here, or alternatively remove the outer YAML quotes and rely on quote for YAML safety.

Suggested change
- "-prometheus-endpoint={{ . | quote }}"
- "-prometheus-endpoint={{ . }}"

Copilot uses AI. Check for mistakes.
{{- end }}
Comment on lines 322 to 324
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

Helm values are injected directly into a double-quoted YAML string here. If the endpoint ever contains characters that need escaping (e.g., quotes), it can render invalid YAML. Prefer using Helm quoting/printf patterns that safely escape the value when building the arg string.

Copilot uses AI. Check for mistakes.
{{- with .Values.config.tlsCertPath }}
- "-tls-cert-path={{ . }}"
{{- end }}
Expand Down
22 changes: 9 additions & 13 deletions charts/headlamp/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ config:
baseURL: ""
# -- session token TTL in seconds (default is 24 hours)
sessionTTL: 86400
prometheus:
# -- Prometheus endpoint for the cluster
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

A new chart value (config.prometheus.endpoint) was added, but the Helm chart documentation/typing should be kept in sync. Update charts/headlamp/README.md configuration tables and charts/headlamp/values.schema.json to include this value (and any constraints/description).

Suggested change
# -- Prometheus endpoint for the cluster
# -- Prometheus HTTP(S) endpoint URL used by Headlamp to query metrics for the cluster. Leave empty to disable Prometheus integration.

Copilot uses AI. Check for mistakes.
endpoint: ""
oidc:
# Option 1:
# @param config.oidc.secret - OIDC secret configuration
Expand Down Expand Up @@ -161,8 +164,7 @@ podLabels: {}
hostUsers: true

# -- Headlamp pod's Security Context
podSecurityContext:
{}
podSecurityContext: {}
# fsGroup: 2000

# -- Headlamp containers Security Context
Expand All @@ -184,7 +186,6 @@ securityContext:
# drop:
# - ALL


service:
# -- Annotations to add to the service
annotations: {}
Expand All @@ -211,8 +212,7 @@ persistentVolumeClaim:
# -- Enable Persistent Volume Claim
enabled: false
# -- Annotations to add to the persistent volume claim (if enabled)
annotations:
{}
annotations: {}
# -- accessModes for the persistent volume claim, eg: ReadWriteOnce, ReadOnlyMany, ReadWriteMany etc.
accessModes: []
# -- size of the persistent volume claim, eg: 10Gi. Required if enabled is true.
Expand All @@ -228,8 +228,7 @@ ingress:
# -- Enable ingress controller resource
enabled: false
# -- Annotations for Ingress resource
annotations:
{}
annotations: {}
# kubernetes.io/tls-acme: "true"

# -- Additional labels to add to the Ingress resource
Expand All @@ -242,8 +241,7 @@ ingress:

# -- Hostname(s) for the Ingress resource
# Please refer to https://kubernetes.io/docs/reference/kubernetes-api/service-resources/ingress-v1/#IngressSpec for more information.
hosts:
[]
hosts: []
# - host: chart-example.local
# paths:
# - path: /
Expand Down Expand Up @@ -288,8 +286,7 @@ httpRoute:
# port: 80

# -- CPU/Memory resource requests/limits
resources:
{}
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
Expand Down Expand Up @@ -353,8 +350,7 @@ pluginsManager:
# cpu: "1000m"
# memory: "4096Mi"
# If omitted, the plugin manager will inherit the global securityContext
securityContext:
{}
securityContext: {}
# runAsUser: 1001
# runAsNonRoot: true
# allowPrivilegeEscalation: false
Expand Down
Loading
Loading