Skip to content

Commit 5b71942

Browse files
authored
Fix privateca for forceDirect (#9693)
This PR removes the temporary PrivateCA* forceDirect exclusion and implements the missing CRUD methods (Create, Update, Delete, Export) and the tagging diff comparison for the PrivateCACAPool direct controller. - Removed the PrivateCA exclusion from `tests/e2e/unified_test.go` to test PrivateCA resources under forceDirect. - Implemented Delete, Create, Update, Export, and status update helper methods in `pkg/controller/direct/privateca/privatecapool_controller.go`. - Generated and aligned mock e2e golden files for privatecacapoolbasic. Fixes #9650
2 parents c67d291 + 15d30b5 commit 5b71942

9 files changed

Lines changed: 1308 additions & 114 deletions

File tree

pkg/controller/direct/common/visitfields.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ func (w *visitorWalker) visitAny(path string, v reflect.Value) {
9898
for i := 0; i < v.Len(); i++ {
9999
w.visitAny(path+"[]", v.Index(i))
100100
}
101-
case reflect.Uint8:
102-
// Do not visit []byte as individual values, treat as a leaf
101+
case reflect.Uint8, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.Bool:
102+
// Treat primitive slices as leaves
103103
default:
104104
w.errs = append(w.errs, fmt.Errorf("visiting slice of type %v is not supported", elemType.Kind()))
105105
}

pkg/controller/direct/privateca/privatecapool_controller.go

Lines changed: 132 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,22 @@ import (
2222
iampb "cloud.google.com/go/iam/apiv1/iampb"
2323
api "cloud.google.com/go/security/privateca/apiv1"
2424
pb "cloud.google.com/go/security/privateca/apiv1/privatecapb"
25+
"google.golang.org/protobuf/types/known/fieldmaskpb"
2526
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2627
"k8s.io/apimachinery/pkg/runtime"
28+
"k8s.io/klog/v2"
2729

2830
krm "github.com/GoogleCloudPlatform/k8s-config-connector/apis/privateca/v1beta1"
2931
refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
3032
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/config"
3133
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
34+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/common"
3235
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
3336
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
37+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/tags"
38+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/label"
39+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/mappers"
40+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/structuredreporting"
3441
)
3542

3643
func init() {
@@ -57,7 +64,7 @@ type caPoolAdapter struct {
5764
location string
5865
caPoolID string
5966

60-
desired *krm.PrivateCACAPool
67+
desired *pb.CaPool
6168
actual *pb.CaPool
6269
caClient *api.CertificateAuthorityClient
6370
}
@@ -78,6 +85,11 @@ func (m *caPoolModel) AdapterForObject(ctx context.Context, op *directbase.Adapt
7885
return nil, fmt.Errorf("error converting to %T: %w", obj, err)
7986
}
8087

88+
// Always call common.NormalizeReferences to resolve references
89+
if err := common.NormalizeReferences(ctx, reader, obj, nil); err != nil {
90+
return nil, fmt.Errorf("normalizing references: %w", err)
91+
}
92+
8193
resourceID := direct.ValueOf(obj.Spec.ResourceID)
8294
if resourceID == "" {
8395
resourceID = obj.GetName()
@@ -100,11 +112,18 @@ func (m *caPoolModel) AdapterForObject(ctx context.Context, op *directbase.Adapt
100112
return nil, fmt.Errorf("cannot resolve project")
101113
}
102114

115+
mapCtx := &direct.MapContext{}
116+
desired := PrivateCACAPoolSpec_ToProto(mapCtx, &obj.Spec)
117+
if mapCtx.Err() != nil {
118+
return nil, mapCtx.Err()
119+
}
120+
desired.Labels = label.NewGCPLabelsFromK8sLabels(u.GetLabels())
121+
103122
return &caPoolAdapter{
104123
caPoolID: resourceID,
105124
location: location,
106125
projectID: projectID,
107-
desired: obj,
126+
desired: desired,
108127
caClient: caClient,
109128
}, nil
110129
}
@@ -136,22 +155,129 @@ func (m *caPoolModel) AdapterForURL(ctx context.Context, url string) (directbase
136155

137156
// Delete implements the Adapter interface.
138157
func (a *caPoolAdapter) Delete(ctx context.Context, deleteOp *directbase.DeleteOperation) (bool, error) {
139-
return false, fmt.Errorf("not implemented")
158+
log := klog.FromContext(ctx)
159+
log.V(2).Info("deleting PrivateCACAPool", "name", a.fullyQualifiedName())
160+
161+
req := &pb.DeleteCaPoolRequest{Name: a.fullyQualifiedName()}
162+
op, err := a.caClient.DeleteCaPool(ctx, req)
163+
if err != nil {
164+
if direct.IsNotFound(err) {
165+
log.V(2).Info("skipping delete for non-existent PrivateCACAPool, assuming it was already deleted", "name", a.fullyQualifiedName())
166+
return true, nil
167+
}
168+
return false, fmt.Errorf("deleting PrivateCACAPool %s: %w", a.fullyQualifiedName(), err)
169+
}
170+
log.V(2).Info("successfully deleted PrivateCACAPool", "name", a.fullyQualifiedName())
171+
172+
err = op.Wait(ctx)
173+
if err != nil {
174+
return false, fmt.Errorf("waiting delete PrivateCACAPool %s: %w", a.fullyQualifiedName(), err)
175+
}
176+
return true, nil
140177
}
141178

142179
// Create implements the Adapter interface.
143180
func (a *caPoolAdapter) Create(ctx context.Context, createOp *directbase.CreateOperation) error {
144-
return fmt.Errorf("not implemented")
181+
log := klog.FromContext(ctx)
182+
log.V(2).Info("creating PrivateCACAPool", "id", a.fullyQualifiedName())
183+
184+
parent := fmt.Sprintf("projects/%s/locations/%s", a.projectID, a.location)
185+
186+
req := &pb.CreateCaPoolRequest{
187+
Parent: parent,
188+
CaPoolId: a.caPoolID,
189+
CaPool: a.desired,
190+
}
191+
op, err := a.caClient.CreateCaPool(ctx, req)
192+
if err != nil {
193+
return fmt.Errorf("creating PrivateCACAPool %s: %w", a.fullyQualifiedName(), err)
194+
}
195+
created, err := op.Wait(ctx)
196+
if err != nil {
197+
return fmt.Errorf("waiting PrivateCACAPool %s creation: %w", a.fullyQualifiedName(), err)
198+
}
199+
log.V(2).Info("successfully created PrivateCACAPool", "name", a.fullyQualifiedName())
200+
201+
return a.updateStatus(ctx, createOp, created)
145202
}
146203

147204
// Update implements the Adapter interface.
148205
func (a *caPoolAdapter) Update(ctx context.Context, updateOp *directbase.UpdateOperation) error {
149-
return fmt.Errorf("not implemented")
206+
log := klog.FromContext(ctx)
207+
log.V(2).Info("updating PrivateCACAPool", "name", a.fullyQualifiedName())
208+
209+
diffs, updateMask, err := comparePrivateCACAPool(ctx, a.actual, a.desired)
210+
if err != nil {
211+
return err
212+
}
213+
214+
latest := a.actual
215+
if diffs.HasDiff() {
216+
diffs.Object = updateOp.GetUnstructured()
217+
structuredreporting.ReportDiff(ctx, diffs)
218+
219+
a.desired.Name = a.fullyQualifiedName()
220+
req := &pb.UpdateCaPoolRequest{
221+
UpdateMask: updateMask,
222+
CaPool: a.desired,
223+
}
224+
op, err := a.caClient.UpdateCaPool(ctx, req)
225+
if err != nil {
226+
return fmt.Errorf("updating PrivateCACAPool %s: %w", a.fullyQualifiedName(), err)
227+
}
228+
updated, err := op.Wait(ctx)
229+
if err != nil {
230+
return fmt.Errorf("waiting update PrivateCACAPool %s: %w", a.fullyQualifiedName(), err)
231+
}
232+
log.V(2).Info("successfully updated PrivateCACAPool", "name", a.fullyQualifiedName())
233+
latest = updated
234+
}
235+
236+
return a.updateStatus(ctx, updateOp, latest)
150237
}
151238

152239
// Export implements the Adapter interface.
153240
func (a *caPoolAdapter) Export(ctx context.Context) (*unstructured.Unstructured, error) {
154-
return nil, fmt.Errorf("not implemented")
241+
if a.actual == nil {
242+
return nil, fmt.Errorf("Find() not called")
243+
}
244+
u := &unstructured.Unstructured{}
245+
246+
obj := &krm.PrivateCACAPool{}
247+
mapCtx := &direct.MapContext{}
248+
obj.Spec = direct.ValueOf(PrivateCACAPoolSpec_FromProto(mapCtx, a.actual))
249+
if mapCtx.Err() != nil {
250+
return nil, mapCtx.Err()
251+
}
252+
253+
obj.Spec.ProjectRef = &refs.ProjectRef{Name: a.projectID}
254+
obj.Spec.Location = a.location
255+
uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
256+
if err != nil {
257+
return nil, err
258+
}
259+
u.Object = uObj
260+
u.SetName(a.actual.Name)
261+
u.SetGroupVersionKind(krm.PrivateCACAPoolGVK)
262+
return u, nil
263+
}
264+
265+
func comparePrivateCACAPool(ctx context.Context, actual, desired *pb.CaPool) (*structuredreporting.Diff, *fieldmaskpb.FieldMask, error) {
266+
maskedActual, err := mappers.OnlySpecFields(actual, PrivateCACAPoolSpec_FromProto, PrivateCACAPoolSpec_ToProto)
267+
if err != nil {
268+
return nil, nil, err
269+
}
270+
maskedActual.Name = desired.Name
271+
diffs, updateMask, err := tags.DiffForTopLevelFields(ctx, desired.ProtoReflect(), maskedActual.ProtoReflect())
272+
if err != nil {
273+
return nil, nil, err
274+
}
275+
return diffs, updateMask, nil
276+
}
277+
278+
func (a *caPoolAdapter) updateStatus(ctx context.Context, op directbase.Operation, latest *pb.CaPool) error {
279+
status := &krm.PrivateCACAPoolStatus{}
280+
return op.UpdateStatus(ctx, status, nil)
155281
}
156282

157283
// Find implements the Adapter interface.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
6d5
2+
< cnrm.cloud.google.com/state-into-spec: absent
3+
10c9
4+
< generation: 3
5+
---
6+
> generation: 2
7+
89d87
8+
< resourceID: privatecacapool-${uniqueId}
9+
98c96
10+
< observedGeneration: 3
11+
---
12+
> observedGeneration: 2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
apiVersion: privateca.cnrm.cloud.google.com/v1beta1
2+
kind: PrivateCACAPool
3+
metadata:
4+
annotations:
5+
cnrm.cloud.google.com/management-conflict-prevention-policy: none
6+
cnrm.cloud.google.com/state-into-spec: absent
7+
finalizers:
8+
- cnrm.cloud.google.com/finalizer
9+
- cnrm.cloud.google.com/deletion-defender
10+
generation: 3
11+
labels:
12+
cnrm-test: "true"
13+
label-one: value-one
14+
label-two: value-two
15+
name: privatecacapool-${uniqueId}
16+
namespace: ${uniqueId}
17+
spec:
18+
issuancePolicy:
19+
allowedIssuanceModes:
20+
allowConfigBasedIssuance: true
21+
allowCsrBasedIssuance: false
22+
allowedKeyTypes:
23+
- rsa:
24+
maxModulusSize: 256
25+
minModulusSize: 128
26+
- ellipticCurve:
27+
signatureAlgorithm: ECDSA_P256
28+
baselineValues:
29+
additionalExtensions:
30+
- critical: true
31+
objectId:
32+
objectIdPath:
33+
- 1
34+
- 6
35+
value: bmV3LXN0cmluZwo=
36+
aiaOcspServers:
37+
- new-string
38+
caOptions:
39+
isCa: true
40+
maxIssuerPathLength: 6
41+
keyUsage:
42+
baseKeyUsage:
43+
certSign: true
44+
contentCommitment: true
45+
crlSign: true
46+
dataEncipherment: true
47+
decipherOnly: true
48+
digitalSignature: true
49+
encipherOnly: true
50+
keyAgreement: true
51+
keyEncipherment: true
52+
extendedKeyUsage:
53+
clientAuth: true
54+
codeSigning: true
55+
emailProtection: true
56+
ocspSigning: true
57+
serverAuth: true
58+
timeStamping: true
59+
unknownExtendedKeyUsages:
60+
- objectIdPath:
61+
- 1
62+
- 6
63+
policyIds:
64+
- objectIdPath:
65+
- 1
66+
- 6
67+
identityConstraints:
68+
allowSubjectAltNamesPassthrough: true
69+
allowSubjectPassthrough: true
70+
celExpression:
71+
description: Always true
72+
expression: "true"
73+
location: update_devops.ca_pool.json
74+
title: Updated expression
75+
maximumLifetime: 86400s
76+
passthroughExtensions:
77+
additionalExtensions:
78+
- objectIdPath:
79+
- 1
80+
- 6
81+
knownExtensions:
82+
- EXTENDED_KEY_USAGE
83+
location: us-central1
84+
projectRef:
85+
external: projects/${projectId}
86+
publishingOptions:
87+
publishCaCert: true
88+
publishCrl: true
89+
resourceID: privatecacapool-${uniqueId}
90+
tier: ENTERPRISE
91+
status:
92+
conditions:
93+
- lastTransitionTime: "1970-01-01T00:00:00Z"
94+
message: The resource is up to date
95+
reason: UpToDate
96+
status: "True"
97+
type: Ready
98+
observedGeneration: 3

pkg/test/resourcefixture/testdata/basic/privateca/v1beta1/privatecacapool/privatecacapoolbasic/_generated_object_privatecacapoolbasic.golden.yaml

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,12 @@
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-
151
apiVersion: privateca.cnrm.cloud.google.com/v1beta1
162
kind: PrivateCACAPool
173
metadata:
184
annotations:
195
cnrm.cloud.google.com/management-conflict-prevention-policy: none
20-
cnrm.cloud.google.com/state-into-spec: absent
216
finalizers:
227
- cnrm.cloud.google.com/finalizer
238
- cnrm.cloud.google.com/deletion-defender
24-
generation: 3
9+
generation: 2
2510
labels:
2611
cnrm-test: "true"
2712
label-one: value-one
@@ -100,7 +85,6 @@ spec:
10085
publishingOptions:
10186
publishCaCert: true
10287
publishCrl: true
103-
resourceID: privatecacapool-${uniqueId}
10488
tier: ENTERPRISE
10589
status:
10690
conditions:
@@ -109,4 +93,4 @@ status:
10993
reason: UpToDate
11094
status: "True"
11195
type: Ready
112-
observedGeneration: 3
96+
observedGeneration: 2

0 commit comments

Comments
 (0)