-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun_command.py
More file actions
140 lines (120 loc) · 5.1 KB
/
Copy pathrun_command.py
File metadata and controls
140 lines (120 loc) · 5.1 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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/env python3
"""
Usage:
{0} <vmname> [-i TEXT | - ] [-e VAREQVALUE]... [--] <program> [<args>...]
-i TEXT, --input TEXT The text sent on stdin.
- If set, input is read from stdin.
-e VAREQVALUE, --env VAREQVALUE Pairs VAR=VALUE for environment variables
"""
__doc__ = __doc__.format(__file__)
import os
import sys
import base64
from time import sleep
from docopt import docopt
from pymaybe import maybe
# noinspection PyUnresolvedReferences
from sh import virsh
from json import dumps, loads
from icecream import ic
from easydict import EasyDict as edict
def run(args):
program = args["<program>"]
vmname = args["<vmname>"]
can_exec = check_can_exec(vmname)
if not can_exec:
print("Error, guest agent does not support the exec command")
sys.exit(1)
if program is not None:
env = args["--env"]
input = args["--input"]
if args["-"]:
input = sys.stdin.read()
args = args["<args>"]
stdout, stderr, exitcode = exec_shell_command(vmname, program=program, args=args, input_data=input, env=env)
print(stdout, file=sys.stdout, end="")
print(stderr,file=sys.stderr, end="")
sys.exit(exitcode)
def check_can_exec(vmname):
supported_commands = get_supported_commands(vmname)
can_exec = "guest-exec" in supported_commands and "guest-exec-status" in supported_commands
return can_exec
def get_supported_commands(vmname):
supported_commands = guest_info(vmname).get("return", {}).get("supported_commands", [])
supported_commands = [command["name"] for command in supported_commands if command["enabled"]]
return supported_commands
def test(vmname):
ic(exec_shell_command(vmname, "env", [], "", ["FOO=BAR"]))
ic(exec_shell_command(vmname, "uname", ["-a"]))
ic(exec_shell_command(vmname, "cat", input_data="foo"))
ic(exec_shell_command(vmname, "bash", ["-c", "cat>&2"], input_data="foo"))
ic(exec_shell_command(vmname, "whoami", ))
ic(exec_shell_command(vmname, "id", ))
# ic(exec_shell_command(vmname, "sleep",["1"]))
# ic(exec_shell_command(vmname, "sleep",["2"]))
# ic(exec_shell_command(vmname, "sleep",["3"]))
# ic(exec_shell_command(vmname, "systemctl"))
ic(exec_shell_command(vmname, "who"))
print(exec_shell_command(vmname, "ip", ["a"])[0])
def exec_shell_command(vmname: str, program: str, args: list[str] = None, input_data: str = None, env: list[str] = None):
if input_data is None:
input_data = ""
if env is None:
env = []
if args is None:
args = []
if isinstance(env, dict):
env = [f"{key}={value}" for key, value in env.items()]
# https://qemu-project.gitlab.io/qemu/interop/qemu-ga-ref.html#qapidoc-199
# guest-exec Command
qemu_reply = exec_command(vmname, dict(execute="guest-exec", arguments={
'path': program,
"capture-output": True,
"input-data": base64.encodebytes(input_data.encode()).decode(),
"env": env,
"arg": args,
}))
# https://qemu-project.gitlab.io/qemu/interop/qemu-ga-ref.html#qapidoc-195
# GuestExec Object
pid = qemu_reply["return"]["pid"]
# ic(qemu_reply)
# https://qemu-project.gitlab.io/qemu/interop/qemu-ga-ref.html#qapidoc-195
# guest-exec-status Command
while True:
qemu_reply = exec_command(vmname, dict(execute="guest-exec-status", arguments={'pid': pid}))
exited: bool = qemu_reply["return"]["exited"]
# ic(exited)
if exited:
# ic(qemu_reply)
exitcode: int = maybe(qemu_reply)["return"]["exitcode"].or_else(None)
signal: int = maybe(qemu_reply)["return"]["signal"].or_else(None)
stdout: str = base64.decodebytes(maybe(qemu_reply)["return"]["out-data"].encode().or_else(b"")).decode()
stderr: str = base64.decodebytes(maybe(qemu_reply)["return"]["err-data"].encode().or_else(b"")).decode()
stdout_trunc: bool = maybe(qemu_reply)["return"]["out-truncated"].or_else(False)
stderr_trunc: bool = maybe(qemu_reply)["return"]["err-truncated"].or_else(False)
# ic(exited, exitcode, signal, stdout, stderr, stdout_trunc, stderr_trunc)
return stdout, stderr, exitcode
# break
sleep(0.05)
def exec_command(vmname, json):
"""
Provide a matching json, as in https://qemu-project.gitlab.io/qemu/interop/qemu-qmp-ref.html#qapidoc-133 :
-> { "execute": "set-action",
"arguments": { "reboot": "shutdown",
"shutdown" : "pause",
"panic": "pause",
"watchdog": "inject-nmi" } }
<- { "return": {} }
"""
command = dumps(json)
response = virsh("qemu-agent-command", vmname, command)
# ic(response.__dict__)
qemu_reply = loads(str(response))
return qemu_reply
def guest_info(vmname):
command = dumps(dict(execute="guest-info", ))
response = virsh("qemu-agent-command", vmname, command)
qemu_reply = loads(str(response))
return qemu_reply
if __name__ == '__main__':
run(docopt(__doc__))