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 @@ -10,7 +10,8 @@ ARGS:
[name=<generated>] Name of the Database Instance
engine Database engine of the Database Instance (PostgreSQL, MySQL, ...)
user-name Username created when the Database Instance is created
password Password of the user
[generate-password=true] Will generate a 21 character-length password that contains a mix of upper/lower case letters, numbers and special symbols
[password] Password of the user
node-type=DB-DEV-S Type of node to use for the Database Instance
[is-ha-cluster] Defines whether or not High-Availability is enabled
[disable-backup] Defines whether or not backups are disabled
Expand Down
11 changes: 6 additions & 5 deletions cmd/scw/testdata/test-all-usage-rdb-user-create-usage.golden
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ USAGE:
scw rdb user create [arg=value ...]

ARGS:
instance-id UUID of the Database Instance in which you want to create a user
[name] Name of the user you want to create
[password] Password of the user you want to create
[is-admin] Defines whether the user will have administrative privileges
[region=fr-par] Region to target. If none is passed will use default region from the config (fr-par | nl-ams | pl-waw)
instance-id UUID of the Database Instance in which you want to create a user
[name] Name of the user you want to create
[generate-password=true] Will generate a 21 character-length password that contains a mix of upper/lower case letters, numbers and special symbols
[password] Password of the user you want to create
[is-admin] Defines whether the user will have administrative privileges
[region=fr-par] Region to target. If none is passed will use default region from the config (fr-par | nl-ams | pl-waw)

FLAGS:
-h, --help help for create
Expand Down
11 changes: 6 additions & 5 deletions cmd/scw/testdata/test-all-usage-rdb-user-update-usage.golden
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ USAGE:
scw rdb user update [arg=value ...]

ARGS:
instance-id UUID of the Database Instance the user belongs to
name Name of the database user
[password] Password of the database user
[is-admin] Defines whether or not this user got administrative privileges
[region=fr-par] Region to target. If none is passed will use default region from the config (fr-par | nl-ams | pl-waw)
instance-id UUID of the Database Instance the user belongs to
name Name of the database user
[generate-password=true] Will generate a 21 character-length password that contains a mix of upper/lower case letters, numbers and special symbols
[password] Password of the database user
[is-admin] Defines whether or not this user got administrative privileges
[region=fr-par] Region to target. If none is passed will use default region from the config (fr-par | nl-ams | pl-waw)

