Skip to content

Commit

Permalink
Add commands for passwords
Browse files Browse the repository at this point in the history
  • Loading branch information
Phani Raj committed Aug 11, 2021
1 parent 31564eb commit 364d561
Show file tree
Hide file tree
Showing 14 changed files with 713 additions and 2 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ require (
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744
gopkg.in/yaml.v2 v2.4.0
)

replace github.com/planetscale/planetscale-go => /Users/phaniraj/ps/planetscale-go
60 changes: 60 additions & 0 deletions internal/cmd/passwords/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package passwords

import (
"fmt"

"github.com/planetscale/cli/internal/cmdutil"
"github.com/planetscale/cli/internal/printer"
ps "github.com/planetscale/planetscale-go/planetscale"

"github.com/spf13/cobra"
)

func CreateCmd(ch *cmdutil.Helper) *cobra.Command {
createReq := &ps.DatabaseBranchPasswordRequest{}
cmd := &cobra.Command{
Use: "create <database> <branch> <name>",
Short: "create a password to access a branch's data",
Args: cmdutil.RequiredArgs("database", "branch", "name"),
Aliases: []string{"p"},
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
database := args[0]
branch := args[1]
name := args[2]
createReq.Database = database
createReq.Branch = branch
createReq.Organization = ch.Config.Organization
createReq.DisplayName = name

client, err := ch.Client()
if err != nil {
return err
}

end := ch.Printer.PrintProgress(fmt.Sprintf("Creating password of %s...", printer.BoldBlue(branch)))
defer end()

pass, err := client.Passwords.Create(ctx, createReq)
if err != nil {
switch cmdutil.ErrCode(err) {
case ps.ErrNotFound:
return fmt.Errorf("branch %s does not exist in database %s (organization: %s)",
printer.BoldBlue(branch), printer.BoldBlue(database), printer.BoldBlue(ch.Config.Organization))
default:
return cmdutil.HandleError(err)
}
}

end()
if ch.Printer.Format() == printer.Human {
saveWarning := printer.BoldRed("Please save the values below as they will not be shown again")
ch.Printer.Printf("Password %s was successfully created.\n%s\n\n", printer.BoldBlue(pass.Name), saveWarning)
}

return ch.Printer.PrintResource(toPasswordWithPlainText(pass))
},
}

return cmd
}
61 changes: 61 additions & 0 deletions internal/cmd/passwords/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package passwords

import (
"bytes"
"context"
"testing"

"github.com/planetscale/cli/internal/cmdutil"
"github.com/planetscale/cli/internal/config"
"github.com/planetscale/cli/internal/mock"
"github.com/planetscale/cli/internal/printer"
ps "github.com/planetscale/planetscale-go/planetscale"

qt "github.com/frankban/quicktest"
)

func TestPassword_CreateCmd(t *testing.T) {
c := qt.New(t)

var buf bytes.Buffer
format := printer.JSON
p := printer.NewPrinter(&format)
p.SetResourceOutput(&buf)

org := "planetscale"
db := "planetscale"
branch := "development"

res := &ps.Password{Name: "foo"}

svc := &mock.PasswordsService{
CreateFn: func(ctx context.Context, req *ps.CreatePasswordRequest) (*ps.Password, error) {
c.Assert(req.Organization, qt.Equals, org)
c.Assert(req.Database, qt.Equals, db)
c.Assert(req.Branch, qt.Equals, branch)

return res, nil
},
}

ch := &cmdutil.Helper{
Printer: p,
Config: &config.Config{
Organization: org,
},
Client: func() (*ps.Client, error) {
return &ps.Client{
Passwords: svc,
}, nil

},
}

cmd := CreateCmd(ch)
cmd.SetArgs([]string{db, branch})
err := cmd.Execute()

c.Assert(err, qt.IsNil)
c.Assert(svc.CreateFnInvoked, qt.IsTrue)
c.Assert(buf.String(), qt.JSONEquals, res)
}
108 changes: 108 additions & 0 deletions internal/cmd/passwords/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package passwords

import (
"errors"
"fmt"
"os"

"github.com/planetscale/cli/internal/cmdutil"
"github.com/planetscale/cli/internal/printer"

"github.com/planetscale/planetscale-go/planetscale"

"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/spf13/cobra"
)

