Skip to content

Commit

Permalink
feat(cmd): add database user creation
Browse files Browse the repository at this point in the history
  • Loading branch information
curzolapierre committed Nov 13, 2023
1 parent 1a1ee8f commit d5dd9d1
Show file tree
Hide file tree
Showing 9 changed files with 242 additions and 1 deletion.
1 change: 1 addition & 0 deletions cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ var (
&databaseDisableFeature,
&databaseListUsers,
&databaseDeleteUser,
&databaseCreateUser,

// Maintenance
&databaseMaintenanceList,
Expand Down
38 changes: 38 additions & 0 deletions cmd/databases.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,44 @@ var (
return nil
},
}

databaseCreateUser = cli.Command{
Name: "database-create-user",
Category: "Addons",
ArgsUsage: "user",
Usage: "Create new database user",
Flags: []cli.Flag{
&appFlag,
&addonFlag,
&cli.BoolFlag{Name: "read-only", Usage: "Create a user with read-only rights"},
},
Description: CommandDescription{
Description: "Create new database user",
Examples: []string{
"scalingo --app myapp --addon addon-uuid database-create-user my_user",
"scalingo --app myapp --addon addon-uuid database-create-user my_user --read-only",
},
}.Render(),

Action: func(c *cli.Context) error {
if c.Args().Len() != 1 {
cli.ShowCommandHelp(c, "database-create-user")
return nil
}

currentApp := detect.CurrentApp(c)
utils.CheckForConsent(c.Context, currentApp, utils.ConsentTypeDBs)
addonName := addonUUIDFromFlags(c, currentApp, true)

username := c.Args().First()

err := dbUsers.CreateUser(c.Context, currentApp, addonName, username, c.Bool("read-only"))
if err != nil {
errorQuit(c.Context, err)
}
return nil
},
}
)

func parseScheduleAtFlag(flag string) (int, *time.Location, error) {
Expand Down
110 changes: 110 additions & 0 deletions db/users/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package users

import (
"context"
"fmt"
"os"

"golang.org/x/term"

"github.com/Scalingo/cli/config"
"github.com/Scalingo/cli/io"
"github.com/Scalingo/go-scalingo/v6"
scErrors "github.com/Scalingo/go-utils/errors/v2"
"github.com/Scalingo/gopassword"
)

func CreateUser(ctx context.Context, app, addonUUID, username string, readonly bool) error {
isSupported, err := isDatabaseHandlesUserManagement(ctx, app, addonUUID)
if err != nil {
return scErrors.Wrap(ctx, err, "get user management information")
}

if !isSupported {
io.Error(ErrDatabaseNotSupportUserManagement)
return nil
}

if usernameValidation, ok := isUsernameValid(username); !ok {
io.Error(usernameValidation)
return nil
}

password, confirmedPassword, err := askForPassword(ctx)
if err != nil {
return scErrors.Wrap(ctx, err, "ask for password")
}

passwordValidation, ok := isPasswordValid(password, confirmedPassword)
if !ok && password != "" {
io.Error(passwordValidation)
return nil
}

isPasswordGenerated := false
if password == "" {
isPasswordGenerated = true
password = gopassword.Generate(64)
confirmedPassword = password
}

c, err := config.ScalingoClient(ctx)
if err != nil {
return scErrors.Wrap(ctx, err, "get Scalingo client")
}

user := scalingo.DatabaseCreateUserParam{
DatabaseID: addonUUID,
Name: username,
Password: password,
PasswordConfirmation: confirmedPassword,
ReadOnly: readonly,
}
databaseUsers, err := c.DatabaseCreateUser(ctx, app, addonUUID, user)
if err != nil {
return scErrors.Wrap(ctx, err, "create the given database user")
}

if isPasswordGenerated {
fmt.Printf("User \"%s\" created with password \"%s\".\n", databaseUsers.Name, password)
return nil
}

fmt.Printf("User \"%s\" created.\n", databaseUsers.Name)
return nil
}

func askForPassword(ctx context.Context) (string, string, error) {
fmt.Printf("Password: (Will be generated if left empty) ")

bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", "", scErrors.Wrap(ctx, err, "read password")
}

fmt.Printf("\nPassword Confirmation: ")
byteConfirmedPassword, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", "", scErrors.Wrap(ctx, err, "read password confirmation")
}
fmt.Printf("\n")

