Skip to content

Commit 00586af

Browse files
authored
Move ComputeDiskResourcePolicyAttachment to identity and refs pattern (#10666)
This PR moves `ComputeDiskResourcePolicyAttachment` to the identity and refs pattern, conforming to the KCC Identity Reference skill specifications. ### Key Changes - Declared interface implementations `identity.IdentityV2` and `identity.Resource` on `ComputeDiskResourcePolicyAttachment`. - Defined the template variable `ComputeDiskResourcePolicyAttachmentIdentityFormat` with `{project}`, `{zone}`, `{disk}`, and `{name}` mapping correctly to struct fields. - Implemented `computediskresourcepolicyattachment_identity.go`, `computediskresourcepolicyattachment_reference.go`, and their unit tests under `apis/compute/v1alpha1/`. - Updated the CAIS golden identities YAML files for `ComputeDiskResourcePolicyAttachment` since its identity is now fully supported. Fixes #10665 --- This PR was generated by the **overseer** agent (powered by the gemini-3.5-flash model).
2 parents 64a2ac9 + f88a5be commit 00586af

26 files changed

Lines changed: 465 additions & 31 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright 2026 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 v1alpha1
16+
17+
import (
18+
"context"
19+
"fmt"
20+
21+
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common/identity"
22+
computev1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/compute/v1beta1"
23+
apirefs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs"
24+
refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
25+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gcpurls"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
)
28+
29+
var (
30+
_ identity.IdentityV2 = &ComputeDiskResourcePolicyAttachmentIdentity{}
31+
_ identity.Resource = &ComputeDiskResourcePolicyAttachment{}
32+
)
33+
34+
var ComputeDiskResourcePolicyAttachmentIdentityFormat = gcpurls.Template[ComputeDiskResourcePolicyAttachmentIdentity](
35+
"compute.googleapis.com",
36+
"projects/{project}/zones/{zone}/disks/{disk}/{name}",
37+
)
38+
39+
// ComputeDiskResourcePolicyAttachmentIdentity is the identity of a GCP ComputeDiskResourcePolicyAttachment resource.
40+
// +k8s:deepcopy-gen=false
41+
type ComputeDiskResourcePolicyAttachmentIdentity struct {
42+
Project string
43+
Zone string
44+
Disk string
45+
Name string
46+
}
47+
48+
func (i *ComputeDiskResourcePolicyAttachmentIdentity) String() string {
49+
return ComputeDiskResourcePolicyAttachmentIdentityFormat.ToString(*i)
50+
}
51+
52+
func (i *ComputeDiskResourcePolicyAttachmentIdentity) FromExternal(ref string) error {
53+
trimmedRef := apirefs.TrimComputeURIPrefix(ref)
54+
parsed, match, err := ComputeDiskResourcePolicyAttachmentIdentityFormat.Parse(trimmedRef)
55+
if err != nil {
56+
return fmt.Errorf("format of ComputeDiskResourcePolicyAttachment external=%q was not known (use %s): %w", ref, ComputeDiskResourcePolicyAttachmentIdentityFormat.CanonicalForm(), err)
57+
}
58+
if !match {
59+
return fmt.Errorf("format of ComputeDiskResourcePolicyAttachment external=%q was not known (use %s)", ref, ComputeDiskResourcePolicyAttachmentIdentityFormat.CanonicalForm())
60+
}
61+
62+
*i = *parsed
63+
return nil
64+
}
65+
66+
func (i *ComputeDiskResourcePolicyAttachmentIdentity) Host() string {
67+
return ComputeDiskResourcePolicyAttachmentIdentityFormat.Host()
68+
}
69+
70+
func (i *ComputeDiskResourcePolicyAttachmentIdentity) ParentString() string {
71+
return fmt.Sprintf("projects/%s/zones/%s/disks/%s", i.Project, i.Zone, i.Disk)
72+
}
73+
74+
func getIdentityFromComputeDiskResourcePolicyAttachmentSpec(ctx context.Context, reader client.Reader, obj *ComputeDiskResourcePolicyAttachment) (*ComputeDiskResourcePolicyAttachmentIdentity, error) {
75+
resourceID, err := refs.GetResourceID(obj)
76+
if err != nil {
77+
return nil, fmt.Errorf("cannot resolve resource ID")
78+
}
79+
80+
projectID, err := refs.ResolveProjectID(ctx, reader, obj)
81+
if err != nil {
82+
return nil, fmt.Errorf("cannot resolve project")
83+
}
84+
85+
zone := obj.Spec.Zone
86+
if zone == "" {
87+
return nil, fmt.Errorf("cannot resolve zone: spec.zone is empty")
88+
}
89+
90+
diskRef := obj.Spec.DiskRef
91+
if err := diskRef.Normalize(ctx, reader, obj.Namespace); err != nil {
92+
return nil, fmt.Errorf("cannot resolve diskRef: %w", err)
93+
}
94+
95+
diskId, err := computev1beta1.ParseComputeDiskExternal(diskRef.External)
96+
if err != nil {
97+
return nil, fmt.Errorf("cannot parse disk external reference: %w", err)
98+
}
99+
100+
identity := &ComputeDiskResourcePolicyAttachmentIdentity{
101+
Project: projectID,
102+
Zone: zone,
103+
Disk: diskId.Disk,
104+
Name: resourceID,
105+
}
106+
return identity, nil
107+
}
108+
109+
func (obj *ComputeDiskResourcePolicyAttachment) GetIdentity(ctx context.Context, reader client.Reader) (identity.Identity, error) {
110+
specIdentity, err := getIdentityFromComputeDiskResourcePolicyAttachmentSpec(ctx, reader, obj)
111+
if err != nil {
112+
return nil, err
113+
}
114+
return specIdentity, nil
115+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// Copyright 2026 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 v1alpha1
16+
17+
import (
18+
"context"
19+
"testing"
20+
21+
computev1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/compute/v1beta1"
22+
"github.com/google/go-cmp/cmp"
23+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
24+
"k8s.io/apimachinery/pkg/runtime"
25+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
26+
)
27+
28+
func TestComputeDiskResourcePolicyAttachmentIdentity_FromExternal(t *testing.T) {
29+
tests := []struct {
30+
name string
31+
ref string
32+
wantErr bool
33+
want *ComputeDiskResourcePolicyAttachmentIdentity
34+
}{
35+
{
36+
name: "valid reference",
37+
ref: "projects/my-project/zones/us-central1-a/disks/my-disk/my-policy",
38+
want: &ComputeDiskResourcePolicyAttachmentIdentity{
39+
Project: "my-project",
40+
Zone: "us-central1-a",
41+
Disk: "my-disk",
42+
Name: "my-policy",
43+
},
44+
},
45+
{
46+
name: "invalid reference format",
47+
ref: "invalid/format",
48+
wantErr: true,
49+
},
50+
{
51+
name: "full url",
52+
ref: "https://compute.googleapis.com/compute/v1/projects/my-project/zones/us-central1-a/disks/my-disk/my-policy",
53+
want: &ComputeDiskResourcePolicyAttachmentIdentity{
54+
Project: "my-project",
55+
Zone: "us-central1-a",
56+
Disk: "my-disk",
57+
Name: "my-policy",
58+
},
59+
},
60+
}
61+
62+
for _, tt := range tests {
63+
t.Run(tt.name, func(t *testing.T) {
64+
i := &ComputeDiskResourcePolicyAttachmentIdentity{}
65+
err := i.FromExternal(tt.ref)
66+
if (err != nil) != tt.wantErr {
67+
t.Fatalf("FromExternal() error = %v, wantErr %v", err, tt.wantErr)
68+
}
69+
if !tt.wantErr {
70+
if diff := cmp.Diff(tt.want, i); diff != "" {
71+
t.Errorf("FromExternal() mismatch (-want +got):\n%s", diff)
72+
}
73+
}
74+
})
75+
}
76+
}
77+
78+
func TestComputeDiskResourcePolicyAttachmentRef_ValidateExternal(t *testing.T) {
79+
tests := []struct {
80+
name string
81+
ref string
82+
wantErr bool
83+
}{
84+
{
85+
name: "valid reference",
86+
ref: "projects/my-project/zones/us-central1-a/disks/my-disk/my-policy",
87+
wantErr: false,
88+
},
89+
{
90+
name: "invalid prefix",
91+
ref: "invalid/my-project/zones/us-central1-a/disks/my-disk/my-policy",
92+
wantErr: true,
93+
},
94+
{
95+
name: "missing disk",
96+
ref: "projects/my-project/zones/us-central1-a/my-policy",
97+
wantErr: true,
98+
},
99+
{
100+
name: "empty string",
101+
ref: "",
102+
wantErr: true,
103+
},
104+
}
105+
106+
for _, tt := range tests {
107+
t.Run(tt.name, func(t *testing.T) {
108+
r := &ComputeDiskResourcePolicyAttachmentRef{}
109+
if err := r.ValidateExternal(tt.ref); (err != nil) != tt.wantErr {
110+
t.Errorf("ComputeDiskResourcePolicyAttachmentRef.ValidateExternal() error = %v, wantErr %v", err, tt.wantErr)
111+
}
112+
})
113+
}
114+
}
115+
116+
func TestComputeDiskResourcePolicyAttachmentRef_Normalize(t *testing.T) {
117+
testCases := []struct {
118+
name string
119+
ref *ComputeDiskResourcePolicyAttachmentRef
120+
otherNamespace string
121+
objects []runtime.Object
122+
wantExternal string
123+
wantErr string
124+
}{
125+
{
126+
name: "external with valid format",
127+
ref: &ComputeDiskResourcePolicyAttachmentRef{
128+
External: "projects/test-project/zones/us-central1-a/disks/test-disk/test-policy",
129+
},
130+
wantExternal: "projects/test-project/zones/us-central1-a/disks/test-disk/test-policy",
131+
},
132+
{
133+
name: "external with invalid format",
134+
ref: &ComputeDiskResourcePolicyAttachmentRef{
135+
External: "invalid-format",
136+
},
137+
wantErr: `format of ComputeDiskResourcePolicyAttachment external="invalid-format" was not known (use projects/{project}/zones/{zone}/disks/{disk}/{name})`,
138+
},
139+
{
140+
name: "name specified, with status.externalRef",
141+
ref: &ComputeDiskResourcePolicyAttachmentRef{
142+
Name: "test-attachment",
143+
Namespace: "my-namespace",
144+
},
145+
objects: []runtime.Object{
146+
&unstructured.Unstructured{
147+
Object: map[string]interface{}{
148+
"apiVersion": "compute.cnrm.cloud.google.com/v1alpha1",
149+
"kind": "ComputeDiskResourcePolicyAttachment",
150+
"metadata": map[string]interface{}{
151+
"name": "test-attachment",
152+
"namespace": "my-namespace",
153+
},
154+
"status": map[string]interface{}{
155+
"externalRef": "projects/test-project/zones/us-central1-a/disks/test-disk/test-policy",
156+
},
157+
},
158+
},
159+
},
160+
wantExternal: "projects/test-project/zones/us-central1-a/disks/test-disk/test-policy",
161+
},
162+
{
163+
name: "name specified, without status.externalRef",
164+
ref: &ComputeDiskResourcePolicyAttachmentRef{
165+
Name: "test-attachment",
166+
Namespace: "my-namespace",
167+
},
168+
objects: []runtime.Object{
169+
&unstructured.Unstructured{
170+
Object: map[string]interface{}{
171+
"apiVersion": "compute.cnrm.cloud.google.com/v1alpha1",
172+
"kind": "ComputeDiskResourcePolicyAttachment",
173+
"metadata": map[string]interface{}{
174+
"name": "test-attachment",
175+
"namespace": "my-namespace",
176+
},
177+
"status": map[string]interface{}{},
178+
},
179+
},
180+
},
181+
wantErr: `reference ComputeDiskResourcePolicyAttachment my-namespace/test-attachment is not ready`,
182+
},
183+
}
184+
185+
for _, tc := range testCases {
186+
t.Run(tc.name, func(t *testing.T) {
187+
s := runtime.NewScheme()
188+
s.AddKnownTypes(GroupVersion, &unstructured.Unstructured{})
189+
s.AddKnownTypes(computev1beta1.GroupVersion, &computev1beta1.ComputeDisk{})
190+
cl := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tc.objects...).Build()
191+
192+
err := tc.ref.Normalize(context.TODO(), cl, "default")
193+
if tc.wantErr != "" {
194+
if err == nil {
195+
t.Fatalf("Normalize() expected error %q, got nil", tc.wantErr)
196+
}
197+
if err.Error() != tc.wantErr {
198+
t.Errorf("Normalize() error = %q, want %q", err.Error(), tc.wantErr)
199+
}
200+
return
201+
}
202+
if err != nil {
203+
t.Fatalf("Normalize() unexpected error: %v", err)
204+
}
205+
if tc.ref.External != tc.wantExternal {
206+
t.Errorf("Normalize() external = %q, want %q", tc.ref.External, tc.wantExternal)
207+
}
208+
})
209+
}
210+
}

0 commit comments

Comments
 (0)