Skip to content

Commit 852b7cd

Browse files
authored
Support applying a package to a GKEHubMembership (#3733)
Put a lot of the previous pieces together!
1 parent 74d800f commit 852b7cd

File tree

7 files changed

+238
-10
lines changed

7 files changed

+238
-10
lines changed

porch/controllers/remoterootsyncsets/config/rbac/role.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,39 @@ rules:
4545
- get
4646
- patch
4747
- update
48+
- apiGroups:
49+
- configcontroller.cnrm.cloud.google.com
50+
resources:
51+
- configcontrollerinstances
52+
verbs:
53+
- get
54+
- list
55+
- watch
56+
- apiGroups:
57+
- container.cnrm.cloud.google.com
58+
resources:
59+
- containerclusters
60+
verbs:
61+
- get
62+
- list
63+
- watch
64+
- apiGroups:
65+
- core.cnrm.cloud.google.com
66+
resources:
67+
- configconnectorcontexts
68+
- configconnectors
69+
verbs:
70+
- get
71+
- list
72+
- watch
73+
- apiGroups:
74+
- gkehub.cnrm.cloud.google.com
75+
resources:
76+
- gkehubmemberships
77+
verbs:
78+
- get
79+
- list
80+
- watch
4881
- apiGroups:
4982
- porch.kpt.dev
5083
resources:

porch/controllers/remoterootsyncsets/pkg/controllers/remoterootsyncset/remoterootsync_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ type RemoteRootSyncSetReconciler struct {
8282
//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=remoterootsyncsets/finalizers,verbs=update
8383
//+kubebuilder:rbac:groups=porch.kpt.dev,resources=packagerevisions;packagerevisionresources,verbs=get;list;watch
8484

85+
//+kubebuilder:rbac:groups=configcontroller.cnrm.cloud.google.com,resources=configcontrollerinstances,verbs=get;list;watch
86+
//+kubebuilder:rbac:groups=container.cnrm.cloud.google.com,resources=containerclusters,verbs=get;list;watch
87+
//+kubebuilder:rbac:groups=gkehub.cnrm.cloud.google.com,resources=gkehubmemberships,verbs=get;list;watch
88+
89+
//+kubebuilder:rbac:groups=core.cnrm.cloud.google.com,resources=configconnectors;configconnectorcontexts,verbs=get;list;watch
90+
8591
// Reconcile implements the main kubernetes reconciliation loop.
8692
func (r *RemoteRootSyncSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
8793
var subject api.RemoteRootSyncSet

porch/controllers/remoterootsyncsets/pkg/remoteclient/getremoteclient.go

Lines changed: 120 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,32 @@ const (
4949
configControllerApiVersion = "configcontroller.cnrm.cloud.google.com/v1beta1"
5050
)
5151

52+
var hubMembershipGVK = schema.GroupVersionKind{
53+
Kind: "GKEHubMembership",
54+
Group: "gkehub.cnrm.cloud.google.com",
55+
Version: "v1beta1",
56+
}
57+
5258
type RemoteClientGetter struct {
5359
client.Client
5460

5561
workloadIdentity WorkloadIdentityHelper
62+
63+
projectCache ProjectCache
5664
}
5765

5866
// Init performs one-off initialization of the object.
5967
func (r *RemoteClientGetter) Init(mgr ctrl.Manager) error {
6068
r.Client = mgr.GetClient()
6169

70+
if err := r.projectCache.Init(mgr); err != nil {
71+
return err
72+
}
73+
6274
return r.workloadIdentity.Init(mgr.GetConfig())
6375
}
6476

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.
6778
func (r *RemoteClientGetter) getCCRESTConfig(ctx context.Context, cluster *unstructured.Unstructured) (*rest.Config, error) {
6879
gkeResourceLink, _, err := unstructured.NestedString(cluster.Object, "status", "gkeResourceLink")
6980
if err != nil {
@@ -81,11 +92,12 @@ func (r *RemoteClientGetter) getCCRESTConfig(ctx context.Context, cluster *unstr
8192
clusterName := googleURL.Extra["clusters"]
8293
klog.Infof("cluster name is %s", clusterName)
8394

84-
tokenSource, err := r.getConfigConnectorContextTokenSource(ctx, cluster.GetNamespace())
95+
tokenSource, err := r.getConfigConnectorTokenSource(ctx, cluster.GetNamespace())
8596
if err != nil {
8697
return nil, err
8798
}
8899

100+
// Temporary workaround for getting the cluster certificate, update after ACP add new fields
89101
gkeClient, err := container.NewClusterManagerClient(ctx, option.WithTokenSource(tokenSource), option.WithQuotaProject(projectID))
90102
if err != nil {
91103
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
122134
return restConfig, nil
123135
}
124136

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) {
127139
if os.Getenv("USE_DEV_AUTH") != "" {
128140
klog.Warningf("using default authentication, intended for local development only")
129141
accessToken, err := GetDefaultAccessToken(ctx)
@@ -133,6 +145,58 @@ func (r *RemoteClientGetter) getConfigConnectorContextTokenSource(ctx context.Co
133145
return oauth2.StaticTokenSource(accessToken), nil
134146
}
135147

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) {
136200
gvr := schema.GroupVersionResource{
137201
Group: "core.cnrm.cloud.google.com",
138202
Version: "v1beta1",
@@ -145,7 +209,7 @@ func (r *RemoteClientGetter) getConfigConnectorContextTokenSource(ctx context.Co
145209
}
146210
cr, err := r.workloadIdentity.dynamicClient.Resource(gvr).Namespace(id.Namespace).Get(ctx, id.Name, metav1.GetOptions{})
147211
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)
149213
}
150214

151215
googleServiceAccount, _, err := unstructured.NestedString(cr.Object, "spec", "googleServiceAccount")
@@ -154,7 +218,7 @@ func (r *RemoteClientGetter) getConfigConnectorContextTokenSource(ctx context.Co
154218
}
155219

156220
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)
158222
}
159223

160224
kubeServiceAccount := types.NamespacedName{
@@ -194,6 +258,8 @@ func toCompletedReference(in Reference, defaultNamespace string) (completedRefer
194258
switch ref.Kind {
195259
case containerClusterKind:
196260
ref.APIVersion = containerClusterApiVersion
261+
case hubMembershipGVK.Kind:
262+
ref.APIVersion = hubMembershipGVK.GroupVersion().Identifier()
197263
case configControllerKind:
198264
ref.APIVersion = configControllerApiVersion
199265
default:
@@ -256,7 +322,9 @@ func (r *RemoteClientGetter) GetRemoteClient(ctx context.Context, clusterRef Ref
256322
if ref.Kind == containerClusterKind {
257323
restConfig, err = r.getGKERESTConfig(ctx, u)
258324
} 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)
260328
} else {
261329
return nil, fmt.Errorf("failed to find target cluster, cluster kind has to be ContainerCluster or ConfigControllerInstance")
262330
}
@@ -298,14 +366,57 @@ func (r *RemoteClientGetter) getGKERESTConfig(ctx context.Context, cluster *unst
298366
restConfig.Host = "https://" + endpoint
299367
klog.Infof("Host endpoint is %s", restConfig.Host)
300368

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())
302402
if err != nil {
303403
return nil, fmt.Errorf("error building authentication token provider: %w", err)
304404
}
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+
305415
token, err := tokenSource.Token()
306416
if err != nil {
307417
return nil, fmt.Errorf("error getting authentication token: %w", err)
308418
}
419+
309420
restConfig.BearerToken = token.AccessToken
310421

311422
return restConfig, nil
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package remoteclient
16+
17+
import (
18+
"context"
19+
"fmt"
20+
21+
"golang.org/x/oauth2"
22+
cloudresourcemanagerv1 "google.golang.org/api/cloudresourcemanager/v1"
23+
"google.golang.org/api/option"
24+
ctrl "sigs.k8s.io/controller-runtime"
25+
)
26+
27+
type ProjectCache struct {
28+
}
29+
30+
type ProjectInfo struct {
31+
ProjectID string
32+
ProjectNumber int64
33+
}
34+
35+
// Init performs one-off initialization of the object.
36+
func (r *ProjectCache) Init(mgr ctrl.Manager) error {
37+
return nil
38+
}
39+
40+
func (r *ProjectCache) LookupByProjectID(ctx context.Context, projectID string, tokenSource oauth2.TokenSource) (*ProjectInfo, error) {
41+
// TODO: Cache
42+
43+
crmClient, err := cloudresourcemanagerv1.NewService(ctx, option.WithTokenSource(tokenSource))
44+
if err != nil {
45+
return nil, fmt.Errorf("failed to create new cloudresourcemanager client: %w", err)
46+
}
47+
48+
project, err := crmClient.Projects.Get(projectID).Context(ctx).Do()
49+
if err != nil {
50+
return nil, fmt.Errorf("error querying project %q: %w", projectID, err)
51+
}
52+
53+
return &ProjectInfo{
54+
ProjectID: project.ProjectId,
55+
ProjectNumber: project.ProjectNumber,
56+
}, nil
57+
}

porch/controllers/rootsyncdeployments/config/rbac/role.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,23 @@ rules:
6969
- get
7070
- list
7171
- watch
72+
- apiGroups:
73+
- core.cnrm.cloud.google.com
74+
resources:
75+
- configconnectorcontexts
76+
- configconnectors
77+
verbs:
78+
- get
79+
- list
80+
- watch
81+
- apiGroups:
82+
- gkehub.cnrm.cloud.google.com
83+
resources:
84+
- gkehubmemberships
85+
verbs:
86+
- get
87+
- list
88+
- watch
7289
- apiGroups:
7390
- porch.kpt.dev
7491
resources:

porch/controllers/rootsyncdeployments/pkg/controllers/rootsyncdeployment/controller.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,12 @@ type RootSyncDeploymentReconciler struct {
9393
//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=rootsyncdeployments/finalizers,verbs=update
9494
//+kubebuilder:rbac:groups=porch.kpt.dev,resources=packagerevisions,verbs=get;list;watch
9595
//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=repositories,verbs=get;list;watch
96+
9697
//+kubebuilder:rbac:groups=configcontroller.cnrm.cloud.google.com,resources=configcontrollerinstances,verbs=get;list;watch
9798
//+kubebuilder:rbac:groups=container.cnrm.cloud.google.com,resources=containerclusters,verbs=get;list;watch
99+
//+kubebuilder:rbac:groups=gkehub.cnrm.cloud.google.com,resources=gkehubmemberships,verbs=get;list;watch
100+
101+
//+kubebuilder:rbac:groups=core.cnrm.cloud.google.com,resources=configconnectors;configconnectorcontexts,verbs=get;list;watch
98102

99103
func (r *RootSyncDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
100104
var rootsyncdeployment v1alpha1.RootSyncDeployment

porch/pkg/controllerrestmapper/caching.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func (c *cachedGroupVersion) fetch(discovery discovery.DiscoveryInterface) (map[
101101
if meta.IsNoMatchError(err) || apierrors.IsNotFound(err) {
102102
return nil, nil
103103
} else {
104-
klog.Infof("unexpected error from ServerResourcesForGroupVersion(%v): %w", c.gv, err)
104+
klog.Infof("unexpected error from ServerResourcesForGroupVersion(%v): %v", c.gv, err)
105105
return nil, fmt.Errorf("error from ServerResourcesForGroupVersion(%v): %w", c.gv, err)
106106
}
107107
}

0 commit comments

Comments
 (0)