return string(bytePassword), string(byteConfirmedPassword), nil
}

func isPasswordValid(password, confirmedPassword string) (string, bool) {
if password != confirmedPassword {
return "Password confirmation don't watch", false
}
if len(password) < 8 || len(password) > 64 {
return "Password must be between 8 and 64 characters", false
}
return "", true
}

func isUsernameValid(username string) (string, bool) {
if len(username) < 6 || len(username) > 32 {
return "name must be between 6 and 32 characters", false
}
return "", true
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/Scalingo/go-utils/errors/v2 v2.3.0
github.com/Scalingo/go-utils/logger v1.2.0
github.com/Scalingo/go-utils/retry v1.1.1
github.com/Scalingo/gopassword v1.0.2
github.com/briandowns/spinner v1.23.0
github.com/cheggaaa/pb/v3 v3.1.4
github.com/dustin/go-humanize v1.0.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ github.com/Scalingo/go-utils/logger v1.2.0 h1:E3jtaoRxpIsFcZu/jsvWew8ttUAwKUYQuf
github.com/Scalingo/go-utils/logger v1.2.0/go.mod h1:JArjD1gHdB/vwnlcVG7rYxuIY0tk8/VG4MtirnRwn8k=
github.com/Scalingo/go-utils/retry v1.1.1 h1:zc5HbXbBzf0fo7zMvhEMvx75qaL2FH7SxDhCoBfS7jE=
github.com/Scalingo/go-utils/retry v1.1.1/go.mod h1:za1k9sUU7fSQEFTv3c4Y8kG/8ybsq6C77sOo3ANEeN4=
github.com/Scalingo/gopassword v1.0.2 h1:wcKt8twrnDgYi4W9ep54jUfNNUyTmUb/1uS8F5+iUvQ=
github.com/Scalingo/gopassword v1.0.2/go.mod h1:qthHL75azoNpGlQlVHIaljbONYubRatIHJW5VYVFOjQ=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
Expand Down
20 changes: 20 additions & 0 deletions vendor/github.com/Scalingo/gopassword/CHANGELOG.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions vendor/github.com/Scalingo/gopassword/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions vendor/github.com/Scalingo/gopassword/gopassword.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ github.com/ProtonMail/go-crypto/openpgp/internal/ecc
github.com/ProtonMail/go-crypto/openpgp/internal/encoding
github.com/ProtonMail/go-crypto/openpgp/packet
github.com/ProtonMail/go-crypto/openpgp/s2k
# github.com/Scalingo/go-scalingo/v6 v6.7.3
# github.com/Scalingo/go-scalingo/v6 v6.7.3 => ../go-scalingo
## explicit; go 1.20
github.com/Scalingo/go-scalingo/v6
github.com/Scalingo/go-scalingo/v6/billing
Expand All @@ -50,6 +50,9 @@ github.com/Scalingo/go-utils/logger
# github.com/Scalingo/go-utils/retry v1.1.1
## explicit; go 1.17
github.com/Scalingo/go-utils/retry
# github.com/Scalingo/gopassword v1.0.2
## explicit; go 1.17
github.com/Scalingo/gopassword
# github.com/VividCortex/ewma v1.2.0
## explicit; go 1.12
github.com/VividCortex/ewma
Expand Down Expand Up @@ -337,3 +340,4 @@ gopkg.in/warnings.v0
# gopkg.in/yaml.v3 v3.0.1
## explicit
gopkg.in/yaml.v3
# github.com/Scalingo/go-scalingo/v6 => ../go-scalingo

0 comments on commit d5dd9d1

Please sign in to comment.