Skip to content

Commit

Permalink
chore: expose additional CLI options in Go interface
Browse files Browse the repository at this point in the history
  • Loading branch information
enocom committed Jan 9, 2024
1 parent ae3796a commit 7e701f6
Show file tree
Hide file tree
Showing 3 changed files with 301 additions and 22 deletions.
84 changes: 84 additions & 0 deletions cmd/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2024 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
//
// https://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 "github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql"

// Option is a function that configures a Command.
type Option func(*Command)

// WithLogger overrides the default logger.
func WithLogger(l cloudsql.Logger) Option {
return func(c *Command) {
c.logger = l
}
}

// WithDialer configures the Command to use the provided dialer to connect to
// Cloud SQL instances.
func WithDialer(d cloudsql.Dialer) Option {
return func(c *Command) {
c.dialer = d
}
}

// WithFuseDir mounts a directory at the path using FUSE to access Cloud SQL
// instances.
func WithFuseDir(dir string) Option {
return func(c *Command) {
c.conf.FUSEDir = dir
}
}

// WithFuseTempDir sets the temp directory where Unix sockets are created with
// FUSE
func WithFuseTempDir(dir string) Option {
return func(c *Command) {
c.conf.FUSETempDir = dir
}
}

// WithMaxConnections sets the maximum allowed number of connections. Default
// is no limit.
func WithMaxConnections(max uint64) Option {
return func(c *Command) {
c.conf.MaxConnections = max
}
}

// WithUserAgent sets additional user agents for Admin API tracking and should
// be a space separated list of additional user agents, e.g.
// cloud-sql-proxy-operator/0.0.1,other-agent/1.0.0
func WithUserAgent(agent string) Option {
return func(c *Command) {
c.conf.OtherUserAgents = agent
}
}

// WithAutoIP enables legacy behavior of v1 and will try to connect to first IP
// address returned by the SQL Admin API. In most cases, this flag should not
// be used. Prefer default of public IP or use --private-ip instead.`
func WithAutoIP() Option {
return func(c *Command) {
c.conf.AutoIP = true
}
}

// WithQuietLogging configures the Proxy to log error messages only.
func WithQuietLogging() Option {
return func(c *Command) {
c.conf.Quiet = true
}
}
213 changes: 213 additions & 0 deletions cmd/options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// Copyright 2024 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
//
// https://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 (
"errors"
"fmt"
"io"
"runtime"
"testing"

"github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql"
"github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log"
"github.com/spf13/cobra"
)

type testDialer struct {
cloudsql.Dialer
}

func TestCommandOptions(t *testing.T) {
logger := log.NewStdLogger(io.Discard, io.Discard)
dialer := &testDialer{}
tcs := []struct {
desc string
isValid func(*Command) error
option Option
skip bool
}{
{
desc: "with logger",
isValid: func(c *Command) error {
if c.logger != logger {
return errors.New("loggers do not match")
}
return nil
},
option: WithLogger(logger),
},
{
desc: "with dialer",
isValid: func(c *Command) error {
if c.dialer != dialer {
return errors.New("dialers do not match")
}
return nil
},
option: WithDialer(dialer),
},
{
desc: "with FUSE dir",
isValid: func(c *Command) error {
if c.conf.FUSEDir != "somedir" {
return fmt.Errorf(
"want = %v, got = %v", "somedir", c.conf.FUSEDir,
)
}
return nil
},
option: WithFuseDir("somedir"),
// FUSE isn't available on GitHub macOS runners
// and FUSE isn't supported on Windows, so skip this test.
skip: runtime.GOOS == "darwin" || runtime.GOOS == "windows",
},
{
desc: "with FUSE temp dir",
isValid: func(c *Command) error {
if c.conf.FUSETempDir != "somedir" {
return fmt.Errorf(
"want = %v, got = %v", "somedir", c.conf.FUSEDir,
)
}
return nil
},
option: WithFuseTempDir("somedir"),
// FUSE isn't available on GitHub macOS runners
// and FUSE isn't supported on Windows, so skip this test.
skip: runtime.GOOS == "darwin" || runtime.GOOS == "windows",
},
{
desc: "with max connections",
isValid: func(c *Command) error {
if c.conf.MaxConnections != 1 {
return fmt.Errorf(
"want = %v, got = %v", 1, c.conf.MaxConnections,
)
}
return nil
},
option: WithMaxConnections(1),
},
{
desc: "with user agent",
isValid: func(c *Command) error {
if c.conf.OtherUserAgents != "agents-go-here" {
return fmt.Errorf(
"want = %v, got = %v",
"agents-go-here", c.conf.OtherUserAgents,
)
}
return nil
},
option: WithUserAgent("agents-go-here"),
},
{
desc: "with auto IP",
isValid: func(c *Command) error {
if !c.conf.AutoIP {
return errors.New("auto IP was false, but should be true")
}
return nil
},
option: WithAutoIP(),
},
{
desc: "with quiet logging",
isValid: func(c *Command) error {
if !c.conf.Quiet {
return errors.New("quiet was false, but should be true")
}
return nil
},
option: WithQuietLogging(),
},
}

for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
if tc.skip {
t.Skip("skipping unsupported test case")
}
got, err := invokeProxyWithOption(nil, tc.option)
if err != nil {
t.Fatal(err)
}
if err := tc.isValid(got); err != nil {
t.Errorf("option did not initialize command correctly: %v", err)
}
})
}
}