func DeleteCmd(ch *cmdutil.Helper) *cobra.Command {
var force bool

cmd := &cobra.Command{
Use: "delete <database> <branch> <password>",
Short: "Delete a branch password",
Args: cmdutil.RequiredArgs("database", "branch", "password"),
Aliases: []string{"rm"},
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
database := args[0]
branch := args[1]
password := args[2]

client, err := ch.Client()
if err != nil {
return err
}

if !force {
if ch.Printer.Format() != printer.Human {
return fmt.Errorf("cannot delete password with the output format %q (run with -force to override)", ch.Printer.Format())
}

confirmationName := fmt.Sprintf("%s/%s/%s", database, branch, password)
if !printer.IsTTY {
return fmt.Errorf("cannot confirm deletion of password %q (run with -force to override)", confirmationName)
}

confirmationMessage := fmt.Sprintf("%s %s %s", printer.Bold("Please type"), printer.BoldBlue(confirmationName), printer.Bold("to confirm:"))

prompt := &survey.Input{
Message: confirmationMessage,
}

var userInput string
err := survey.AskOne(prompt, &userInput)
if err != nil {
if err == terminal.InterruptErr {
os.Exit(0)
} else {
return err
}
}

// If the confirmations don't match up, let's return an error.
if userInput != confirmationName {
return errors.New("incorrect password name entered, skipping password deletion")
}
}

end := ch.Printer.PrintProgress(fmt.Sprintf("Deleting password %s from %s", printer.BoldBlue(password), printer.BoldBlue(branch)))
defer end()

err = client.Passwords.Delete(ctx, &planetscale.DatabaseBranchPasswordRequest{
Organization: ch.Config.Organization,
Database: database,
Branch: branch,
DisplayName: password,
})
if err != nil {
switch cmdutil.ErrCode(err) {
case planetscale.ErrNotFound:
return fmt.Errorf("password %s does not exist in branch %s of %s (organization: %s)",
printer.BoldBlue(password), printer.BoldBlue(branch), printer.BoldBlue(database), printer.BoldBlue(ch.Config.Organization))
default:
return cmdutil.HandleError(err)
}
}

end()

if ch.Printer.Format() == printer.Human {
ch.Printer.Printf("Password %s was successfully deleted from %s.\n",
printer.BoldBlue(password), printer.BoldBlue(branch))
return nil
}

return ch.Printer.PrintResource(
map[string]string{
"result": "password deleted",
"password": password,
"branch": branch,
},
)
},
}

cmd.Flags().BoolVar(&force, "force", false, "Delete a password without confirmation")
return cmd
}
67 changes: 67 additions & 0 deletions internal/cmd/passwords/delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package passwords

import (
"bytes"
"context"
"testing"

"github.com/planetscale/cli/internal/cmdutil"
"github.com/planetscale/cli/internal/config"
"github.com/planetscale/cli/internal/mock"
"github.com/planetscale/cli/internal/printer"
ps "github.com/planetscale/planetscale-go/planetscale"

qt "github.com/frankban/quicktest"
)

func TestPassword_DeleteCmd(t *testing.T) {
c := qt.New(t)

var buf bytes.Buffer
format := printer.JSON
p := printer.NewPrinter(&format)
p.SetResourceOutput(&buf)

org := "planetscale"
db := "planetscale"
branch := "development"
password := "mypassword"

svc := &mock.PasswordsService{
DeleteFn: func(ctx context.Context, req *ps.DeletePasswordRequest) error {
c.Assert(req.Organization, qt.Equals, org)
c.Assert(req.Database, qt.Equals, db)
c.Assert(req.Branch, qt.Equals, branch)
c.Assert(req.Password, qt.Equals, password)

return nil
},
}

ch := &cmdutil.Helper{
Printer: p,
Config: &config.Config{
Organization: org,
},
Client: func() (*ps.Client, error) {
return &ps.Client{
Passwords: svc,
}, nil

},
}

cmd := DeleteCmd(ch)
cmd.SetArgs([]string{db, branch, password, "--force"})
err := cmd.Execute()

c.Assert(err, qt.IsNil)
c.Assert(svc.DeleteFnInvoked, qt.IsTrue)

res := map[string]string{
"result": "password deleted",
"password": password,
"branch": branch,
}
c.Assert(buf.String(), qt.JSONEquals, res)
}
74 changes: 74 additions & 0 deletions internal/cmd/passwords/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package passwords

import (
"fmt"

"github.com/planetscale/cli/internal/cmdutil"
"github.com/planetscale/cli/internal/printer"
"github.com/planetscale/planetscale-go/planetscale"

"github.com/pkg/browser"
"github.com/spf13/cobra"
)

// ListCmd encapsulates the command for listing passwords for a branch.
func ListCmd(ch *cmdutil.Helper) *cobra.Command {
cmd := &cobra.Command{
Use: "list <database> <branch>",
Short: "List all passwords of a branch",
Args: cmdutil.RequiredArgs("database", "branch"),
Aliases: []string{"ls"},
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
database := args[0]
branch := args[1]

web, err := cmd.Flags().GetBool("web")
if err != nil {
return err
}

if web {
fmt.Println("🌐 Redirecting you to your passwords in your web browser.")
err := browser.OpenURL(fmt.Sprintf("%s/%s/%s/%s/passwords", cmdutil.ApplicationURL, ch.Config.Organization, database, branch))
if err != nil {
return err
}
return nil
}

client, err := ch.Client()
if err != nil {
return err
}

end := ch.Printer.PrintProgress(fmt.Sprintf("Fetching passwords for %s", printer.BoldBlue(branch)))
defer end()
passwords, err := client.Passwords.List(ctx, &planetscale.ListDatabaseBranchPasswordRequest{
Organization: ch.Config.Organization,
Database: database,
Branch: branch,
})
if err != nil {
switch cmdutil.ErrCode(err) {
case planetscale.ErrNotFound:
return fmt.Errorf("branch %s does not exist in database %s (organization: %s)",
printer.BoldBlue(branch), printer.BoldBlue(database), printer.BoldBlue(ch.Config.Organization))
default:
return cmdutil.HandleError(err)
}
}
end()

if len(passwords) == 0 && ch.Printer.Format() == printer.Human {
ch.Printer.Printf("No passwords exist in %s.\n", printer.BoldBlue(branch))
return nil
}

return ch.Printer.PrintResource(toPasswords(passwords))
},
}

cmd.Flags().BoolP("web", "w", false, "List passwords in your web browser.")
return cmd
}

0 comments on commit 364d561

Please sign in to comment.