Skip to content
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## To Be Released

* feat(dbdr/net-peering) [STORY-3557] Add commands to configure net peerings of DB-DR
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: most of the time we are not mentioning story number on the public changelog.

* fix(addons): parse `maintenance-window-hour` as an int
* feat(session): remove region cache on logout
* feat: add JSON output for the `apps` command
Expand Down
6 changes: 6 additions & 0 deletions cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,12 @@ var (
&databaseFirewallRulesAddCommand,
&databaseFirewallRulesRemoveCommand,
&databaseFirewallManagedRangesCommand,

// Network
&databaseNetPeeringsListCommand,
&databaseNetPeeringsAddCommand,
&databaseNetPeeringsRemoveCommand,
&databaseNetworkConfigurationShowCommand,
}

globalCommands = []*cli.Command{
Expand Down
172 changes: 172 additions & 0 deletions cmd/databases_networking.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package cmd

import (
"context"

"github.com/urfave/cli/v3"

"github.com/Scalingo/cli/cmd/autocomplete"
"github.com/Scalingo/cli/dbng"
"github.com/Scalingo/cli/detect"
"github.com/Scalingo/cli/utils"
"github.com/Scalingo/go-scalingo/v11"
)

var (
databaseNetPeeringsListCommand = cli.Command{
Name: "database-net-peerings",
Category: "Databases DR",
Usage: "List net peerings of a database",
ArgsUsage: "database-id",
Flags: []cli.Flag{databaseFlag()},
Description: CommandDescription{
Description: "List all net peerings of a database",
Examples: []string{
"scalingo database-net-peerings my-db-id",
"scalingo --database my-db database-net-peerings",
},
SeeAlso: []string{"database-net-peerings-add", "database-net-peerings-remove", "database-network-configuration"},
}.Render(),
Action: func(ctx context.Context, c *cli.Command) error {
databaseID := detect.ExtractDatabaseNameFromCommandLineOrEnv(c)
if databaseID == "" {
return cli.ShowCommandHelp(ctx, c, "database-net-peerings")
}
utils.CheckForConsent(ctx, databaseID, utils.ConsentTypeDBs)

err := dbng.DatabaseNetPeeringsList(ctx, databaseID)
if err != nil {
errorQuit(ctx, err)
}

return nil
},
ShellComplete: func(ctx context.Context, c *cli.Command) {
_ = autocomplete.CmdFlagsAutoComplete(c, "database-net-peerings")
_ = autocomplete.DatabasesNgListAutoComplete(ctx)
},
}

databaseNetPeeringsAddCommand = cli.Command{
Name: "database-net-peerings-add",
Category: "Databases DR",
Usage: "Add a net peering to a database",
ArgsUsage: "database-id",
Flags: []cli.Flag{
databaseFlag(),
&cli.StringFlag{
Name: "outscale-net-peering-id",
Usage: "Outscale net peering ID",
Required: true,
},
},
Description: CommandDescription{
Description: "Initiate the creation of a net peering for a database",
Examples: []string{
"scalingo database-net-peerings-add my-db-id --outscale-net-peering-id pcx-123456789",
"scalingo --database my-db database-net-peerings-add --outscale-net-peering-id pcx-123456789",
},
SeeAlso: []string{"database-net-peerings", "database-net-peerings-remove", "database-network-configuration"},
}.Render(),
Action: func(ctx context.Context, c *cli.Command) error {
databaseID := detect.ExtractDatabaseNameFromCommandLineOrEnv(c)
if databaseID == "" {
return cli.ShowCommandHelp(ctx, c, "database-net-peerings-add")
}

utils.CheckForConsent(ctx, databaseID, utils.ConsentTypeDBs)

params := scalingo.DatabaseNetPeeringCreateParams{
OutscaleNetPeeringID: c.String("outscale-net-peering-id"),
}

err := dbng.DatabaseNetPeeringsAdd(ctx, databaseID, params)
if err != nil {
errorQuit(ctx, err)
}

return nil
},
ShellComplete: func(ctx context.Context, c *cli.Command) {
_ = autocomplete.CmdFlagsAutoComplete(c, "database-net-peerings-add")
_ = autocomplete.DatabasesNgListAutoComplete(ctx)
},
}

databaseNetPeeringsRemoveCommand = cli.Command{
Name: "database-net-peerings-remove",
Category: "Databases DR",
Usage: "Remove a net peering from a database",
ArgsUsage: "database-id net-peering-id",
Flags: []cli.Flag{
databaseFlag(),
&cli.StringFlag{
Name: "net-peering",
Aliases: []string{"np"},
Usage: "Net peering ID to remove",
},
},
Description: CommandDescription{
Description: "Delete an existing net peering from a database",
Examples: []string{
"scalingo database-net-peerings-remove my-db-id np-id",
"scalingo --database my-db database-net-peerings-remove np-id",
},
SeeAlso: []string{"database-net-peerings", "database-net-peerings-add", "database-network-configuration"},
}.Render(),
Action: func(ctx context.Context, c *cli.Command) error {
databaseID := detect.ExtractDatabaseNameFromCommandLineOrEnv(c)
if databaseID == "" {
return cli.ShowCommandHelp(ctx, c, "database-net-peerings-remove")
}

utils.CheckForConsent(ctx, databaseID, utils.ConsentTypeDBs)

err := dbng.DatabaseNetPeeringsRemove(ctx, databaseID, c.String("net-peering"))
if err != nil {
errorQuit(ctx, err)
}

return nil
},
ShellComplete: func(ctx context.Context, c *cli.Command) {
_ = autocomplete.CmdFlagsAutoComplete(c, "database-net-peerings-remove")
_ = autocomplete.DatabasesNgListAutoComplete(ctx)
},
}

databaseNetworkConfigurationShowCommand = cli.Command{
Name: "database-network-configuration",
Category: "Databases DR",
Usage: "Show network configuration of a database",
ArgsUsage: "database-id",
Flags: []cli.Flag{databaseFlag()},
Description: CommandDescription{
Description: "Get network configuration of a database",
Examples: []string{
"scalingo database-network-configuration my-db-id",
"scalingo --database my-db database-network-configuration",
},
SeeAlso: []string{"database-net-peerings", "database-net-peerings-add", "database-net-peerings-remove"},
}.Render(),
Action: func(ctx context.Context, c *cli.Command) error {
databaseID := detect.ExtractDatabaseNameFromCommandLineOrEnv(c)
if databaseID == "" {
return cli.ShowCommandHelp(ctx, c, "database-network-configuration")
}

utils.CheckForConsent(ctx, databaseID, utils.ConsentTypeDBs)

err := dbng.DatabaseNetworkConfigurationShow(ctx, databaseID)
if err != nil {
errorQuit(ctx, err)
}

return nil
},
ShellComplete: func(ctx context.Context, c *cli.Command) {
_ = autocomplete.CmdFlagsAutoComplete(c, "database-network-configuration")
_ = autocomplete.DatabasesNgListAutoComplete(ctx)
},
}
)
3 changes: 3 additions & 0 deletions cmd/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ func displayRequestFailedError(requestFailedErr *httpclient.RequestFailedError,
fmt.Println(io.Indent(err.Error(), 7))
}

case http.StatusConflict: // 409
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: do we want to also handle debug here? (like for 404)

// In case of conflict error, we only want to display the API error.
io.Errorf("%s\n", strings.ReplaceAll(requestFailedErr.Error(), `\n`, "\n"))
case http.StatusUnprocessableEntity:
unprocessableEntityErr, ok := requestFailedErr.APIError.(httpclient.UnprocessableEntity)
if !ok {
Expand Down
8 changes: 4 additions & 4 deletions cmd/firewall_rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
var (
databaseFirewallRulesListCommand = cli.Command{
Name: "database-firewall-rules",
Category: "Databases NG",
Category: "Databases DR",
Usage: "List firewall rules of a database",
ArgsUsage: "database-id",
Flags: []cli.Flag{databaseFlag()},
Expand Down Expand Up @@ -56,7 +56,7 @@ var (

databaseFirewallRulesAddCommand = cli.Command{
Name: "database-firewall-rules-add",
Category: "Databases NG",
Category: "Databases DR",
Usage: "Add a firewall rule to a database",
ArgsUsage: "database-id",
Flags: []cli.Flag{
Expand Down Expand Up @@ -133,7 +133,7 @@ var (

databaseFirewallRulesRemoveCommand = cli.Command{
Name: "database-firewall-rules-remove",
Category: "Databases NG",
Category: "Databases DR",
Usage: "Remove a firewall rule from a database",
ArgsUsage: "database-id rule-id",
Flags: []cli.Flag{databaseFlag()},
Expand Down Expand Up @@ -194,7 +194,7 @@ var (

databaseFirewallManagedRangesCommand = cli.Command{
Name: "database-firewall-managed-ranges",
Category: "Databases NG",
Category: "Databases DR",
Usage: "List available managed ranges for a database",
ArgsUsage: "database-id",
Flags: []cli.Flag{databaseFlag()},
Expand Down
104 changes: 104 additions & 0 deletions dbng/networking.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package dbng

import (
"context"
"os"

"github.com/olekukonko/tablewriter"

"github.com/Scalingo/cli/config"
"github.com/Scalingo/cli/io"
"github.com/Scalingo/go-scalingo/v11"
"github.com/Scalingo/go-utils/errors/v3"
)

func DatabaseNetPeeringsList(ctx context.Context, databaseID string) error {
c, err := config.ScalingoClient(ctx)
if err != nil {
return errors.Wrap(ctx, err, "get Scalingo client")
}

netPeerings, err := c.Preview().DatabaseNetPeeringsList(ctx, databaseID)
if err != nil {
return errors.Wrap(ctx, err, "list database net peerings")
}

if len(netPeerings) == 0 {
io.Status("No net peering configured for this database.")
return nil
}

t := tablewriter.NewWriter(os.Stdout)
t.Header([]string{"ID", "Status", "Outscale Net Peering ID", "Source Net ID", "Source Net IP Range", "Source Account ID"})

for _, netPeering := range netPeerings {
_ = t.Append([]string{
netPeering.ID,
string(netPeering.Status),
netPeering.OutscaleNetPeeringID,
netPeering.OutscaleSourceNetID,
netPeering.OutscaleSourceNetIPRange,
netPeering.OutscaleSourceAccountID,
})
}

_ = t.Render()

return nil
}

func DatabaseNetPeeringsAdd(ctx context.Context, databaseID string, params scalingo.DatabaseNetPeeringCreateParams) error {
c, err := config.ScalingoClient(ctx)
if err != nil {
return errors.Wrap(ctx, err, "get Scalingo client")
}

netPeering, err := c.Preview().DatabaseNetPeeringCreate(ctx, databaseID, params)
if err != nil {
return errors.Wrap(ctx, err, "add database net peering")
}

io.Statusf("Net peering '%s' creation has been initiated for database '%s'.\n", netPeering.ID, databaseID)
io.Warning("Expect some delay for the net peering to be applied.")

return nil
}

func DatabaseNetPeeringsRemove(ctx context.Context, databaseID, netPeeringID string) error {
c, err := config.ScalingoClient(ctx)
if err != nil {
return errors.Wrap(ctx, err, "get Scalingo client")
}

err = c.Preview().DatabaseNetPeeringDestroy(ctx, databaseID, netPeeringID)
if err != nil {
return errors.Wrap(ctx, err, "remove database net peering")
}

io.Statusf("Net peering '%s' has been removed from database '%s'.\n", netPeeringID, databaseID)
io.Warning("Expect some delay for the net peering removal to be applied.")

return nil
}

func DatabaseNetworkConfigurationShow(ctx context.Context, databaseID string) error {
c, err := config.ScalingoClient(ctx)
if err != nil {
return errors.Wrap(ctx, err, "get Scalingo client")
}

networkConfiguration, err := c.Preview().DatabaseNetworkConfigurationShow(ctx, databaseID)
if err != nil {
return errors.Wrap(ctx, err, "show database network configuration")
}

t := tablewriter.NewWriter(os.Stdout)
_ = t.Bulk([][]string{
{"Outscale account ID", networkConfiguration.OutscaleAccountID},
{"Outscale net ID", networkConfiguration.OutscaleNetID},
{"IP range", networkConfiguration.IPRange},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: no need for the OutscaleSourceAccountID like for the list?

})
_ = t.Render()

return nil
}
4 changes: 2 additions & 2 deletions detect/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func CurrentApp(ctx context.Context, c *cli.Command) string {
// It returns an empty string and errDatabaseNotFound error if the database name
// is not provided or resource ID not found.
func currentDatabaseNameAndUUID(ctx context.Context, c *cli.Command) (string, string, error) {
dbName := extractDatabaseNameFromCommandLineOrEnv(c)
dbName := ExtractDatabaseNameFromCommandLineOrEnv(c)
if dbName == "" {
return "", "", errDatabaseNotFound
}
Expand Down Expand Up @@ -251,7 +251,7 @@ func extractAppName(ctx context.Context, c *cli.Command) string {
return appName
}

func extractDatabaseNameFromCommandLineOrEnv(c *cli.Command) string {
func ExtractDatabaseNameFromCommandLineOrEnv(c *cli.Command) string {
if os.Getenv("SCALINGO_PREVIEW_FEATURES") != "true" {
return ""
}
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ go 1.25.0

require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/Scalingo/go-scalingo/v11 v11.0.3
github.com/Scalingo/go-scalingo/v11 v11.0.1-0.20260513124010-187ac32aa251
github.com/Scalingo/go-utils/errors/v3 v3.2.1
github.com/Scalingo/go-utils/logger v1.12.2
github.com/Scalingo/go-utils/logger v1.12.1
github.com/Scalingo/go-utils/pagination v1.2.0
github.com/Scalingo/gopassword v1.1.0
github.com/briandowns/spinner v1.23.2
Expand All @@ -27,7 +27,7 @@ require (
go.uber.org/mock v0.6.0
golang.org/x/crypto v0.50.0
golang.org/x/term v0.42.0
golang.org/x/text v0.36.0
golang.org/x/text v0.37.0
)

require (
Expand Down
Loading