Skip to content

Commit 0326465

Browse files
authored
Merge pull request #237 from roots/create-known-hosts-secret-during-key-generate
Set known hosts secret during key generation
2 parents a349702 + 588fa82 commit 0326465

File tree

2 files changed

+109
-19
lines changed

2 files changed

+109
-19
lines changed

cmd/key_generate.go

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"bytes"
5+
"errors"
56
"flag"
67
"fmt"
78
"io"
@@ -19,8 +20,11 @@ import (
1920
"github.com/roots/trellis-cli/trellis"
2021
)
2122

22-
const secretName = "TRELLIS_DEPLOY_SSH_PRIVATE_KEY"
23-
const deployKeyName = "Trellis deploy"
23+
const (
24+
sshKeySecret = "TRELLIS_DEPLOY_SSH_PRIVATE_KEY"
25+
sshKnownHostsSecret = "TRELLIS_DEPLOY_SSH_KNOWN_HOSTS"
26+
deployKeyName = "Trellis deploy"
27+
)
2428

2529
func NewKeyGenerateCommand(ui cli.Ui, trellis *trellis.Trellis) *KeyGenerateCommand {
2630
c := &KeyGenerateCommand{UI: ui, Trellis: trellis}
@@ -133,7 +137,7 @@ func (c *KeyGenerateCommand) Run(args []string) int {
133137
_, err = exec.LookPath("gh")
134138
if err != nil {
135139
c.UI.Error("Error: GitHub CLI not found")
136-
c.UI.Error("gh command must be available to create a GitHub secret")
140+
c.UI.Error("gh command must be available to interact with GitHub")
137141
c.UI.Error("See https://cli.github.com")
138142
return 1
139143
}
@@ -145,19 +149,14 @@ func (c *KeyGenerateCommand) Run(args []string) int {
145149
return 1
146150
}
147151

148-
ghArgs := []string{"secret", "set", secretName, "--body", string(privateKeyContent)}
149-
150-
ghSecret := exec.Command("gh", ghArgs...)
151-
ghSecret.Stdout = io.Discard
152-
ghSecret.Stderr = os.Stderr
153-
err = ghSecret.Run()
152+
err = githubCLI("secret", "set", sshKeySecret, "--body", string(privateKeyContent))
154153
if err != nil {
155-
c.UI.Error("Error: could not create GitHub secret")
154+
c.UI.Error("Error: could not set GitHub secret")
156155
c.UI.Error(err.Error())
157156
return 1
158157
}
159158

160-
c.UI.Info(fmt.Sprintf("%s GitHub secret set [%s]", color.GreenString("[✓]"), secretName))
159+
c.UI.Info(fmt.Sprintf("%s GitHub private key secret set [%s]", color.GreenString("[✓]"), sshKeySecret))
161160

162161
publicKeyContent, err := ioutil.ReadFile(trellisPublicKeyPath)
163162
if err != nil {
@@ -167,24 +166,42 @@ func (c *KeyGenerateCommand) Run(args []string) int {
167166
}
168167

169168
publicKeyContent = bytes.TrimSuffix(publicKeyContent, []byte("\n"))
170-
171169
title := fmt.Sprintf("title=%s", deployKeyName)
172170
key := fmt.Sprintf("key=%s", string(publicKeyContent))
173-
ghApiArgs := []string{"api", "repos/{owner}/{repo}/keys", "-f", title, "-f", key, "-f", "read_only=true"}
174171

175-
ghApi := exec.Command("gh", ghApiArgs...)
176-
ghApi.Stdout = io.Discard
177-
ghApi.Stderr = os.Stderr
178-
err = ghApi.Run()
172+
err = githubCLI("api", "repos/{owner}/{repo}/keys", "-f", title, "-f", key, "-f", "read_only=true")
179173
if err != nil {
180174
c.UI.Error("Error: could not create GitHub deploy key")
181175
c.UI.Error(err.Error())
182176
return 1
183177
}
184-
c.UI.Info(fmt.Sprintf("%s GitHub deploy key added [%s]\n", color.GreenString("[✓]"), deployKeyName))
178+
c.UI.Info(fmt.Sprintf("%s GitHub deploy key added [%s]", color.GreenString("[✓]"), deployKeyName))
179+
180+
hosts, err := getAnsibleHosts()
181+
if err != nil {
182+
c.UI.Error("Error: could not get hosts to set known hosts secret")
183+
c.UI.Error(err.Error())
184+
return 1
185+
}
186+
187+
keyscanOutput, err := exec.Command("ssh-keyscan", "-t", "ed25519", "-H", strings.Join(hosts, " ")).Output()
188+
if err != nil {
189+
c.UI.Error("Error: could not set SSH known hosts. ssh-keyscan command failed.")
190+
c.UI.Error(err.Error())
191+
return 1
192+
}
193+
194+
err = githubCLI("secret", "set", sshKnownHostsSecret, "--body", string(keyscanOutput))
195+
if err != nil {
196+
c.UI.Error("Error: could not set GitHub secret")
197+
c.UI.Error(err.Error())
198+
return 1
199+
}
200+
201+
c.UI.Info(fmt.Sprintf("%s GitHub known hosts secret set [%s]", color.GreenString("[✓]"), sshKnownHostsSecret))
185202

186203
if c.provisionEnv == "" {
187-
c.UI.Info("The public key will not be usable until it's added to your server.")
204+
c.UI.Info("\nThe public key will not be usable until it's added to your server.")
188205
prompt := promptui.Prompt{
189206
Label: "Provision now and apply the new public key",
190207
IsConfirm: true,
@@ -276,3 +293,54 @@ func (c *KeyGenerateCommand) AutocompleteFlags() complete.Flags {
276293
"--provision": complete.PredictSet(environmentNames...),
277294
}
278295
}
296+
297+
func githubCLI(args ...string) error {
298+
ghCmd := exec.Command("gh", args...)
299+
ghCmd.Stdout = io.Discard
300+
ghCmd.Stderr = os.Stderr
301+
302+
return ghCmd.Run()
303+
}
304+
305+
func getAnsibleHosts() (hosts []string, err error) {
306+
hostsOutput, err := exec.Command("ansible", "all", "--list-hosts").Output()
307+
308+
if err != nil {
309+
return nil, err
310+
}
311+
312+
hosts = parseAnsibleHosts(string(hostsOutput))
313+
314+
if len(hosts) == 0 {
315+
return nil, errors.New("No hosts found by Ansible. This is either a bug in trellis-cli or your host files are invalid.")
316+
}
317+
318+
return hosts, nil
319+
}
320+
321+
/*
322+
323+
Parses the output of `ansible all --list-hosts` into a slice of host strings
324+
325+
Example input:
326+
hosts (3):
327+
192.168.56.5
328+
192.168.56.10
329+
your_server_hostname
330+
*/
331+
332+
func parseAnsibleHosts(output string) (hosts []string) {
333+
lines := strings.Split(string(output), "\n")
334+
335+
for _, host := range lines {
336+
host = strings.TrimSpace(host)
337+
338+
if strings.HasPrefix(host, "hosts (") || host == "" {
339+
continue
340+
}
341+
342+
hosts = append(hosts, host)
343+
}
344+
345+
return hosts
346+
}

cmd/key_generate_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io/ioutil"
66
"os"
77
"path/filepath"
8+
"reflect"
89
"strings"
910
"testing"
1011

@@ -147,3 +148,24 @@ func TestKeyGenerateExistingPublicKey(t *testing.T) {
147148
t.Errorf("expected output %q to contain %q", combined, expected)
148149
}
149150
}
151+
152+
func TestParseAnsibleHosts(t *testing.T) {
153+
output := `
154+
hosts (3):
155+
192.168.56.5
156+
192.168.56.10
157+
your_server_hostname
158+
`
159+
160+
hosts := parseAnsibleHosts(output)
161+
162+
expectedHosts := []string{
163+
"192.168.56.5",
164+
"192.168.56.10",
165+
"your_server_hostname",
166+
}
167+
168+
if !reflect.DeepEqual(hosts, expectedHosts) {
169+
t.Errorf("expected hosts %q to equal %q", hosts, expectedHosts)
170+
}
171+
}

0 commit comments

Comments
 (0)