Skip to content

Commit 75e05f0

Browse files
authored
Merge pull request #3153 from jingyih/bqcc
feat: allow user-specified connection ID in BigQueryConnection Connection
2 parents 4ed76fc + 63eb7f6 commit 75e05f0

File tree

45 files changed

+380
-54
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+380
-54
lines changed

apis/bigqueryconnection/v1alpha1/connection_types.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ type Parent struct {
3838
type BigQueryConnectionConnectionSpec struct {
3939
Parent `json:",inline"`
4040

41-
// The BigQuery ConnectionID. This is a server-generated ID in the UUID format.
42-
// If not provided, ConfigConnector will create a new Connection and store the UUID in `status.serviceGeneratedID` field.
41+
// Immutable. Optional.
42+
// The BigQuery Connection ID used for resource creation or acquisition.
43+
// For creation: If specified, this value is used as the connection ID. If not provided, a UUID will be generated and assigned as the connection ID.
44+
// For acquisition: This field must be provided to identify the connection resource to acquire.
4345
ResourceID *string `json:"resourceID,omitempty"`
4446

4547
// User provided display name for the connection.

apis/bigqueryconnection/v1beta1/connection_reference.go

+37-37
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
2323
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
2424
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
25-
"github.com/google/uuid"
2625
apierrors "k8s.io/apimachinery/pkg/api/errors"
2726
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2827
"k8s.io/apimachinery/pkg/types"
@@ -44,46 +43,33 @@ func NewBigQueryConnectionConnectionRef(ctx context.Context, reader client.Reade
4443
// Get location
4544
location := obj.Spec.Location
4645

47-
// Get desired service-generated ID from spec
48-
desiredServiceID := direct.ValueOf(obj.Spec.ResourceID)
49-
if desiredServiceID != "" {
50-
if _, err := uuid.Parse(desiredServiceID); err != nil {
51-
return nil, fmt.Errorf("spec.resourceID should be in a UUID format, got %s ", desiredServiceID)
52-
}
53-
}
46+
// Get desired connection ID from spec
47+
desiredID := direct.ValueOf(obj.Spec.ResourceID)
5448

55-
// Get externalReference
49+
// Validate status.externalRef
5650
externalRef := direct.ValueOf(obj.Status.ExternalRef)
5751
if externalRef != "" {
58-
tokens := strings.Split(externalRef, "/")
59-
60-
if len(tokens) != 6 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "connections" {
61-
return nil, fmt.Errorf("externalRef should be projects/<project>/locations/<location>/connections/<Connection>, got %s", externalRef)
52+
actualProject, actualLocation, actualID, err := parseExternal(externalRef)
53+
if err != nil {
54+
return nil, err
6255
}
63-
id.parent = "projects/" + tokens[1] + "/locations/" + tokens[3]
64-
6556
// Validate spec parent and resourceID field if the resource is already reconcilied with a GCP Connection resource.
66-
if tokens[1] != projectID {
67-
return nil, fmt.Errorf("spec.projectRef changed, expect %s, got %s",
68-
tokens[1], projectID)
57+
if projectID != actualProject {
58+
return nil, fmt.Errorf("spec.projectRef changed, expect %s, got %s", projectID, actualProject)
6959
}
70-
if tokens[3] != location {
71-
return nil, fmt.Errorf("spec.location changed, expect %s, got %s",
72-
tokens[3], location)
60+
if location != actualLocation {
61+
return nil, fmt.Errorf("spec.location changed, expect %s, got %s", location, actualLocation)
7362
}
74-
if desiredServiceID != "" && tokens[5] != desiredServiceID {
75-
// Service generated ID shall not be reset in the same BigQueryConnectionConnection.
63+
if desiredID != "" && desiredID != actualID {
64+
// Connection ID shall not be reset in the same BigQueryConnectionConnection.
7665
// TODO: what if multiple BigQueryConnectionConnection points to the same GCP Connection?
7766
return nil, fmt.Errorf("cannot reset `spec.resourceID` to %s, since it has already acquired the Connection %s",
78-
desiredServiceID, tokens[5])
67+
desiredID, actualID)
7968
}
8069
id.External = externalRef
8170
return id, nil
8271
}
83-
id.parent = "projects/" + projectID + "/locations/" + location
84-
if desiredServiceID != "" {
85-
id.External = id.parent + "/connections/" + desiredServiceID
86-
}
72+
id.External = "projects/" + projectID + "/locations/" + location + "/connections/" + desiredID
8773
return id, nil
8874
}
8975

@@ -100,26 +86,40 @@ type BigQueryConnectionConnectionRef struct {
10086
Name string `json:"name,omitempty"`
10187
// The `namespace` of a `BigQueryConnectionConnection` resource.
10288
Namespace string `json:"namespace,omitempty"`
89+
}
10390

104-
parent string
91+
func parseExternal(external string) (string, string, string, error) {
92+
tokens := strings.Split(external, "/")
93+
if len(tokens) != 6 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "connections" {
94+
return "", "", "", fmt.Errorf("external should be projects/<project>/locations/<location>/connections/<Connection>, got %s", external)
95+
}
96+
return tokens[1], tokens[3], tokens[5], nil
10597
}
10698

10799
func (r *BigQueryConnectionConnectionRef) Parent() (string, error) {
108-
if r.parent != "" {
109-
return r.parent, nil
110-
}
111100
if r.External != "" {
112101
r.External = strings.TrimPrefix(r.External, "/")
113-
tokens := strings.Split(r.External, "/")
114-
if len(tokens) != 6 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "connections" {
115-
return "", fmt.Errorf("format of BigQueryConnectionConnection external=%q was not known (use projects/<projectId>/locations/<location>/connections/<connectionID>)", r.External)
102+
project, location, _, err := parseExternal(r.External)
103+
if err != nil {
104+
return "", err
116105
}
117-
r.parent = "projects/" + tokens[1] + "/locations/" + tokens[3]
118-
return r.parent, nil
106+
return "projects/" + project + "/locations/" + location, nil
119107
}
120108
return "", fmt.Errorf("BigQueryConnectionConnectionRef not normalized to External form or not created from `New()`")
121109
}
122110

111+
// ConnectionID returns the connection ID, a boolean indicating whether the connection ID is specified by user (or generated by service), and an error.
112+
func (r *BigQueryConnectionConnectionRef) ConnectionID() (string, bool, error) {
113+
if r.External != "" {
114+
_, _, id, err := parseExternal(r.External)
115+
if err != nil {
116+
return "", false, err
117+
}
118+
return id, id != "", nil
119+
}
120+
return "", false, fmt.Errorf("BigQueryConnectionConnectionRef not normalized to External form or not created from `New()`")
121+
}
122+
123123
// NormalizedExternal provision the "External" value.
124124
// If the "External" comes from the ConfigConnector object, it has to acquire or reconcile with the GCP resource already.
125125
func (r *BigQueryConnectionConnectionRef) NormalizedExternal(ctx context.Context, reader client.Reader, othernamespace string) (string, error) {

apis/bigqueryconnection/v1beta1/connection_types.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ type Parent struct {
3838
type BigQueryConnectionConnectionSpec struct {
3939
Parent `json:",inline"`
4040

41-
// The BigQuery ConnectionID. This is a server-generated ID in the UUID format.
42-
// If not provided, ConfigConnector will create a new Connection and store the UUID in `status.serviceGeneratedID` field.
41+
// Immutable. Optional.
42+
// The BigQuery Connection ID used for resource creation or acquisition.
43+
// For creation: If specified, this value is used as the connection ID. If not provided, a UUID will be generated and assigned as the connection ID.
44+
// For acquisition: This field must be provided to identify the connection resource to acquire.
4345
ResourceID *string `json:"resourceID,omitempty"`
4446

4547
// User provided display name for the connection.

config/crds/resources/apiextensions.k8s.io_v1_customresourcedefinition_bigqueryconnectionconnections.bigqueryconnection.cnrm.cloud.google.com.yaml

+12-8
Original file line numberDiff line numberDiff line change
@@ -305,10 +305,12 @@ spec:
305305
type: string
306306
type: object
307307
resourceID:
308-
description: The BigQuery ConnectionID. This is a server-generated
309-
ID in the UUID format. If not provided, ConfigConnector will create
310-
a new Connection and store the UUID in `status.serviceGeneratedID`
311-
field.
308+
description: 'Immutable. Optional. The BigQuery Connection ID used
309+
for resource creation or acquisition. For creation: If specified,
310+
this value is used as the connection ID. If not provided, a UUID
311+
will be generated and assigned as the connection ID. For acquisition:
312+
This field must be provided to identify the connection resource
313+
to acquire.'
312314
type: string
313315
spark:
314316
description: Spark properties.
@@ -800,10 +802,12 @@ spec:
800802
type: string
801803
type: object
802804
resourceID:
803-
description: The BigQuery ConnectionID. This is a server-generated
804-
ID in the UUID format. If not provided, ConfigConnector will create
805-
a new Connection and store the UUID in `status.serviceGeneratedID`
806-
field.
805+
description: 'Immutable. Optional. The BigQuery Connection ID used
806+
for resource creation or acquisition. For creation: If specified,
807+
this value is used as the connection ID. If not provided, a UUID
808+
will be generated and assigned as the connection ID. For acquisition:
809+
This field must be provided to identify the connection resource
810+
to acquire.'
807811
type: string
808812
spark:
809813
description: Spark properties.

pkg/clients/generated/apis/bigqueryconnection/v1beta1/bigqueryconnectionconnection_types.go

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

pkg/controller/direct/bigqueryconnection/connection_controller.go

+17-3
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,11 @@ func (a *Adapter) Find(ctx context.Context) (bool, error) {
176176

177177
log.V(2).Info("getting BigQueryConnectionConnection", "name", a.id.External)
178178

179-
if a.id.External == "" {
180-
// Cannot retrieve the Connection without ServiceGeneratedID, expecting to create a new Connection.
179+
_, idIsSet, err := a.id.ConnectionID()
180+
if err != nil {
181+
return false, err
182+
}
183+
if !idIsSet { // resource is not yet created
181184
return false, nil
182185
}
183186
req := &bigqueryconnectionpb.GetConnectionRequest{Name: a.id.External}
@@ -211,12 +214,23 @@ func (a *Adapter) Create(ctx context.Context, createOp *directbase.CreateOperati
211214

212215
parent, err := a.id.Parent()
213216
if err != nil {
214-
return fmt.Errorf("get BigQueryConnectionConnection parent %s: %w", a.id.External, err)
217+
return err
215218
}
216219
req := &bigqueryconnectionpb.CreateConnectionRequest{
217220
Parent: parent,
218221
Connection: resource,
219222
}
223+
id, isIsSet, err := a.id.ConnectionID()
224+
if err != nil {
225+
return err
226+
}
227+
if isIsSet { // during "Create", this means user has specified connection ID in `spec.ResourceID` field.
228+
req = &bigqueryconnectionpb.CreateConnectionRequest{
229+
Parent: parent,
230+
ConnectionId: id,
231+
Connection: resource,
232+
}
233+
}
220234
created, err := a.gcpClient.CreateConnection(ctx, req)
221235
if err != nil {
222236
return fmt.Errorf("creating Connection %s: %w", a.id.External, err)

scripts/generate-google3-docs/resource-reference/generated/resource-docs/bigqueryconnection/bigqueryconnectionconnection.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
GET https://bigqueryconnection.googleapis.com/v1/projects/${projectId}/locations/us-central1/connections/test-bqcc-acquisition?%24alt=json%3Benum-encoding%3Dint
2+
Content-Type: application/json
3+
User-Agent: kcc/controller-manager
4+
x-goog-request-params: name=projects%2F${projectId}%2Flocations%2Fus-central1%2Fconnections%2Ftest-bqcc-acquisition
5+
6+
404 Not Found
7+
Cache-Control: private
8+
Content-Type: application/json; charset=UTF-8
9+
Server: ESF
10+
Vary: Origin
11+
Vary: X-Origin
12+
Vary: Referer
13+
X-Content-Type-Options: nosniff
14+
X-Frame-Options: SAMEORIGIN
15+
X-Xss-Protection: 0
16+
17+
{
18+
"error": {
19+
"code": 404,
20+
"message": "Not found: Connection projects/${projectNumber}/locations/us-central1/connections/test-bqcc-acquisition",
21+
"status": "NOT_FOUND"
22+
}
23+
}
24+
25+
---
26+
27+
POST https://bigqueryconnection.googleapis.com/v1/projects/${projectId}/locations/us-central1/connections?%24alt=json%3Benum-encoding%3Dint&connectionId=test-bqcc-acquisition
28+
Content-Type: application/json
29+
User-Agent: kcc/controller-manager
30+
x-goog-request-params: parent=projects%2F${projectId}%2Flocations%2Fus-central1
31+
32+
{
33+
"cloudResource": {},
34+
"description": "BigQueryConnection Connection resource for acquisition"
35+
}
36+
37+
200 OK
38+
Cache-Control: private
39+
Content-Type: application/json; charset=UTF-8
40+
Server: ESF
41+
Vary: Origin
42+
Vary: X-Origin
43+
Vary: Referer
44+
X-Content-Type-Options: nosniff
45+
X-Frame-Options: SAMEORIGIN
46+
X-Xss-Protection: 0
47+
48+
{
49+
"cloudResource": {
50+
"serviceAccountId": "bqcx-projects/${projectId}/locations/[email protected]"
51+
},
52+
"creationTime": "1731379730",
53+
"description": "BigQueryConnection Connection resource for acquisition",
54+
"lastModifiedTime": "1731379730",
55+
"name": "projects/${projectNumber}/locations/us-central1/connections/test-bqcc-acquisition"
56+
}

0 commit comments

Comments
 (0)