-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvdc.js
191 lines (171 loc) · 7.56 KB
/
vdc.js
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env node
/*
** vdc.js -- Simple msg Cloud vDC Command-Line Interface (CLI)
** Copyright (c) 2017-2019 Dr. Ralf S. Engelschall <http://engelschall.com>
**
** This Source Code Form is subject to the terms of the Mozilla Public
** License (MPL), version 2.0. If a copy of the MPL was not distributed
** with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* load external requirements */
const Inquirer = require("inquirer")
const Bluebird = require("bluebird")
const co = require("co")
const chalk = require("chalk")
const Caporal = require("caporal")
const SuperAgent = require("superagent")
const SuperAgentProxy = require("superagent-proxy")
const SSH = require("node-ssh")
const HostId = require("hostid")
const path = require("path")
/* provide an outer asynchronous environment */
co(async () => {
/* determine package information */
const pkg = require(path.join(__dirname, "package.json"))
/* determine unique host identifier */
let hostid = HostId().replace(/-/g, "")
/* toggle power of a VM via vDC */
const vdcPower = async (level, vms, opts) => {
/* optionally ask for password interactively */
if (typeof opts.password !== "string") {
let answers = await Inquirer.prompt([ {
type: "password",
name: "password",
message: `${opts.username} vDC password:`
}])
opts.password = answers.password
}
/* create a new cookie-aware and proxy-aware HTTP agent */
SuperAgentProxy(SuperAgent)
const proxy = process.env["http_proxy"] ? process.env["http_proxy"] : null
const agent = SuperAgent.agent()
const request = (agent) => {
if (proxy !== null)
agent.proxy(proxy)
agent.set("User-Agent", `${pkg.name}/${pkg.version}`)
return agent
}
/* authenticate via login endpoint */
let res = await request(agent
.post(`${opts.location}/api/login`)
.accept("application/json")
.send({
email: opts.username,
password: opts.password,
hash: hostid,
force_login: true,
lang: "en"
}))
/* determine API userid/token via panel dialog */
res = await request(agent
.get(`${opts.location}/Panel/`))
let [ , apiUser ] = res.text.match(/"user":\s*'(.+?)'/)
let [ , apiToken ] = res.text.match(/"token":\s*'(.+?)'/)
let [ , apiURL ] = res.text.match(/"api":\s*'(.+?)'/)
/* determine name/UUID of all servers */
res = await request(agent
.get(`${apiURL}/objects/servers`)
.accept("application/json")
.set("X-Auth-UserId", apiUser)
.set("X-Auth-Token", apiToken))
let name2id = {}
let servers = res.body.servers
Object.keys(servers).forEach((id) => {
let server = servers[id]
name2id[server.name] = id
})
/* iterate over all VMs */
let vmmax = vms.split(/,/).length
let vmcur = 0
await Bluebird.each(vms.split(/,/), async (vm) => {
/* map name to vDC id */
let id = name2id[vm]
if (typeof id !== "string")
throw new Error(`invalid VM name "${vm}"`)
/* power on/off a particular VM */
console.log(`${chalk.blue(vm)}: switching power ${level ? "on" : "off"}`)
res = await request(agent
.patch(`${apiURL}/objects/servers/${id}/power`)
.accept("application/json")
.set("X-Auth-UserId", apiUser)
.set("X-Auth-Token", apiToken)
.send({ power: level }))
/* shameless workaround: vDC API dislikes subsequent power togglings within too short time */
if (++vmcur < vmmax)
await new Promise((resolve, reject) => { setTimeout(resolve, 10*1000) })
})
}
/* execute a command under an OS via SSH */
const sshCommand = async (cmd, hosts, opts) => {
/* optionally ask for password interactively */
if (typeof opts.password !== "string") {
let answers = await Inquirer.prompt([ {
type: "password",
name: "password",
message: `${opts.username} SSH password:`
}])
opts.password = answers.password
}
/* iterate over all hosts */
await Bluebird.each(hosts.split(/,/), async (host) => {
/* send command to remote host via SSH */
let prefix = `${opts.username}@${host}: `
console.log(`${chalk.blue(prefix)}$ ${cmd}`)
const ssh = new SSH()
await ssh.connect({
host: host,
username: opts.username,
password: opts.password,
readyTimeout: 5*1000,
})
let result = await ssh.execCommand(cmd, { pty: true })
if (result.stdout !== "")
console.log(result.stdout.replace(/^/mg, chalk.blue(prefix)))
})
}
/* parse command-line arguments */
let command = null
const action = (name) =>
(args, opts, logger) =>
command = { name: name, args, opts }
Caporal
.version(pkg.version)
.description("Simple msg Cloud Virtual Data Center (vDC) Command-Line Interface (CLI)")
/* power-on command */
.command("power-on", "Switch power of a VM ON via vDC").alias("on")
.option("-l, --location <url>", "The vDC URL", /^http/, "https://vdc.msg.systems")
.option("-u, --username <username>", "The vDC login username (usually the Email address)")
.option("-p, --password <password>", "The vDC login password")
.argument("<vm-name>", "Name of VM(s)")
.action(action("power-on"))
/* power-off command */
.command("power-off", "Switch power of a VM OFF via vDC").alias("off")
.option("-l, --location <url>", "The vDC URL", /^http/, "https://vdc.msg.systems")
.option("-u, --username <username>", "The vDC login username (usually the Email address)")
.option("-p, --password <password>", "The vDC login password")
.argument("<vm-name>", "Name of VM(s)")
.action(action("power-off"))
/* exec command */
.command("exec", "Execute a shell command under an OS via SSH")
.option("-u, --username <username>", "The OS login username", /^.+$/, "root")
.option("-p, --password <password>", "The OS login password")
.argument("<host-name>", "Name of host(s)")
.argument("<command>", "Command to execute")
.action(action("exec"))
Caporal.parse(process.argv)
/* dispatch according to command */
if (command == null)
process.exit(1)
switch (command.name) {
case "power-on": await vdcPower(true, command.args.vmName, command.opts); break
case "power-off": await vdcPower(false, command.args.vmName, command.opts); break
case "exec": await sshCommand(command.args.command, command.args.hostName, command.opts); break
}
/* die gracefully */
process.exit(0)
}).catch((err) => {
/* central error handling */
console.log(`vdc: ${chalk.red("ERROR")}: ${err}`)
console.log(`vdc: ${chalk.red("ERROR")}: ${require("util").inspect(err, { color: true, depth: 4 })}`)
process.exit(1)
})