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
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ USAGE:
scw rdb instance connect <instance-id ...> [arg=value ...]

ARGS:
instance-id UUID of the instance
username Name of the user to connect with to the database
[database=rdb] Name of the database
[cli-db] Command line tool to use, default to psql/mysql
[region=fr-par] Region to target. If none is passed will use default region from the config (fr-par | nl-ams)
[private-network=false] Connect by the private network endpoint attached.
instance-id UUID of the instance
username Name of the user to connect with to the database
[database=rdb] Name of the database
[cli-db] Command line tool to use, default to psql/mysql
[region=fr-par] Region to target. If none is passed will use default region from the config (fr-par | nl-ams)

FLAGS:
-h, --help help for connect
Expand Down
1 change: 1 addition & 0 deletions docs/commands/rdb.md
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@ scw rdb instance connect <instance-id ...> [arg=value ...]

| Name | | Description |
|------|---|-------------|
| private-network | Default: `false` | Connect by the private network endpoint attached. |
| instance-id | Required | UUID of the instance |
| username | Required | Name of the user to connect with to the database |
| database | Default: `rdb` | Name of the database |
Expand Down
74 changes: 61 additions & 13 deletions internal/namespaces/rdb/v1/custom_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import (
)
Comment thread
Monitob marked this conversation as resolved.

const (
instanceActionTimeout = 20 * time.Minute
instanceActionTimeout = 20 * time.Minute
errorMessagePublicEndpointNotFound = "public endpoint not found"
errorMessagePrivateEndpointNotFound = "private endpoint not found"
errorMessageEndpointNotFound = "any endpoint is associated on your instance"
)

