Skip to content

Commit cd7beac

Browse files
Merge pull request #4290 from gemmahou/resource-dataplex-entrygroup
Add dataplex entrygroup types, mappers, CRD and fuzzer
2 parents af14ac3 + da886cb commit cd7beac

9 files changed

+1021
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2025 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+
"strings"
21+
22+
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
23+
refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
24+
"sigs.k8s.io/controller-runtime/pkg/client"
25+
)
26+
27+
// EntryGroupIdentity defines the resource reference to DataplexEntryGroup, which "External" field
28+
// holds the GCP identifier for the KRM object.
29+
type EntryGroupIdentity struct {
30+
parent *EntryGroupParent
31+
id string
32+
}
33+
34+
func (i *EntryGroupIdentity) String() string {
35+
return i.parent.String() + "/entryGroups/" + i.id
36+
}
37+
38+
func (i *EntryGroupIdentity) ID() string {
39+
return i.id
40+
}
41+
42+
func (i *EntryGroupIdentity) Parent() *EntryGroupParent {
43+
return i.parent
44+
}
45+
46+
// No changes needed as EntryGroupParent fields and resource pattern are correct.
47+
type EntryGroupParent struct {
48+
ProjectID string
49+
Location string
50+
}
51+
52+
func (p *EntryGroupParent) String() string {
53+
return "projects/" + p.ProjectID + "/locations/" + p.Location
54+
}
55+
56+
// New builds a EntryGroupIdentity from the Config Connector EntryGroup object.
57+
func NewEntryGroupIdentity(ctx context.Context, reader client.Reader, obj *DataplexEntryGroup) (*EntryGroupIdentity, error) {
58+
59+
// Get Parent
60+
projectRef, err := refsv1beta1.ResolveProject(ctx, reader, obj.GetNamespace(), obj.Spec.ProjectRef)
61+
if err != nil {
62+
return nil, err
63+
}
64+
projectID := projectRef.ProjectID
65+
if projectID == "" {
66+
return nil, fmt.Errorf("cannot resolve project")
67+
}
68+
location := obj.Spec.Location
69+
70+
// Get desired ID
71+
resourceID := common.ValueOf(obj.Spec.ResourceID)
72+
if resourceID == "" {
73+
resourceID = obj.GetName()
74+
}
75+
if resourceID == "" {
76+
return nil, fmt.Errorf("cannot resolve resource ID")
77+
}
78+
79+
// Use approved External
80+
externalRef := common.ValueOf(obj.Status.ExternalRef)
81+
if externalRef != "" {
82+
// Validate desired with actual
83+
actualParent, actualResourceID, err := ParseEntryGroupExternal(externalRef)
84+
if err != nil {
85+
return nil, err
86+
}
87+
if actualParent.ProjectID != projectID {
88+
return nil, fmt.Errorf("spec.projectRef changed, expect %s, got %s", actualParent.ProjectID, projectID)
89+
}
90+
if actualParent.Location != location {
91+
return nil, fmt.Errorf("spec.location changed, expect %s, got %s", actualParent.Location, location)
92+
}
93+
if actualResourceID != resourceID {
94+
return nil, fmt.Errorf("cannot reset `metadata.name` or `spec.resourceID` to %s, since it has already assigned to %s",
95+
resourceID, actualResourceID)
96+
}
97+
}
98+
return &EntryGroupIdentity{
99+
parent: &EntryGroupParent{
100+
ProjectID: projectID,
101+
Location: location,
102+
},
103+
id: resourceID,
104+
}, nil
105+
}
106+
107+
func ParseEntryGroupExternal(external string) (parent *EntryGroupParent, resourceID string, err error) {
108+
tokens := strings.Split(external, "/")
109+
if len(tokens) != 6 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "entryGroups" {
110+
return nil, "", fmt.Errorf("format of DataplexEntryGroup external=%q was not known (use projects/{{projectID}}/locations/{{location}}/entryGroups/{{entrygroupID}})", external)
111+
}
112+
parent = &EntryGroupParent{
113+
ProjectID: tokens[1],
114+
Location: tokens[3],
115+
}
116+
resourceID = tokens[5]
117+
return parent, resourceID, nil
118+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2025 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+
refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
22+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
23+
apierrors "k8s.io/apimachinery/pkg/api/errors"
24+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
25+
"k8s.io/apimachinery/pkg/types"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
)
28+
29+
var _ refsv1beta1.ExternalNormalizer = &EntryGroupRef{}
30+
31+
// EntryGroupRef defines the resource reference to DataplexEntryGroup, which "External" field
32+
// holds the GCP identifier for the KRM object.
33+
type EntryGroupRef struct {
34+
// A reference to an externally managed DataplexEntryGroup resource.
35+
// Should be in the format "projects/{{projectID}}/locations/{{location}}/entrygroups/{{entrygroupID}}".
36+
External string `json:"external,omitempty"`
37+
38+
// The name of a DataplexEntryGroup resource.
39+
Name string `json:"name,omitempty"`
40+
41+
// The namespace of a DataplexEntryGroup resource.
42+
Namespace string `json:"namespace,omitempty"`
43+
}
44+
45+
// NormalizedExternal provision the "External" value for other resource that depends on DataplexEntryGroup.
46+
// If the "External" is given in the other resource's spec.DataplexEntryGroupRef, the given value will be used.
47+
// Otherwise, the "Name" and "Namespace" will be used to query the actual DataplexEntryGroup object from the cluster.
48+
func (r *EntryGroupRef) NormalizedExternal(ctx context.Context, reader client.Reader, otherNamespace string) (string, error) {
49+
if r.External != "" && r.Name != "" {
50+
return "", fmt.Errorf("cannot specify both name and external on %s reference", DataplexEntryGroupGVK.Kind)
51+
}
52+
// From given External
53+
if r.External != "" {
54+
if _, _, err := ParseEntryGroupExternal(r.External); err != nil {
55+
return "", err
56+
}
57+
return r.External, nil
58+
}
59+
60+
// From the Config Connector object
61+
if r.Namespace == "" {
62+
r.Namespace = otherNamespace
63+
}
64+
key := types.NamespacedName{Name: r.Name, Namespace: r.Namespace}
65+
u := &unstructured.Unstructured{}
66+
u.SetGroupVersionKind(DataplexEntryGroupGVK)
67+
if err := reader.Get(ctx, key, u); err != nil {
68+
if apierrors.IsNotFound(err) {
69+
return "", k8s.NewReferenceNotFoundError(u.GroupVersionKind(), key)
70+
}
71+
return "", fmt.Errorf("reading referenced %s %s: %w", DataplexEntryGroupGVK, key, err)
72+
}
73+
// Get external from status.externalRef. This is the most trustworthy place.
74+
actualExternalRef, _, err := unstructured.NestedString(u.Object, "status", "externalRef")
75+
if err != nil {
76+
return "", fmt.Errorf("reading status.externalRef: %w", err)
77+
}
78+
if actualExternalRef == "" {
79+
return "", k8s.NewReferenceNotReadyError(u.GroupVersionKind(), key)
80+
}
81+
r.External = actualExternalRef
82+
return r.External, nil
83+
}
+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2025 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+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
19+
// GCP Resource Reference type.
20+
refv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
)
23+
24+
var DataplexEntryGroupGVK = GroupVersion.WithKind("DataplexEntryGroup")
25+
26+
// The Parent resource that the DataplexEntryGroup resource resides in.
27+
type DataplexEntryGroupParent struct {
28+
// +required
29+
ProjectRef *refv1beta1.ProjectRef `json:"projectRef"`
30+
// +required
31+
Location string `json:"location"`
32+
}
33+
34+
// DataplexEntryGroupSpec defines the desired state of DataplexEntryGroup
35+
// +kcc:proto=google.cloud.dataplex.v1.EntryGroup
36+
type DataplexEntryGroupSpec struct {
37+
DataplexEntryGroupParent `json:",inline"`
38+
// The DataplexEntryGroup name. If not given, the metadata.name will be used.
39+
ResourceID *string `json:"resourceID,omitempty"`
40+
41+
// Optional. Description of the EntryGroup.
42+
// +kcc:proto:field=google.cloud.dataplex.v1.EntryGroup.description
43+
Description *string `json:"description,omitempty"`
44+
45+
// Optional. User friendly display name.
46+
// +kcc:proto:field=google.cloud.dataplex.v1.EntryGroup.display_name
47+
DisplayName *string `json:"displayName,omitempty"`
48+
49+
// Optional. User-defined labels for the EntryGroup.
50+
// +kcc:proto:field=google.cloud.dataplex.v1.EntryGroup.labels
51+
Labels map[string]string `json:"labels,omitempty"`
52+
53+
// This checksum is computed by the service, and might be sent on update and
54+
// delete requests to ensure the client has an up-to-date value before
55+
// proceeding.
56+
// +kcc:proto:field=google.cloud.dataplex.v1.EntryGroup.etag
57+
Etag *string `json:"etag,omitempty"`
58+
}
59+
60+
// DataplexEntryGroupStatus defines the config connector machine state of DataplexEntryGroup
61+
type DataplexEntryGroupStatus struct {
62+
/* Conditions represent the latest available observations of the
63+
object's current state. */
64+
Conditions []v1alpha1.Condition `json:"conditions,omitempty"`
65+
66+
// ObservedGeneration is the generation of the resource that was most recently observed by the Config Connector controller. If this is equal to metadata.generation, then that means that the current reported status reflects the most recent desired state of the resource.
67+
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
68+
69+
// A unique specifier for the DataplexEntryGroup resource in GCP.
70+
ExternalRef *string `json:"externalRef,omitempty"`
71+
72+
// ObservedState is the state of the resource as most recently observed in GCP.
73+
ObservedState *DataplexEntryGroupObservedState `json:"observedState,omitempty"`
74+
}
75+
76+
// DataplexEntryGroupObservedState is the state of the DataplexEntryGroup resource as most recently observed in GCP.
77+
// +kcc:proto=google.cloud.dataplex.v1.EntryGroup
78+
type DataplexEntryGroupObservedState struct {
79+
// Output only. System generated globally unique ID for the EntryGroup. If you
80+
// delete and recreate the EntryGroup with the same name, this ID will be
81+
// different.
82+
// +kcc:proto:field=google.cloud.dataplex.v1.EntryGroup.uid
83+
Uid *string `json:"uid,omitempty"`
84+
85+
// Output only. The time when the EntryGroup was created.
86+
// +kcc:proto:field=google.cloud.dataplex.v1.EntryGroup.create_time
87+
CreateTime *string `json:"createTime,omitempty"`
88+
89+
// Output only. The time when the EntryGroup was last updated.
90+
// +kcc:proto:field=google.cloud.dataplex.v1.EntryGroup.update_time
91+
UpdateTime *string `json:"updateTime,omitempty"`
92+
93+
// Output only. Denotes the transfer status of the Entry Group. It is
94+
// unspecified for Entry Group created from Dataplex API.
95+
// +kcc:proto:field=google.cloud.dataplex.v1.EntryGroup.transfer_status
96+
TransferStatus *string `json:"transferStatus,omitempty"`
97+
}
98+
99+
// +genclient
100+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
101+
// TODO(user): make sure the pluralizaiton below is correct
102+
// +kubebuilder:resource:categories=gcp,shortName=gcpdataplexentrygroup;gcpdataplexentrygroups
103+
// +kubebuilder:subresource:status
104+
// +kubebuilder:metadata:labels="cnrm.cloud.google.com/managed-by-kcc=true";"cnrm.cloud.google.com/system=true"
105+
// +kubebuilder:printcolumn:name="Age",JSONPath=".metadata.creationTimestamp",type="date"
106+
// +kubebuilder:printcolumn:name="Ready",JSONPath=".status.conditions[?(@.type=='Ready')].status",type="string",description="When 'True', the most recent reconcile of the resource succeeded"
107+
// +kubebuilder:printcolumn:name="Status",JSONPath=".status.conditions[?(@.type=='Ready')].reason",type="string",description="The reason for the value in 'Ready'"
108+
// +kubebuilder:printcolumn:name="Status Age",JSONPath=".status.conditions[?(@.type=='Ready')].lastTransitionTime",type="date",description="The last transition time for the value in 'Status'"
109+
110+
// DataplexEntryGroup is the Schema for the DataplexEntryGroup API
111+
// +k8s:openapi-gen=true
112+
type DataplexEntryGroup struct {
113+
metav1.TypeMeta `json:",inline"`
114+
metav1.ObjectMeta `json:"metadata,omitempty"`
115+
116+
// +required
117+
Spec DataplexEntryGroupSpec `json:"spec,omitempty"`
118+
Status DataplexEntryGroupStatus `json:"status,omitempty"`
119+
}
120+
121+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
122+
// DataplexEntryGroupList contains a list of DataplexEntryGroup
123+
type DataplexEntryGroupList struct {
124+
metav1.TypeMeta `json:",inline"`
125+
metav1.ListMeta `json:"metadata,omitempty"`
126+
Items []DataplexEntryGroup `json:"items"`
127+
}
128+
129+
func init() {
130+
SchemeBuilder.Register(&DataplexEntryGroup{}, &DataplexEntryGroupList{})
131+
}

apis/dataplex/v1alpha1/types.generated.go

+50
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)