codecraftai-interop-vmworkstation 1.2.0__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.
- ccai/interop/vmworkstation/__init__.py +49 -0
- ccai/interop/vmworkstation/aspectsvmworkstation.py +48 -0
- ccai/interop/vmworkstation/exceptions.py +79 -0
- ccai/interop/vmworkstation/guestcredential.py +31 -0
- ccai/interop/vmworkstation/py.typed +0 -0
- ccai/interop/vmworkstation/redaction.py +62 -0
- ccai/interop/vmworkstation/results.py +81 -0
- ccai/interop/vmworkstation/snapshots.py +180 -0
- ccai/interop/vmworkstation/vmrundispatcher.py +171 -0
- ccai/interop/vmworkstation/vmworkstationclient.py +783 -0
- codecraftai_interop_vmworkstation-1.2.0.dist-info/METADATA +90 -0
- codecraftai_interop_vmworkstation-1.2.0.dist-info/RECORD +14 -0
- codecraftai_interop_vmworkstation-1.2.0.dist-info/WHEEL +4 -0
- codecraftai_interop_vmworkstation-1.2.0.dist-info/licenses/LICENSE.md +204 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. module:: vmworkstation
|
|
3
|
+
:platform: Darwin, Linux, Unix, Windows
|
|
4
|
+
:synopsis: Public exports for the VMware Workstation interop client.
|
|
5
|
+
|
|
6
|
+
.. moduleauthor:: Myron Walker <myron.walker@gmail.com>
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__author__ = "Myron Walker"
|
|
10
|
+
__copyright__ = "Copyright 2026, Code Craft AI LLC"
|
|
11
|
+
__credits__ = []
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from ccai.interop.vmworkstation.aspectsvmworkstation import (
|
|
15
|
+
AspectsVMWorkstation,
|
|
16
|
+
DEFAULT_VMWORKSTATION_ASPECTS,
|
|
17
|
+
)
|
|
18
|
+
from ccai.interop.vmworkstation.exceptions import (
|
|
19
|
+
VMWorkstationError,
|
|
20
|
+
VMRunCommandError,
|
|
21
|
+
VMWorkstationParseError,
|
|
22
|
+
)
|
|
23
|
+
from ccai.interop.vmworkstation.guestcredential import GuestCredential
|
|
24
|
+
from ccai.interop.vmworkstation.redaction import redact_argv
|
|
25
|
+
from ccai.interop.vmworkstation.results import (
|
|
26
|
+
GuestExecResult,
|
|
27
|
+
ToolsState,
|
|
28
|
+
VMWorkstationActionResult,
|
|
29
|
+
VMWorkstationActionStatus,
|
|
30
|
+
)
|
|
31
|
+
from ccai.interop.vmworkstation.snapshots import SnapshotRecord
|
|
32
|
+
from ccai.interop.vmworkstation.vmworkstationclient import VMWorkstationClient
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"AspectsVMWorkstation",
|
|
37
|
+
"DEFAULT_VMWORKSTATION_ASPECTS",
|
|
38
|
+
"GuestCredential",
|
|
39
|
+
"GuestExecResult",
|
|
40
|
+
"redact_argv",
|
|
41
|
+
"SnapshotRecord",
|
|
42
|
+
"ToolsState",
|
|
43
|
+
"VMRunCommandError",
|
|
44
|
+
"VMWorkstationActionResult",
|
|
45
|
+
"VMWorkstationActionStatus",
|
|
46
|
+
"VMWorkstationClient",
|
|
47
|
+
"VMWorkstationError",
|
|
48
|
+
"VMWorkstationParseError",
|
|
49
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. module:: aspectsvmworkstation
|
|
3
|
+
:platform: Darwin, Linux, Unix, Windows
|
|
4
|
+
:synopsis: Behaviour-modifying parameters for the VMware Workstation
|
|
5
|
+
interop client.
|
|
6
|
+
|
|
7
|
+
.. moduleauthor:: Myron Walker <myron.walker@gmail.com>
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__author__ = "Myron Walker"
|
|
11
|
+
__copyright__ = "Copyright 2026, Code Craft AI LLC"
|
|
12
|
+
__credits__ = []
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
from ccai.aspects.aspects import Aspects
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class AspectsVMWorkstation(Aspects):
|
|
23
|
+
"""
|
|
24
|
+
Workstation-specific behaviour carrier.
|
|
25
|
+
|
|
26
|
+
Inherits the generic completion / timeout / attempt fields from
|
|
27
|
+
:class:`Aspects` and adds the small set of behaviour controls the
|
|
28
|
+
``vmrun`` dispatcher honours.
|
|
29
|
+
|
|
30
|
+
:param elevated: When True, the runner is invoked with ``elevated``.
|
|
31
|
+
:param raise_on_failure: When True, non-zero ``vmrun`` exits raise a
|
|
32
|
+
:class:`VMRunCommandError` instead of returning a ``FAILED``
|
|
33
|
+
result.
|
|
34
|
+
:param command_timeout: Per-invocation timeout passed through to the
|
|
35
|
+
runner. ``None`` means no enforced timeout at the runner.
|
|
36
|
+
"""
|
|
37
|
+
elevated: bool = False
|
|
38
|
+
raise_on_failure: bool = False
|
|
39
|
+
command_timeout: Optional[float] = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
DEFAULT_VMWORKSTATION_ASPECTS = AspectsVMWorkstation()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"AspectsVMWorkstation",
|
|
47
|
+
"DEFAULT_VMWORKSTATION_ASPECTS",
|
|
48
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. module:: exceptions
|
|
3
|
+
:platform: Darwin, Linux, Unix, Windows
|
|
4
|
+
:synopsis: Typed exceptions raised by the VMware Workstation interop
|
|
5
|
+
client.
|
|
6
|
+
|
|
7
|
+
.. moduleauthor:: Myron Walker <myron.walker@gmail.com>
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__author__ = "Myron Walker"
|
|
11
|
+
__copyright__ = "Copyright 2026, Code Craft AI LLC"
|
|
12
|
+
__credits__ = []
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class VMWorkstationError(Exception):
|
|
19
|
+
"""
|
|
20
|
+
Base class for all VMware Workstation interop errors.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class VMRunCommandError(VMWorkstationError):
|
|
25
|
+
"""
|
|
26
|
+
Raised when a ``vmrun`` invocation exits non-zero and the caller has
|
|
27
|
+
opted into exception-on-failure behaviour.
|
|
28
|
+
|
|
29
|
+
:param message: Human-readable failure description.
|
|
30
|
+
:param exit_code: The ``vmrun`` exit code, when available.
|
|
31
|
+
:param stdout: Captured stdout, when available.
|
|
32
|
+
:param stderr: Captured stderr, when available.
|
|
33
|
+
:param command: The argv that was dispatched, when available.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
*,
|
|
39
|
+
message: str,
|
|
40
|
+
exit_code: Optional[int],
|
|
41
|
+
stdout: Optional[str],
|
|
42
|
+
stderr: Optional[str],
|
|
43
|
+
command: Optional[list],
|
|
44
|
+
) -> None:
|
|
45
|
+
super().__init__(message)
|
|
46
|
+
self.message = message
|
|
47
|
+
self.exit_code = exit_code
|
|
48
|
+
self.stdout = stdout
|
|
49
|
+
self.stderr = stderr
|
|
50
|
+
self.command = command
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class VMWorkstationParseError(VMWorkstationError):
|
|
55
|
+
"""
|
|
56
|
+
Raised when ``vmrun`` output cannot be parsed into the expected
|
|
57
|
+
structure.
|
|
58
|
+
|
|
59
|
+
:param message: Human-readable description of the parse failure.
|
|
60
|
+
:param raw_output: The raw output that could not be parsed.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
*,
|
|
66
|
+
message: str,
|
|
67
|
+
raw_output: Optional[str],
|
|
68
|
+
) -> None:
|
|
69
|
+
super().__init__(message)
|
|
70
|
+
self.message = message
|
|
71
|
+
self.raw_output = raw_output
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
"VMRunCommandError",
|
|
77
|
+
"VMWorkstationError",
|
|
78
|
+
"VMWorkstationParseError",
|
|
79
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. module:: guestcredential
|
|
3
|
+
:platform: Darwin, Linux, Unix, Windows
|
|
4
|
+
:synopsis: Guest credential carrier for ``vmrun`` guest-exec verbs.
|
|
5
|
+
|
|
6
|
+
.. moduleauthor:: Myron Walker <myron.walker@gmail.com>
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__author__ = "Myron Walker"
|
|
10
|
+
__copyright__ = "Copyright 2026, Code Craft AI LLC"
|
|
11
|
+
__credits__ = []
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class GuestCredential:
|
|
19
|
+
"""
|
|
20
|
+
Username/password pair passed to ``vmrun -gu/-gp`` for guest-exec
|
|
21
|
+
verbs (``runProgramInGuest``, ``copyFileFromHostToGuest``,
|
|
22
|
+
``copyFileFromGuestToHost``).
|
|
23
|
+
|
|
24
|
+
:param username: Guest OS username.
|
|
25
|
+
:param password: Guest OS password.
|
|
26
|
+
"""
|
|
27
|
+
username: str
|
|
28
|
+
password: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__all__ = ["GuestCredential"]
|
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. module:: redaction
|
|
3
|
+
:platform: Darwin, Linux, Unix, Windows
|
|
4
|
+
:synopsis: Argv redaction helpers for the VMware Workstation interop
|
|
5
|
+
client.
|
|
6
|
+
|
|
7
|
+
The ``vmrun`` guest-exec subcommands accept the guest password on the
|
|
8
|
+
command line via the ``-gp`` flag. The real argv must carry the
|
|
9
|
+
plaintext password so the command can actually execute, but any copy of
|
|
10
|
+
that argv that is stored on a result object, embedded in an exception,
|
|
11
|
+
or written to a log must have the secret scrubbed.
|
|
12
|
+
|
|
13
|
+
.. moduleauthor:: Myron Walker <myron.walker@gmail.com>
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__author__ = "Myron Walker"
|
|
17
|
+
__copyright__ = "Copyright 2026, Code Craft AI LLC"
|
|
18
|
+
__credits__ = []
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
from typing import List, Optional
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_REDACTED_PLACEHOLDER = "<redacted>"
|
|
25
|
+
_SECRET_FLAGS = ("-gp",)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def redact_argv(*, argv: Optional[List[str]]) -> Optional[List[str]]:
|
|
29
|
+
"""
|
|
30
|
+
Return a copy of ``argv`` with the value following any secret flag
|
|
31
|
+
(currently ``-gp``, the guest password) replaced by a redaction
|
|
32
|
+
placeholder.
|
|
33
|
+
|
|
34
|
+
The original list is never mutated; the real argv used for dispatch
|
|
35
|
+
is left intact.
|
|
36
|
+
|
|
37
|
+
:param argv: The argv to redact, or ``None``.
|
|
38
|
+
|
|
39
|
+
:returns: A redacted copy of ``argv``, or ``None`` when ``argv`` is
|
|
40
|
+
``None``.
|
|
41
|
+
"""
|
|
42
|
+
if argv is None:
|
|
43
|
+
rtnval: Optional[List[str]] = None
|
|
44
|
+
return rtnval
|
|
45
|
+
|
|
46
|
+
redacted: List[str] = []
|
|
47
|
+
redact_next = False
|
|
48
|
+
for token in argv:
|
|
49
|
+
if redact_next is True:
|
|
50
|
+
redacted.append(_REDACTED_PLACEHOLDER)
|
|
51
|
+
redact_next = False
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
redacted.append(token)
|
|
55
|
+
if token in _SECRET_FLAGS:
|
|
56
|
+
redact_next = True
|
|
57
|
+
|
|
58
|
+
rtnval = redacted
|
|
59
|
+
return rtnval
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
__all__ = ["redact_argv"]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. module:: results
|
|
3
|
+
:platform: Darwin, Linux, Unix, Windows
|
|
4
|
+
:synopsis: Result types for the VMware Workstation interop client.
|
|
5
|
+
|
|
6
|
+
.. moduleauthor:: Myron Walker <myron.walker@gmail.com>
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__author__ = "Myron Walker"
|
|
10
|
+
__copyright__ = "Copyright 2026, Code Craft AI LLC"
|
|
11
|
+
__credits__ = []
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from typing import List, Optional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class VMWorkstationActionStatus(Enum):
|
|
20
|
+
"""
|
|
21
|
+
Status enumeration for ``vmrun`` action results.
|
|
22
|
+
"""
|
|
23
|
+
SUCCESS = "success"
|
|
24
|
+
FAILED = "failed"
|
|
25
|
+
DRY_RUN = "dry_run"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ToolsState(Enum):
|
|
29
|
+
"""
|
|
30
|
+
VMware Tools state as reported by ``vmrun checkToolsState``.
|
|
31
|
+
"""
|
|
32
|
+
INSTALLED = "installed"
|
|
33
|
+
RUNNING = "running"
|
|
34
|
+
NOT_INSTALLED = "not_installed"
|
|
35
|
+
UNKNOWN = "unknown"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class VMWorkstationActionResult:
|
|
40
|
+
"""
|
|
41
|
+
Result of a ``vmrun`` action invocation.
|
|
42
|
+
|
|
43
|
+
:param status: The outcome status.
|
|
44
|
+
:param detail: Free-form description of the action context.
|
|
45
|
+
:param exit_code: The ``vmrun`` exit code, when available.
|
|
46
|
+
:param stdout: Captured stdout, when available.
|
|
47
|
+
:param stderr: Captured stderr, when available.
|
|
48
|
+
:param command: The argv that was dispatched, when available.
|
|
49
|
+
"""
|
|
50
|
+
status: VMWorkstationActionStatus
|
|
51
|
+
detail: Optional[str]
|
|
52
|
+
exit_code: Optional[int]
|
|
53
|
+
stdout: Optional[str]
|
|
54
|
+
stderr: Optional[str]
|
|
55
|
+
command: Optional[List[str]]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class GuestExecResult:
|
|
60
|
+
"""
|
|
61
|
+
Result of a guest-exec ``vmrun`` invocation.
|
|
62
|
+
|
|
63
|
+
:param status: The outcome status.
|
|
64
|
+
:param exit_code: The ``vmrun`` exit code, when available.
|
|
65
|
+
:param stdout: Captured stdout, when available.
|
|
66
|
+
:param stderr: Captured stderr, when available.
|
|
67
|
+
:param command: The argv that was dispatched, when available.
|
|
68
|
+
"""
|
|
69
|
+
status: VMWorkstationActionStatus
|
|
70
|
+
exit_code: Optional[int]
|
|
71
|
+
stdout: Optional[str]
|
|
72
|
+
stderr: Optional[str]
|
|
73
|
+
command: Optional[List[str]]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
__all__ = [
|
|
77
|
+
"GuestExecResult",
|
|
78
|
+
"ToolsState",
|
|
79
|
+
"VMWorkstationActionResult",
|
|
80
|
+
"VMWorkstationActionStatus",
|
|
81
|
+
]
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. module:: snapshots
|
|
3
|
+
:platform: Darwin, Linux, Unix, Windows
|
|
4
|
+
:synopsis: ``vmrun listSnapshots`` parsing and the
|
|
5
|
+
:class:`SnapshotRecord` data class.
|
|
6
|
+
|
|
7
|
+
.. moduleauthor:: Myron Walker <myron.walker@gmail.com>
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__author__ = "Myron Walker"
|
|
11
|
+
__copyright__ = "Copyright 2026, Code Craft AI LLC"
|
|
12
|
+
__credits__ = []
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import List, Optional
|
|
19
|
+
|
|
20
|
+
from ccai.interop.vmworkstation.exceptions import VMWorkstationParseError
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class SnapshotRecord:
|
|
25
|
+
"""
|
|
26
|
+
Record describing one snapshot reported by ``vmrun listSnapshots``.
|
|
27
|
+
|
|
28
|
+
:param name: The snapshot name (as printed by ``vmrun``).
|
|
29
|
+
:param parent: The parent snapshot name, or ``None`` for top-level.
|
|
30
|
+
"""
|
|
31
|
+
name: str
|
|
32
|
+
parent: Optional[str]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def parse_list_snapshots(*, payload: str) -> List[SnapshotRecord]:
|
|
36
|
+
"""
|
|
37
|
+
Parse the stdout of ``vmrun listSnapshots <vmx>``.
|
|
38
|
+
|
|
39
|
+
``vmrun listSnapshots`` typically prints::
|
|
40
|
+
|
|
41
|
+
Total snapshots: <n>
|
|
42
|
+
<name1>
|
|
43
|
+
<name2>
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
Snapshot trees are presented as flat names; parent linkage is not
|
|
47
|
+
available from the ``listSnapshots`` plain form, so :attr:`parent`
|
|
48
|
+
is always ``None``.
|
|
49
|
+
|
|
50
|
+
:param payload: The decoded stdout from ``vmrun``.
|
|
51
|
+
|
|
52
|
+
:returns: A list of :class:`SnapshotRecord`, possibly empty.
|
|
53
|
+
:raises VMWorkstationParseError: When the header line cannot be
|
|
54
|
+
located or the declared count does not match the body.
|
|
55
|
+
"""
|
|
56
|
+
if payload is None:
|
|
57
|
+
text = ""
|
|
58
|
+
else:
|
|
59
|
+
text = payload
|
|
60
|
+
|
|
61
|
+
raw_lines = text.splitlines()
|
|
62
|
+
stripped_lines: List[str] = []
|
|
63
|
+
for raw in raw_lines:
|
|
64
|
+
stripped = raw.strip()
|
|
65
|
+
if len(stripped) > 0:
|
|
66
|
+
stripped_lines.append(stripped)
|
|
67
|
+
|
|
68
|
+
if len(stripped_lines) == 0:
|
|
69
|
+
rtnval: List[SnapshotRecord] = []
|
|
70
|
+
return rtnval
|
|
71
|
+
|
|
72
|
+
header = stripped_lines[0]
|
|
73
|
+
lower_header = header.lower()
|
|
74
|
+
if lower_header.startswith("total snapshots:") is False:
|
|
75
|
+
err_msg_lines = ["Unexpected 'vmrun listSnapshots' header."]
|
|
76
|
+
err_msg_lines.append(" HEADER: {!r}".format(header))
|
|
77
|
+
err_msg = os.linesep.join(err_msg_lines)
|
|
78
|
+
raise VMWorkstationParseError(message=err_msg, raw_output=text)
|
|
79
|
+
|
|
80
|
+
count_text = header.split(":", 1)[1].strip()
|
|
81
|
+
try:
|
|
82
|
+
declared_count = int(count_text)
|
|
83
|
+
except ValueError:
|
|
84
|
+
err_msg_lines = ["Unable to parse snapshot count."]
|
|
85
|
+
err_msg_lines.append(" COUNT_TEXT: {!r}".format(count_text))
|
|
86
|
+
err_msg = os.linesep.join(err_msg_lines)
|
|
87
|
+
raise VMWorkstationParseError(
|
|
88
|
+
message=err_msg, raw_output=text,
|
|
89
|
+
) from None
|
|
90
|
+
|
|
91
|
+
body_lines = stripped_lines[1:]
|
|
92
|
+
if len(body_lines) != declared_count:
|
|
93
|
+
err_msg_lines = [
|
|
94
|
+
"Snapshot count mismatch between header and body.",
|
|
95
|
+
]
|
|
96
|
+
err_msg_lines.append(" DECLARED: {}".format(declared_count))
|
|
97
|
+
err_msg_lines.append(" ACTUAL: {}".format(len(body_lines)))
|
|
98
|
+
err_msg = os.linesep.join(err_msg_lines)
|
|
99
|
+
raise VMWorkstationParseError(message=err_msg, raw_output=text)
|
|
100
|
+
|
|
101
|
+
records: List[SnapshotRecord] = []
|
|
102
|
+
for body in body_lines:
|
|
103
|
+
record = SnapshotRecord(name=body, parent=None)
|
|
104
|
+
records.append(record)
|
|
105
|
+
|
|
106
|
+
rtnval = records
|
|
107
|
+
return rtnval
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def parse_list_running(*, payload: str) -> List[str]:
|
|
111
|
+
"""
|
|
112
|
+
Parse the stdout of ``vmrun list``.
|
|
113
|
+
|
|
114
|
+
``vmrun list`` prints::
|
|
115
|
+
|
|
116
|
+
Total running VMs: <n>
|
|
117
|
+
<vmx_path_1>
|
|
118
|
+
<vmx_path_2>
|
|
119
|
+
...
|
|
120
|
+
|
|
121
|
+
:param payload: The decoded stdout from ``vmrun``.
|
|
122
|
+
|
|
123
|
+
:returns: A list of VMX paths, possibly empty.
|
|
124
|
+
:raises VMWorkstationParseError: When the header line cannot be
|
|
125
|
+
located or the declared count does not match the body.
|
|
126
|
+
"""
|
|
127
|
+
if payload is None:
|
|
128
|
+
text = ""
|
|
129
|
+
else:
|
|
130
|
+
text = payload
|
|
131
|
+
|
|
132
|
+
raw_lines = text.splitlines()
|
|
133
|
+
stripped_lines: List[str] = []
|
|
134
|
+
for raw in raw_lines:
|
|
135
|
+
stripped = raw.strip()
|
|
136
|
+
if len(stripped) > 0:
|
|
137
|
+
stripped_lines.append(stripped)
|
|
138
|
+
|
|
139
|
+
if len(stripped_lines) == 0:
|
|
140
|
+
rtnval: List[str] = []
|
|
141
|
+
return rtnval
|
|
142
|
+
|
|
143
|
+
header = stripped_lines[0]
|
|
144
|
+
lower_header = header.lower()
|
|
145
|
+
if lower_header.startswith("total running vms:") is False:
|
|
146
|
+
err_msg_lines = ["Unexpected 'vmrun list' header."]
|
|
147
|
+
err_msg_lines.append(" HEADER: {!r}".format(header))
|
|
148
|
+
err_msg = os.linesep.join(err_msg_lines)
|
|
149
|
+
raise VMWorkstationParseError(message=err_msg, raw_output=text)
|
|
150
|
+
|
|
151
|
+
count_text = header.split(":", 1)[1].strip()
|
|
152
|
+
try:
|
|
153
|
+
declared_count = int(count_text)
|
|
154
|
+
except ValueError:
|
|
155
|
+
err_msg_lines = ["Unable to parse running VM count."]
|
|
156
|
+
err_msg_lines.append(" COUNT_TEXT: {!r}".format(count_text))
|
|
157
|
+
err_msg = os.linesep.join(err_msg_lines)
|
|
158
|
+
raise VMWorkstationParseError(
|
|
159
|
+
message=err_msg, raw_output=text,
|
|
160
|
+
) from None
|
|
161
|
+
|
|
162
|
+
body_lines = stripped_lines[1:]
|
|
163
|
+
if len(body_lines) != declared_count:
|
|
164
|
+
err_msg_lines = [
|
|
165
|
+
"Running VM count mismatch between header and body.",
|
|
166
|
+
]
|
|
167
|
+
err_msg_lines.append(" DECLARED: {}".format(declared_count))
|
|
168
|
+
err_msg_lines.append(" ACTUAL: {}".format(len(body_lines)))
|
|
169
|
+
err_msg = os.linesep.join(err_msg_lines)
|
|
170
|
+
raise VMWorkstationParseError(message=err_msg, raw_output=text)
|
|
171
|
+
|
|
172
|
+
rtnval = list(body_lines)
|
|
173
|
+
return rtnval
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
__all__ = [
|
|
177
|
+
"SnapshotRecord",
|
|
178
|
+
"parse_list_running",
|
|
179
|
+
"parse_list_snapshots",
|
|
180
|
+
]
|