Skip to content

Commit eb9f436

Browse files
committed
Initial version
0 parents  commit eb9f436

File tree

3 files changed

+272
-0
lines changed

3 files changed

+272
-0
lines changed

main.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"github.com/go-yaml/yaml"
6+
"github.com/buger/cloud-ssh/provider"
7+
"os"
8+
"os/exec"
9+
"log"
10+
"strings"
11+
"runtime"
12+
"io/ioutil"
13+
"regexp"
14+
"strconv"
15+
)
16+
17+
type CloudInstances map[string]provider.Instances
18+
type StrMap map[string]string
19+
type Config map[string]StrMap
20+
21+
func splitHostname(str string) (user string, hostname string) {
22+
if arr := strings.Split(str, "@"); len(arr) > 1 {
23+
return arr[0], arr[1]
24+
} else {
25+
return "", str
26+
}
27+
}
28+
29+
func joinHostname(user string, hostname string) string {
30+
if user != "" {
31+
return user + "@" + hostname
32+
} else {
33+
return hostname
34+
}
35+
}
36+
37+
func getTargetHostname(args []string) (user string, hostname string, arg_idx int) {
38+
for idx, arg := range args {
39+
if !strings.HasPrefix(arg, "-") {
40+
if idx == 0 {
41+
hostname = arg
42+
arg_idx = idx
43+
break
44+
} else {
45+
if !strings.HasPrefix(args[idx-1], "-") {
46+
hostname = arg
47+
arg_idx = idx
48+
break
49+
}
50+
}
51+
}
52+
}
53+
54+
user, hostname = splitHostname(hostname)
55+
56+
return
57+
}
58+
59+
func getInstances(config Config) (clouds CloudInstances) {
60+
clouds = make(CloudInstances)
61+
62+
for name, cfg := range config {
63+
for k,v := range cfg {
64+
cfg["name"] = name
65+
66+
if k == "provider" {
67+
switch v {
68+
case "aws":
69+
clouds[name] = provider.GetEC2Instances(cfg)
70+
default:
71+
log.Println("Unknown provider: ", v)
72+
}
73+
}
74+
}
75+
}
76+
77+
return
78+
}
79+
80+
func userHomeDir() string {
81+
if runtime.GOOS == "windows" {
82+
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
83+
if home == "" {
84+
home = os.Getenv("USERPROFILE")
85+
}
86+
return home
87+
}
88+
return os.Getenv("HOME")
89+
}
90+
91+
func readConfig() (config Config) {
92+
config = make(Config)
93+
94+
prefferedPaths := []string{
95+
"./cloud-ssh.yaml",
96+
userHomeDir() + "/.ssh/cloud-ssh.yaml",
97+
"/etc/cloud-ssh.yaml",
98+
}
99+
100+
var content []byte
101+
102+
for _, path := range prefferedPaths {
103+
if _, err := os.Stat(path); err == nil {
104+
fmt.Println("Found config:", path)
105+
content, err = ioutil.ReadFile(path)
106+
107+
if err != nil {
108+
log.Fatal("Error while reading config: ", err)
109+
}
110+
}
111+
}
112+
113+
if len(content) == 0 {
114+
if os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "" {
115+
config["default"] = make(StrMap)
116+
config["default"]["access_key"] = os.Getenv("AWS_ACCESS_KEY_ID")
117+
config["default"]["secret_key"] = os.Getenv("AWS_SECRET_ACCESS_KEY")
118+
config["default"]["region"] = os.Getenv("AWS_REGION")
119+
config["default"]["provider"] = "aws"
120+
121+
return
122+
}
123+
}
124+
125+
if len(content) == 0 {
126+
fmt.Println("Can't find any configuration or ENV variables. Check http://github.com/buger/cloud-ssh for documentation.")
127+
return
128+
}
129+
130+
if err := yaml.Unmarshal(content, &config); err != nil {
131+
log.Fatal(err)
132+
}
133+
134+
return
135+
}
136+
137+
138+
func getMatchedInstances(clouds CloudInstances, filter string) (matched []StrMap) {
139+
140+
// Fuzzy matching (like SublimeText)
141+
filter = strings.Join(strings.Split(filter,""), ".*?")
142+
143+
rHost := regexp.MustCompile(filter)
144+
145+
for cloud, instances := range clouds {
146+
for addr, tags := range instances {
147+
for _, tag := range tags {
148+
if rHost.MatchString(tag.Value) {
149+
matched = append(matched, StrMap{
150+
"cloud": cloud,
151+
"addr": addr,
152+
"tag_name": tag.Name,
153+
"tag_value": tag.Value,
154+
})
155+
156+
break
157+
}
158+
}
159+
}
160+
}
161+
162+
return
163+
}
164+
165+
func formatMatchedInstance(inst StrMap) string {
166+
return "Cloud: " + inst["cloud"] + "\tMatched by: " + inst["tag_name"] + "=" + inst["tag_value"] + "\tAddr: " + inst["addr"]
167+
}
168+
169+
func main() {
170+
config := readConfig()
171+
instances := getInstances(config)
172+
173+
args := os.Args[1:len(os.Args)]
174+
175+
user, hostname, arg_idx := getTargetHostname(args)
176+
177+
match := getMatchedInstances(instances, hostname)
178+
179+
180+
if len(match) == 0 {
181+
fmt.Println("Can't find cloud instance, trying to connect anyway")
182+
} else if len(match) == 1 {
183+
hostname = match[0]["addr"]
184+
fmt.Println("Found clound instance:")
185+
fmt.Println(formatMatchedInstance(match[0]))
186+
} else {
187+
fmt.Println("Found multiple instances:")
188+
for i, host := range match {
189+
fmt.Println(strconv.Itoa(i+1)+") ", formatMatchedInstance(host))
190+
}
191+
fmt.Print("Choose instance: ")
192+
193+
var i int
194+
_, err := fmt.Scanf("%d", &i)
195+
196+
if err != nil || i > len(match)+1 {
197+
log.Fatal("Wrong index")
198+
}
199+
200+
hostname = match[i-1]["addr"]
201+
}
202+
203+
args[arg_idx] = joinHostname(user, hostname)
204+
205+
cmd := exec.Command("ssh", args...)
206+
cmd.Stdin = os.Stdin
207+
cmd.Stdout = os.Stdout
208+
cmd.Stderr = os.Stderr
209+
210+
cmd.Run()
211+
}

