Skip to content

Commit 6ecde0b

Browse files
authored
fix(rdb): connect to public or private endpoint (#3094)
1 parent 3252886 commit 6ecde0b

File tree

9 files changed

+929
-594
lines changed

9 files changed

+929
-594
lines changed

cmd/scw/testdata/test-all-usage-rdb-instance-connect-usage.golden

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ USAGE:
66
scw rdb instance connect <instance-id ...> [arg=value ...]
77

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

1516
FLAGS:
1617
-h, --help help for connect

docs/commands/rdb.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,7 @@ scw rdb instance connect <instance-id ...> [arg=value ...]
659659

660660
| Name | | Description |
661661
|------|---|-------------|
662+
| private-network | Default: `false` | Connect by the private network endpoint attached. |
662663
| instance-id | Required | UUID of the instance |
663664
| username | Required | Name of the user to connect with to the database |
664665
| database | Default: `rdb` | Name of the database |

internal/namespaces/rdb/v1/custom_instance.go

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import (
2020
)
2121

2222
const (
23-
instanceActionTimeout = 20 * time.Minute
23+
instanceActionTimeout = 20 * time.Minute
24+
errorMessagePublicEndpointNotFound = "public endpoint not found"
25+
errorMessagePrivateEndpointNotFound = "private endpoint not found"
26+
errorMessageEndpointNotFound = "any endpoint is associated on your instance"
2427
)
2528

2629
var (
@@ -396,11 +399,12 @@ func instanceWaitCommand() *core.Command {
396399
}
397400

398401
type instanceConnectArgs struct {
399-
Region scw.Region
400-
InstanceID string
401-
Username string
402-
Database *string
403-
CliDB *string
402+
Region scw.Region
403+
PrivateNetwork bool
404+
InstanceID string
405+
Username string
406+
Database *string
407+
CliDB *string
404408
}
405409

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

466-
func createConnectCommandLineArgs(instance *rdb.Instance, family engineFamily, args *instanceConnectArgs) ([]string, error) {
470+
func getPublicEndpoint(endpoints []*rdb.Endpoint) (*rdb.Endpoint, error) {
471+
for _, e := range endpoints {
472+
if e.LoadBalancer != nil {
473+
return e, nil
474+
}
475+
}
476+
477+
return nil, fmt.Errorf(errorMessagePublicEndpointNotFound)
478+
}
479+
480+
func getPrivateEndpoint(endpoints []*rdb.Endpoint) (*rdb.Endpoint, error) {
481+
for _, e := range endpoints {
482+
if e.PrivateNetwork != nil {
483+
return e, nil
484+
}
485+
}
486+
487+
return nil, fmt.Errorf(errorMessagePrivateEndpointNotFound)
488+
}
489+
490+
func createConnectCommandLineArgs(endpoint *rdb.Endpoint, family engineFamily, args *instanceConnectArgs) ([]string, error) {
467491
database := "rdb"
468492
if args.Database != nil {
469493
database = *args.Database
@@ -479,8 +503,8 @@ func createConnectCommandLineArgs(instance *rdb.Instance, family engineFamily, a
479503
// psql -h 51.159.25.206 --port 13917 -d rdb -U username
480504
return []string{
481505
clidb,
482-
"--host", instance.Endpoints[0].IP.String(),
483-
"--port", fmt.Sprintf("%d", instance.Endpoints[0].Port),
506+
"--host", endpoint.IP.String(),
507+
"--port", fmt.Sprintf("%d", endpoint.Port),
484508
"--username", args.Username,
485509
"--dbname", database,
486510
}, nil
@@ -493,14 +517,14 @@ func createConnectCommandLineArgs(instance *rdb.Instance, family engineFamily, a
493517
// mysql -h 195.154.69.163 --port 12210 -p -u username
494518
return []string{
495519
clidb,
496-
"--host", instance.Endpoints[0].IP.String(),
497-
"--port", fmt.Sprintf("%d", instance.Endpoints[0].Port),
520+
"--host", endpoint.IP.String(),
521+
"--port", fmt.Sprintf("%d", endpoint.Port),
498522
"--database", database,
499523
"--user", args.Username,
500524
}, nil
501525
}
502526

503-
return nil, fmt.Errorf("unrecognize database engine: %s", instance.Engine)
527+
return nil, fmt.Errorf("unrecognize database engine: %s", family)
504528
}
505529

506530
func instanceConnectCommand() *core.Command {
@@ -512,6 +536,12 @@ func instanceConnectCommand() *core.Command {
512536
Long: "Connect to an instance using locally installed CLI such as psql or mysql.",
513537
ArgsType: reflect.TypeOf(instanceConnectArgs{}),
514538
ArgSpecs: core.ArgSpecs{
539+
{
540+
Name: "private-network",
541+
Short: `Connect by the private network endpoint attached.`,
542+
Required: false,
543+
Default: core.DefaultValueSetter("false"),
544+
},
515545
{
516546
Name: "instance-id",
517547
Short: `UUID of the instance`,
@@ -552,7 +582,25 @@ func instanceConnectCommand() *core.Command {
552582
return nil, err
553583
}
554584

555-
cmdArgs, err := createConnectCommandLineArgs(instance, engineFamily, args)
585+
if len(instance.Endpoints) == 0 {
586+
return nil, fmt.Errorf(errorMessageEndpointNotFound)
587+
}
588+
589+
var endpoint *rdb.Endpoint
590+
switch {
591+
case args.PrivateNetwork:
592+
endpoint, err = getPrivateEndpoint(instance.Endpoints)
593+
if err != nil {
594+
return nil, err
595+
}
596+
default:
597+
endpoint, err = getPublicEndpoint(instance.Endpoints)
598+
if err != nil {
599+
return nil, err
600+
}
601+
}
602+
603+
cmdArgs, err := createConnectCommandLineArgs(endpoint, engineFamily, args)
556604
if err != nil {
557605
return nil, err
558606
}

internal/namespaces/rdb/v1/custom_instance_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,19 @@ func Test_Connect(t *testing.T) {
180180
OverrideExec: core.OverrideExecSimple("psql --host {{ .Instance.Endpoint.IP }} --port {{ .Instance.Endpoint.Port }} --username {{ .username }} --dbname rdb", 0),
181181
AfterFunc: deleteInstance(),
182182
}))
183+
t.Run("psql", core.Test(&core.TestConfig{
184+
Commands: GetCommands(),
185+
BeforeFunc: core.BeforeFuncCombine(
186+
core.BeforeFuncStoreInMeta("username", user),
187+
createPN(),
188+
createInstanceWithPrivateNetwork("PostgreSQL-14"),
189+
),
190+
Cmd: "scw rdb instance connect {{ .Instance.ID }} username={{ .username }}",
191+
Check: core.TestCheckCombine(
192+
core.TestCheckGolden(),
193+
core.TestCheckExitCode(0),
194+
),
195+
OverrideExec: core.OverrideExecSimple("psql --host {{ .Instance.Endpoint.IP }} --port {{ .Instance.Endpoint.Port }} --username {{ .username }} --dbname rdb", 0),
196+
AfterFunc: deleteInstance(),
197+
}))
183198
}

internal/namespaces/rdb/v1/helper_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"fmt"
55

66
"github.com/scaleway/scaleway-cli/v2/internal/core"
7+
"github.com/scaleway/scaleway-sdk-go/api/vpc/v1"
8+
"github.com/scaleway/scaleway-sdk-go/scw"
79
)
810

911
const (
@@ -20,6 +22,40 @@ func createInstance(engine string) core.BeforeFunc {
2022
)
2123
}
2224

25+
func createInstanceWithPrivateNetwork(engine string) core.BeforeFunc {
26+
return core.ExecStoreBeforeCmd(
27+
"Instance",
28+
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),
29+
)
30+
}
31+
32+
func createPN() core.BeforeFunc {
33+
return func(ctx *core.BeforeFuncCtx) error {
34+
var err error
35+
api := vpc.NewAPI(ctx.Client)
36+
pn, _ := api.CreatePrivateNetwork(&vpc.CreatePrivateNetworkRequest{})
37+
ctx.Meta["PN"] = pn
38+
ctx.Meta["IPNet"], err = getIPSubnet(pn.Subnets[0])
39+
if err != nil {
40+
return err
41+
}
42+
return nil
43+
}
44+
}
45+
46+
func getIPSubnet(ipNet scw.IPNet) (*string, error) {
47+
addr := ipNet.IP.To4()
48+
if addr == nil {
49+
return nil, fmt.Errorf("could get ip 4 bytes")
50+
}
51+
addr = addr.Mask(addr.DefaultMask())
52+
addr[3] = +3
53+
54+
sz, _ := ipNet.Mask.Size()
55+
ipNetStr := fmt.Sprintf("%s/%d", addr.String(), sz)
56+
return &ipNetStr, nil
57+
}
58+
2359
func deleteInstance() core.AfterFunc {
2460
return core.ExecAfterCmd("scw rdb instance delete {{ .Instance.ID }}")
2561
}

0 commit comments

Comments
 (0)