Skip to content

Commit 6acde7b

Browse files
authored
Merge pull request #253 from roots/key-generate-better-error-handling
Fix ssh-keyscan error handling in `key generate` command
2 parents 70cda5f + e372240 commit 6acde7b

File tree

3 files changed

+106
-10
lines changed

3 files changed

+106
-10
lines changed

cmd/key_generate.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/mitchellh/cli"
1818
"github.com/mitchellh/go-homedir"
1919
"github.com/posener/complete"
20+
"github.com/roots/trellis-cli/command"
2021
"github.com/roots/trellis-cli/trellis"
2122
)
2223

@@ -82,7 +83,7 @@ func (c *KeyGenerateCommand) Run(args []string) int {
8283
return 1
8384
}
8485

85-
_, err = exec.Command("gh", "auth", "status").Output()
86+
_, err = command.Cmd("gh", []string{"auth", "status"}).Output()
8687
if err != nil {
8788
c.UI.Error("Error: GitHub CLI is not authenticated.")
8889
c.UI.Error("Run `gh auth login` first.")
@@ -128,7 +129,7 @@ func (c *KeyGenerateCommand) Run(args []string) int {
128129
}
129130

130131
keygenArgs := []string{"-t", "ed25519", "-C", deployKeyName, "-f", keyPath, "-P", ""}
131-
sshKeygen := exec.Command("ssh-keygen", keygenArgs...)
132+
sshKeygen := command.Cmd("ssh-keygen", keygenArgs)
132133
sshKeygen.Stdout = io.Discard
133134
sshKeygen.Stderr = os.Stderr
134135
err := sshKeygen.Run()
@@ -197,14 +198,15 @@ func (c *KeyGenerateCommand) Run(args []string) int {
197198
return 1
198199
}
199200

200-
keyscanOutput, err := exec.Command("ssh-keyscan", "-t", "ed25519", "-H", strings.Join(hosts, " ")).Output()
201-
if err != nil {
202-
c.UI.Error("Error: could not set SSH known hosts. ssh-keyscan command failed.")
203-
c.UI.Error(err.Error())
201+
knownHosts := keyscanHosts(hosts)
202+
203+
if len(knownHosts) == 0 {
204+
c.UI.Error("Error: could not set SSH known hosts.")
205+
c.UI.Error(fmt.Sprintf("ssh-keyscan command failed for all hosts: %s", hosts))
204206
return 1
205207
}
206208

207-
err = githubCLI("secret", "set", sshKnownHostsSecret, "--body", string(keyscanOutput))
209+
err = githubCLI("secret", "set", sshKnownHostsSecret, "--body", strings.Join(knownHosts, "\n"))
208210
if err != nil {
209211
c.UI.Error("Error: could not set GitHub secret")
210212
c.UI.Error(err.Error())
@@ -308,15 +310,16 @@ func (c *KeyGenerateCommand) AutocompleteFlags() complete.Flags {
308310
}
309311

310312
func githubCLI(args ...string) error {
311-
ghCmd := exec.Command("gh", args...)
313+
ghCmd := command.Cmd("gh", args)
312314
ghCmd.Stdout = io.Discard
313315
ghCmd.Stderr = os.Stderr
314316

315317
return ghCmd.Run()
316318
}
317319

318320
func getAnsibleHosts() (hosts []string, err error) {
319-
hostsOutput, err := exec.Command("ansible", "all", "--list-hosts").Output()
321+
args := []string{"all", "--list-hosts", "--limit", "!development"}
322+
hostsOutput, err := command.Cmd("ansible", args).Output()
320323

321324
if err != nil {
322325
return nil, err
@@ -352,8 +355,26 @@ func parseAnsibleHosts(output string) (hosts []string) {
352355
continue
353356
}
354357

358+
// remove default placeholder since it will cause an error
359+
// this isn't ideal, but it will do
360+
if host == "your_server_hostname" {
361+
continue
362+
}
363+
355364
hosts = append(hosts, host)
356365
}
357366

358367
return hosts
359368
}
369+
370+
func keyscanHosts(hosts []string) (knownHosts []string) {
371+
for _, host := range hosts {
372+
output, err := command.Cmd("ssh-keyscan", []string{"-t", "ed25519", "-H", "-T", "1", host}).Output()
373+
374+
if err == nil {
375+
knownHosts = append(knownHosts, string(output))
376+
}
377+
}
378+
379+
return knownHosts
380+
}

cmd/key_generate_test.go

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import (
44
"fmt"
55
"io/ioutil"
66
"os"
7+
"os/exec"
78
"path/filepath"
89
"reflect"
910
"strings"
1011
"testing"
1112

1213
"github.com/mitchellh/cli"
14+
"github.com/roots/trellis-cli/command"
1315
"github.com/roots/trellis-cli/trellis"
1416
)
1517

@@ -149,6 +151,40 @@ func TestKeyGenerateExistingPublicKey(t *testing.T) {
149151
}
150152
}
151153

154+
func TestKeyGenerateKeyscan(t *testing.T) {
155+
defer trellis.LoadFixtureProject(t)()
156+
trellis := trellis.NewTrellis()
157+
158+
tmpDir, _ := ioutil.TempDir("", "key_generate_test")
159+
defer os.RemoveAll(tmpDir)
160+
161+
// fake gh binary to satisfy ok.LookPath
162+
ghPath := filepath.Join(tmpDir, "gh")
163+
os.OpenFile(ghPath, os.O_CREATE, 0555)
164+
path := os.Getenv("PATH")
165+
os.Setenv("PATH", fmt.Sprintf("PATH=%s:%s", path, tmpDir))
166+
167+
mockExecCommand := func(command string, args []string) *exec.Cmd {
168+
cs := []string{"-test.run=TestKeyGenerateHelperProcess", "--", command}
169+
cs = append(cs, args...)
170+
cmd := exec.Command(os.Args[0], cs...)
171+
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1", fmt.Sprintf("GO_TEST_HELPER_TMP_PATH=%s", tmpDir)}
172+
return cmd
173+
}
174+
175+
command.Mock(mockExecCommand)
176+
defer command.Restore()
177+
178+
ui := cli.NewMockUi()
179+
keyGenerateCommand := NewKeyGenerateCommand(ui, trellis)
180+
181+
code := keyGenerateCommand.Run([]string{"--path", tmpDir})
182+
183+
if code != 0 {
184+
t.Errorf("expected code %d to be %d", code, 0)
185+
}
186+
}
187+
152188
func TestParseAnsibleHosts(t *testing.T) {
153189
output := `
154190
hosts (3):
@@ -162,10 +198,49 @@ func TestParseAnsibleHosts(t *testing.T) {
162198
expectedHosts := []string{
163199
"192.168.56.5",
164200
"192.168.56.10",
165-
"your_server_hostname",
166201
}
167202

168203
if !reflect.DeepEqual(hosts, expectedHosts) {
169204
t.Errorf("expected hosts %q to equal %q", hosts, expectedHosts)
170205
}
171206
}
207+
208+
func TestKeyGenerateHelperProcess(t *testing.T) {
209+
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
210+
return
211+
}
212+
213+
switch os.Args[3] {
214+
case "ansible":
215+
hosts := `
216+
good_host
217+
bad_host
218+
your_server_hostname
219+
`
220+
fmt.Fprintf(os.Stdout, hosts)
221+
os.Exit(0)
222+
case "ssh-keyscan":
223+
switch os.Args[len(os.Args)-1] {
224+
case "good_host":
225+
// return fake hash for a good host
226+
host := "|1|5XBUprxMy6abCgLQkQ0= ssh-ed25519 AAAAC3NzaC1lZYqEOf"
227+
fmt.Fprintf(os.Stdout, host)
228+
os.Exit(0)
229+
case "bad_host":
230+
// simulate error for a bad host
231+
os.Exit(1)
232+
}
233+
case "ssh-keygen":
234+
tmpDir := os.Getenv("GO_TEST_HELPER_TMP_PATH")
235+
path := filepath.Join(tmpDir, "trellis_example_com_ed25519.pub")
236+
os.OpenFile(path, os.O_CREATE, 0644)
237+
path = filepath.Join(tmpDir, "trellis_example_com_ed25519")
238+
os.OpenFile(path, os.O_CREATE, 0644)
239+
os.Exit(0)
240+
case "gh":
241+
// make all gh commands succeed. No output needed
242+
os.Exit(0)
243+
default:
244+
t.Fatalf("unexpected command %s", os.Args[3])
245+
}
246+
}

trellis/testdata/trellis/public_keys/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)