-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
114 lines (100 loc) · 3.47 KB
/
main.go
File metadata and controls
114 lines (100 loc) · 3.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Package wishlistlite is a pared down version of Charm's Wishlist.
//
// It leverages SSH-related executables already present on the local system to
// simplify everything and relies on just regular expressions to parse an SSH
// configuration.
//
// Its aim was to provide a more hands-on way to learn about Go and isn't to be
// taken seriously.
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"syscall"
"charm.land/bubbles/v2/list"
tea "charm.land/bubbletea/v2"
)
// sshExecutableName is the name of the SSH executable present on the local system.
const sshExecutableName = "ssh"
func newPingOpts(count int) []string {
return []string{"-c", fmt.Sprint(count)}
}
func getSshControlPath() string {
if runtime.GOOS == "darwin" {
return "/tmp"
}
return "/dev/shm"
}
// Paths, 'ping' and SSH control options used by package.
var (
defaultSshConfigPath = expandTilde("~/.ssh/config")
defaultRecentlyUsedPath = expandTilde("~/.ssh/recent.json")
sshControlPath = fmt.Sprintf("%s/control:%s", getSshControlPath(), "%h:%p:%r")
sshControlChildOpts = []string{"-S", sshControlPath}
sshControlParentOpts = []string{"-T", "-o", "ControlMaster=auto", "-o", "ControlPersist=5s", "-o", fmt.Sprintf("ControlPath=%s", sshControlPath)}
defaultPingCount = 4
pingOpts = newPingOpts(defaultPingCount)
)
func main() {
sshConfigPath := flag.String("sshconfigpath", defaultSshConfigPath, "Path to SSH configuration file")
recentlyUsedPath := flag.String("recentlyusedpath", defaultRecentlyUsedPath, "Path to recent SSH connections file")
iniFilePath := flag.String("inifilepath", "", "Path to INI file path (e.g. Ansible inventory file) in lieu of SSH configuration file")
switchFilter := flag.Bool("switchfilter", false, "Whether or not to switch filter value from host to hostname")
pingCount := flag.Int("pingcount", defaultPingCount, "Number of times a host should be pinged")
sshOpts := flag.String("sshoptions", "", "Additional options passed to SSH. Must be contained in quotes")
flag.Parse()
if *pingCount != defaultPingCount {
pingOpts = newPingOpts(*pingCount)
}
sshExecutablePath, err := exec.LookPath(sshExecutableName)
// Using 'panic()' as it's supposedly acceptable during initialization phases:
// https://go.dev/doc/effective_go#panic
if err != nil {
panic(err)
}
var items []list.Item
if *iniFilePath != "" {
items, err = iniHosts(*iniFilePath, *switchFilter)
if err != nil {
fmt.Println("failed to read input file: %w", err)
os.Exit(1)
}
} else {
items, err = sshConfigHosts(*sshConfigPath)
if err != nil {
fmt.Println("failed to read SSH configuration: %w", err)
os.Exit(1)
}
}
sortedItems, err := itemsFromJson(*recentlyUsedPath)
if err != nil {
sortedItems = []list.Item{}
}
sshopts := strings.Split(*sshOpts, " ")
if *sshOpts == "" {
sshopts = []string{}
}
p := tea.NewProgram(newModel(items, sortedItems, *recentlyUsedPath, pingOpts, sshopts))
m, err := p.Run()
if err != nil {
fmt.Println("failed to execute: %w", err)
os.Exit(1)
}
if m, ok := m.(model); ok && m.choice != "" {
fmt.Printf("Connected in %v\n", m.connection.startupTime)
fmt.Println(m.connection.output)
args := append([]string{sshExecutableName, m.choice}, sshControlChildOpts...)
err := syscall.Exec(sshExecutablePath, args, os.Environ())
if err != nil {
fmt.Println("unable to run executable: %w", err)
os.Exit(1)
}
} else if m.err != "" {
fmt.Printf("unable to connect: %s", m.err)
os.Exit(1)
}
}