Skip to content

Commit 3411f03

Browse files
committed
add host file support
1 parent b101c18 commit 3411f03

File tree

4 files changed

+93
-3
lines changed

4 files changed

+93
-3
lines changed

README.md

+34-1
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,44 @@ extra costs to create a process, the second one means faster and less resource a
1212
and public key to authenticate.
1313

1414
## Usages
15+
16+
### Use ssh command to access host1 and host2 running ls.
1517
```bash
16-
// Use ssh command to access host1 and host2 running ls.
1718
$ llsh --hosts hosts1,hosts2 \
1819
--user sojiang \
1920
-c "ls"
21+
```
22+
23+
### Use host file
24+
Hhost file is INI-like file, the format is like this, file name can be `test.hosts`
25+
```ini
26+
host1
27+
host2
28+
29+
[app]
30+
app1
31+
app2
32+
33+
[web]
34+
web1
35+
web2
2036
```
2137

38+
If you want to run `ls` on all of the servers,
39+
```bash
40+
$ llsh --host_file test.hosts \
41+
--user sojiang \
42+
-c "ls"
43+
```
44+
45+
If you only want to manipulate app section, it will choose only `app1` and `app2`
46+
```bash
47+
$ llsh --host_file test.hosts \
48+
--section app \
49+
--user sojiang \
50+
-c "ls"
51+
```
52+
2253
### Help
2354
```bash
2455
$ llsh --help
@@ -30,9 +61,11 @@ Usage:
3061
Flags:
3162
-c, --command string The command you need to run on the servers
3263
-h, --help help for llsh
64+
--host_file string The path of host file, the content should be hosts separated by new line, if specified, hosts will be omitted
3365
--hosts string The host names for remote server, split by comma, for example, host1,host2,host3
3466
-p, --password If we need to input password from terminal
3567
-k, --publickey string The file path of the public key, like ~/.ssh/id_rsa.pub
68+
--section string The section of the host_file, default to all hosts when not specified or empty
3669
--ssh_command_path string The file path of the ssh command, like /bin/ssh (default "ssh")
3770
--use_ssh_command Use the ssh executable, like /bin/ssh (default true)
3871
-u, --user string The username for login user

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ require (
77
github.com/mattn/go-colorable v0.1.2 // indirect
88
github.com/spf13/cobra v0.0.5
99
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
10+
gopkg.in/ini.v1 v1.47.0
1011
)

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,6 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpbl
4040
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
4141
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
4242
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
43+
gopkg.in/ini.v1 v1.47.0 h1:XAVsOWcIxjm6JVEbCbSZgSBZIF0BrCzXs4orAQr6uc8=
44+
gopkg.in/ini.v1 v1.47.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
4345
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

pkg/llsh/cmd/cmd.go

+56-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package cmd
33
import (
44
"fmt"
55
"io/ioutil"
6+
"os"
67
"strings"
78
"sync"
89

10+
"gopkg.in/ini.v1"
11+
912
"github.com/fatih/color"
1013
"github.com/songrgg/llsh/pkg/helper"
1114
"github.com/songrgg/llsh/pkg/ssh"
@@ -15,6 +18,8 @@ import (
1518
type options struct {
1619
Command string
1720
Hosts string
21+
HostFile string
22+
Section string
1823
User string
1924
UseSSHCommand bool
2025
UsePassword bool
@@ -43,6 +48,10 @@ func NewLLSHCommand() *cobra.Command {
4348

4449
cmds.PersistentFlags().StringVar(&options.Hosts, "hosts", "",
4550
"The host names for remote server, split by comma, for example, host1,host2,host3")
51+
cmds.PersistentFlags().StringVar(&options.HostFile, "host_file", "",
52+
"The path of host file, the content should be hosts separated by new line, if specified, hosts will be omitted")
53+
cmds.PersistentFlags().StringVar(&options.Section, "section", "",
54+
"The section of the host_file, default to all hosts when not specified or empty")
4655
cmds.PersistentFlags().StringVarP(&options.User, "user", "u", "",
4756
"The username for login user")
4857
cmds.PersistentFlags().StringVarP(&options.Command, "command", "c", "",
@@ -86,10 +95,55 @@ func (o *options) authMethods() []ssh.AuthMethod {
8695
return authMethods
8796
}
8897

98+
func (o *options) hosts() ([]string, error) {
99+
if o.HostFile != "" {
100+
//cfg, err := ini.Load(o.HostFile)
101+
cfg, err := ini.LoadSources(ini.LoadOptions{SkipUnrecognizableLines: true}, o.HostFile)
102+
if err != nil {
103+
return nil, err
104+
}
105+
106+
cfg, err = ini.LoadSources(ini.LoadOptions{
107+
UnparseableSections: cfg.SectionStrings(),
108+
}, o.HostFile)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
// Set to all roles which is the ini `DEFAULT` section.
114+
var hosts []string
115+
var sections []*ini.Section
116+
if o.Section == "" {
117+
sections = cfg.Sections()
118+
} else {
119+
sec, err := cfg.GetSection(o.Section)
120+
if err != nil {
121+
return nil, err
122+
}
123+
sections = append(sections, sec)
124+
}
125+
126+
for _, sec := range sections {
127+
for _, host := range strings.Split(string(sec.Body()), "\n") {
128+
if h := strings.Trim(host, " "); h != "" {
129+
hosts = append(hosts, h)
130+
}
131+
}
132+
}
133+
return hosts, nil
134+
}
135+
return strings.Split(o.Hosts, ","), nil
136+
}
137+
89138
func (o *options) run() {
90139
authMethods := o.authMethods()
91140

92-
hosts := strings.Split(o.Hosts, ",")
141+
hosts, err := o.hosts()
142+
if err != nil {
143+
color.Red("Invalid hosts: %v", err)
144+
os.Exit(1)
145+
}
146+
93147
var wg sync.WaitGroup
94148
var results []sshResult
95149
var lock = sync.Mutex{}
@@ -134,7 +188,7 @@ func (o *options) run() {
134188
}
135189

136190
func (o *options) validate() {
137-
if o.Hosts == "" {
191+
if o.HostFile == "" && o.Hosts == "" {
138192
helper.Errorlq("hosts can't be empty")
139193
}
140194

0 commit comments

Comments
 (0)