Skip to content

Commit db2656f

Browse files
committed
Greenfield: Implement direct KRM types, identity, and generate.sh for SecurityCenterManagementSecurityHealthAnalyticsCustomModule
Implement direct KRM types, identity, and generate.sh for the Greenfield resource SecurityCenterManagementSecurityHealthAnalyticsCustomModule. In this change, we: 1. Scaffolded apis/securitycentermanagement/v1alpha1 directory and added doc.go, groupversion_info.go, and generate.sh. 2. Implemented direct KRM types in apis/securitycentermanagement/v1alpha1/securitycentermanagementsecurityhealthanalyticscustommodule_types.go. 3. Implemented identity interface with gcpurls.Template in apis/securitycentermanagement/v1alpha1/securitycentermanagementsecurityhealthanalyticscustommodule_identity.go. 4. Implemented reference type and normalization logic in apis/securitycentermanagement/v1alpha1/securitycentermanagementsecurityhealthanalyticscustommodule_reference.go. 5. Added unit tests for FromExternal identity parsing. 6. Generated CRDs, clientsets, and static config files. Fixes #11397
1 parent a64a351 commit db2656f

21 files changed

Lines changed: 1597 additions & 0 deletions

.github/workflows/ci-presubmit.yaml

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
// +kcc:proto=google.cloud.securitycentermanagement.v1
16+
package v1alpha1
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/bash
2+
# Copyright 2026 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
set -o errexit
17+
set -o nounset
18+
set -o pipefail
19+
20+
REPO_ROOT="$(git rev-parse --show-toplevel)"
21+
source "${REPO_ROOT}/dev/tools/goimports.sh"
22+
cd ${REPO_ROOT}/dev/tools/controllerbuilder
23+
24+
./generate-proto.sh
25+
26+
go run . generate-types \
27+
--service google.cloud.securitycentermanagement.v1 \
28+
--api-version securitycentermanagement.cnrm.cloud.google.com/v1alpha1 \
29+
--include-skipped-output \
30+
--resource SecurityCenterManagementSecurityHealthAnalyticsCustomModule:SecurityHealthAnalyticsCustomModule
31+
32+
go run . generate-mapper \
33+
--service google.cloud.securitycentermanagement.v1 \
34+
--api-version securitycentermanagement.cnrm.cloud.google.com/v1alpha1 \
35+
--include-skipped-output
36+
37+
cd ${REPO_ROOT}
38+
dev/tasks/generate-crds
39+
40+
go run -mod=readonly golang.org/x/tools/cmd/goimports@${GOLANG_X_TOOLS_VERSION} -w pkg/controller/direct/securitycentermanagement/
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
// +kubebuilder:object:generate=true
16+
// +groupName=securitycentermanagement.cnrm.cloud.google.com
17+
package v1alpha1
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/runtime/schema"
21+
"sigs.k8s.io/controller-runtime/pkg/scheme"
22+
)
23+
24+
var (
25+
// GroupVersion is group version used to register these objects
26+
GroupVersion = schema.GroupVersion{Group: "securitycentermanagement.cnrm.cloud.google.com", Version: "v1alpha1"}
27+
28+
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
29+
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
30+
31+
// AddToScheme adds the types in this group-version to the given scheme.
32+
AddToScheme = SchemeBuilder.AddToScheme
33+
)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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"
22+
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common/identity"
23+
refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
24+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gcpurls"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
)
27+
28+
var (
29+
_ identity.IdentityV2 = &SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity{}
30+
_ identity.Resource = &SecurityCenterManagementSecurityHealthAnalyticsCustomModule{}
31+
)
32+
33+
var SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentityFormat = gcpurls.Template[SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity](
34+
"securitycentermanagement.googleapis.com",
35+
"projects/{project}/locations/{location}/securityHealthAnalyticsCustomModules/{securitycentermanagementsecurityhealthanalyticscustommodule}",
36+
)
37+
38+
// SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity is the identity of a GCP SecurityCenterManagementSecurityHealthAnalyticsCustomModule resource.
39+
// +k8s:deepcopy-gen=false
40+
type SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity struct {
41+
Project string
42+
Location string
43+
SecurityCenterManagementSecurityHealthAnalyticsCustomModule string
44+
}
45+
46+
func (i *SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity) String() string {
47+
return SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentityFormat.ToString(*i)
48+
}
49+
50+
func (i *SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity) FromExternal(ref string) error {
51+
parsed, match, err := SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentityFormat.Parse(ref)
52+
if err != nil {
53+
return fmt.Errorf("format of SecurityCenterManagementSecurityHealthAnalyticsCustomModule external=%q was not known (use %s): %w", ref, SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentityFormat.CanonicalForm(), err)
54+
}
55+
if !match {
56+
return fmt.Errorf("format of SecurityCenterManagementSecurityHealthAnalyticsCustomModule external=%q was not known (use %s)", ref, SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentityFormat.CanonicalForm())
57+
}
58+
59+
*i = *parsed
60+
return nil
61+
}
62+
63+
func (i *SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity) Host() string {
64+
return SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentityFormat.Host()
65+
}
66+
67+
func (i *SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity) ParentString() string {
68+
return fmt.Sprintf("projects/%s/locations/%s", i.Project, i.Location)
69+
}
70+
71+
func getIdentityFromSecurityCenterManagementSecurityHealthAnalyticsCustomModuleSpec(ctx context.Context, reader client.Reader, obj *SecurityCenterManagementSecurityHealthAnalyticsCustomModule) (*SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity, error) {
72+
resourceID, err := refs.GetResourceID(obj)
73+
if err != nil {
74+
return nil, fmt.Errorf("cannot resolve resource ID: %w", err)
75+
}
76+
77+
location := obj.Spec.Location
78+
if location == "" {
79+
return nil, fmt.Errorf("cannot resolve location")
80+
}
81+
82+
projectID, err := refs.ResolveProjectID(ctx, reader, obj)
83+
if err != nil {
84+
return nil, fmt.Errorf("cannot resolve project: %w", err)
85+
}
86+
87+
identity := &SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity{
88+
Project: projectID,
89+
Location: location,
90+
SecurityCenterManagementSecurityHealthAnalyticsCustomModule: resourceID,
91+
}
92+
return identity, nil
93+
}
94+
95+
func (obj *SecurityCenterManagementSecurityHealthAnalyticsCustomModule) GetIdentity(ctx context.Context, reader client.Reader) (identity.Identity, error) {
96+
specIdentity, err := getIdentityFromSecurityCenterManagementSecurityHealthAnalyticsCustomModuleSpec(ctx, reader, obj)
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
// Cross-check the identity against the status value, if present.
102+
externalRef := common.ValueOf(obj.Status.ExternalRef)
103+
if externalRef != "" {
104+
// Validate desired with actual
105+
statusIdentity := &SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity{}
106+
if err := statusIdentity.FromExternal(externalRef); err != nil {
107+
return nil, err
108+
}
109+
110+
if statusIdentity.String() != specIdentity.String() {
111+
return nil, fmt.Errorf("cannot change SecurityCenterManagementSecurityHealthAnalyticsCustomModule identity (old=%q, new=%q)", statusIdentity.String(), specIdentity.String())
112+
}
113+
}
114+
115+
return specIdentity, nil
116+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
"testing"
19+
20+
"github.com/google/go-cmp/cmp"
21+
)
22+
23+
func TestSecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity_FromExternal(t *testing.T) {
24+
tests := []struct {
25+
name string
26+
input string
27+
expected *SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity
28+
hasError bool
29+
}{
30+
{
31+
name: "Full resource name",
32+
input: "projects/my-project/locations/global/securityHealthAnalyticsCustomModules/my-custom-module",
33+
expected: &SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity{
34+
Project: "my-project",
35+
Location: "global",
36+
SecurityCenterManagementSecurityHealthAnalyticsCustomModule: "my-custom-module",
37+
},
38+
hasError: false,
39+
},
40+
{
41+
name: "Full resource name with host",
42+
input: "securitycentermanagement.googleapis.com/projects/my-project/locations/global/securityHealthAnalyticsCustomModules/my-custom-module",
43+
expected: &SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity{
44+
Project: "my-project",
45+
Location: "global",
46+
SecurityCenterManagementSecurityHealthAnalyticsCustomModule: "my-custom-module",
47+
},
48+
hasError: false,
49+
},
50+
{
51+
name: "Invalid format",
52+
input: "projects/my-project/locations/global/invalid/my-custom-module",
53+
expected: nil,
54+
hasError: true,
55+
},
56+
}
57+
58+
for _, tc := range tests {
59+
t.Run(tc.name, func(t *testing.T) {
60+
id := &SecurityCenterManagementSecurityHealthAnalyticsCustomModuleIdentity{}
61+
err := id.FromExternal(tc.input)
62+
if tc.hasError {
63+
if err == nil {
64+
t.Fatal("expected error, got nil")
65+
}
66+
return
67+
}
68+
if err != nil {
69+
t.Fatalf("unexpected error: %v", err)
70+
}
71+
if diff := cmp.Diff(tc.expected, id); diff != "" {
72+
t.Errorf("FromExternal() mismatch (-want +got):\n%s", diff)
73+
}
74+
})
75+
}
76+
}

0 commit comments

Comments
 (0)