Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Environment subcommands #89

Closed
wants to merge 14 commits into from
Prev Previous commit
Next Next commit
Migrate secrets to urfave
  • Loading branch information
cmoog committed Jul 31, 2020
commit 659aabcfd3df3ecaca7149d192a2c35a652c7919
2 changes: 1 addition & 1 deletion cmd/coder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ func (r *rootCmd) Subcommands() []cdrcli.Command {
&envsCmd{},
&syncCmd{},
&urlsCmd{},
&secretsCmd{},
}
}

Expand All @@ -67,6 +66,7 @@ func main() {
makeShellCmd(),
makeUsersCmd(),
makeConfigSSHCmd(),
makeSecretsCmd(),
}
err = app.Run(os.Args)
if err != nil {
Expand Down
256 changes: 119 additions & 137 deletions cmd/coder/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,132 @@ import (

"cdr.dev/coder-cli/internal/entclient"
"cdr.dev/coder-cli/internal/x/xtabwriter"
"cdr.dev/coder-cli/internal/x/xvalidate"
"github.com/manifoldco/promptui"
"github.com/spf13/pflag"
"github.com/urfave/cli"
"golang.org/x/xerrors"

"go.coder.com/flog"

"go.coder.com/cli"
)

type secretsCmd struct {
}

func (cmd secretsCmd) Spec() cli.CommandSpec {
return cli.CommandSpec{
Name: "secrets",
Usage: "[subcommand]",
Desc: "interact with secrets",
}
}

func (cmd secretsCmd) Run(fl *pflag.FlagSet) {
exitUsage(fl)
}

func (cmd secretsCmd) RegisterFlags(fl *pflag.FlagSet) {}

func (cmd secretsCmd) Subcommands() []cli.Command {
return []cli.Command{
&listSecretsCmd{},
&viewSecretsCmd{},
&createSecretCmd{},
&deleteSecretsCmd{},
func makeSecretsCmd() cli.Command {
return cli.Command{
Name: "secrets",
Usage: "",
Description: "interact with secrets",
Subcommands: []cli.Command{
{
Name: "ls",
Usage: "",
Description: "",
Action: listSecrets,
},
makeCreateSecret(),
{
Name: "rm",
Usage: "",
Description: "",
Action: removeSecret,
},
{
Name: "view",
Usage: "",
Description: "",
Action: viewSecret,
},
},
Flags: nil,
}
}

type listSecretsCmd struct{}
func makeCreateSecret() cli.Command {
var (
fromFile string
fromLiteral string
fromPrompt bool
description string
)

func (cmd *listSecretsCmd) Spec() cli.CommandSpec {
return cli.CommandSpec{
Name: "ls",
Desc: "list all secrets",
return cli.Command{
Name: "create",
Usage: "",
Description: "",
Before: func(c *cli.Context) error {
if c.Args().First() == "" {
return xerrors.Errorf("[name] is a required field argument")
}
if fromPrompt && (fromLiteral != "" || fromFile != "") {
return xerrors.Errorf("--from-prompt cannot be set along with --from-file or --from-literal")
}
if fromLiteral != "" && fromFile != "" {
return xerrors.Errorf("--from-literal and --from-file cannot both be set")
}
if !fromPrompt && fromFile == "" && fromLiteral == "" {
return xerrors.Errorf("one of [--from-literal, --from-file, --from-prompt] is required")
}
return nil
},
Action: func(c *cli.Context) {
var (
client = requireAuth()
name = c.Args().First()
value string
err error
)
if fromLiteral != "" {
value = fromLiteral
} else if fromFile != "" {
contents, err := ioutil.ReadFile(fromFile)
requireSuccess(err, "failed to read file: %v", err)
value = string(contents)
} else {
prompt := promptui.Prompt{
Label: "value",
Mask: '*',
Validate: func(s string) error {
if len(s) < 1 {
return xerrors.Errorf("a length > 0 is required")
}
return nil
},
}
value, err = prompt.Run()
requireSuccess(err, "failed to prompt for value: %v", err)
}

err = client.InsertSecret(entclient.InsertSecretReq{
Name: name,
Value: value,
Description: description,
})
requireSuccess(err, "failed to insert secret: %v", err)
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "from-file",
Usage: "a file from which to read the value of the secret",
TakesFile: true,
Destination: &fromFile,
},
cli.StringFlag{
Name: "from-literal",
Usage: "the value of the secret",
Destination: &fromLiteral,
},
cli.BoolFlag{
Name: "from-prompt",
Usage: "enter the secret value through a terminal prompt",
Destination: &fromPrompt,
},
cli.StringFlag{
Name: "description",
Usage: "a description of the secret",
Destination: &description,
},
},
}
}

func (cmd *listSecretsCmd) Run(fl *pflag.FlagSet) {
func listSecrets(_ *cli.Context) {
client := requireAuth()

secrets, err := client.Secrets()
Expand All @@ -76,25 +156,13 @@ func (cmd *listSecretsCmd) Run(fl *pflag.FlagSet) {
requireSuccess(err, "failed to flush writer: %v", err)
}

func (cmd *listSecretsCmd) RegisterFlags(fl *pflag.FlagSet) {}

type viewSecretsCmd struct{}

func (cmd viewSecretsCmd) Spec() cli.CommandSpec {
return cli.CommandSpec{
Name: "view",
Usage: "[secret_name]",
Desc: "view a secret",
}
}

func (cmd viewSecretsCmd) Run(fl *pflag.FlagSet) {
func viewSecret(c *cli.Context) {
var (
client = requireAuth()
name = fl.Arg(0)
name = c.Args().First()
)
if name == "" {
exitUsage(fl)
flog.Fatal("[name] is a required argument")
}

secret, err := client.SecretByName(name)
Expand All @@ -104,99 +172,13 @@ func (cmd viewSecretsCmd) Run(fl *pflag.FlagSet) {
requireSuccess(err, "failed to write: %v", err)
}

type createSecretCmd struct {
description string
fromFile string
fromLiteral string
fromPrompt bool
}

func (cmd *createSecretCmd) Spec() cli.CommandSpec {
return cli.CommandSpec{
Name: "create",
Usage: `[secret_name] [...flags]`,
Desc: "create a new secret",
}
}

func (cmd *createSecretCmd) Validate(fl *pflag.FlagSet) (e []error) {
if cmd.fromPrompt && (cmd.fromLiteral != "" || cmd.fromFile != "") {
e = append(e, xerrors.Errorf("--from-prompt cannot be set along with --from-file or --from-literal"))
}
if cmd.fromLiteral != "" && cmd.fromFile != "" {
e = append(e, xerrors.Errorf("--from-literal and --from-file cannot both be set"))
}
if !cmd.fromPrompt && cmd.fromFile == "" && cmd.fromLiteral == "" {
e = append(e, xerrors.Errorf("one of [--from-literal, --from-file, --from-prompt] is required"))
}
return e
}

func (cmd *createSecretCmd) Run(fl *pflag.FlagSet) {
var (
client = requireAuth()
name = fl.Arg(0)
value string
err error
)
if name == "" {
exitUsage(fl)
}
xvalidate.Validate(fl, cmd)

if cmd.fromLiteral != "" {
value = cmd.fromLiteral
} else if cmd.fromFile != "" {
contents, err := ioutil.ReadFile(cmd.fromFile)
requireSuccess(err, "failed to read file: %v", err)
value = string(contents)
} else {
prompt := promptui.Prompt{
Label: "value",
Mask: '*',
Validate: func(s string) error {
if len(s) < 1 {
return xerrors.Errorf("a length > 0 is required")
}
return nil
},
}
value, err = prompt.Run()
requireSuccess(err, "failed to prompt for value: %v", err)
}

err = client.InsertSecret(entclient.InsertSecretReq{
Name: name,
Value: value,
Description: cmd.description,
})
requireSuccess(err, "failed to insert secret: %v", err)
}

func (cmd *createSecretCmd) RegisterFlags(fl *pflag.FlagSet) {
fl.StringVar(&cmd.fromFile, "from-file", "", "specify a file from which to read the value of the secret")
fl.StringVar(&cmd.fromLiteral, "from-literal", "", "specify the value of the secret")
fl.BoolVar(&cmd.fromPrompt, "from-prompt", false, "specify the value of the secret through a prompt")
fl.StringVar(&cmd.description, "description", "", "specify a description of the secret")
}

type deleteSecretsCmd struct{}

func (cmd *deleteSecretsCmd) Spec() cli.CommandSpec {
return cli.CommandSpec{
Name: "rm",
Usage: "[secret_name]",
Desc: "remove a secret",
}
}

func (cmd *deleteSecretsCmd) Run(fl *pflag.FlagSet) {
func removeSecret(c *cli.Context) {
var (
client = requireAuth()
name = fl.Arg(0)
name = c.Args().First()
)
if name == "" {
exitUsage(fl)
flog.Fatal("[name] is a required argument")
}

err := client.DeleteSecretByName(name)
Expand Down