This repository has been archived by the owner on Feb 27, 2020. It is now read-only.
/
cmd.go
156 lines (135 loc) · 4.51 KB
/
cmd.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
154
155
156
// Package qemuguesttools implements the command that runs inside a QEMU VM.
// These guest tools are responsible for fetching and executing the task
// command, as well as posting the log from the task command to the meta-data
// service.
//
// The guest tools are also responsible for polling the meta-data service for
// actions to do like list-folder, get-artifact or execute a new shell.
//
// The guest tools is pretty much the only way taskcluster-worker can talk to
// the guest virtual machine. As you can't execute processes inside a virtual
// machine without SSH'ing into it or something. That something is these
// guest tools.
package qemuguesttools
import (
"fmt"
"io"
"io/ioutil"
"os"
yaml "gopkg.in/yaml.v2"
schematypes "github.com/taskcluster/go-schematypes"
"github.com/taskcluster/taskcluster-worker/commands"
"github.com/taskcluster/taskcluster-worker/runtime/monitoring"
"github.com/taskcluster/taskcluster-worker/runtime/util"
)
var debug = util.Debug("guesttools")
func init() {
commands.Register("qemu-guest-tools", cmd{})
}
type cmd struct{}
func (cmd) Summary() string {
return "Run guest-tools, for use in VMs for the QEMU engine"
}
func (cmd) Usage() string {
return `taskcluster-worker qemu-guest-tools start the guest tools that should
run inside the virtual machines used with QEMU engine.
The "run" (default) command will fetch a command to execute from the meta-data
service, upload the log and result as success/failed. The command will also
continuously poll the meta-data service for actions, such as put-artifact,
list-folder or start an interactive shell.
The "post-log" command will upload <log-file> to the meta-data service. If - is
given it will read the log from standard input. This command is useful as
meta-data can handle more than one log stream, granted they might get mangled.
Usage:
taskcluster-worker qemu-guest-tools [options] [run]
taskcluster-worker qemu-guest-tools [options] post-log [--] <log-file>
Options:
-c, --config <file> Load YAML configuration for file.
--host <ip> IP-address of meta-data server [default: 169.254.169.254].
-h, --help Show this screen.
Configuration:
entrypoint: [] # Wrapper command if any
env: # Default environment variables
HOME: "/home/worker"
shell: ["bash", "-bash"] # Default interactive system shell
user: "worker" # User to run commands under
workdir: "/home/worker" # Current working directory for commands
`
}
func (cmd) Execute(arguments map[string]interface{}) bool {
monitor := monitoring.NewLoggingMonitor("info", nil, "").WithTag("component", "qemu-guest-tools")
host := arguments["--host"].(string)
configFile, _ := arguments["--config"].(string)
// Load configuration
var C config
if configFile != "" {
data, err := ioutil.ReadFile(configFile)
if err != nil {
monitor.Panicf("Failed to read configFile: %s, error: %s", configFile, err)
}
var c interface{}
if err := yaml.Unmarshal(data, &c); err != nil {
monitor.Panicf("Failed to parse configFile: %s, error: %s", configFile, err)
}
c = convertSimpleJSONTypes(c)
if err := configSchema.Validate(c); err != nil {
monitor.Panicf("Invalid configFile: %s, error: %s", configFile, err)
}
schematypes.MustValidateAndMap(configSchema, c, &C)
}
// Create guest tools
g := new(C, host, monitor)
if arguments["post-log"].(bool) {
logFile := arguments["<log-file>"].(string)
var r io.Reader
if logFile == "-" {
r = os.Stdin
} else {
f, err := os.Open(logFile)
if err != nil {
monitor.Error("Failed to open log-file, error: ", err)
return false
}
defer f.Close()
r = f
}
w, done := g.CreateTaskLog()
_, err := io.Copy(w, r)
if err != nil {
monitor.Error("Failed to post entire log, error: ", err)
} else {
err = w.Close()
<-done
}
return err == nil
}
go g.Run()
// Process actions forever, this must run in the main thread as exiting the
// main thread will cause the go program to exit.
g.ProcessActions()
return true
}
func convertSimpleJSONTypes(val interface{}) interface{} {
switch val := val.(type) {
case []interface{}:
r := make([]interface{}, len(val))
for i, v := range val {
r[i] = convertSimpleJSONTypes(v)
}
return r
case map[interface{}]interface{}:
r := make(map[string]interface{})
for k, v := range val {
s, ok := k.(string)
if !ok {
s = fmt.Sprintf("%v", k)
}
r[s] = convertSimpleJSONTypes(v)
}
return r
case int:
return float64(val)
default:
return val
}
}