FLAGS:
-h, --help help for update
Expand Down
5 changes: 4 additions & 1 deletion docs/commands/rdb.md
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,8 @@ scw rdb instance create [arg=value ...]
| name | Default: `<generated>` | Name of the Database Instance |
| engine | Required | Database engine of the Database Instance (PostgreSQL, MySQL, ...) |
| user-name | Required | Username created when the Database Instance is created |
| password | Required | Password of the user |
| generate-password | Default: `true` | Will generate a 21 character-length password that contains a mix of upper/lower case letters, numbers and special symbols |
| password | | Password of the user |
| node-type | Required<br />Default: `DB-DEV-S` | Type of node to use for the Database Instance |
| is-ha-cluster | | Defines whether or not High-Availability is enabled |
| disable-backup | | Defines whether or not backups are disabled |
Expand Down Expand Up @@ -1513,6 +1514,7 @@ scw rdb user create [arg=value ...]
|------|---|-------------|
| instance-id | Required | UUID of the Database Instance in which you want to create a user |
| name | | Name of the user you want to create |
| generate-password | Default: `true` | Will generate a 21 character-length password that contains a mix of upper/lower case letters, numbers and special symbols |
| password | | Password of the user you want to create |
| is-admin | | Defines whether the user will have administrative privileges |
| region | Default: `fr-par`<br />One of: `fr-par`, `nl-ams`, `pl-waw` | Region to target. If none is passed will use default region from the config |
Expand Down Expand Up @@ -1579,6 +1581,7 @@ scw rdb user update [arg=value ...]
|------|---|-------------|
| instance-id | Required | UUID of the Database Instance the user belongs to |
| name | Required | Name of the database user |
| generate-password | Default: `true` | Will generate a 21 character-length password that contains a mix of upper/lower case letters, numbers and special symbols |
| password | | Password of the database user |
| is-admin | | Defines whether or not this user got administrative privileges |
| region | Default: `fr-par`<br />One of: `fr-par`, `nl-ams`, `pl-waw` | Region to target. If none is passed will use default region from the config |
Expand Down
3 changes: 3 additions & 0 deletions internal/namespaces/rdb/v1/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func GetCommands() *core.Commands {
human.RegisterMarshalerFunc(rdb.Instance{}, instanceMarshalerFunc)
human.RegisterMarshalerFunc(rdb.BackupSchedule{}, backupScheduleMarshalerFunc)
human.RegisterMarshalerFunc(backupDownloadResult{}, backupResultMarshallerFunc)
human.RegisterMarshalerFunc(createInstanceResult{}, createInstanceResultMarshalerFunc)

human.RegisterMarshalerFunc(rdb.InstanceStatus(""), human.EnumMarshalFunc(instanceStatusMarshalSpecs))
human.RegisterMarshalerFunc(rdb.DatabaseBackupStatus(""), human.EnumMarshalFunc(backupStatusMarshalSpecs))
Expand Down Expand Up @@ -41,6 +42,8 @@ func GetCommands() *core.Commands {
cmds.MustFind("rdb", "engine", "list").Override(engineListBuilder)

cmds.MustFind("rdb", "user", "list").Override(userListBuilder)
cmds.MustFind("rdb", "user", "create").Override(userCreateBuilder)
cmds.MustFind("rdb", "user", "update").Override(userUpdateBuilder)

cmds.MustFind("rdb", "backup", "list").Override(backupListBuilder)

Expand Down
101 changes: 97 additions & 4 deletions internal/namespaces/rdb/v1/custom_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"github.com/scaleway/scaleway-cli/v2/internal/core"
"github.com/scaleway/scaleway-cli/v2/internal/human"
"github.com/scaleway/scaleway-cli/v2/internal/interactive"
"github.com/scaleway/scaleway-cli/v2/internal/passwordgenerator"
"github.com/scaleway/scaleway-cli/v2/internal/terminal"
"github.com/scaleway/scaleway-sdk-go/api/rdb/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
Expand Down Expand Up @@ -48,6 +50,40 @@ type serverWaitRequest struct {
Timeout time.Duration
}

type createInstanceResult struct {
*rdb.Instance
Password string `json:"password"`
}

func createInstanceResultMarshalerFunc(i interface{}, opt *human.MarshalOpt) (string, error) {
instanceResult := i.(createInstanceResult)

opt.Sections = []*human.MarshalSection{
{
FieldName: "Endpoint",
},
{
FieldName: "Volume",
},
{
FieldName: "BackupSchedule",
},
{
FieldName: "Settings",
},
}

instanceStr, err := human.Marshal(instanceResult.Instance, opt)
if err != nil {
return "", err
}

return strings.Join([]string{
instanceStr,
terminal.Style("Password: ", color.Bold) + "\n" + instanceResult.Password,
}, "\n\n"), nil
}

func instanceMarshalerFunc(i interface{}, opt *human.MarshalOpt) (string, error) {
// To avoid recursion of human.Marshal we create a dummy type
type tmp rdb.Instance
Expand Down Expand Up @@ -154,22 +190,79 @@ func autoCompleteNodeType(ctx context.Context, prefix string) core.AutocompleteS
}

func instanceCreateBuilder(c *core.Command) *core.Command {
type rdbCreateInstanceRequestCustom struct {
*rdb.CreateInstanceRequest
GeneratePassword bool
}

c.ArgSpecs.AddBefore("password", &core.ArgSpec{
Name: "generate-password",
Short: `Will generate a 21 character-length password that contains a mix of upper/lower case letters, numbers and special symbols`,
Required: false,
Deprecated: false,
Positional: false,
Default: core.DefaultValueSetter("true"),
})
c.ArgSpecs.GetByName("password").Required = false
c.ArgSpecs.GetByName("node-type").Default = core.DefaultValueSetter("DB-DEV-S")
c.ArgSpecs.GetByName("node-type").AutoCompleteFunc = autoCompleteNodeType

c.ArgsType = reflect.TypeOf(rdbCreateInstanceRequestCustom{})

c.WaitFunc = func(ctx context.Context, argsI, respI interface{}) (interface{}, error) {
api := rdb.NewAPI(core.ExtractClient(ctx))
return api.WaitForInstance(&rdb.WaitForInstanceRequest{
InstanceID: respI.(*rdb.Instance).ID,
Region: respI.(*rdb.Instance).Region,
instance, err := api.WaitForInstance(&rdb.WaitForInstanceRequest{
InstanceID: respI.(createInstanceResult).Instance.ID,
Region: respI.(createInstanceResult).Instance.Region,
Timeout: scw.TimeDurationPtr(instanceActionTimeout),
RetryInterval: core.DefaultRetryInterval,
})
if err != nil {
return nil, err
}

result := createInstanceResult{
Instance: instance,
Password: respI.(createInstanceResult).Password,
}

return result, nil
}

c.Run = func(ctx context.Context, argsI interface{}) (interface{}, error) {
client := core.ExtractClient(ctx)
api := rdb.NewAPI(client)

customRequest := argsI.(*rdbCreateInstanceRequestCustom)
createInstanceRequest := customRequest.CreateInstanceRequest

var err error
createInstanceRequest.NodeType = strings.ToLower(createInstanceRequest.NodeType)
if customRequest.GeneratePassword && customRequest.Password == "" {
createInstanceRequest.Password, err = passwordgenerator.GeneratePassword(21, 1, 1, 1, 1)
if err != nil {
return nil, err
}
fmt.Printf("Your generated password is %s \n", createInstanceRequest.Password)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information

[Sensitive data returned by an access to Password](1) flows to a logging call. [Sensitive data returned by an access to password](2) flows to a logging call. [Sensitive data returned by an access to Password](3) flows to a logging call.
fmt.Printf("\n")
}

instance, err := api.CreateInstance(createInstanceRequest)
if err != nil {
return nil, err
}

result := createInstanceResult{
Instance: instance,
Password: createInstanceRequest.Password,
}

return result, nil
}

// Waiting for API to accept uppercase node-type
c.Interceptor = func(ctx context.Context, argsI interface{}, runner core.CommandRunner) (interface{}, error) {
args := argsI.(*rdb.CreateInstanceRequest)
args := argsI.(*rdbCreateInstanceRequestCustom)
args.NodeType = strings.ToLower(args.NodeType)
return runner(ctx, args)
}
Expand Down
8 changes: 8 additions & 0 deletions internal/namespaces/rdb/v1/custom_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ func Test_CreateInstance(t *testing.T) {
Check: core.TestCheckGolden(),
AfterFunc: core.ExecAfterCmd("scw rdb instance delete {{ .CmdResult.ID }}"),
}))

t.Run("With password generator", core.Test(&core.TestConfig{
Commands: GetCommands(),
Cmd: fmt.Sprintf("scw rdb instance create node-type=DB-DEV-S is-ha-cluster=false name=%s engine=%s user-name=%s generate-password=true --wait", name, engine, user),
// do not check the golden as the password generated locally and on CI will necessarily be different
Check: core.TestCheckExitCode(0),
AfterFunc: core.ExecAfterCmd("scw rdb instance delete {{ .CmdResult.ID }}"),
}))
}

func Test_GetInstance(t *testing.T) {
Expand Down
113 changes: 113 additions & 0 deletions internal/namespaces/rdb/v1/custom_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package rdb

import (
"context"
"fmt"
"reflect"

"github.com/scaleway/scaleway-cli/v2/internal/core"
"github.com/scaleway/scaleway-cli/v2/internal/passwordgenerator"
"github.com/scaleway/scaleway-sdk-go/api/rdb/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
Expand Down Expand Up @@ -80,3 +83,113 @@ func userListBuilder(c *core.Command) *core.Command {

return c
}

func userCreateBuilder(c *core.Command) *core.Command {
type rdbCreateUserRequestCustom struct {
*rdb.CreateUserRequest
GeneratePassword bool
}

type rdbCreateUserResponseCustom struct {
*rdb.User
Password string `json:"password"`
}

c.ArgSpecs.AddBefore("password", &core.ArgSpec{
Name: "generate-password",
Short: `Will generate a 21 character-length password that contains a mix of upper/lower case letters, numbers and special symbols`,
Required: false,
Deprecated: false,
Positional: false,
Default: core.DefaultValueSetter("true"),
})
c.ArgsType = reflect.TypeOf(rdbCreateUserRequestCustom{})

c.Run = func(ctx context.Context, argsI interface{}) (interface{}, error) {
client := core.ExtractClient(ctx)
api := rdb.NewAPI(client)

customRequest := argsI.(*rdbCreateUserRequestCustom)
createUserRequest := customRequest.CreateUserRequest

var err error
if customRequest.GeneratePassword && customRequest.Password == "" {
createUserRequest.Password, err = passwordgenerator.GeneratePassword(21, 1, 1, 1, 1)
if err != nil {
return nil, err
}
fmt.Printf("Your generated password is %s \n", createUserRequest.Password)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information

[Sensitive data returned by an access to Password](1) flows to a logging call. [Sensitive data returned by an access to password](2) flows to a logging call. [Sensitive data returned by an access to Password](3) flows to a logging call.
fmt.Printf("\n")
}

user, err := api.CreateUser(createUserRequest)
if err != nil {
return nil, err
}

result := rdbCreateUserResponseCustom{
User: user,
Password: createUserRequest.Password,
}

return result, nil
}

return c
}

func userUpdateBuilder(c *core.Command) *core.Command {
type rdbUpdateUserRequestCustom struct {
*rdb.UpdateUserRequest
GeneratePassword bool
}

type rdbUpdateUserResponseCustom struct {
*rdb.User
Password string `json:"password"`
}

c.ArgSpecs.AddBefore("password", &core.ArgSpec{
Name: "generate-password",
Short: `Will generate a 21 character-length password that contains a mix of upper/lower case letters, numbers and special symbols`,
Required: false,
Deprecated: false,
Positional: false,
Default: core.DefaultValueSetter("true"),
})
c.ArgsType = reflect.TypeOf(rdbUpdateUserRequestCustom{})

c.Run = func(ctx context.Context, argsI interface{}) (interface{}, error) {
client := core.ExtractClient(ctx)
api := rdb.NewAPI(client)

customRequest := argsI.(*rdbUpdateUserRequestCustom)

updateUserRequest := customRequest.UpdateUserRequest

var err error
if customRequest.GeneratePassword && customRequest.Password == nil {
updateUserRequest.Password = new(string)
*updateUserRequest.Password, err = passwordgenerator.GeneratePassword(21, 1, 1, 1, 1)
if err != nil {
return nil, err
}
fmt.Printf("Your generated password is %v \n", *updateUserRequest.Password)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information

[Sensitive data returned by an access to Password](1) flows to a logging call. [Sensitive data returned by an access to Password](2) flows to a logging call.
fmt.Printf("\n")
}

user, err := api.UpdateUser(updateUserRequest)
if err != nil {
return nil, err
}

result := rdbUpdateUserResponseCustom{
User: user,
Password: *updateUserRequest.Password,
}

return result, nil
}

return c
}
Loading