@@ -28,17 +28,31 @@ type MetadataInjectionConfig struct {
28
28
HostOverride string // For virtual workspaces
29
29
}
30
30
31
+ // MetadataInjector provides metadata injection services with structured logging
32
+ type MetadataInjector struct {
33
+ log * logger.Logger
34
+ client client.Client
35
+ }
36
+
37
+ // NewMetadataInjector creates a new MetadataInjector service
38
+ func NewMetadataInjector (log * logger.Logger , client client.Client ) * MetadataInjector {
39
+ return & MetadataInjector {
40
+ log : log ,
41
+ client : client ,
42
+ }
43
+ }
44
+
31
45
// InjectClusterMetadata injects cluster metadata into schema JSON
32
46
// This unified function handles both KCP and ClusterAccess use cases
33
- func InjectClusterMetadata (ctx context.Context , schemaJSON []byte , config MetadataInjectionConfig , k8sClient client. Client , log * logger. Logger ) ([]byte , error ) {
47
+ func ( m * MetadataInjector ) InjectClusterMetadata (ctx context.Context , schemaJSON []byte , config MetadataInjectionConfig ) ([]byte , error ) {
34
48
// Parse the existing schema JSON
35
49
var schemaData map [string ]interface {}
36
50
if err := json .Unmarshal (schemaJSON , & schemaData ); err != nil {
37
51
return nil , fmt .Errorf ("failed to parse schema JSON: %w" , err )
38
52
}
39
53
40
54
// Determine the host to use
41
- host := determineHost (config .Host , config .HostOverride , log )
55
+ host := m . determineHost (config .Host , config .HostOverride )
42
56
43
57
// Create cluster metadata
44
58
metadata := map [string ]interface {}{
@@ -48,36 +62,36 @@ func InjectClusterMetadata(ctx context.Context, schemaJSON []byte, config Metada
48
62
49
63
// Add auth data if configured
50
64
if config .Auth != nil {
51
- authMetadata , err := extractAuthDataForMetadata (ctx , config .Auth , k8sClient )
65
+ authMetadata , err := m . extractAuthDataForMetadata (ctx , config .Auth )
52
66
if err != nil {
53
- log .Warn ().Err (err ).Msg ("failed to extract auth data for metadata" )
67
+ m . log .Warn ().Err (err ).Msg ("failed to extract auth data for metadata" )
54
68
} else if authMetadata != nil {
55
69
metadata ["auth" ] = authMetadata
56
70
}
57
71
}
58
72
59
73
// Add CA data - prefer explicit CA config, fallback to kubeconfig CA
60
74
if config .CA != nil {
61
- caData , err := ExtractCAData (ctx , config .CA , k8sClient )
75
+ caData , err := ExtractCAData (ctx , config .CA , m . client )
62
76
if err != nil {
63
- log .Warn ().Err (err ).Msg ("failed to extract CA data for metadata" )
77
+ m . log .Warn ().Err (err ).Msg ("failed to extract CA data for metadata" )
64
78
} else if caData != nil {
65
79
metadata ["ca" ] = map [string ]interface {}{
66
80
"data" : base64 .StdEncoding .EncodeToString (caData ),
67
81
}
68
82
}
69
83
} else if config .Auth != nil {
70
- tryExtractKubeconfigCA (ctx , config .Auth , k8sClient , metadata , log )
84
+ m . tryExtractKubeconfigCA (ctx , config .Auth , metadata )
71
85
}
72
86
73
- return finalizeSchemaInjection (schemaData , metadata , host , config .Path , config .CA != nil || config .Auth != nil , log )
87
+ return m . finalizeSchemaInjection (schemaData , metadata , host , config .Path , config .CA != nil || config .Auth != nil )
74
88
}
75
89
76
90
// InjectKCPMetadataFromEnv injects KCP metadata using kubeconfig from environment
77
91
// This is a convenience function for KCP use cases
78
- func InjectKCPMetadataFromEnv (schemaJSON []byte , clusterPath string , log * logger. Logger , hostOverride ... string ) ([]byte , error ) {
92
+ func ( m * MetadataInjector ) InjectKCPMetadataFromEnv (schemaJSON []byte , clusterPath string , hostOverride ... string ) ([]byte , error ) {
79
93
// Get kubeconfig from environment (same sources as ctrl.GetConfig())
80
- kubeconfigData , kubeconfigHost , err := extractKubeconfigFromEnv (log )
94
+ kubeconfigData , kubeconfigHost , err := m . extractKubeconfigFromEnv ()
81
95
if err != nil {
82
96
return nil , fmt .Errorf ("failed to extract kubeconfig data: %w" , err )
83
97
}
@@ -95,7 +109,7 @@ func InjectKCPMetadataFromEnv(schemaJSON []byte, clusterPath string, log *logger
95
109
}
96
110
97
111
// Determine which host to use
98
- host := determineKCPHost (kubeconfigHost , override , clusterPath , log )
112
+ host := m . determineKCPHost (kubeconfigHost , override , clusterPath )
99
113
100
114
// Create cluster metadata with environment kubeconfig
101
115
metadata := map [string ]interface {}{
@@ -108,40 +122,40 @@ func InjectKCPMetadataFromEnv(schemaJSON []byte, clusterPath string, log *logger
108
122
}
109
123
110
124
// Extract CA data from kubeconfig if available
111
- caData := extractCAFromKubeconfigData (kubeconfigData , log )
125
+ caData := m . extractCAFromKubeconfigData (kubeconfigData )
112
126
if caData != nil {
113
127
metadata ["ca" ] = map [string ]interface {}{
114
128
"data" : base64 .StdEncoding .EncodeToString (caData ),
115
129
}
116
130
}
117
131
118
- return finalizeSchemaInjection (schemaData , metadata , host , clusterPath , caData != nil , log )
132
+ return m . finalizeSchemaInjection (schemaData , metadata , host , clusterPath , caData != nil )
119
133
}
120
134
121
135
// extractAuthDataForMetadata extracts auth data from AuthConfig for metadata injection
122
- func extractAuthDataForMetadata (ctx context.Context , auth * gatewayv1alpha1.AuthConfig , k8sClient client. Client ) (map [string ]interface {}, error ) {
136
+ func ( m * MetadataInjector ) extractAuthDataForMetadata (ctx context.Context , auth * gatewayv1alpha1.AuthConfig ) (map [string ]interface {}, error ) {
123
137
if auth == nil {
124
138
return nil , nil
125
139
}
126
140
127
141
if auth .SecretRef != nil {
128
- return extractTokenAuth (ctx , auth .SecretRef , k8sClient )
142
+ return m . extractTokenAuth (ctx , auth .SecretRef )
129
143
}
130
144
131
145
if auth .KubeconfigSecretRef != nil {
132
- return extractKubeconfigAuth (ctx , auth .KubeconfigSecretRef , k8sClient )
146
+ return m . extractKubeconfigAuth (ctx , auth .KubeconfigSecretRef )
133
147
}
134
148
135
149
if auth .ClientCertificateRef != nil {
136
- return extractClientCertAuth (ctx , auth .ClientCertificateRef , k8sClient )
150
+ return m . extractClientCertAuth (ctx , auth .ClientCertificateRef )
137
151
}
138
152
139
153
return nil , nil // No auth configured
140
154
}
141
155
142
156
// extractTokenAuth handles token-based authentication from SecretRef
143
- func extractTokenAuth (ctx context.Context , secretRef * gatewayv1alpha1.SecretRef , k8sClient client. Client ) (map [string ]interface {}, error ) {
144
- secret , err := getSecret (ctx , secretRef .Name , secretRef .Namespace , k8sClient )
157
+ func ( m * MetadataInjector ) extractTokenAuth (ctx context.Context , secretRef * gatewayv1alpha1.SecretRef ) (map [string ]interface {}, error ) {
158
+ secret , err := m . getSecret (ctx , secretRef .Name , secretRef .Namespace )
145
159
if err != nil {
146
160
return nil , fmt .Errorf ("failed to get auth secret: %w" , err )
147
161
}
@@ -158,8 +172,8 @@ func extractTokenAuth(ctx context.Context, secretRef *gatewayv1alpha1.SecretRef,
158
172
}
159
173
160
174
// extractKubeconfigAuth handles kubeconfig-based authentication from KubeconfigSecretRef
161
- func extractKubeconfigAuth (ctx context.Context , kubeconfigRef * gatewayv1alpha1.KubeconfigSecretRef , k8sClient client. Client ) (map [string ]interface {}, error ) {
162
- secret , err := getSecret (ctx , kubeconfigRef .Name , kubeconfigRef .Namespace , k8sClient )
175
+ func ( m * MetadataInjector ) extractKubeconfigAuth (ctx context.Context , kubeconfigRef * gatewayv1alpha1.KubeconfigSecretRef ) (map [string ]interface {}, error ) {
176
+ secret , err := m . getSecret (ctx , kubeconfigRef .Name , kubeconfigRef .Namespace )
163
177
if err != nil {
164
178
return nil , fmt .Errorf ("failed to get kubeconfig secret: %w" , err )
165
179
}
@@ -176,8 +190,8 @@ func extractKubeconfigAuth(ctx context.Context, kubeconfigRef *gatewayv1alpha1.K
176
190
}
177
191
178
192
// extractClientCertAuth handles client certificate authentication from ClientCertificateRef
179
- func extractClientCertAuth (ctx context.Context , certRef * gatewayv1alpha1.ClientCertificateRef , k8sClient client. Client ) (map [string ]interface {}, error ) {
180
- secret , err := getSecret (ctx , certRef .Name , certRef .Namespace , k8sClient )
193
+ func ( m * MetadataInjector ) extractClientCertAuth (ctx context.Context , certRef * gatewayv1alpha1.ClientCertificateRef ) (map [string ]interface {}, error ) {
194
+ secret , err := m . getSecret (ctx , certRef .Name , certRef .Namespace )
181
195
if err != nil {
182
196
return nil , fmt .Errorf ("failed to get client certificate secret: %w" , err )
183
197
}
@@ -197,13 +211,13 @@ func extractClientCertAuth(ctx context.Context, certRef *gatewayv1alpha1.ClientC
197
211
}
198
212
199
213
// getSecret is a helper function to retrieve secrets with namespace defaulting
200
- func getSecret (ctx context.Context , name , namespace string , k8sClient client. Client ) (* corev1.Secret , error ) {
214
+ func ( m * MetadataInjector ) getSecret (ctx context.Context , name , namespace string ) (* corev1.Secret , error ) {
201
215
if namespace == "" {
202
216
namespace = "default"
203
217
}
204
218
205
219
secret := & corev1.Secret {}
206
- err := k8sClient .Get (ctx , types.NamespacedName {
220
+ err := m . client .Get (ctx , types.NamespacedName {
207
221
Name : name ,
208
222
Namespace : namespace ,
209
223
}, secret )
@@ -215,11 +229,11 @@ func getSecret(ctx context.Context, name, namespace string, k8sClient client.Cli
215
229
}
216
230
217
231
// extractKubeconfigFromEnv gets kubeconfig data from the same sources as ctrl.GetConfig()
218
- func extractKubeconfigFromEnv ( log * logger. Logger ) ([]byte , string , error ) {
232
+ func ( m * MetadataInjector ) extractKubeconfigFromEnv ( ) ([]byte , string , error ) {
219
233
// Check KUBECONFIG environment variable first
220
234
kubeconfigPath := os .Getenv ("KUBECONFIG" )
221
235
if kubeconfigPath != "" {
222
- log .Debug ().Str ("source" , "KUBECONFIG env var" ).Str ("path" , kubeconfigPath ).Msg ("using kubeconfig from environment variable" )
236
+ m . log .Debug ().Str ("source" , "KUBECONFIG env var" ).Str ("path" , kubeconfigPath ).Msg ("using kubeconfig from environment variable" )
223
237
}
224
238
225
239
// Fall back to default kubeconfig location if not set
@@ -229,7 +243,7 @@ func extractKubeconfigFromEnv(log *logger.Logger) ([]byte, string, error) {
229
243
return nil , "" , fmt .Errorf ("failed to determine kubeconfig location: %w" , err )
230
244
}
231
245
kubeconfigPath = home + "/.kube/config"
232
- log .Debug ().Str ("source" , "default location" ).Str ("path" , kubeconfigPath ).Msg ("using default kubeconfig location" )
246
+ m . log .Debug ().Str ("source" , "default location" ).Str ("path" , kubeconfigPath ).Msg ("using default kubeconfig location" )
233
247
}
234
248
235
249
// Check if file exists
@@ -301,54 +315,54 @@ func stripVirtualWorkspacePath(hostURL string) string {
301
315
}
302
316
303
317
// extractCAFromKubeconfigData extracts CA certificate data from raw kubeconfig bytes
304
- func extractCAFromKubeconfigData (kubeconfigData []byte , log * logger. Logger ) []byte {
318
+ func ( m * MetadataInjector ) extractCAFromKubeconfigData (kubeconfigData []byte ) []byte {
305
319
config , err := clientcmd .Load (kubeconfigData )
306
320
if err != nil {
307
- log .Warn ().Err (err ).Msg ("failed to parse kubeconfig for CA extraction" )
321
+ m . log .Warn ().Err (err ).Msg ("failed to parse kubeconfig for CA extraction" )
308
322
return nil
309
323
}
310
324
311
325
if config .CurrentContext == "" {
312
- log .Warn ().Msg ("no current context in kubeconfig for CA extraction" )
326
+ m . log .Warn ().Msg ("no current context in kubeconfig for CA extraction" )
313
327
return nil
314
328
}
315
329
316
330
context , exists := config .Contexts [config .CurrentContext ]
317
331
if ! exists {
318
- log .Warn ().Str ("context" , config .CurrentContext ).Msg ("current context not found in kubeconfig for CA extraction" )
332
+ m . log .Warn ().Str ("context" , config .CurrentContext ).Msg ("current context not found in kubeconfig for CA extraction" )
319
333
return nil
320
334
}
321
335
322
336
cluster , exists := config .Clusters [context .Cluster ]
323
337
if ! exists {
324
- log .Warn ().Str ("cluster" , context .Cluster ).Msg ("cluster not found in kubeconfig for CA extraction" )
338
+ m . log .Warn ().Str ("cluster" , context .Cluster ).Msg ("cluster not found in kubeconfig for CA extraction" )
325
339
return nil
326
340
}
327
341
328
342
if len (cluster .CertificateAuthorityData ) == 0 {
329
- log .Debug ().Msg ("no CA data found in kubeconfig" )
343
+ m . log .Debug ().Msg ("no CA data found in kubeconfig" )
330
344
return nil
331
345
}
332
346
333
347
return cluster .CertificateAuthorityData
334
348
}
335
349
336
350
// extractCAFromKubeconfigB64 extracts CA certificate data from base64-encoded kubeconfig
337
- func extractCAFromKubeconfigB64 (kubeconfigB64 string , log * logger. Logger ) []byte {
351
+ func ( m * MetadataInjector ) extractCAFromKubeconfigB64 (kubeconfigB64 string ) []byte {
338
352
kubeconfigData , err := base64 .StdEncoding .DecodeString (kubeconfigB64 )
339
353
if err != nil {
340
- log .Warn ().Err (err ).Msg ("failed to decode kubeconfig for CA extraction" )
354
+ m . log .Warn ().Err (err ).Msg ("failed to decode kubeconfig for CA extraction" )
341
355
return nil
342
356
}
343
357
344
- return extractCAFromKubeconfigData (kubeconfigData , log )
358
+ return m . extractCAFromKubeconfigData (kubeconfigData )
345
359
}
346
360
347
361
// tryExtractKubeconfigCA attempts to extract CA data from kubeconfig auth and adds it to metadata
348
- func tryExtractKubeconfigCA (ctx context.Context , auth * gatewayv1alpha1.AuthConfig , k8sClient client. Client , metadata map [string ]interface {}, log * logger. Logger ) {
349
- authMetadata , err := extractAuthDataForMetadata (ctx , auth , k8sClient )
362
+ func ( m * MetadataInjector ) tryExtractKubeconfigCA (ctx context.Context , auth * gatewayv1alpha1.AuthConfig , metadata map [string ]interface {}) {
363
+ authMetadata , err := m . extractAuthDataForMetadata (ctx , auth )
350
364
if err != nil {
351
- log .Warn ().Err (err ).Msg ("failed to extract auth data for CA extraction" )
365
+ m . log .Warn ().Err (err ).Msg ("failed to extract auth data for CA extraction" )
352
366
return
353
367
}
354
368
@@ -366,21 +380,21 @@ func tryExtractKubeconfigCA(ctx context.Context, auth *gatewayv1alpha1.AuthConfi
366
380
return
367
381
}
368
382
369
- kubeconfigCAData := extractCAFromKubeconfigB64 (kubeconfigB64 , log )
383
+ kubeconfigCAData := m . extractCAFromKubeconfigB64 (kubeconfigB64 )
370
384
if kubeconfigCAData == nil {
371
385
return
372
386
}
373
387
374
388
metadata ["ca" ] = map [string ]interface {}{
375
389
"data" : base64 .StdEncoding .EncodeToString (kubeconfigCAData ),
376
390
}
377
- log .Info ().Msg ("extracted CA data from kubeconfig" )
391
+ m . log .Info ().Msg ("extracted CA data from kubeconfig" )
378
392
}
379
393
380
394
// determineHost determines which host to use based on configuration
381
- func determineHost (originalHost , hostOverride string , log * logger. Logger ) string {
395
+ func ( m * MetadataInjector ) determineHost (originalHost , hostOverride string ) string {
382
396
if hostOverride != "" {
383
- log .Info ().
397
+ m . log .Info ().
384
398
Str ("originalHost" , originalHost ).
385
399
Str ("overrideHost" , hostOverride ).
386
400
Msg ("using host override for virtual workspace" )
@@ -390,7 +404,7 @@ func determineHost(originalHost, hostOverride string, log *logger.Logger) string
390
404
// For normal workspaces, ensure we use a clean host by stripping any virtual workspace paths
391
405
cleanedHost := stripVirtualWorkspacePath (originalHost )
392
406
if cleanedHost != originalHost {
393
- log .Info ().
407
+ m . log .Info ().
394
408
Str ("originalHost" , originalHost ).
395
409
Str ("cleanedHost" , cleanedHost ).
396
410
Msg ("cleaned virtual workspace path from host for normal workspace" )
@@ -399,9 +413,9 @@ func determineHost(originalHost, hostOverride string, log *logger.Logger) string
399
413
}
400
414
401
415
// determineKCPHost determines which host to use for KCP metadata injection
402
- func determineKCPHost (kubeconfigHost , override , clusterPath string , log * logger. Logger ) string {
416
+ func ( m * MetadataInjector ) determineKCPHost (kubeconfigHost , override , clusterPath string ) string {
403
417
if override != "" {
404
- log .Info ().
418
+ m . log .Info ().
405
419
Str ("clusterPath" , clusterPath ).
406
420
Str ("originalHost" , kubeconfigHost ).
407
421
Str ("overrideHost" , override ).
@@ -412,7 +426,7 @@ func determineKCPHost(kubeconfigHost, override, clusterPath string, log *logger.
412
426
// For normal workspaces, ensure we use a clean KCP host by stripping any virtual workspace paths
413
427
host := stripVirtualWorkspacePath (kubeconfigHost )
414
428
if host != kubeconfigHost {
415
- log .Info ().
429
+ m . log .Info ().
416
430
Str ("clusterPath" , clusterPath ).
417
431
Str ("originalHost" , kubeconfigHost ).
418
432
Str ("cleanedHost" , host ).
@@ -422,7 +436,7 @@ func determineKCPHost(kubeconfigHost, override, clusterPath string, log *logger.
422
436
}
423
437
424
438
// finalizeSchemaInjection finalizes the schema injection process
425
- func finalizeSchemaInjection (schemaData map [string ]interface {}, metadata map [string ]interface {}, host , path string , hasCA bool , log * logger. Logger ) ([]byte , error ) {
439
+ func ( m * MetadataInjector ) finalizeSchemaInjection (schemaData map [string ]interface {}, metadata map [string ]interface {}, host , path string , hasCA bool ) ([]byte , error ) {
426
440
// Inject the metadata into the schema
427
441
schemaData ["x-cluster-metadata" ] = metadata
428
442
@@ -432,11 +446,40 @@ func finalizeSchemaInjection(schemaData map[string]interface{}, metadata map[str
432
446
return nil , fmt .Errorf ("failed to marshal modified schema: %w" , err )
433
447
}
434
448
435
- log .Info ().
449
+ m . log .Info ().
436
450
Str ("host" , host ).
437
451
Str ("path" , path ).
438
452
Bool ("hasCA" , hasCA ).
439
453
Msg ("successfully injected cluster metadata into schema" )
440
454
441
455
return modifiedJSON , nil
442
456
}
457
+
458
+ // Legacy function wrappers for backward compatibility
459
+ // These can be removed after updating all callers
460
+
461
+ // InjectClusterMetadata is a legacy wrapper for backward compatibility
462
+ func InjectClusterMetadata (ctx context.Context , schemaJSON []byte , config MetadataInjectionConfig , k8sClient client.Client , log * logger.Logger ) ([]byte , error ) {
463
+ injector := NewMetadataInjector (log , k8sClient )
464
+ return injector .InjectClusterMetadata (ctx , schemaJSON , config )
465
+ }
466
+
467
+ // InjectKCPMetadataFromEnv is a legacy wrapper for backward compatibility
468
+ func InjectKCPMetadataFromEnv (schemaJSON []byte , clusterPath string , log * logger.Logger , hostOverride ... string ) ([]byte , error ) {
469
+ injector := NewMetadataInjector (log , nil )
470
+ return injector .InjectKCPMetadataFromEnv (schemaJSON , clusterPath , hostOverride ... )
471
+ }
472
+
473
+ // Test exports for internal testing - these expose internal methods for unit tests
474
+
475
+ // extractKubeconfigFromEnv is exported for testing
476
+ func extractKubeconfigFromEnv (log * logger.Logger ) ([]byte , string , error ) {
477
+ injector := NewMetadataInjector (log , nil )
478
+ return injector .extractKubeconfigFromEnv ()
479
+ }
480
+
481
+ // extractAuthDataForMetadata is exported for testing
482
+ func extractAuthDataForMetadata (ctx context.Context , auth * gatewayv1alpha1.AuthConfig , k8sClient client.Client ) (map [string ]interface {}, error ) {
483
+ injector := NewMetadataInjector (nil , k8sClient )
484
+ return injector .extractAuthDataForMetadata (ctx , auth )
485
+ }
0 commit comments