atex 0.7__py3-none-any.whl → 0.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atex/cli/fmf.py +143 -0
- atex/cli/libvirt.py +127 -0
- atex/cli/testingfarm.py +35 -13
- atex/connection/__init__.py +13 -19
- atex/connection/podman.py +63 -0
- atex/connection/ssh.py +34 -52
- atex/executor/__init__.py +2 -0
- atex/executor/duration.py +60 -0
- atex/executor/executor.py +402 -0
- atex/executor/reporter.py +101 -0
- atex/{minitmt → executor}/scripts.py +37 -25
- atex/{minitmt → executor}/testcontrol.py +54 -42
- atex/fmf.py +237 -0
- atex/orchestrator/__init__.py +3 -59
- atex/orchestrator/aggregator.py +82 -134
- atex/orchestrator/orchestrator.py +385 -0
- atex/provision/__init__.py +74 -105
- atex/provision/libvirt/__init__.py +2 -24
- atex/provision/libvirt/libvirt.py +465 -0
- atex/provision/libvirt/locking.py +168 -0
- atex/provision/libvirt/setup-libvirt.sh +21 -1
- atex/provision/podman/__init__.py +1 -0
- atex/provision/podman/podman.py +274 -0
- atex/provision/testingfarm/__init__.py +2 -29
- atex/provision/testingfarm/api.py +123 -65
- atex/provision/testingfarm/testingfarm.py +234 -0
- atex/util/__init__.py +1 -6
- atex/util/libvirt.py +18 -0
- atex/util/log.py +31 -8
- atex/util/named_mapping.py +158 -0
- atex/util/path.py +16 -0
- atex/util/ssh_keygen.py +14 -0
- atex/util/threads.py +99 -0
- atex-0.9.dist-info/METADATA +178 -0
- atex-0.9.dist-info/RECORD +43 -0
- atex/cli/minitmt.py +0 -175
- atex/minitmt/__init__.py +0 -23
- atex/minitmt/executor.py +0 -348
- atex/minitmt/fmf.py +0 -202
- atex/provision/nspawn/README +0 -74
- atex/provision/podman/README +0 -59
- atex/provision/podman/host_container.sh +0 -74
- atex/provision/testingfarm/foo.py +0 -1
- atex-0.7.dist-info/METADATA +0 -102
- atex-0.7.dist-info/RECORD +0 -32
- {atex-0.7.dist-info → atex-0.9.dist-info}/WHEEL +0 -0
- {atex-0.7.dist-info → atex-0.9.dist-info}/entry_points.txt +0 -0
- {atex-0.7.dist-info → atex-0.9.dist-info}/licenses/COPYING.txt +0 -0
atex/cli/fmf.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import pprint
|
|
3
|
+
|
|
4
|
+
from .. import fmf
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _fatal(msg):
|
|
8
|
+
print(msg, file=sys.stderr)
|
|
9
|
+
sys.exit(1)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _get_context(args):
|
|
13
|
+
context = {}
|
|
14
|
+
if args.context:
|
|
15
|
+
for c in args.context:
|
|
16
|
+
key, value = c.split("=", 1)
|
|
17
|
+
context[key] = value
|
|
18
|
+
return context or None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def make_fmftests(args):
|
|
22
|
+
return fmf.FMFTests(
|
|
23
|
+
args.root,
|
|
24
|
+
args.plan,
|
|
25
|
+
names=args.test or None,
|
|
26
|
+
filters=args.filter or None,
|
|
27
|
+
conditions=args.condition or None,
|
|
28
|
+
excludes=args.exclude or None,
|
|
29
|
+
context=_get_context(args),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def requires(args):
|
|
34
|
+
result = make_fmftests(args)
|
|
35
|
+
all_pkgs = set()
|
|
36
|
+
all_pkgs.update(fmf.all_pkg_requires(result, key="require"))
|
|
37
|
+
all_pkgs.update(fmf.all_pkg_requires(result, key="recommend"))
|
|
38
|
+
for pkg in sorted(all_pkgs):
|
|
39
|
+
print(pkg)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def discover(args):
|
|
43
|
+
result = make_fmftests(args)
|
|
44
|
+
for name in result.tests:
|
|
45
|
+
print(name)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def show(args):
|
|
49
|
+
result = make_fmftests(args)
|
|
50
|
+
for name, data in result.tests.items():
|
|
51
|
+
print(f"\n--- {name} ---")
|
|
52
|
+
pprint.pprint(data)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def prepare(args):
|
|
56
|
+
result = make_fmftests(args)
|
|
57
|
+
print("--- fmf root ---")
|
|
58
|
+
print(str(result.root))
|
|
59
|
+
print("--- prepare packages ---")
|
|
60
|
+
print("\n".join(result.prepare_pkgs))
|
|
61
|
+
print("--- plan environment ---")
|
|
62
|
+
print("\n".join("{k}={v}" for k,v in result.plan_env))
|
|
63
|
+
for script in result.prepare_scripts:
|
|
64
|
+
print("--- prepare script ---")
|
|
65
|
+
print(script)
|
|
66
|
+
print("----------------------")
|
|
67
|
+
for script in result.finish_scripts:
|
|
68
|
+
print("--- finish script ---")
|
|
69
|
+
print(script)
|
|
70
|
+
print("----------------------")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def add_fmf_options(parser):
|
|
74
|
+
parser.add_argument("--root", help="path to directory with fmf tests", default=".")
|
|
75
|
+
parser.add_argument("--plan", help="plan name (defaults to dummy plan)")
|
|
76
|
+
parser.add_argument(
|
|
77
|
+
"--test", "-t", help="test name regex (replacing 'test' from plan)",
|
|
78
|
+
action="append",
|
|
79
|
+
)
|
|
80
|
+
parser.add_argument(
|
|
81
|
+
"--exclude", help="test name regex (replacing 'exclude' from plan)",
|
|
82
|
+
action="append",
|
|
83
|
+
)
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
"--condition", help="fmf-style python condition",
|
|
86
|
+
action="append",
|
|
87
|
+
)
|
|
88
|
+
parser.add_argument(
|
|
89
|
+
"--filter", help="fmf-style expression filter (replacing 'filter' from plan)",
|
|
90
|
+
action="append",
|
|
91
|
+
)
|
|
92
|
+
parser.add_argument(
|
|
93
|
+
"--context", "-c", help="tmt style key=value context",
|
|
94
|
+
action="append",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def parse_args(parser):
|
|
99
|
+
add_fmf_options(parser)
|
|
100
|
+
|
|
101
|
+
cmds = parser.add_subparsers(
|
|
102
|
+
dest="_cmd", help="fmf feature", metavar="<cmd>", required=True,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
cmds.add_parser(
|
|
106
|
+
"requires", aliases=("req",),
|
|
107
|
+
help="list requires/recommends of the plan and its tests",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
cmds.add_parser(
|
|
111
|
+
"discover", aliases=("di",),
|
|
112
|
+
help="list tests, possibly post-processed by a tmt plan",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
cmds.add_parser(
|
|
116
|
+
"show",
|
|
117
|
+
help="show fmf metadata of test(s)",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
cmds.add_parser(
|
|
121
|
+
"prepare",
|
|
122
|
+
help="show prepare-related details from a plan",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def main(args):
|
|
127
|
+
if args._cmd in ("requires", "req"):
|
|
128
|
+
requires(args)
|
|
129
|
+
elif args._cmd in ("discover", "di"):
|
|
130
|
+
discover(args)
|
|
131
|
+
elif args._cmd == "show":
|
|
132
|
+
show(args)
|
|
133
|
+
elif args._cmd == "prepare":
|
|
134
|
+
prepare(args)
|
|
135
|
+
else:
|
|
136
|
+
raise RuntimeError(f"unknown args: {args}")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
CLI_SPEC = {
|
|
140
|
+
"help": "simple CLI interface to atex.fmf",
|
|
141
|
+
"args": parse_args,
|
|
142
|
+
"main": main,
|
|
143
|
+
}
|
atex/cli/libvirt.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
import libvirt
|
|
5
|
+
|
|
6
|
+
from ..provision.libvirt import locking
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _libvirt_open(url=None):
|
|
10
|
+
# pass no arguments if url is None
|
|
11
|
+
conn = libvirt.open(*((url,) if url else ()))
|
|
12
|
+
print(f"Connected to {conn.getHostname()} via {conn.getURI()}\n")
|
|
13
|
+
return conn
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_locks(args):
|
|
17
|
+
conn = _libvirt_open(args.connect)
|
|
18
|
+
domains = conn.listAllDomains(libvirt.VIR_CONNECT_LIST_DOMAINS_PERSISTENT)
|
|
19
|
+
for domain in sorted(domains, key=lambda d: d.name()):
|
|
20
|
+
print(f"{domain.name()}:")
|
|
21
|
+
for sig, stamp in locking.get_locks(domain, expired=args.expired):
|
|
22
|
+
print(f" {sig} {stamp}")
|
|
23
|
+
print()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def unlock(args):
|
|
27
|
+
conn = _libvirt_open(args.connect)
|
|
28
|
+
dom = conn.lookupByName(args.domain)
|
|
29
|
+
locking.unlock(dom, args.signature)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def lock(args):
|
|
33
|
+
conn = _libvirt_open(args.connect)
|
|
34
|
+
dom = conn.lookupByName(args.domain)
|
|
35
|
+
if locking.lock(dom, args.signature, args.timestamp):
|
|
36
|
+
print("Succeeded.")
|
|
37
|
+
sys.exit(0)
|
|
38
|
+
else:
|
|
39
|
+
print("Failed (already locked).")
|
|
40
|
+
sys.exit(2)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def unlock_all(args):
|
|
44
|
+
conn = _libvirt_open(args.connect)
|
|
45
|
+
if args.domains:
|
|
46
|
+
def domains(dom):
|
|
47
|
+
return bool(re.fullmatch(args.domains, dom.name()))
|
|
48
|
+
else:
|
|
49
|
+
def domains(_):
|
|
50
|
+
return True
|
|
51
|
+
locking.unlock_all(conn, args.signature, args.shutdown, domains)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def cleanup_expired(args):
|
|
55
|
+
conn = _libvirt_open(args.connect)
|
|
56
|
+
if args.domains:
|
|
57
|
+
def domains(dom):
|
|
58
|
+
return bool(re.fullmatch(args.domains, dom.name()))
|
|
59
|
+
else:
|
|
60
|
+
def domains(_):
|
|
61
|
+
return True
|
|
62
|
+
locking.cleanup_expired(conn, args.timestamp, domains)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def parse_args(parser):
|
|
66
|
+
parser.add_argument("--connect", "-c", help="Libvirt URL to connect to", metavar="URL")
|
|
67
|
+
cmds = parser.add_subparsers(
|
|
68
|
+
dest="_cmd", help="libvirt helper to run", metavar="<cmd>", required=True,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
cmd = cmds.add_parser(
|
|
72
|
+
"get-locks",
|
|
73
|
+
help="List all locks (signatures)",
|
|
74
|
+
)
|
|
75
|
+
cmd.add_argument("--expired", help="List also expired locks", action="store_true")
|
|
76
|
+
|
|
77
|
+
cmd = cmds.add_parser(
|
|
78
|
+
"unlock",
|
|
79
|
+
help="Remove a lock signature from a domain",
|
|
80
|
+
)
|
|
81
|
+
cmd.add_argument("domain", help="Domain name")
|
|
82
|
+
cmd.add_argument("signature", help="Lock signature UUID")
|
|
83
|
+
|
|
84
|
+
cmd = cmds.add_parser(
|
|
85
|
+
"lock",
|
|
86
|
+
help="Lock a domain (exit 0:success, 2:fail)",
|
|
87
|
+
)
|
|
88
|
+
cmd.add_argument("domain", help="Domain name")
|
|
89
|
+
cmd.add_argument("signature", help="Lock signature UUID")
|
|
90
|
+
cmd.add_argument("timestamp", help="Expiration time for the lock")
|
|
91
|
+
|
|
92
|
+
cmd = cmds.add_parser(
|
|
93
|
+
"unlock-all",
|
|
94
|
+
help="Remove all lock signatures from all domains",
|
|
95
|
+
)
|
|
96
|
+
cmd.add_argument("--signature", help="Only remove this UUID")
|
|
97
|
+
cmd.add_argument("--shutdown", help="Also destroy the domains", action="store_true")
|
|
98
|
+
cmd.add_argument("--domains", help="Which domains names to impact", metavar="REGEX")
|
|
99
|
+
|
|
100
|
+
cmd = cmds.add_parser(
|
|
101
|
+
"cleanup-expired",
|
|
102
|
+
help="Remove expired lock signatures from all domains",
|
|
103
|
+
)
|
|
104
|
+
cmd.add_argument("--timestamp", help="Check against this instead of UTC now()")
|
|
105
|
+
cmd.add_argument("--domains", help="Which domains names to impact", metavar="REGEX")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def main(args):
|
|
109
|
+
if args._cmd == "get-locks":
|
|
110
|
+
get_locks(args)
|
|
111
|
+
elif args._cmd == "unlock":
|
|
112
|
+
unlock(args)
|
|
113
|
+
elif args._cmd == "lock":
|
|
114
|
+
lock(args)
|
|
115
|
+
elif args._cmd == "unlock-all":
|
|
116
|
+
unlock_all(args)
|
|
117
|
+
elif args._cmd == "cleanup-expired":
|
|
118
|
+
cleanup_expired(args)
|
|
119
|
+
else:
|
|
120
|
+
raise RuntimeError(f"unknown args: {args}")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
CLI_SPEC = {
|
|
124
|
+
"help": "various utils for the Libvirt provisioner",
|
|
125
|
+
"args": parse_args,
|
|
126
|
+
"main": main,
|
|
127
|
+
}
|
atex/cli/testingfarm.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
import json
|
|
2
3
|
import pprint
|
|
3
4
|
|
|
4
5
|
from .. import util
|
|
@@ -49,6 +50,8 @@ def search_requests(args):
|
|
|
49
50
|
reply = api.search_requests(
|
|
50
51
|
state=args.state,
|
|
51
52
|
mine=not args.all,
|
|
53
|
+
user_id=args.user_id,
|
|
54
|
+
token_id=args.token_id,
|
|
52
55
|
ranch=args.ranch,
|
|
53
56
|
created_before=args.before,
|
|
54
57
|
created_after=args.after,
|
|
@@ -56,20 +59,24 @@ def search_requests(args):
|
|
|
56
59
|
if not reply:
|
|
57
60
|
return
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
if args.json:
|
|
63
|
+
for req in sorted(reply, key=lambda x: x["created"]):
|
|
64
|
+
print(json.dumps(req))
|
|
65
|
+
else:
|
|
66
|
+
for req in sorted(reply, key=lambda x: x["created"]):
|
|
67
|
+
req_id = req["id"]
|
|
68
|
+
created = req["created"].partition(".")[0]
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
envs = []
|
|
71
|
+
for env in req["environments_requested"]:
|
|
72
|
+
if "os" in env and env["os"] and "compose" in env["os"]:
|
|
73
|
+
compose = env["os"]["compose"]
|
|
74
|
+
arch = env["arch"]
|
|
75
|
+
if compose and arch:
|
|
76
|
+
envs.append(f"{compose}@{arch}")
|
|
77
|
+
envs_str = ", ".join(envs)
|
|
71
78
|
|
|
72
|
-
|
|
79
|
+
print(f"{created} {req_id} : {envs_str}")
|
|
73
80
|
|
|
74
81
|
|
|
75
82
|
def reserve(args):
|
|
@@ -80,12 +87,19 @@ def reserve(args):
|
|
|
80
87
|
else:
|
|
81
88
|
hardware = None
|
|
82
89
|
|
|
90
|
+
if args.native_test:
|
|
91
|
+
test = tf.DEFAULT_RESERVE_TEST.copy()
|
|
92
|
+
test["name"] = "/plans/testing-farm-native"
|
|
93
|
+
else:
|
|
94
|
+
test = None
|
|
95
|
+
|
|
83
96
|
api = _get_api(args)
|
|
84
97
|
res = tf.Reserve(
|
|
85
98
|
compose=args.compose,
|
|
86
99
|
arch=args.arch,
|
|
87
100
|
timeout=args.timeout,
|
|
88
101
|
hardware=hardware,
|
|
102
|
+
reserve_test=test,
|
|
89
103
|
api=api,
|
|
90
104
|
)
|
|
91
105
|
with res as m:
|
|
@@ -177,9 +191,12 @@ def parse_args(parser):
|
|
|
177
191
|
)
|
|
178
192
|
cmd.add_argument("--state", help="request state (running, etc.)", required=True)
|
|
179
193
|
cmd.add_argument("--all", help="all requests, not just owned by token", action="store_true")
|
|
180
|
-
cmd.add_argument("--ranch", help="Testing Farm ranch")
|
|
194
|
+
cmd.add_argument("--ranch", help="Testing Farm ranch (detected from token)")
|
|
195
|
+
cmd.add_argument("--user-id", help="'user_id' request field (detected from token)")
|
|
196
|
+
cmd.add_argument("--token-id", help="'token_id' request field (detected from token)")
|
|
181
197
|
cmd.add_argument("--before", help="only requests created before ISO8601")
|
|
182
198
|
cmd.add_argument("--after", help="only requests created after ISO8601")
|
|
199
|
+
cmd.add_argument("--json", help="full details, one request per line", action="store_true")
|
|
183
200
|
|
|
184
201
|
cmd = cmds.add_parser(
|
|
185
202
|
"reserve",
|
|
@@ -190,6 +207,11 @@ def parse_args(parser):
|
|
|
190
207
|
cmd.add_argument("--timeout", "-t", help="pipeline timeout (in minutes)", type=int, default=60)
|
|
191
208
|
cmd.add_argument("--ssh-key", help="path to a ssh private key file like 'id_rsa'")
|
|
192
209
|
cmd.add_argument("--hvm", help="request a HVM virtualization capable HW", action="store_true")
|
|
210
|
+
cmd.add_argument(
|
|
211
|
+
"--native-test",
|
|
212
|
+
help="use the default testing farm reserve test",
|
|
213
|
+
action="store_true",
|
|
214
|
+
)
|
|
193
215
|
|
|
194
216
|
cmd = cmds.add_parser(
|
|
195
217
|
"watch-pipeline", aliases=("wp",),
|
atex/connection/__init__.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import importlib as _importlib
|
|
2
2
|
import pkgutil as _pkgutil
|
|
3
|
-
import threading as _threading
|
|
4
3
|
|
|
5
4
|
from .. import util as _util
|
|
6
5
|
|
|
@@ -34,18 +33,21 @@ class Connection:
|
|
|
34
33
|
ie. disconnect() might be called from a different thread while connect()
|
|
35
34
|
or cmd() are still running.
|
|
36
35
|
Similarly, multiple threads may run cmd() or rsync() independently.
|
|
37
|
-
"""
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
TODO: document that any exceptions raised by a Connection should be children
|
|
38
|
+
of ConnectionError
|
|
39
|
+
|
|
40
|
+
If any connection-related error happens, a ConnectionError (or an exception
|
|
41
|
+
derived from it) must be raised.
|
|
42
|
+
"""
|
|
45
43
|
|
|
46
44
|
def __enter__(self):
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
try:
|
|
46
|
+
self.connect()
|
|
47
|
+
return self
|
|
48
|
+
except Exception:
|
|
49
|
+
self.disconnect()
|
|
50
|
+
raise
|
|
49
51
|
|
|
50
52
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
51
53
|
self.disconnect()
|
|
@@ -65,15 +67,7 @@ class Connection:
|
|
|
65
67
|
"""
|
|
66
68
|
raise NotImplementedError(f"'disconnect' not implemented for {self.__class__.__name__}")
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
#def alive(self):
|
|
70
|
-
# """
|
|
71
|
-
# Return True if the connection was established and is active,
|
|
72
|
-
# False otherwise.
|
|
73
|
-
# """
|
|
74
|
-
# raise NotImplementedError(f"'alive' not implemented for {self.__class__.__name__}")
|
|
75
|
-
|
|
76
|
-
def cmd(self, command, func=_util.subprocess_run, **func_args):
|
|
70
|
+
def cmd(self, command, *, func=_util.subprocess_run, **func_args):
|
|
77
71
|
"""
|
|
78
72
|
Execute a single command on the remote, using subprocess-like semantics.
|
|
79
73
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Connection API implementation using the 'podman' CLI client.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
from .. import util
|
|
8
|
+
from . import Connection
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PodmanConnError(ConnectionError):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PodmanConn(Connection):
|
|
16
|
+
"""
|
|
17
|
+
Implements the Connection API via 'podman container exec' on an
|
|
18
|
+
already-running container, it does not handle any image pulling,
|
|
19
|
+
container creation, starting or stopping.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# def __init__(self, container, *, user=None, workdir=None):
|
|
23
|
+
# """
|
|
24
|
+
# 'container' is a string with either the full or partial podman
|
|
25
|
+
# container ID, or a container name, as recognized by podman CLI.
|
|
26
|
+
#
|
|
27
|
+
# 'user' is a string with a username or UID, possibly including a GID,
|
|
28
|
+
# passed to the podman CLI as --user.
|
|
29
|
+
#
|
|
30
|
+
# 'workdir' is a string specifying the CWD inside the container.
|
|
31
|
+
# """
|
|
32
|
+
def __init__(self, container):
|
|
33
|
+
self.container = container
|
|
34
|
+
|
|
35
|
+
def connect(self, block=True):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def disconnect(self):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
# have options as kwarg to be compatible with other functions here
|
|
42
|
+
def cmd(self, command, *, func=util.subprocess_run, **func_args):
|
|
43
|
+
return func(
|
|
44
|
+
("podman", "container", "exec", "-i", self.container, *command),
|
|
45
|
+
skip_frames=1,
|
|
46
|
+
**func_args,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def rsync(self, *args, func=util.subprocess_run, **func_args):
|
|
50
|
+
return func(
|
|
51
|
+
(
|
|
52
|
+
"rsync",
|
|
53
|
+
# use shell to strip off the destination argument rsync passes
|
|
54
|
+
# cmd[0]=/bin/bash cmd[1]=-c cmd[2]=exec podman ... cmd[3]=destination
|
|
55
|
+
# cmd[4]=rsync cmd[5]=--server cmd[6]=-vve.LsfxCIvu cmd[7]=. cmd[8]=.
|
|
56
|
+
"-e", f"/bin/bash -c 'exec podman container exec -i {self.container} \"$@\"'",
|
|
57
|
+
*args,
|
|
58
|
+
),
|
|
59
|
+
skip_frames=1,
|
|
60
|
+
check=True,
|
|
61
|
+
stdin=subprocess.DEVNULL,
|
|
62
|
+
**func_args,
|
|
63
|
+
)
|