Skip to content

Commit afc51eb

Browse files
committed
fix(k8s-client): fix error classification for failed kubeconfig ingestion
Signed-off-by: Tjark Gunnar Rasche <trasche@nvidia.com>
1 parent 755c37a commit afc51eb

2 files changed

Lines changed: 95 additions & 15 deletions

File tree

pkg/k8s/client/client.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,19 @@ func BuildKubeClient(kubeconfig string) (*kubernetes.Clientset, *rest.Config, er
175175
} else {
176176
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
177177
if err != nil {
178-
return nil, nil, errors.WrapWithContext(errors.ErrCodeInternal, "failed to build kube config", err, map[string]interface{}{
178+
return nil, nil, errors.WrapWithContext(errors.ErrCodeInvalidRequest, "failed to build kube config", err, map[string]interface{}{
179179
"kubeconfig": kubeconfig,
180180
})
181181
}
182182
}
183183

184184
client, err := kubernetes.NewForConfig(config)
185185
if err != nil {
186+
if kubeconfig != "" {
187+
return nil, nil, errors.WrapWithContext(errors.ErrCodeInvalidRequest,
188+
"failed to create kubernetes client from kubeconfig", err,
189+
map[string]interface{}{"kubeconfig": kubeconfig})
190+
}
186191
return nil, nil, errors.Wrap(errors.ErrCodeInternal, "failed to create kubernetes client", err)
187192
}
188193

pkg/k8s/client/client_test.go

Lines changed: 89 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,32 @@
1515
package client
1616

1717
import (
18+
stderrors "errors"
1819
"os"
1920
"path/filepath"
2021
"strings"
2122
"sync"
2223
"testing"
24+
25+
"github.com/NVIDIA/aicr/pkg/errors"
2326
)
2427

28+
func assertKubeconfigErrorContext(t *testing.T, err error, wantKubeconfig string) {
29+
t.Helper()
30+
31+
var structuredErr *errors.StructuredError
32+
if !stderrors.As(err, &structuredErr) {
33+
t.Fatalf("error = %v, want *errors.StructuredError", err)
34+
}
35+
gotKubeconfig, ok := structuredErr.Context["kubeconfig"].(string)
36+
if !ok {
37+
t.Fatalf("error context kubeconfig = %v, want string", structuredErr.Context["kubeconfig"])
38+
}
39+
if gotKubeconfig != wantKubeconfig {
40+
t.Errorf("error context kubeconfig = %q, want %q", gotKubeconfig, wantKubeconfig)
41+
}
42+
}
43+
2544
// TestBuildKubeClient_PathResolution tests the kubeconfig path resolution logic
2645
// without attempting to connect to a cluster.
2746
func TestBuildKubeClient_PathResolution(t *testing.T) {
@@ -30,24 +49,30 @@ func TestBuildKubeClient_PathResolution(t *testing.T) {
3049
t.Setenv("KUBECONFIG", os.Getenv("KUBECONFIG"))
3150

3251
tests := []struct {
33-
name string
34-
kubeconfigArg string
35-
kubeconfigEnv string
36-
wantErr bool
37-
errorContains string
52+
name string
53+
kubeconfigArg string
54+
kubeconfigEnv string
55+
wantErr bool
56+
errorContains string
57+
wantCode errors.ErrorCode
58+
wantKubeconfig string
3859
}{
3960
{
40-
name: "explicit invalid path",
41-
kubeconfigArg: "/nonexistent/path/to/kubeconfig",
42-
wantErr: true,
43-
errorContains: "failed to build kube config",
61+
name: "explicit invalid path",
62+
kubeconfigArg: " /nonexistent/path/to/kubeconfig ",
63+
wantErr: true,
64+
errorContains: "failed to build kube config",
65+
wantCode: errors.ErrCodeInvalidRequest,
66+
wantKubeconfig: "/nonexistent/path/to/kubeconfig",
4467
},
4568
{
46-
name: "env var with invalid path",
47-
kubeconfigArg: "",
48-
kubeconfigEnv: "/nonexistent/env/kubeconfig",
49-
wantErr: true,
50-
errorContains: "failed to build kube config",
69+
name: "env var with invalid path",
70+
kubeconfigArg: "",
71+
kubeconfigEnv: "/nonexistent/env/kubeconfig",
72+
wantErr: true,
73+
errorContains: "failed to build kube config",
74+
wantCode: errors.ErrCodeInvalidRequest,
75+
wantKubeconfig: "/nonexistent/env/kubeconfig",
5176
},
5277
}
5378

@@ -72,6 +97,12 @@ func TestBuildKubeClient_PathResolution(t *testing.T) {
7297
t.Errorf("BuildKubeClient() error = %v, want error containing %q", err, tt.errorContains)
7398
}
7499
}
100+
if err != nil && tt.wantCode != "" && !stderrors.Is(err, errors.New(tt.wantCode, "")) {
101+
t.Errorf("BuildKubeClient() error = %v, want code %s", err, tt.wantCode)
102+
}
103+
if err != nil && tt.wantKubeconfig != "" {
104+
assertKubeconfigErrorContext(t, err, tt.wantKubeconfig)
105+
}
75106
})
76107
}
77108
}
@@ -117,6 +148,50 @@ func TestBuildKubeClient_ExplicitPath(t *testing.T) {
117148
if !strings.Contains(err.Error(), "failed to build kube config") {
118149
t.Errorf("BuildKubeClient() error = %v, want error containing 'failed to build kube config'", err)
119150
}
151+
if !stderrors.Is(err, errors.New(errors.ErrCodeInvalidRequest, "")) {
152+
t.Errorf("BuildKubeClient() error = %v, want ErrCodeInvalidRequest", err)
153+
}
154+
assertKubeconfigErrorContext(t, err, invalidConfig)
155+
}
156+
157+
// TestBuildKubeClient_InvalidClientConfigReturnsInvalidRequest verifies that a
158+
// kubeconfig which parses successfully but cannot initialize a Kubernetes
159+
// client is still classified as caller input rather than an internal failure.
160+
func TestBuildKubeClient_InvalidClientConfigReturnsInvalidRequest(t *testing.T) {
161+
kubeconfig := filepath.Join(t.TempDir(), "invalid-client-config")
162+
content := `apiVersion: v1
163+
kind: Config
164+
clusters:
165+
- name: test
166+
cluster:
167+
server: https://127.0.0.1
168+
certificate-authority-data: bm90IGEgcGVtIGNlcnRpZmljYXRl
169+
contexts:
170+
- name: test
171+
context:
172+
cluster: test
173+
user: test
174+
current-context: test
175+
users:
176+
- name: test
177+
user:
178+
token: test
179+
`
180+
if err := os.WriteFile(kubeconfig, []byte(content), 0o600); err != nil {
181+
t.Fatalf("failed to write test kubeconfig: %v", err)
182+
}
183+
184+
_, _, err := BuildKubeClient(kubeconfig)
185+
if err == nil {
186+
t.Fatal("BuildKubeClient() error = nil, want invalid request")
187+
}
188+
if !stderrors.Is(err, errors.New(errors.ErrCodeInvalidRequest, "")) {
189+
t.Errorf("BuildKubeClient() error = %v, want ErrCodeInvalidRequest", err)
190+
}
191+
if !strings.Contains(err.Error(), "failed to create kubernetes client from kubeconfig") {
192+
t.Errorf("BuildKubeClient() error = %v, want client construction failure", err)
193+
}
194+
assertKubeconfigErrorContext(t, err, kubeconfig)
120195
}
121196

122197
// TestGetKubeClient_Singleton tests that GetKubeClient returns the same instance.

0 commit comments

Comments
 (0)