diff --git a/README.md b/README.md index f630a30..8f742a6 100644 --- a/README.md +++ b/README.md @@ -28,4 +28,5 @@ Flags: --memory-limit="128Mi" Memory limit of the sidecar container --proxy-version="1.11" CloudSQL proxy version --verbose=VERBOSE Verbose mode (eg. false) + --term-timeout Delay CloudSQL proxy termination. Optional. Details: https://github.com/GoogleCloudPlatform/cloudsql-proxy ``` \ No newline at end of file diff --git a/main.go b/main.go index a6f475f..3812784 100644 --- a/main.go +++ b/main.go @@ -3,10 +3,12 @@ package main import ( "bytes" "fmt" - "github.com/pkg/errors" "io" "io/ioutil" "os" + "time" + + "github.com/pkg/errors" "gopkg.in/alecthomas/kingpin.v2" "k8s.io/api/apps/v1beta1" @@ -27,6 +29,7 @@ var ( memoryLimit = kingpin.Flag("memory-limit", "Memory limit of the sidecar container").Default("128Mi").String() proxyVersion = kingpin.Flag("proxy-version", "CloudSQL proxy version").Default("1.13").String() verbose = kingpin.Flag("verbose", "CloudSQL proxy verbose mode").Default("false").String() + termTimeout = kingpin.Flag("term-timeout", "Delay CloudSQL proxy termination. Optional. Details: https://github.com/GoogleCloudPlatform/cloudsql-proxy").String() ) func main() { @@ -164,7 +167,7 @@ func getCloudContainer() v1.Container { cloudSQLProxyContainer = v1.Container{} cloudSQLProxyContainer.Name = "cloudsql-proxy" cloudSQLProxyContainer.Image = fmt.Sprintf("gcr.io/cloudsql-docker/gce-proxy:%s", *proxyVersion) - cloudSQLProxyContainer.Command = []string{"/cloud_sql_proxy", fmt.Sprintf("-instances=%s:%s:%s", *project, *region, *instance), "-log_debug_stdout=true", fmt.Sprintf("-verbose=%s", *verbose), "-credential_file=/secrets/cloudsql/credentials.json"} + cloudSQLProxyContainer.Command = buildCommand() cloudSQLProxyContainer.Resources = v1.ResourceRequirements{Requests: requestResources, Limits: limitResources} cloudSQLProxyContainer.SecurityContext = &securityContext cloudSQLProxyContainer.VolumeMounts = append(cloudSQLProxyContainer.VolumeMounts, volumeMount) @@ -180,3 +183,14 @@ func putItBack(otherResources [][]byte, w io.Writer) { w.Write(resourceBytes) } } + +// build cloud_sql_proxy options. validate termTimeout, if not valid do not set it. +func buildCommand() []string { + commands := []string{"/cloud_sql_proxy", fmt.Sprintf("-instances=%s:%s:%s", *project, *region, *instance), "-log_debug_stdout=true", fmt.Sprintf("-verbose=%s", *verbose), "-credential_file=/secrets/cloudsql/credentials.json"} + + if _, err := time.ParseDuration(*termTimeout); err == nil { + commands = append(commands, fmt.Sprintf("-term_timeout=%s", *termTimeout)) + } + + return commands +} diff --git a/main_test.go b/main_test.go index 37312f2..90be6a6 100644 --- a/main_test.go +++ b/main_test.go @@ -93,3 +93,262 @@ spec: io.Copy(&buf, r) assert.Equal(t, expectedOutput, buf.String()) } + +func Test_runInjectorCorrectTimeout(t *testing.T) { + + expectedOutput := `apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + creationTimestamp: null + name: test +spec: + replicas: 1 + strategy: {} + template: + metadata: + creationTimestamp: null + spec: + containers: + - image: some-image + name: name-test + resources: {} + - command: + - /cloud_sql_proxy + - -instances=project-test:region-test:instance-test + - -log_debug_stdout=true + - -verbose= + - -credential_file=/secrets/cloudsql/credentials.json + - -term_timeout=35s + image: gcr.io/cloudsql-docker/gce-proxy:1.11 + name: cloudsql-proxy + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 5m + memory: 8Mi + securityContext: + allowPrivilegeEscalation: false + runAsUser: 2 + volumeMounts: + - mountPath: /secrets/cloudsql + name: cloudsql-proxy-credentials + readOnly: true + volumes: + - name: test-volume + secret: + secretName: test-secret + - name: cloudsql-proxy-credentials + secret: + secretName: cloudsql-proxy-credentials +status: {} + +--- + +apiVersion: v1 +kind: Service +metadata: + name: test-svc +spec: + ports: + - name: web + port: 8080` + + // Just to trick to get control other stdout + // r and w are linked => whatever is written in w is readable in r + oldStdout := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + *path = "./test/test.yaml" + *instance = "instance-test" + *region = "region-test" + *project = "project-test" + *cpuRequest = "5m" + *memoryRequest = "8Mi" + *cpuLimit = "100m" + *memoryLimit = "128Mi" + *proxyVersion = "1.11" + *termTimeout = "35s" + + runInjector() + os.Stdout = oldStdout + w.Close() + var buf bytes.Buffer + io.Copy(&buf, r) + assert.Equal(t, expectedOutput, buf.String()) +} + +func Test_runInjectorTimeoutIncorrect(t *testing.T) { + + expectedOutput := `apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + creationTimestamp: null + name: test +spec: + replicas: 1 + strategy: {} + template: + metadata: + creationTimestamp: null + spec: + containers: + - image: some-image + name: name-test + resources: {} + - command: + - /cloud_sql_proxy + - -instances=project-test:region-test:instance-test + - -log_debug_stdout=true + - -verbose= + - -credential_file=/secrets/cloudsql/credentials.json + image: gcr.io/cloudsql-docker/gce-proxy:1.11 + name: cloudsql-proxy + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 5m + memory: 8Mi + securityContext: + allowPrivilegeEscalation: false + runAsUser: 2 + volumeMounts: + - mountPath: /secrets/cloudsql + name: cloudsql-proxy-credentials + readOnly: true + volumes: + - name: test-volume + secret: + secretName: test-secret + - name: cloudsql-proxy-credentials + secret: + secretName: cloudsql-proxy-credentials +status: {} + +--- + +apiVersion: v1 +kind: Service +metadata: + name: test-svc +spec: + ports: + - name: web + port: 8080` + + // Just to trick to get control other stdout + // r and w are linked => whatever is written in w is readable in r + oldStdout := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + *path = "./test/test.yaml" + *instance = "instance-test" + *region = "region-test" + *project = "project-test" + *cpuRequest = "5m" + *memoryRequest = "8Mi" + *cpuLimit = "100m" + *memoryLimit = "128Mi" + *proxyVersion = "1.11" + *termTimeout = "35" + + runInjector() + os.Stdout = oldStdout + w.Close() + var buf bytes.Buffer + io.Copy(&buf, r) + assert.Equal(t, expectedOutput, buf.String()) +} + +func Test_runInjectorTimeoutEmpty(t *testing.T) { + + expectedOutput := `apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + creationTimestamp: null + name: test +spec: + replicas: 1 + strategy: {} + template: + metadata: + creationTimestamp: null + spec: + containers: + - image: some-image + name: name-test + resources: {} + - command: + - /cloud_sql_proxy + - -instances=project-test:region-test:instance-test + - -log_debug_stdout=true + - -verbose= + - -credential_file=/secrets/cloudsql/credentials.json + image: gcr.io/cloudsql-docker/gce-proxy:1.11 + name: cloudsql-proxy + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 5m + memory: 8Mi + securityContext: + allowPrivilegeEscalation: false + runAsUser: 2 + volumeMounts: + - mountPath: /secrets/cloudsql + name: cloudsql-proxy-credentials + readOnly: true + volumes: + - name: test-volume + secret: + secretName: test-secret + - name: cloudsql-proxy-credentials + secret: + secretName: cloudsql-proxy-credentials +status: {} + +--- + +apiVersion: v1 +kind: Service +metadata: + name: test-svc +spec: + ports: + - name: web + port: 8080` + + // Just to trick to get control other stdout + // r and w are linked => whatever is written in w is readable in r + oldStdout := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + *path = "./test/test.yaml" + *instance = "instance-test" + *region = "region-test" + *project = "project-test" + *cpuRequest = "5m" + *memoryRequest = "8Mi" + *cpuLimit = "100m" + *memoryLimit = "128Mi" + *proxyVersion = "1.11" + *termTimeout = "" + + runInjector() + os.Stdout = oldStdout + w.Close() + var buf bytes.Buffer + io.Copy(&buf, r) + assert.Equal(t, expectedOutput, buf.String()) +}