Skip to content

Commit d8df7c2

Browse files
committed
feat: Support dns name for connector names
Adds test cases to demonstrate that the DNS name may be used for the instance connection name for instances with DNS TXT records. Fixes #683
1 parent fc5af2e commit d8df7c2

6 files changed

Lines changed: 123 additions & 42 deletions

File tree

docs/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ _Appears in:_
157157

158158
| Field | Description | Default | Validation |
159159
| --- | --- | --- | --- |
160-
| `connectionString` _string_ | ConnectionString is the connection string for the Cloud SQL Instance<br />in the format `project_id:region:instance_name` | | Pattern: `^([^:]+(:[^:]+)?):([^:]+):([^:]+)$` <br />Required: \{\} <br /> |
160+
| `connectionString` _string_ | ConnectionString is the connection string for the Cloud SQL Instance.<br />This may be an instance connection name in the format `project_id:region:instance_name`<br />or a DNS name for the instance. | | Required: \{\} <br /> |
161161
| `port` _integer_ | Port (optional) sets the tcp port for this instance. If not set, a value will<br />be automatically assigned by the operator and set as an environment variable<br />on all containers in the workload named according to PortEnvName. The operator will choose<br />a port so that it does not conflict with other ports on the workload. | | Minimum: 1 <br />Optional: \{\} <br /> |
162162
| `autoIAMAuthN` _boolean_ | AutoIAMAuthN (optional) Enables IAM Authentication for this instance.<br />Default value is false. | | Optional: \{\} <br /> |
163163
| `privateIP` _boolean_ | PrivateIP (optional) Enable connection to the Cloud SQL instance's private ip for this instance.<br />Default value is false. | | Optional: \{\} <br /> |

