Skip to content

Commit d1d0cae

Browse files
committed
cli: convert to using babycli for argument processing
1 parent 435c581 commit d1d0cae

File tree

18 files changed

+401
-708
lines changed

18 files changed

+401
-708
lines changed

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
NAME = envy
22

3-
default: build
4-
5-
.PHONY: build
3+
.PHONY: compile
64
build: clean
7-
@echo "--> Build ..."
5+
@echo "--> Compile ..."
86
CGO_ENABLED=0 go build -o output/$(NAME)
97

108
.PHONY: clean
@@ -39,3 +37,5 @@ release:
3937
@echo "--> RELEASE ..."
4038
envy exec gh-release goreleaser release --clean
4139
$(MAKE) clean
40+
41+
default: compile

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ require (
2424
github.com/google/go-cmp v0.6.0 // indirect
2525
github.com/pmezard/go-difflib v1.0.0 // indirect
2626
golang.org/x/sys v0.24.0 // indirect
27+
noxide.lol/go/babycli v0.1.4 // indirect
28+
noxide.lol/go/stacks v1.0.0 // indirect
2729
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,7 @@ golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
4242
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
4343
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
4444
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
45+
noxide.lol/go/babycli v0.1.4 h1:gdgpLn1ydzy7VK3ZQAIbHnnPk1yHfyd6OYLo9UsWulY=
46+
noxide.lol/go/babycli v0.1.4/go.mod h1:WatWwUui1Zf1KtSz4EzLtOkGkljrhOFfXfLHVoBRDj4=
47+
noxide.lol/go/stacks v1.0.0 h1:g4MPkizQF/6B3u1ejGxWDIjL1zM/MSHMzoS1DEZTOWY=
48+
noxide.lol/go/stacks v1.0.0/go.mod h1:rwC8UA5l8uwwfRNAKuBSI+7hpP8ilJVK3gozAn7vjzM=

internal/commands/common.go

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,55 @@
44
package commands
55

66
import (
7-
"flag"
87
"regexp"
9-
"strconv"
108
"strings"
119

1210
"github.com/hashicorp/go-set/v2"
1311
"github.com/pkg/errors"
1412
"github.com/shoenig/envy/internal/keyring"
1513
"github.com/shoenig/envy/internal/safe"
14+
"github.com/shoenig/envy/internal/setup"
1615
"github.com/shoenig/go-conceal"
1716
"github.com/shoenig/regexplus"
17+
"noxide.lol/go/babycli"
1818
)
1919

2020
var (
2121
argRe = regexp.MustCompile(`^(?P<key>\w+)=(?P<secret>.+)$`)
2222
namespaceRe = regexp.MustCompile(`^[-:/\w]+$`)
2323
)
2424

25+
const (
26+
description = `
27+
The envy is a command line tool for managing profiles of
28+
environment variables. Values are stored securely using
29+
encryption with keys protected by your desktop keychain.`
30+
)
31+
32+
func Invoke(args []string, tool *setup.Tool) babycli.Code {
33+
return invoke(args, tool)
34+
}
35+
36+
func invoke(args []string, tool *setup.Tool) babycli.Code {
37+
r := babycli.New(&babycli.Configuration{
38+
Arguments: args,
39+
Version: "v0",
40+
Top: &babycli.Component{
41+
Name: "envy",
42+
Help: "wrangle environment varibles",
43+
Description: description,
44+
Components: babycli.Components{
45+
newListCmd(tool),
46+
newSetCmd(tool),
47+
newPurgeCmd(tool),
48+
newShowCmd(tool),
49+
newExecCmd(tool),
50+
},
51+
},
52+
})
53+
return r.Run()
54+
}
55+
2556
func checkName(namespace string) error {
2657
if !namespaceRe.MatchString(namespace) {
2758
return errors.New("namespace uses non-word characters")
@@ -105,11 +136,3 @@ func (e *extractor) encryptEnvVar(kv *conceal.Text) (string, safe.Encrypted, err
105136
func (e *extractor) encrypt(s *conceal.Text) safe.Encrypted {
106137
return e.ring.Encrypt(s)
107138
}
108-
109-
func fsBool(fs *flag.FlagSet, name string) bool {
110-
b, err := strconv.ParseBool(fs.Lookup(name).Value.String())
111-
if err != nil {
112-
return false
113-
}
114-
return b
115-
}

internal/commands/common_test.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,8 @@ package commands
55

66
import (
77
"bytes"
8-
"flag"
9-
"os"
10-
"testing"
118

129
"github.com/shoenig/envy/internal/output"
13-
"github.com/shoenig/test/must"
1410
"github.com/zalando/go-keyring"
1511
)
1612

@@ -19,27 +15,7 @@ func init() {
1915
keyring.MockInit()
2016
}
2117

22-
func newDBFile(t *testing.T) string {
23-
f, err := os.CreateTemp("", "tool-")
24-
must.NoError(t, err)
25-
err = f.Close()
26-
must.NoError(t, err)
27-
return f.Name()
28-
}
29-
30-
func cleanupFile(t *testing.T, name string) {
31-
err := os.Remove(name)
32-
must.NoError(t, err)
33-
}
34-
3518
func newWriter() (*bytes.Buffer, *bytes.Buffer, output.Writer) {
3619
var a, b bytes.Buffer
3720
return &a, &b, output.New(&a, &b)
3821
}
39-
40-
func setupFlagSet(t *testing.T, arguments []string) (*flag.FlagSet, interface{}) {
41-
fs := flag.NewFlagSet("test", flag.PanicOnError)
42-
err := fs.Parse(arguments)
43-
must.NoError(t, err)
44-
return fs, arguments
45-
}

internal/commands/exec.go

Lines changed: 66 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -5,87 +5,88 @@ package commands
55

66
import (
77
"context"
8-
"flag"
98
"fmt"
10-
"io"
119
"os"
1210
"os/exec"
1311
"strings"
1412

15-
"github.com/google/subcommands"
16-
"github.com/shoenig/envy/internal/keyring"
17-
"github.com/shoenig/envy/internal/output"
1813
"github.com/shoenig/envy/internal/safe"
1914
"github.com/shoenig/envy/internal/setup"
15+
"noxide.lol/go/babycli"
2016
)
2117

22-
const (
23-
execCmdName = "exec"
24-
execCmdSynopsis = "Run command with environment variables from namespace."
25-
execCmdUsage = "exec [namespace] [command] <args, ...>"
26-
27-
flagInsulate = "insulate"
28-
)
29-
30-
func NewExecCmd(t *setup.Tool) subcommands.Command {
31-
return &execCmd{
32-
writer: t.Writer,
33-
ring: t.Ring,
34-
box: t.Box,
35-
execInputStd: os.Stdin,
36-
execOutputStd: os.Stdout,
37-
execOutputErr: os.Stderr,
18+
func newExecCmd(tool *setup.Tool) *babycli.Component {
19+
return &babycli.Component{
20+
Name: "exec",
21+
Help: "run a command using environment variables from profile",
22+
Flags: babycli.Flags{
23+
{
24+
Type: babycli.BooleanFlag,
25+
Long: "insulate",
26+
Short: "i",
27+
Help: "disable child process from inheriting parent environment variables",
28+
Default: &babycli.Default{
29+
Value: false,
30+
Show: false,
31+
},
32+
},
33+
},
34+
Function: func(c *babycli.Component) babycli.Code {
35+
if c.Nargs() < 2 {
36+
tool.Writer.Errorf("must specify profile and command argument(s)")
37+
return babycli.Failure
38+
}
39+
40+
args := c.Arguments()
41+
p, err := tool.Box.Get(args[0])
42+
if err != nil {
43+
tool.Writer.Errorf("unable to read profile: %v", err)
44+
return babycli.Failure
45+
}
46+
47+
insulate := c.GetBool("insulate")
48+
argVars, command, args := splitArgs(args[1:])
49+
cmd := newCmd(tool, p, insulate, argVars, command, args)
50+
51+
if err := cmd.Run(); err != nil {
52+
tool.Writer.Errorf("failed to exec: %v", err)
53+
return babycli.Failure
54+
}
55+
56+
return babycli.Success
57+
},
3858
}
3959
}
40-
41-
type execCmd struct {
42-
writer output.Writer
43-
ring keyring.Ring
44-
box safe.Box
45-
execInputStd io.Reader
46-
execOutputStd io.Writer
47-
execOutputErr io.Writer
48-
}
49-
50-
func (wc execCmd) Name() string {
51-
return execCmdName
52-
}
53-
54-
func (wc execCmd) Synopsis() string {
55-
return execCmdSynopsis
56-
}
57-
58-
func (wc execCmd) Usage() string {
59-
return execCmdUsage
60-
}
61-
62-
func (wc execCmd) SetFlags(fs *flag.FlagSet) {
63-
// no flags when running command through exec
64-
_ = fs.Bool(flagInsulate, false, "insulate will run command without passing through environment")
65-
}
66-
67-
func (wc execCmd) Execute(_ context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
68-
insulate := fsBool(fs, flagInsulate)
69-
70-
if len(fs.Args()) < 2 {
71-
wc.writer.Errorf("expected namespace and command argument(s)")
72-
return subcommands.ExitUsageError
60+
func env(tool *setup.Tool, ns *safe.Namespace, environment []string) []string {
61+
for key, value := range ns.Content {
62+
secret := tool.Ring.Decrypt(value).Unveil()
63+
environment = append(environment, fmt.Sprintf(
64+
"%s=%s", key, secret,
65+
))
7366
}
67+
return environment
68+
}
7469

75-
ns, err := wc.box.Get(fs.Arg(0))
76-
if err != nil {
77-
wc.writer.Errorf("could not retrieve namespace: %v", err)
78-
return subcommands.ExitUsageError
70+
func envContext(insulate bool) []string {
71+
if insulate {
72+
return nil
7973
}
74+
return os.Environ()
75+
}
8076

81-
argVars, command, args := splitArgs(fs.Args()[1:])
82-
cmd := wc.newCmd(ns, insulate, argVars, command, args)
83-
if err := cmd.Run(); err != nil {
84-
wc.writer.Errorf("failed to exec: %v", err)
85-
return subcommands.ExitFailure
86-
}
77+
func newCmd(tool *setup.Tool, ns *safe.Namespace, insulate bool, argVars []string, command string, args []string) *exec.Cmd {
78+
ctx := context.Background()
79+
cmd := exec.CommandContext(ctx, command, args...)
8780

88-
return subcommands.ExitSuccess
81+
// Environment variables are injected in the following order:
82+
// 1. OS variables if insulate is false
83+
// 2. envy namespace vars
84+
// 3. Variables in input args
85+
cmd.Env = append(env(tool, ns, envContext(insulate)), argVars...)
86+
cmd.Stdout = os.Stdout
87+
cmd.Stderr = os.Stderr
88+
cmd.Stdin = os.Stdin
89+
return cmd
8990
}
9091

9192
// splitArgs will split the list of flag.Args() into:
@@ -123,35 +124,3 @@ func splitArgs(flagArgs []string) (argVars []string, command string, commandArgs
123124

124125
return argVars, command, commandArgs
125126
}
126-
127-
func (wc execCmd) newCmd(ns *safe.Namespace, insulate bool, argVars []string, command string, args []string) *exec.Cmd {
128-
ctx := context.Background()
129-
cmd := exec.CommandContext(ctx, command, args...)
130-
131-
// Environment variables are injected in the following order:
132-
// 1. OS variables if insulate is false
133-
// 2. envy namespace vars
134-
// 3. Variables in input args
135-
cmd.Env = append(wc.env(ns, envContext(insulate)), argVars...)
136-
cmd.Stdout = wc.execOutputStd
137-
cmd.Stderr = wc.execOutputErr
138-
cmd.Stdin = wc.execInputStd
139-
return cmd
140-
}
141-
142-
func envContext(insulate bool) []string {
143-
if insulate {
144-
return nil
145-
}
146-
return os.Environ()
147-
}
148-
149-
func (wc execCmd) env(ns *safe.Namespace, environment []string) []string {
150-
for key, value := range ns.Content {
151-
secret := wc.ring.Decrypt(value).Unveil()
152-
environment = append(environment, fmt.Sprintf(
153-
"%s=%s", key, secret,
154-
))
155-
}
156-
return environment
157-
}

0 commit comments

Comments
 (0)