/
crane.go
153 lines (139 loc) · 3.78 KB
/
crane.go
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package crane
import (
"errors"
"fmt"
"github.com/flynn/go-shlex"
"github.com/michaelsauter/crane/print"
"io"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"syscall"
)
type StatusError struct {
error error
status int
}
var requiredDockerVersion = []int{1, 3}
func RealMain() {
// On panic, recover the error, display it and return the given status code if any
defer func() {
var statusError StatusError
switch err := recover().(type) {
case StatusError:
statusError = err
case error:
statusError = StatusError{err, 1}
case string:
statusError = StatusError{errors.New(err), 1}
default:
statusError = StatusError{}
}
if statusError.error != nil {
print.Errorf("ERROR: %s\n", statusError.error)
}
os.Exit(statusError.status)
}()
checkDockerClient()
handleCmd()
}
// Ensure there is a docker binary in the path,
// and printing an error if its version is below the minimal requirement.
func checkDockerClient() {
output, err := commandOutput("docker", []string{"--version"})
if err != nil {
panic(StatusError{errors.New("Error when probing Docker's client version. Is docker installed and within the $PATH?"), 69})
}
re := regexp.MustCompile("([0-9]+)\\.([0-9]+)\\.?([0-9]+)?")
rawVersions := re.FindStringSubmatch(string(output))
var versions []int
for _, rawVersion := range rawVersions[1:] {
version, err := strconv.Atoi(rawVersion)
if err != nil {
print.Errorf("Error when parsing Docker's version %v: %v", rawVersion, err)
break
}
versions = append(versions, version)
}
for i, expectedVersion := range requiredDockerVersion {
if versions[i] > expectedVersion {
break
}
if versions[i] < expectedVersion {
print.Errorf("Unsupported client version! Please upgrade to Docker %v or later.\n", intJoin(requiredDockerVersion, "."))
}
}
}
// Similar to strings.Join() for int slices.
func intJoin(intSlice []int, sep string) string {
var stringSlice []string
for _, v := range intSlice {
stringSlice = append(stringSlice, fmt.Sprint(v))
}
return strings.Join(stringSlice, ".")
}
func executeHook(hook string) {
cmds, err := shlex.Split(hook)
if err != nil {
panic(StatusError{fmt.Errorf("Error when parsing hook `%v`: %v", hook, err), 64})
}
switch len(cmds) {
case 0:
return
case 1:
executeCommand(cmds[0], []string{})
default:
executeCommand(cmds[0], cmds[1:])
}
}
func executeCommand(name string, args []string) {
if isVerbose() {
print.Infof("\n--> %s %s\n", name, strings.Join(args, " "))
}
cmd := exec.Command(name, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Run()
if !cmd.ProcessState.Success() {
status := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
panic(StatusError{errors.New(cmd.ProcessState.String()), status})
}
}
func executeCommandBackground(name string, args []string) (stdout, stderr io.ReadCloser) {
if isVerbose() {
print.Infof("--> %s %s\n\n", name, strings.Join(args, " "))
}
cmd := exec.Command(name, args...)
stdout, _ = cmd.StdoutPipe()
stderr, _ = cmd.StderrPipe()
cmd.Start()
return stdout, stderr
}
func commandOutput(name string, args []string) (string, error) {
out, err := exec.Command(name, args...).CombinedOutput()
return strings.TrimSpace(string(out)), err
}
// From https://gist.github.com/dagoof/1477401
func pipedCommandOutput(pipedCommandArgs ...[]string) ([]byte, error) {
var commands []exec.Cmd
for _, commandArgs := range pipedCommandArgs {
cmd := exec.Command(commandArgs[0], commandArgs[1:]...)
commands = append(commands, *cmd)
}
for i, command := range commands[:len(commands)-1] {
out, err := command.StdoutPipe()
if err != nil {
return nil, err
}
command.Start()
commands[i+1].Stdin = out
}
final, err := commands[len(commands)-1].Output()
if err != nil {
return nil, err
}
return final, nil
}