├── .ci
└── cloudbuild.yaml
├── .envrc.example
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug_report.yaml
│ ├── documentation_issue.yaml
│ ├── feature_request.yaml
│ └── question.yaml
├── auto-label.yaml
├── blunderbuss.yml
├── flakybot.yaml
├── header-checker-lint.yml
├── labels.yml
├── release-please.yml
├── renovate.json5
├── trusted-contribution.yml
└── workflows
│ ├── codeql.yml
│ ├── coverage.yaml
│ ├── docs.yaml
│ ├── govulncheck.yaml
│ ├── labels.yaml
│ ├── lint.yaml
│ ├── scorecard.yml
│ ├── tests.yaml
│ └── v1-periodic.yaml
├── .gitignore
├── .golangci.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── Dockerfile.alpine
├── Dockerfile.bookworm
├── Dockerfile.bullseye
├── LICENSE
├── README.md
├── SECURITY.md
├── cloudsql
└── cloudsql.go
├── cmd
├── config_test.go
├── errors.go
├── gendocs
│ └── gen_cloud-sql-proxy_docs.go
├── options.go
├── options_test.go
├── root.go
├── root_linux_test.go
├── root_test.go
├── root_windows_test.go
├── testdata
│ ├── config-json.json
│ ├── config-toml.toml
│ ├── config-yaml.yaml
│ └── two-instances.toml
├── version.txt
└── wait_test.go
├── docs
└── cmd
│ ├── cloud-sql-proxy.md
│ ├── cloud-sql-proxy_completion.md
│ ├── cloud-sql-proxy_completion_bash.md
│ ├── cloud-sql-proxy_completion_fish.md
│ ├── cloud-sql-proxy_completion_powershell.md
│ ├── cloud-sql-proxy_completion_zsh.md
│ └── cloud-sql-proxy_wait.md
├── examples
├── disaster-recovery
│ ├── README.md
│ └── failover.sh
├── k8s-health-check
│ ├── README.md
│ └── proxy_with_http_health_check.yaml
├── k8s-service
│ ├── README.md
│ ├── ca_csr.json
│ ├── deployment.yaml
│ ├── pgbouncer_deployment.yaml
│ ├── pgbouncer_service.yaml
│ └── server_csr.json
├── k8s-sidecar
│ ├── README.md
│ ├── job_with_shutdown_hook.yaml
│ ├── job_with_sidecar.yaml
│ ├── no_proxy_private_ip.yaml
│ ├── proxy_with_sa_key.yaml
│ ├── proxy_with_workload_identity.yaml
│ └── service_account.yaml
└── multi-container
│ └── ruby
│ ├── Dockerfile
│ ├── Gemfile
│ ├── README.md
│ ├── app.rb
│ ├── config.ru
│ └── multicontainer.yaml
├── go.mod
├── go.sum
├── internal
├── gcloud
│ ├── gcloud.go
│ └── gcloud_test.go
├── healthcheck
│ ├── healthcheck.go
│ └── healthcheck_test.go
├── log
│ └── log.go
└── proxy
│ ├── fuse.go
│ ├── fuse_darwin.go
│ ├── fuse_freebsd.go
│ ├── fuse_linux.go
│ ├── fuse_linux_test.go
│ ├── fuse_openbsd.go
│ ├── fuse_test.go
│ ├── fuse_windows.go
│ ├── internal_test.go
│ ├── proxy.go
│ ├── proxy_other.go
│ ├── proxy_other_test.go
│ ├── proxy_test.go
│ ├── proxy_windows_test.go
│ └── unix.go
├── main.go
├── main_windows.go
├── migration-guide.md
├── tests
├── common_test.go
├── connection_test.go
├── fuse_test.go
├── mysql_test.go
├── other_test.go
├── postgres_test.go
└── sqlserver_test.go
├── windows-service-guide.md
├── windows_install_service.bat
└── windows_remove_service.bat
/.ci/cloudbuild.yaml:
--------------------------------------------------------------------------------
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 | steps:
16 |
17 | - id: run integration tests
18 | name: golang:${_VERSION}
19 | env:
20 | [
21 | "IP_TYPE=${_IP_TYPE}",
22 | "GOOGLE_CLOUD_PROJECT=${PROJECT_ID}",
23 | "TMPDIR=/tmp"
24 | ]
25 | secretEnv:
26 | [
27 | "MYSQL_CONNECTION_NAME",
28 | "MYSQL_USER",
29 | "MYSQL_PASS",
30 | "MYSQL_DB",
31 | "MYSQL_MCP_CONNECTION_NAME",
32 | "MYSQL_MCP_PASS",
33 | "POSTGRES_CONNECTION_NAME",
34 | "POSTGRES_USER",
35 | "POSTGRES_USER_IAM",
36 | "POSTGRES_PASS",
37 | "POSTGRES_DB",
38 | "POSTGRES_CAS_CONNECTION_NAME",
39 | "POSTGRES_CAS_PASS",
40 | "POSTGRES_CUSTOMER_CAS_CONNECTION_NAME",
41 | "POSTGRES_CUSTOMER_CAS_PASS",
42 | "POSTGRES_CUSTOMER_CAS_DOMAIN_NAME",
43 | "POSTGRES_MCP_CONNECTION_NAME",
44 | "POSTGRES_MCP_PASS",
45 | "SQLSERVER_CONNECTION_NAME",
46 | "SQLSERVER_USER",
47 | "SQLSERVER_PASS",
48 | "SQLSERVER_DB",
49 | "IMPERSONATED_USER",
50 | ]
51 | entrypoint: bash
52 | args:
53 | - -c
54 | - |
55 | go test -race -v ./tests/...
56 |
57 | substitutions:
58 | _VERSION: ${_VERSION}
59 | _IP_TYPE: ${_IP_TYPE}
60 |
61 | availableSecrets:
62 | secretManager:
63 | - versionName: "projects/$PROJECT_ID/secrets/MYSQL_CONNECTION_NAME/versions/latest"
64 | env: "MYSQL_CONNECTION_NAME"
65 | - versionName: "projects/$PROJECT_ID/secrets/MYSQL_USER/versions/latest"
66 | env: "MYSQL_USER"
67 | - versionName: "projects/$PROJECT_ID/secrets/MYSQL_PASS/versions/latest"
68 | env: "MYSQL_PASS"
69 | - versionName: "projects/$PROJECT_ID/secrets/MYSQL_DB/versions/latest"
70 | env: "MYSQL_DB"
71 | - versionName: "projects/$PROJECT_ID/secrets/MYSQL_MCP_CONNECTION_NAME/versions/latest"
72 | env: "MYSQL_MCP_CONNECTION_NAME"
73 | - versionName: "projects/$PROJECT_ID/secrets/MYSQL_MCP_PASS/versions/latest"
74 | env: "MYSQL_MCP_PASS"
75 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_CONNECTION_NAME/versions/latest"
76 | env: "POSTGRES_CONNECTION_NAME"
77 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_USER/versions/latest"
78 | env: "POSTGRES_USER"
79 | - versionName: "projects/$PROJECT_ID/secrets/CLOUD_BUILD_POSTGRES_IAM_USER/versions/latest"
80 | env: "POSTGRES_USER_IAM"
81 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_PASS/versions/latest"
82 | env: "POSTGRES_PASS"
83 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_DB/versions/latest"
84 | env: "POSTGRES_DB"
85 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_CAS_CONNECTION_NAME/versions/latest"
86 | env: "POSTGRES_CAS_CONNECTION_NAME"
87 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_CAS_PASS/versions/latest"
88 | env: "POSTGRES_CAS_PASS"
89 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_CUSTOMER_CAS_CONNECTION_NAME/versions/latest"
90 | env: "POSTGRES_CUSTOMER_CAS_CONNECTION_NAME"
91 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_CUSTOMER_CAS_PASS/versions/latest"
92 | env: "POSTGRES_CUSTOMER_CAS_PASS"
93 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_CUSTOMER_CAS_DOMAIN_NAME/versions/latest"
94 | env: "POSTGRES_CUSTOMER_CAS_DOMAIN_NAME"
95 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_MCP_CONNECTION_NAME/versions/latest"
96 | env: "POSTGRES_MCP_CONNECTION_NAME"
97 | - versionName: "projects/$PROJECT_ID/secrets/POSTGRES_MCP_PASS/versions/latest"
98 | env: "POSTGRES_MCP_PASS"
99 | - versionName: "projects/$PROJECT_ID/secrets/SQLSERVER_CONNECTION_NAME/versions/latest"
100 | env: "SQLSERVER_CONNECTION_NAME"
101 | - versionName: "projects/$PROJECT_ID/secrets/SQLSERVER_USER/versions/latest"
102 | env: "SQLSERVER_USER"
103 | - versionName: "projects/$PROJECT_ID/secrets/SQLSERVER_PASS/versions/latest"
104 | env: "SQLSERVER_PASS"
105 | - versionName: "projects/$PROJECT_ID/secrets/SQLSERVER_DB/versions/latest"
106 | env: "SQLSERVER_DB"
107 | - versionName: "projects/$PROJECT_ID/secrets/CLOUD_BUILD_SA/versions/latest"
108 | env: "IMPERSONATED_USER"
109 |
110 | options:
111 | dynamicSubstitutions: true
112 | pool:
113 | name: ${_POOL_NAME}
114 | logging: CLOUD_LOGGING_ONLY
115 |
--------------------------------------------------------------------------------
/.envrc.example:
--------------------------------------------------------------------------------
1 | export GOOGLE_CLOUD_PROJECT="project-name"
2 |
3 | export MYSQL_CONNECTION_NAME="project:region:instance"
4 | export MYSQL_USER="mysql-user"
5 | export MYSQL_PASS="mysql-password"
6 | export MYSQL_DB="mysql-db-name"
7 |
8 | export POSTGRES_CONNECTION_NAME="project:region:instance"
9 | export POSTGRES_USER="postgres-user"
10 | export POSTGRES_PASS="postgres-password"
11 | export POSTGRES_DB="postgres-db-name"
12 | export POSTGRES_USER_IAM="some-user-with-db-access@example.com"
13 |
14 | export SQLSERVER_CONNECTION_NAME="project:region:instance"
15 | export SQLSERVER_USER="sqlserver-user"
16 | export SQLSERVER_PASS="sqlserver-password"
17 | export SQLSERVER_DB="sqlserver-db-name"
18 |
19 | export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json
20 |
21 | # Requires the impersonating IAM principal to have
22 | # roles/iam.serviceAccountTokenCreator
23 | export IMPERSONATED_USER="some-user-with-db-access@example.com"
24 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @GoogleCloudPlatform/cloud-sql-connectors
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | # https://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 | name: 🐞 Bug Report
16 | description: File a bug report
17 | title: "Brief summary of what bug or error was observed"
18 | labels: ["type: bug"]
19 | body:
20 | - type: markdown
21 | attributes:
22 | value: |
23 | Thanks for stopping by to let us know something could be better!
24 | Please run down the following list and make sure you've tried the usual "quick fixes":
25 | - Search the [current open issues](https://github.com/GoogleCloudPlatform/cloud-sql-proxy/issues)
26 | - Check for answers on [StackOverflow](https://stackoverflow.com/questions/tagged/google-cloud-sql) (under the 'google-cloud-sql' tag)
27 |
28 | If you are still having issues, please include as much information as possible below! :smile:
29 | - type: textarea
30 | id: bug-description
31 | attributes:
32 | label: Bug Description
33 | description: "Please enter a detailed description of the bug, and any information about what behavior you noticed and why it is defective or unintentional."
34 | validations:
35 | required: true
36 | - type: textarea
37 | id: example-code
38 | attributes:
39 | label: Example code (or command)
40 | description: "Please paste any useful application code related to the bug below. (if your code is in a public repo, feel free to paste a link!)"
41 | value: |
42 | ```
43 | // paste your code or command here
44 | ```
45 | - type: textarea
46 | id: stacktrace
47 | attributes:
48 | label: Stacktrace
49 | description: "Paste any relevant stacktrace or error you are running into here. Be sure to filter sensitive information!"
50 | render: bash
51 | - type: textarea
52 | id: repro
53 | attributes:
54 | label: Steps to reproduce?
55 | description: "How do you trigger this bug? Please walk us through it step by step."
56 | value: |
57 | 1. ?
58 | 2. ?
59 | 3. ?
60 | ...
61 | validations:
62 | required: true
63 | - type: textarea
64 | id: environment
65 | attributes:
66 | label: Environment
67 | description: "Let us know some details about the environment in which you are seeing the bug!"
68 | value: |
69 | 1. OS type and version:
70 | 2. Cloud SQL Proxy version (`./cloud-sql-proxy --version`):
71 | 3. Proxy invocation command (for example, `./cloud-sql-proxy --port 5432 INSTANCE_CONNECTION_NAME`):
72 | validations:
73 | required: true
74 | - type: textarea
75 | id: additional-details
76 | attributes:
77 | label: Additional Details
78 | description: "Any other information you want us to know?"
79 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation_issue.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | # https://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 | name: 📝 Documentation Issue
16 | description: Report wrong or missing information with the documentation in this repo.
17 | title: "Brief summary of what is missing or incorrect"
18 | labels: ["type: docs"]
19 | body:
20 | - type: markdown
21 | attributes:
22 | value: |
23 | Thanks for stopping by to let us know something could be better! :smile:
24 |
25 | Please explain below how we can improve our documentation.
26 | - type: textarea
27 | id: description
28 | attributes:
29 | label: Description
30 | description: "Provide a short description of what is missing or incorrect, as well as a link to the specific location of the issue."
31 | validations:
32 | required: true
33 | - type: textarea
34 | id: potential-solution
35 | attributes:
36 | label: Potential Solution
37 | description: "What would you prefer the documentation say? Why would this information be more accurate or helpful?"
38 | - type: textarea
39 | id: additional-details
40 | attributes:
41 | label: Additional Details
42 | description: "Please reference any other relevant issues, PRs, descriptions, or screenshots here."
43 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | # https://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 | name: ✨ Feature Request
16 | description: Suggest an idea for new or improved behavior.
17 | title: "Brief summary of the proposed feature"
18 | labels: ["type: feature request"]
19 | body:
20 | - type: markdown
21 | attributes:
22 | value: |
23 | Thanks for stopping by to let us know something could be better!
24 |
25 | Please run down the following list before proceeding with your feature request:
26 | - Search the [current open issues](https://github.com/GoogleCloudPlatform/cloud-sql-proxy/issues) to prevent creating a duplicate.
27 |
28 | Please include as much information as possible below! :smile:
29 | - type: textarea
30 | id: feature-description
31 | attributes:
32 | label: Feature Description
33 | description: "A clear and concise description of what feature you would like to see, and why it would be useful to have added."
34 | validations:
35 | required: true
36 | - type: textarea
37 | id: sample-code
38 | attributes:
39 | label: Sample code
40 | description: "If you already have an idea of what the implementation of this feature would like in code please provide it. (pseudo code is okay!)"
41 | value: |
42 | ```
43 | // sample code here
44 | ```
45 | - type: textarea
46 | id: alternatives-considered
47 | attributes:
48 | label: Alternatives Considered
49 | description: "Are there any workaround or third party tools to replicate this behavior? Why would adding this feature be preferred over them?"
50 | - type: textarea
51 | id: additional-details
52 | attributes:
53 | label: Additional Details
54 | description: "Any additional information we should know? Please reference it here (issues, PRs, descriptions, or screenshots)"
55 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | # https://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 | name: 💬 Question
16 | description: Questions on how something works or the best way to do something?
17 | title: "Brief summary of your question"
18 | labels: ["type: question"]
19 | body:
20 | - type: markdown
21 | attributes:
22 | value: |
23 | Thanks for stopping by to let us know something could be better!
24 |
25 | Please run down the following list and make sure you've tried the usual "quick fixes":
26 | - Search the [current open issues](https://github.com/GoogleCloudPlatform/cloud-sql-proxy/issues) for a similar question
27 | - Check for answers on [StackOverflow](https://stackoverflow.com/questions/tagged/google-cloud-sql) (under the 'google-cloud-sql' tag)
28 |
29 | If you still have a question, please include as much information as possible below! :smile:
30 | - type: textarea
31 | id: question
32 | attributes:
33 | label: Question
34 | description: "What's your question? Please provide as much relevant information as possible to reduce turnaround time."
35 | placeholder: "Example: How do I connect using this connector with Private IP from Cloud Run?"
36 | validations:
37 | required: true
38 | - type: textarea
39 | id: code
40 | attributes:
41 | label: Code
42 | description: "Please paste any useful application code that might be relevant to your question. (if your code is in a public repo, feel free to paste a link!)"
43 | render: Go
44 | - type: textarea
45 | id: additional-details
46 | attributes:
47 | label: Additional Details
48 | description: "Any other information you want us to know that might be helpful in answering your question? (link issues, PRs, descriptions, or screenshots)"
49 |
--------------------------------------------------------------------------------
/.github/auto-label.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 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 | # https://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 | enabled: false
16 |
--------------------------------------------------------------------------------
/.github/blunderbuss.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 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 | assign_issues:
16 | - hessjcg
17 | - kgala2
18 |
19 | assign_prs:
20 | - hessjcg
21 | - kgala2
22 |
--------------------------------------------------------------------------------
/.github/flakybot.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 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 | # https://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 | issuePriority: p2
16 |
--------------------------------------------------------------------------------
/.github/header-checker-lint.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 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 | # https://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 | allowedCopyrightHolders:
16 | - 'Google LLC'
17 | allowedLicenses:
18 | - 'Apache-2.0'
19 | sourceFileExtensions:
20 | - 'go'
21 | - 'yaml'
22 | - 'yml'
23 |
--------------------------------------------------------------------------------
/.github/labels.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 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 | - name: duplicate
16 | color: ededed
17 | description: ""
18 |
19 | - name: 'type: bug'
20 | color: db4437
21 | description: Error or flaw in code with unintended results or allowing sub-optimal
22 | usage patterns.
23 | - name: 'type: cleanup'
24 | color: c5def5
25 | description: An internal cleanup or hygiene concern.
26 | - name: 'type: docs'
27 | color: 0000A0
28 | description: Improvement to the documentation for an API.
29 | - name: 'type: feature request'
30 | color: c5def5
31 | description: ‘Nice-to-have’ improvement, new feature or different behavior or design.
32 | - name: 'type: process'
33 | color: c5def5
34 | description: A process-related concern. May include testing, release, or the like.
35 | - name: 'type: question'
36 | color: c5def5
37 | description: Request for information or clarification.
38 |
39 | - name: 'priority: p0'
40 | color: b60205
41 | description: Highest priority. Critical issue. P0 implies highest priority.
42 | - name: 'priority: p1'
43 | color: ffa03e
44 | description: Important issue which blocks shipping the next release. Will be fixed
45 | prior to next release.
46 | - name: 'priority: p2'
47 | color: fef2c0
48 | description: Moderately-important priority. Fix may not be included in next release.
49 | - name: 'priority: p3'
50 | color: ffffc7
51 | description: Desirable enhancement or fix. May not be included in next release.
52 |
53 | - name: 'v1'
54 | color: d93f0b
55 | description: Functionality for v1 of the proxy.
56 |
57 | - name: automerge
58 | color: 00ff00
59 | description: Merge the pull request once unit tests and other checks pass.
60 | - name: 'automerge: exact'
61 | color: 8dd517
62 | description: Summon MOG for automerging, but approvals need to be against the latest
63 | commit
64 | - name: do not merge
65 | color: d93f0b
66 | description: Indicates a pull request not ready for merge, due to either quality
67 | or timing.
68 |
69 | - name: 'autorelease: pending'
70 | color: ededed
71 | description: Release please needs to do its work on this.
72 | - name: 'autorelease: triggered'
73 | color: ededed
74 | description: Release please has triggered a release for this.
75 | - name: 'autorelease: tagged'
76 | color: ededed
77 | description: Release please has completed a release for this.
78 |
79 | - name: 'tests: run'
80 | color: 3DED97
81 | description: Label to trigger Github Action tests.
82 |
83 | - name: 'flakybot: flaky'
84 | color: 86d9d7
85 | description: Tells the Flaky Bot not to close or comment on this issue.
86 | - name: 'flakybot: quiet'
87 | color: 86d9d7
88 | description: Tells the Flaky Bot to comment less.
89 | - name: 'flakybot: issue'
90 | color: a9f9f7
91 | description: An issue filed by the Flaky Bot. Should not be added manually.
92 |
93 | - name: 'k8s'
94 | color: 1D76DB
95 | description: Kubernetes related issue or functionality.
96 |
--------------------------------------------------------------------------------
/.github/release-please.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 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 | handleGHRelease: true
16 | packageName: cloud-sql-proxy
17 | releaseType: simple
18 | versionFile: "cmd/version.txt"
19 | branches:
20 | - branch: v1
21 | versionFile: "proxy/util/version.txt"
22 | handleGHRelease: true
23 | packageName: cloud-sql-proxy
24 | releaseType: simple
25 | extraFiles:
26 | - README.md
27 | - cmd/root.go
28 | - docs/cmd/cloud-sql-proxy.md
29 | - examples/k8s-health-check/README.md
30 | - examples/k8s-service/README.md
31 | - examples/k8s-sidecar/README.md
32 |
--------------------------------------------------------------------------------
/.github/renovate.json5:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:recommended"
4 | ],
5 | "dependencyDashboardLabels": ["type: process"],
6 | "commitMessagePrefix": "deps: ",
7 | "postUpdateOptions": [
8 | "gomodTidy"
9 | ],
10 | "prConcurrentLimit": 3,
11 | "packageRules": [
12 | {
13 | "matchManagers": ["github-actions"],
14 | "groupName": "dependencies for github"
15 | },
16 | {
17 | "matchManagers": ["dockerfile"],
18 | "groupName": "container images"
19 | },
20 | {
21 | "groupName": "Non-major dependency updates",
22 | "matchManagers": ["gomod"],
23 | "matchUpdateTypes": ["minor", "patch"],
24 | },
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/.github/trusted-contribution.yml:
--------------------------------------------------------------------------------
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 | # Trigger presubmit tests for trusted contributors
16 | # https://github.com/googleapis/repo-automation-bots/tree/main/packages/trusted-contribution
17 | # Install: https://github.com/apps/trusted-contributions-gcf
18 |
19 | trustedContributors:
20 | - "dependabot[bot]"
21 | - "renovate-bot"
22 | - "renovate[bot]"
23 | - "forking-renovate[bot]"
24 | - "release-please[bot]"
25 | annotations:
26 | # Trigger Cloud Build tests
27 | - type: comment
28 | text: "/gcbrun"
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | name: "CodeQL"
16 |
17 | on:
18 | push:
19 | branches: ["main"]
20 | pull_request:
21 | branches: ["main"]
22 | paths-ignore:
23 | - "**/*.md"
24 | - "**/*.txt"
25 |
26 | # Declare default permissions as read only.
27 | permissions: read-all
28 |
29 | jobs:
30 | analyze:
31 | name: Analyze
32 | runs-on: ubuntu-latest
33 | permissions:
34 | actions: read
35 | contents: read
36 | security-events: write
37 |
38 | strategy:
39 | fail-fast: false
40 | matrix:
41 | language: ["go"]
42 |
43 | steps:
44 | - name: Checkout repository
45 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
46 |
47 | - name: Setup Go
48 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
49 | with:
50 | go-version: "1.24"
51 | if: ${{ matrix.language == 'go' }}
52 |
53 | # Initializes the CodeQL tools for scanning.
54 | - name: Initialize CodeQL
55 | uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
56 | with:
57 | languages: ${{ matrix.language }}
58 |
59 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
60 | # If this step fails, then you should remove it and run the build manually
61 | - name: Autobuild
62 | uses: github/codeql-action/autobuild@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
63 |
64 | - name: Perform CodeQL Analysis
65 | uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
66 | with:
67 | category: "/language:${{matrix.language}}"
68 |
--------------------------------------------------------------------------------
/.github/workflows/coverage.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 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 | name: code coverage
16 | on: [pull_request]
17 |
18 | # Declare default permissions as read only.
19 | permissions: read-all
20 |
21 | jobs:
22 | build:
23 | runs-on: ubuntu-latest
24 | steps:
25 | - name: Setup Go
26 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
27 | with:
28 | go-version: "1.24"
29 |
30 | - name: Checkout base branch
31 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
32 | with:
33 | ref: ${{ github.base_ref }}
34 | - name: Calculate base code coverage
35 | run: |
36 | go test -short -coverprofile current_cover.out ./... || true
37 | export CUR_COVER=$(go tool cover -func current_cover.out | grep total | awk '{print substr($3, 1, length($3)-1)}')
38 | echo "CUR_COVER=$CUR_COVER" >> $GITHUB_ENV
39 |
40 | - name: Checkout PR branch
41 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
42 | - name: Calculate PR code coverage
43 | run: |
44 | go test -short -coverprofile pr_cover.out ./... || true
45 | export PR_COVER=$(go tool cover -func pr_cover.out | grep total | awk '{print substr($3, 1, length($3)-1)}')
46 | echo "PR_COVER=$PR_COVER" >> $GITHUB_ENV
47 |
48 | - name: Verify code coverage. If your reading this and the step has failed, please add tests to cover your changes.
49 | run: |
50 | go tool cover -func pr_cover.out
51 | if [ "${{ env.PR_COVER }}" -lt "${{ env.CUR_COVER }}" ]; then
52 | exit 1;
53 | fi
54 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2024 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 | name: docs
16 | on:
17 | pull_request:
18 |
19 | # Declare default permissions as read only.
20 | permissions: read-all
21 |
22 | jobs:
23 | docs:
24 | name: Check docs are up to date
25 | runs-on: ubuntu-latest
26 | steps:
27 | - name: Setup Go
28 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
29 | with:
30 | go-version: "1.24"
31 | - name: Checkout code
32 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
33 | - name: Generate docs and fail if there are differences
34 | run: |
35 | go install ./cmd/gendocs
36 | gendocs
37 | git diff --exit-code
38 |
--------------------------------------------------------------------------------
/.github/workflows/govulncheck.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | name: govulncheck
16 |
17 | # Declare default permissions as read only.
18 | permissions: read-all
19 |
20 | on:
21 | push:
22 | branches:
23 | - "main"
24 | - "v1"
25 | pull_request:
26 | branches:
27 | - "main"
28 | - "v1"
29 | schedule:
30 | - cron: "0 2 * * *"
31 |
32 | jobs:
33 | govulncheck_job:
34 | runs-on: ubuntu-latest
35 | name: Run govulncheck
36 | steps:
37 | - name: Setup Go
38 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
39 | with:
40 | go-version: "1.24"
41 | check-latest: true
42 | - name: Checkout code
43 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
44 | with:
45 | ref: ${{ github.event.pull_request.head.sha }}
46 | repository: ${{ github.event.pull_request.head.repo.full_name }}
47 | - id: govulncheck
48 | uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1
49 | with:
50 | # Let actions/checkout above check-out the correct SHA
51 | repo-checkout: false
52 |
--------------------------------------------------------------------------------
/.github/workflows/labels.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 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 | name: Sync labels
16 | on:
17 | push:
18 | branches:
19 | - main
20 |
21 | # Declare default permissions as read only.
22 | permissions: read-all
23 |
24 | jobs:
25 | build:
26 | runs-on: ubuntu-latest
27 | permissions:
28 | issues: write
29 | pull-requests: write
30 | steps:
31 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
32 | - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c # v1.3.0
33 | env:
34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 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 | name: lint
16 | on:
17 | pull_request:
18 |
19 | # Declare default permissions as read only.
20 | permissions: read-all
21 |
22 | jobs:
23 | lint:
24 | name: run lint
25 | runs-on: ubuntu-latest
26 | steps:
27 | - name: Setup Go
28 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
29 | with:
30 | go-version: "1.24"
31 | - name: Checkout code
32 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
33 | - name: >
34 | Verify go mod tidy. If you're reading this and the check has
35 | failed, run `goimports -w . && go mod tidy && golangci-lint run`
36 | run: |
37 | go mod tidy && git diff --exit-code
38 | - name: golangci-lint
39 | uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6.5.2
40 | with:
41 | version: latest
42 | args: --timeout 3m
43 |
--------------------------------------------------------------------------------
/.github/workflows/scorecard.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | name: OSSF Scorecard
16 | on:
17 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
18 | branch_protection_rule:
19 | schedule:
20 | # weekly on Sunday
21 | - cron: '0 20 * * 0'
22 | push:
23 | branches: [ "main" ]
24 |
25 | # Declare default permissions as read only.
26 | permissions: read-all
27 |
28 | jobs:
29 | analysis:
30 | name: Scorecard analysis
31 | runs-on: ubuntu-latest
32 | permissions:
33 | # Needed to upload the results to code-scanning dashboard.
34 | security-events: write
35 |
36 | steps:
37 | - name: "Checkout code"
38 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
39 | with:
40 | persist-credentials: false
41 |
42 | - name: "Run analysis"
43 | uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
44 | with:
45 | results_file: results.sarif
46 | results_format: sarif
47 |
48 | - name: Filter SARIF to skip false positives
49 | # filter out DangerousWorkflow alerts as they do not account for safe use of labels to trigger actions
50 | env:
51 | SCORECARD_SKIPPED_RULE_IDS: "DangerousWorkflowID"
52 | run: |
53 | SCORECARD_SKIPPED_RULE_IDS_JSON=$(echo $SCORECARD_SKIPPED_RULE_IDS | jq -cR 'split(",")')
54 | # Trim the SARIF file to remove false positive detections
55 | cat results.sarif | jq '.runs[].results |= map(select(.ruleId as $id | '$SCORECARD_SKIPPED_RULE_IDS_JSON' | all($id != .)))' > resultsFiltered.sarif
56 |
57 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
58 | # format to the repository Actions tab.
59 | - name: "Upload artifact"
60 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
61 | with:
62 | name: SARIF file
63 | path: results.sarif
64 | retention-days: 5
65 |
66 | # Upload the results to GitHub's code scanning dashboard.
67 | - name: "Upload to code-scanning"
68 | uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
69 | with:
70 | sarif_file: resultsFiltered.sarif
71 |
--------------------------------------------------------------------------------
/.github/workflows/v1-periodic.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 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 | name: v1 periodic
16 | on:
17 | schedule:
18 | - cron: "0 2 * * *"
19 |
20 | # Declare default permissions as read only.
21 | permissions: read-all
22 |
23 | jobs:
24 | integration:
25 | name: integration tests
26 | runs-on: ${{ matrix.os }}
27 | strategy:
28 | matrix:
29 | os: [macos-latest, windows-latest, ubuntu-latest]
30 | fail-fast: false
31 | permissions:
32 | contents: "read"
33 | id-token: "write"
34 | steps:
35 | - name: Checkout code
36 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
37 | with:
38 | ref: v1
39 |
40 | - name: Setup Go
41 | uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
42 | with:
43 | go-version: "1.24"
44 |
45 | - id: auth
46 | name: Authenticate to Google Cloud
47 | uses: google-github-actions/auth@ba79af03959ebeac9769e648f473a284504d9193 # v2.1.10
48 | with:
49 | workload_identity_provider: ${{ secrets.PROVIDER_NAME }}
50 | service_account: ${{ secrets.SERVICE_ACCOUNT }}
51 | access_token_lifetime: 600s
52 |
53 | - id: secrets
54 | name: Get secrets
55 | uses: google-github-actions/get-secretmanager-secrets@a8440875e1c2892062aef9061228d4f1af8f919b # v2.2.3
56 | with:
57 | secrets: |-
58 | MYSQL_CONNECTION_NAME:${{ secrets.GOOGLE_CLOUD_PROJECT }}/MYSQL_CONNECTION_NAME
59 | MYSQL_USER:${{ secrets.GOOGLE_CLOUD_PROJECT }}/MYSQL_USER
60 | MYSQL_PASS:${{ secrets.GOOGLE_CLOUD_PROJECT }}/MYSQL_PASS
61 | MYSQL_DB:${{ secrets.GOOGLE_CLOUD_PROJECT }}/MYSQL_DB
62 | POSTGRES_CONNECTION_NAME:${{ secrets.GOOGLE_CLOUD_PROJECT }}/POSTGRES_CONNECTION_NAME
63 | POSTGRES_USER:${{ secrets.GOOGLE_CLOUD_PROJECT }}/POSTGRES_USER
64 | POSTGRES_USER_IAM:${{ secrets.GOOGLE_CLOUD_PROJECT }}/POSTGRES_USER_IAM
65 | POSTGRES_PASS:${{ secrets.GOOGLE_CLOUD_PROJECT }}/POSTGRES_PASS
66 | POSTGRES_DB:${{ secrets.GOOGLE_CLOUD_PROJECT }}/POSTGRES_DB
67 | SQLSERVER_CONNECTION_NAME:${{ secrets.GOOGLE_CLOUD_PROJECT }}/SQLSERVER_CONNECTION_NAME
68 | SQLSERVER_USER:${{ secrets.GOOGLE_CLOUD_PROJECT }}/SQLSERVER_USER
69 | SQLSERVER_PASS:${{ secrets.GOOGLE_CLOUD_PROJECT }}/SQLSERVER_PASS
70 | SQLSERVER_DB:${{ secrets.GOOGLE_CLOUD_PROJECT }}/SQLSERVER_DB
71 |
72 | - name: Enable fuse config (Linux)
73 | if: runner.os == 'Linux'
74 | run: |
75 | sudo sed -i 's/#user_allow_other/user_allow_other/g' /etc/fuse.conf
76 |
77 | - name: Run tests
78 | env:
79 | GOOGLE_CLOUD_PROJECT: "${{ secrets.GOOGLE_CLOUD_PROJECT }}"
80 | MYSQL_CONNECTION_NAME: "${{ steps.secrets.outputs.MYSQL_CONNECTION_NAME }}"
81 | MYSQL_USER: "${{ steps.secrets.outputs.MYSQL_USER }}"
82 | MYSQL_PASS: "${{ steps.secrets.outputs.MYSQL_PASS }}"
83 | MYSQL_DB: "${{ steps.secrets.outputs.MYSQL_DB }}"
84 | POSTGRES_CONNECTION_NAME: "${{ steps.secrets.outputs.POSTGRES_CONNECTION_NAME }}"
85 | POSTGRES_USER: "${{ steps.secrets.outputs.POSTGRES_USER }}"
86 | POSTGRES_USER_IAM: "${{ steps.secrets.outputs.POSTGRES_USER_IAM }}"
87 | POSTGRES_PASS: "${{ steps.secrets.outputs.POSTGRES_PASS }}"
88 | POSTGRES_DB: "${{ steps.secrets.outputs.POSTGRES_DB }}"
89 | SQLSERVER_CONNECTION_NAME: "${{ steps.secrets.outputs.SQLSERVER_CONNECTION_NAME }}"
90 | SQLSERVER_USER: "${{ steps.secrets.outputs.SQLSERVER_USER }}"
91 | SQLSERVER_PASS: "${{ steps.secrets.outputs.SQLSERVER_PASS }}"
92 | SQLSERVER_DB: "${{ steps.secrets.outputs.SQLSERVER_DB }}"
93 | TMPDIR: "/tmp"
94 | TMP: "${{ runner.temp }}"
95 | # specifying bash shell ensures a failure in a piped process isn't lost by using `set -eo pipefail`
96 | shell: bash
97 | run: |
98 | go test -race -v ./... | tee test_results.txt
99 |
100 | - name: Convert test output to XML
101 | if: ${{ github.event_name == 'schedule' && always() }}
102 | run: |
103 | go install github.com/jstemmer/go-junit-report/v2@latest
104 | go-junit-report -in test_results.txt -set-exit-code -out v1periodic_sponge_log.xml
105 |
106 | - name: FlakyBot (Linux)
107 | # only run flakybot on periodic (schedule) event
108 | if: ${{ github.event_name == 'schedule' && runner.os == 'Linux' && always() }}
109 | run: |
110 | curl https://github.com/googleapis/repo-automation-bots/releases/download/flakybot-1.1.0/flakybot -o flakybot -s -L
111 | chmod +x ./flakybot
112 | ./flakybot --repo ${{github.repository}} --commit_hash ${{github.sha}} --build_url https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
113 |
114 | - name: FlakyBot (Windows)
115 | # only run flakybot on periodic (schedule) event
116 | if: ${{ github.event_name == 'schedule' && runner.os == 'Windows' && always() }}
117 | run: |
118 | curl https://github.com/googleapis/repo-automation-bots/releases/download/flakybot-1.1.0/flakybot.exe -o flakybot.exe -s -L
119 | ./flakybot.exe --repo ${{github.repository}} --commit_hash ${{github.sha}} --build_url https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
120 |
121 | - name: FlakyBot (macOS)
122 | # only run flakybot on periodic (schedule) event
123 | if: ${{ github.event_name == 'schedule' && runner.os == 'macOS' && always() }}
124 | run: |
125 | curl https://github.com/googleapis/repo-automation-bots/releases/download/flakybot-1.1.0/flakybot-darwin-amd64 -o flakybot -s -L
126 | chmod +x ./flakybot
127 | ./flakybot --repo ${{github.repository}} --commit_hash ${{github.sha}} --build_url https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
128 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # direnv
2 | .envrc
3 |
4 | # IDEs
5 | .idea/
6 | .vscode/
7 |
8 | /cloud-sql-proxy
9 | /cloud-sql-proxy.exe
10 |
11 | /key.json
12 | /logs/
13 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 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 | # .golangci.yml
15 | linters:
16 | disable-all: true
17 | enable:
18 | # From https://golangci-lint.run/usage/linters/
19 | # This is a minor deviation from the default linters
20 | - goimports
21 | - gosimple
22 | - govet
23 | - ineffassign
24 | - revive
25 | - staticcheck
26 | - unused
27 | issues:
28 | exclude-use-default: false
29 | linters-settings:
30 | revive:
31 | rules:
32 | # From https://revive.run/docs#recommended-configuration
33 | - name: blank-imports
34 | - name: context-as-argument
35 | - name: context-keys-type
36 | - name: dot-imports
37 | - name: empty-block
38 | - name: errorf
39 | - name: error-naming
40 | - name: error-return
41 | - name: error-strings
42 | - name: exported
43 | - name: if-return
44 | - name: import-shadowing
45 | - name: increment-decrement
46 | - name: indent-error-flow
47 | - name: range
48 | - name: range-val-address
49 | - name: range-val-in-closure
50 | - name: receiver-naming
51 | - name: redefines-builtin-id
52 | - name: superfluous-else
53 | - name: time-naming
54 | - name: unexported-return
55 | - name: unreachable-code
56 | - name: unused-parameter
57 | - name: var-declaration
58 | - name: var-naming
59 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project,
4 | and in the interest of fostering an open and welcoming community,
5 | we pledge to respect all people who contribute through reporting issues,
6 | posting feature requests, updating documentation,
7 | submitting pull requests or patches, and other activities.
8 |
9 | We are committed to making participation in this project
10 | a harassment-free experience for everyone,
11 | regardless of level of experience, gender, gender identity and expression,
12 | sexual orientation, disability, personal appearance,
13 | body size, race, ethnicity, age, religion, or nationality.
14 |
15 | Examples of unacceptable behavior by participants include:
16 |
17 | * The use of sexualized language or imagery
18 | * Personal attacks
19 | * Trolling or insulting/derogatory comments
20 | * Public or private harassment
21 | * Publishing other's private information,
22 | such as physical or electronic
23 | addresses, without explicit permission
24 | * Other unethical or unprofessional conduct.
25 |
26 | Project maintainers have the right and responsibility to remove, edit, or reject
27 | comments, commits, code, wiki edits, issues, and other contributions
28 | that are not aligned to this Code of Conduct.
29 | By adopting this Code of Conduct,
30 | project maintainers commit themselves to fairly and consistently
31 | applying these principles to every aspect of managing this project.
32 | Project maintainers who do not follow or enforce the Code of Conduct
33 | may be permanently removed from the project team.
34 |
35 | This code of conduct applies both within project spaces and in public spaces
36 | when an individual is representing the project or its community.
37 |
38 | Instances of abusive, harassing, or otherwise unacceptable behavior
39 | may be reported by opening an issue
40 | or contacting one or more of the project maintainers.
41 |
42 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
43 | available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
44 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project.
4 |
5 | ## Before you begin
6 |
7 | ### Sign our Contributor License Agreement
8 |
9 | Contributions to this project must be accompanied by a
10 | [Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
11 | You (or your employer) retain the copyright to your contribution; this simply
12 | gives us permission to use and redistribute your contributions as part of the
13 | project.
14 |
15 | If you or your current employer have already signed the Google CLA (even if it
16 | was for a different project), you probably don't need to do it again.
17 |
18 | Visit to see your current agreements or to
19 | sign a new one.
20 |
21 | ### Review our Community Guidelines
22 |
23 | This project follows
24 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/).
25 |
26 | ## Contribution process
27 |
28 | ### Code Reviews
29 |
30 | All submissions, including submissions by project members, require review. We
31 | use GitHub pull requests for this purpose. Consult
32 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
33 | information on using pull requests.
34 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright 2019 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 | # Use the latest stable golang 1.x to compile to a binary
16 | FROM --platform=$BUILDPLATFORM golang:1 as build
17 |
18 | WORKDIR /go/src/cloud-sql-proxy
19 | COPY . .
20 |
21 | ARG TARGETOS
22 | ARG TARGETARCH
23 |
24 | RUN go get ./...
25 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
26 | go build -ldflags "-X github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd.metadataString=container"
27 |
28 | # Final Stage
29 | FROM gcr.io/distroless/static:nonroot@sha256:188ddfb9e497f861177352057cb21913d840ecae6c843d39e00d44fa64daa51c
30 |
31 | LABEL org.opencontainers.image.source="https://github.com/GoogleCloudPlatform/cloud-sql-proxy"
32 |
33 | COPY --from=build --chown=nonroot /go/src/cloud-sql-proxy/cloud-sql-proxy /cloud-sql-proxy
34 | # set the uid as an integer for compatibility with runAsNonRoot in Kubernetes
35 | USER 65532
36 | ENTRYPOINT ["/cloud-sql-proxy"]
37 |
--------------------------------------------------------------------------------
/Dockerfile.alpine:
--------------------------------------------------------------------------------
1 | # Copyright 2020 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 | # Use the latest stable golang 1.x to compile to a binary
16 | FROM --platform=$BUILDPLATFORM golang:1-alpine as build
17 |
18 | WORKDIR /go/src/cloud-sql-proxy
19 | COPY . .
20 |
21 | ARG TARGETOS
22 | ARG TARGETARCH
23 |
24 | RUN go get ./...
25 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
26 | go build -ldflags "-X github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd.metadataString=container.alpine"
27 |
28 | # Final stage
29 | FROM alpine:3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
30 |
31 | LABEL org.opencontainers.image.source="https://github.com/GoogleCloudPlatform/cloud-sql-proxy"
32 |
33 | RUN apk add --no-cache \
34 | ca-certificates \
35 | libc6-compat
36 | # Install fuse and allow enable non-root users to mount
37 | RUN apk add --no-cache fuse && sed -i 's/^#user_allow_other$/user_allow_other/g' /etc/fuse.conf
38 | # Add a non-root user matching the nonroot user from the main container
39 | RUN addgroup -g 65532 -S nonroot && adduser -u 65532 -S nonroot -G nonroot
40 | # Set the uid as an integer for compatibility with runAsNonRoot in Kubernetes
41 | USER 65532
42 |
43 | COPY --from=build --chown=nonroot /go/src/cloud-sql-proxy/cloud-sql-proxy /cloud-sql-proxy
44 | ENTRYPOINT ["/cloud-sql-proxy"]
45 |
--------------------------------------------------------------------------------
/Dockerfile.bookworm:
--------------------------------------------------------------------------------
1 | # Copyright 2024 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 | # Use the latest stable golang 1.x to compile to a binary
16 | FROM --platform=$BUILDPLATFORM golang:1 as build
17 |
18 | WORKDIR /go/src/cloud-sql-proxy
19 | COPY . .
20 |
21 | ARG TARGETOS
22 | ARG TARGETARCH
23 |
24 | RUN go get ./...
25 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
26 | go build -ldflags "-X github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd.metadataString=container.bookworm"
27 |
28 | # Final stage
29 | FROM gcr.io/cloud-marketplace-containers/google/debian12@sha256:12e5f5b44ddf1814696b7814c4d3a180a58b34cdcbde554b57150468f3023c61
30 |
31 | LABEL org.opencontainers.image.source="https://github.com/GoogleCloudPlatform/cloud-sql-proxy"
32 |
33 | RUN apt-get update && apt-get install -y ca-certificates
34 | # Install fuse and allow enable non-root users to mount
35 | RUN apt-get update && apt-get install -y fuse && sed -i 's/^#user_allow_other$/user_allow_other/g' /etc/fuse.conf
36 | # Add a non-root user matching the nonroot user from the main container
37 | RUN groupadd -g 65532 -r nonroot && useradd -u 65532 -g 65532 -r nonroot
38 | # Set the uid as an integer for compatibility with runAsNonRoot in Kubernetes
39 | USER 65532
40 |
41 | COPY --from=build --chown=nonroot /go/src/cloud-sql-proxy/cloud-sql-proxy /cloud-sql-proxy
42 | ENTRYPOINT ["/cloud-sql-proxy"]
43 |
--------------------------------------------------------------------------------
/Dockerfile.bullseye:
--------------------------------------------------------------------------------
1 | # Copyright 2020 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 | # Use the latest stable golang 1.x to compile to a binary
16 | FROM --platform=$BUILDPLATFORM golang:1 as build
17 |
18 | WORKDIR /go/src/cloud-sql-proxy
19 | COPY . .
20 |
21 | ARG TARGETOS
22 | ARG TARGETARCH
23 |
24 | RUN go get ./...
25 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
26 | go build -ldflags "-X github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd.metadataString=container.bullseye"
27 |
28 | # Final stage
29 | FROM gcr.io/cloud-marketplace-containers/google/debian11@sha256:80b80968e1ad7e0d0769ecd6d14b4fbd009284049b2b7b12f2853758cc2ff337
30 |
31 | LABEL org.opencontainers.image.source="https://github.com/GoogleCloudPlatform/cloud-sql-proxy"
32 |
33 | RUN apt-get update && apt-get install -y ca-certificates
34 | # Install fuse and allow enable non-root users to mount
35 | RUN apt-get update && apt-get install -y fuse && sed -i 's/^#user_allow_other$/user_allow_other/g' /etc/fuse.conf
36 | # Add a non-root user matching the nonroot user from the main container
37 | RUN groupadd -g 65532 -r nonroot && useradd -u 65532 -g 65532 -r nonroot
38 | # Set the uid as an integer for compatibility with runAsNonRoot in Kubernetes
39 | USER 65532
40 |
41 | COPY --from=build --chown=nonroot /go/src/cloud-sql-proxy/cloud-sql-proxy /cloud-sql-proxy
42 | ENTRYPOINT ["/cloud-sql-proxy"]
43 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | To report a security issue, please use [g.co/vulnz](https://g.co/vulnz).
4 |
5 | The Google Security Team will respond within 5 working days of your report on g.co/vulnz.
6 |
7 | We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue.
8 |
--------------------------------------------------------------------------------
/cloudsql/cloudsql.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 cloudsql
16 |
17 | import (
18 | "context"
19 | "io"
20 | "net"
21 |
22 | "cloud.google.com/go/cloudsqlconn"
23 | )
24 |
25 | // Dialer dials a Cloud SQL instance and returns its database engine version.
26 | type Dialer interface {
27 | // Dial returns a connection to the specified instance.
28 | Dial(ctx context.Context, inst string, opts ...cloudsqlconn.DialOption) (net.Conn, error)
29 | // EngineVersion retrieves the provided instance's database version (e.g.,
30 | // POSTGRES_14)
31 | EngineVersion(ctx context.Context, inst string) (string, error)
32 |
33 | io.Closer
34 | }
35 |
36 | // Logger is the interface used throughout the project for logging.
37 | type Logger interface {
38 | // Debugf is for reporting additional information about internal operations.
39 | Debugf(format string, args ...interface{})
40 | // Infof is for reporting informational messages.
41 | Infof(format string, args ...interface{})
42 | // Errorf is for reporting errors.
43 | Errorf(format string, args ...interface{})
44 | }
45 |
--------------------------------------------------------------------------------
/cmd/config_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2024 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 | // https://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 cmd
16 |
17 | import (
18 | "testing"
19 | )
20 |
21 | func assert[T comparable](t *testing.T, want, got T) {
22 | t.Helper()
23 | if got != want {
24 | t.Errorf("got %v, want %v", got, want)
25 | }
26 | }
27 |
28 | func TestNewCommandWithConfigFile(t *testing.T) {
29 | tcs := []struct {
30 | desc string
31 | args []string
32 | setup func()
33 | assert func(t *testing.T, c *Command)
34 | }{
35 | {
36 | desc: "toml config file",
37 | args: []string{"--config-file", "testdata/config-toml.toml"},
38 | setup: func() {},
39 | assert: func(t *testing.T, c *Command) {
40 | assert(t, 1, len(c.conf.Instances))
41 | assert(t, true, c.conf.Debug)
42 | assert(t, 5555, c.conf.Port)
43 | assert(t, true, c.conf.DebugLogs)
44 | assert(t, true, c.conf.IAMAuthN)
45 | },
46 | },
47 | {
48 | desc: "yaml config file",
49 | args: []string{"--config-file", "testdata/config-yaml.yaml"},
50 | setup: func() {},
51 | assert: func(t *testing.T, c *Command) {
52 | assert(t, 1, len(c.conf.Instances))
53 | assert(t, true, c.conf.Debug)
54 | },
55 | },
56 | {
57 | desc: "json config file",
58 | args: []string{"--config-file", "testdata/config-json.json"},
59 | setup: func() {},
60 | assert: func(t *testing.T, c *Command) {
61 | assert(t, 1, len(c.conf.Instances))
62 | assert(t, true, c.conf.Debug)
63 | },
64 | },
65 | {
66 | desc: "config file with two instances",
67 | args: []string{"--config-file", "testdata/two-instances.toml"},
68 | setup: func() {},
69 | assert: func(t *testing.T, c *Command) {
70 | assert(t, 2, len(c.conf.Instances))
71 | },
72 | },
73 | {
74 | desc: "instance argument overrides env config precedence",
75 | args: []string{"proj:region:inst"},
76 | setup: func() {
77 | t.Setenv("CSQL_PROXY_INSTANCE_CONNECTION_NAME", "p:r:i")
78 | },
79 | assert: func(t *testing.T, c *Command) {
80 | assert(t, "proj:region:inst", c.conf.Instances[0].Name)
81 | },
82 | },
83 | {
84 | desc: "instance env overrides config file precedence",
85 | args: []string{"--config-file", "testdata/config.json"},
86 | setup: func() {
87 | t.Setenv("CSQL_PROXY_INSTANCE_CONNECTION_NAME", "p:r:i")
88 | },
89 | assert: func(t *testing.T, c *Command) {
90 | assert(t, "p:r:i", c.conf.Instances[0].Name)
91 | },
92 | },
93 | {
94 | desc: "flag overrides env config precedence",
95 | args: []string{"proj:region:inst", "--debug"},
96 | setup: func() {
97 | t.Setenv("CSQL_PROXY_DEBUG", "false")
98 | },
99 | assert: func(t *testing.T, c *Command) {
100 | assert(t, true, c.conf.Debug)
101 | },
102 | },
103 | {
104 | desc: "flag overrides config file precedence",
105 | args: []string{
106 | "proj:region:inst",
107 | "--config-file", "testdata/config.toml",
108 | "--debug",
109 | },
110 | setup: func() {},
111 | assert: func(t *testing.T, c *Command) {
112 | assert(t, true, c.conf.Debug)
113 | },
114 | },
115 | {
116 | desc: "env overrides config file precedence",
117 | args: []string{
118 | "proj:region:inst",
119 | "--config-file", "testdata/config.toml",
120 | },
121 | setup: func() {
122 | t.Setenv("CSQL_PROXY_DEBUG", "false")
123 | },
124 | assert: func(t *testing.T, c *Command) {
125 | assert(t, false, c.conf.Debug)
126 | },
127 | },
128 | }
129 |
130 | for _, tc := range tcs {
131 | t.Run(tc.desc, func(t *testing.T) {
132 | tc.setup()
133 |
134 | cmd, err := invokeProxyCommand(tc.args)
135 | if err != nil {
136 | t.Fatalf("want error = nil, got = %v", err)
137 | }
138 |
139 | tc.assert(t, cmd)
140 | })
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/cmd/errors.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 | // https://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 cmd
16 |
17 | import (
18 | "errors"
19 | )
20 |
21 | var (
22 | errSigInt = &exitError{
23 | Err: errors.New("SIGINT signal received"),
24 | Code: 130,
25 | }
26 |
27 | errSigTerm = &exitError{
28 | Err: errors.New("SIGTERM signal received"),
29 | Code: 143,
30 | }
31 |
32 | errSigTermZero = &exitError{
33 | Err: errors.New("SIGTERM signal received"),
34 | Code: 0,
35 | }
36 |
37 | errQuitQuitQuit = &exitError{
38 | Err: errors.New("/quitquitquit received request"),
39 | Code: 0, // This error guarantees a clean exit.
40 | }
41 | )
42 |
43 | func newBadCommandError(msg string) error {
44 | return &exitError{
45 | Err: errors.New(msg),
46 | Code: 1,
47 | }
48 | }
49 |
50 | // exitError is an error with an exit code, that's returned when the cmd exits.
51 | // When possible, try to match these conventions: https://tldp.org/LDP/abs/html/exitcodes.html
52 | type exitError struct {
53 | Code int
54 | Err error
55 | }
56 |
57 | func (e *exitError) Error() string {
58 | if e.Err == nil {
59 | return ""
60 | }
61 | return e.Err.Error()
62 | }
63 |
--------------------------------------------------------------------------------
/cmd/gendocs/gen_cloud-sql-proxy_docs.go:
--------------------------------------------------------------------------------
1 | // Copyright 2024 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 | // https://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 main
16 |
17 | import (
18 | "fmt"
19 | "os"
20 | "path/filepath"
21 |
22 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd"
23 | "github.com/spf13/cobra/doc"
24 | )
25 |
26 | func main() {
27 | if len(os.Args) > 2 {
28 | fmt.Fprintf(os.Stderr, "usage: %s [output directory]\n", os.Args[0])
29 | os.Exit(1)
30 | }
31 |
32 | path := "docs/cmd"
33 | if len(os.Args) == 2 {
34 | path = os.Args[1]
35 | }
36 |
37 | outDir, err := filepath.Abs(path)
38 | if err != nil {
39 | fmt.Fprintf(os.Stderr, "failed to get output directory: %v\n", err)
40 | os.Exit(1)
41 | }
42 |
43 | // Set environment variables used so the output is consistent,
44 | // regardless of where we run.
45 | os.Setenv("TMPDIR", "/tmp")
46 |
47 | cloudSQLProxy := cmd.NewCommand()
48 | cloudSQLProxy.Execute()
49 | cloudSQLProxy.Command.DisableAutoGenTag = true
50 | doc.GenMarkdownTree(cloudSQLProxy.Command, outDir)
51 | }
52 |
--------------------------------------------------------------------------------
/cmd/options.go:
--------------------------------------------------------------------------------
1 | // Copyright 2024 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 | // https://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 cmd
16 |
17 | import "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql"
18 |
19 | // Option is a function that configures a Command.
20 | type Option func(*Command)
21 |
22 | // WithLogger overrides the default logger.
23 | func WithLogger(l cloudsql.Logger) Option {
24 | return func(c *Command) {
25 | c.logger = l
26 | }
27 | }
28 |
29 | // WithDialer configures the Command to use the provided dialer to connect to
30 | // Cloud SQL instances.
31 | func WithDialer(d cloudsql.Dialer) Option {
32 | return func(c *Command) {
33 | c.dialer = d
34 | }
35 | }
36 |
37 | // WithFuseDir mounts a directory at the path using FUSE to access Cloud SQL
38 | // instances.
39 | func WithFuseDir(dir string) Option {
40 | return func(c *Command) {
41 | c.conf.FUSEDir = dir
42 | }
43 | }
44 |
45 | // WithFuseTempDir sets the temp directory where Unix sockets are created with
46 | // FUSE
47 | func WithFuseTempDir(dir string) Option {
48 | return func(c *Command) {
49 | c.conf.FUSETempDir = dir
50 | }
51 | }
52 |
53 | // WithMaxConnections sets the maximum allowed number of connections. Default
54 | // is no limit.
55 | func WithMaxConnections(mc uint64) Option {
56 | return func(c *Command) {
57 | c.conf.MaxConnections = mc
58 | }
59 | }
60 |
61 | // WithUserAgent sets additional user agents for Admin API tracking and should
62 | // be a space separated list of additional user agents, e.g.
63 | // cloud-sql-proxy-operator/0.0.1,other-agent/1.0.0
64 | func WithUserAgent(agent string) Option {
65 | return func(c *Command) {
66 | c.conf.OtherUserAgents = agent
67 | }
68 | }
69 |
70 | // WithAutoIP enables legacy behavior of v1 and will try to connect to first IP
71 | // address returned by the SQL Admin API. In most cases, this flag should not
72 | // be used. Prefer default of public IP or use --private-ip instead.`
73 | func WithAutoIP() Option {
74 | return func(c *Command) {
75 | c.conf.AutoIP = true
76 | }
77 | }
78 |
79 | // WithQuietLogging configures the Proxy to log error messages only.
80 | func WithQuietLogging() Option {
81 | return func(c *Command) {
82 | c.conf.Quiet = true
83 | }
84 | }
85 |
86 | // WithDebugLogging configures the Proxy to log debug level messages.
87 | func WithDebugLogging() Option {
88 | return func(c *Command) {
89 | c.conf.DebugLogs = true
90 | }
91 | }
92 |
93 | // WithLazyRefresh configures the Proxy to refresh connection info on an
94 | // as-needed basis when the cached copy has expired.
95 | func WithLazyRefresh() Option {
96 | return func(c *Command) {
97 | c.conf.LazyRefresh = true
98 | }
99 | }
100 |
101 | // WithConnRefuseNotify configures the Proxy to call the provided function when
102 | // a connection is refused. The notification function is run in a goroutine.
103 | func WithConnRefuseNotify(n func()) Option {
104 | return func(c *Command) {
105 | c.connRefuseNotify = n
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/cmd/options_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2024 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 | // https://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 cmd
16 |
17 | import (
18 | "errors"
19 | "fmt"
20 | "io"
21 | "runtime"
22 | "testing"
23 |
24 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql"
25 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log"
26 | "github.com/spf13/cobra"
27 | )
28 |
29 | type testDialer struct {
30 | cloudsql.Dialer
31 | }
32 |
33 | func TestCommandOptions(t *testing.T) {
34 | logger := log.NewStdLogger(io.Discard, io.Discard)
35 | dialer := &testDialer{}
36 | tcs := []struct {
37 | desc string
38 | isValid func(*Command) error
39 | option Option
40 | skip bool
41 | }{
42 | {
43 | desc: "with logger",
44 | isValid: func(c *Command) error {
45 | if c.logger != logger {
46 | return errors.New("loggers do not match")
47 | }
48 | return nil
49 | },
50 | option: WithLogger(logger),
51 | },
52 | {
53 | desc: "with dialer",
54 | isValid: func(c *Command) error {
55 | if c.dialer != dialer {
56 | return errors.New("dialers do not match")
57 | }
58 | return nil
59 | },
60 | option: WithDialer(dialer),
61 | },
62 | {
63 | desc: "with FUSE dir",
64 | isValid: func(c *Command) error {
65 | if c.conf.FUSEDir != "somedir" {
66 | return fmt.Errorf(
67 | "want = %v, got = %v", "somedir", c.conf.FUSEDir,
68 | )
69 | }
70 | return nil
71 | },
72 | option: WithFuseDir("somedir"),
73 | // FUSE isn't available on GitHub macOS runners
74 | // and FUSE isn't supported on Windows, so skip this test.
75 | skip: runtime.GOOS == "darwin" || runtime.GOOS == "windows",
76 | },
77 | {
78 | desc: "with FUSE temp dir",
79 | isValid: func(c *Command) error {
80 | if c.conf.FUSETempDir != "somedir" {
81 | return fmt.Errorf(
82 | "want = %v, got = %v", "somedir", c.conf.FUSEDir,
83 | )
84 | }
85 | return nil
86 | },
87 | option: WithFuseTempDir("somedir"),
88 | // FUSE isn't available on GitHub macOS runners
89 | // and FUSE isn't supported on Windows, so skip this test.
90 | skip: runtime.GOOS == "darwin" || runtime.GOOS == "windows",
91 | },
92 | {
93 | desc: "with max connections",
94 | isValid: func(c *Command) error {
95 | if c.conf.MaxConnections != 1 {
96 | return fmt.Errorf(
97 | "want = %v, got = %v", 1, c.conf.MaxConnections,
98 | )
99 | }
100 | return nil
101 | },
102 | option: WithMaxConnections(1),
103 | },
104 | {
105 | desc: "with user agent",
106 | isValid: func(c *Command) error {
107 | if c.conf.OtherUserAgents != "agents-go-here" {
108 | return fmt.Errorf(
109 | "want = %v, got = %v",
110 | "agents-go-here", c.conf.OtherUserAgents,
111 | )
112 | }
113 | return nil
114 | },
115 | option: WithUserAgent("agents-go-here"),
116 | },
117 | {
118 | desc: "with auto IP",
119 | isValid: func(c *Command) error {
120 | if !c.conf.AutoIP {
121 | return errors.New("auto IP was false, but should be true")
122 | }
123 | return nil
124 | },
125 | option: WithAutoIP(),
126 | },
127 | {
128 | desc: "with quiet logging",
129 | isValid: func(c *Command) error {
130 | if !c.conf.Quiet {
131 | return errors.New("quiet was false, but should be true")
132 | }
133 | return nil
134 | },
135 | option: WithQuietLogging(),
136 | },
137 | {
138 | desc: "with lazy refresh",
139 | isValid: func(c *Command) error {
140 | if !c.conf.LazyRefresh {
141 | return errors.New(
142 | "LazyRefresh was false, but should be true",
143 | )
144 | }
145 | return nil
146 | },
147 | option: WithLazyRefresh(),
148 | },
149 | }
150 |
151 | for _, tc := range tcs {
152 | t.Run(tc.desc, func(t *testing.T) {
153 | if tc.skip {
154 | t.Skip("skipping unsupported test case")
155 | }
156 | got, err := invokeProxyWithOption(nil, tc.option)
157 | if err != nil {
158 | t.Fatal(err)
159 | }
160 | if err := tc.isValid(got); err != nil {
161 | t.Errorf("option did not initialize command correctly: %v", err)
162 | }
163 | })
164 | }
165 | }
166 |
167 | func TestCommandOptionsOverridesCLI(t *testing.T) {
168 | tcs := []struct {
169 | desc string
170 | isValid func(*Command) error
171 | option Option
172 | args []string
173 | }{
174 | {
175 | desc: "with duplicate max connections",
176 | isValid: func(c *Command) error {
177 | if c.conf.MaxConnections != 10 {
178 | return errors.New("max connections do not match")
179 | }
180 | return nil
181 | },
182 | option: WithMaxConnections(10),
183 | args: []string{"--max-connections", "20"},
184 | },
185 | {
186 | desc: "with quiet logging",
187 | isValid: func(c *Command) error {
188 | if !c.conf.Quiet {
189 | return errors.New("quiet was false, but should be true")
190 | }
191 | return nil
192 | },
193 | option: WithQuietLogging(),
194 | args: []string{"--quiet", "false"},
195 | },
196 | }
197 | for _, tc := range tcs {
198 | t.Run(tc.desc, func(t *testing.T) {
199 | got, err := invokeProxyWithOption(tc.args, tc.option)
200 | if err != nil {
201 | t.Fatal(err)
202 | }
203 | if err := tc.isValid(got); err != nil {
204 | t.Errorf("option did not initialize command correctly: %v", err)
205 | }
206 | })
207 | }
208 | }
209 |
210 | func invokeProxyWithOption(args []string, o Option) (*Command, error) {
211 | c := NewCommand(o)
212 | // Keep the test output quiet
213 | c.SilenceUsage = true
214 | c.SilenceErrors = true
215 | // Disable execute behavior
216 | c.RunE = func(*cobra.Command, []string) error {
217 | return nil
218 | }
219 | args = append(args, "test-project:us-central1:test-instance")
220 | c.SetArgs(args)
221 |
222 | err := c.Execute()
223 |
224 | return c, err
225 | }
226 |
--------------------------------------------------------------------------------
/cmd/root_linux_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 cmd
16 |
17 | import (
18 | "context"
19 | "net"
20 | "os"
21 | "path/filepath"
22 | "testing"
23 | "time"
24 |
25 | "github.com/coreos/go-systemd/v22/daemon"
26 | "github.com/spf13/cobra"
27 | )
28 |
29 | func TestNewCommandArgumentsOnLinux(t *testing.T) {
30 | defaultTmp := filepath.Join(os.TempDir(), "csql-tmp")
31 | tcs := []struct {
32 | desc string
33 | args []string
34 | wantDir string
35 | wantTempDir string
36 | }{
37 | {
38 | desc: "using the fuse flag",
39 | args: []string{"--fuse", "/cloudsql"},
40 | wantDir: "/cloudsql",
41 | wantTempDir: defaultTmp,
42 | },
43 | {
44 | desc: "using the fuse temporary directory flag",
45 | args: []string{"--fuse", "/cloudsql", "--fuse-tmp-dir", "/mycooldir"},
46 | wantDir: "/cloudsql",
47 | wantTempDir: "/mycooldir",
48 | },
49 | }
50 |
51 | for _, tc := range tcs {
52 | t.Run(tc.desc, func(t *testing.T) {
53 | c := NewCommand()
54 | // Keep the test output quiet
55 | c.SilenceUsage = true
56 | c.SilenceErrors = true
57 | // Disable execute behavior
58 | c.RunE = func(*cobra.Command, []string) error {
59 | return nil
60 | }
61 | c.SetArgs(tc.args)
62 |
63 | err := c.Execute()
64 | if err != nil {
65 | t.Fatalf("want error = nil, got = %v", err)
66 | }
67 |
68 | if got, want := c.conf.FUSEDir, tc.wantDir; got != want {
69 | t.Fatalf("FUSEDir: want = %v, got = %v", want, got)
70 | }
71 |
72 | if got, want := c.conf.FUSETempDir, tc.wantTempDir; got != want {
73 | t.Fatalf("FUSEDir: want = %v, got = %v", want, got)
74 | }
75 | })
76 | }
77 | }
78 |
79 | func TestSdNotifyOnLinux(t *testing.T) {
80 | tcs := []struct {
81 | desc string
82 | proxyMustFail bool
83 | notifyState string
84 | }{
85 | {
86 | desc: "System with systemd Type=notify and proxy started successfully",
87 | proxyMustFail: false,
88 | notifyState: daemon.SdNotifyReady,
89 | },
90 | {
91 | desc: "System with systemd Type=notify and proxy failed to start",
92 | proxyMustFail: true,
93 | notifyState: daemon.SdNotifyStopping,
94 | },
95 | }
96 |
97 | // Create a temp dir for the socket file.
98 | testDir, err := os.MkdirTemp("/tmp/", "test-")
99 | if err != nil {
100 | t.Fatalf("Fail to create the temp dir: %v", err)
101 | }
102 | defer os.RemoveAll(testDir)
103 |
104 | //Set up the socket stream to listen for notifications.
105 | socketAddr := filepath.Join(testDir, "notify-socket.sock")
106 | conn, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: socketAddr, Net: "unixgram"})
107 | if err != nil {
108 | t.Fatalf("net.ListenUnixgram error: %v", err)
109 | }
110 |
111 | // To simulate systemd behavior with Type=notify, set NOTIFY_SOCKET
112 | // to the name of the socket that listens for notifications.
113 | os.Setenv("NOTIFY_SOCKET", socketAddr)
114 | defer os.Unsetenv("NOTIFY_SOCKET")
115 |
116 | s := &spyDialer{}
117 | c := NewCommand(WithDialer(s))
118 | // Keep the test output quiet
119 | c.SilenceUsage = false
120 | c.SilenceErrors = false
121 | c.SetArgs([]string{"my-project:my-region:my-instance"})
122 |
123 | ctx, cancel := context.WithTimeout(context.Background(), time.Second)
124 | defer cancel()
125 |
126 | for _, tc := range tcs {
127 | t.Run(tc.desc, func(t *testing.T) {
128 |
129 | if tc.proxyMustFail {
130 | c.conf.FUSEDir = "invalid"
131 | }
132 |
133 | go c.ExecuteContext(ctx)
134 |
135 | stateReceived := make([]byte, 4096)
136 | length, _, err := conn.ReadFromUnix(stateReceived)
137 | if err != nil {
138 | t.Fatalf("conn.ReadFromUnix error: %s\n", err)
139 | }
140 | if string(stateReceived[0:length]) != tc.notifyState {
141 | t.Fatalf("Expected Notify State %v, got %v", tc.notifyState, string(stateReceived))
142 | }
143 |
144 | })
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/cmd/root_windows_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 cmd
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/spf13/cobra"
21 | )
22 |
23 | func TestWindowsDoesNotSupportFUSE(t *testing.T) {
24 | c := NewCommand()
25 | // Keep the test output quiet
26 | c.SilenceUsage = true
27 | c.SilenceErrors = true
28 | // Disable execute behavior
29 | c.RunE = func(*cobra.Command, []string) error { return nil }
30 | c.SetArgs([]string{"--fuse"})
31 |
32 | err := c.Execute()
33 | if err == nil {
34 | t.Fatal("want error != nil, got = nil")
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/cmd/testdata/config-json.json:
--------------------------------------------------------------------------------
1 | {
2 | "instance-connection-name": "proj:region:inst",
3 | "debug": true
4 | }
5 |
--------------------------------------------------------------------------------
/cmd/testdata/config-toml.toml:
--------------------------------------------------------------------------------
1 | instance-connection-name = "proj:region:inst"
2 | debug = true
3 | port = "5555"
4 | debug-logs = true
5 | auto-iam-authn = true
6 |
--------------------------------------------------------------------------------
/cmd/testdata/config-yaml.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2024 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 | instance-connection-name: "proj:region:inst"
16 | debug: true
17 |
--------------------------------------------------------------------------------
/cmd/testdata/two-instances.toml:
--------------------------------------------------------------------------------
1 | instance-connection-name-0 = "x:y:z"
2 | instance-connection-name-1 = "a:b:c"
3 |
--------------------------------------------------------------------------------
/cmd/version.txt:
--------------------------------------------------------------------------------
1 | 2.16.0
2 |
--------------------------------------------------------------------------------
/cmd/wait_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 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 cmd
16 |
17 | import (
18 | "io"
19 | "net"
20 | "testing"
21 | "time"
22 | )
23 |
24 | func TestWaitCommandFlags(t *testing.T) {
25 | ln, err := net.Listen("tcp", "localhost:0")
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 | defer ln.Close()
30 | host, port, err := net.SplitHostPort(ln.Addr().String())
31 | if err != nil {
32 | t.Fatal(err)
33 | }
34 | go func() {
35 | conn, err := ln.Accept()
36 | if err != nil {
37 | return
38 | }
39 | defer conn.Close()
40 | // Use a read deadline to produce read error
41 | conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
42 | // Read client request first.
43 | io.ReadAll(conn)
44 | // Write a generic 200 response back.
45 | conn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
46 | }()
47 |
48 | _, err = invokeProxyCommand([]string{
49 | "wait",
50 | "--http-address", host,
51 | "--http-port", port,
52 | "--max=1s",
53 | })
54 | if err != nil {
55 | t.Fatal(err)
56 | }
57 | }
58 |
59 | func TestWaitCommandFails(t *testing.T) {
60 | _, err := invokeProxyCommand([]string{
61 | "wait",
62 | // assuming default host and port
63 | "--max=100ms",
64 | })
65 | if err == nil {
66 | t.Fatal("wait should fail when endpoint does not respond")
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/docs/cmd/cloud-sql-proxy_completion.md:
--------------------------------------------------------------------------------
1 | ## cloud-sql-proxy completion
2 |
3 | Generate the autocompletion script for the specified shell
4 |
5 | ### Synopsis
6 |
7 | Generate the autocompletion script for cloud-sql-proxy for the specified shell.
8 | See each sub-command's help for details on how to use the generated script.
9 |
10 |
11 | ### Options
12 |
13 | ```
14 | -h, --help help for completion
15 | ```
16 |
17 | ### Options inherited from parent commands
18 |
19 | ```
20 | --http-address string Address for Prometheus and health check server (default "localhost")
21 | --http-port string Port for Prometheus and health check server (default "9090")
22 | ```
23 |
24 | ### SEE ALSO
25 |
26 | * [cloud-sql-proxy](cloud-sql-proxy.md) - cloud-sql-proxy authorizes and encrypts connections to Cloud SQL.
27 | * [cloud-sql-proxy completion bash](cloud-sql-proxy_completion_bash.md) - Generate the autocompletion script for bash
28 | * [cloud-sql-proxy completion fish](cloud-sql-proxy_completion_fish.md) - Generate the autocompletion script for fish
29 | * [cloud-sql-proxy completion powershell](cloud-sql-proxy_completion_powershell.md) - Generate the autocompletion script for powershell
30 | * [cloud-sql-proxy completion zsh](cloud-sql-proxy_completion_zsh.md) - Generate the autocompletion script for zsh
31 |
32 |
--------------------------------------------------------------------------------
/docs/cmd/cloud-sql-proxy_completion_bash.md:
--------------------------------------------------------------------------------
1 | ## cloud-sql-proxy completion bash
2 |
3 | Generate the autocompletion script for bash
4 |
5 | ### Synopsis
6 |
7 | Generate the autocompletion script for the bash shell.
8 |
9 | This script depends on the 'bash-completion' package.
10 | If it is not installed already, you can install it via your OS's package manager.
11 |
12 | To load completions in your current shell session:
13 |
14 | source <(cloud-sql-proxy completion bash)
15 |
16 | To load completions for every new session, execute once:
17 |
18 | #### Linux:
19 |
20 | cloud-sql-proxy completion bash > /etc/bash_completion.d/cloud-sql-proxy
21 |
22 | #### macOS:
23 |
24 | cloud-sql-proxy completion bash > $(brew --prefix)/etc/bash_completion.d/cloud-sql-proxy
25 |
26 | You will need to start a new shell for this setup to take effect.
27 |
28 |
29 | ```
30 | cloud-sql-proxy completion bash
31 | ```
32 |
33 | ### Options
34 |
35 | ```
36 | -h, --help help for bash
37 | --no-descriptions disable completion descriptions
38 | ```
39 |
40 | ### Options inherited from parent commands
41 |
42 | ```
43 | --http-address string Address for Prometheus and health check server (default "localhost")
44 | --http-port string Port for Prometheus and health check server (default "9090")
45 | ```
46 |
47 | ### SEE ALSO
48 |
49 | * [cloud-sql-proxy completion](cloud-sql-proxy_completion.md) - Generate the autocompletion script for the specified shell
50 |
51 |
--------------------------------------------------------------------------------
/docs/cmd/cloud-sql-proxy_completion_fish.md:
--------------------------------------------------------------------------------
1 | ## cloud-sql-proxy completion fish
2 |
3 | Generate the autocompletion script for fish
4 |
5 | ### Synopsis
6 |
7 | Generate the autocompletion script for the fish shell.
8 |
9 | To load completions in your current shell session:
10 |
11 | cloud-sql-proxy completion fish | source
12 |
13 | To load completions for every new session, execute once:
14 |
15 | cloud-sql-proxy completion fish > ~/.config/fish/completions/cloud-sql-proxy.fish
16 |
17 | You will need to start a new shell for this setup to take effect.
18 |
19 |
20 | ```
21 | cloud-sql-proxy completion fish [flags]
22 | ```
23 |
24 | ### Options
25 |
26 | ```
27 | -h, --help help for fish
28 | --no-descriptions disable completion descriptions
29 | ```
30 |
31 | ### Options inherited from parent commands
32 |
33 | ```
34 | --http-address string Address for Prometheus and health check server (default "localhost")
35 | --http-port string Port for Prometheus and health check server (default "9090")
36 | ```
37 |
38 | ### SEE ALSO
39 |
40 | * [cloud-sql-proxy completion](cloud-sql-proxy_completion.md) - Generate the autocompletion script for the specified shell
41 |
42 |
--------------------------------------------------------------------------------
/docs/cmd/cloud-sql-proxy_completion_powershell.md:
--------------------------------------------------------------------------------
1 | ## cloud-sql-proxy completion powershell
2 |
3 | Generate the autocompletion script for powershell
4 |
5 | ### Synopsis
6 |
7 | Generate the autocompletion script for powershell.
8 |
9 | To load completions in your current shell session:
10 |
11 | cloud-sql-proxy completion powershell | Out-String | Invoke-Expression
12 |
13 | To load completions for every new session, add the output of the above command
14 | to your powershell profile.
15 |
16 |
17 | ```
18 | cloud-sql-proxy completion powershell [flags]
19 | ```
20 |
21 | ### Options
22 |
23 | ```
24 | -h, --help help for powershell
25 | --no-descriptions disable completion descriptions
26 | ```
27 |
28 | ### Options inherited from parent commands
29 |
30 | ```
31 | --http-address string Address for Prometheus and health check server (default "localhost")
32 | --http-port string Port for Prometheus and health check server (default "9090")
33 | ```
34 |
35 | ### SEE ALSO
36 |
37 | * [cloud-sql-proxy completion](cloud-sql-proxy_completion.md) - Generate the autocompletion script for the specified shell
38 |
39 |
--------------------------------------------------------------------------------
/docs/cmd/cloud-sql-proxy_completion_zsh.md:
--------------------------------------------------------------------------------
1 | ## cloud-sql-proxy completion zsh
2 |
3 | Generate the autocompletion script for zsh
4 |
5 | ### Synopsis
6 |
7 | Generate the autocompletion script for the zsh shell.
8 |
9 | If shell completion is not already enabled in your environment you will need
10 | to enable it. You can execute the following once:
11 |
12 | echo "autoload -U compinit; compinit" >> ~/.zshrc
13 |
14 | To load completions in your current shell session:
15 |
16 | source <(cloud-sql-proxy completion zsh)
17 |
18 | To load completions for every new session, execute once:
19 |
20 | #### Linux:
21 |
22 | cloud-sql-proxy completion zsh > "${fpath[1]}/_cloud-sql-proxy"
23 |
24 | #### macOS:
25 |
26 | cloud-sql-proxy completion zsh > $(brew --prefix)/share/zsh/site-functions/_cloud-sql-proxy
27 |
28 | You will need to start a new shell for this setup to take effect.
29 |
30 |
31 | ```
32 | cloud-sql-proxy completion zsh [flags]
33 | ```
34 |
35 | ### Options
36 |
37 | ```
38 | -h, --help help for zsh
39 | --no-descriptions disable completion descriptions
40 | ```
41 |
42 | ### Options inherited from parent commands
43 |
44 | ```
45 | --http-address string Address for Prometheus and health check server (default "localhost")
46 | --http-port string Port for Prometheus and health check server (default "9090")
47 | ```
48 |
49 | ### SEE ALSO
50 |
51 | * [cloud-sql-proxy completion](cloud-sql-proxy_completion.md) - Generate the autocompletion script for the specified shell
52 |
53 |
--------------------------------------------------------------------------------
/docs/cmd/cloud-sql-proxy_wait.md:
--------------------------------------------------------------------------------
1 | ## cloud-sql-proxy wait
2 |
3 | Wait for another Proxy process to start
4 |
5 | ### Synopsis
6 |
7 |
8 | Waiting for Proxy Startup
9 |
10 | Sometimes it is necessary to wait for the Proxy to start.
11 |
12 | To help ensure the Proxy is up and ready, the Proxy includes a wait
13 | subcommand with an optional --max flag to set the maximum time to wait.
14 | The wait command uses a separate Proxy's startup endpoint to determine
15 | if the other Proxy process is ready.
16 |
17 | Invoke the wait command, like this:
18 |
19 | # waits for another Proxy process' startup endpoint to respond
20 | ./cloud-sql-proxy wait
21 |
22 | Configuration
23 |
24 | By default, the Proxy will wait up to the maximum time for the startup
25 | endpoint to respond. The wait command requires that the Proxy be started in
26 | another process with the HTTP health check or Prometheus enabled. If an
27 | alternate health check port or address is used, as in:
28 |
29 | ./cloud-sql-proxy \
30 | --http-address 0.0.0.0 \
31 | --http-port 9191 \
32 | --health-check
33 |
34 | Then the wait command must also be told to use the same custom values:
35 |
36 | ./cloud-sql-proxy wait \
37 | --http-address 0.0.0.0 \
38 | --http-port 9191
39 |
40 | By default the wait command will wait 30 seconds. To alter this value,
41 | use:
42 |
43 | ./cloud-sql-proxy wait --max 10s
44 |
45 |
46 | ```
47 | cloud-sql-proxy wait [flags]
48 | ```
49 |
50 | ### Options
51 |
52 | ```
53 | -h, --help help for wait
54 | -m, --max duration maximum amount of time to wait for startup (default 30s)
55 | ```
56 |
57 | ### Options inherited from parent commands
58 |
59 | ```
60 | --http-address string Address for Prometheus and health check server (default "localhost")
61 | --http-port string Port for Prometheus and health check server (default "9090")
62 | ```
63 |
64 | ### SEE ALSO
65 |
66 | * [cloud-sql-proxy](cloud-sql-proxy.md) - cloud-sql-proxy authorizes and encrypts connections to Cloud SQL.
67 |
68 |
--------------------------------------------------------------------------------
/examples/disaster-recovery/README.md:
--------------------------------------------------------------------------------
1 | # Coordinate disaster recovery with Secret Manager
2 |
3 | ## Background
4 |
5 | This document assumes you are already using the following strategy for
6 | detecting and triggering failovers:
7 | 1. Using an independent service to detect when the primary is down
8 | 2. Trigger a promotion of an existing read replica to become a primary
9 | 3. Update a Secret Manager secret with the name of the current primary
10 |
11 | ## Restart Auth Proxy when secret changes
12 |
13 | This option uses a wrapper script around the Cloud SQL Auth Proxy to detect
14 | when the secret has changed, and restart the Proxy with the new value. This
15 | could be done in many languages, but here’s an example using bash:
16 |
17 | > [failover.sh](examples/disaster-recovery/failover.sh)
18 | ```sh
19 | #! /bin/bash
20 |
21 | SECRET_ID="my-secret-id" # TODO(developer): replace this value
22 | REFRESH_INTERVAL=5
23 | PORT=5432
24 |
25 | # Get the latest version of the secret and start the Proxy
26 | INSTANCE=$(gcloud secrets versions access "latest" --secret="$SECRET_ID")
27 | cloud-sql-proxy --port "$PORT" "$INSTANCE" &
28 | PID=$!
29 |
30 | # Every 5s, get the latest version of the secret. If it's changed, restart the
31 | # Proxy with the new value.
32 | while true; do
33 | sleep $REFRESH_INTERVAL
34 | NEW=$(gcloud secrets versions access "latest" --secret="$SECRET_ID")
35 | if [ "$INSTANCE" != "$NEW" ]; then
36 | INSTANCE=$NEW
37 | kill $PID
38 | wait $PID
39 | cloud-sql-proxy --port "$PORT" "$INSTANCE" &
40 | PID=$!
41 | fi
42 | done
43 | ```
44 |
45 | ## Benefits of this approach
46 |
47 | Using this approach will help assist with failovers without needing to
48 | reconfigure your application. Instead, by changing the Proxy the application
49 | will always connect to 127.0.0.1 and won’t need to restart to apply
50 | configuration changes. Additionally, it will prevent split brain syndrome by
51 | ensuring that your application can only connect to the current “primary”.
52 |
--------------------------------------------------------------------------------
/examples/disaster-recovery/failover.sh:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | # [START cloud_sql_proxy_secret_manager_failover]
16 | #! /bin/bash
17 |
18 | SECRET_ID="my-secret-id" # TODO(developer): replace this value
19 | PORT=5432
20 |
21 | # Get the latest version of the secret and start the proxy
22 | INSTANCE=$(gcloud secrets versions access "latest" --secret="$SECRET_ID")
23 | cloud-sql-proxy --port "$PORT" "$INSTANCE" &
24 | PID=$!
25 |
26 | # Every 5s, get the latest version of the secret. If it's changed, restart the
27 | # proxy with the new value.
28 | while true; do
29 | sleep 5
30 | NEW=$(gcloud secrets versions access "latest" --secret="$SECRET_ID")
31 | if [ "$INSTANCE" != "$NEW" ]; then
32 | INSTANCE=$NEW
33 | kill $PID
34 | wait $PID
35 | cloud-sql-proxy --port "$PORT" "$INSTANCE" &
36 | PID=$!
37 | fi
38 | done
39 | # [END cloud_sql_proxy_secret_manager_failover]
40 |
41 |
--------------------------------------------------------------------------------
/examples/k8s-service/ca_csr.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosts": [],
3 | "key": {
4 | "algo": "rsa",
5 | "size": 2048
6 | },
7 | "names": [
8 | {
9 | "C": "US",
10 | "L": "Boulder",
11 | "O": "My Cool Self-Signing Certificate Authority",
12 | "OU": "WWW",
13 | "ST": "Colorado"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/examples/k8s-service/deployment.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 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 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | name:
19 | spec:
20 | replicas: 5
21 | selector:
22 | matchLabels:
23 | app:
24 | template:
25 | metadata:
26 | labels:
27 | app:
28 | spec:
29 | serviceAccountName:
30 | volumes:
31 | - name: cacert
32 | secret:
33 | secretName:
34 | items:
35 | - key: tls.crt
36 | path: cert.pem
37 | containers:
38 | - name:
39 | image:
40 | ports:
41 | - containerPort: 8080
42 | volumeMounts:
43 | - name: cacert
44 | mountPath: "/etc/ca"
45 | readOnly: true
46 | env:
47 | - name: DB_HOST
48 | value: ".default.svc.cluster.local" # using the "default" namespace
49 | - name: DB_USER
50 | valueFrom:
51 | secretKeyRef:
52 | name:
53 | key: username
54 | - name: DB_PASS
55 | valueFrom:
56 | secretKeyRef:
57 | name:
58 | key: password
59 | - name: DB_NAME
60 | valueFrom:
61 | secretKeyRef:
62 | name:
63 | key: database
64 | - name: DB_PORT
65 | value: "5432"
66 | - name: CA_CERT
67 | value: "/etc/ca/cert.pem"
68 |
--------------------------------------------------------------------------------
/examples/k8s-service/pgbouncer_deployment.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 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 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | name:
19 | spec:
20 | selector:
21 | matchLabels:
22 | app:
23 | template:
24 | metadata:
25 | labels:
26 | app:
27 | spec:
28 | serviceAccountName:
29 | volumes:
30 | - name: cacert
31 | secret:
32 | secretName:
33 | items:
34 | - key: tls.crt
35 | path: cert.pem
36 | - name: servercert
37 | secret:
38 | secretName:
39 | items:
40 | - key: tls.crt
41 | path: cert.pem
42 | - key: tls.key
43 | path: key.pem
44 | containers:
45 | - name: pgbouncer
46 | image:
47 | ports:
48 | - containerPort: 5432
49 | volumeMounts:
50 | - name: cacert
51 | mountPath: "/etc/ca"
52 | readOnly: true
53 | - name: servercert
54 | mountPath: "/etc/server"
55 | readOnly: true
56 | env:
57 | - name: DB_HOST
58 | value: "127.0.0.1"
59 | - name: DB_USER
60 | valueFrom:
61 | secretKeyRef:
62 | name:
63 | key: username
64 | - name: DB_PASSWORD
65 | valueFrom:
66 | secretKeyRef:
67 | name:
68 | key: password
69 | - name: DB_NAME
70 | valueFrom:
71 | secretKeyRef:
72 | name:
73 | key: database
74 | - name: DB_PORT
75 | value: "5431"
76 | - name: CLIENT_TLS_SSLMODE
77 | value: "require"
78 | - name: CLIENT_TLS_CA_FILE
79 | value: "/etc/ca/cert.pem"
80 | - name: CLIENT_TLS_KEY_FILE
81 | value: "/etc/server/key.pem"
82 | - name: CLIENT_TLS_CERT_FILE
83 | value: "/etc/server/cert.pem"
84 | initContainers:
85 | - name: cloud-sql-proxy
86 | restartPolicy: Always
87 | # It is recommended to use the latest version of the Cloud SQL Auth Proxy
88 | # Make sure to update on a regular schedule!
89 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.11.4
90 | args:
91 | - "--port=5431"
92 | - ""
93 | securityContext:
94 | runAsNonRoot: true
95 |
--------------------------------------------------------------------------------
/examples/k8s-service/pgbouncer_service.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 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 | apiVersion: v1
16 | kind: Service
17 | metadata:
18 | name:
19 | spec:
20 | selector:
21 | app:
22 | ports:
23 | - protocol: TCP
24 | port: 5432
25 | targetPort: 5432
26 |
--------------------------------------------------------------------------------
/examples/k8s-service/server_csr.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosts": [
3 | "pgbouncersvc.default.svc.cluster.local",
4 | "localhost"
5 | ],
6 | "key": {
7 | "algo": "rsa",
8 | "size": 2048
9 | },
10 | "names": [
11 | {
12 | "C": "US",
13 | "L": "Boulder",
14 | "O": "My Cool Kubernetes Cluster",
15 | "OU": "WWW",
16 | "ST": "Colorado"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/examples/k8s-sidecar/job_with_shutdown_hook.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | ###
16 | # This demonstrates how to configure a batch job so that it shuts down
17 | # the proxy containers when it has finished processing.
18 | #
19 | # The main job container should send a POST request to the proxy's /quitquitquit
20 | # api when the job process finishes. This will cause the proxy side-car
21 | # container to shut down.
22 | #
23 | # In Kubernetes 1.28, side-car containers will be properly supported, and this
24 | # extra step will become unnecessary.
25 | #
26 | # See https://github.com/kubernetes/enhancements/issues/753
27 | # and https://github.com/GoogleCloudPlatform/cloud-sql-proxy-operator/issues/381
28 |
29 | apiVersion: batch/v1
30 | kind: Job
31 | metadata:
32 | name: job
33 | labels:
34 | app: busybox
35 | spec:
36 | template:
37 | metadata:
38 | creationTimestamp: null
39 | labels:
40 | app: busybox
41 | spec:
42 | containers:
43 | - name: my-application
44 | # Note: This demonstrates a way to run the proxy in an older
45 | # kubernetes cluster that does not support native sidecar containers.
46 | # It is better to run the job as a native sidecar container.
47 | #
48 | # See the Kubernetes documentation:
49 | # https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/
50 | #
51 | # Run your batch job command.
52 | # Then, send a HTTTP POST request to the proxy sidecar container's
53 | # /quitquitquit api. This will cause the proxy process to exit.
54 | command:
55 | - sh
56 | - -c
57 | - >
58 | my_batch_job --host=127.0.0.1 --port= --username= --dbname=
59 | curl http://localhost:9091/quitquitquit
60 | image: busybox
61 | imagePullPolicy: IfNotPresent
62 | resources: {}
63 | terminationMessagePath: /dev/termination-log
64 | terminationMessagePolicy: File
65 | - name: cloud-sql-proxy
66 | # It is recommended to use the latest version of the Cloud SQL Auth Proxy
67 | # Make sure to update on a regular schedule!
68 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.1
69 | args:
70 | # Enable the admin api server on port 9091
71 | - "--admin-port=9091"
72 | # Enable the /quitquitquit admin api endpoint
73 | - "--quitquitquit"
74 |
75 | # Tell the proxy to exit gracefully if it receives a SIGTERM
76 | - "--exit-zero-on-sigterm"
77 |
78 | # Replace DB_PORT with the port the proxy should listen on
79 | - "--port="
80 | - ""
81 |
82 | securityContext:
83 | runAsNonRoot: true
84 | resources:
85 | requests:
86 | memory: "2Gi"
87 | cpu: "1"
88 | restartPolicy: Never
89 | terminationGracePeriodSeconds: 30
90 |
--------------------------------------------------------------------------------
/examples/k8s-sidecar/job_with_sidecar.yaml:
--------------------------------------------------------------------------------
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 | ###
16 | # This demonstrates how to configure a batch job so that it shuts down
17 | # the proxy containers when it has finished processing.
18 | #
19 | # This works in Kubernetes 1.29 and higher, demonstrating how to run the proxy
20 | # using a native side-car container.
21 | #
22 | # See https://github.com/kubernetes/enhancements/issues/753
23 | # and https://github.com/GoogleCloudPlatform/cloud-sql-proxy-operator/issues/381
24 |
25 | apiVersion: batch/v1
26 | kind: Job
27 | metadata:
28 | name: job
29 | labels:
30 | app: busybox
31 | spec:
32 | template:
33 | metadata:
34 | creationTimestamp: null
35 | labels:
36 | app: busybox
37 | spec:
38 | containers:
39 | - name: my-application
40 | # Run your batch job command.
41 | # Then, send a HTTTP POST request to the proxy sidecar container's
42 | # /quitquitquit api. This will cause the proxy process to exit.
43 | command:
44 | - my_batch_job
45 | - --host=127.0.0.1
46 | - --port=
47 | - --username=
48 | - --dbname=
49 | image: my-application-image
50 | imagePullPolicy: IfNotPresent
51 | resources: {}
52 | terminationMessagePath: /dev/termination-log
53 | terminationMessagePolicy: File
54 | restartPolicy: Never
55 | terminationGracePeriodSeconds: 30
56 | initContainers:
57 | - name: cloud-sql-proxy
58 | restartPolicy: Always
59 | # It is recommended to use the latest version of the Cloud SQL Auth Proxy
60 | # Make sure to update on a regular schedule!
61 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.3
62 | args:
63 | # Enable the admin api server on port 9091
64 | - "--admin-port=9091"
65 |
66 | # Tell the proxy to exit gracefully if it receives a SIGTERM
67 | - "--exit-zero-on-sigterm"
68 |
69 | # Replace DB_PORT with the port the proxy should listen on
70 | - "--port="
71 | - ""
72 | securityContext:
73 | runAsNonRoot: true
74 | resources:
75 | requests:
76 | memory: "2Gi"
77 | cpu: "1"
78 |
--------------------------------------------------------------------------------
/examples/k8s-sidecar/no_proxy_private_ip.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 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 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | name:
19 | spec:
20 | selector:
21 | matchLabels:
22 | app:
23 | template:
24 | metadata:
25 | labels:
26 | app:
27 | spec:
28 | containers:
29 | - name:
30 | # ... other container configuration
31 | env:
32 | - name: DB_USER
33 | valueFrom:
34 | secretKeyRef:
35 | name:
36 | key: username
37 | - name: DB_PASS
38 | valueFrom:
39 | secretKeyRef:
40 | name:
41 | key: password
42 | - name: DB_NAME
43 | valueFrom:
44 | secretKeyRef:
45 | name:
46 | key: database
47 | # [START cloud_sql_proxy_secret_host]
48 | - name: DB_HOST
49 | valueFrom:
50 | secretKeyRef:
51 | name:
52 | key: db_host
53 | # [END cloud_sql_proxy_secret_host]
54 |
--------------------------------------------------------------------------------
/examples/k8s-sidecar/proxy_with_sa_key.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 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 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | name:
19 | spec:
20 | selector:
21 | matchLabels:
22 | app:
23 | template:
24 | metadata:
25 | labels:
26 | app:
27 | spec:
28 | containers:
29 | - name:
30 | # ... other container configuration
31 | env:
32 | - name: DB_USER
33 | valueFrom:
34 | secretKeyRef:
35 | name:
36 | key: username
37 | - name: DB_PASS
38 | valueFrom:
39 | secretKeyRef:
40 | name:
41 | key: password
42 | - name: DB_NAME
43 | valueFrom:
44 | secretKeyRef:
45 | name:
46 | key: database
47 | initContainers:
48 | - name: cloud-sql-proxy
49 | restartPolicy: Always
50 | # It is recommended to use the latest version of the Cloud SQL Auth Proxy
51 | # Make sure to update on a regular schedule!
52 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.1
53 | args:
54 | # If connecting from a VPC-native GKE cluster, you can use the
55 | # following flag to have the proxy connect over private IP
56 | # - "--private-ip"
57 |
58 | # If you are not connecting with Automatic IAM AuthN, you can delete
59 | # the following flag.
60 | - "--auto-iam-authn"
61 |
62 | # Enable structured logging with LogEntry format:
63 | - "--structured-logs"
64 |
65 | # Replace DB_PORT with the port the proxy should listen on
66 | - "--port="
67 | - ""
68 |
69 | # [START cloud_sql_proxy_k8s_volume_mount]
70 | # This flag specifies where the service account key can be found
71 | - "--credentials-file=/secrets/service_account.json"
72 | securityContext:
73 | # The default Cloud SQL Auth Proxy image runs as the
74 | # "nonroot" user and group (uid: 65532) by default.
75 | runAsNonRoot: true
76 | volumeMounts:
77 | - name:
78 | mountPath: /secrets/
79 | readOnly: true
80 | # [END cloud_sql_proxy_k8s_volume_mount]
81 | # Resource configuration depends on an application's requirements. You
82 | # should adjust the following values based on what your application
83 | # needs. For details, see https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
84 | resources:
85 | requests:
86 | # The proxy's memory use scales linearly with the number of active
87 | # connections. Fewer open connections will use less memory. Adjust
88 | # this value based on your application's requirements.
89 | memory: "2Gi"
90 | # The proxy's CPU use scales linearly with the amount of IO between
91 | # the database and the application. Adjust this value based on your
92 | # application's requirements.
93 | cpu: "1"
94 | # [START cloud_sql_proxy_k8s_volume_secret]
95 | volumes:
96 | - name:
97 | secret:
98 | secretName:
99 | # [END cloud_sql_proxy_k8s_volume_secret]
100 |
--------------------------------------------------------------------------------
/examples/k8s-sidecar/proxy_with_workload_identity.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 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 | # [START cloud_sql_proxy_k8s_sa]
16 | apiVersion: apps/v1
17 | kind: Deployment
18 | metadata:
19 | name:
20 | spec:
21 | selector:
22 | matchLabels:
23 | app:
24 | template:
25 | metadata:
26 | labels:
27 | app:
28 | spec:
29 | serviceAccountName:
30 | # [END cloud_sql_proxy_k8s_sa]
31 | # [START cloud_sql_proxy_k8s_secrets]
32 | containers:
33 | - name:
34 | # ... other container configuration
35 | env:
36 | - name: DB_USER
37 | valueFrom:
38 | secretKeyRef:
39 | name:
40 | key: username
41 | - name: DB_PASS
42 | valueFrom:
43 | secretKeyRef:
44 | name:
45 | key: password
46 | - name: DB_NAME
47 | valueFrom:
48 | secretKeyRef:
49 | name:
50 | key: database
51 | # [END cloud_sql_proxy_k8s_secrets]
52 | # [START cloud_sql_proxy_k8s_container]
53 | initContainers:
54 | - name: cloud-sql-proxy
55 | restartPolicy: Always
56 | # It is recommended to use the latest version of the Cloud SQL Auth Proxy
57 | # Make sure to update on a regular schedule!
58 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.1
59 | args:
60 | # If connecting from a VPC-native GKE cluster, you can use the
61 | # following flag to have the proxy connect over private IP
62 | # - "--private-ip"
63 |
64 | # If you are not connecting with Automatic IAM, you can delete
65 | # the following flag.
66 | - "--auto-iam-authn"
67 |
68 | # Enable structured logging with LogEntry format:
69 | - "--structured-logs"
70 |
71 | # Replace DB_PORT with the port the proxy should listen on
72 | - "--port="
73 | - ""
74 |
75 | securityContext:
76 | # The default Cloud SQL Auth Proxy image runs as the
77 | # "nonroot" user and group (uid: 65532) by default.
78 | runAsNonRoot: true
79 | # You should use resource requests/limits as a best practice to prevent
80 | # pods from consuming too many resources and affecting the execution of
81 | # other pods. You should adjust the following values based on what your
82 | # application needs. For details, see
83 | # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
84 | resources:
85 | requests:
86 | # The proxy's memory use scales linearly with the number of active
87 | # connections. Fewer open connections will use less memory. Adjust
88 | # this value based on your application's requirements.
89 | memory: "2Gi"
90 | # The proxy's CPU use scales linearly with the amount of IO between
91 | # the database and the application. Adjust this value based on your
92 | # application's requirements.
93 | cpu: "1"
94 | # [END cloud_sql_proxy_k8s_container]
95 |
--------------------------------------------------------------------------------
/examples/k8s-sidecar/service_account.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 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 | # [START cloud_sql_proxy_k8s_sa_yml]
16 | apiVersion: v1
17 | kind: ServiceAccount
18 | metadata:
19 | name: # TODO(developer): replace these values
20 | # [END cloud_sql_proxy_k8s_sa_yml]
--------------------------------------------------------------------------------
/examples/multi-container/ruby/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 |
16 | FROM ruby:3.2.2
17 |
18 | # Copy local code to the container image.
19 | ENV APP_HOME /app
20 | WORKDIR $APP_HOME
21 | COPY . ./
22 |
23 | # Install production dependencies.
24 | RUN bundle install
25 |
26 | EXPOSE 8080
27 |
28 | CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "8080"]
29 |
--------------------------------------------------------------------------------
/examples/multi-container/ruby/Gemfile:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | source 'https://rubygems.org'
16 |
17 | gem 'json'
18 | gem 'pg'
19 | gem 'sequel'
20 | gem 'sinatra'
21 | gem 'thin'
22 |
--------------------------------------------------------------------------------
/examples/multi-container/ruby/README.md:
--------------------------------------------------------------------------------
1 | # Cloud SQL Auth Proxy Sidecar
2 |
3 | In the following example, we will deploy the Cloud SQL Proxy as a sidecar to an
4 | existing application which connects to a Cloud SQL instance.
5 |
6 | ## Before you begin
7 |
8 | 1. If you haven't already, [create a project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project).
9 |
10 | 1. [Enable the APIs](https://console.cloud.google.com/flows/enableapi?apiid=run.googleapis.com,sqladmin.googleapis.com,run.googleapis.com) that will be used during this tutorial:
11 |
12 | * Cloud SQL Admin
13 | * Cloud Build
14 | * Cloud Run
15 |
16 | 1. Create a Cloud SQL Postgres Instance by following these
17 | [instructions](https://cloud.google.com/sql/docs/postgres/create-instance).
18 | Note the connection string and default password that you create.
19 |
20 | 1. Create a database for your application by following these
21 | [instructions](https://cloud.google.com/sql/docs/postgres/create-manage-databases).
22 | Note the database name.
23 |
24 | 1. Optionally, create a database user for your instance following these
25 | [instructions](https://cloud.google.com/sql/docs/postgres/create-manage-users).
26 | Otherwise, use the username "postgres".
27 |
28 | ## Deploying the Application
29 |
30 | The application you will be deploying should connect to the Cloud SQL Proxy using
31 | TCP mode (for example, using the address "127.0.0.1:5432"). Follow the examples
32 | on the [Connect Auth Proxy documentation](https://cloud.google.com/sql/docs/postgres/connect-auth-proxy#expandable-1)
33 | page to correctly configure your application.
34 |
35 | The connection pool is configured in the following sample:
36 |
37 | ```ruby
38 | require 'sinatra'
39 | require 'sequel'
40 |
41 | set :bind, '0.0.0.0'
42 | set :port, 8080
43 |
44 | # Configure a connection pool that connects to the proxy via TCP
45 | def connect_tcp
46 | Sequel.connect(
47 | adapter: 'postgres',
48 | host: ENV["INSTANCE_HOST"],
49 | port: ENV["DB_PORT"],
50 | database: ENV["DB_NAME"],
51 | user: ENV["DB_USER"],
52 | password: ENV["DB_PASS"],
53 | pool_timeout: 5,
54 | max_connections: 5,
55 | )
56 | end
57 |
58 | DB = connect_tcp()
59 | ```
60 |
61 | Next, build the container image for the main application and deploy it:
62 |
63 | ```bash
64 | gcloud builds submit --tag gcr.io//run-cloudsql
65 | ```
66 |
67 | Finally, update the `multicontainer.yaml` file with the correct values for your
68 | deployment for `YOUR_PROJECT_ID`, `DB_USER`, `DB_PASS`, `DB_NAME`, and `INSTANCE_CONNECTION_NAME`
69 | listing the Cloud SQL container image as a sidecar:
70 |
71 | ```yaml
72 | apiVersion: serving.knative.dev/v1
73 | kind: Service
74 | metadata:
75 | annotations:
76 | run.googleapis.com/launch-stage: ALPHA
77 | name: multicontainer-service
78 | spec:
79 | template:
80 | metadata:
81 | annotations:
82 | run.googleapis.com/execution-environment: gen1 #or gen2
83 |
84 | spec:
85 | containers:
86 | - name: my-app
87 | image: gcr.io//run-cloudsql
88 | ports:
89 | - containerPort: 8080
90 | env:
91 | - name: DB_USER
92 | value:
93 | - name: DB_PASS
94 | value:
95 | - name: DB_NAME
96 | value:
97 | - name: INSTANCE_HOST
98 | value: "127.0.0.1"
99 | - name: DB_PORT
100 | value: "5432"
101 | initContainers:
102 | - name: cloud-sql-proxy
103 | restartPolicy: Always
104 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:latest
105 | args:
106 | # Ensure the port number on the --port argument matches the value of
107 | # the DB_PORT env var on the my-app container.
108 | - "--port=5432"
109 | - ""
110 |
111 | ```
112 |
113 | You can optionally use Secret Manager to store the database password. See
114 | [this documentation](https://cloud.google.com/run/docs/deploying#yaml) for more details.
115 |
116 | Before deploying, you will need to make sure that the service account associated
117 | with the Cloud Run deployment has the Cloud SQL Client role.
118 | See [this documentation](https://cloud.google.com/sql/docs/postgres/roles-and-permissions)
119 | for more details. The default service account will already have these permissions.
120 |
121 | Finally, you can deploy the service using:
122 |
123 | ```bash
124 | gcloud run services replace multicontainer.yaml
125 | ```
126 |
127 | Once the service is deployed, the console should print out a URL. You can test
128 | the service by sending a curl request with your gcloud identity token in the headers:
129 |
130 | ```bash
131 | curl -H \
132 | "Authorization: Bearer $(gcloud auth print-identity-token)" \
133 |
134 | ```
135 |
--------------------------------------------------------------------------------
/examples/multi-container/ruby/app.rb:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | require 'json'
16 | require 'sequel'
17 | require 'sinatra'
18 |
19 |
20 | set :bind, '0.0.0.0'
21 | set :port, 8080
22 |
23 | # Configure a connection pool that connects to the proxy via TCP
24 | def connect_tcp
25 | Sequel.connect(
26 | adapter: :postgres,
27 | host: ENV.fetch("INSTANCE_HOST") { "127.0.0.1" },
28 | port: ENV.fetch("DB_PORT") { 5432 },
29 | database: ENV["DB_NAME"],
30 | user: ENV["DB_USER"],
31 | password: ENV["DB_PASS"],
32 | pool_timeout: 5,
33 | max_connections: 5,
34 | )
35 | end
36 |
37 | DB = connect_tcp()
38 |
39 |
40 | get '/' do
41 | content_type :json
42 | # Connect to the database and get the current time
43 | return DB["SELECT NOW()"].all.to_json
44 | end
45 |
--------------------------------------------------------------------------------
/examples/multi-container/ruby/config.ru:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | require './app'
16 |
17 | run Sinatra::Application
18 |
--------------------------------------------------------------------------------
/examples/multi-container/ruby/multicontainer.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 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 | apiVersion: serving.knative.dev/v1
16 | kind: Service
17 | metadata:
18 | annotations:
19 | run.googleapis.com/launch-stage: ALPHA
20 | name: multicontainer-service
21 | spec:
22 | template:
23 | metadata:
24 | annotations:
25 | run.googleapis.com/execution-environment: gen1 #or gen2
26 | # Uncomment the following line if connecting to Cloud SQL using Private IP
27 | # via a VPC access connector
28 | # run.googleapis.com/vpc-access-connector:
29 | spec:
30 | containers:
31 | - name: my-app
32 | image: gcr.io//run-cloudsql:latest
33 | ports:
34 | - containerPort: 8080
35 | env:
36 | - name: DB_USER
37 | value:
38 | - name: DB_PASS
39 | value:
40 | - name: DB_NAME
41 | value:
42 | - name: INSTANCE_HOST
43 | value: "127.0.0.1"
44 | - name: DB_PORT
45 | value: "5432"
46 | initContainers:
47 | - name: cloud-sql-proxy
48 | restartPolicy: Always
49 | image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:latest
50 | args:
51 | # If connecting to a Cloud SQL instance within a VPC network, you can use the
52 | # following flag to have the proxy connect over private IP
53 | # - "--private-ip"
54 |
55 | # Ensure the port number on the --port argument matches the value of the DB_PORT env var on the my-app container.
56 | - "--port=5432"
57 | # instance connection name takes format "PROJECT:REGION:INSTANCE_NAME"
58 | - ""
59 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/GoogleCloudPlatform/cloud-sql-proxy/v2
2 |
3 | go 1.24
4 |
5 | require (
6 | cloud.google.com/go/cloudsqlconn v1.17.1
7 | contrib.go.opencensus.io/exporter/prometheus v0.4.2
8 | contrib.go.opencensus.io/exporter/stackdriver v0.13.14
9 | github.com/coreos/go-systemd/v22 v22.5.0
10 | github.com/go-sql-driver/mysql v1.9.2
11 | github.com/google/go-cmp v0.7.0
12 | github.com/hanwen/go-fuse/v2 v2.7.2
13 | github.com/jackc/pgx/v5 v5.7.5
14 | github.com/microsoft/go-mssqldb v1.8.1
15 | github.com/spf13/cobra v1.9.1
16 | github.com/spf13/pflag v1.0.6
17 | github.com/spf13/viper v1.20.1
18 | go.opencensus.io v0.24.0
19 | go.uber.org/zap v1.27.0
20 | golang.org/x/oauth2 v0.30.0
21 | golang.org/x/sys v0.33.0
22 | google.golang.org/api v0.233.0
23 | gopkg.in/natefinch/lumberjack.v2 v2.2.1
24 | )
25 |
26 | require (
27 | cloud.google.com/go/auth v0.16.1 // indirect
28 | cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
29 | cloud.google.com/go/compute/metadata v0.6.0 // indirect
30 | cloud.google.com/go/monitoring v1.21.2 // indirect
31 | cloud.google.com/go/trace v1.11.2 // indirect
32 | filippo.io/edwards25519 v1.1.0 // indirect
33 | github.com/aws/aws-sdk-go v1.43.31 // indirect
34 | github.com/beorn7/perks v1.0.1 // indirect
35 | github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
36 | github.com/cespare/xxhash/v2 v2.3.0 // indirect
37 | github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
38 | github.com/felixge/httpsnoop v1.0.4 // indirect
39 | github.com/fsnotify/fsnotify v1.8.0 // indirect
40 | github.com/go-kit/log v0.2.1 // indirect
41 | github.com/go-logfmt/logfmt v0.5.1 // indirect
42 | github.com/go-logr/logr v1.4.2 // indirect
43 | github.com/go-logr/stdr v1.2.2 // indirect
44 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
45 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
46 | github.com/golang-sql/sqlexp v0.1.0 // indirect
47 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
48 | github.com/golang/protobuf v1.5.4 // indirect
49 | github.com/google/s2a-go v0.1.9 // indirect
50 | github.com/google/uuid v1.6.0 // indirect
51 | github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
52 | github.com/googleapis/gax-go/v2 v2.14.1 // indirect
53 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
54 | github.com/jackc/pgpassfile v1.0.0 // indirect
55 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
56 | github.com/jackc/puddle/v2 v2.2.2 // indirect
57 | github.com/jmespath/go-jmespath v0.4.0 // indirect
58 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
59 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect
60 | github.com/prometheus/client_golang v1.13.0 // indirect
61 | github.com/prometheus/client_model v0.2.0 // indirect
62 | github.com/prometheus/common v0.37.0 // indirect
63 | github.com/prometheus/procfs v0.8.0 // indirect
64 | github.com/prometheus/prometheus v0.35.0 // indirect
65 | github.com/prometheus/statsd_exporter v0.22.7 // indirect
66 | github.com/russross/blackfriday/v2 v2.1.0 // indirect
67 | github.com/sagikazarmark/locafero v0.7.0 // indirect
68 | github.com/sourcegraph/conc v0.3.0 // indirect
69 | github.com/spf13/afero v1.12.0 // indirect
70 | github.com/spf13/cast v1.7.1 // indirect
71 | github.com/subosito/gotenv v1.6.0 // indirect
72 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect
73 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
74 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
75 | go.opentelemetry.io/otel v1.35.0 // indirect
76 | go.opentelemetry.io/otel/metric v1.35.0 // indirect
77 | go.opentelemetry.io/otel/trace v1.35.0 // indirect
78 | go.uber.org/multierr v1.10.0 // indirect
79 | golang.org/x/crypto v0.38.0 // indirect
80 | golang.org/x/net v0.40.0 // indirect
81 | golang.org/x/sync v0.14.0 // indirect
82 | golang.org/x/text v0.25.0 // indirect
83 | golang.org/x/time v0.11.0 // indirect
84 | google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
85 | google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
86 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
87 | google.golang.org/grpc v1.72.1 // indirect
88 | google.golang.org/protobuf v1.36.6 // indirect
89 | gopkg.in/yaml.v2 v2.4.0 // indirect
90 | gopkg.in/yaml.v3 v3.0.1 // indirect
91 | )
92 |
--------------------------------------------------------------------------------
/internal/gcloud/gcloud.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 gcloud
16 |
17 | import (
18 | "bytes"
19 | "encoding/json"
20 | "fmt"
21 | "runtime"
22 | "time"
23 |
24 | "golang.org/x/oauth2"
25 | exec "golang.org/x/sys/execabs"
26 | )
27 |
28 | // config represents the credentials returned by `gcloud config config-helper`.
29 | type config struct {
30 | Credential struct {
31 | AccessToken string `json:"access_token"`
32 | TokenExpiry time.Time `json:"token_expiry"`
33 | }
34 | }
35 |
36 | func (c *config) Token() *oauth2.Token {
37 | return &oauth2.Token{
38 | AccessToken: c.Credential.AccessToken,
39 | Expiry: c.Credential.TokenExpiry,
40 | }
41 | }
42 |
43 | // Path returns the absolute path to the gcloud command. If the command is not
44 | // found it returns an error.
45 | func Path() (string, error) {
46 | g := "gcloud"
47 | if runtime.GOOS == "windows" {
48 | g = g + ".cmd"
49 | }
50 | return exec.LookPath(g)
51 | }
52 |
53 | // configHelper implements oauth2.TokenSource via the `gcloud config config-helper` command.
54 | type configHelper struct{}
55 |
56 | // Token helps gcloudTokenSource implement oauth2.TokenSource.
57 | func (configHelper) Token() (*oauth2.Token, error) {
58 | gcloudCmd, err := Path()
59 | if err != nil {
60 | return nil, err
61 | }
62 | buf, errbuf := new(bytes.Buffer), new(bytes.Buffer)
63 | cmd := exec.Command(gcloudCmd, "--format", "json", "config", "config-helper", "--min-expiry", "1h")
64 | cmd.Stdout = buf
65 | cmd.Stderr = errbuf
66 |
67 | if err := cmd.Run(); err != nil {
68 | err = fmt.Errorf("error reading config: %v; stderr was:\n%v", err, errbuf)
69 | return nil, err
70 | }
71 |
72 | c := &config{}
73 | if err := json.Unmarshal(buf.Bytes(), c); err != nil {
74 | return nil, err
75 | }
76 | return c.Token(), nil
77 | }
78 |
79 | // TokenSource returns an oauth2.TokenSource backed by the gcloud CLI.
80 | func TokenSource() (oauth2.TokenSource, error) {
81 | h := configHelper{}
82 | tok, err := h.Token()
83 | if err != nil {
84 | return nil, err
85 | }
86 | return oauth2.ReuseTokenSource(tok, h), nil
87 | }
88 |
--------------------------------------------------------------------------------
/internal/gcloud/gcloud_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 gcloud_test
16 |
17 | import (
18 | "testing"
19 |
20 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/gcloud"
21 | )
22 |
23 | func TestGcloud(t *testing.T) {
24 | if testing.Short() {
25 | t.Skip("skipping gcloud integration tests")
26 | }
27 |
28 | // gcloud is configured. Try to obtain a token from gcloud config
29 | // helper.
30 | ts, err := gcloud.TokenSource()
31 | if err != nil {
32 | t.Fatalf("failed to get token source: %v", err)
33 | }
34 |
35 | _, err = ts.Token()
36 | if err != nil {
37 | t.Fatalf("failed to get token: %v", err)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/internal/healthcheck/healthcheck.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 healthcheck tests and communicates the health of the Cloud SQL Auth Proxy.
16 | package healthcheck
17 |
18 | import (
19 | "errors"
20 | "fmt"
21 | "net/http"
22 | "sync"
23 |
24 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql"
25 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/proxy"
26 | )
27 |
28 | // Check provides HTTP handlers for use as healthchecks typically in a
29 | // Kubernetes context.
30 | type Check struct {
31 | startedOnce *sync.Once
32 | started chan struct{}
33 | stoppedOnce *sync.Once
34 | stopped chan struct{}
35 | proxy *proxy.Client
36 | logger cloudsql.Logger
37 | }
38 |
39 | // NewCheck is the initializer for Check.
40 | func NewCheck(p *proxy.Client, l cloudsql.Logger) *Check {
41 | return &Check{
42 | startedOnce: &sync.Once{},
43 | started: make(chan struct{}),
44 | stoppedOnce: &sync.Once{},
45 | stopped: make(chan struct{}),
46 | proxy: p,
47 | logger: l,
48 | }
49 | }
50 |
51 | // NotifyStarted notifies the check that the proxy has started up successfully.
52 | func (c *Check) NotifyStarted() {
53 | c.startedOnce.Do(func() { close(c.started) })
54 | }
55 |
56 | // NotifyStopped notifies the check that the proxy has started up successfully.
57 | func (c *Check) NotifyStopped() {
58 | c.stoppedOnce.Do(func() { close(c.stopped) })
59 | }
60 |
61 | // HandleStartup reports whether the Check has been notified of startup.
62 | func (c *Check) HandleStartup(w http.ResponseWriter, _ *http.Request) {
63 | select {
64 | case <-c.started:
65 | w.WriteHeader(http.StatusOK)
66 | w.Write([]byte("ok"))
67 | default:
68 | w.WriteHeader(http.StatusServiceUnavailable)
69 | w.Write([]byte("error"))
70 | }
71 | }
72 |
73 | var (
74 | errNotStarted = errors.New("proxy is not started")
75 | errStopped = errors.New("proxy has stopped")
76 | )
77 |
78 | // HandleReadiness ensures the Check has been notified of successful startup,
79 | // that the proxy has not reached maximum connections, and that the Proxy has
80 | // not started shutting down.
81 | func (c *Check) HandleReadiness(w http.ResponseWriter, _ *http.Request) {
82 | select {
83 | case <-c.started:
84 | default:
85 | c.logger.Errorf("[Health Check] Readiness failed: %v", errNotStarted)
86 | w.WriteHeader(http.StatusServiceUnavailable)
87 | w.Write([]byte(errNotStarted.Error()))
88 | return
89 | }
90 |
91 | select {
92 | case <-c.stopped:
93 | c.logger.Errorf("[Health Check] Readiness failed: %v", errStopped)
94 | w.WriteHeader(http.StatusServiceUnavailable)
95 | w.Write([]byte(errStopped.Error()))
96 | return
97 | default:
98 | }
99 |
100 | if open, maxCount := c.proxy.ConnCount(); maxCount > 0 && open == maxCount {
101 | err := fmt.Errorf("max connections reached (open = %v, max = %v)", open, maxCount)
102 | c.logger.Errorf("[Health Check] Readiness failed: %v", err)
103 | w.WriteHeader(http.StatusServiceUnavailable)
104 | w.Write([]byte(err.Error()))
105 | return
106 | }
107 |
108 | // No error cases apply, 200 status.
109 | w.WriteHeader(http.StatusOK)
110 | w.Write([]byte("ok"))
111 | }
112 |
113 | // HandleLiveness indicates the process is up and responding to HTTP requests.
114 | // If this check fails (because it's not reachable), the process is in a bad
115 | // state and should be restarted.
116 | func (c *Check) HandleLiveness(w http.ResponseWriter, _ *http.Request) {
117 | w.WriteHeader(http.StatusOK)
118 | w.Write([]byte("ok"))
119 | }
120 |
--------------------------------------------------------------------------------
/internal/healthcheck/healthcheck_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 healthcheck_test
16 |
17 | import (
18 | "context"
19 | "fmt"
20 | "io"
21 | "net"
22 | "net/http"
23 | "net/http/httptest"
24 | "net/url"
25 | "os"
26 | "strings"
27 | "testing"
28 | "time"
29 |
30 | "cloud.google.com/go/cloudsqlconn"
31 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql"
32 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/healthcheck"
33 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log"
34 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/proxy"
35 | )
36 |
37 | var (
38 | logger = log.NewStdLogger(os.Stdout, os.Stdout)
39 | proxyHost = "127.0.0.1"
40 | proxyPort = 9000
41 | )
42 |
43 | func proxyAddr() string {
44 | return fmt.Sprintf("%s:%d", proxyHost, proxyPort)
45 | }
46 |
47 | func dialTCP(t *testing.T, addr string) net.Conn {
48 | for i := 0; i < 10; i++ {
49 | conn, err := net.Dial("tcp", addr)
50 | if err == nil {
51 | return conn
52 | }
53 | time.Sleep(100 * time.Millisecond)
54 | }
55 | t.Fatalf("failed to dial %v", addr)
56 | return nil
57 | }
58 |
59 | type fakeDialer struct{}
60 |
61 | func (*fakeDialer) Dial(_ context.Context, _ string, _ ...cloudsqlconn.DialOption) (net.Conn, error) {
62 | conn, _ := net.Pipe()
63 | return conn, nil
64 | }
65 |
66 | func (*fakeDialer) EngineVersion(_ context.Context, _ string) (string, error) {
67 | return "POSTGRES_14", nil
68 | }
69 |
70 | func (*fakeDialer) Close() error {
71 | return nil
72 | }
73 |
74 | func newProxyWithParams(t *testing.T, maxConns uint64, dialer cloudsql.Dialer, instances []proxy.InstanceConnConfig) *proxy.Client {
75 | c := &proxy.Config{
76 | Addr: proxyHost,
77 | Port: proxyPort,
78 | Instances: instances,
79 | MaxConnections: maxConns,
80 | }
81 | p, err := proxy.NewClient(context.Background(), dialer, logger, c, nil)
82 | if err != nil {
83 | t.Fatalf("proxy.NewClient: %v", err)
84 | }
85 | return p
86 | }
87 |
88 | func newTestProxyWithMaxConns(t *testing.T, maxConns uint64) *proxy.Client {
89 | return newProxyWithParams(t, maxConns, &fakeDialer{}, []proxy.InstanceConnConfig{
90 | {Name: "proj:region:pg"},
91 | })
92 | }
93 |
94 | func newTestProxy(t *testing.T) *proxy.Client {
95 | return newProxyWithParams(t, 0, &fakeDialer{}, []proxy.InstanceConnConfig{{Name: "proj:region:pg"}})
96 | }
97 |
98 | func TestHandleStartupWhenNotNotified(t *testing.T) {
99 | p := newTestProxy(t)
100 | defer func() {
101 | if err := p.Close(); err != nil {
102 | t.Logf("failed to close proxy client: %v", err)
103 | }
104 | }()
105 | check := healthcheck.NewCheck(p, logger)
106 |
107 | rec := httptest.NewRecorder()
108 | check.HandleStartup(rec, &http.Request{URL: &url.URL{}})
109 |
110 | // Startup is not complete because the Check has not been notified of the
111 | // proxy's startup.
112 | resp := rec.Result()
113 | if got, want := resp.StatusCode, http.StatusServiceUnavailable; got != want {
114 | t.Fatalf("want = %v, got = %v", want, got)
115 | }
116 | }
117 |
118 | func TestHandleStartupWhenNotified(t *testing.T) {
119 | p := newTestProxy(t)
120 | defer func() {
121 | if err := p.Close(); err != nil {
122 | t.Logf("failed to close proxy client: %v", err)
123 | }
124 | }()
125 | check := healthcheck.NewCheck(p, logger)
126 |
127 | check.NotifyStarted()
128 |
129 | rec := httptest.NewRecorder()
130 | check.HandleStartup(rec, &http.Request{URL: &url.URL{}})
131 |
132 | resp := rec.Result()
133 | if got, want := resp.StatusCode, http.StatusOK; got != want {
134 | t.Fatalf("want = %v, got = %v", want, got)
135 | }
136 | }
137 |
138 | func TestHandleReadinessWhenNotNotified(t *testing.T) {
139 | p := newTestProxy(t)
140 | defer func() {
141 | if err := p.Close(); err != nil {
142 | t.Logf("failed to close proxy client: %v", err)
143 | }
144 | }()
145 | check := healthcheck.NewCheck(p, logger)
146 |
147 | rec := httptest.NewRecorder()
148 | check.HandleReadiness(rec, &http.Request{URL: &url.URL{}})
149 |
150 | resp := rec.Result()
151 | if got, want := resp.StatusCode, http.StatusServiceUnavailable; got != want {
152 | t.Fatalf("want = %v, got = %v", want, got)
153 | }
154 | }
155 |
156 | func TestHandleReadinessWhenStopped(t *testing.T) {
157 | p := newTestProxy(t)
158 | defer func() {
159 | if err := p.Close(); err != nil {
160 | t.Logf("failed to close proxy client: %v", err)
161 | }
162 | }()
163 | check := healthcheck.NewCheck(p, logger)
164 |
165 | check.NotifyStarted() // The Proxy has started.
166 | check.NotifyStopped() // And now the Proxy is shutting down.
167 |
168 | rec := httptest.NewRecorder()
169 | check.HandleReadiness(rec, &http.Request{URL: &url.URL{}})
170 |
171 | resp := rec.Result()
172 | if got, want := resp.StatusCode, http.StatusServiceUnavailable; got != want {
173 | t.Fatalf("want = %v, got = %v", want, got)
174 | }
175 | }
176 |
177 | func TestHandleReadinessForMaxConns(t *testing.T) {
178 | p := newTestProxyWithMaxConns(t, 1)
179 | defer func() {
180 | if err := p.Close(); err != nil {
181 | t.Logf("failed to close proxy client: %v", err)
182 | }
183 | }()
184 | started := make(chan struct{})
185 | check := healthcheck.NewCheck(p, logger)
186 | go p.Serve(context.Background(), func() {
187 | check.NotifyStarted()
188 | close(started)
189 | })
190 | select {
191 | case <-started:
192 | // proxy has started
193 | case <-time.After(10 * time.Second):
194 | t.Fatal("proxy has not started after 10 seconds")
195 | }
196 |
197 | conn := dialTCP(t, proxyAddr())
198 | defer conn.Close()
199 |
200 | // The proxy calls the dialer in a separate goroutine. So wait for that
201 | // goroutine to run before asserting on the readiness response.
202 | waitForConnect := func(t *testing.T, wantCode int) *http.Response {
203 | for i := 0; i < 10; i++ {
204 | rec := httptest.NewRecorder()
205 | check.HandleReadiness(rec, &http.Request{URL: &url.URL{}})
206 | resp := rec.Result()
207 | if resp.StatusCode == wantCode {
208 | return resp
209 | }
210 | time.Sleep(time.Second)
211 | }
212 | t.Fatalf("failed to receive status code = %v", wantCode)
213 | return nil
214 | }
215 | resp := waitForConnect(t, http.StatusServiceUnavailable)
216 |
217 | body, err := io.ReadAll(resp.Body)
218 | if err != nil {
219 | t.Fatalf("failed to read response body: %v", err)
220 | }
221 | if !strings.Contains(string(body), "max connections") {
222 | t.Fatalf("want max connections error, got = %v", string(body))
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/internal/log/log.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 log
16 |
17 | import (
18 | "io"
19 | llog "log"
20 | "os"
21 |
22 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql"
23 | "go.uber.org/zap"
24 | "go.uber.org/zap/zapcore"
25 | )
26 |
27 | // StdLogger is the standard logger that distinguishes between info and error
28 | // logs.
29 | type StdLogger struct {
30 | infoLog *llog.Logger
31 | debugLog *llog.Logger
32 | errLog *llog.Logger
33 | }
34 |
35 | // NewStdLogger create a Logger that uses out and err for informational and
36 | // error messages.
37 | func NewStdLogger(out, err io.Writer) cloudsql.Logger {
38 | return &StdLogger{
39 | infoLog: llog.New(out, "", llog.LstdFlags),
40 | debugLog: llog.New(out, "", llog.LstdFlags),
41 | errLog: llog.New(err, "", llog.LstdFlags),
42 | }
43 | }
44 |
45 | // Infof logs informational messages
46 | func (l *StdLogger) Infof(format string, v ...interface{}) {
47 | l.infoLog.Printf(format, v...)
48 | }
49 |
50 | // Errorf logs error messages
51 | func (l *StdLogger) Errorf(format string, v ...interface{}) {
52 | l.errLog.Printf(format, v...)
53 | }
54 |
55 | // Debugf logs debug messages
56 | func (l *StdLogger) Debugf(format string, v ...interface{}) {
57 | l.debugLog.Printf(format, v...)
58 | }
59 |
60 | // StructuredLogger writes log messages in JSON.
61 | type StructuredLogger struct {
62 | logger *zap.SugaredLogger
63 | }
64 |
65 | // Infof logs informational messages
66 | func (l *StructuredLogger) Infof(format string, v ...interface{}) {
67 | l.logger.Infof(format, v...)
68 | }
69 |
70 | // Errorf logs error messages
71 | func (l *StructuredLogger) Errorf(format string, v ...interface{}) {
72 | l.logger.Errorf(format, v...)
73 | }
74 |
75 | // Debugf logs debug messages
76 | func (l *StructuredLogger) Debugf(format string, v ...interface{}) {
77 | l.logger.Debugf(format, v...)
78 | }
79 |
80 | // NewStructuredLogger creates a Logger that logs messages using JSON.
81 | func NewStructuredLogger(quiet bool) (cloudsql.Logger, func() error) {
82 | // Configure structured logs to adhere to LogEntry format
83 | // https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
84 | c := zap.NewProductionEncoderConfig()
85 | c.LevelKey = "severity"
86 | c.MessageKey = "message"
87 | c.TimeKey = "timestamp"
88 | c.EncodeLevel = zapcore.CapitalLevelEncoder
89 | c.EncodeTime = zapcore.ISO8601TimeEncoder
90 |
91 | enc := zapcore.NewJSONEncoder(c)
92 |
93 | var syncer zapcore.WriteSyncer
94 | // quiet disables writing to the info log
95 | if quiet {
96 | syncer = zapcore.AddSync(io.Discard)
97 | } else {
98 | syncer = zapcore.Lock(os.Stdout)
99 | }
100 | core := zapcore.NewTee(
101 | zapcore.NewCore(enc, syncer, zap.LevelEnablerFunc(func(l zapcore.Level) bool {
102 | // Anything below error, goes to the info log.
103 | return l < zapcore.ErrorLevel
104 | })),
105 | zapcore.NewCore(enc, zapcore.Lock(os.Stderr), zap.LevelEnablerFunc(func(l zapcore.Level) bool {
106 | // Anything at error or higher goes to the error log.
107 | return l >= zapcore.ErrorLevel
108 | })),
109 | )
110 | l := &StructuredLogger{
111 | logger: zap.New(core).Sugar(),
112 | }
113 | return l, l.logger.Sync
114 | }
115 |
--------------------------------------------------------------------------------
/internal/proxy/fuse.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 | // https://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 | //go:build !windows && !openbsd && !freebsd
16 | // +build !windows,!openbsd,!freebsd
17 |
18 | package proxy
19 |
20 | import (
21 | "context"
22 | "syscall"
23 |
24 | "github.com/hanwen/go-fuse/v2/fs"
25 | "github.com/hanwen/go-fuse/v2/fuse"
26 | "github.com/hanwen/go-fuse/v2/fuse/nodefs"
27 | )
28 |
29 | // symlink implements a symbolic link, returning the underlying path when
30 | // Readlink is called.
31 | type symlink struct {
32 | fs.Inode
33 | path string
34 | }
35 |
36 | // Readlink implements fs.NodeReadlinker and returns the symlink's path.
37 | func (s *symlink) Readlink(_ context.Context) ([]byte, syscall.Errno) {
38 | return []byte(s.path), fs.OK
39 | }
40 |
41 | // readme represents a static read-only text file.
42 | type readme struct {
43 | fs.Inode
44 | }
45 |
46 | const readmeText = `
47 | When applications attempt to open files in this directory, a remote connection
48 | to the Cloud SQL instance of the same name will be established.
49 |
50 | For example, when you run one of the following commands, the proxy will initiate
51 | a connection to the corresponding Cloud SQL instance, given you have the correct
52 | IAM permissions.
53 |
54 | mysql -u root -S "/somedir/project:region:instance"
55 |
56 | # or
57 |
58 | psql "host=/somedir/project:region:instance dbname=mydb user=myuser"
59 |
60 | For MySQL, the proxy will create a socket with the instance connection name
61 | (e.g., project:region:instance) in this directory. For Postgres, the proxy will
62 | create a directory with the instance connection name, and create a socket inside
63 | that directory with the special Postgres name: .s.PGSQL.5432.
64 |
65 | Listing the contents of this directory will show all instances with active
66 | connections.
67 | `
68 |
69 | // Getattr implements fs.NodeGetattrer and indicates that this file is a regular
70 | // file.
71 | func (*readme) Getattr(_ context.Context, _ fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
72 | *out = fuse.AttrOut{Attr: fuse.Attr{
73 | Mode: 0444 | syscall.S_IFREG,
74 | Size: uint64(len(readmeText)),
75 | }}
76 | return fs.OK
77 | }
78 |
79 | // Read implements fs.NodeReader and supports incremental reads.
80 | func (*readme) Read(_ context.Context, _ fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
81 | end := int(off) + len(dest)
82 | if end > len(readmeText) {
83 | end = len(readmeText)
84 | }
85 | return fuse.ReadResultData([]byte(readmeText[off:end])), fs.OK
86 | }
87 |
88 | // Open implements fs.NodeOpener and supports opening the README as a read-only
89 | // file.
90 | func (*readme) Open(_ context.Context, _ uint32) (fs.FileHandle, uint32, syscall.Errno) {
91 | df := nodefs.NewDataFile([]byte(readmeText))
92 | rf := nodefs.NewReadOnlyFile(df)
93 | return rf, 0, fs.OK
94 | }
95 |
--------------------------------------------------------------------------------
/internal/proxy/fuse_darwin.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 proxy
16 |
17 | import (
18 | "errors"
19 | "os"
20 | )
21 |
22 | const (
23 | macfusePath = "/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse"
24 | osxfusePath = "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse"
25 | )
26 |
27 | // SupportsFUSE checks if macfuse or osxfuse are installed on the host by
28 | // looking for both in their known installation location.
29 | func SupportsFUSE() error {
30 | // This code follows the same strategy as hanwen/go-fuse.
31 | // See https://github.com/hanwen/go-fuse/blob/0f728ba15b38579efefc3dc47821882ca18ffea7/fuse/mount_darwin.go#L121-L124.
32 |
33 | // check for macfuse first (newer version of osxfuse)
34 | if _, err := os.Stat(macfusePath); err != nil {
35 | // if that fails, check for osxfuse next
36 | if _, err := os.Stat(osxfusePath); err != nil {
37 | return errors.New("failed to find osxfuse or macfuse: verify FUSE installation and try again (see https://osxfuse.github.io).")
38 | }
39 | }
40 | return nil
41 | }
42 |
--------------------------------------------------------------------------------
/internal/proxy/fuse_freebsd.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 proxy
16 |
17 | import (
18 | "context"
19 | "errors"
20 | )
21 |
22 | var errFUSENotSupported = errors.New("FUSE is not supported on FreeBSD")
23 |
24 | // SupportsFUSE is false on FreeBSD.
25 | func SupportsFUSE() error {
26 | return errFUSENotSupported
27 | }
28 |
29 | type fuseMount struct {
30 | // fuseDir is always an empty string on FreeBSD.
31 | fuseDir string
32 | }
33 |
34 | func configureFUSE(c *Client, conf *Config) (*Client, error) { return nil, errFUSENotSupported }
35 | func (c *Client) fuseMounts() []*socketMount { return nil }
36 | func (c *Client) serveFuse(ctx context.Context, notify func()) error { return errFUSENotSupported }
37 | func (c *Client) unmountFUSE() error { return nil }
38 | func (c *Client) waitForFUSEMounts() {}
39 |
--------------------------------------------------------------------------------
/internal/proxy/fuse_linux.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 proxy
16 |
17 | import (
18 | "errors"
19 | "os/exec"
20 | )
21 |
22 | // SupportsFUSE checks if the fusermount binary is present in the PATH or a well
23 | // known location.
24 | func SupportsFUSE() error {
25 | // This code follows the same strategy found in hanwen/go-fuse.
26 | // See https://github.com/hanwen/go-fuse/blob/0f728ba15b38579efefc3dc47821882ca18ffea7/fuse/mount_linux.go#L184-L198.
27 | if _, err := exec.LookPath("fusermount"); err != nil {
28 | if _, err := exec.LookPath("/bin/fusermount"); err != nil {
29 | return errors.New("fusermount binary not found in PATH or /bin")
30 | }
31 | }
32 | return nil
33 | }
34 |
--------------------------------------------------------------------------------
/internal/proxy/fuse_linux_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 proxy_test
16 |
17 | import (
18 | "os"
19 | "testing"
20 |
21 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/proxy"
22 | )
23 |
24 | func TestFUSESupport(t *testing.T) {
25 | if testing.Short() {
26 | t.Skip("skipping fuse tests in short mode.")
27 | }
28 |
29 | removePath := func() func() {
30 | original := os.Getenv("PATH")
31 | os.Unsetenv("PATH")
32 | return func() { os.Setenv("PATH", original) }
33 | }
34 | if err := proxy.SupportsFUSE(); err != nil {
35 | t.Fatalf("expected FUSE to be support (PATH set): %v", err)
36 | }
37 | cleanup := removePath()
38 | defer cleanup()
39 |
40 | if err := proxy.SupportsFUSE(); err != nil {
41 | t.Fatalf("expected FUSE to be supported (PATH unset): %v", err)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/internal/proxy/fuse_openbsd.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 proxy
16 |
17 | import (
18 | "context"
19 | "errors"
20 | )
21 |
22 | var errFUSENotSupported = errors.New("FUSE is not supported on OpenBSD")
23 |
24 | // SupportsFUSE is false on OpenBSD.
25 | func SupportsFUSE() error {
26 | return errFUSENotSupported
27 | }
28 |
29 | type fuseMount struct {
30 | // fuseDir is always an empty string on OpenBSD.
31 | fuseDir string
32 | }
33 |
34 | func configureFUSE(c *Client, conf *Config) (*Client, error) { return nil, errFUSENotSupported }
35 | func (c *Client) fuseMounts() []*socketMount { return nil }
36 | func (c *Client) serveFuse(ctx context.Context, notify func()) error { return errFUSENotSupported }
37 | func (c *Client) unmountFUSE() error { return nil }
38 | func (c *Client) waitForFUSEMounts() {}
39 |
--------------------------------------------------------------------------------
/internal/proxy/fuse_windows.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 proxy
16 |
17 | import (
18 | "context"
19 | "errors"
20 | "path/filepath"
21 | "strings"
22 | )
23 |
24 | var errFUSENotSupported = errors.New("FUSE is not supported on Windows")
25 |
26 | // SupportsFUSE is false on Windows.
27 | func SupportsFUSE() error {
28 | return errFUSENotSupported
29 | }
30 |
31 | // UnixAddress returns the Unix socket for a given instance in the provided
32 | // directory, by replacing all colons in the instance's name with periods.
33 | func UnixAddress(dir, inst string) string {
34 | inst2 := strings.ReplaceAll(inst, ":", ".")
35 | return filepath.Join(dir, inst2)
36 | }
37 |
38 | type fuseMount struct {
39 | // fuseDir is always an empty string on Windows.
40 | fuseDir string
41 | }
42 |
43 | func configureFUSE(c *Client, conf *Config) (*Client, error) { return nil, errFUSENotSupported }
44 | func (c *Client) fuseMounts() []*socketMount { return nil }
45 | func (c *Client) serveFuse(ctx context.Context, notify func()) error { return errFUSENotSupported }
46 | func (c *Client) unmountFUSE() error { return nil }
47 | func (c *Client) waitForFUSEMounts() {}
48 |
--------------------------------------------------------------------------------
/internal/proxy/internal_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 proxy
16 |
17 | import (
18 | "testing"
19 | "unsafe"
20 |
21 | "github.com/google/go-cmp/cmp"
22 | )
23 |
24 | func TestClientUsesSyncAtomicAlignment(t *testing.T) {
25 | // The sync/atomic pkg has a bug that requires the developer to guarantee
26 | // 64-bit alignment when using 64-bit functions on 32-bit systems.
27 | c := &Client{} //nolint:staticcheck
28 |
29 | if a := unsafe.Offsetof(c.connCount); a%64 != 0 {
30 | t.Errorf("Client.connCount is not 64-bit aligned: want 0, got %v", a)
31 | }
32 | }
33 |
34 | func TestParseImpersonationChain(t *testing.T) {
35 | tcs := []struct {
36 | desc string
37 | in string
38 | wantTarget string
39 | wantChain []string
40 | }{
41 | {
42 | desc: "when there is only a target",
43 | in: "sv1@developer.gserviceaccount.com",
44 | wantTarget: "sv1@developer.gserviceaccount.com",
45 | },
46 | {
47 | desc: "when there are delegates",
48 | in: "sv1@developer.gserviceaccount.com,sv2@developer.gserviceaccount.com,sv3@developer.gserviceaccount.com",
49 | wantTarget: "sv1@developer.gserviceaccount.com",
50 | wantChain: []string{
51 | "sv3@developer.gserviceaccount.com",
52 | "sv2@developer.gserviceaccount.com",
53 | },
54 | },
55 | }
56 | for _, tc := range tcs {
57 | t.Run(tc.desc, func(t *testing.T) {
58 | gotTarget, gotChain := parseImpersonationChain(tc.in)
59 | if gotTarget != tc.wantTarget {
60 | t.Fatalf("target: want = %v, got = %v", tc.wantTarget, gotTarget)
61 | }
62 | if !equalSlice(tc.wantChain, gotChain) {
63 | t.Fatalf("want chain != got chain: %v", cmp.Diff(tc.wantChain, gotChain))
64 | }
65 | })
66 | }
67 | }
68 |
69 | func equalSlice[T comparable](x, y []T) bool {
70 | if len(x) != len(y) {
71 | return false
72 | }
73 | for i := 0; i < len(x); i++ {
74 | if x[i] != y[i] {
75 | return false
76 | }
77 | }
78 | return true
79 | }
80 |
--------------------------------------------------------------------------------
/internal/proxy/proxy_other.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 | //go:build !windows && !openbsd && !freebsd
16 | // +build !windows,!openbsd,!freebsd
17 |
18 | package proxy
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "os"
24 | "path/filepath"
25 | "sync"
26 | "syscall"
27 |
28 | "github.com/hanwen/go-fuse/v2/fs"
29 | "github.com/hanwen/go-fuse/v2/fuse"
30 | )
31 |
32 | type socketSymlink struct {
33 | socket *socketMount
34 | symlink *symlink
35 | }
36 |
37 | func configureFUSE(c *Client, conf *Config) (*Client, error) {
38 | if _, err := os.Stat(conf.FUSEDir); err != nil {
39 | return nil, err
40 | }
41 | if err := os.MkdirAll(conf.FUSETempDir, 0777); err != nil {
42 | return nil, err
43 | }
44 | c.fuseMount = fuseMount{
45 | fuseDir: conf.FUSEDir,
46 | fuseTempDir: conf.FUSETempDir,
47 | fuseSockets: map[string]socketSymlink{},
48 | // Use pointers for the following mutexes so fuseMount may be embedded
49 | // as a value and support zero value lookups on fuseDir.
50 | fuseMu: &sync.Mutex{},
51 | fuseServerMu: &sync.Mutex{},
52 | fuseWg: &sync.WaitGroup{},
53 | }
54 | return c, nil
55 | }
56 |
57 | type fuseMount struct {
58 | // fuseDir specifies the directory where a FUSE server is mounted. The value
59 | // is empty if FUSE is not enabled. The directory holds symlinks to Unix
60 | // domain sockets in the fuseTmpDir.
61 | fuseDir string
62 | fuseTempDir string
63 | // fuseMu protects access to fuseSockets.
64 | fuseMu *sync.Mutex
65 | // fuseSockets is a map of instance connection name to socketMount and
66 | // symlink.
67 | fuseSockets map[string]socketSymlink
68 | fuseServerMu *sync.Mutex
69 | fuseServer *fuse.Server
70 | fuseWg *sync.WaitGroup
71 | fuseExitCh chan error
72 |
73 | // Inode adds support for FUSE operations.
74 | fs.Inode
75 | }
76 |
77 | // Readdir returns a list of all active Unix sockets in addition to the README.
78 | func (c *Client) Readdir(_ context.Context) (fs.DirStream, syscall.Errno) {
79 | entries := []fuse.DirEntry{
80 | {Name: "README", Mode: 0555 | fuse.S_IFREG},
81 | }
82 | var active []string
83 | c.fuseMu.Lock()
84 | for k := range c.fuseSockets {
85 | active = append(active, k)
86 | }
87 | c.fuseMu.Unlock()
88 |
89 | for _, a := range active {
90 | entries = append(entries, fuse.DirEntry{
91 | Name: a,
92 | Mode: 0777 | syscall.S_IFSOCK,
93 | })
94 | }
95 | return fs.NewListDirStream(entries), fs.OK
96 | }
97 |
98 | // Lookup implements the fs.NodeLookuper interface and returns an index node
99 | // (inode) for a symlink that points to a Unix domain socket. The Unix domain
100 | // socket is connected to the requested Cloud SQL instance. Lookup returns a
101 | // symlink (instead of the socket itself) so that multiple callers all use the
102 | // same Unix socket.
103 | func (c *Client) Lookup(_ context.Context, instance string, _ *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
104 | ctx := context.Background()
105 | if instance == "README" {
106 | return c.NewInode(ctx, &readme{}, fs.StableAttr{}), fs.OK
107 | }
108 |
109 | if _, err := parseConnName(instance); err != nil {
110 | c.logger.Debugf("could not parse instance connection name for %q: %v", instance, err)
111 | return nil, syscall.ENOENT
112 | }
113 |
114 | c.fuseMu.Lock()
115 | defer c.fuseMu.Unlock()
116 | if l, ok := c.fuseSockets[instance]; ok {
117 | c.logger.Debugf("found existing socket for instance %q", instance)
118 | return l.symlink.EmbeddedInode(), fs.OK
119 | }
120 |
121 | c.logger.Debugf("creating new socket for instance %q", instance)
122 | s, err := c.newSocketMount(
123 | ctx, withUnixSocket(*c.conf, c.fuseTempDir),
124 | nil, InstanceConnConfig{Name: instance},
125 | )
126 | if err != nil {
127 | c.logger.Errorf("could not create socket for %q: %v", instance, err)
128 | return nil, syscall.ENOENT
129 | }
130 |
131 | c.fuseWg.Add(1)
132 | go func() {
133 | defer c.fuseWg.Done()
134 | sErr := c.serveSocketMount(ctx, s)
135 | if sErr != nil {
136 | c.logger.Debugf("could not serve socket for instance %q: %v", instance, sErr)
137 | c.fuseMu.Lock()
138 | defer c.fuseMu.Unlock()
139 | delete(c.fuseSockets, instance)
140 | select {
141 | // Best effort attempt to send error.
142 | // If this send fails, it means the reading goroutine has
143 | // already pulled a value out of the channel and is no longer
144 | // reading any more values. In other words, we report only the
145 | // first error.
146 | case c.fuseExitCh <- sErr:
147 | default:
148 | return
149 | }
150 | }
151 | }()
152 |
153 | // Return a symlink that points to the actual Unix socket within the
154 | // temporary directory. For Postgres, return a symlink that points to the
155 | // directory which holds the ".s.PGSQL.5432" Unix socket.
156 | sl := &symlink{path: filepath.Join(c.fuseTempDir, instance)}
157 | c.fuseSockets[instance] = socketSymlink{
158 | socket: s,
159 | symlink: sl,
160 | }
161 | return c.NewInode(ctx, sl, fs.StableAttr{
162 | Mode: 0777 | fuse.S_IFLNK},
163 | ), fs.OK
164 | }
165 |
166 | func withUnixSocket(c Config, tmpDir string) *Config {
167 | c.UnixSocket = tmpDir
168 | return &c
169 | }
170 |
171 | func (c *Client) serveFuse(ctx context.Context, notify func()) error {
172 | srv, err := fs.Mount(c.fuseDir, c, &fs.Options{
173 | MountOptions: fuse.MountOptions{AllowOther: true},
174 | })
175 | if err != nil {
176 | return fmt.Errorf("FUSE mount failed: %q: %v", c.fuseDir, err)
177 | }
178 | c.fuseServerMu.Lock()
179 | c.fuseServer = srv
180 | c.fuseExitCh = make(chan error)
181 |
182 | c.fuseServerMu.Unlock()
183 | notify()
184 | select {
185 | case err = <-c.fuseExitCh:
186 | return err
187 | case <-ctx.Done():
188 | return ctx.Err()
189 | }
190 | }
191 |
192 | func (c *Client) fuseMounts() []*socketMount {
193 | var mnts []*socketMount
194 | c.fuseMu.Lock()
195 | for _, m := range c.fuseSockets {
196 | mnts = append(mnts, m.socket)
197 | }
198 | c.fuseMu.Unlock()
199 | return mnts
200 | }
201 |
202 | func (c *Client) unmountFUSE() error {
203 | c.fuseServerMu.Lock()
204 | defer c.fuseServerMu.Unlock()
205 | if c.fuseServer == nil {
206 | return nil
207 | }
208 | return c.fuseServer.Unmount()
209 | }
210 |
211 | func (c *Client) waitForFUSEMounts() { c.fuseWg.Wait() }
212 |
--------------------------------------------------------------------------------
/internal/proxy/proxy_other_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 | //go:build !windows
16 | // +build !windows
17 |
18 | package proxy_test
19 |
20 | import (
21 | "context"
22 | "os"
23 | "testing"
24 |
25 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/proxy"
26 | )
27 |
28 | var (
29 | pg = "proj:region:pg"
30 | pg2 = "proj:region:pg2"
31 | mysql = "proj:region:mysql"
32 | mysql2 = "proj:region:mysql2"
33 | sqlserver = "proj:region:sqlserver"
34 | sqlserver2 = "proj:region:sqlserver2"
35 | )
36 |
37 | func verifySocketPermissions(t *testing.T, addr string) {
38 | fi, err := os.Stat(addr)
39 | if err != nil {
40 | t.Fatalf("os.Stat(%v): %v", addr, err)
41 | }
42 | if fm := fi.Mode(); fm != 0777|os.ModeSocket {
43 | t.Fatalf("file mode: want = %v, got = %v", 0777|os.ModeSocket, fm)
44 | }
45 | }
46 |
47 | func TestFuseClosesGracefully(t *testing.T) {
48 | c, err := proxy.NewClient(
49 | context.Background(), nil, testLogger,
50 | &proxy.Config{
51 | FUSEDir: t.TempDir(),
52 | FUSETempDir: t.TempDir(),
53 | Token: "mytoken",
54 | },
55 | nil)
56 | if err != nil {
57 | t.Fatal(err)
58 | }
59 | if err := c.Close(); err != nil {
60 | t.Fatal(err)
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/internal/proxy/proxy_windows_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 proxy_test
16 |
17 | import (
18 | "strings"
19 | "testing"
20 | )
21 |
22 | var (
23 | pg = strings.ReplaceAll("proj:region:pg", ":", ".")
24 | pg2 = strings.ReplaceAll("proj:region:pg2", ":", ".")
25 | mysql = strings.ReplaceAll("proj:region:mysql", ":", ".")
26 | mysql2 = strings.ReplaceAll("proj:region:mysql2", ":", ".")
27 | sqlserver = strings.ReplaceAll("proj:region:sqlserver", ":", ".")
28 | sqlserver2 = strings.ReplaceAll("proj:region:sqlserver2", ":", ".")
29 | )
30 |
31 | func verifySocketPermissions(t *testing.T, addr string) {
32 | // On Linux and Darwin, we check that the socket named by addr exists with
33 | // os.Stat. That operation is not supported on Windows.
34 | // See https://github.com/microsoft/Windows-Containers/issues/97#issuecomment-887713195
35 | }
36 |
--------------------------------------------------------------------------------
/internal/proxy/unix.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 | //go:build !windows
16 | // +build !windows
17 |
18 | package proxy
19 |
20 | import "path/filepath"
21 |
22 | // UnixAddress is defined as a function to distinguish between Unix-based
23 | // implementations where the dir and inst are simply joined, and Windows-based
24 | // implementations where the inst must be further altered.
25 | func UnixAddress(dir, inst string) string {
26 | return filepath.Join(dir, inst)
27 | }
28 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 | // https://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 | //go:build !windows
16 | // +build !windows
17 |
18 | package main
19 |
20 | import (
21 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd"
22 | )
23 |
24 | func main() {
25 | cmd.Execute()
26 | }
27 |
--------------------------------------------------------------------------------
/main_windows.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 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 | // https://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 main
16 |
17 | import (
18 | "context"
19 | "errors"
20 | "os"
21 | "path/filepath"
22 | "time"
23 |
24 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd"
25 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log"
26 | "golang.org/x/sys/windows/svc"
27 | "gopkg.in/natefinch/lumberjack.v2"
28 | )
29 |
30 | type windowsService struct{}
31 |
32 | func (m *windowsService) Execute(_ []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
33 | // start the service
34 | changes <- svc.Status{State: svc.StartPending}
35 |
36 | // set up the log file
37 | exePath, err := os.Executable()
38 | if err != nil {
39 | changes <- svc.Status{State: svc.StopPending}
40 | return true, 101 // service specific exit code=101
41 | }
42 |
43 | logFolder := filepath.Join(filepath.Dir(exePath), "logs")
44 | os.Mkdir(logFolder, 0644) // ignore all errors
45 |
46 | logFile := &lumberjack.Logger{
47 | Filename: filepath.Join(logFolder, "cloud-sql-proxy.log"),
48 | MaxSize: 50, // megabytes
49 | MaxBackups: 10,
50 | MaxAge: 30, //days
51 | }
52 |
53 | logger := log.NewStdLogger(logFile, logFile)
54 | logger.Infof("Starting...")
55 |
56 | // start the main command
57 | ctx, cancel := context.WithCancel(context.Background())
58 | defer cancel()
59 |
60 | app := cmd.NewCommand(cmd.WithLogger(logger))
61 |
62 | cmdErrCh := make(chan error, 1)
63 | go func() {
64 | cmdErrCh <- app.ExecuteContext(ctx)
65 | }()
66 |
67 | // now running
68 | changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}
69 |
70 | var cmdErr error
71 |
72 | loop:
73 | for {
74 | select {
75 | case err := <-cmdErrCh:
76 | cmdErr = err
77 | break loop
78 |
79 | case c := <-r:
80 | switch c.Cmd {
81 | case svc.Interrogate:
82 | changes <- c.CurrentStatus
83 | // testing deadlock from https://code.google.com/archive/p/winsvc/issues/4
84 | time.Sleep(100 * time.Millisecond)
85 | changes <- c.CurrentStatus
86 |
87 | case svc.Stop, svc.Shutdown:
88 | cancel()
89 |
90 | default:
91 | logger.Errorf("unexpected control request #%d", c)
92 | }
93 | }
94 | }
95 |
96 | // start shutting down
97 | logger.Infof("Stopping...")
98 |
99 | changes <- svc.Status{State: svc.StopPending}
100 |
101 | if cmdErr != nil && errors.Is(cmdErr, context.Canceled) {
102 | logger.Errorf("Unexpected error: %v", cmdErr)
103 | return true, 2
104 | }
105 |
106 | return false, 0
107 | }
108 |
109 | func main() {
110 | // determine if running as a windows service
111 | inService, err := svc.IsWindowsService()
112 | if err != nil {
113 | os.Exit(99) // failed to determine service status
114 | }
115 |
116 | // running as service?
117 | if inService {
118 | err := svc.Run("cloud-sql-proxy", &windowsService{})
119 | if err != nil {
120 | os.Exit(100) // failed to execute service
121 | }
122 | return
123 | }
124 |
125 | // run as commandline
126 | cmd.Execute()
127 | }
128 |
--------------------------------------------------------------------------------
/migration-guide.md:
--------------------------------------------------------------------------------
1 | # Migrating from v1 to v2
2 |
3 | The Cloud SQL Auth Proxy v2 CLI interface maintains a close match to the v1
4 | interface. Migrating to v2 will require minimal changes. Below are a number
5 | of examples of v1 vs v2 invocations covering the most common uses. See
6 | [Flag Changes](#flag-changes) for details.
7 |
8 | All the examples below use `` as a placeholder for
9 | your instance connection name, e.g., `my-cool-project:us-central1:my-db`.
10 |
11 | ## Container Image Name Change
12 |
13 | As part of releasing a v2, we have updated the image name to be more descriptive.
14 | Compare:
15 |
16 | ```
17 | # v1
18 | gcr.io/cloudsql-docker/gce-proxy
19 | ```
20 |
21 | vs
22 |
23 | ```
24 | # v2
25 | gcr.io/cloud-sql-connectors/cloud-sql-proxy
26 | ```
27 |
28 | To update to the v2 container, make sure to update the image name.
29 |
30 | ## Behavior Differences
31 |
32 | In v1, when a client connected, the Proxy would first try to use a public IP
33 | and then attempt to use a private IP. In v2, the Proxy now defaults to public
34 | IP without trying private IP. If you want to use private IP, you must pass
35 | either the `--private-ip` flag or the query parameter. See the README for details.
36 |
37 | In some cases, the v1 behavior may be preferable. Use the `--auto-ip` flag to
38 | mimic v1 behavior. We generally recommend using deterministic IP address selection,
39 | but recognize in some legacy environments `--auto-ip` may be necessary.
40 |
41 | ## Executable Name Change
42 |
43 | Note that the name of the executable has changed, using hyphens rather than underscores:
44 |
45 | ```shell
46 | # v1
47 | ./cloud_sql_proxy
48 | ```
49 |
50 | vs
51 |
52 | ```shell
53 | # v2
54 | ./cloud-sql-proxy
55 | ```
56 |
57 | ## Sample Invocations
58 |
59 | ### Listen on TCP socket
60 |
61 | ```shell
62 | # v1
63 | ./cloud_sql_proxy -instances==tcp:5432
64 |
65 | # v2
66 | # Using automatic database port selection (MySQL 3306, Postgres 5432, SQL Server 1433)
67 | ./cloud-sql-proxy
68 | ```
69 |
70 | ### Listen on Unix Socket
71 |
72 | ```shell
73 | # v1
74 | ./cloud_sql_proxy -dir /cloudsql -instances=
75 |
76 | # v2
77 | ./cloud-sql-proxy --unix-socket /cloudsql
78 | ```
79 |
80 | ### Listen on multiple TCP sockets with incrementing ports
81 |
82 | ```shell
83 | # v1
84 | ./cloud_sql_proxy -instances==tcp:5000,=tcp:5001
85 |
86 | # v2
87 | # starts listener on port 5000, increments for additional listeners
88 | ./cloud-sql-proxy --port 5000
89 | ```
90 |
91 | ### Listen on multiple TCP sockets with non-sequential ports
92 |
93 | ```shell
94 | # v1
95 | ./cloud_sql_proxy -instances==tcp:6000,=tcp:7000
96 |
97 | # v2
98 | ./cloud-sql-proxy '?port=6000' '?port=7000'
99 | ```
100 |
101 | ### Listen on all interfaces
102 |
103 | ```shell
104 | # v1
105 | ./cloud_sql_proxy -instances==tcp:0.0.0.0:6000
106 |
107 | # v2
108 | ./cloud-sql-proxy --address 0.0.0.0 --port 6000
109 | ```
110 |
111 | ## Environment variable changes
112 |
113 | In v1 it was possible to do this:
114 |
115 | ``` shell
116 | export INSTANCES="=tcp:3306,=tcp:5432"
117 |
118 | ./cloud_sql_proxy
119 | ```
120 |
121 | In v2, we've significantly expanded the support for environment variables.
122 | All flags can be set with an environment variable including instance connection names.
123 |
124 | For example, in v2 this is possible:
125 |
126 | ``` shell
127 | export CSQL_PROXY_INSTANCE_CONNECTION_NAME_0="?port=3306"
128 | export CSQL_PROXY_INSTANCE_CONNECTION_NAME_1="?port=5432"
129 |
130 | export CSQL_PROXY_AUTO_IAM_AUTHN=true
131 |
132 | ./cloud-sql-proxy
133 | ```
134 |
135 | See the [help message][] for more details.
136 |
137 | [help message]: https://github.com/GoogleCloudPlatform/cloud-sql-proxy/blob/10bec27e4d44c14fe9e68f25fef6c373324e8bab/cmd/root.go#L240-L264
138 |
139 | ## Flag Changes
140 |
141 | The following table lists in alphabetical order v1 flags and their v2 version.
142 |
143 | - 🗓️: Planned
144 | - ❌: Not supported in V2
145 | - 🤔: Unplanned, but has open feature request
146 |
147 | | v1 | v2 | Notes |
148 | | --------------------------- | --------------------- | ------------------------------------------------------------------------------------ |
149 | | check_region | ❌ | |
150 | | credential_file | credentials-file | |
151 | | dir | unix-socket | |
152 | | enable_iam_login | auto-iam-authn | |
153 | | fd_rlimit | 🤔 | [Feature Request](https://github.com/GoogleCloudPlatform/cloudsql-proxy/issues/1258) |
154 | | fuse | fuse | |
155 | | fuse_tmp | fuse-temp-dir | |
156 | | health_check_port | http-port | Use --http-address=0.0.0.0 when using a health check in Kubernetes |
157 | | host | sqladmin-api-endpoint | |
158 | | instances_metadata | 🤔 | [Feature Request](https://github.com/GoogleCloudPlatform/cloudsql-proxy/issues/1259) |
159 | | ip_address_types | private-ip | Defaults to public. To connect to a private IP, you must add the --private-ip flag |
160 | | log_debug_stdout | ❌ | v2 logs to stdout, errors to stderr by default |
161 | | max_connections | max-connections | |
162 | | projects | ❌ | v2 prefers explicit connection configuration to avoid user error |
163 | | quiet | quiet | quiet disables all logging except errors |
164 | | quota_project | quota-project | |
165 | | refresh_config_throttle | ❌ | |
166 | | skip_failed_instance_config | ❌ | This flag was only necessary with Unix sockets. Use TCP sockets to avoid failed startup. |
167 | | structured_logs | structured-logs | |
168 | | term_timeout | max-sigterm-delay | |
169 | | token | token | |
170 | | use_http_health_check | health-check | |
171 | | verbose | ❌ | |
172 | | version | version | |
173 |
--------------------------------------------------------------------------------
/tests/common_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 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 | // https://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 tests contains end to end tests meant to verify the Cloud SQL Auth
16 | // proxy works as expected when executed as a binary.
17 | //
18 | // Required flags:
19 | //
20 | // -mysql_conn_name, -db_user, -db_pass
21 | package tests
22 |
23 | import (
24 | "bufio"
25 | "context"
26 | "errors"
27 | "flag"
28 | "fmt"
29 | "io"
30 | "os"
31 | "strings"
32 |
33 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cmd"
34 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log"
35 | )
36 |
37 | var (
38 | impersonatedUser = flag.String(
39 | "impersonated_user",
40 | os.Getenv("IMPERSONATED_USER"),
41 | "Name of the service account that supports impersonation (impersonator must have roles/iam.serviceAccountTokenCreator)",
42 | )
43 | )
44 |
45 | // ProxyExec represents an execution of the Cloud SQL Auth Proxy.
46 | type ProxyExec struct {
47 | Out io.ReadCloser
48 |
49 | cmd *cmd.Command
50 | cancel context.CancelFunc
51 | closers []io.Closer
52 | done chan bool // closed once the cmd is completed
53 | err error
54 | }
55 |
56 | // StartProxy returns a proxyExec representing a running instance of the proxy.
57 | func StartProxy(ctx context.Context, args ...string) (*ProxyExec, error) {
58 | ctx, cancel := context.WithCancel(ctx)
59 | // Open a pipe for tracking the output from the cmd
60 | pr, pw, err := os.Pipe()
61 | if err != nil {
62 | cancel()
63 | return nil, fmt.Errorf("unable to open stdout pipe: %w", err)
64 | }
65 |
66 | c := cmd.NewCommand(cmd.WithLogger(log.NewStdLogger(pw, pw)))
67 | c.SetArgs(args)
68 | c.SetOut(pw)
69 | c.SetErr(pw)
70 |
71 | p := &ProxyExec{
72 | Out: pr,
73 | cmd: c,
74 | cancel: cancel,
75 | closers: []io.Closer{pr, pw},
76 | done: make(chan bool),
77 | }
78 | // Start the command in the background
79 | go func() {
80 | defer close(p.done)
81 | defer cancel()
82 | p.err = c.ExecuteContext(ctx)
83 | }()
84 | return p, nil
85 | }
86 |
87 | // Stop sends the TERM signal to the proxy and returns.
88 | func (p *ProxyExec) Stop() {
89 | p.cancel()
90 | }
91 |
92 | // Waits until the execution is completed and returns any error.
93 | func (p *ProxyExec) Wait(ctx context.Context) error {
94 | select {
95 | case <-ctx.Done():
96 | return ctx.Err()
97 | case <-p.done:
98 | return p.err
99 | }
100 | }
101 |
102 | // Done returns true if the proxy has exited.
103 | func (p *ProxyExec) Done() bool {
104 | select {
105 | case <-p.done:
106 | return true
107 | default:
108 | }
109 | return false
110 | }
111 |
112 | // Close releases any resources associated with the instance.
113 | func (p *ProxyExec) Close() {
114 | p.cancel()
115 | for _, c := range p.closers {
116 | c.Close()
117 | }
118 | }
119 |
120 | // WaitForServe waits until the proxy ready to serve traffic by waiting for a
121 | // known log message (i.e. "ready for new connections"). Returns any output
122 | // from the proxy while starting or any errors experienced before the proxy was
123 | // ready to server.
124 | func (p *ProxyExec) WaitForServe(ctx context.Context) (string, error) {
125 | in := bufio.NewReader(p.Out)
126 | for {
127 | select {
128 | case <-ctx.Done():
129 | // dump all output and return it as an error
130 | all, err := io.ReadAll(in)
131 | if err != nil {
132 | return "", err
133 | }
134 | return "", errors.New(string(all))
135 | default:
136 | }
137 | s, err := in.ReadString('\n')
138 | if err != nil {
139 | return "", err
140 | }
141 | if strings.Contains(s, "Error") || strings.Contains(s, "error") {
142 | return "", errors.New(s)
143 | }
144 | if strings.Contains(s, "ready for new connections") {
145 | return s, nil
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/tests/connection_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 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 | // https://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 tests
16 |
17 | import (
18 | "context"
19 | "database/sql"
20 | "net/http"
21 | "net/http/httputil"
22 | "os"
23 | "testing"
24 | "time"
25 |
26 | "golang.org/x/oauth2"
27 | "golang.org/x/oauth2/google"
28 | )
29 |
30 | const connTestTimeout = time.Minute
31 |
32 | // removeAuthEnvVar retrieves an OAuth2 token and a path to a service account key
33 | // and then unsets GOOGLE_APPLICATION_CREDENTIALS. It returns a cleanup function
34 | // that restores the original setup.
35 | func removeAuthEnvVar(t *testing.T) (*oauth2.Token, string, func()) {
36 | ts, err := google.DefaultTokenSource(context.Background(),
37 | "https://www.googleapis.com/auth/cloud-platform",
38 | )
39 | if err != nil {
40 | t.Errorf("failed to resolve token source: %v", err)
41 | }
42 | tok, err := ts.Token()
43 | if err != nil {
44 | t.Errorf("failed to get token: %v", err)
45 | }
46 | if *ipType != "public" {
47 | return tok, "", func() {}
48 | }
49 | path, ok := os.LookupEnv("GOOGLE_APPLICATION_CREDENTIALS")
50 | if !ok {
51 | t.Fatalf("GOOGLE_APPLICATION_CREDENTIALS was not set in the environment")
52 | }
53 | if err := os.Unsetenv("GOOGLE_APPLICATION_CREDENTIALS"); err != nil {
54 | t.Fatalf("failed to unset GOOGLE_APPLICATION_CREDENTIALS")
55 | }
56 | return tok, path, func() {
57 | os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", path)
58 | }
59 | }
60 |
61 | func keyfile(t *testing.T) string {
62 | path := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
63 | if path == "" {
64 | t.Fatal("GOOGLE_APPLICATION_CREDENTIALS not set")
65 | }
66 | creds, err := os.ReadFile(path)
67 | if err != nil {
68 | t.Fatalf("io.ReadAll(): %v", err)
69 | }
70 | return string(creds)
71 | }
72 | func proxyConnTestWithReady(t *testing.T, args []string, driver, dsn string, ready func() error) {
73 | ctx, cancel := context.WithTimeout(context.Background(), connTestTimeout)
74 | defer cancel()
75 | // Start the proxy
76 | p, err := StartProxy(ctx, args...)
77 | if err != nil {
78 | t.Fatalf("unable to start proxy: %v", err)
79 | }
80 | defer p.Close()
81 | output, err := p.WaitForServe(ctx)
82 | if err != nil {
83 | t.Fatalf("unable to verify proxy was serving: %s \n %s", err, output)
84 | }
85 | if err := ready(); err != nil {
86 | t.Fatalf("proxy was not ready: %v", err)
87 | }
88 |
89 | // Connect to the instance
90 | db, err := sql.Open(driver, dsn)
91 | if err != nil {
92 | t.Fatalf("unable to connect to db: %s", err)
93 | }
94 | defer db.Close()
95 | _, err = db.Exec("SELECT 1;")
96 | if err != nil {
97 | t.Fatalf("unable to exec on db: %s", err)
98 | }
99 | }
100 |
101 | // proxyConnTest is a test helper to verify the proxy works with a basic connectivity test.
102 | func proxyConnTest(t *testing.T, args []string, driver, dsn string) {
103 | proxyConnTestWithReady(t, args, driver, dsn, func() error { return nil })
104 | }
105 |
106 | // testHealthCheck verifies that when a proxy client serves the given instance,
107 | // the readiness endpoint serves http.StatusOK.
108 | func testHealthCheck(t *testing.T, connName string) {
109 | t.Helper()
110 | ctx, cancel := context.WithTimeout(context.Background(), connTestTimeout)
111 | defer cancel()
112 |
113 | args := []string{connName, "--health-check"}
114 | // Start the proxy.
115 | p, err := StartProxy(ctx, args...)
116 | if err != nil {
117 | t.Fatalf("unable to start proxy: %v", err)
118 | }
119 | defer p.Close()
120 | _, err = p.WaitForServe(ctx)
121 | if err != nil {
122 | t.Fatal(err)
123 | }
124 |
125 | var (
126 | gErr error
127 | resp *http.Response
128 | )
129 | for i := 0; i < 10; i++ {
130 | resp, gErr = http.Get("http://localhost:9090/readiness")
131 | if gErr != nil {
132 | time.Sleep(100 * time.Millisecond)
133 | continue
134 | }
135 | if resp.StatusCode != http.StatusOK {
136 | time.Sleep(100 * time.Millisecond)
137 | continue
138 | }
139 | return // The response is OK, the test passes.
140 | }
141 | if gErr != nil {
142 | t.Fatalf("HTTP GET failed: %v", gErr)
143 | }
144 | respBody, dErr := httputil.DumpResponse(resp, true)
145 | if dErr != nil {
146 | t.Fatalf("failed to dump HTTP response: %v", dErr)
147 | }
148 | t.Fatalf("HTTP GET failed: response =\n%v", string(respBody))
149 | }
150 |
--------------------------------------------------------------------------------
/tests/fuse_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 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 | // https://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 | //go:build !windows && !darwin
16 |
17 | package tests
18 |
19 | import (
20 | "fmt"
21 | "os"
22 | "testing"
23 | "time"
24 |
25 | "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/proxy"
26 | )
27 |
28 | func TestPostgresFUSEConnect(t *testing.T) {
29 | if v := os.Getenv("IP_TYPE"); v == "private" || v == "psc" {
30 | t.Skipf("skipping test because IP_TYPE is set to %v", v)
31 | }
32 | if testing.Short() {
33 | t.Skip("skipping Postgres integration tests")
34 | }
35 | tmpDir, cleanup := createTempDir(t)
36 | defer cleanup()
37 |
38 | host := proxy.UnixAddress(tmpDir, *postgresConnName)
39 | dsn := fmt.Sprintf(
40 | "host=%s user=%s password=%s database=%s sslmode=disable",
41 | host, *postgresUser, *postgresPass, *postgresDB,
42 | )
43 | testFUSE(t, tmpDir, host, dsn)
44 | }
45 |
46 | func testFUSE(t *testing.T, tmpDir, host string, dsn string) {
47 | tmpDir2, cleanup2 := createTempDir(t)
48 | defer cleanup2()
49 |
50 | waitForFUSE := func() error {
51 | var err error
52 | for i := 0; i < 10; i++ {
53 | _, err = os.Stat(host)
54 | if err == nil {
55 | return nil
56 | }
57 | time.Sleep(500 * time.Millisecond)
58 | }
59 | return fmt.Errorf("failed to find FUSE mounted Unix socket: %v", err)
60 | }
61 |
62 | tcs := []struct {
63 | desc string
64 | dbUser string
65 | args []string
66 | }{
67 | {
68 | desc: "using default fuse",
69 | args: []string{fmt.Sprintf("--fuse=%s", tmpDir), fmt.Sprintf("--fuse-tmp-dir=%s", tmpDir2)},
70 | },
71 | {
72 | desc: "using fuse with auto-iam-authn",
73 | args: []string{fmt.Sprintf("--fuse=%s", tmpDir), "--auto-iam-authn"},
74 | },
75 | }
76 |
77 | for _, tc := range tcs {
78 | t.Run(tc.desc, func(t *testing.T) {
79 | proxyConnTestWithReady(t, tc.args, "pgx", dsn, waitForFUSE)
80 | // given the kernel some time to unmount the fuse
81 | time.Sleep(100 * time.Millisecond)
82 | })
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/tests/other_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 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 | // https://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 | // other_test runs various tests that are database agnostic.
16 | package tests
17 |
18 | import (
19 | "bufio"
20 | "context"
21 | "os"
22 | "strings"
23 | "testing"
24 | "time"
25 | )
26 |
27 | func TestVersion(t *testing.T) {
28 | ctx := context.Background()
29 |
30 | data, err := os.ReadFile("../cmd/version.txt")
31 | if err != nil {
32 | t.Fatalf("failed to read version.txt: %v", err)
33 | }
34 | want := strings.TrimSpace(string(data))
35 |
36 | // Start the proxy
37 | p, err := StartProxy(ctx, "--version")
38 | if err != nil {
39 | t.Fatalf("proxy start failed: %v", err)
40 | }
41 | defer p.Close()
42 |
43 | // Assume the proxy should be able to print "version" relatively quickly
44 | ctx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
45 | defer cancel()
46 | err = p.Wait(ctx)
47 | if err != nil {
48 | t.Fatalf("proxy exited unexpectedly: %v", err)
49 | }
50 | output, err := bufio.NewReader(p.Out).ReadString('\n')
51 | if err != nil {
52 | t.Fatalf("failed to read output from proxy: %v", err)
53 | }
54 | if !strings.Contains(output, want) {
55 | t.Errorf("proxy did not return correct version: want %q, got %q", want, output)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/sqlserver_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 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 | // https://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 | // sqlserver_test runs various tests against a SqlServer flavored Cloud SQL instance.
16 | package tests
17 |
18 | import (
19 | "flag"
20 | "fmt"
21 | "os"
22 | "testing"
23 |
24 | _ "github.com/microsoft/go-mssqldb"
25 | )
26 |
27 | var (
28 | sqlserverConnName = flag.String("sqlserver_conn_name", os.Getenv("SQLSERVER_CONNECTION_NAME"), "Cloud SQL SqlServer instance connection name, in the form of 'project:region:instance'.")
29 | sqlserverUser = flag.String("sqlserver_user", os.Getenv("SQLSERVER_USER"), "Name of database user.")
30 | sqlserverPass = flag.String("sqlserver_pass", os.Getenv("SQLSERVER_PASS"), "Password for the database user; be careful when entering a password on the command line (it may go into your terminal's history).")
31 | sqlserverDB = flag.String("sqlserver_db", os.Getenv("SQLSERVER_DB"), "Name of the database to connect to.")
32 | )
33 |
34 | func requireSQLServerVars(t *testing.T) {
35 | switch "" {
36 | case *sqlserverConnName:
37 | t.Fatal("'sqlserver_conn_name' not set")
38 | case *sqlserverUser:
39 | t.Fatal("'sqlserver_user' not set")
40 | case *sqlserverPass:
41 | t.Fatal("'sqlserver_pass' not set")
42 | case *sqlserverDB:
43 | t.Fatal("'sqlserver_db' not set")
44 | }
45 | }
46 |
47 | func sqlserverDSN() string {
48 | return fmt.Sprintf("sqlserver://%s:%s@127.0.0.1?database=%s",
49 | *sqlserverUser, *sqlserverPass, *sqlserverDB)
50 | }
51 |
52 | func TestSQLServerTCP(t *testing.T) {
53 | if testing.Short() {
54 | t.Skip("skipping SQL Server integration tests")
55 | }
56 | requireSQLServerVars(t)
57 | // Prepare the initial arguments
58 | args := []string{*sqlserverConnName}
59 | // Add the IP type flag using the helper
60 | args = AddIPTypeFlag(args)
61 | // Run the test
62 | proxyConnTest(t, args, "sqlserver", sqlserverDSN())
63 | }
64 |
65 | func TestSQLServerImpersonation(t *testing.T) {
66 | if testing.Short() {
67 | t.Skip("skipping SQL Server integration tests")
68 | }
69 | requireSQLServerVars(t)
70 | // Prepare the initial arguments
71 | args := []string{
72 | "--impersonate-service-account", *impersonatedUser,
73 | *sqlserverConnName,
74 | }
75 | // Add the IP type flag using the helper
76 | args = AddIPTypeFlag(args)
77 | // Run the test
78 | proxyConnTest(t, args, "sqlserver", sqlserverDSN())
79 | }
80 |
81 | func TestSQLServerAuthentication(t *testing.T) {
82 | if testing.Short() {
83 | t.Skip("skipping SQL Server integration tests")
84 | }
85 | requireSQLServerVars(t)
86 |
87 | var creds string
88 | if *ipType == "public" {
89 | creds = keyfile(t)
90 | }
91 | tok, path, cleanup := removeAuthEnvVar(t)
92 | defer cleanup()
93 |
94 | tcs := []struct {
95 | desc string
96 | args []string
97 | }{
98 | {
99 | desc: "with token",
100 | args: []string{"--token", tok.AccessToken, *sqlserverConnName},
101 | },
102 | {
103 | desc: "with token and impersonation",
104 | args: []string{
105 | "--token", tok.AccessToken,
106 | "--impersonate-service-account", *impersonatedUser,
107 | *sqlserverConnName},
108 | },
109 | }
110 | if *ipType == "public" {
111 | additionaTcs := []struct {
112 | desc string
113 | args []string
114 | }{
115 | {
116 | desc: "with credentials file",
117 | args: []string{"--credentials-file", path, *sqlserverConnName},
118 | },
119 | {
120 | desc: "with credentials file and impersonation",
121 | args: []string{
122 | "--credentials-file", path,
123 | "--impersonate-service-account", *impersonatedUser,
124 | *sqlserverConnName,
125 | },
126 | },
127 | {
128 | desc: "with credentials JSON",
129 | args: []string{"--json-credentials", string(creds), *sqlserverConnName},
130 | },
131 | {
132 | desc: "with credentials JSON and impersonation",
133 | args: []string{
134 | "--json-credentials", string(creds),
135 | "--impersonate-service-account", *impersonatedUser,
136 | *sqlserverConnName,
137 | },
138 | },
139 | }
140 | tcs = append(tcs, additionaTcs...)
141 | }
142 | for _, tc := range tcs {
143 | t.Run(tc.desc, func(t *testing.T) {
144 | proxyConnTest(t, AddIPTypeFlag(tc.args), "sqlserver", sqlserverDSN())
145 | })
146 | }
147 | }
148 |
149 | func TestSQLServerGcloudAuth(t *testing.T) {
150 | if testing.Short() {
151 | t.Skip("skipping SQL Server integration tests")
152 | }
153 | if v := os.Getenv("IP_TYPE"); v == "private" || v == "psc" {
154 | t.Skipf("skipping test because IP_TYPE is set to %v", v)
155 | }
156 | requireSQLServerVars(t)
157 |
158 | tcs := []struct {
159 | desc string
160 | args []string
161 | }{
162 | {
163 | desc: "gcloud user authentication",
164 | args: []string{"--gcloud-auth", *sqlserverConnName},
165 | },
166 | {
167 | desc: "gcloud user authentication with impersonation",
168 | args: []string{
169 | "--gcloud-auth",
170 | "--impersonate-service-account", *impersonatedUser,
171 | *sqlserverConnName},
172 | },
173 | }
174 | for _, tc := range tcs {
175 | t.Run(tc.desc, func(t *testing.T) {
176 | proxyConnTest(t, AddIPTypeFlag(tc.args), "sqlserver", sqlserverDSN())
177 | })
178 | }
179 | }
180 |
181 | func TestSQLServerHealthCheck(t *testing.T) {
182 | if testing.Short() {
183 | t.Skip("skipping SQL Server integration tests")
184 | }
185 | testHealthCheck(t, *sqlserverConnName)
186 | }
187 |
--------------------------------------------------------------------------------
/windows-service-guide.md:
--------------------------------------------------------------------------------
1 | # Cloud SQL Auth Proxy Windows Service Guide
2 |
3 | This document covers running the *Cloud SQL Auth Proxy* as service
4 | on the Windows operating system.
5 |
6 | It was originally built and tested using Go 1.20.2 on Windows Server 2019.
7 |
8 | ## Install the Windows Service
9 |
10 | Prerequisites: A built binary for Windows of the Cloud SQL Auth Proxy is required. Either build it from source or [download a release](https://github.com/GoogleCloudPlatform/cloud-sql-proxy/releases) of a Windows pre-built version, e.g. `cloud-sql-proxy.x64.exe`.
11 |
12 | First, install the binary by:
13 |
14 | 1. Create a new empty folder, e.g. `C:\Program Files\cloud-sql-proxy`
15 | 2. Copy the binary and helper batch files
16 | 3. Modify the batch files as needed:
17 | - `SERVICE` is the Windows internal service name (as shown in the Task Manager)
18 | - `DISPLAYNAME` is the service name (as shown in the Windows Administration Console (MMC))
19 | - `CREDENTIALSFILE` is the *full* path to the credentials file, where `%~dp0` points to the full path of the script file folder.
20 | - `CONNECTIONNAME` is the Google SQL connection name in the format of `project-id:region:db-instance`
21 | - Please note that the `--credentials-file \"%CREDENTIALSFILE%\"` argument is optional and is not needed if the local machine runs within the Google Cloud Compute Engine and "defaults" to the VM instance service account.
22 | 4. Grant *read & execute* access to the `Network Service` user
23 | 5. Create a `logs` sub-folder, e.g. `C:\Program Files\cloud-sql-proxy\logs`
24 | 6. Grant *modify* access to the `Network Service` user
25 | 7. Run the `windows_install_service.bat` batch file within an *elevated* command line prompt (read: *Run as Administrator*).
26 |
27 | After that, perform the setup:
28 |
29 | 1. Copy the JSON credentials file, if required
30 | 2. Modify the `windows_install_service.bat` file to your needs
31 | 3. Run the `windows_install_service.bat` file from the commandline
32 |
33 | Please see the FAQ below for common error messages.
34 |
35 | ## Uninstall the Windows Service
36 |
37 | To uninstall the Windows Service, perform the following steps:
38 |
39 | 1. Modify the `windows_remove_service.bat` file to your needs
40 | 2. Run the `windows_remove_service.bat` file from the commandline
41 |
42 | ## FAQ
43 |
44 | ### Error Message: *Access is denied*
45 |
46 | The error message `Access is denied.` (or `System error 5 has occurred.`) occurs when
47 | trying to start the installed service but the service account does not have access
48 | to the service's file directory.
49 |
50 | Usually this is the *Network Service* built-in user.
51 |
52 | Please note that write access is also required for creating and managing the log files, e.g.:
53 |
54 | - `cloud-sql-proxy.log`
55 | - `cloud-sql-proxy-2016-11-04T18-30-00.000.log`
56 |
57 | ### Error Message: *The specified service has been marked for deletion.*
58 |
59 | The error message `The specified service has been marked for deletion.` occurs when
60 | reinstalling the service and the previous deletion request could not be completed
61 | (e.g. because the service was still running or opened in the service manager).
62 |
63 | In this case, the local machine needs to be restarted.
64 |
65 | ### Why not running as the *System* user?
66 |
67 | Since the Cloud Proxy does not require and file system access, besides the log files,
68 | extensive operating system access is not required.
69 |
70 | The *Network Service* accounts allow binding ports while not granting
71 | access to file system resources.
72 |
73 | ### Why not using *Automatic (Delayed Start)* startup type?
74 |
75 | The service is installed in the *Automatic* startup type, by default.
76 |
77 | The alternative *Automatic (Delayed Start)* startup type was introduced
78 | by Microsoft for services that are not required for operating system operations
79 | like Windows Update and similar services.
80 |
81 | However, if the primary purpose of the local machine is to provide services
82 | which require access to the cloud database, then the start of the service
83 | should not be delayed.
84 |
85 | Delayed services might be started even minutes after operating system startup.
86 |
--------------------------------------------------------------------------------
/windows_install_service.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | setlocal
4 |
5 | set SERVICE=cloud-sql-proxy
6 | set DISPLAYNAME=Google Cloud SQL Auth Proxy
7 | set CREDENTIALSFILE=%~dp0key.json
8 | set CONNECTIONNAME=project-id:region:db-instance
9 |
10 | sc.exe create "%SERVICE%" binPath= "\"%~dp0cloud-sql-proxy.exe\" --credentials-file \"%CREDENTIALSFILE%\" %CONNECTIONNAME%" obj= "NT AUTHORITY\Network Service" start= auto displayName= "%DISPLAYNAME%"
11 | sc.exe failure "%SERVICE%" reset= 0 actions= restart/0/restart/0/restart/0
12 | net start "%SERVICE%"
13 |
14 | endlocal
15 |
--------------------------------------------------------------------------------
/windows_remove_service.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | setlocal
4 |
5 | set SERVICE=cloud-sql-proxy
6 |
7 | net stop "%SERVICE%"
8 | sc.exe delete "%SERVICE%"
9 |
10 | endlocal
11 |
--------------------------------------------------------------------------------