@@ -49,21 +49,32 @@ const (
49
49
configControllerApiVersion = "configcontroller.cnrm.cloud.google.com/v1beta1"
50
50
)
51
51
52
+ var hubMembershipGVK = schema.GroupVersionKind {
53
+ Kind : "GKEHubMembership" ,
54
+ Group : "gkehub.cnrm.cloud.google.com" ,
55
+ Version : "v1beta1" ,
56
+ }
57
+
52
58
type RemoteClientGetter struct {
53
59
client.Client
54
60
55
61
workloadIdentity WorkloadIdentityHelper
62
+
63
+ projectCache ProjectCache
56
64
}
57
65
58
66
// Init performs one-off initialization of the object.
59
67
func (r * RemoteClientGetter ) Init (mgr ctrl.Manager ) error {
60
68
r .Client = mgr .GetClient ()
61
69
70
+ if err := r .projectCache .Init (mgr ); err != nil {
71
+ return err
72
+ }
73
+
62
74
return r .workloadIdentity .Init (mgr .GetConfig ())
63
75
}
64
76
65
- // getCCRESTConfig builds a rest.Config for accessing the config controller cluster,
66
- // this is a tmp workaround.
77
+ // getCCRESTConfig builds a rest.Config for accessing the config controller cluster.
67
78
func (r * RemoteClientGetter ) getCCRESTConfig (ctx context.Context , cluster * unstructured.Unstructured ) (* rest.Config , error ) {
68
79
gkeResourceLink , _ , err := unstructured .NestedString (cluster .Object , "status" , "gkeResourceLink" )
69
80
if err != nil {
@@ -81,11 +92,12 @@ func (r *RemoteClientGetter) getCCRESTConfig(ctx context.Context, cluster *unstr
81
92
clusterName := googleURL .Extra ["clusters" ]
82
93
klog .Infof ("cluster name is %s" , clusterName )
83
94
84
- tokenSource , err := r .getConfigConnectorContextTokenSource (ctx , cluster .GetNamespace ())
95
+ tokenSource , err := r .getConfigConnectorTokenSource (ctx , cluster .GetNamespace ())
85
96
if err != nil {
86
97
return nil , err
87
98
}
88
99
100
+ // Temporary workaround for getting the cluster certificate, update after ACP add new fields
89
101
gkeClient , err := container .NewClusterManagerClient (ctx , option .WithTokenSource (tokenSource ), option .WithQuotaProject (projectID ))
90
102
if err != nil {
91
103
return nil , fmt .Errorf ("failed to create new cluster manager client: %w" , err )
@@ -122,8 +134,8 @@ func (r *RemoteClientGetter) getCCRESTConfig(ctx context.Context, cluster *unstr
122
134
return restConfig , nil
123
135
}
124
136
125
- // getConfigConnectorContextTokenSource gets and returns the ConfigConnectorContext for the given namespace.
126
- func (r * RemoteClientGetter ) getConfigConnectorContextTokenSource (ctx context.Context , ns string ) (oauth2.TokenSource , error ) {
137
+ // getConfigConnectorTokenSource gets and returns the token source to authenticate as KCC in the given namespace.
138
+ func (r * RemoteClientGetter ) getConfigConnectorTokenSource (ctx context.Context , ns string ) (oauth2.TokenSource , error ) {
127
139
if os .Getenv ("USE_DEV_AUTH" ) != "" {
128
140
klog .Warningf ("using default authentication, intended for local development only" )
129
141
accessToken , err := GetDefaultAccessToken (ctx )
@@ -133,6 +145,58 @@ func (r *RemoteClientGetter) getConfigConnectorContextTokenSource(ctx context.Co
133
145
return oauth2 .StaticTokenSource (accessToken ), nil
134
146
}
135
147
148
+ gvr := schema.GroupVersionResource {
149
+ Group : "core.cnrm.cloud.google.com" ,
150
+ Version : "v1beta1" ,
151
+ Resource : "configconnectors" ,
152
+ }
153
+
154
+ id := types.NamespacedName {
155
+ Name : "configconnector.core.cnrm.cloud.google.com" ,
156
+ }
157
+ cr , err := r .workloadIdentity .dynamicClient .Resource (gvr ).Get (ctx , id .Name , metav1.GetOptions {})
158
+ if err != nil {
159
+ return nil , fmt .Errorf ("unable to get ConfigConnector resource %v: %w" , id , err )
160
+ }
161
+
162
+ mode , _ , err := unstructured .NestedString (cr .Object , "spec" , "mode" )
163
+ if err != nil {
164
+ return nil , fmt .Errorf ("error reading spec.mode from ConfigConnector resource: %w" , err )
165
+ }
166
+
167
+ // Default is namespaced
168
+ if mode == "" {
169
+ mode = "namespaced"
170
+ }
171
+
172
+ switch mode {
173
+ case "namespaced" :
174
+ return r .getConfigConnectorTokenSourceNamespaced (ctx , ns )
175
+ case "cluster" :
176
+ // ok
177
+ default :
178
+ return nil , fmt .Errorf ("unknown spec.mode %q in ConfigConnector resource" , mode )
179
+ }
180
+
181
+ googleServiceAccount , _ , err := unstructured .NestedString (cr .Object , "spec" , "googleServiceAccount" )
182
+ if err != nil {
183
+ return nil , fmt .Errorf ("error reading spec.googleServiceAccount from ConfigConnector resource: %w" , err )
184
+ }
185
+
186
+ if googleServiceAccount == "" {
187
+ return nil , fmt .Errorf ("could not find spec.googleServiceAccount from ConfigConnector resource" )
188
+ }
189
+
190
+ kubeServiceAccount := types.NamespacedName {
191
+ Namespace : "cnrm-system" ,
192
+ Name : "cnrm-controller-manager" ,
193
+ }
194
+ return r .workloadIdentity .GetGcloudAccessTokenSource (ctx , kubeServiceAccount , googleServiceAccount )
195
+ }
196
+
197
+ // getConfigConnectorTokenSourceNamespaced gets and returns the ConfigConnectorContext for the given namespace,
198
+ // when running in namespace mode.
199
+ func (r * RemoteClientGetter ) getConfigConnectorTokenSourceNamespaced (ctx context.Context , ns string ) (oauth2.TokenSource , error ) {
136
200
gvr := schema.GroupVersionResource {
137
201
Group : "core.cnrm.cloud.google.com" ,
138
202
Version : "v1beta1" ,
@@ -145,7 +209,7 @@ func (r *RemoteClientGetter) getConfigConnectorContextTokenSource(ctx context.Co
145
209
}
146
210
cr , err := r .workloadIdentity .dynamicClient .Resource (gvr ).Namespace (id .Namespace ).Get (ctx , id .Name , metav1.GetOptions {})
147
211
if err != nil {
148
- return nil , fmt .Errorf ("unable to get configconnectorcontext %v: %w" , id , err )
212
+ return nil , fmt .Errorf ("unable to get ConfigConnectorContext resource %v: %w" , id , err )
149
213
}
150
214
151
215
googleServiceAccount , _ , err := unstructured .NestedString (cr .Object , "spec" , "googleServiceAccount" )
@@ -154,7 +218,7 @@ func (r *RemoteClientGetter) getConfigConnectorContextTokenSource(ctx context.Co
154
218
}
155
219
156
220
if googleServiceAccount == "" {
157
- return nil , fmt .Errorf ("could not find spec.googleServiceAccount from ConfigConnectorContext in %q: %w " , ns , err )
221
+ return nil , fmt .Errorf ("could not find spec.googleServiceAccount from ConfigConnectorContext in %q" , ns )
158
222
}
159
223
160
224
kubeServiceAccount := types.NamespacedName {
@@ -194,6 +258,8 @@ func toCompletedReference(in Reference, defaultNamespace string) (completedRefer
194
258
switch ref .Kind {
195
259
case containerClusterKind :
196
260
ref .APIVersion = containerClusterApiVersion
261
+ case hubMembershipGVK .Kind :
262
+ ref .APIVersion = hubMembershipGVK .GroupVersion ().Identifier ()
197
263
case configControllerKind :
198
264
ref .APIVersion = configControllerApiVersion
199
265
default :
@@ -256,7 +322,9 @@ func (r *RemoteClientGetter) GetRemoteClient(ctx context.Context, clusterRef Ref
256
322
if ref .Kind == containerClusterKind {
257
323
restConfig , err = r .getGKERESTConfig (ctx , u )
258
324
} else if ref .Kind == configControllerKind {
259
- restConfig , err = r .getCCRESTConfig (ctx , u ) //TODO: tmp workaround, update after ACP add new fields
325
+ restConfig , err = r .getCCRESTConfig (ctx , u )
326
+ } else if ref .Kind == hubMembershipGVK .Kind {
327
+ restConfig , err = r .getHubMembershipRESTConfig (ctx , u )
260
328
} else {
261
329
return nil , fmt .Errorf ("failed to find target cluster, cluster kind has to be ContainerCluster or ConfigControllerInstance" )
262
330
}
@@ -298,14 +366,57 @@ func (r *RemoteClientGetter) getGKERESTConfig(ctx context.Context, cluster *unst
298
366
restConfig .Host = "https://" + endpoint
299
367
klog .Infof ("Host endpoint is %s" , restConfig .Host )
300
368
301
- tokenSource , err := r .getConfigConnectorContextTokenSource (ctx , cluster .GetNamespace ())
369
+ tokenSource , err := r .getConfigConnectorTokenSource (ctx , cluster .GetNamespace ())
370
+ if err != nil {
371
+ return nil , fmt .Errorf ("error building authentication token provider: %w" , err )
372
+ }
373
+ token , err := tokenSource .Token ()
374
+ if err != nil {
375
+ return nil , fmt .Errorf ("error getting authentication token: %w" , err )
376
+ }
377
+ restConfig .BearerToken = token .AccessToken
378
+
379
+ return restConfig , nil
380
+ }
381
+
382
+ // getHubMembershipRESTConfig builds a rest.Config for accessing the specified cluster through connect gateway.
383
+ func (r * RemoteClientGetter ) getHubMembershipRESTConfig (ctx context.Context , cluster * unstructured.Unstructured ) (* rest.Config , error ) {
384
+ restConfig := & rest.Config {}
385
+
386
+ // TODO: We could really use a selfLink field here!
387
+
388
+ projectID := cluster .GetAnnotations ()["cnrm.cloud.google.com/project-id" ]
389
+ if projectID == "" {
390
+ return nil , fmt .Errorf ("cannot determine project-id for object" )
391
+ }
392
+
393
+ membershipName , _ , err := unstructured .NestedString (cluster .Object , "spec" , "resourceID" )
394
+ if err != nil {
395
+ return nil , fmt .Errorf ("failed to get spec.resourceID: %w" , err )
396
+ }
397
+ if membershipName == "" {
398
+ return nil , fmt .Errorf ("spec.resourceID field was not set" )
399
+ }
400
+
401
+ tokenSource , err := r .getConfigConnectorTokenSource (ctx , cluster .GetNamespace ())
302
402
if err != nil {
303
403
return nil , fmt .Errorf ("error building authentication token provider: %w" , err )
304
404
}
405
+
406
+ projectInfo , err := r .projectCache .LookupByProjectID (ctx , projectID , tokenSource )
407
+ if err != nil {
408
+ return nil , err
409
+ }
410
+
411
+ host := fmt .Sprintf ("https://connectgateway.googleapis.com/v1/projects/%d/locations/global/memberships/%s" , projectInfo .ProjectNumber , membershipName )
412
+ restConfig .Host = host
413
+ klog .Infof ("Host endpoint is %s" , restConfig .Host )
414
+
305
415
token , err := tokenSource .Token ()
306
416
if err != nil {
307
417
return nil , fmt .Errorf ("error getting authentication token: %w" , err )
308
418
}
419
+
309
420
restConfig .BearerToken = token .AccessToken
310
421
311
422
return restConfig , nil
0 commit comments