var (
Expand Down Expand Up @@ -396,11 +399,12 @@ func instanceWaitCommand() *core.Command {
}

type instanceConnectArgs struct {
Region scw.Region
InstanceID string
Username string
Database *string
CliDB *string
Region scw.Region
PrivateNetwork bool
InstanceID string
Username string
Database *string
CliDB *string
}

type engineFamily string
Expand Down Expand Up @@ -463,7 +467,27 @@ func detectEngineFamily(instance *rdb.Instance) (engineFamily, error) {
return Unknown, fmt.Errorf("unknown engine: %s", instance.Engine)
}

func createConnectCommandLineArgs(instance *rdb.Instance, family engineFamily, args *instanceConnectArgs) ([]string, error) {
func getPublicEndpoint(endpoints []*rdb.Endpoint) (*rdb.Endpoint, error) {
for _, e := range endpoints {
if e.LoadBalancer != nil {
return e, nil
}
}

return nil, fmt.Errorf(errorMessagePublicEndpointNotFound)
}

func getPrivateEndpoint(endpoints []*rdb.Endpoint) (*rdb.Endpoint, error) {
for _, e := range endpoints {
if e.PrivateNetwork != nil {
return e, nil
}
}

return nil, fmt.Errorf(errorMessagePrivateEndpointNotFound)
}

func createConnectCommandLineArgs(endpoint *rdb.Endpoint, family engineFamily, args *instanceConnectArgs) ([]string, error) {
database := "rdb"
if args.Database != nil {
database = *args.Database
Expand All @@ -479,8 +503,8 @@ func createConnectCommandLineArgs(instance *rdb.Instance, family engineFamily, a
// psql -h 51.159.25.206 --port 13917 -d rdb -U username
return []string{
clidb,
"--host", instance.Endpoints[0].IP.String(),
"--port", fmt.Sprintf("%d", instance.Endpoints[0].Port),
"--host", endpoint.IP.String(),
"--port", fmt.Sprintf("%d", endpoint.Port),
"--username", args.Username,
"--dbname", database,
}, nil
Expand All @@ -493,14 +517,14 @@ func createConnectCommandLineArgs(instance *rdb.Instance, family engineFamily, a
// mysql -h 195.154.69.163 --port 12210 -p -u username
return []string{
clidb,
"--host", instance.Endpoints[0].IP.String(),
"--port", fmt.Sprintf("%d", instance.Endpoints[0].Port),
"--host", endpoint.IP.String(),
"--port", fmt.Sprintf("%d", endpoint.Port),
"--database", database,
"--user", args.Username,
}, nil
}

return nil, fmt.Errorf("unrecognize database engine: %s", instance.Engine)
return nil, fmt.Errorf("unrecognize database engine: %s", family)
}

func instanceConnectCommand() *core.Command {
Expand All @@ -512,6 +536,12 @@ func instanceConnectCommand() *core.Command {
Long: "Connect to an instance using locally installed CLI such as psql or mysql.",
ArgsType: reflect.TypeOf(instanceConnectArgs{}),
ArgSpecs: core.ArgSpecs{
{
Name: "private-network",
Short: `Connect by the private network endpoint attached.`,
Required: false,
Default: core.DefaultValueSetter("false"),
},
{
Name: "instance-id",
Short: `UUID of the instance`,
Expand Down Expand Up @@ -552,7 +582,25 @@ func instanceConnectCommand() *core.Command {
return nil, err
}

cmdArgs, err := createConnectCommandLineArgs(instance, engineFamily, args)
if len(instance.Endpoints) == 0 {
return nil, fmt.Errorf(errorMessageEndpointNotFound)
}

var endpoint *rdb.Endpoint
switch {
case args.PrivateNetwork:
endpoint, err = getPrivateEndpoint(instance.Endpoints)
if err != nil {
return nil, err
}
default:
endpoint, err = getPublicEndpoint(instance.Endpoints)
if err != nil {
return nil, err
}
}

cmdArgs, err := createConnectCommandLineArgs(endpoint, engineFamily, args)
if err != nil {
return nil, err
}
Expand Down
15 changes: 15 additions & 0 deletions internal/namespaces/rdb/v1/custom_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,19 @@ func Test_Connect(t *testing.T) {
OverrideExec: core.OverrideExecSimple("psql --host {{ .Instance.Endpoint.IP }} --port {{ .Instance.Endpoint.Port }} --username {{ .username }} --dbname rdb", 0),
AfterFunc: deleteInstance(),
}))
t.Run("psql", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: core.BeforeFuncCombine(
core.BeforeFuncStoreInMeta("username", user),
createPN(),
createInstanceWithPrivateNetwork("PostgreSQL-14"),
),
Cmd: "scw rdb instance connect {{ .Instance.ID }} username={{ .username }}",
Check: core.TestCheckCombine(
core.TestCheckGolden(),
core.TestCheckExitCode(0),
),
OverrideExec: core.OverrideExecSimple("psql --host {{ .Instance.Endpoint.IP }} --port {{ .Instance.Endpoint.Port }} --username {{ .username }} --dbname rdb", 0),
AfterFunc: deleteInstance(),
}))
}
36 changes: 36 additions & 0 deletions internal/namespaces/rdb/v1/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"

"github.com/scaleway/scaleway-cli/v2/internal/core"
"github.com/scaleway/scaleway-sdk-go/api/vpc/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)

const (
Expand All @@ -20,6 +22,40 @@ func createInstance(engine string) core.BeforeFunc {
)
}

func createInstanceWithPrivateNetwork(engine string) core.BeforeFunc {
return core.ExecStoreBeforeCmd(
"Instance",
fmt.Sprintf("scw rdb instance create node-type=DB-DEV-S is-ha-cluster=false name=%s engine=%s user-name=%s password=%s init-endpoints.0.private-network.private-network-id={{ .PN.ID }} init-endpoints.0.private-network.service-ip={{ .IPNet }} --wait", name, engine, user, password),
)
}

func createPN() core.BeforeFunc {
return func(ctx *core.BeforeFuncCtx) error {
var err error
api := vpc.NewAPI(ctx.Client)
pn, _ := api.CreatePrivateNetwork(&vpc.CreatePrivateNetworkRequest{})
ctx.Meta["PN"] = pn
ctx.Meta["IPNet"], err = getIPSubnet(pn.Subnets[0])
if err != nil {
return err
}
return nil
}
}

func getIPSubnet(ipNet scw.IPNet) (*string, error) {
addr := ipNet.IP.To4()
if addr == nil {
return nil, fmt.Errorf("could get ip 4 bytes")
}
addr = addr.Mask(addr.DefaultMask())
addr[3] = +3

sz, _ := ipNet.Mask.Size()
ipNetStr := fmt.Sprintf("%s/%d", addr.String(), sz)
return &ipNetStr, nil
}

func deleteInstance() core.AfterFunc {
return core.ExecAfterCmd("scw rdb instance delete {{ .Instance.ID }}")
}
Loading