Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ function clean() {

## build - Builds the project without running tests.
function build() {
go build ./...
go build -o ./cloud-sql-proxy main.go
}

## test - Runs local unit tests.
function test() {
go test -v -race -cover -short
go test -v -race -cover -short ./...
}

## e2e - Runs end-to-end integration tests.
Expand Down Expand Up @@ -83,6 +83,9 @@ function fix() {
".tools/goimports" -w .
go mod tidy
go fmt ./...

# Generate CMD docs
go run ./cmd/gendocs/gen_cloud-sql-proxy_docs.go
}

## lint - runs the linters
Expand Down
64 changes: 63 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,33 @@ Configuration
./cloud-sql-proxy wait --max 10s
`

var shutdownHelp = `
Shutting Down the Proxy

The shutdown command signals a running Proxy process to gracefully shut
down. This is useful for scripting and for Kubernetes environments.

The shutdown command requires that the Proxy be started in another process
with the admin server enabled. For example:

./cloud-sql-proxy <INSTANCE_CONNECTION_NAME> --quitquitquit

Invoke the shutdown command like this:

# signals another Proxy process to shut down
./cloud-sql-proxy shutdown

Configuration

If the running Proxy is configured with a non-default admin port, the
shutdown command must also be told to use the same custom value:

./cloud-sql-proxy shutdown --admin-port 9192
`

const (
waitMaxFlag = "max"
adminPortFlag = "admin-port"
httpAddressFlag = "http-address"
httpPortFlag = "http-port"
)
Expand Down Expand Up @@ -384,6 +409,29 @@ func runWaitCmd(c *cobra.Command, _ []string) error {
}
}

func runShutdownCmd(c *cobra.Command, _ []string) error {
p, _ := c.Flags().GetString(adminPortFlag)
addr := fmt.Sprintf("http://127.0.0.1:%v/quitquitquit", p)
c.SilenceUsage = true

req, err := http.NewRequestWithContext(c.Context(), "POST", addr, nil)
if err != nil {
return fmt.Errorf("failed to create shutdown request: %w", err)
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("failed to send shutdown request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("shutdown request failed: status code %v, %v", resp.StatusCode, resp.Status)
}

return nil
}

const envPrefix = "CSQL_PROXY"

// NewCommand returns a Command object representing an invocation of the proxy.
Expand Down Expand Up @@ -419,6 +467,20 @@ func NewCommand(opts ...Option) *Command {
)
rootCmd.AddCommand(waitCmd)

var shutdownCmd = &cobra.Command{
Use: "shutdown",
Short: "Signal a running Proxy process to shut down",
Long: shutdownHelp,
RunE: runShutdownCmd,
}
shutdownFlags := shutdownCmd.Flags()
shutdownFlags.String(
adminPortFlag,
"9091",
"port for the admin server",
)
rootCmd.AddCommand(shutdownCmd)

rootCmd.Args = func(_ *cobra.Command, args []string) error {
// Load the configuration file before running the command. This should
// ensure that the configuration is loaded in the correct order:
Expand Down Expand Up @@ -490,7 +552,7 @@ the Proxy will then pick-up automatically.`)
"Enable pprof on the localhost admin server")
localFlags.BoolVar(&c.conf.QuitQuitQuit, "quitquitquit", false,
"Enable quitquitquit endpoint on the localhost admin server")
localFlags.StringVar(&c.conf.AdminPort, "admin-port", "9091",
localFlags.StringVar(&c.conf.AdminPort, adminPortFlag, "9091",
"Port for localhost-only admin server")
localFlags.BoolVar(&c.conf.HealthCheck, "health-check", false,
"Enables health check endpoints /startup, /liveness, and /readiness on localhost.")
Expand Down
67 changes: 67 additions & 0 deletions cmd/shutdown_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"net"
"net/http"
"net/http/httptest"
"testing"
"time"
)

func TestShutdownCommand(t *testing.T) {
shutdownCh := make(chan bool, 1)
handler := func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("want = POST, got = %v", r.Method)
}
w.WriteHeader(http.StatusOK)
shutdownCh <- true
}
server := httptest.NewServer(http.HandlerFunc(handler))
defer server.Close()

_, port, err := net.SplitHostPort(server.Listener.Addr().String())
if err != nil {
t.Fatal(err)
}

_, err = invokeProxyCommand([]string{
"shutdown",
"--admin-port", port,
})
if err != nil {
t.Fatalf("invokeProxyCommand failed: %v", err)
}

select {
case <-shutdownCh:
// success
case <-time.After(1 * time.Second):
t.Fatal("server did not receive shutdown request")
}
}

func TestShutdownCommandFails(t *testing.T) {
_, err := invokeProxyCommand([]string{
"shutdown",
// assuming default host and port
"--wait=100ms",
})
if err == nil {
t.Fatal("shutdown should fail when endpoint does not respond")
}
}
1 change: 1 addition & 0 deletions docs/cmd/cloud-sql-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,6 @@ cloud-sql-proxy INSTANCE_CONNECTION_NAME... [flags]
### SEE ALSO

* [cloud-sql-proxy completion](cloud-sql-proxy_completion.md) - Generate the autocompletion script for the specified shell
* [cloud-sql-proxy shutdown](cloud-sql-proxy_shutdown.md) - Signal a running Proxy process to shut down
* [cloud-sql-proxy wait](cloud-sql-proxy_wait.md) - Wait for another Proxy process to start

52 changes: 52 additions & 0 deletions docs/cmd/cloud-sql-proxy_shutdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## cloud-sql-proxy shutdown

Signal a running Proxy process to shut down

### Synopsis


Shutting Down the Proxy

The shutdown command signals a running Proxy process to gracefully shut
down. This is useful for scripting and for Kubernetes environments.

The shutdown command requires that the Proxy be started in another process
with the admin server enabled. For example:

./cloud-sql-proxy <INSTANCE_CONNECTION_NAME> --quitquitquit

Invoke the shutdown command like this:

# signals another Proxy process to shut down
./cloud-sql-proxy shutdown

Configuration

If the running Proxy is configured with a non-default admin port, the
shutdown command must also be told to use the same custom value:

./cloud-sql-proxy shutdown --admin-port 9192


```
cloud-sql-proxy shutdown [flags]
```

### Options

```
--admin-port string port for the admin server (default "9091")
-h, --help help for shutdown
```

### Options inherited from parent commands

```
--http-address string Address for Prometheus and health check server (default "localhost")
--http-port string Port for Prometheus and health check server (default "9090")
```

### SEE ALSO

* [cloud-sql-proxy](cloud-sql-proxy.md) - cloud-sql-proxy authorizes and encrypts connections to Cloud SQL.

12 changes: 5 additions & 7 deletions examples/k8s-health-check/proxy_with_http_health_check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,13 @@ spec:
- name: CSQL_PROXY_STRUCTURED_LOGS
value: "true"

# Configure kubernetes to call the /quitquitquit endpoint on the
# admin server before sending SIGTERM to the proxy before stopping
# the pod. This will give the proxy more time to gracefully exit.
# Configure kubernetes to call run the cloud-sql-proxy shutdown command
# before sending SIGTERM to the proxy when stopping the pod. This will
# give the proxy more time to gracefully exit.
lifecycle:
preStop:
httpGet:
path: /quitquitquit
port: 9092
scheme: HTTP
exec:
command: ["/cloud-sql-proxy","shutdown", "--admin-port","9192"]

# The /startup probe returns OK when the proxy is ready to receive
# connections from the application. In this example, k8s will check
Expand Down
Loading