provider/aws.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package provider
2+
3+
import (
4+
"log"
5+
"launchpad.net/goamz/aws"
6+
"launchpad.net/goamz/ec2"
7+
)
8+
9+
func GetEC2Instances(config map[string]string) (instances Instances) {
10+
instances = make(Instances)
11+
12+
if _,ok := config["access_key"]; !ok {
13+
log.Fatal("Missing access_key for ", config["name"], " AWS cloud")
14+
}
15+
16+
if _,ok := config["secret_key"]; !ok {
17+
log.Fatal("Missing secret_key for ", config["name"], " AWS cloud")
18+
}
19+
20+
if _,ok := config["region"]; !ok {
21+
config["region"] = "us-east-1"
22+
}
23+
24+
auth := aws.Auth{config["access_key"], config["secret_key"]}
25+
26+
e := ec2.New(auth, aws.Regions[config["region"]])
27+
resp, err := e.Instances(nil, nil)
28+
29+
if err != nil {
30+
log.Println(err)
31+
return
32+
}
33+
34+
for _, res := range resp.Reservations {
35+
for _, inst := range res.Instances {
36+
37+
if inst.DNSName != "" {
38+
var tags []Tag
39+
40+
for _, tag := range inst.Tags {
41+
tags = append(tags, Tag{"Tag", tag.Value})
42+
}
43+
44+
for _, sg := range inst.SecurityGroups {
45+
tags = append(tags, Tag{"Security group", sg.Name})
46+
}
47+
48+
instances[inst.DNSName] = tags
49+
}
50+
}
51+
}
52+
53+
return
54+
}

provider/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package provider
2+
3+
type Tag struct {
4+
Name, Value string
5+
}
6+
7+
type Instances map[string][]Tag

0 commit comments

Comments
 (0)