Skip to content

Commit c67d291

Browse files
authored
ai:chore: Implement direct types for: CCInsightsPhraseMatcher (#9335)
Implementing initial KRM types, CRD, and IdentityV2 for `CCInsightsPhraseMatcher` resource under the `contactcenterinsights.cnrm.cloud.google.com/v1alpha1` group using the direct approach. - Scaffolded and refined types in `phrasematcher_types.go`. - Implemented `IdentityV2` and `ExternalIdentifier()` interfaces in `ccinsightsphrasematcher_identity.go`. - Registered `CCInsightsPhraseMatcher` in static configuration `static_config.go`. - Regenerated manifests, cluster roles, reports, and Go CRD clients successfully. - Verified that all local prerequisite validations (`scripts/validate-prereqs.sh`) pass cleanly. As instructed, this PR does not contain the controller or mapper steps. Related chore file: [.agents/greenfield-direct-new-resource-types.md](https://github.com/GoogleCloudPlatform/k8s-config-connector/blob/main/.agents/greenfield-direct-new-resource-types.md) Fixes #9262
2 parents 087f762 + 7209df6 commit c67d291

19 files changed

Lines changed: 1702 additions & 1 deletion

.gemini/journals/contactcenterinsights.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Contact Center Insights Service Journal
2+
13
### [2026-06-05] CCInsightsView Greenfield Types Scaffolding
24
- **Context**: Implementing Greenfield direct types, CRD, and IdentityV2 for `CCInsightsView` resource (mapped to `google.cloud.contactcenterinsights.v1.View`).
35
- **Problem**: Greenfield types need to strictly follow the KRM layout, specifically:
@@ -11,3 +13,9 @@
1113
4. Implemented `CCInsightsViewRef` and registered it for resource referencing.
1214
5. Wrote comprehensive unit tests to validate identity and reference logic.
1315
- **Impact**: Establishes a standard, fully validated direct type foundation for Contact Center Insights (CCInsightsView), passing all presubmits and client-go code generation cleanly.
16+
17+
### [2026-06-05] CCInsightsPhraseMatcher Greenfield Types and Identity
18+
- **Context**: Greenfield types, CRD, and IdentityV2 implementation for `CCInsightsPhraseMatcher` (GCP `PhraseMatcher` under `contactcenterinsights.googleapis.com`).
19+
- **Problem**: The proto-to-KRM generator comments out nested structs as "unreachable types" initially because `phrasematcher_types.go` is stubbed out. Also, we had to ensure scalar types are represented as pointers to satisfy KCC's strict pointer rule.
20+
- **Solution**: We populated the nested structs (such as `PhraseMatchRuleGroup` and `PhraseMatchRule`) explicitly in `phrasematcher_types.go` and defined `Location` in `CCInsightsPhraseMatcherSpec` directly as `*string`. Running `generate.sh` then resolved all unreachable types cleanly.
21+
- **Impact**: All future developers working on `contactcenterinsights` can leverage the scaffolded types, CRDs, and identity structure, knowing that the compiler and validators are fully satisfied with the pointer fields.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 = &CCInsightsPhraseMatcherIdentity{}
30+
_ identity.Resource = &CCInsightsPhraseMatcher{}
31+
)
32+
33+
var CCInsightsPhraseMatcherIdentityFormat = gcpurls.Template[CCInsightsPhraseMatcherIdentity]("contactcenterinsights.googleapis.com", "projects/{project}/locations/{location}/phraseMatchers/{phrase_matcher}")
34+
35+
// +k8s:deepcopy-gen=false
36+
type CCInsightsPhraseMatcherIdentity struct {
37+
Project string
38+
Location string
39+
Phrase_matcher string
40+
}
41+
42+
func (i *CCInsightsPhraseMatcherIdentity) String() string {
43+
return CCInsightsPhraseMatcherIdentityFormat.ToString(*i)
44+
}
45+
46+
func (i *CCInsightsPhraseMatcherIdentity) FromExternal(ref string) error {
47+
parsed, match, err := CCInsightsPhraseMatcherIdentityFormat.Parse(ref)
48+
if err != nil {
49+
return fmt.Errorf("format of CCInsightsPhraseMatcher external=%q was not known (use %s): %w", ref, CCInsightsPhraseMatcherIdentityFormat.CanonicalForm(), err)
50+
}
51+
if !match {
52+
return fmt.Errorf("format of CCInsightsPhraseMatcher external=%q was not known (use %s)", ref, CCInsightsPhraseMatcherIdentityFormat.CanonicalForm())
53+
}
54+
55+
*i = *parsed
56+
return nil
57+
}
58+
59+
func (i *CCInsightsPhraseMatcherIdentity) Host() string {
60+
return CCInsightsPhraseMatcherIdentityFormat.Host()
61+
}
62+
63+
func getIdentityFromCCInsightsPhraseMatcherSpec(ctx context.Context, reader client.Reader, obj client.Object) (*CCInsightsPhraseMatcherIdentity, error) {
64+
resourceID, err := refs.GetResourceID(obj)
65+
if err != nil {
66+
return nil, fmt.Errorf("cannot resolve resource ID")
67+
}
68+
69+
location, err := refs.GetLocation(obj)
70+
if err != nil {
71+
return nil, fmt.Errorf("cannot resolve location")
72+
}
73+
74+
projectID, err := refs.ResolveProjectID(ctx, reader, obj)
75+
if err != nil {
76+
return nil, fmt.Errorf("cannot resolve project")
77+
}
78+
79+
identity := &CCInsightsPhraseMatcherIdentity{
80+
Project: projectID,
81+
Location: location,
82+
Phrase_matcher: resourceID,
83+
}
84+
return identity, nil
85+
}
86+
87+
func (obj *CCInsightsPhraseMatcher) GetIdentity(ctx context.Context, reader client.Reader) (identity.Identity, error) {
88+
specIdentity, err := getIdentityFromCCInsightsPhraseMatcherSpec(ctx, reader, obj)
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
externalRef := common.ValueOf(obj.Status.ExternalRef)
94+
if externalRef != "" {
95+
statusIdentity := &CCInsightsPhraseMatcherIdentity{}
96+
if err := statusIdentity.FromExternal(externalRef); err != nil {
97+
return nil, err
98+
}
99+
100+
if statusIdentity.String() != specIdentity.String() {
101+
return nil, fmt.Errorf("cannot change CCInsightsPhraseMatcher identity (old=%q, new=%q)", statusIdentity.String(), specIdentity.String())
102+
}
103+
}
104+
105+
return specIdentity, nil
106+
}
107+
108+
func (obj *CCInsightsPhraseMatcher) ExternalIdentifier() *string {
109+
if obj.Status.ExternalRef != nil {
110+
return obj.Status.ExternalRef
111+
}
112+
return nil
113+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
21+
func TestCCInsightsPhraseMatcherIdentity_FromExternal(t *testing.T) {
22+
tests := []struct {
23+
name string
24+
input string
25+
expected *CCInsightsPhraseMatcherIdentity
26+
hasError bool
27+
}{
28+
{
29+
name: "Full resource name",
30+
input: "projects/my-project/locations/us-central1/phraseMatchers/my-phrasematcher",
31+
expected: &CCInsightsPhraseMatcherIdentity{
32+
Project: "my-project",
33+
Location: "us-central1",
34+
Phrase_matcher: "my-phrasematcher",
35+
},
36+
hasError: false,
37+
},
38+
{
39+
name: "Full resource name with host",
40+
input: "contactcenterinsights.googleapis.com/projects/my-project/locations/us-central1/phraseMatchers/my-phrasematcher",
41+
expected: &CCInsightsPhraseMatcherIdentity{
42+
Project: "my-project",
43+
Location: "us-central1",
44+
Phrase_matcher: "my-phrasematcher",
45+
},
46+
hasError: false,
47+
},
48+
{
49+
name: "Invalid format",
50+
input: "projects/my-project/locations/us-central1/invalid/my-phrasematcher",
51+
expected: nil,
52+
hasError: true,
53+
},
54+
}
55+
56+
for _, tc := range tests {
57+
t.Run(tc.name, func(t *testing.T) {
58+
id := &CCInsightsPhraseMatcherIdentity{}
59+
err := id.FromExternal(tc.input)
60+
if tc.hasError {
61+
if err == nil {
62+
t.Fatal("expected error, got nil")
63+
}
64+
return
65+
}
66+
if err != nil {
67+
t.Fatalf("unexpected error: %v", err)
68+
}
69+
if id.Project != tc.expected.Project || id.Location != tc.expected.Location || id.Phrase_matcher != tc.expected.Phrase_matcher {
70+
t.Fatalf("expected %+v, got %+v", tc.expected, id)
71+
}
72+
})
73+
}
74+
}

apis/contactcenterinsights/v1alpha1/generate.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ go run . generate-types \
2626
--service google.cloud.contactcenterinsights.v1 \
2727
--api-version contactcenterinsights.cnrm.cloud.google.com/v1alpha1 \
2828
--resource CCInsightsView:View \
29-
--prune-unused-types=false
29+
--resource CCInsightsPhraseMatcher:PhraseMatcher \
30+
--prune-unused-types=false \
31+
--include-skipped-output
3032

3133
cd ${REPO_ROOT}
3234
dev/tasks/generate-crds

0 commit comments

Comments
 (0)