Skip to content

Commit 696d21f

Browse files
Merge pull request #4279 from anhdle-sso/resource-networkconnectivity-internalranges
feat: Resource networkconnectivity internalranges controller
2 parents d48d877 + 41df8b9 commit 696d21f

File tree

8 files changed

+887
-1
lines changed

8 files changed

+887
-1
lines changed

config/tests/samples/create/harness.go

+1
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,7 @@ func MaybeSkip(t *testing.T, name string, resources []*unstructured.Unstructured
901901
case schema.GroupKind{Group: "monitoring.cnrm.cloud.google.com", Kind: "MonitoringUptimeCheckConfig"}:
902902

903903
case schema.GroupKind{Group: "networkconnectivity.cnrm.cloud.google.com", Kind: "NetworkConnectivityServiceConnectionPolicy"}:
904+
case schema.GroupKind{Group: "networkconnectivity.cnrm.cloud.google.com", Kind: "NetworkConnectivityInternalRange"}:
904905

905906
case schema.GroupKind{Group: "netapp.cnrm.cloud.google.com", Kind: "NetAppBackupPolicy"}:
906907

mockgcp/mocknetworkconnectivity/internalranges.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,20 @@ func (r *internalRanges) PatchProjectsLocationsInternalRange(ctx context.Context
123123
switch path {
124124
case "prefixLength":
125125
obj.PrefixLength = patch.PrefixLength
126-
126+
case "ip_cidr_range":
127+
obj.IpCidrRange = patch.IpCidrRange
128+
case "labels":
129+
obj.Labels = patch.Labels
130+
case "description":
131+
obj.Description = patch.Description
132+
case "network":
133+
obj.Network = patch.Network
134+
case "peering":
135+
obj.Peering = patch.Peering
136+
case "target_cidr_range":
137+
obj.TargetCidrRange = patch.TargetCidrRange
138+
case "usage":
139+
obj.Usage = patch.Usage
127140
default:
128141
log.Info("unsupported update_mask", "req", req)
129142
return nil, status.Errorf(codes.InvalidArgument, "update_mask path %q not supported by mock", path)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
// Copyright 2024 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+
// +tool:controller
16+
// proto.service: google.cloud.networkconnectivity.v1.HubService
17+
// proto.message: google.cloud.networkconnectivity.v1.InternalRange
18+
// crd.type: NetworkConnectivityInternalRange
19+
// crd.version: v1alpha1
20+
21+
package networkconnectivity
22+
23+
import (
24+
"context"
25+
"fmt"
26+
"reflect"
27+
"strings"
28+
"time"
29+
30+
pb "github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/generated/mockgcp/cloud/networkconnectivity/v1"
31+
api "google.golang.org/api/networkconnectivity/v1"
32+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
33+
"k8s.io/apimachinery/pkg/runtime"
34+
"k8s.io/klog/v2"
35+
"sigs.k8s.io/controller-runtime/pkg/client"
36+
37+
krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/networkconnectivity/v1alpha1"
38+
refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
39+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
40+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
41+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
42+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
43+
)
44+
45+
func init() {
46+
registry.RegisterModel(krm.NetworkConnectivityInternalRangeGVK, NewInternalRangeModel)
47+
}
48+
49+
func NewInternalRangeModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
50+
return &internalRangeModel{config: *config}, nil
51+
}
52+
53+
var _ directbase.Model = &internalRangeModel{}
54+
55+
type internalRangeModel struct {
56+
config config.ControllerConfig
57+
}
58+
59+
func (m *internalRangeModel) AdapterForObject(ctx context.Context, reader client.Reader, u *unstructured.Unstructured) (directbase.Adapter, error) {
60+
obj := &krm.NetworkConnectivityInternalRange{}
61+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &obj); err != nil {
62+
return nil, fmt.Errorf("error converting to %T: %w", obj, err)
63+
}
64+
65+
id, err := krm.NewInternalRangeIdentity(ctx, reader, obj)
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
// normalize reference fields
71+
if obj.Spec.NetworkRef != nil {
72+
if err := obj.Spec.NetworkRef.Normalize(ctx, reader, obj); err != nil {
73+
return nil, err
74+
}
75+
}
76+
77+
// Get networkconnectivity GCP client
78+
gcpClient, err := newGCPClient(ctx, &m.config)
79+
if err != nil {
80+
return nil, err
81+
}
82+
client, err := gcpClient.newNetworkConnectivityClient(ctx)
83+
if err != nil {
84+
return nil, err
85+
}
86+
return &internalRangeAdapter{
87+
gcpClient: client,
88+
id: id,
89+
desired: obj,
90+
}, nil
91+
}
92+
93+
func (m *internalRangeModel) AdapterForURL(ctx context.Context, url string) (directbase.Adapter, error) {
94+
// TODO: Support URLs
95+
return nil, nil
96+
}
97+
98+
type internalRangeAdapter struct {
99+
gcpClient *api.Service
100+
id *krm.InternalRangeIdentity
101+
desired *krm.NetworkConnectivityInternalRange
102+
actual *pb.InternalRange
103+
}
104+
105+
var _ directbase.Adapter = &internalRangeAdapter{}
106+
107+
// Find retrieves the GCP resource.
108+
// Return true means the object is found. This triggers Adapter `Update` call.
109+
// Return false means the object is not found. This triggers Adapter `Create` call.
110+
// Return a non-nil error requeues the requests.
111+
func (a *internalRangeAdapter) Find(ctx context.Context) (bool, error) {
112+
log := klog.FromContext(ctx)
113+
log.V(2).Info("getting networkconnectivity internalrange", "name", a.id)
114+
fqn := a.id.String()
115+
actual, err := a.gcpClient.Projects.Locations.InternalRanges.Get(fqn).Context(ctx).Do()
116+
if err != nil {
117+
if direct.IsNotFound(err) {
118+
return false, nil
119+
}
120+
return false, fmt.Errorf("getting networkconnectivity internalrange %q from gcp: %w", a.id.String(), err)
121+
}
122+
123+
if err := convertAPIToProto(actual, &a.actual); err != nil {
124+
return false, err
125+
}
126+
127+
return true, nil
128+
}
129+
130+
func (a *internalRangeAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
131+
u := createOp.GetUnstructured()
132+
133+
log := klog.FromContext(ctx)
134+
log.V(2).Info("creating networkconnectivity internalrange", "name", a.id)
135+
mapCtx := &direct.MapContext{}
136+
137+
desired := NetworkConnectivityInternalRangeSpec_ToProto(mapCtx, &a.desired.DeepCopy().Spec)
138+
if mapCtx.Err() != nil {
139+
return mapCtx.Err()
140+
}
141+
req := &api.InternalRange{}
142+
err := convertProtoToAPI(desired, req)
143+
144+
fqn := a.id.String()
145+
146+
op, err := a.gcpClient.Projects.Locations.InternalRanges.Create(a.id.Parent().String(), req).InternalRangeId(a.id.ID()).Context(ctx).Do()
147+
if err != nil {
148+
return fmt.Errorf("creating networkconnectivity internalrange %s: %w", fqn, err)
149+
}
150+
if err := a.waitForOperation(ctx, op); err != nil {
151+
return fmt.Errorf("waiting for create of internalrange %q: %w", fqn, err)
152+
}
153+
154+
log.V(2).Info("successfully created networkconnectivity internalrange in gcp", "name", a.id)
155+
156+
created, err := a.gcpClient.Projects.Locations.InternalRanges.Get(fqn).Context(ctx).Do()
157+
if err != nil {
158+
return fmt.Errorf("getting created internalrange %q: %w", fqn, err)
159+
}
160+
161+
resourceID := lastComponent(created.Name)
162+
if err := unstructured.SetNestedField(u.Object, resourceID, "spec", "resourceID"); err != nil {
163+
return fmt.Errorf("setting spec.resourceID: %w", err)
164+
}
165+
var createdPB *pb.InternalRange
166+
if err := convertAPIToProto(created, &createdPB); err != nil {
167+
return err
168+
}
169+
status := &krm.NetworkConnectivityInternalRangeStatus{}
170+
status.ObservedState = NetworkConnectivityInternalRangeObservedState_FromProto(mapCtx, createdPB)
171+
if mapCtx.Err() != nil {
172+
return mapCtx.Err()
173+
}
174+
return createOp.UpdateStatus(ctx, status, nil)
175+
}
176+
177+
func (a *internalRangeAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error {
178+
log := klog.FromContext(ctx)
179+
log.V(2).Info("updating networkconnectivity internalrange", "name", a.id)
180+
mapCtx := &direct.MapContext{}
181+
182+
desired := a.desired.DeepCopy()
183+
resource := NetworkConnectivityInternalRangeSpec_ToProto(mapCtx, &desired.Spec)
184+
if mapCtx.Err() != nil {
185+
return mapCtx.Err()
186+
}
187+
188+
paths := []string{}
189+
if desired.Spec.Description != nil && !reflect.DeepEqual(resource.Description, a.actual.Description) {
190+
paths = append(paths, "description")
191+
}
192+
if desired.Spec.IPCIDRRange != nil && !reflect.DeepEqual(resource.IpCidrRange, a.actual.IpCidrRange) {
193+
paths = append(paths, "ipCidrRange")
194+
}
195+
if desired.Spec.Labels != nil && !reflect.DeepEqual(resource.Labels, a.actual.Labels) {
196+
paths = append(paths, "labels")
197+
}
198+
if desired.Spec.Peering != nil && !reflect.DeepEqual(resource.Peering, a.actual.Peering) {
199+
paths = append(paths, "peering")
200+
}
201+
if desired.Spec.PrefixLength != nil && !reflect.DeepEqual(resource.PrefixLength, a.actual.PrefixLength) {
202+
paths = append(paths, "prefixLength")
203+
}
204+
if desired.Spec.TargetCIDRRange != nil && !reflect.DeepEqual(resource.TargetCidrRange, a.actual.TargetCidrRange) {
205+
paths = append(paths, "targetCidrRange")
206+
}
207+
if desired.Spec.Usage != nil && !reflect.DeepEqual(resource.Usage, a.actual.Usage) {
208+
paths = append(paths, "usage")
209+
}
210+
211+
if len(paths) > 0 {
212+
resource.Name = a.id.String() // we need to set the name so that GCP API can identify the resource
213+
req := &api.InternalRange{}
214+
if err := convertProtoToAPI(resource, req); err != nil {
215+
return err
216+
}
217+
fqn := a.id.String()
218+
op, err := a.gcpClient.Projects.Locations.InternalRanges.Patch(fqn, req).UpdateMask(strings.Join(paths, ",")).Context(ctx).Do()
219+
if err != nil {
220+
return fmt.Errorf("updating networkconnectivity internalrange %s: %w", fqn, err)
221+
}
222+
if err := a.waitForOperation(ctx, op); err != nil {
223+
return fmt.Errorf("waiting for update of internalrange %q: %w", fqn, err)
224+
}
225+
log.V(2).Info("successfully updated networkconnectivity internalrange", "name", fqn)
226+
updatedAPI, err := a.gcpClient.Projects.Locations.InternalRanges.Get(fqn).Context(ctx).Do()
227+
if err != nil {
228+
return fmt.Errorf("getting updated internalrange %q: %w", fqn, err)
229+
}
230+
if err := convertAPIToProto(updatedAPI, &a.actual); err != nil {
231+
return err
232+
}
233+
}
234+
235+
status := &krm.NetworkConnectivityInternalRangeStatus{}
236+
status.ObservedState = NetworkConnectivityInternalRangeObservedState_FromProto(mapCtx, a.actual)
237+
if mapCtx.Err() != nil {
238+
return mapCtx.Err()
239+
}
240+
return updateOp.UpdateStatus(ctx, status, nil)
241+
}
242+
243+
func (a *internalRangeAdapter) Export(ctx context.Context) (*unstructured.Unstructured, error) {
244+
if a.actual == nil {
245+
return nil, fmt.Errorf("Find() not called")
246+
}
247+
u := &unstructured.Unstructured{}
248+
249+
obj := &krm.NetworkConnectivityInternalRange{}
250+
mapCtx := &direct.MapContext{}
251+
obj.Spec = direct.ValueOf(NetworkConnectivityInternalRangeSpec_FromProto(mapCtx, a.actual))
252+
if mapCtx.Err() != nil {
253+
return nil, mapCtx.Err()
254+
}
255+
obj.Spec.ProjectRef = &refs.ProjectRef{External: a.id.Parent().ProjectID}
256+
obj.Spec.Location = a.id.Parent().Location
257+
uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
258+
if err != nil {
259+
return nil, err
260+
}
261+
262+
u.SetName(a.actual.Name)
263+
u.SetGroupVersionKind(krm.NetworkConnectivityInternalRangeGVK)
264+
u.Object = uObj
265+
return u, nil
266+
}
267+
268+
// Delete implements the Adapter interface.
269+
func (a *internalRangeAdapter) Delete(ctx context.Context, deleteOp *directbase.DeleteOperation) (bool, error) {
270+
log := klog.FromContext(ctx)
271+
log.V(2).Info("deleting networkconnectivity internalrange", "name", a.id)
272+
fqn := a.id.String()
273+
op, err := a.gcpClient.Projects.Locations.ServiceConnectionPolicies.Delete(fqn).Context(ctx).Do()
274+
if err != nil {
275+
if direct.IsNotFound(err) {
276+
log.V(2).Info("skipping delete for non-existent networkconnectivity internalrange, assuming it was already deleted", "name", a.id)
277+
return true, nil
278+
}
279+
return false, fmt.Errorf("deleting networkconnectivity internalrange %s: %w", a.id.String(), err)
280+
}
281+
log.V(2).Info("successfully deleted networkconnectivity internalrange", "name", a.id)
282+
283+
if err := a.waitForOperation(ctx, op); err != nil {
284+
return false, fmt.Errorf("waiting delete networkconnectivity internalrange %s: %w", a.id, err)
285+
}
286+
return true, nil
287+
}
288+
289+
func (a *internalRangeAdapter) waitForOperation(ctx context.Context, op *api.GoogleLongrunningOperation) error {
290+
for {
291+
if err := ctx.Err(); err != nil {
292+
return err
293+
}
294+
295+
latest, err := a.gcpClient.Projects.Locations.Operations.Get(op.Name).Context(ctx).Do()
296+
if err != nil {
297+
return fmt.Errorf("getting operation %q: %w", op.Name, err)
298+
}
299+
300+
if latest.Done {
301+
return nil
302+
}
303+
304+
time.Sleep(2 * time.Second)
305+
}
306+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
apiVersion: networkconnectivity.cnrm.cloud.google.com/v1alpha1
2+
kind: NetworkConnectivityInternalRange
3+
metadata:
4+
finalizers:
5+
- cnrm.cloud.google.com/finalizer
6+
- cnrm.cloud.google.com/deletion-defender
7+
generation: 2
8+
labels:
9+
cnrm-test: "true"
10+
name: networkconnectivityinternalrange-minimal-${uniqueId}
11+
namespace: ${uniqueId}
12+
spec:
13+
description: Updated description
14+
location: global
15+
networkRef:
16+
name: computenetwork-${uniqueId}
17+
prefixLength: 28
18+
projectRef:
19+
external: ${projectId}
20+
status:
21+
conditions:
22+
- lastTransitionTime: "1970-01-01T00:00:00Z"
23+
message: The resource is up to date
24+
reason: UpToDate
25+
status: "True"
26+
type: Ready
27+
observedGeneration: 2
28+
observedState: {}

0 commit comments

Comments
 (0)