Skip to content

Commit 5af7d70

Browse files
committed
feat(bigtable): implement BigtableSchemaBundle direct controller and mock
This PR adds the BigtableSchemaBundle CRD and its direct controller implementation. BigtableSchemaBundle is a resource under Table, following the pattern: projects/{project}/instances/{instance}/tables/{table}/schemaBundles/{schema_bundle} The implementation includes: - BigtableSchemaBundle CRD in v1alpha1. - Direct controller implementation for CRUD operations. - Mock GCP implementation for Bigtable SchemaBundle methods. - Comprehensive mapper unit tests and E2E test fixtures. - Fixes for package shadowing and alias issues in v1alpha1. Fixes #6641
1 parent fc65fd1 commit 5af7d70

File tree

19 files changed

+1338
-1
lines changed

19 files changed

+1338
-1
lines changed

apis/bigtable/v1alpha1/generate.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ go run . generate-types \
3030
--resource BigtableBackup:Backup \
3131
--resource BigtableCluster:Cluster \
3232
--resource BigtableLogicalView:LogicalView \
33-
--resource BigtableMaterializedView:MaterializedView
33+
--resource BigtableMaterializedView:MaterializedView \
34+
--resource BigtableSchemaBundle:SchemaBundle
3435

3536
go run . generate-mapper \
37+
--multiversion \
3638
--service google.bigtable.admin.v2 \
3739
--api-version bigtable.cnrm.cloud.google.com/v1alpha1
3840

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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+
"strings"
21+
22+
bigtablev1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/bigtable/v1beta1"
23+
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
24+
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common/parent"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
)
27+
28+
// SchemaBundleIdentity defines the resource reference to BigtableSchemaBundle, which "External" field
29+
// holds the GCP identifier for the KRM object.
30+
type SchemaBundleIdentity struct {
31+
parent *bigtablev1beta1.TableIdentity
32+
id string
33+
}
34+
35+
func (i *SchemaBundleIdentity) String() string {
36+
return i.parent.String() + "/schemaBundles/" + i.id
37+
}
38+
39+
func (i *SchemaBundleIdentity) ID() string {
40+
return i.id
41+
}
42+
43+
func (i *SchemaBundleIdentity) Parent() *bigtablev1beta1.TableIdentity {
44+
return i.parent
45+
}
46+
47+
// New builds a SchemaBundleIdentity from the Config Connector SchemaBundle object.
48+
func NewSchemaBundleIdentity(ctx context.Context, reader client.Reader, obj *BigtableSchemaBundle) (*SchemaBundleIdentity, error) {
49+
50+
// Get Parent
51+
tableRef, err := obj.Spec.TableRef.NormalizedExternal(ctx, reader, obj.GetNamespace())
52+
if err != nil {
53+
return nil, err
54+
}
55+
tableParent, tableID, err := bigtablev1beta1.ParseTableExternal(tableRef)
56+
if err != nil {
57+
return nil, err
58+
}
59+
60+
// Get desired ID
61+
resourceID := common.ValueOf(obj.Spec.ResourceID)
62+
if resourceID == "" {
63+
resourceID = obj.GetName()
64+
}
65+
if resourceID == "" {
66+
return nil, fmt.Errorf("cannot resolve schema bundle name")
67+
}
68+
69+
// Use approved External
70+
externalRef := common.ValueOf(obj.Status.ExternalRef)
71+
if externalRef != "" {
72+
// Validate desired with actual
73+
actualParent, actualResourceID, err := ParseSchemaBundleExternal(externalRef)
74+
if err != nil {
75+
return nil, err
76+
}
77+
if actualParent.Parent.Parent.ProjectID != tableParent.Parent.ProjectID {
78+
return nil, fmt.Errorf("ProjectID changed, expect %s, got %s", actualParent.Parent.Parent.ProjectID, tableParent.Parent.ProjectID)
79+
}
80+
if actualParent.Parent.Id != tableParent.Id {
81+
return nil, fmt.Errorf("InstanceID changed, expect %s, got %s", actualParent.Parent.Id, tableParent.Id)
82+
}
83+
if actualParent.Id != tableID {
84+
return nil, fmt.Errorf("TableID changed, expect %s, got %s", actualParent.Id, tableID)
85+
}
86+
if actualResourceID != resourceID {
87+
return nil, fmt.Errorf("cannot reset `metadata.name` or `spec.resourceID` to %s, since it has already assigned to %s",
88+
resourceID, actualResourceID)
89+
}
90+
}
91+
return &SchemaBundleIdentity{
92+
parent: &bigtablev1beta1.TableIdentity{
93+
Parent: tableParent,
94+
Id: tableID,
95+
},
96+
id: resourceID,
97+
}, nil
98+
}
99+
100+
func ParseSchemaBundleExternal(external string) (*bigtablev1beta1.TableIdentity, string, error) {
101+
tokens := strings.Split(external, "/")
102+
if len(tokens) != 8 || tokens[0] != "projects" || tokens[2] != "instances" || tokens[4] != "tables" || tokens[6] != "schemaBundles" {
103+
return nil, "", fmt.Errorf("format of BigtableSchemaBundle external=%q was not known (use projects/{{projectID}}/instances/{{instanceID}}/tables/{{tableID}}/schemaBundles/{{schemaBundleID}})", external)
104+
}
105+
return &bigtablev1beta1.TableIdentity{
106+
Parent: &bigtablev1beta1.InstanceIdentity{
107+
Parent: &parent.ProjectParent{ProjectID: tokens[1]},
108+
Id: tokens[3],
109+
},
110+
Id: tokens[5],
111+
}, tokens[7], nil
112+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
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 = &SchemaBundleRef{}
30+
31+
// SchemaBundleRef defines the resource reference to BigtableSchemaBundle, which "External" field
32+
// holds the GCP identifier for the KRM object.
33+
type SchemaBundleRef struct {
34+
// A reference to an externally managed BigtableSchemaBundle resource.
35+
// Should be in the format "projects/{{projectID}}/instances/{{instanceID}}/tables/{{tableID}}/schemaBundles/{{schemaBundleID}}".
36+
External string `json:"external,omitempty"`
37+
38+
// The name of a BigtableSchemaBundle resource.
39+
Name string `json:"name,omitempty"`
40+
41+
// The namespace of a BigtableSchemaBundle resource.
42+
Namespace string `json:"namespace,omitempty"`
43+
}
44+
45+
// NormalizedExternal provision the "External" value for other resource that depends on BigtableSchemaBundle.
46+
// If the "External" is given in the other resource's spec.BigtableSchemaBundleRef, the given value will be used.
47+
// Otherwise, the "Name" and "Namespace" will be used to query the actual BigtableSchemaBundle object from the cluster.
48+
func (r *SchemaBundleRef) 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", BigtableSchemaBundleGVK.Kind)
51+
}
52+
// From given External
53+
if r.External != "" {
54+
if _, _, err := ParseSchemaBundleExternal(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(BigtableSchemaBundleGVK)
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", BigtableSchemaBundleGVK, 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+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
bigtablev1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/bigtable/v1beta1"
19+
k8sv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
var BigtableSchemaBundleGVK = GroupVersion.WithKind("BigtableSchemaBundle")
24+
25+
// BigtableSchemaBundleSpec defines the desired state of BigtableSchemaBundle
26+
// +kcc:spec:proto=google.bigtable.admin.v2.SchemaBundle
27+
type BigtableSchemaBundleSpec struct {
28+
// The table to create the schema bundle in.
29+
// +required
30+
TableRef bigtablev1beta1.TableRef `json:"tableRef"`
31+
32+
// The BigtableSchemaBundle name. If not given, the metadata.name will be used.
33+
ResourceID *string `json:"resourceID,omitempty"`
34+
35+
// Optional. The protobuf schema for the table.
36+
// +optional
37+
ProtoSchema *ProtoSchema `json:"protoSchema,omitempty"`
38+
}
39+
40+
// ProtoSchema contains a protobuf-serialized FileDescriptorSet.
41+
type ProtoSchema struct {
42+
// Optional. The protobuf schema descriptor.
43+
// The schema descriptor must be a file descriptor set, which can be generated using `protoc --descriptor_set_out=myschema.fd myschema.proto`.
44+
// The file descriptor set must be base64 encoded.
45+
// +optional
46+
ProtoDescriptors []byte `json:"protoDescriptors,omitempty"`
47+
}
48+
49+
// BigtableSchemaBundleStatus defines the config connector machine state of BigtableSchemaBundle
50+
type BigtableSchemaBundleStatus struct {
51+
/* Conditions represent the latest available observations of the
52+
object's current state. */
53+
Conditions []k8sv1alpha1.Condition `json:"conditions,omitempty"`
54+
55+
// 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.
56+
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
57+
58+
// A unique specifier for the BigtableSchemaBundle resource in GCP.
59+
ExternalRef *string `json:"externalRef,omitempty"`
60+
61+
// ObservedState is the state of the resource as most recently observed in GCP.
62+
ObservedState *BigtableSchemaBundleObservedState `json:"observedState,omitempty"`
63+
}
64+
65+
// BigtableSchemaBundleObservedState is the state of the BigtableSchemaBundle resource as most recently observed in GCP.
66+
// +kcc:observedstate:proto=google.bigtable.admin.v2.SchemaBundle
67+
type BigtableSchemaBundleObservedState struct {
68+
// Optional. The etag for this schema bundle.
69+
Etag *string `json:"etag,omitempty"`
70+
}
71+
72+
// +genclient
73+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
74+
// +kubebuilder:resource:categories=gcp,shortName=gcpbigtableschemabundle;gcpbigtableschemabundles
75+
// +kubebuilder:subresource:status
76+
// +kubebuilder:metadata:labels="cnrm.cloud.google.com/managed-by-kcc=true"
77+
// +kubebuilder:metadata:labels="cnrm.cloud.google.com/system=true"
78+
// +kubebuilder:printcolumn:name="Age",JSONPath=".metadata.creationTimestamp",type="date"
79+
// +kubebuilder:printcolumn:name="Ready",JSONPath=".status.conditions[?(@.type=='Ready')].status",type="string",description="When 'True', the most recent reconcile of the resource succeeded"
80+
// +kubebuilder:printcolumn:name="Status",JSONPath=".status.conditions[?(@.type=='Ready')].reason",type="string",description="The reason for the value in 'Ready'"
81+
// +kubebuilder:printcolumn:name="Status Age",JSONPath=".status.conditions[?(@.type=='Ready')].lastTransitionTime",type="date",description="The last transition time for the value in 'Status'"
82+
83+
// BigtableSchemaBundle is the Schema for the BigtableSchemaBundle API
84+
// +k8s:openapi-gen=true
85+
type BigtableSchemaBundle struct {
86+
metav1.TypeMeta `json:",inline"`
87+
metav1.ObjectMeta `json:"metadata,omitempty"`
88+
89+
// +required
90+
Spec BigtableSchemaBundleSpec `json:"spec,omitempty"`
91+
Status BigtableSchemaBundleStatus `json:"status,omitempty"`
92+
}
93+
94+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
95+
// BigtableSchemaBundleList contains a list of BigtableSchemaBundle
96+
type BigtableSchemaBundleList struct {
97+
metav1.TypeMeta `json:",inline"`
98+
metav1.ListMeta `json:"metadata,omitempty"`
99+
Items []BigtableSchemaBundle `json:"items"`
100+
}
101+
102+
func init() {
103+
SchemeBuilder.Register(&BigtableSchemaBundle{}, &BigtableSchemaBundleList{})
104+
}

apis/bigtable/v1alpha1/types.generated.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)