This repository was archived by the owner on Aug 6, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdeploy.go
More file actions
125 lines (99 loc) · 3.14 KB
/
deploy.go
File metadata and controls
125 lines (99 loc) · 3.14 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
115
116
117
118
119
120
121
122
123
124
125
// Copyright (c) 2016 Axel Smeets
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"sync"
"golang.org/x/crypto/ssh"
)
var (
target = flag.String("target", "target.json", "path to the `file` with JSON-formatted targets")
script = flag.String("script", "script.sh", "path to the shell script `file`")
stdout = flag.Bool("stdout", false, "pipe remote shell stdout to current shell stdout")
)
func fatalError(msg string, err error) {
if err != nil {
log.Fatal(msg + ": " + err.Error())
}
}
func logTaskStatus(id int, target *targetConfig, status string) {
log.Printf("%s task #%d (%s@%s)\n",
status, id, target.User, target.Host)
}
func execRemoteShell(host string, conf *ssh.ClientConfig, script *[]byte) error {
client, err := ssh.Dial("tcp", host, conf)
if err != nil {
return fmt.Errorf("Failed to dial target: %s", err.Error())
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
return fmt.Errorf("Failed to start session: %s", err.Error())
}
defer session.Close()
if *stdout {
session.Stdout = os.Stdout
}
stdin, err := session.StdinPipe()
if err != nil {
return fmt.Errorf("Failed setting up stdin: %s\n", err.Error())
}
if err := session.Shell(); err != nil {
return fmt.Errorf("Error starting remote shell: %s\n", err.Error())
}
if _, err := stdin.Write(*script); err != nil {
return fmt.Errorf("Error writing script: %s\n", err.Error())
}
if err := stdin.Close(); err != nil {
return fmt.Errorf("Error closing session stdin: %s\n", err.Error())
}
if err := session.Wait(); err != nil {
return fmt.Errorf("Error during shell session: %s\n", err.Error())
}
return nil
}
func deploy(taskId int, target targetConfig, script *[]byte, wg *sync.WaitGroup) {
defer wg.Done()
if err := target.Preprocess(); err != nil {
logTaskStatus(taskId, &target, "Aborted: "+err.Error())
return
}
conf, err := target.ClientConfig()
if err != nil {
logTaskStatus(taskId, &target, "Aborted: "+err.Error())
return
}
logTaskStatus(taskId, &target, "Starting")
if err := execRemoteShell(target.Host, conf, script); err != nil {
logTaskStatus(taskId, &target, "Errored: "+err.Error())
} else {
logTaskStatus(taskId, &target, "Completed")
}
}
func main() {
flag.Parse()
// Easier on memory usage to use a json Decoder on the file (a reader),
// than reading file into memory and calling Unmarshal.
authReader, err := os.Open(*target)
fatalError("Failed to read target config", err)
defer authReader.Close()
// Easier on disk to read file once, instead of once/target. (Readers are
// consumed and must be instantiated per target)
cmd, err := ioutil.ReadFile(*script)
fatalError("Couldn't read script file", err)
// Use array (as opposed to floating entries) so json is valid
var targets []targetConfig
authDec := json.NewDecoder(authReader)
err = authDec.Decode(&targets)
fatalError("Couldn't parse targets file", err)
var wg sync.WaitGroup
wg.Add(len(targets))
for i, conf := range targets {
go deploy(i, conf, &cmd, &wg)
}
wg.Wait()
}