func TestCommandOptionsOverridesCLI(t *testing.T) {
tcs := []struct {
desc string
isValid func(*Command) error
option Option
args []string
}{
{
desc: "with duplicate max connections",
isValid: func(c *Command) error {
if c.conf.MaxConnections != 10 {
return errors.New("max connections do not match")
}
return nil
},
option: WithMaxConnections(10),
args: []string{"--max-connections", "20"},
},
{
desc: "with quiet logging",
isValid: func(c *Command) error {
if !c.conf.Quiet {
return errors.New("quiet was false, but should be true")
}
return nil
},
option: WithQuietLogging(),
args: []string{"--quiet", "false"},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got, err := invokeProxyWithOption(tc.args, tc.option)
if err != nil {
t.Fatal(err)
}
if err := tc.isValid(got); err != nil {
t.Errorf("option did not initialize command correctly: %v", err)
}
})
}
}

func invokeProxyWithOption(args []string, o Option) (*Command, error) {
c := NewCommand(o)
// Keep the test output quiet
c.SilenceUsage = true
c.SilenceErrors = true
// Disable execute behavior
c.RunE = func(*cobra.Command, []string) error {
return nil
}
args = append(args, "test-project:us-central1:test-instance")
c.SetArgs(args)

err := c.Execute()

return c, err
}
26 changes: 4 additions & 22 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,24 +91,6 @@ type Command struct {
cleanup func() error
}

// Option is a function that configures a Command.
type Option func(*Command)

// WithLogger overrides the default logger.
func WithLogger(l cloudsql.Logger) Option {
return func(c *Command) {
c.logger = l
}
}

// WithDialer configures the Command to use the provided dialer to connect to
// Cloud SQL instances.
func WithDialer(d cloudsql.Dialer) Option {
return func(c *Command) {
c.dialer = d
}
}

var longHelp = `
Overview
Expand Down Expand Up @@ -397,10 +379,6 @@ func NewCommand(opts ...Option) *Command {
UserAgent: userAgent,
},
}
for _, o := range opts {
o(c)
}

var waitCmd = &cobra.Command{
Use: "wait",
RunE: runWaitCmd,
Expand All @@ -418,6 +396,10 @@ func NewCommand(opts ...Option) *Command {
if len(args) == 0 {
args = instanceFromEnv(args)
}
// Override CLI based arguments with any programmatic options.
for _, o := range opts {
o(c)
}
// Handle logger separately from config
if c.conf.StructuredLogs {
c.logger, c.cleanup = log.NewStructuredLogger(c.conf.Quiet)
Expand Down

0 comments on commit 7e701f6

Please sign in to comment.