Skip to content

Commit 55d4d60

Browse files
authored
Merge pull request #4 from jacobbednarz/no-args-means-new-shell
exec: spawn new shell on no arguments
2 parents 568f949 + 66c6a90 commit 55d4d60

File tree

4 files changed

+87
-35
lines changed

4 files changed

+87
-35
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ $ env | grep -i cloudflare
2828
# => no results
2929

3030
$ cf-vault exec work -- env | grep -i cloudflare
31+
CLOUDFLARE_VAULT_SESSION=work
3132
3233
CLOUDFLARE_API_KEY=s3cr3t
3334
```
35+
36+
If you don't provide a command, you will be dropped into a new shell with the
37+
credentials populated.
38+
39+
```shell
40+
$ cf-vault exec work
41+
$ env | grep -i cloudflare
42+
CLOUDFLARE_VAULT_SESSION=work
43+
44+
CLOUDFLARE_API_KEY=s3cr3t
45+
46+
$ exit
47+
$ env | grep -i cloudflare
48+
# => no results
49+
```

cmd/add.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ var addCmd = &cobra.Command{
1919
Use: "add [profile]",
2020
Short: "Add a new profile to your configuration and keychain",
2121
Long: "",
22+
Example: `
23+
Add a new profile (you will be prompted for credentials)
24+
25+
$ cf-vault add example-profile
26+
`,
2227
Args: func(cmd *cobra.Command, args []string) error {
2328
if len(args) < 1 {
2429
return errors.New("requires a profile argument")
@@ -65,11 +70,7 @@ var addCmd = &cobra.Command{
6570
cfg.Section(fmt.Sprintf("profile %s", profileName)).NewKey("auth_type", authType)
6671
cfg.SaveTo(home + defaultFullConfigPath)
6772

68-
ring, _ := keyring.Open(keyring.Config{
69-
FileDir: "~/.cf-vault/keys/",
70-
ServiceName: projectName,
71-
KeychainName: projectName,
72-
})
73+
ring, _ := keyring.Open(keyringDefaults)
7374

7475
_ = ring.Set(keyring.Item{
7576
Key: fmt.Sprintf("%s-%s", profileName, authType),

cmd/exec.go

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"strings"
8+
"syscall"
89

910
"os/exec"
1011

@@ -19,6 +20,22 @@ var execCmd = &cobra.Command{
1920
Use: "exec [profile]",
2021
Short: "Execute a command with Cloudflare credentials populated",
2122
Long: "",
23+
Example: `
24+
Execute a single command with credentials populated
25+
26+
$ cf-vault exec example-profile -- env | grep -i cloudflare
27+
CLOUDFLARE_VAULT_SESSION=example-profile
28+
29+
CLOUDFLARE_API_KEY=s3cr3t
30+
31+
Spawn a new shell with credentials populated
32+
33+
$ cf-vault exec example-profile --
34+
$ env | grep -i cloudflare
35+
CLOUDFLARE_VAULT_SESSION=example-profile
36+
37+
CLOUDFLARE_API_KEY=s3cr3t
38+
`,
2239
Args: func(cmd *cobra.Command, args []string) error {
2340
if len(args) < 1 {
2441
return errors.New("requires a profile argument")
@@ -32,6 +49,11 @@ var execCmd = &cobra.Command{
3249
},
3350
Run: func(cmd *cobra.Command, args []string) {
3451
profileName := args[0]
52+
// Remove the extra executable name at the beginning of the slice.
53+
copy(args[0:], args[0+1:])
54+
args[len(args)-1] = ""
55+
args = args[:len(args)-1]
56+
3557
log.Debug("using profile: ", profileName)
3658

3759
home, err := homedir.Dir()
@@ -45,42 +67,40 @@ var execCmd = &cobra.Command{
4567
}
4668

4769
if profileSection.Key("email").String() == "" {
48-
log.Fatal(fmt.Sprintf("no profile matching %q found in the configuration file at %s", profileName, defaultFullConfigPath))
70+
log.Fatalf("no profile matching %q found in the configuration file at %s", profileName, defaultFullConfigPath)
4971
}
5072

51-
ring, _ := keyring.Open(keyring.Config{
52-
FileDir: "~/.cf-vault/keys/",
53-
ServiceName: projectName,
54-
KeychainName: projectName,
55-
})
56-
57-
keychain, _ := ring.Get(fmt.Sprintf("%s-%s", profileName, profileSection.Key("auth_type").String()))
58-
59-
command := strings.Split(args[1], " ")
60-
executable := command[0]
61-
argv0, err := exec.LookPath(executable)
62-
if err != nil {
63-
log.Fatalf("couldn't find the executable '%s': %w", executable, err)
73+
// Don't allow nesting of cf-vault sessions, it gets messy.
74+
if os.Getenv("CLOUDFLARE_VAULT_SESSION") != "" {
75+
log.Fatal("cf-vault sessions shouldn't be nested, unset CLOUDFLARE_VAULT_SESSION to continue or open a new shell session")
6476
}
6577

66-
log.Printf("found executable %s", argv0)
67-
68-
// Remove the extra executable name at the beginning of the slice.
69-
copy(args[0:], args[0+1:])
70-
args[len(args)-1] = ""
71-
args = args[:len(args)-1]
78+
ring, _ := keyring.Open(keyringDefaults)
79+
keychain, _ := ring.Get(fmt.Sprintf("%s-%s", profileName, profileSection.Key("auth_type").String()))
7280

73-
runningCommand := exec.Command(executable, args...)
74-
runningCommand.Env = append(os.Environ(),
81+
cloudflareCreds := []string{
82+
fmt.Sprintf("CLOUDFLARE_VAULT_SESSION=%s", profileName),
7583
fmt.Sprintf("CLOUDFLARE_EMAIL=%s", profileSection.Key("email").String()),
7684
fmt.Sprintf("CLOUDFLARE_%s=%s", strings.ToUpper(profileSection.Key("auth_type").String()), string(keychain.Data)),
77-
)
78-
stdoutStderr, err := runningCommand.CombinedOutput()
85+
}
7986

87+
// Should a command not be provided, drop into a fresh shell with the
88+
// credentials populated alongside the existing env.
89+
if len(args) == 0 {
90+
log.Debug("launching new shell with credentials populated")
91+
envVars := append(syscall.Environ(), cloudflareCreds...)
92+
syscall.Exec(os.Getenv("SHELL"), []string{os.Getenv("SHELL")}, envVars)
93+
}
94+
95+
executable := args[0]
96+
pathtoExec, err := exec.LookPath(executable)
8097
if err != nil {
81-
log.Fatal(err)
98+
log.Fatalf("couldn't find the executable '%s': %w", pathtoExec, err)
8299
}
83100

84-
fmt.Printf("%s\n", stdoutStderr)
101+
log.Debugf("found executable %s", pathtoExec)
102+
log.Debugf("executing command: %s", strings.Join(args, " "))
103+
104+
syscall.Exec(pathtoExec, args, cloudflareCreds)
85105
},
86106
}

cmd/root.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
11
package cmd
22

33
import (
4+
"fmt"
5+
6+
"github.com/99designs/keyring"
47
log "github.com/sirupsen/logrus"
58
"github.com/spf13/cobra"
69
)
710

811
var (
9-
verbose bool
10-
projectName = "cf-vault"
11-
defaultConfigDirectory = "/." + projectName
12-
defaultFullConfigPath = defaultConfigDirectory + "/config"
12+
verbose bool
13+
projectName = "cf-vault"
14+
projectNameWithoutHyphen = "cfvault"
15+
defaultConfigDirectory = "/." + projectName
16+
defaultFullConfigPath = defaultConfigDirectory + "/config"
1317
)
1418

19+
var keyringDefaults = keyring.Config{
20+
FileDir: fmt.Sprintf("~/.%s/keys/", projectName),
21+
ServiceName: projectName,
22+
KeychainName: projectName,
23+
LibSecretCollectionName: projectNameWithoutHyphen,
24+
KWalletAppID: projectName,
25+
KWalletFolder: projectName,
26+
KeychainTrustApplication: true,
27+
WinCredPrefix: projectName,
28+
}
29+
1530
var rootCmd = &cobra.Command{
1631
Use: projectName,
1732
Long: "Manage your Cloudflare credentials, securely",

0 commit comments

Comments
 (0)