From 40f11efdf1d38674d76ee5a0e5d9179ee3125577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20JACQUES?= Date: Fri, 6 Oct 2023 17:54:43 +0200 Subject: [PATCH] [receiver/kubeletstats] Support specifying context for `kubeConfig` `auth_type` (#26665) (#27240) **Description:** Support specifying context for `kubeConfig` `auth_type` when communicating with a Kubernetes cluster. **Link to tracking Issue:** 26665 **Testing:** unit test added **Documentation:** kubeletstatsreceiver README updated Signed-off-by: JACQUES Francois --- .chloggen/kubelet_kubeconfig_context.yaml | 25 +++++++ internal/k8sconfig/config.go | 6 ++ internal/kubelet/client_test.go | 82 ++++++++++++++--------- internal/kubelet/testdata/kubeconfig | 18 +++-- receiver/kubeletstatsreceiver/README.md | 2 + 5 files changed, 99 insertions(+), 34 deletions(-) create mode 100755 .chloggen/kubelet_kubeconfig_context.yaml diff --git a/.chloggen/kubelet_kubeconfig_context.yaml b/.chloggen/kubelet_kubeconfig_context.yaml new file mode 100755 index 000000000000..9b2c44960339 --- /dev/null +++ b/.chloggen/kubelet_kubeconfig_context.yaml @@ -0,0 +1,25 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: kubeletstatsreceiver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Support specifying context for `kubeConfig` `auth_type` + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [26665] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] diff --git a/internal/k8sconfig/config.go b/internal/k8sconfig/config.go index 11d0f544ec20..b3ac3d13f334 100644 --- a/internal/k8sconfig/config.go +++ b/internal/k8sconfig/config.go @@ -54,6 +54,9 @@ type APIConfig struct { // token provided to the agent pod), or `kubeConfig` to use credentials // from `~/.kube/config`. AuthType AuthType `mapstructure:"auth_type"` + + // When using auth_type `kubeConfig`, override the current context. + Context string `mapstructure:"context"` } // Validate validates the K8s API config @@ -85,6 +88,9 @@ func CreateRestConfig(apiConf APIConfig) (*rest.Config, error) { case AuthTypeKubeConfig: loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() configOverrides := &clientcmd.ConfigOverrides{} + if apiConf.Context != "" { + configOverrides.CurrentContext = apiConf.Context + } authConf, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig( loadingRules, configOverrides).ClientConfig() diff --git a/internal/kubelet/client_test.go b/internal/kubelet/client_test.go index 1e0ee7c5cc75..d9fa5f6547f6 100644 --- a/internal/kubelet/client_test.go +++ b/internal/kubelet/client_test.go @@ -154,37 +154,59 @@ func TestSAClientBadTLS(t *testing.T) { } func TestNewKubeConfigClient(t *testing.T) { - server := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - // Check if call is authenticated using provided kubeconfig - require.Equal(t, req.Header.Get("Authorization"), "Bearer my-token") - require.Equal(t, "/api/v1/nodes/nodename/proxy/", req.URL.EscapedPath()) - // Send response to be tested - _, err := rw.Write([]byte(`OK`)) - require.NoError(t, err) - })) - server.StartTLS() - defer server.Close() - - kubeConfig, err := clientcmd.LoadFromFile("testdata/kubeconfig") - require.NoError(t, err) - kubeConfig.Clusters["my-cluster"].Server = "https://" + server.Listener.Addr().String() - tempKubeConfig := filepath.Join(t.TempDir(), "kubeconfig") - require.NoError(t, clientcmd.WriteToFile(*kubeConfig, tempKubeConfig)) - t.Setenv("KUBECONFIG", tempKubeConfig) - - p, err := NewClientProvider("nodename", &ClientConfig{ - APIConfig: k8sconfig.APIConfig{ - AuthType: k8sconfig.AuthTypeKubeConfig, + tests := []struct { + name string + cluster string + context string + }{ + { + name: "current context", + cluster: "my-cluster-1", + context: "", }, - InsecureSkipVerify: true, - }, zap.NewNop()) - require.NoError(t, err) - require.NotNil(t, p) - client, err := p.BuildClient() - require.NoError(t, err) - resp, err := client.Get("/") - require.NoError(t, err) - require.Equal(t, []byte(`OK`), resp) + { + name: "override context", + cluster: "my-cluster-2", + context: "my-context-2", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + // Check if call is authenticated using provided kubeconfig + require.Equal(t, req.Header.Get("Authorization"), "Bearer my-token") + require.Equal(t, "/api/v1/nodes/nodename/proxy/", req.URL.EscapedPath()) + // Send response to be tested + _, err := rw.Write([]byte(`OK`)) + require.NoError(t, err) + })) + server.StartTLS() + defer server.Close() + + kubeConfig, err := clientcmd.LoadFromFile("testdata/kubeconfig") + require.NoError(t, err) + kubeConfig.Clusters[tt.cluster].Server = "https://" + server.Listener.Addr().String() + tempKubeConfig := filepath.Join(t.TempDir(), "kubeconfig") + require.NoError(t, clientcmd.WriteToFile(*kubeConfig, tempKubeConfig)) + t.Setenv("KUBECONFIG", tempKubeConfig) + + p, err := NewClientProvider("nodename", &ClientConfig{ + APIConfig: k8sconfig.APIConfig{ + AuthType: k8sconfig.AuthTypeKubeConfig, + Context: tt.context, + }, + InsecureSkipVerify: true, + }, zap.NewNop()) + require.NoError(t, err) + require.NotNil(t, p) + client, err := p.BuildClient() + require.NoError(t, err) + resp, err := client.Get("/") + require.NoError(t, err) + require.Equal(t, []byte(`OK`), resp) + }) + } } func TestBuildEndpoint(t *testing.T) { diff --git a/internal/kubelet/testdata/kubeconfig b/internal/kubelet/testdata/kubeconfig index 6b2882be2570..adf9ab3698df 100644 --- a/internal/kubelet/testdata/kubeconfig +++ b/internal/kubelet/testdata/kubeconfig @@ -5,14 +5,24 @@ clusters: - cluster: certificate-authority-data: Y2VydA== server: https://my-cluster.address - name: my-cluster + name: my-cluster-1 +- cluster: + certificate-authority-data: Y2VydA== + server: https://my-cluster.address + name: my-cluster-2 contexts: - context: - cluster: my-cluster + cluster: my-cluster-1 + namespace: my-namespace + user: my-user + name: my-context-1 +- context: + cluster: my-cluster-2 namespace: my-namespace user: my-user - name: my-context -current-context: my-context + name: my-context-2 + +current-context: my-context-1 users: - name: my-user user: diff --git a/receiver/kubeletstatsreceiver/README.md b/receiver/kubeletstatsreceiver/README.md index eabebe511ffd..f69d3a7eb915 100644 --- a/receiver/kubeletstatsreceiver/README.md +++ b/receiver/kubeletstatsreceiver/README.md @@ -132,6 +132,7 @@ receivers: kubeletstats: collection_interval: 20s auth_type: "kubeConfig" + context: "my-context" insecure_skip_verify: true endpoint: "${env:K8S_NODE_NAME}" exporters: @@ -145,6 +146,7 @@ service: ``` Note that using `auth_type` `kubeConfig`, the endpoint should only be the node name as the communication to the kubelet is proxied by the API server configured in the `kubeConfig`. `insecure_skip_verify` still applies by overriding the `kubeConfig` settings. +If no `context` is specified, the current context or the default context is used. ### Extra metadata labels