-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathexec.go
133 lines (107 loc) · 3.42 KB
/
exec.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
package commands
import (
"bytes"
"fmt"
"io"
"kool-dev/kool/core/builder"
"kool-dev/kool/core/environment"
"os"
"strings"
"github.com/spf13/cobra"
)
// KoolExecFlags holds the flags for the exec command
type KoolExecFlags struct {
EnvVariables []string
Detach bool
}
// KoolExec holds handlers and functions to implement the exec command logic
type KoolExec struct {
DefaultKoolService
Flags *KoolExecFlags
env environment.EnvStorage
composeExec builder.Command
}
func AddKoolExec(root *cobra.Command) {
var (
exec = NewKoolExec()
execCmd = NewExecCommand(exec)
)
root.AddCommand(execCmd)
}
// NewKoolExec creates a new handler for exec logic
func NewKoolExec() *KoolExec {
return &KoolExec{
*newDefaultKoolService(),
&KoolExecFlags{[]string{}, false},
environment.NewEnvStorage(),
builder.NewCommand("docker", "compose", "exec"),
}
}
func (e *KoolExec) detectTTY() {
var isTerminal = e.Shell().IsTerminal()
if !isTerminal {
e.composeExec.AppendArgs("-T")
}
}
func (e *KoolExec) checkUser(service string) {
var (
asuser string
err error
actualInput io.Reader
)
if asuser = e.env.Get("KOOL_ASUSER"); asuser == "" {
return
}
actualInput = e.Shell().InStream()
defer e.Shell().SetInStream(actualInput) // return actualInput
// avoid interference of Exec on actual input
// by temporarily setting stdin to a fake mock
e.Shell().SetInStream(bytes.NewBuffer([]byte{}))
// we have a KOOL_ASUSER env; now we need to know whether
// the image of the target service have such user
var passwd string
if passwd, err = e.Shell().Exec(e.composeExec, "-T", service, "cat", "/etc/passwd"); err != nil {
// for safety, let's write the warning message to os.Stderr
// so we avoid getting cross-fire on in/out redirections
actualOut := e.Shell().OutStream()
defer e.Shell().SetOutStream(actualOut)
e.Shell().SetOutStream(os.Stderr)
if e.env.IsTrue("KOOL_VERBOSE") {
e.Shell().Warning(fmt.Sprintf("failed to check running container for kool user; did you forget kool start? (err: %s)", err.Error()))
}
} else if strings.Contains(passwd, fmt.Sprintf("kool:x:%s", asuser)) {
// since user (kool:x:UID) exists within the container, we set it
e.composeExec.AppendArgs("--user", asuser)
}
}
// Execute runs the exec logic with incoming arguments.
func (e *KoolExec) Execute(args []string) (err error) {
e.detectTTY()
e.checkUser(args[0])
if len(e.Flags.EnvVariables) > 0 {
for _, envVar := range e.Flags.EnvVariables {
e.composeExec.AppendArgs("--env", envVar)
}
}
if e.Flags.Detach {
e.composeExec.AppendArgs("--detach")
}
err = e.Shell().Interactive(e.composeExec, args...)
return
}
// NewExecCommand initializes new kool exec command
func NewExecCommand(exec *KoolExec) (execCmd *cobra.Command) {
execCmd = &cobra.Command{
Use: "exec [OPTIONS] SERVICE COMMAND [--] [ARG...]",
Short: "Execute a command inside a running service container",
Long: `Execute a COMMAND inside the specified SERVICE container (similar to an SSH session).`,
Args: cobra.MinimumNArgs(2),
RunE: DefaultCommandRunFunction(exec),
DisableFlagsInUseLine: true,
}
execCmd.Flags().StringArrayVarP(&exec.Flags.EnvVariables, "env", "e", []string{}, "Environment variables.")
execCmd.Flags().BoolVarP(&exec.Flags.Detach, "detach", "d", false, "Detached mode: Run command in the background.")
//After a non-flag arg, stop parsing flags
execCmd.Flags().SetInterspersed(false)
return
}