├── .DS_Store ├── .github ├── dependabot.yml └── workflow │ └── workflow.yml ├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── README.md ├── cloudsql.go ├── cmd ├── check.go ├── connect.go ├── disconnect.go ├── doctor.go ├── list.go ├── nobell.go └── root.go ├── go.mod └── go.sum /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-okayama/cloudsql/dca885e00cfff0baca6443c3b7adf512710d80da/.DS_Store -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflow/workflow.yml: -------------------------------------------------------------------------------- 1 | name: workflow 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.18 20 | - name: Run GoReleaser 21 | uses: goreleaser/goreleaser-action@v2 22 | with: 23 | distribution: goreleaser 24 | version: latest 25 | args: release --rm-dist 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} 29 | VERSION: ${{ github.ref_name }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | .idea 8 | release/* 9 | .envrc 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | dist/ 21 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod download 4 | builds: 5 | - env: 6 | - CGO_ENABLED=0 7 | main: ./ 8 | binary: cloudsql 9 | archives: 10 | - name_template: >- 11 | {{ .ProjectName }}_ 12 | {{- .Version }}_ 13 | {{- if eq .Os "freebsd" }}FreeBSD 14 | {{- else }}{{ title .Os }}{{ end }}_ 15 | {{- if eq .Arch "amd64" }}64bit 16 | {{- else if eq .Arch "386" }}32bit 17 | {{- else if eq .Arch "arm64" }}ARM64 18 | {{- else }}{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ end }} 19 | checksum: 20 | name_template: 'checksums.txt' 21 | snapshot: 22 | name_template: "{{ .Tag }}-next" 23 | changelog: 24 | sort: asc 25 | filters: 26 | exclude: 27 | - '^docs:' 28 | - '^test:' 29 | metadata: 30 | notarize: 31 | gomod: 32 | github_urls: 33 | release: 34 | snapcrafts: 35 | - 36 | dist: 37 | source: 38 | project_name: 39 | kos: 40 | - 41 | signs: 42 | - 43 | git: 44 | scoops: 45 | - 46 | aurs: 47 | - 48 | report_sizes: false 49 | publishers: 50 | - 51 | nix: 52 | - 53 | nfpms: 54 | - 55 | blobs: 56 | - 57 | universal_binaries: 58 | - 59 | krews: 60 | - 61 | sboms: 62 | - 63 | chocolateys: 64 | - 65 | gitlab_urls: 66 | artifactories: 67 | - 68 | gitea_urls: 69 | force_token: github 70 | env: 71 | - 72 | version: 2 73 | dockers: 74 | - 75 | env_files: 76 | announce: 77 | uploads: 78 | - 79 | winget: 80 | - 81 | docker_manifests: 82 | - 83 | milestones: 84 | - 85 | upx: 86 | - 87 | brews: 88 | - repository: 89 | name: homebrew-cloudsql 90 | owner: s-okayama 91 | token: "{{ .Env.GITHUB_TOKEN }}" 92 | directory: Formula 93 | homepage: https://github.com/s-okayama/cloudsql 94 | description: cloudsql 95 | test: | 96 | system "#{bin}/cloudsql --version" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 s-okayama 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cloudsql 2 | ## Overview 3 | Save time for gcloud sql instances list and cloud-sql-proxy command. 4 | ![output](https://user-images.githubusercontent.com/66108143/201511654-c577bc7a-bcdb-45e9-a64c-f0abe4792680.gif) 5 | 6 | ## Install 7 | - Information 8 | __When cloud-sql-proxy was updated from version 1 to 2, the command was changed from cloud_sql_proxy to cloud-sql-proxy (underscores are now hyphens). Please upgrade to the new version (v2) if you are using the old version (v1).__ 9 | 10 | 11 | - Preparation  12 | ``` 13 | 1. Install Cloud SQL Auth Proxy 14 | https://cloud.google.com/sql/docs/postgres/sql-proxy?hl=ja#install 15 | $ chmod +x cloud-sql-proxy 16 | $ sudo mv cloud-sql-proxy /usr/local/bin/ 17 | 18 | 2. Install Gcloud CLI & Auth 19 | https://cloud.google.com/sdk/docs/install?hl=ja 20 | $ gcloud auth login 21 | $ gcloud auth application-default login 22 | 23 | 3. Check Preparation 24 | $ gcloud --version 25 | $ cloud-sql-proxy --version 26 | ``` 27 | - Install 28 | Chose `Download` or `Build` or `brew install` 29 | ``` 30 | - brew install 31 | $ brew tap s-okayama/homebrew-cloudsql 32 | $ brew install s-okayama/cloudsql/cloudsql 33 | 34 | - Download 35 | https://github.com/s-okayama/cloudsql/releases 36 | $ chmod +x cloudsql 37 | $ sudo mv cloudsql /usr/local/bin/ 38 | 39 | - Build 40 | $ git clone git@github.com:s-okayama/cloudsql.git 41 | $ go build 42 | $ chmod +x cloudsql 43 | $ sudo mv cloudsql /usr/local/bin/ 44 | ``` 45 | 46 | - Set Config File 47 | Set **Your GCP Project ID** to a Config File 48 | ``` 49 | $ mkdir ~/.cloudsql/ 50 | $ vim ~/.cloudsql/config 51 | project-dev 52 | project-prd 53 | ``` 54 | 55 | ## Usage 56 | - help 57 | ``` 58 | $ cloudsql 59 | CloudSQL CLI 60 | 61 | Usage: 62 | cloudsql [command] 63 | 64 | Available Commands: 65 | completion Generate the autocompletion script for the specified shell 66 | connect connect to cloudsql instance 67 | disconnect disconnect cloudsql instance 68 | doctor troubleshooting 69 | help Help about any command 70 | list list connected cloudsql instance 71 | version Print the version number of cloudsql 72 | 73 | Flags: 74 | -h, --help help for cloudsql 75 | ``` 76 | 77 | - connect 78 | ``` 79 | # You can set sub-command "--port" like this. → $ cloudsql connect --port 12345 80 | $ cloudsql connect 81 | Use the arrow keys to navigate: ↓ ↑ → ← 82 | ? Select Project: 83 | project-prd 84 | ▸ project-dev 85 | ✔ project-dev 86 | 87 | ? Select Project:project-dev 88 | ▸ project-dev:asia-northeast1:stg-hoge-db-fecdf019 89 | project-dev:asia-northeast1:stg-postgres-0e80e42e 90 | project-dev:asia-northeast1:stg-mysql-db-8347a466 91 | project-dev:asia-northeast1:stg-metabase-db-3413a639 92 | ✔ project-dev:asia-northeast1:stg-postgres-db-0e80e42e 93 | 94 | ? Select Database: 95 | ▸ postgres 96 | test-db 97 | Connecting Instance 98 | 2022/11/06 21:21:45 Cloudsql proxy process is running in background, process_id: 65464 99 | Can connect using: 100 | psql -h localhost -U yamada.taro@gmail.com -p 5432 -d postgres 101 | ``` 102 | 103 | - disconnect 104 | ``` 105 | $ cloudsql disconnect 106 | Use the arrow keys to navigate: ↓ ↑ → ← 107 | ? Select Instance to disconnect: 108 | ▸ project-dev:asia-northeast1:stg-postgres-0e80e42e=tcp:5432 109 | ``` 110 | 111 | - doctor 112 | ``` 113 | $ cloudsql doctor 114 | cloudsql % go run cloudsql.go doctor 115 | gcloud version: Google Cloud SDK 420.0.0 116 | Authenticated user account: xxxxx@example.com 117 | cloud-sql-proxy version: cloud-sql-proxy version 2.0.0 118 | psql version: psql (PostgreSQL) 15.2 119 | mysql version: mysql Ver 8.0.32 for macos12.6 on arm64 (Homebrew) 120 | config file: ok 121 | Your system is All Green! 122 | ``` 123 | 124 | ## ToDo 125 | - [x] Disable sound for Mac 126 | → Add nobell.go 127 | - [x] Add search feature 128 | → search by / 129 | - [x] Add Select Database feature 130 | → Add getDatabase & get listDatabase func 131 | - [x] Add proxy & connect mode 132 | - [x] Add Doctor feature(check cloud-sql-proxy & postgres & mysql) 133 | → Add doctor command 134 | - [x] brew install 135 | - [x] Support PostgreSQL cloud-sql-proxy version 2.0.0 136 | - [x] Support MySQL cloud-sql-proxy version 2.0.0 137 | - [x] Display error when Port is already in use 138 | -------------------------------------------------------------------------------- /cloudsql.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "s-okayama/cloudsql/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /cmd/check.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fatih/color" 6 | "os" 7 | "os/exec" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | func checkVersionCloudSqlProxy() { 13 | red := color.New(color.FgRed) 14 | boldRed := red.Add(color.Bold) 15 | green := color.New(color.FgGreen) 16 | boldGreen := green.Add(color.Bold) 17 | 18 | // Check Version cloud-sql-proxy 19 | cloudsqlproxyversion := exec.Command("cloud-sql-proxy", "--version") 20 | _, err := cloudsqlproxyversion.Output() 21 | 22 | if err != nil { 23 | _, _ = boldRed.Println("Error: %s", err) 24 | _, _ = boldRed.Println("Please upgrade your cloud-sql-proxy version to 2 or higher") 25 | _, _ = boldGreen.Println("Install URL:https://cloud.google.com/sql/docs/postgres/sql-proxy?hl=ja#install") 26 | os.Exit(0) 27 | } 28 | } 29 | 30 | func checkPort(port int) { 31 | red := color.New(color.FgRed) 32 | boldRed := red.Add(color.Bold) 33 | green := color.New(color.FgGreen) 34 | boldGreen := green.Add(color.Bold) 35 | 36 | //command := fmt.Sprintf("lsof -i tcp:%s", port) 37 | command := fmt.Sprintf("lsof -i tcp:" + strconv.Itoa(port)) 38 | processlist := exec.Command("bash", "-c", command) 39 | output, _ := processlist.Output() 40 | line := strings.TrimSuffix(string(output), "\n") 41 | list := strings.Split(line, "\n") 42 | if list[0] != "" { 43 | _, _ = boldRed.Printf("Port \"%d\" already in use\n", port) 44 | _, _ = boldRed.Printf(string(output)) 45 | _, _ = boldGreen.Printf("Can connect using:\n") 46 | _, _ = boldGreen.Printf("cloudsql connect --port 12345\n") 47 | os.Exit(0) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cmd/connect.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "google.golang.org/api/sqladmin/v1" 8 | "log" 9 | "os" 10 | "os/exec" 11 | "path/filepath" 12 | "regexp" 13 | "strconv" 14 | "strings" 15 | 16 | "github.com/fatih/color" 17 | "github.com/manifoldco/promptui" 18 | ) 19 | 20 | func setProject(noConfig bool) string { 21 | if noConfig { 22 | var projId []string 23 | getprojectcommand := fmt.Sprintf("gcloud projects list --format='value(project_id)'") 24 | getproject := exec.Command("bash", "-c", getprojectcommand) 25 | getprojectout, err := getproject.Output() 26 | if err != nil { 27 | log.Fatal(err) 28 | } else { 29 | proj := strings.TrimSuffix(string(getprojectout), "\n") 30 | projId = strings.Split(proj, "\n") 31 | } 32 | searcher := func(input string, index int) bool { 33 | name := projId[index] 34 | input = strings.Replace(strings.ToLower(input), " ", "", -1) 35 | return strings.Contains(name, input) 36 | } 37 | 38 | prompt := promptui.Select{ 39 | Label: "Select GCP Project", 40 | Items: projId, 41 | Searcher: searcher, 42 | Stdout: NoBellStdout, 43 | } 44 | 45 | _, result, err := prompt.Run() 46 | 47 | if err != nil { 48 | fmt.Printf("Prompt failed %v\n", err) 49 | os.Exit(1) 50 | return "" 51 | } 52 | 53 | fmt.Printf("Project ID: %q\n", result) 54 | promptresult := strings.Split(result, ":") 55 | projectId := promptresult[len(promptresult)-1] 56 | return projectId 57 | 58 | } else { 59 | f, err := os.Open(filepath.Join(os.Getenv("HOME"), "/.cloudsql/config")) 60 | 61 | if err != nil { 62 | fmt.Println("error") 63 | } 64 | 65 | defer func(f *os.File) { 66 | err := f.Close() 67 | if err != nil { 68 | fmt.Println("error") 69 | } 70 | }(f) 71 | 72 | lines := make([]string, 0, 100) 73 | scanner := bufio.NewScanner(f) 74 | for scanner.Scan() { 75 | lines = append(lines, scanner.Text()) 76 | } 77 | 78 | searcher := func(input string, index int) bool { 79 | name := lines[index] 80 | input = strings.Replace(strings.ToLower(input), " ", "", -1) 81 | return strings.Contains(name, input) 82 | } 83 | 84 | prompt := promptui.Select{ 85 | Label: "Select Project", 86 | Items: lines, 87 | Searcher: searcher, 88 | Stdout: NoBellStdout, 89 | } 90 | 91 | _, result, err := prompt.Run() 92 | 93 | if err != nil { 94 | fmt.Printf("Prompt failed %v\n", err) 95 | os.Exit(1) 96 | return "" 97 | } 98 | 99 | fmt.Printf("You choose %q\n", result) 100 | return result 101 | } 102 | } 103 | 104 | func listInstances(project string) []string { 105 | var list []string 106 | ctx := context.Background() 107 | 108 | sqladminService, err := sqladmin.NewService(ctx) 109 | if err != nil { 110 | log.Fatal(err) 111 | } 112 | 113 | req := sqladminService.Instances.List(project) 114 | if err := req.Pages(ctx, func(page *sqladmin.InstancesListResponse) error { 115 | for _, databaseInstance := range page.Items { 116 | list = append(list, databaseInstance.ConnectionName) 117 | } 118 | return nil 119 | }); err != nil { 120 | log.Fatal(err) 121 | } 122 | return list 123 | } 124 | 125 | func getInstance(project string) string { 126 | instancelist := listInstances(project) 127 | 128 | searcher := func(input string, index int) bool { 129 | name := instancelist[index] 130 | input = strings.Replace(strings.ToLower(input), " ", "", -1) 131 | return strings.Contains(name, input) 132 | } 133 | 134 | prompt := promptui.Select{ 135 | Label: "Select Instance", 136 | Items: instancelist, 137 | Searcher: searcher, 138 | Stdout: NoBellStdout, 139 | } 140 | 141 | _, result, err := prompt.Run() 142 | 143 | if err != nil { 144 | fmt.Printf("Prompt failed %v\n", err) 145 | os.Exit(1) 146 | } 147 | 148 | fmt.Printf("You choose %q\n", result) 149 | 150 | return result 151 | } 152 | 153 | func listDatabases(instance string, project string) []string { 154 | var list []string 155 | ctx := context.Background() 156 | 157 | sqladminService, err := sqladmin.NewService(ctx) 158 | if err != nil { 159 | log.Fatal(err) 160 | } 161 | 162 | resp, err := sqladminService.Databases.List(project, instance).Context(ctx).Do() 163 | if err != nil { 164 | log.Fatal(err) 165 | } 166 | 167 | if resp == nil || resp.Items == nil { 168 | log.Fatal("No databases found or unable to retrieve databases") 169 | } 170 | 171 | for _, database := range resp.Items { 172 | list = append(list, database.Name) 173 | } 174 | 175 | return list 176 | } 177 | 178 | func getDatabase(instance string, project string) string { 179 | databaseList := listDatabases(instance, project) 180 | 181 | searcher := func(input string, index int) bool { 182 | name := databaseList[index] 183 | input = strings.Replace(strings.ToLower(input), " ", "", -1) 184 | return strings.Contains(name, input) 185 | } 186 | 187 | prompt := promptui.Select{ 188 | Label: "Select Database", 189 | Items: databaseList, 190 | Searcher: searcher, 191 | Stdout: NoBellStdout, 192 | } 193 | 194 | _, result, err := prompt.Run() 195 | 196 | if err != nil { 197 | fmt.Printf("Prompt failed %v\n", err) 198 | os.Exit(0) 199 | return "" 200 | } 201 | 202 | return result 203 | } 204 | 205 | func getUser() string { 206 | var userName string 207 | command := fmt.Sprintf("gcloud auth list --filter=status:ACTIVE --format='value(account)'") 208 | user := exec.Command("bash", "-c", command) 209 | userOut, err := user.Output() 210 | if err != nil { 211 | userName = "" 212 | } else { 213 | userName = strings.TrimSuffix(string(userOut), "\n") 214 | } 215 | return userName 216 | } 217 | 218 | func getdbTypeName(instance string, project string) string { 219 | var result string 220 | 221 | getdbtype := fmt.Sprintf("gcloud sql instances describe " + instance + " --project=" + project + " --format='value(databaseVersion)'") 222 | dbtype := exec.Command("bash", "-c", getdbtype) 223 | getdbtypeOut, err1 := dbtype.Output() 224 | 225 | if err1 != nil { 226 | result = "" 227 | } else { 228 | result = strings.TrimSuffix(string(getdbtypeOut), "\n") 229 | } 230 | if result == "" || result == "" { 231 | fmt.Println("Error : You do not have permissions to CloudSQL or there is a problem with the gcloud command") 232 | os.Exit(0) 233 | } 234 | return result 235 | } 236 | 237 | func connectInstance(port int, noConfig bool, debug bool) { 238 | var dbTypeName string 239 | var sqlInstanceName []string 240 | var sqlConnectionName string 241 | var userName string 242 | 243 | // color setting 244 | green := color.New(color.FgGreen) 245 | blue := color.New(color.FgBlue) 246 | boldGreen := green.Add(color.Bold) 247 | boldBlue := blue.Add(color.Bold) 248 | 249 | // select database 250 | project := setProject(noConfig) 251 | sqlConnectionName = getInstance(project) 252 | sqlInstanceName = strings.Split(sqlConnectionName, ":") 253 | databaseList := getDatabase(sqlInstanceName[2], project) 254 | dbTypeName = getdbTypeName(sqlInstanceName[2], project) 255 | userName = getUser() 256 | 257 | // connect instance 258 | if strings.Contains(dbTypeName, "POSTGRES") { 259 | if debug { 260 | command := fmt.Sprintf("cloud-sql-proxy %s --auto-iam-authn --debug --private-ip --port=%d", sqlConnectionName, port) 261 | color.Blue("[Debug Mode]\nThe following commands are executed in foreground.\n") 262 | _, _ = boldBlue.Printf("%s\n", command) 263 | color.Green("Can connect using:\n") 264 | _, _ = boldGreen.Printf("psql -h localhost -U %s -p %d -d %s\n", userName, port, databaseList) 265 | debug := exec.Command("bash", "-c", command) 266 | debug.Stdout = os.Stdout 267 | debug.Stderr = os.Stderr 268 | err := debug.Run() 269 | if err != nil { 270 | log.Fatal(err) 271 | } 272 | 273 | } else { 274 | cmd := exec.Command("cloud-sql-proxy", sqlConnectionName, "--auto-iam-authn", "--private-ip", "--quiet", "--port="+strconv.Itoa(port)) 275 | cmd.Stdout = os.Stdout 276 | err := cmd.Start() 277 | if err != nil { 278 | log.Fatal(err) 279 | } 280 | log.Printf("Cloudsql proxy process is running in background, process_id: %d\n", cmd.Process.Pid) 281 | 282 | color.Blue("Can connect using:") 283 | _, _ = boldGreen.Printf("psql -h localhost -U %s -p %d -d %s\n", userName, port, databaseList) 284 | } 285 | } 286 | if strings.Contains(dbTypeName, "MYSQL") { 287 | if debug { 288 | command := fmt.Sprintf("cloud-sql-proxy %s --auto-iam-authn --private-ip --debug --port=%d", sqlConnectionName, port) 289 | color.Blue("[Debug Mode]\nThe following commands are executed in foreground.\n") 290 | _, _ = boldBlue.Printf("%s\n", command) 291 | color.Green("Can connect using:\n") 292 | var re = regexp.MustCompile("@.*") 293 | _, _ = boldGreen.Printf("mysql --user=%s --password=`gcloud auth print-access-token` --enable-cleartext-plugin --host=127.0.0.1 --port=%d\n", re.ReplaceAllString(userName, ""), port) 294 | debug := exec.Command("bash", "-c", command) 295 | debug.Stdout = os.Stdout 296 | debug.Stderr = os.Stderr 297 | err := debug.Run() 298 | if err != nil { 299 | log.Fatal(err) 300 | } 301 | } 302 | 303 | port = 3306 304 | cmd := exec.Command("cloud-sql-proxy", sqlConnectionName, "--auto-iam-authn", "--private-ip", "--quiet", "--port="+strconv.Itoa(port)) 305 | cmd.Stdout = os.Stdout 306 | err := cmd.Start() 307 | if err != nil { 308 | log.Fatal(err) 309 | } 310 | log.Printf("Cloudsql proxy process is running in background, process_id: %d\n", cmd.Process.Pid) 311 | 312 | color.Blue("Can connect using:") 313 | var re = regexp.MustCompile("@.*") 314 | _, _ = boldGreen.Printf("mysql --user=%s --password=`gcloud auth print-access-token` --enable-cleartext-plugin --host=127.0.0.1 --port=%d --database=%s\n", re.ReplaceAllString(userName, ""), port, databaseList) 315 | // Temporarily commented out when database is selected because the connection is not possible due to permission issues. 316 | //_, _ = boldGreen.Printf("mysql --user=%s --password=`gcloud auth print-access-token` --enable-cleartext-plugin --host=127.0.0.1 --port=%d --database=%s\n", re.ReplaceAllString(userName, ""), port, databaseList) 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /cmd/disconnect.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | 10 | "github.com/manifoldco/promptui" 11 | ) 12 | 13 | func getPort() string { 14 | str := listInstance() 15 | 16 | prompt := promptui.Select{ 17 | Label: "Select Instance to disconnect", 18 | Items: str, 19 | Stdout: NoBellStdout, 20 | } 21 | 22 | _, result, err := prompt.Run() 23 | 24 | if err != nil { 25 | fmt.Printf("Prompt failed %v\n", err) 26 | os.Exit(0) 27 | return "" 28 | } 29 | 30 | fmt.Printf("You choose %q\n", result) 31 | res1 := strings.Split(result, "=") 32 | port := res1[len(res1)-1] 33 | return port 34 | } 35 | 36 | func disconnectInstance() { 37 | port := getPort() 38 | command := fmt.Sprintf("lsof -i tcp:%s | grep LISTEN | awk '{print $2}' | xargs kill -9", port) 39 | cmd := exec.Command("bash", "-c", command) 40 | cmd.Stdout = os.Stdout 41 | err := cmd.Start() 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | log.Printf("Instance disconnected") 46 | } 47 | -------------------------------------------------------------------------------- /cmd/doctor.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "regexp" 9 | 10 | "github.com/fatih/color" 11 | ) 12 | 13 | func doctor() { 14 | red := color.New(color.FgRed) 15 | boldRed := red.Add(color.Bold) 16 | green := color.New(color.FgGreen) 17 | boldGreen := green.Add(color.Bold) 18 | 19 | // Find out the postgresSupportVersion from the following link. 20 | // https://github.com/google-cloud-sdk-unofficial/google-cloud-sdk/blame/7ffc79beeaa5ec5b900847691f3e047998033acf/lib/googlecloudsdk/generated_clients/apis/sqladmin/v1beta4/sqladmin_v1beta4_messages.py#L1175 21 | postgresSupportVersion := "486.0.0" 22 | 23 | // Check gcloud sdk 24 | gcloudversioncommand := fmt.Sprintf("gcloud version") 25 | gcloudversion := exec.Command("bash", "-c", gcloudversioncommand) 26 | gcloudversionout, err := gcloudversion.Output() 27 | 28 | re := regexp.MustCompile(`\d+\.\d+\.\d+`) 29 | version := re.FindString(string(gcloudversionout)) 30 | fmt.Println("Google Cloud SDK Version:", version) 31 | 32 | checkErr := true 33 | if err != nil { 34 | _, _ = boldRed.Println("Please check gcloud sdk") 35 | _, _ = boldRed.Printf("Error: %s\n", err) 36 | checkErr = false 37 | } else if version < postgresSupportVersion { 38 | _, _ = boldRed.Printf("Your gcloud sdk version is %s. This version does not support Cloudsql 16.\n", version) 39 | _, _ = boldRed.Printf("You need to upgrade to at least above version %s.\n", postgresSupportVersion) 40 | checkErr = false 41 | } else { 42 | fmt.Printf("gcloud version: %s", version) 43 | } 44 | 45 | // Check user is authenticated in gcloud 46 | gcloudusercommand := fmt.Sprintf("gcloud auth list --filter=status:ACTIVE --format='value(account)'") 47 | gclouduser := exec.Command("bash", "-c", gcloudusercommand) 48 | gclouduserout, err := gclouduser.Output() 49 | if err != nil { 50 | _, _ = boldRed.Println("User not authenticatedRun: gcloud auth application-default login") 51 | _, _ = boldRed.Printf("Error: %s\n", err) 52 | checkErr = false 53 | } else { 54 | fmt.Printf("Authenticated user account: %s", gclouduserout) 55 | } 56 | 57 | // Check cloud-sql-proxy 58 | cloudsqlproxyversion := exec.Command("cloud-sql-proxy", "--version") 59 | cloudsqlproxyversionout, err := cloudsqlproxyversion.Output() 60 | if err != nil { 61 | _, _ = boldRed.Println("Please check cloud-sql-proxy") 62 | _, _ = boldRed.Printf("Error: %s\n", err) 63 | checkErr = false 64 | } else { 65 | fmt.Printf("cloud-sql-proxy version: %s", cloudsqlproxyversionout) 66 | } 67 | 68 | // Check psql 69 | psqlversion := exec.Command("psql", "--version") 70 | psqlversionout, err := psqlversion.Output() 71 | if err != nil { 72 | _, _ = boldRed.Println("Please check psql") 73 | _, _ = boldRed.Printf("Error: %s\n", err) 74 | checkErr = false 75 | } else { 76 | fmt.Printf("psql version: %s", psqlversionout) 77 | } 78 | 79 | // Check mysql 80 | mysqlversion := exec.Command("mysql", "--version") 81 | mysqlversionout, err := mysqlversion.Output() 82 | if err != nil { 83 | _, _ = boldRed.Println("Please check mysql") 84 | _, _ = boldRed.Printf("Error: %s\n", err) 85 | checkErr = false 86 | } else { 87 | fmt.Printf("mysql version: %s", mysqlversionout) 88 | } 89 | 90 | // Check config file 91 | _, err = os.Stat(filepath.Join(os.Getenv("HOME"), "/.cloudsql/config")) 92 | if err != nil { 93 | _, _ = boldRed.Println("Please check config file ~/.cloudsql/config") 94 | _, _ = boldRed.Printf("Error: %s\n", err) 95 | checkErr = false 96 | } else { 97 | fmt.Println("config file: ok") 98 | } 99 | 100 | if checkErr == true { 101 | _, _ = boldGreen.Println("Your system is All Green!") 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /cmd/list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | func listInstance() []string { 11 | command := fmt.Sprintf("ps aux | grep cloud-sql-proxy | grep -v grep | grep -v 'awk -F cloud-sql-proxy' | awk -F 'cloud-sql-proxy ' '{print $NF}'") 12 | processlist := exec.Command("bash", "-c", command) 13 | output, _ := processlist.Output() 14 | line := strings.TrimSuffix(string(output), "\n") 15 | list := strings.Split(line, "\n") 16 | if list[0] == "" { 17 | fmt.Println("No Instance connected") 18 | os.Exit(0) 19 | } 20 | 21 | return list 22 | } 23 | -------------------------------------------------------------------------------- /cmd/nobell.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import "github.com/chzyer/readline" 4 | 5 | type noBellStdout struct{} 6 | 7 | func (n *noBellStdout) Write(p []byte) (int, error) { 8 | if len(p) == 1 && p[0] == readline.CharBell { 9 | return 0, nil 10 | } 11 | return readline.Stdout.Write(p) 12 | } 13 | 14 | func (n *noBellStdout) Close() error { 15 | return readline.Stdout.Close() 16 | } 17 | 18 | var NoBellStdout = &noBellStdout{} -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/spf13/cobra" 6 | "log" 7 | ) 8 | 9 | var rootCmd *cobra.Command 10 | var connectCmd *cobra.Command 11 | var disconnectCmd *cobra.Command 12 | var listCmd *cobra.Command 13 | var versionCmd *cobra.Command 14 | var doctorCmd *cobra.Command 15 | 16 | func init() { 17 | rootCmd = &cobra.Command{ 18 | Use: "cloudsql [sub]", 19 | Short: "CloudSQL CLI", 20 | } 21 | 22 | connectCmd = &cobra.Command{ 23 | Use: "connect", 24 | Short: "connect to cloudsql instance", 25 | Run: func(cmd *cobra.Command, args []string) { 26 | port, err := cmd.Flags().GetInt("port") 27 | if err != nil { 28 | log.Fatalf("Error getting port: %v", err) 29 | } 30 | 31 | noConfig, err := cmd.Flags().GetBool("no-config") 32 | if err != nil { 33 | log.Fatalf("Error getting no-config: %v", err) 34 | } 35 | 36 | debug, err := cmd.Flags().GetBool("debug") 37 | if err != nil { 38 | log.Fatalf("Error getting debug: %v", err) 39 | } 40 | checkPort(port) 41 | connectInstance(port, noConfig, debug) 42 | }, 43 | } 44 | 45 | disconnectCmd = &cobra.Command{ 46 | Use: "disconnect", 47 | Short: "disconnect cloudsql instance", 48 | Run: func(cmd *cobra.Command, args []string) { 49 | disconnectInstance() 50 | }, 51 | } 52 | 53 | listCmd = &cobra.Command{ 54 | Use: "list", 55 | Short: "list connected cloudsql instance", 56 | Run: func(cmd *cobra.Command, args []string) { 57 | for _, value := range listInstance() { 58 | fmt.Println(value) 59 | } 60 | }, 61 | } 62 | 63 | versionCmd = &cobra.Command{ 64 | Use: "version", 65 | Short: "Print the version number of cloudsql", 66 | Run: func(cmd *cobra.Command, args []string) { 67 | fmt.Println("cloudsql 2.1.0") 68 | }, 69 | } 70 | 71 | doctorCmd = &cobra.Command{ 72 | Use: "doctor", 73 | Short: "troubleshooting", 74 | Args: cobra.NoArgs, 75 | Run: func(cmd *cobra.Command, args []string) { 76 | doctor() 77 | }, 78 | } 79 | 80 | rootCmd.AddCommand(disconnectCmd, connectCmd, listCmd, versionCmd, doctorCmd) 81 | checkVersionCloudSqlProxy() 82 | connectCmd.PersistentFlags().Int("port", 5432, "port") 83 | connectCmd.Flags().BoolP("no-config", "", false, "load config from gcloud") 84 | connectCmd.Flags().BoolP("debug", "", false, "for troubleshooting. you can get cloud-sql-proxy log") 85 | } 86 | 87 | func Execute() { 88 | err := connectCmd.Execute() 89 | if err != nil { 90 | log.Fatalf("Error executing connect command: %v", err) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module s-okayama/cloudsql 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/chzyer/readline v1.5.1 9 | github.com/fatih/color v1.18.0 10 | github.com/manifoldco/promptui v0.9.0 11 | github.com/spf13/cobra v1.9.1 12 | google.golang.org/api v0.235.0 13 | ) 14 | 15 | require ( 16 | cloud.google.com/go/auth v0.16.1 // indirect 17 | cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect 18 | cloud.google.com/go/compute/metadata v0.7.0 // indirect 19 | github.com/felixge/httpsnoop v1.0.4 // indirect 20 | github.com/go-logr/logr v1.4.2 // indirect 21 | github.com/go-logr/stdr v1.2.2 // indirect 22 | github.com/google/s2a-go v0.1.9 // indirect 23 | github.com/google/uuid v1.6.0 // indirect 24 | github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect 25 | github.com/googleapis/gax-go/v2 v2.14.2 // indirect 26 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 27 | github.com/mattn/go-colorable v0.1.13 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/spf13/pflag v1.0.6 // indirect 30 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 31 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect 32 | go.opentelemetry.io/otel v1.35.0 // indirect 33 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 34 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 35 | golang.org/x/crypto v0.38.0 // indirect 36 | golang.org/x/net v0.40.0 // indirect 37 | golang.org/x/oauth2 v0.30.0 // indirect 38 | golang.org/x/sys v0.33.0 // indirect 39 | golang.org/x/text v0.25.0 // indirect 40 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect 41 | google.golang.org/grpc v1.72.1 // indirect 42 | google.golang.org/protobuf v1.36.6 // indirect 43 | ) 44 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= 2 | cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= 3 | cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= 4 | cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= 5 | cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= 6 | cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= 7 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 8 | github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= 9 | github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= 10 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 11 | github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= 12 | github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= 13 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 14 | github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= 15 | github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= 16 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 17 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 18 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 20 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 21 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 22 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 23 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 24 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 25 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 26 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 27 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 28 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 29 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 30 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 31 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 32 | github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= 33 | github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= 34 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 35 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 36 | github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= 37 | github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= 38 | github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= 39 | github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= 40 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 41 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 42 | github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= 43 | github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= 44 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 45 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 46 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 47 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 48 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 49 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 50 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 51 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 52 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 53 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 54 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 55 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 56 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 57 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 58 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 59 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 60 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= 61 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= 62 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= 63 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= 64 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= 65 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= 66 | go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= 67 | go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= 68 | go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= 69 | go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= 70 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= 71 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= 72 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 73 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 74 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 75 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 76 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= 77 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 78 | golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 79 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 80 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 81 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 82 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 83 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 84 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 85 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 86 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 87 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 88 | google.golang.org/api v0.235.0 h1:C3MkpQSRxS1Jy6AkzTGKKrpSCOd2WOGrezZ+icKSkKo= 89 | google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg= 90 | google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= 91 | google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= 92 | google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0= 93 | google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw= 94 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg= 95 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 96 | google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= 97 | google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 98 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 99 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 100 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 101 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 102 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 103 | --------------------------------------------------------------------------------