holoscan-cli 2.9.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.
- holoscan_cli/__init__.py +35 -0
- holoscan_cli/__main__.py +164 -0
- holoscan_cli/common/argparse_types.py +156 -0
- holoscan_cli/common/artifact_sources.py +160 -0
- holoscan_cli/common/constants.py +119 -0
- holoscan_cli/common/dockerutils.py +521 -0
- holoscan_cli/common/enum_types.py +49 -0
- holoscan_cli/common/exceptions.py +126 -0
- holoscan_cli/common/sdk_utils.py +195 -0
- holoscan_cli/common/utils.py +137 -0
- holoscan_cli/logging.json +37 -0
- holoscan_cli/nics/__init__.py +15 -0
- holoscan_cli/nics/nics.py +33 -0
- holoscan_cli/package-source.json +32 -0
- holoscan_cli/packager/__init__.py +15 -0
- holoscan_cli/packager/arguments.py +148 -0
- holoscan_cli/packager/config_reader.py +180 -0
- holoscan_cli/packager/container_builder.py +426 -0
- holoscan_cli/packager/manifest_files.py +217 -0
- holoscan_cli/packager/models.py +90 -0
- holoscan_cli/packager/package_command.py +197 -0
- holoscan_cli/packager/packager.py +124 -0
- holoscan_cli/packager/parameters.py +603 -0
- holoscan_cli/packager/platforms.py +426 -0
- holoscan_cli/packager/templates/Dockerfile.jinja2 +479 -0
- holoscan_cli/packager/templates/dockerignore +92 -0
- holoscan_cli/packager/templates/tools.sh +414 -0
- holoscan_cli/py.typed +14 -0
- holoscan_cli/runner/__init__.py +15 -0
- holoscan_cli/runner/resources.py +185 -0
- holoscan_cli/runner/run_command.py +207 -0
- holoscan_cli/runner/runner.py +340 -0
- holoscan_cli/version/__init__.py +15 -0
- holoscan_cli/version/version.py +53 -0
- holoscan_cli-2.9.0.dist-info/LICENSE +201 -0
- holoscan_cli-2.9.0.dist-info/METADATA +102 -0
- holoscan_cli-2.9.0.dist-info/RECORD +39 -0
- holoscan_cli-2.9.0.dist-info/WHEEL +4 -0
- holoscan_cli-2.9.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
from argparse import ArgumentParser, HelpFormatter, _SubParsersAction
|
|
18
|
+
|
|
19
|
+
from ..common import argparse_types
|
|
20
|
+
from ..common.argparse_types import valid_existing_path
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("runner")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_run_parser(
|
|
26
|
+
subparser: _SubParsersAction, command: str, parents: list[ArgumentParser]
|
|
27
|
+
) -> ArgumentParser:
|
|
28
|
+
parser: ArgumentParser = subparser.add_parser(
|
|
29
|
+
command, formatter_class=HelpFormatter, parents=parents, add_help=False
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
parser.add_argument("map", metavar="<image[:tag]>", help="HAP/MAP image name.")
|
|
33
|
+
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"--address",
|
|
36
|
+
dest="address",
|
|
37
|
+
help="address ('[<IP or hostname>][:<port>]') of the App Driver. If not specified, "
|
|
38
|
+
"the App Driver uses the default host address ('0.0.0.0') with the default port "
|
|
39
|
+
"number ('8765').",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--driver",
|
|
44
|
+
dest="driver",
|
|
45
|
+
action="store_true",
|
|
46
|
+
default=False,
|
|
47
|
+
help="run the App Driver on the current machine. Can be used together with the "
|
|
48
|
+
"'--worker' option "
|
|
49
|
+
"to run both the App Driver and the App Worker on the same machine.",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"-i",
|
|
54
|
+
"--input",
|
|
55
|
+
metavar="<input>",
|
|
56
|
+
type=argparse_types.valid_dir_path,
|
|
57
|
+
help="input data directory path.",
|
|
58
|
+
required=False,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
"-o",
|
|
63
|
+
"--output",
|
|
64
|
+
metavar="<output>",
|
|
65
|
+
type=argparse_types.valid_dir_path,
|
|
66
|
+
help="output data directory path.",
|
|
67
|
+
required=False,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
parser.add_argument(
|
|
71
|
+
"-f",
|
|
72
|
+
"--fragments",
|
|
73
|
+
dest="fragments",
|
|
74
|
+
help="comma-separated names of the fragments to be executed by the App Worker. "
|
|
75
|
+
"If not specified, only one fragment (selected by the App Driver) will be executed. "
|
|
76
|
+
"'all' can be used to run all the fragments.",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--worker",
|
|
81
|
+
dest="worker",
|
|
82
|
+
action="store_true",
|
|
83
|
+
default=False,
|
|
84
|
+
help="run the App Worker.",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
"--worker-address",
|
|
89
|
+
dest="worker_address",
|
|
90
|
+
help="address (`[<IP or hostname>][:<port>]`) of the App Worker. If not specified, the App "
|
|
91
|
+
"Worker uses the default host address ('0.0.0.0') with a randomly chosen port number "
|
|
92
|
+
"between 10000 and 32767 that is not currently in use.",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
advanced_group = parser.add_argument_group(title="advanced run options")
|
|
96
|
+
|
|
97
|
+
advanced_group.add_argument(
|
|
98
|
+
"--config",
|
|
99
|
+
dest="config",
|
|
100
|
+
type=valid_existing_path,
|
|
101
|
+
help="path to the configuration file. This will override the configuration file embedded "
|
|
102
|
+
"in the application.",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
advanced_group.add_argument(
|
|
106
|
+
"--name",
|
|
107
|
+
dest="name",
|
|
108
|
+
help="name and hostname of the container to create.",
|
|
109
|
+
)
|
|
110
|
+
advanced_group.add_argument(
|
|
111
|
+
"--health-check",
|
|
112
|
+
dest="health_check",
|
|
113
|
+
action="store_true",
|
|
114
|
+
default="False",
|
|
115
|
+
help="enable the health check service for a distributed application. (default: False)",
|
|
116
|
+
)
|
|
117
|
+
advanced_group.add_argument(
|
|
118
|
+
"-n",
|
|
119
|
+
"--network",
|
|
120
|
+
dest="network",
|
|
121
|
+
default="host",
|
|
122
|
+
help="name of the Docker network this application will be connected to. (default: host)",
|
|
123
|
+
)
|
|
124
|
+
advanced_group.add_argument(
|
|
125
|
+
"--nic",
|
|
126
|
+
dest="nic",
|
|
127
|
+
help="name of the network interface to use with a distributed multi-fragment application. "
|
|
128
|
+
"This option sets UCX_NET_DEVICES environment variable with the value specified.",
|
|
129
|
+
)
|
|
130
|
+
advanced_group.add_argument(
|
|
131
|
+
"--use-all-nics",
|
|
132
|
+
dest="use_all_nics",
|
|
133
|
+
action="store_true",
|
|
134
|
+
default=False,
|
|
135
|
+
help="allow UCX to control the selection of network interface cards for data "
|
|
136
|
+
"transfer. Otherwise, the network interface card specified with '--nic' is used. "
|
|
137
|
+
"This option sets UCX_CM_USE_ALL_DEVICES to 'y'. (default: False)",
|
|
138
|
+
)
|
|
139
|
+
advanced_group.add_argument(
|
|
140
|
+
"-r",
|
|
141
|
+
"--render",
|
|
142
|
+
dest="render",
|
|
143
|
+
action="store_true",
|
|
144
|
+
default=False,
|
|
145
|
+
help="enable rendering (default: False); runs the container with required flags to enable "
|
|
146
|
+
"rendering of graphics.",
|
|
147
|
+
)
|
|
148
|
+
advanced_group.add_argument(
|
|
149
|
+
"-q",
|
|
150
|
+
"--quiet",
|
|
151
|
+
dest="quiet",
|
|
152
|
+
action="store_true",
|
|
153
|
+
default=False,
|
|
154
|
+
help="suppress the STDOUT and print only STDERR from the application. (default: False)",
|
|
155
|
+
)
|
|
156
|
+
advanced_group.add_argument(
|
|
157
|
+
"--shm-size",
|
|
158
|
+
dest="shm_size",
|
|
159
|
+
help="set the size of /dev/shm. The format is "
|
|
160
|
+
"<number(int,float)>[MB|m|GB|g|Mi|MiB|Gi|GiB]. "
|
|
161
|
+
"Use 'config' to read the shared memory value defined in the app.json manifest. "
|
|
162
|
+
"If not specified, the container is launched using '--ipc=host' with host system's "
|
|
163
|
+
"/dev/shm mounted.",
|
|
164
|
+
)
|
|
165
|
+
advanced_group.add_argument(
|
|
166
|
+
"--terminal",
|
|
167
|
+
dest="terminal",
|
|
168
|
+
action="store_true",
|
|
169
|
+
default=False,
|
|
170
|
+
help="enter terminal with all configured volume mappings and environment variables. "
|
|
171
|
+
"(default: False)",
|
|
172
|
+
)
|
|
173
|
+
advanced_group.add_argument(
|
|
174
|
+
"--device",
|
|
175
|
+
dest="device",
|
|
176
|
+
nargs="+",
|
|
177
|
+
action="extend",
|
|
178
|
+
help="""Mount additional devices.
|
|
179
|
+
For example:
|
|
180
|
+
--device ajantv* to mount all AJA capture cards.
|
|
181
|
+
--device ajantv0 ajantv1 to mount AJA capture card 0 and 1.
|
|
182
|
+
--device video1 to mount V4L2 video device 1. """,
|
|
183
|
+
)
|
|
184
|
+
advanced_group.add_argument(
|
|
185
|
+
"--gpus",
|
|
186
|
+
dest="gpus",
|
|
187
|
+
help="""Override the value of NVIDIA_VISIBLE_DEVICES environment variable.
|
|
188
|
+
default: the value specified in the package manifest file or 'all' if not specified.
|
|
189
|
+
Refer to https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/docker-specialized.html#gpu-enumeration
|
|
190
|
+
for all available options.""",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
user_group = parser.add_argument_group(title="security options")
|
|
194
|
+
user_group.add_argument(
|
|
195
|
+
"--uid",
|
|
196
|
+
type=str,
|
|
197
|
+
default=os.getuid(),
|
|
198
|
+
help=f"run the container with the UID. (default:{os.getuid()})",
|
|
199
|
+
)
|
|
200
|
+
user_group.add_argument(
|
|
201
|
+
"--gid",
|
|
202
|
+
type=str,
|
|
203
|
+
default=os.getgid(),
|
|
204
|
+
help=f"run the container with the GID. (default:{os.getgid()})",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return parser
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import shutil
|
|
20
|
+
import sys
|
|
21
|
+
import tempfile
|
|
22
|
+
from argparse import Namespace
|
|
23
|
+
from glob import glob
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
from ..common.dockerutils import create_or_use_network, docker_run, image_exists
|
|
28
|
+
from ..common.exceptions import ManifestReadError, UnmatchedDeviceError
|
|
29
|
+
from ..common.utils import (
|
|
30
|
+
compare_versions,
|
|
31
|
+
get_requested_gpus,
|
|
32
|
+
print_manifest_json,
|
|
33
|
+
run_cmd,
|
|
34
|
+
run_cmd_output,
|
|
35
|
+
)
|
|
36
|
+
from .resources import get_shared_memory_size
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger("runner")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _fetch_map_manifest(map_name: str) -> tuple[dict, dict]:
|
|
42
|
+
"""
|
|
43
|
+
Execute HAP/MAP and fetch the manifest files.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
map_name: HAP/MAP image name.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
app_info: application manifest as a python dict
|
|
50
|
+
pkg_info: package manifest as a python dict
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def _ensure_valid_exit_code(returncode: int) -> None:
|
|
54
|
+
if returncode != 0:
|
|
55
|
+
raise ManifestReadError("Error reading manifest file from the package.")
|
|
56
|
+
|
|
57
|
+
logger.info("Reading HAP/MAP manifest...")
|
|
58
|
+
|
|
59
|
+
with tempfile.TemporaryDirectory() as info_dir:
|
|
60
|
+
docker_id = run_cmd_output(["docker", "create", map_name]).strip()
|
|
61
|
+
returncode = run_cmd(
|
|
62
|
+
[
|
|
63
|
+
"docker",
|
|
64
|
+
"cp",
|
|
65
|
+
f"{docker_id}:/etc/holoscan/app.json",
|
|
66
|
+
f"{info_dir}/app.json",
|
|
67
|
+
]
|
|
68
|
+
)
|
|
69
|
+
_ensure_valid_exit_code(returncode)
|
|
70
|
+
returncode = run_cmd(
|
|
71
|
+
[
|
|
72
|
+
"docker",
|
|
73
|
+
"cp",
|
|
74
|
+
f"{docker_id}:/etc/holoscan/pkg.json",
|
|
75
|
+
f"{info_dir}/pkg.json",
|
|
76
|
+
]
|
|
77
|
+
)
|
|
78
|
+
_ensure_valid_exit_code(returncode)
|
|
79
|
+
returncode = run_cmd(["docker", "rm", "-v", docker_id])
|
|
80
|
+
_ensure_valid_exit_code(returncode)
|
|
81
|
+
|
|
82
|
+
app_json = Path(f"{info_dir}/app.json")
|
|
83
|
+
pkg_json = Path(f"{info_dir}/pkg.json")
|
|
84
|
+
|
|
85
|
+
app_info = json.loads(app_json.read_text())
|
|
86
|
+
pkg_info = json.loads(pkg_json.read_text())
|
|
87
|
+
|
|
88
|
+
print_manifest_json(app_info, "app.json")
|
|
89
|
+
print_manifest_json(pkg_info, "pkg.json")
|
|
90
|
+
|
|
91
|
+
return app_info, pkg_info
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _run_app(args: Namespace, app_info: dict, pkg_info: dict):
|
|
95
|
+
"""
|
|
96
|
+
Executes the Holoscan Application Package.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
args: user arguments
|
|
100
|
+
app_info: application manifest dictionary
|
|
101
|
+
pkg_info: package manifest dictionary
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
returncode: command returncode
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
map_name: str = args.map
|
|
108
|
+
input_path: Path = args.input
|
|
109
|
+
output_path: Path = args.output
|
|
110
|
+
quiet: bool = args.quiet
|
|
111
|
+
driver: bool = args.driver
|
|
112
|
+
worker: bool = args.worker
|
|
113
|
+
health_check: bool = args.health_check
|
|
114
|
+
fragments: Optional[str] = args.fragments
|
|
115
|
+
network: str = create_or_use_network(args.network, map_name)
|
|
116
|
+
nic: Optional[str] = args.nic if args.nic else None
|
|
117
|
+
use_all_nics: bool = args.use_all_nics
|
|
118
|
+
gpus: Optional[str] = args.gpus if args.gpus else None
|
|
119
|
+
config: Optional[Path] = args.config if args.config else None
|
|
120
|
+
address: Optional[str] = args.address if args.address else None
|
|
121
|
+
worker_address: Optional[str] = args.worker_address if args.worker_address else None
|
|
122
|
+
render: bool = args.render
|
|
123
|
+
user: str = f"{args.uid}:{args.gid}"
|
|
124
|
+
hostname: Optional[str] = "driver" if driver else None
|
|
125
|
+
terminal: bool = args.terminal
|
|
126
|
+
platform_config: str = pkg_info.get("platformConfig")
|
|
127
|
+
shared_memory_size: Optional[str] = (
|
|
128
|
+
get_shared_memory_size(pkg_info, worker, driver, fragments, args.shm_size)
|
|
129
|
+
if args.shm_size
|
|
130
|
+
else None
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
commands = []
|
|
134
|
+
devices = _lookup_devices(args.device) if args.device is not None else []
|
|
135
|
+
|
|
136
|
+
if driver:
|
|
137
|
+
commands.append("--driver")
|
|
138
|
+
logger.info("Application running in Driver mode")
|
|
139
|
+
if worker:
|
|
140
|
+
commands.append("--worker")
|
|
141
|
+
logger.info("Application running in Worker mode")
|
|
142
|
+
if fragments:
|
|
143
|
+
commands.append("--fragments")
|
|
144
|
+
commands.append(fragments)
|
|
145
|
+
logger.info(f"Configured fragments: {fragments}")
|
|
146
|
+
if address:
|
|
147
|
+
commands.append("--address")
|
|
148
|
+
commands.append(address)
|
|
149
|
+
logger.info(f"App Driver address and port: {address}")
|
|
150
|
+
if worker_address:
|
|
151
|
+
commands.append("--worker_address")
|
|
152
|
+
commands.append(worker_address)
|
|
153
|
+
logger.info(f"App Worker address and port: {worker_address}")
|
|
154
|
+
|
|
155
|
+
docker_run(
|
|
156
|
+
hostname,
|
|
157
|
+
map_name,
|
|
158
|
+
input_path,
|
|
159
|
+
output_path,
|
|
160
|
+
app_info,
|
|
161
|
+
pkg_info,
|
|
162
|
+
quiet,
|
|
163
|
+
commands,
|
|
164
|
+
health_check,
|
|
165
|
+
network,
|
|
166
|
+
nic,
|
|
167
|
+
use_all_nics,
|
|
168
|
+
gpus,
|
|
169
|
+
config,
|
|
170
|
+
render,
|
|
171
|
+
user,
|
|
172
|
+
terminal,
|
|
173
|
+
devices,
|
|
174
|
+
platform_config,
|
|
175
|
+
shared_memory_size,
|
|
176
|
+
args.uid == 0,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _lookup_devices(devices: list[str]) -> list[str]:
|
|
181
|
+
"""
|
|
182
|
+
Looks up matching devices in /dev and returns a list
|
|
183
|
+
of fully qualified device paths.
|
|
184
|
+
|
|
185
|
+
Raises exception if any devices cannot be found.
|
|
186
|
+
"""
|
|
187
|
+
matched_devices = []
|
|
188
|
+
unmatched_devices = []
|
|
189
|
+
|
|
190
|
+
for dev in devices:
|
|
191
|
+
pattern = dev
|
|
192
|
+
if not pattern.startswith("/"):
|
|
193
|
+
pattern = "/dev/" + pattern
|
|
194
|
+
found_devices = glob(pattern)
|
|
195
|
+
if len(found_devices) == 0:
|
|
196
|
+
unmatched_devices.append(dev)
|
|
197
|
+
else:
|
|
198
|
+
matched_devices.extend(found_devices)
|
|
199
|
+
|
|
200
|
+
if len(unmatched_devices) > 0:
|
|
201
|
+
raise UnmatchedDeviceError(unmatched_devices)
|
|
202
|
+
|
|
203
|
+
return matched_devices
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _dependency_verification(map_name: str) -> bool:
|
|
207
|
+
"""Check if all the dependencies are installed or not.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
map_name: HAP/MAP name
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
True if all dependencies are satisfied, otherwise False.
|
|
214
|
+
"""
|
|
215
|
+
logger.info("Checking dependencies...")
|
|
216
|
+
|
|
217
|
+
# check for docker
|
|
218
|
+
prog = "docker"
|
|
219
|
+
logger.info(f'--> Verifying if "{prog}" is installed...\n')
|
|
220
|
+
if not shutil.which(prog):
|
|
221
|
+
logger.error(
|
|
222
|
+
'"%s" not installed, please install it from https://docs.docker.com/engine/install/.',
|
|
223
|
+
prog,
|
|
224
|
+
)
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
buildx_paths = [
|
|
228
|
+
os.path.expandvars("$HOME/.docker/cli-plugins"),
|
|
229
|
+
"/usr/local/lib/docker/cli-plugins",
|
|
230
|
+
"/usr/local/libexec/docker/cli-plugins",
|
|
231
|
+
"/usr/lib/docker/cli-plugins",
|
|
232
|
+
"/usr/libexec/docker/cli-plugins",
|
|
233
|
+
]
|
|
234
|
+
prog = "docker-buildx"
|
|
235
|
+
logger.info('--> Verifying if "%s" is installed...\n', prog)
|
|
236
|
+
buildx_found = False
|
|
237
|
+
for path in buildx_paths:
|
|
238
|
+
if shutil.which(os.path.join(path, prog)):
|
|
239
|
+
buildx_found = True
|
|
240
|
+
|
|
241
|
+
if not buildx_found:
|
|
242
|
+
logger.error(
|
|
243
|
+
'"%s" not installed, please install it from https://docs.docker.com/engine/install/.',
|
|
244
|
+
prog,
|
|
245
|
+
)
|
|
246
|
+
return False
|
|
247
|
+
|
|
248
|
+
# check for map image
|
|
249
|
+
logger.info('--> Verifying if "%s" is available...\n', map_name)
|
|
250
|
+
if not image_exists(map_name):
|
|
251
|
+
logger.error("Unable to fetch required image.")
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
return True
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _pkg_specific_dependency_verification(pkg_info: dict) -> bool:
|
|
258
|
+
"""Checks for any package specific dependencies.
|
|
259
|
+
|
|
260
|
+
Currently it verifies the following dependencies:
|
|
261
|
+
* If gpu has been requested by the application, verify that nvidia-ctk is installed.
|
|
262
|
+
Note: when running inside a Docker container, always assume nvidia-ctk is installed.
|
|
263
|
+
Args:
|
|
264
|
+
pkg_info: package manifest as a python dict
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
True if all dependencies are satisfied, otherwise False.
|
|
268
|
+
"""
|
|
269
|
+
if (
|
|
270
|
+
os.path.exists("/.dockerenv")
|
|
271
|
+
or os.environ.get("HOLOSCAN_SKIP_NVIDIA_CTK_CHECK") is not None
|
|
272
|
+
):
|
|
273
|
+
logger.info("--> Skipping nvidia-ctk check inside Docker...\n")
|
|
274
|
+
return True
|
|
275
|
+
|
|
276
|
+
requested_gpus = get_requested_gpus(pkg_info)
|
|
277
|
+
if requested_gpus > 0:
|
|
278
|
+
# check for NVIDIA Container TOolkit
|
|
279
|
+
prog = "nvidia-ctk"
|
|
280
|
+
logger.info('--> Verifying if "%s" is installed...\n', prog)
|
|
281
|
+
if not shutil.which(prog):
|
|
282
|
+
logger.error(
|
|
283
|
+
'"%s" not installed, please install NVIDIA Container Toolkit.', prog
|
|
284
|
+
)
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
logger.info('--> Verifying "%s" version...\n', prog)
|
|
288
|
+
output = run_cmd_output(["nvidia-ctk", "--version"], "version")
|
|
289
|
+
match = re.search(r"([0-9]+\.[0-9]+\.[0-9]+)", output)
|
|
290
|
+
min_ctk_version = "1.12.0"
|
|
291
|
+
recommended_ctk_version = "1.14.1"
|
|
292
|
+
|
|
293
|
+
if match is None:
|
|
294
|
+
logger.error(
|
|
295
|
+
f"Error detecting NVIDIA Container Toolkit version. "
|
|
296
|
+
f"Version {min_ctk_version}+ is required ({recommended_ctk_version}+ recommended)."
|
|
297
|
+
)
|
|
298
|
+
return False
|
|
299
|
+
elif compare_versions(min_ctk_version, match.group()) > 0:
|
|
300
|
+
logger.error(
|
|
301
|
+
f"Found '{prog}' Version {match.group()}. "
|
|
302
|
+
f"Version {min_ctk_version}+ is required ({recommended_ctk_version}+ recommended)."
|
|
303
|
+
)
|
|
304
|
+
return False
|
|
305
|
+
|
|
306
|
+
return True
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def execute_run_command(args: Namespace):
|
|
310
|
+
"""
|
|
311
|
+
Entrypoint for the Holoscan Run command.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
args: Namespace object containing user arguments.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
None
|
|
318
|
+
"""
|
|
319
|
+
if not _dependency_verification(args.map):
|
|
320
|
+
logger.error("Execution Aborted")
|
|
321
|
+
sys.exit(2)
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
# Fetch application manifest from MAP
|
|
325
|
+
app_info, pkg_info = _fetch_map_manifest(args.map)
|
|
326
|
+
except Exception as e:
|
|
327
|
+
logger.error(f"Failed to fetch MAP manifest: {e}")
|
|
328
|
+
sys.exit(2)
|
|
329
|
+
|
|
330
|
+
if not _pkg_specific_dependency_verification(pkg_info):
|
|
331
|
+
logger.error("Execution Aborted")
|
|
332
|
+
sys.exit(2)
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
# Run Holoscan Application
|
|
336
|
+
_run_app(args, app_info, pkg_info)
|
|
337
|
+
except Exception as ex:
|
|
338
|
+
logger.debug(ex, exc_info=True)
|
|
339
|
+
logger.error(f"Error executing {args.map}: {ex}")
|
|
340
|
+
sys.exit(2)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
from .version import execute_version_command # noqa: F401
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
from argparse import Namespace
|
|
19
|
+
|
|
20
|
+
from ..common.enum_types import SdkType
|
|
21
|
+
from ..common.sdk_utils import (
|
|
22
|
+
detect_holoscan_version,
|
|
23
|
+
detect_monaideploy_version,
|
|
24
|
+
detect_sdk,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger("version")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def execute_version_command(args: Namespace):
|
|
31
|
+
print(
|
|
32
|
+
f"You are executing Holoscan CLI from: {os.path.dirname(os.path.abspath(sys.argv[0]))}\n"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
sdk = detect_sdk()
|
|
37
|
+
try:
|
|
38
|
+
sdk_version = detect_holoscan_version()
|
|
39
|
+
print(f"Holoscan SDK: {sdk_version}")
|
|
40
|
+
except Exception:
|
|
41
|
+
print("Holoscan SDK: N/A")
|
|
42
|
+
|
|
43
|
+
if sdk == SdkType.MonaiDeploy:
|
|
44
|
+
try:
|
|
45
|
+
sdk_version = detect_monaideploy_version()
|
|
46
|
+
print(f"MONAI Deploy App SDK: {sdk_version}")
|
|
47
|
+
except Exception:
|
|
48
|
+
print("MONAI Deploy App SDK: N/A")
|
|
49
|
+
|
|
50
|
+
except Exception as ex:
|
|
51
|
+
logging.error("Error executing version command.")
|
|
52
|
+
logger.debug(ex)
|
|
53
|
+
sys.exit(3)
|