internal/api/v1/authproxyworkload_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,30 @@ func TestAuthProxyWorkload_ValidateCreate_InstanceSpec(t *testing.T) {
111111
}},
112112
wantValid: false,
113113
},
114+
{
115+
desc: "Valid, Instance configured with valid instance name",
116+
spec: []cloudsqlapi.InstanceSpec{{
117+
ConnectionString: "proj:region:db2",
118+
Port: ptr(int32(5000)),
119+
}},
120+
wantValid: true,
121+
},
122+
{
123+
desc: "Valid, Instance configured with domain name name",
124+
spec: []cloudsqlapi.InstanceSpec{{
125+
ConnectionString: "proj:region:db2",
126+
Port: ptr(int32(5000)),
127+
}},
128+
wantValid: true,
129+
},
130+
{
131+
desc: "Invalid, Instance configured with malformed name",
132+
spec: []cloudsqlapi.InstanceSpec{{
133+
ConnectionString: "bad name!",
134+
Port: ptr(int32(5000)),
135+
}},
136+
wantValid: false,
137+
},
114138
}
115139
for _, tc := range data {
116140
t.Run(tc.desc, func(t *testing.T) {

internal/api/v1/authproxyworkload_types.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,10 @@ type TelemetrySpec struct {
341341
// `{ "connectionString":"my-project:us-central1:my-db-server", "port":5000 }`
342342
type InstanceSpec struct {
343343

344-
// ConnectionString is the connection string for the Cloud SQL Instance
345-
// in the format `project_id:region:instance_name`
344+
// ConnectionString is the connection string for the Cloud SQL Instance.
345+
// This may be an instance connection name in the format `project_id:region:instance_name`
346+
// or a DNS name for the instance.
346347
//+kubebuilder:validation:Required
347-
//+kubebuilder:validation:Pattern:="^([^:]+(:[^:]+)?):([^:]+):([^:]+)$"
348348
ConnectionString string `json:"connectionString,omitempty"`
349349

350350
// Port (optional) sets the tcp port for this instance. If not set, a value will

internal/api/v1/authproxyworkload_webhook.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"path"
2121
"reflect"
2222

23+
"cloud.google.com/go/cloudsqlconn/instance"
2324
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
2425

2526
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -281,6 +282,7 @@ func validateInstances(spec *[]InstanceSpec, f *field.Path) field.ErrorList {
281282
}
282283
for i, inst := range *spec {
283284
ff := f.Child(fmt.Sprintf("%d", i))
285+
errs = append(errs, validateConnectionString(inst, f)...)
284286
if inst.Port != nil {
285287
for _, s := range apivalidation.IsValidPortNum(int(*inst.Port)) {
286288
errs = append(errs, field.Invalid(ff.Child("port"), inst.Port, s))
@@ -311,6 +313,16 @@ func validateInstances(spec *[]InstanceSpec, f *field.Path) field.ErrorList {
311313
return errs
312314
}
313315

316+
func validateConnectionString(inst InstanceSpec, f *field.Path) field.ErrorList {
317+
if instance.IsValidDomain(inst.ConnectionString) {
318+
return nil
319+
}
320+
if _, err := instance.ParseConnName(inst.ConnectionString); err != nil {
321+
return []*field.Error{field.Invalid(f, inst.ConnectionString, "is not a valid instance connection name or dns name")}
322+
}
323+
return nil
324+
}
325+
314326
func validateEnvName(f *field.Path, envName string) field.ErrorList {
315327
var errs field.ErrorList
316328
if envName != "" {

internal/testintegration/integration_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,34 @@ func TestCreateAndDeleteResource(t *testing.T) {
8484

8585
}
8686

87+
// newTestCaseClient Creates a new TestCaseClient providing unique namespace and
88+
// other default values.
89+
func newDomainNameTestCaseClient(name string, c client.Client) *testhelpers.TestCaseClient {
90+
return &testhelpers.TestCaseClient{
91+
Client: c,
92+
Namespace: testhelpers.NewNamespaceName(name),
93+
ConnectionString: "db.example.com",
94+
}
95+
}
96+
97+
func TestCreateAndDeleteDomainNameResource(t *testing.T) {
98+
ctx := testintegration.TestContext()
99+
tcc := newDomainNameTestCaseClient("create", defaultClient)
100+
res, err := tcc.CreateResource(ctx)
101+
if err != nil {
102+
t.Fatal(err)
103+
}
104+
err = tcc.WaitForFinalizerOnResource(ctx, res)
105+
if err != nil {
106+
t.Fatal(err)
107+
}
108+
err = tcc.DeleteResourceAndWait(ctx, res)
109+
if err != nil {
110+
t.Fatal(err)
111+
}
112+
113+
}
114+
87115
func TestModifiesNewDeployment(t *testing.T) {
88116
ctx := testintegration.TestContext()
89117
tcc := newTestCaseClient("modifynew", defaultClient)

internal/workload/podspec_updates_test.go

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -135,52 +135,69 @@ func configureProxies(u *workload.Updater, wl *workload.PodWorkload, proxies []*
135135
}
136136

137137
func TestUpdatePodWorkload(t *testing.T) {
138-
var (
139-
wantsName = "instance1"
140-
wantsPort int32 = 8080
141-
wantContainerName = "csql-default-" + wantsName
142-
wantsInstanceName = "project:server:db"
143-
wantsInstanceArg = fmt.Sprintf("%s?port=%d", wantsInstanceName, wantsPort)
144-
u = workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
145-
)
146-
var err error
138+
tcs := []struct {
139+
name string
140+
wantsName string
141+
wantsPort int32
142+
wantsInstanceName string
143+
}{{
144+
name: "WithInstanceConnectionString",
145+
wantsName: "instance1",
146+
wantsPort: int32(8080),
147+
wantsInstanceName: "project:server:db",
148+
}, {
149+
name: "WithDnsNameString",
150+
wantsName: "instance1",
151+
wantsPort: int32(8080),
152+
wantsInstanceName: "db.example.com",
153+
},
154+
}
155+
for _, tc := range tcs {
156+
t.Run(tc.name, func(t *testing.T) {
157+
u := workload.NewUpdater("cloud-sql-proxy-operator/dev", workload.DefaultProxyImage, false)
158+
wantsContainerName := "csql-default-" + tc.wantsName
159+
wantsInstanceArg := fmt.Sprintf("%s?port=%d", tc.wantsInstanceName, tc.wantsPort)
147160

148-
// Create a pod
149-
wl := podWorkload()
161+
var err error
150162

151-
// ensure that the deployment only has one container before
152-
// updating the deployment.
153-
if len(wl.Pod.Spec.Containers) != 1 {
154-
t.Fatalf("got %v, wants 1. deployment containers length", len(wl.Pod.Spec.Containers))
155-
}
163+
// Create a pod
164+
wl := podWorkload()
156165

157-
// Create a AuthProxyWorkload that matches the deployment
158-
proxy := simpleAuthProxy(wantsName, wantsInstanceName)
159-
proxy.Spec.Instances[0].Port = ptr(wantsPort)
166+
// ensure that the deployment only has one container before
167+
// updating the deployment.
168+
if len(wl.Pod.Spec.Containers) != 1 {
169+
t.Fatalf("got %v, wants 1. deployment containers length", len(wl.Pod.Spec.Containers))
170+
}
160171

161-
// Update the container with new markWorkloadNeedsUpdate
162-
err = configureProxies(u, wl, []*cloudsqlapi.AuthProxyWorkload{proxy})
163-
if err != nil {
164-
t.Fatal(err)
165-
}
172+
// Create a AuthProxyWorkload that matches the deployment
173+
proxy := simpleAuthProxy(tc.wantsName, tc.wantsInstanceName)
174+
proxy.Spec.Instances[0].Port = ptr(tc.wantsPort)
166175

167-
// test that there are now 2 containers
168-
if want, got := 2, len(wl.Pod.Spec.Containers); want != got {
169-
t.Fatalf("got %v want %v, number of deployment containers", got, want)
170-
}
176+
// Update the container with new markWorkloadNeedsUpdate
177+
err = configureProxies(u, wl, []*cloudsqlapi.AuthProxyWorkload{proxy})
178+
if err != nil {
179+
t.Fatal(err)
180+
}
171181

172-
t.Logf("Containers: {%v}", wl.Pod.Spec.Containers)
182+
// test that there are now 2 containers
183+
if want, got := 2, len(wl.Pod.Spec.Containers); want != got {
184+
t.Fatalf("got %v want %v, number of deployment containers", got, want)
185+
}
173186

174-
// test that the container has the proper name following the conventions
175-
foundContainer, err := findContainer(wl, wantContainerName)
176-
if err != nil {
177-
t.Fatal(err)
178-
}
187+
t.Logf("Containers: {%v}", wl.Pod.Spec.Containers)
179188

180-
// test that the container args have the expected args
181-
if gotArg, err := hasArg(wl, wantContainerName, wantsInstanceArg); err != nil || !gotArg {
182-
t.Errorf("wants connection string arg %v but it was not present in proxy container args %v",
183-
wantsInstanceArg, foundContainer.Args)
189+
// test that the container has the proper name following the conventions
190+
foundContainer, err := findContainer(wl, wantsContainerName)
191+
if err != nil {
192+
t.Fatal(err)
193+
}
194+
195+
// test that the container args have the expected args
196+
if gotArg, err := hasArg(wl, wantsContainerName, wantsInstanceArg); err != nil || !gotArg {
197+
t.Errorf("wants connection string arg %v but it was not present in proxy container args %v",
198+
wantsInstanceArg, foundContainer.Args)
199+
}
200+
})
184201
}
185202

186203
}

0 commit comments

Comments
 (0)