Skip to content

Commit 592ff97

Browse files
authored
Create run_comand
1 parent 72fb9dc commit 592ff97

File tree

1 file changed

+139
-0
lines changed

1 file changed

+139
-0
lines changed

run_comand

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Usage:
4+
{0} <vmname> [-i TEXT | - ] [-e VAREQVALUE]... [--] <program> [<args>...]
5+
6+
-i TEXT, --input TEXT The text sent on stdin.
7+
- If set, input is read from stdin.
8+
-e VAREQVALUE, --env VAREQVALUE Pairs VAR=VALUE for environment variables
9+
"""
10+
__doc__ = __doc__.format(__file__)
11+
12+
import os
13+
import sys
14+
import base64
15+
from time import sleep
16+
from docopt import docopt
17+
from pymaybe import maybe
18+
# noinspection PyUnresolvedReferences
19+
from sh import virsh
20+
from json import dumps, loads
21+
from icecream import ic
22+
from easydict import EasyDict as edict
23+
24+
def run(args):
25+
program = args["<program>"]
26+
vmname = args["<vmname>"]
27+
can_exec = check_can_exec(vmname)
28+
if not can_exec:
29+
print("Error, guest agent does not support the exec command")
30+
sys.exit(1)
31+
if program is not None:
32+
env = args["--env"]
33+
input = args["--input"]
34+
if args["-"]:
35+
input = sys.stdin.read()
36+
args = args["<args>"]
37+
stdout, stderr = exec_shell_command(vmname, program=program, args=args, input_data=input, env=env)
38+
print(stdout, file=sys.stdout, end="")
39+
print(stderr,file=sys.stderr, end="")
40+
41+
42+
def check_can_exec(vmname):
43+
supported_commands = get_supported_commands(vmname)
44+
can_exec = "guest-exec" in supported_commands and "guest-exec-status" in supported_commands
45+
return can_exec
46+
47+
48+
def get_supported_commands(vmname):
49+
supported_commands = guest_info(vmname).get("return", {}).get("supported_commands", [])
50+
supported_commands = [command["name"] for command in supported_commands if command["enabled"]]
51+
return supported_commands
52+
53+
54+
def test(vmname):
55+
# https://qemu-project.gitlab.io/qemu/interop/qemu-ga-ref.html#qapidoc-199
56+
# guest-exec Command
57+
58+
ic(exec_shell_command(vmname, "env", [], "", ["FOO=BAR"]))
59+
ic(exec_shell_command(vmname, "uname", ["-a"]))
60+
ic(exec_shell_command(vmname, "cat", input_data="foo"))
61+
ic(exec_shell_command(vmname, "bash", ["-c", "cat>&2"], input_data="foo"))
62+
ic(exec_shell_command(vmname, "whoami", ))
63+
ic(exec_shell_command(vmname, "id", ))
64+
# ic(exec_shell_command(vmname, "sleep",["1"]))
65+
# ic(exec_shell_command(vmname, "sleep",["2"]))
66+
# ic(exec_shell_command(vmname, "sleep",["3"]))
67+
# ic(exec_shell_command(vmname, "systemctl"))
68+
ic(exec_shell_command(vmname, "who"))
69+
print(exec_shell_command(vmname, "ip", ["a"])[0])
70+
71+
72+
def exec_shell_command(vmname: str, program: str, args: list[str] = None, input_data: str = None, env: list[str] = None):
73+
if input_data is None:
74+
input_data = ""
75+
if env is None:
76+
env = []
77+
if args is None:
78+
args = []
79+
80+
if isinstance(env, dict):
81+
env = [f"{key}={value}" for key, value in env.items()]
82+
83+
qemu_reply = exec_command(vmname, dict(execute="guest-exec", arguments={
84+
'path': program,
85+
"capture-output": True,
86+
"input-data": base64.encodebytes(input_data.encode()).decode(),
87+
"env": env,
88+
"arg": args,
89+
}))
90+
# https://qemu-project.gitlab.io/qemu/interop/qemu-ga-ref.html#qapidoc-195
91+
# GuestExec Object
92+
pid = qemu_reply["return"]["pid"]
93+
# ic(qemu_reply)
94+
# https://qemu-project.gitlab.io/qemu/interop/qemu-ga-ref.html#qapidoc-195
95+
# guest-exec-status Command
96+
while True:
97+
qemu_reply = exec_command(vmname, dict(execute="guest-exec-status", arguments={'pid': pid}))
98+
exited: bool = qemu_reply["return"]["exited"]
99+
# ic(exited)
100+
if exited:
101+
# ic(qemu_reply)
102+
exitcode: int = maybe(qemu_reply)["return"]["exitcode"].or_else(None)
103+
signal: int = maybe(qemu_reply)["return"]["signal"].or_else(None)
104+
stdout: str = base64.decodebytes(maybe(qemu_reply)["return"]["out-data"].encode().or_else(b"")).decode()
105+
stderr: str = base64.decodebytes(maybe(qemu_reply)["return"]["err-data"].encode().or_else(b"")).decode()
106+
stdout_trunc: bool = maybe(qemu_reply)["return"]["out-truncated"].or_else(False)
107+
stderr_trunc: bool = maybe(qemu_reply)["return"]["err-truncated"].or_else(False)
108+
# ic(exited, exitcode, signal, stdout, stderr, stdout_trunc, stderr_trunc)
109+
return stdout, stderr
110+
# break
111+
sleep(0.05)
112+
113+
114+
def exec_command(vmname, json):
115+
"""
116+
Provide a matching json, as in https://qemu-project.gitlab.io/qemu/interop/qemu-qmp-ref.html#qapidoc-133 :
117+
-> { "execute": "set-action",
118+
"arguments": { "reboot": "shutdown",
119+
"shutdown" : "pause",
120+
"panic": "pause",
121+
"watchdog": "inject-nmi" } }
122+
<- { "return": {} }
123+
"""
124+
command = dumps(json)
125+
response = virsh("qemu-agent-command", vmname, command)
126+
# ic(response.__dict__)
127+
qemu_reply = loads(str(response))
128+
return qemu_reply
129+
130+
131+
def guest_info(vmname):
132+
command = dumps(dict(execute="guest-info", ))
133+
response = virsh("qemu-agent-command", vmname, command)
134+
qemu_reply = loads(str(response))
135+
return qemu_reply
136+
137+
138+
if __name__ == '__main__':
139+
run(docopt(__doc__))

0 commit comments

Comments
 (0)