idmtools-platform-comps 0.0.0.dev0__py3-none-any.whl → 0.0.2__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.
- idmtools_platform_comps/__init__.py +25 -8
- idmtools_platform_comps/cli/__init__.py +4 -0
- idmtools_platform_comps/cli/cli_functions.py +50 -0
- idmtools_platform_comps/cli/comps.py +492 -0
- idmtools_platform_comps/comps_cli.py +48 -0
- idmtools_platform_comps/comps_operations/__init__.py +6 -0
- idmtools_platform_comps/comps_operations/asset_collection_operations.py +263 -0
- idmtools_platform_comps/comps_operations/experiment_operations.py +569 -0
- idmtools_platform_comps/comps_operations/simulation_operations.py +678 -0
- idmtools_platform_comps/comps_operations/suite_operations.py +228 -0
- idmtools_platform_comps/comps_operations/workflow_item_operations.py +269 -0
- idmtools_platform_comps/comps_platform.py +309 -0
- idmtools_platform_comps/plugin_info.py +168 -0
- idmtools_platform_comps/ssmt_operations/__init__.py +6 -0
- idmtools_platform_comps/ssmt_operations/simulation_operations.py +77 -0
- idmtools_platform_comps/ssmt_operations/workflow_item_operations.py +73 -0
- idmtools_platform_comps/ssmt_platform.py +44 -0
- idmtools_platform_comps/ssmt_work_items/__init__.py +4 -0
- idmtools_platform_comps/ssmt_work_items/comps_work_order_task.py +29 -0
- idmtools_platform_comps/ssmt_work_items/comps_workitems.py +113 -0
- idmtools_platform_comps/ssmt_work_items/icomps_workflowitem.py +71 -0
- idmtools_platform_comps/ssmt_work_items/work_order.py +54 -0
- idmtools_platform_comps/utils/__init__.py +4 -0
- idmtools_platform_comps/utils/assetize_output/__init__.py +4 -0
- idmtools_platform_comps/utils/assetize_output/assetize_output.py +125 -0
- idmtools_platform_comps/utils/assetize_output/assetize_ssmt_script.py +144 -0
- idmtools_platform_comps/utils/base_singularity_work_order.json +6 -0
- idmtools_platform_comps/utils/download/__init__.py +4 -0
- idmtools_platform_comps/utils/download/download.py +178 -0
- idmtools_platform_comps/utils/download/download_ssmt.py +81 -0
- idmtools_platform_comps/utils/download_experiment.py +116 -0
- idmtools_platform_comps/utils/file_filter_workitem.py +519 -0
- idmtools_platform_comps/utils/general.py +358 -0
- idmtools_platform_comps/utils/linux_mounts.py +73 -0
- idmtools_platform_comps/utils/lookups.py +123 -0
- idmtools_platform_comps/utils/package_version.py +489 -0
- idmtools_platform_comps/utils/python_requirements_ac/__init__.py +4 -0
- idmtools_platform_comps/utils/python_requirements_ac/create_asset_collection.py +155 -0
- idmtools_platform_comps/utils/python_requirements_ac/install_requirements.py +109 -0
- idmtools_platform_comps/utils/python_requirements_ac/requirements_to_asset_collection.py +374 -0
- idmtools_platform_comps/utils/python_version.py +40 -0
- idmtools_platform_comps/utils/scheduling.py +154 -0
- idmtools_platform_comps/utils/singularity_build.py +491 -0
- idmtools_platform_comps/utils/spatial_output.py +76 -0
- idmtools_platform_comps/utils/ssmt_utils/__init__.py +6 -0
- idmtools_platform_comps/utils/ssmt_utils/common.py +70 -0
- idmtools_platform_comps/utils/ssmt_utils/file_filter.py +568 -0
- idmtools_platform_comps/utils/sweeping.py +162 -0
- idmtools_platform_comps-0.0.2.dist-info/METADATA +100 -0
- idmtools_platform_comps-0.0.2.dist-info/RECORD +62 -0
- idmtools_platform_comps-0.0.2.dist-info/entry_points.txt +9 -0
- idmtools_platform_comps-0.0.2.dist-info/licenses/LICENSE.TXT +3 -0
- {idmtools_platform_comps-0.0.0.dev0.dist-info → idmtools_platform_comps-0.0.2.dist-info}/top_level.txt +1 -0
- ssmt_image/Dockerfile +52 -0
- ssmt_image/Makefile +21 -0
- ssmt_image/__init__.py +6 -0
- ssmt_image/bootstrap.sh +30 -0
- ssmt_image/build_docker_image.py +161 -0
- ssmt_image/pip.conf +3 -0
- ssmt_image/push_docker_image.py +49 -0
- ssmt_image/requirements.txt +9 -0
- idmtools_platform_comps-0.0.0.dev0.dist-info/METADATA +0 -41
- idmtools_platform_comps-0.0.0.dev0.dist-info/RECORD +0 -5
- {idmtools_platform_comps-0.0.0.dev0.dist-info → idmtools_platform_comps-0.0.2.dist-info}/WHEEL +0 -0
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
"""idmtools comps platform.
|
|
2
|
+
|
|
3
|
+
We try to load the CLI here but if idmtools-cli is not installed, we fail gracefully.
|
|
4
|
+
|
|
5
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
6
|
+
"""
|
|
7
|
+
# flake8: noqa F821
|
|
8
|
+
from idmtools_platform_comps.plugin_info import COMPSPlatformSpecification
|
|
9
|
+
try: # since cli is not required but we always try to load file, wrap in try except
|
|
10
|
+
from idmtools_platform_comps.comps_cli import CompsCLI
|
|
11
|
+
except ImportError:
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
16
|
+
except ImportError:
|
|
17
|
+
# Python < 3.8
|
|
18
|
+
from importlib_metadata import version, PackageNotFoundError
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
__version__ = version("idmtools-platform-comps") # Use your actual package name
|
|
22
|
+
except PackageNotFoundError:
|
|
23
|
+
# Package not installed, use fallback
|
|
24
|
+
__version__ = "0.0.0+unknown"
|
|
25
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""idmtools cli utils.
|
|
2
|
+
|
|
3
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
4
|
+
"""
|
|
5
|
+
from dataclasses import Field
|
|
6
|
+
from typing import Dict, Tuple
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def validate_range(value: float, min: float, max: float) -> Tuple[bool, str]:
|
|
10
|
+
"""
|
|
11
|
+
Function used to validate an integer value between min and max.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
value: The value set by the user
|
|
15
|
+
min: Minimum value
|
|
16
|
+
max: Maximum value
|
|
17
|
+
|
|
18
|
+
Returns: tuple with validation result and error message if needed
|
|
19
|
+
"""
|
|
20
|
+
if min <= value <= max:
|
|
21
|
+
return True, ''
|
|
22
|
+
return False, f"The value needs to be between {min} and {max}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def environment_list(previous_settings: Dict, current_field: Field) -> Dict:
|
|
26
|
+
"""
|
|
27
|
+
Allows the CLI to provide a list of available environments.
|
|
28
|
+
|
|
29
|
+
Uses the previous_settings to get the endpoint to query for environments
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
previous_settings: previous settings set by the user in the CLI.
|
|
33
|
+
current_field: Current field specs
|
|
34
|
+
|
|
35
|
+
Returns: updates to the choices and default
|
|
36
|
+
"""
|
|
37
|
+
from COMPS import Client
|
|
38
|
+
Client.login(previous_settings["endpoint"])
|
|
39
|
+
client = Client.get("environments")
|
|
40
|
+
environment_choices = []
|
|
41
|
+
for environment_info in client.json()["Environments"]:
|
|
42
|
+
environment_choices.append(environment_info["EnvironmentName"])
|
|
43
|
+
|
|
44
|
+
# Set a valid default
|
|
45
|
+
if current_field.default not in environment_choices:
|
|
46
|
+
default_env = environment_choices[0]
|
|
47
|
+
else:
|
|
48
|
+
default_env = current_field.default
|
|
49
|
+
|
|
50
|
+
return {"choices": environment_choices, "default": default_env}
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
"""idmtools comps cli comands.
|
|
2
|
+
|
|
3
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
4
|
+
"""
|
|
5
|
+
# flake8: D301
|
|
6
|
+
import glob
|
|
7
|
+
import json as json_parser
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Optional, List
|
|
11
|
+
import tabulate
|
|
12
|
+
from getpass import getpass
|
|
13
|
+
from logging import getLogger
|
|
14
|
+
from COMPS.CredentialPrompt import CredentialPrompt
|
|
15
|
+
import json as je
|
|
16
|
+
from idmtools.assets import AssetCollection
|
|
17
|
+
from idmtools_platform_comps.utils.singularity_build import SingularityBuildWorkItem
|
|
18
|
+
|
|
19
|
+
logger = getLogger(__name__)
|
|
20
|
+
user_logger = getLogger('user')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def add_item(assets: AssetCollection, file: str):
|
|
24
|
+
"""
|
|
25
|
+
Add Item.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
assets: Assets
|
|
29
|
+
file: File or Directory
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
None
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
FileNotFoundError if file cannot be found.
|
|
36
|
+
"""
|
|
37
|
+
if os.path.isdir(file):
|
|
38
|
+
assets.add_directory(file)
|
|
39
|
+
elif os.path.isfile(file):
|
|
40
|
+
assets.add_asset(file)
|
|
41
|
+
else:
|
|
42
|
+
user_logger.error(f"Cannot find file {file}")
|
|
43
|
+
raise FileNotFoundError(f"Cannot find file {file}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
|
|
48
|
+
class StaticCredentialPrompt(CredentialPrompt):
|
|
49
|
+
"""Provides a class to allow login to comps from a username password that is static or provided."""
|
|
50
|
+
def __init__(self, comps_url, username, password):
|
|
51
|
+
"""Constructor."""
|
|
52
|
+
if (comps_url is None) or (username is None) or (password is None):
|
|
53
|
+
raise RuntimeError('Missing comps_url, or username or password')
|
|
54
|
+
self._times_prompted = 0
|
|
55
|
+
self.comps_url = comps_url
|
|
56
|
+
self.username = username
|
|
57
|
+
self.password = password
|
|
58
|
+
|
|
59
|
+
def prompt(self):
|
|
60
|
+
"""Return our stores username and password."""
|
|
61
|
+
self._times_prompted = self._times_prompted + 1
|
|
62
|
+
if self._times_prompted > 3:
|
|
63
|
+
raise PermissionError('Failure authenticating')
|
|
64
|
+
return {'Username': self.username, 'Password': self.password}
|
|
65
|
+
|
|
66
|
+
os.environ['IDMTOOLS_NO_CONFIG_WARNING'] = '1'
|
|
67
|
+
from idmtools.core.platform_factory import Platform
|
|
68
|
+
import click
|
|
69
|
+
from idmtools_platform_comps.utils.assetize_output.assetize_output import AssetizeOutput
|
|
70
|
+
from idmtools_platform_comps.utils.file_filter_workitem import DEFAULT_EXCLUDES
|
|
71
|
+
from idmtools_platform_comps.comps_platform import COMPSPlatform
|
|
72
|
+
|
|
73
|
+
@click.group(short_help="COMPS platform related commands.")
|
|
74
|
+
@click.argument('config-block')
|
|
75
|
+
@click.pass_context
|
|
76
|
+
def comps(ctx: click.Context, config_block):
|
|
77
|
+
"""
|
|
78
|
+
Commands related to managing the COMPS platform.
|
|
79
|
+
|
|
80
|
+
CONFIG_BLOCK - Name of configuration section or alias to load COMPS connection information from
|
|
81
|
+
"""
|
|
82
|
+
ctx.obj = dict(config_block=config_block)
|
|
83
|
+
|
|
84
|
+
@comps.command(help="Login to COMPS")
|
|
85
|
+
@click.option('--username', required=True, help="Username")
|
|
86
|
+
@click.option('--password', help="Password")
|
|
87
|
+
@click.pass_context
|
|
88
|
+
def login(ctx: click.Context, username, password): # noqa D103
|
|
89
|
+
from COMPS import Client
|
|
90
|
+
from idmtools.core.logging import SUCCESS
|
|
91
|
+
os.environ['IDMTOOLS_LOGGING_USER_OUTPUT'] = '0'
|
|
92
|
+
if password:
|
|
93
|
+
user_logger.warning("Password the password via the command line is considered insecure")
|
|
94
|
+
else:
|
|
95
|
+
password = getpass("Password:")
|
|
96
|
+
# make platform object to load info from alias or config but don't login
|
|
97
|
+
platform = Platform(ctx.obj['config_block'], _skip_login=True)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
Client.login(platform.endpoint,
|
|
101
|
+
StaticCredentialPrompt(comps_url=platform.endpoint, username=username, password=password))
|
|
102
|
+
user_logger.log(SUCCESS, "Login succeeded")
|
|
103
|
+
except PermissionError:
|
|
104
|
+
user_logger.error(f"Could not loging to {platform.endpoint}")
|
|
105
|
+
sys.exit(-1)
|
|
106
|
+
|
|
107
|
+
@comps.command(help="Allows Downloading outputs from the command line")
|
|
108
|
+
@click.option('--pattern', default=[], multiple=True, help="File patterns")
|
|
109
|
+
@click.option('--exclude-pattern', default=DEFAULT_EXCLUDES, multiple=True, help="File patterns")
|
|
110
|
+
@click.option('--experiment', default=[], multiple=True, help="Experiment ids to filter for files to download")
|
|
111
|
+
@click.option('--simulation', default=[], multiple=True, help="Simulation ids to filter for files to download")
|
|
112
|
+
@click.option('--work-item', default=[], multiple=True, help="WorkItems ids to filter for files to download")
|
|
113
|
+
@click.option('--asset-collection', default=[], multiple=True,
|
|
114
|
+
help="Asset Collection ids to filter for files to download")
|
|
115
|
+
@click.option('--dry-run/--no-dry-run', default=False,
|
|
116
|
+
help="Gather a list of files that would be downloaded instead of actually downloading")
|
|
117
|
+
@click.option('--wait/--no-wait', default=True, help="Wait on item to finish")
|
|
118
|
+
@click.option('--include-assets/--no-include-assets', default=False,
|
|
119
|
+
help="Scan common assets of WorkItems and Experiments when filtering")
|
|
120
|
+
@click.option('--verbose/--no-verbose', default=True, help="Enable verbose output in worker")
|
|
121
|
+
@click.option('--json/--no-json', default=False, help="Outputs File list as JSON when used with dry run")
|
|
122
|
+
@click.option('--simulation-prefix-format-str', default=None,
|
|
123
|
+
help="Simulation Prefix Format str. Defaults to '{simulation.id}'. For no prefix, pass a empty string")
|
|
124
|
+
@click.option('--work-item-prefix-format-str', default=None, help="WorkItem Prefix Format str. Defaults to ''")
|
|
125
|
+
@click.option('--name', default=None, help="Name of Download Workitem. If not provided, one will be generated")
|
|
126
|
+
@click.option('--output-path', default=os.getcwd(), help="Output path to save zip")
|
|
127
|
+
@click.option('--delete-after-download/--no-delete-after-download', default=True,
|
|
128
|
+
help="Delete the workitem used to gather files after download")
|
|
129
|
+
@click.option('--extract-after-download/--no-extract-after-download', default=True,
|
|
130
|
+
help="Extract zip after download")
|
|
131
|
+
@click.option('--zip-name', default="output.zip", help="Name of zipfile")
|
|
132
|
+
@click.pass_context
|
|
133
|
+
def download( # noqa D103
|
|
134
|
+
ctx: click.Context, pattern, exclude_pattern, experiment, simulation, work_item, asset_collection, dry_run,
|
|
135
|
+
wait,
|
|
136
|
+
include_assets, verbose, json, simulation_prefix_format_str, work_item_prefix_format_str, name, output_path,
|
|
137
|
+
delete_after_download,
|
|
138
|
+
extract_after_download, zip_name
|
|
139
|
+
):
|
|
140
|
+
from idmtools_platform_comps.utils.download.download import DownloadWorkItem
|
|
141
|
+
|
|
142
|
+
if json and not dry_run:
|
|
143
|
+
user_logger.error("You cannot return JSON without enabling dry-run mode")
|
|
144
|
+
sys.exit(-1)
|
|
145
|
+
|
|
146
|
+
if dry_run and delete_after_download:
|
|
147
|
+
user_logger.warning(
|
|
148
|
+
"You are using dry-run with delete after download. This will most result in an empty file list since "
|
|
149
|
+
"the item will be deleted before the output can be fetched.")
|
|
150
|
+
|
|
151
|
+
# ensure no output is enabled when using --json
|
|
152
|
+
if json:
|
|
153
|
+
os.environ['IDMTOOLS_LOGGING_USER_OUTPUT'] = '0'
|
|
154
|
+
os.environ['IDMTOOLS_DISABLE_PROGRESS_BAR'] = '1'
|
|
155
|
+
|
|
156
|
+
p: COMPSPlatform = Platform(ctx.obj['config_block'])
|
|
157
|
+
|
|
158
|
+
dl_wi = DownloadWorkItem(
|
|
159
|
+
output_path=output_path,
|
|
160
|
+
delete_after_download=delete_after_download,
|
|
161
|
+
extract_after_download=extract_after_download,
|
|
162
|
+
zip_name=zip_name
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if name:
|
|
166
|
+
dl_wi.name = name
|
|
167
|
+
if pattern:
|
|
168
|
+
dl_wi.file_patterns = list(pattern)
|
|
169
|
+
if exclude_pattern:
|
|
170
|
+
dl_wi.exclude_patterns = exclude_pattern if isinstance(exclude_pattern, list) else list(exclude_pattern)
|
|
171
|
+
|
|
172
|
+
dl_wi.related_experiments = list(experiment)
|
|
173
|
+
dl_wi.related_simulations = list(simulation)
|
|
174
|
+
dl_wi.related_work_items = list(work_item)
|
|
175
|
+
dl_wi.related_asset_collections = list(asset_collection)
|
|
176
|
+
dl_wi.include_assets = include_assets
|
|
177
|
+
dl_wi.dry_run = dry_run
|
|
178
|
+
dl_wi.verbose = verbose
|
|
179
|
+
if simulation_prefix_format_str is not None:
|
|
180
|
+
if simulation_prefix_format_str.strip() == "":
|
|
181
|
+
dl_wi.no_simulation_prefix = True
|
|
182
|
+
else:
|
|
183
|
+
dl_wi.simulation_prefix_format_str = simulation_prefix_format_str
|
|
184
|
+
if work_item_prefix_format_str is not None:
|
|
185
|
+
dl_wi.work_item_prefix_format_str = work_item_prefix_format_str
|
|
186
|
+
|
|
187
|
+
if dl_wi.total_items_watched() == 0:
|
|
188
|
+
user_logger.error("You must specify at least one item to download")
|
|
189
|
+
|
|
190
|
+
dl_wi.run(wait_until_done=False, platform=p)
|
|
191
|
+
if not json and not delete_after_download:
|
|
192
|
+
user_logger.info(f"Item can be viewed at {p.get_workitem_link(dl_wi)}")
|
|
193
|
+
if wait:
|
|
194
|
+
dl_wi.wait(wait_on_done_progress=wait)
|
|
195
|
+
if dl_wi.succeeded:
|
|
196
|
+
if dl_wi.dry_run and not delete_after_download:
|
|
197
|
+
file = p.get_files(dl_wi, ['file_list.json'])
|
|
198
|
+
file = file['file_list.json'].decode('utf-8')
|
|
199
|
+
if json:
|
|
200
|
+
user_logger.info(file)
|
|
201
|
+
else:
|
|
202
|
+
file = json_parser.loads(file)
|
|
203
|
+
user_logger.info(tabulate.tabulate([x.values() for x in file], file[0].keys()))
|
|
204
|
+
else:
|
|
205
|
+
pass
|
|
206
|
+
elif dl_wi.failed:
|
|
207
|
+
user_logger.error("Download failed. Check logs in COMPS")
|
|
208
|
+
if dl_wi.failed:
|
|
209
|
+
dl_wi.fetch_error()
|
|
210
|
+
sys.exit(-1)
|
|
211
|
+
|
|
212
|
+
@comps.command(help="Allows assetizing outputs from the command line")
|
|
213
|
+
@click.option('--pattern', default=[], multiple=True, help="File patterns")
|
|
214
|
+
@click.option('--exclude-pattern', default=DEFAULT_EXCLUDES, multiple=True, help="File patterns")
|
|
215
|
+
@click.option('--experiment', default=[], multiple=True, help="Experiment ids to assetize")
|
|
216
|
+
@click.option('--simulation', default=[], multiple=True, help="Simulation ids to assetize")
|
|
217
|
+
@click.option('--work-item', default=[], multiple=True, help="WorkItems ids to assetize")
|
|
218
|
+
@click.option('--asset-collection', default=[], multiple=True, help="Asset Collection ids to assetize")
|
|
219
|
+
@click.option('--dry-run/--no-dry-run', default=False,
|
|
220
|
+
help="Gather a list of files that would be assetized instead of actually assetizing")
|
|
221
|
+
@click.option('--wait/--no-wait', default=True, help="Wait on item to finish")
|
|
222
|
+
@click.option('--include-assets/--no-include-assets', default=False,
|
|
223
|
+
help="Scan common assets of WorkItems and Experiments when filtering")
|
|
224
|
+
@click.option('--verbose/--no-verbose', default=True, help="Enable verbose output in worker")
|
|
225
|
+
@click.option('--json/--no-json', default=False, help="Outputs File list as JSON when used with dry run")
|
|
226
|
+
@click.option('--simulation-prefix-format-str', default=None,
|
|
227
|
+
help="Simulation Prefix Format str. Defaults to '{simulation.id}'. For no prefix, pass a empty string")
|
|
228
|
+
@click.option('--work-item-prefix-format-str', default=None, help="WorkItem Prefix Format str. Defaults to ''")
|
|
229
|
+
@click.option('--tag', default=[], type=(str, str), multiple=True,
|
|
230
|
+
help="Tags to add to the created asset collection as pairs.")
|
|
231
|
+
@click.option('--name', default=None, help="Name of AssetizeWorkitem. If not provided, one will be generated")
|
|
232
|
+
@click.option('--id-file/--no-id-file', default=False, help="Enable or disable writing out an id file")
|
|
233
|
+
@click.option('--id-filename', default=None,
|
|
234
|
+
help="Name of ID file to save build as. Required when id file is enabled")
|
|
235
|
+
@click.pass_context
|
|
236
|
+
def assetize_outputs( # noqa D103
|
|
237
|
+
ctx: click.Context, pattern, exclude_pattern, experiment, simulation, work_item, asset_collection, dry_run,
|
|
238
|
+
wait,
|
|
239
|
+
include_assets, verbose, json, simulation_prefix_format_str, work_item_prefix_format_str, tag, name,
|
|
240
|
+
id_file, id_filename
|
|
241
|
+
):
|
|
242
|
+
|
|
243
|
+
if id_file and id_filename is None:
|
|
244
|
+
user_logger.error("--id-filename is required when filename is not provided")
|
|
245
|
+
sys.exit(-1)
|
|
246
|
+
if json:
|
|
247
|
+
os.environ['IDMTOOLS_LOGGING_USER_OUTPUT'] = '0'
|
|
248
|
+
os.environ['IDMTOOLS_DISABLE_PROGRESS_BAR'] = '1'
|
|
249
|
+
|
|
250
|
+
p: COMPSPlatform = Platform(ctx.obj['config_block'])
|
|
251
|
+
ao = AssetizeOutput()
|
|
252
|
+
if name:
|
|
253
|
+
ao.name = name
|
|
254
|
+
if pattern:
|
|
255
|
+
ao.file_patterns = list(pattern)
|
|
256
|
+
if exclude_pattern:
|
|
257
|
+
ao.exclude_patterns = exclude_pattern if isinstance(exclude_pattern, list) else list(exclude_pattern)
|
|
258
|
+
ao.related_experiments = list(experiment)
|
|
259
|
+
ao.related_simulations = list(simulation)
|
|
260
|
+
ao.related_work_items = list(work_item)
|
|
261
|
+
ao.related_asset_collections = list(asset_collection)
|
|
262
|
+
ao.include_assets = include_assets
|
|
263
|
+
ao.dry_run = dry_run
|
|
264
|
+
ao.verbose = verbose
|
|
265
|
+
if simulation_prefix_format_str is not None:
|
|
266
|
+
if simulation_prefix_format_str.strip() == "":
|
|
267
|
+
ao.no_simulation_prefix = True
|
|
268
|
+
else:
|
|
269
|
+
ao.simulation_prefix_format_str = simulation_prefix_format_str
|
|
270
|
+
if work_item_prefix_format_str is not None:
|
|
271
|
+
ao.work_item_prefix_format_str = work_item_prefix_format_str
|
|
272
|
+
if tag:
|
|
273
|
+
for name, value in tag:
|
|
274
|
+
ao.asset_tags[name] = value
|
|
275
|
+
if ao.total_items_watched() == 0:
|
|
276
|
+
user_logger.error("You must specify at least one item to assetize")
|
|
277
|
+
ao.run(wait_until_done=False, platform=p)
|
|
278
|
+
if not json:
|
|
279
|
+
user_logger.info(f"Item can be viewed at {p.get_workitem_link(ao)}")
|
|
280
|
+
if wait:
|
|
281
|
+
ao.wait(wait_on_done_progress=wait)
|
|
282
|
+
if ao.succeeded:
|
|
283
|
+
if ao.dry_run:
|
|
284
|
+
file = p.get_files(ao, ['file_list.json'])
|
|
285
|
+
file = file['file_list.json'].decode('utf-8')
|
|
286
|
+
if json:
|
|
287
|
+
user_logger.info(file)
|
|
288
|
+
else:
|
|
289
|
+
file = json_parser.loads(file)
|
|
290
|
+
user_logger.info(tabulate.tabulate([x.values() for x in file], file[0].keys()))
|
|
291
|
+
else:
|
|
292
|
+
if id_file:
|
|
293
|
+
ao.asset_collection.to_id_file(id_filename)
|
|
294
|
+
if json:
|
|
295
|
+
result = [i.short_remote_path() for i in ao.asset_collection.assets]
|
|
296
|
+
user_logger.info(je.dumps(result))
|
|
297
|
+
else:
|
|
298
|
+
user_logger.info(f"Created {ao.asset_collection.id}")
|
|
299
|
+
user_logger.info(f"It can be viewed at {p.get_asset_collection_link(ao.asset_collection)}")
|
|
300
|
+
user_logger.info("Items in Asset Collection")
|
|
301
|
+
user_logger.info("-------------------------")
|
|
302
|
+
for asset in ao.asset_collection:
|
|
303
|
+
user_logger.info(asset.short_remote_path())
|
|
304
|
+
elif ao.failed:
|
|
305
|
+
user_logger.error("Assetized failed. Check logs in COMPS")
|
|
306
|
+
if ao.failed:
|
|
307
|
+
ao.fetch_error()
|
|
308
|
+
sys.exit(-1)
|
|
309
|
+
|
|
310
|
+
@comps.command(help="""
|
|
311
|
+
\b
|
|
312
|
+
Create ac from requirement file
|
|
313
|
+
Args:
|
|
314
|
+
asset_tag: tag to be added to ac
|
|
315
|
+
pkg: package name (along with version)
|
|
316
|
+
wheel: package wheel file
|
|
317
|
+
""")
|
|
318
|
+
@click.argument('requirement', type=click.Path(exists=True), required=False)
|
|
319
|
+
@click.option('--asset_tag', multiple=True, help="Tag to be added to AC. Format: 'key:value'")
|
|
320
|
+
@click.option('--pkg', multiple=True, help="Package for override. Format: 'key==value'")
|
|
321
|
+
@click.option('--wheel', multiple=True, help="Local wheel file")
|
|
322
|
+
@click.pass_context
|
|
323
|
+
def req2ac(ctx: click.Context, requirement: str = None, asset_tag: Optional[List[str]] = None, # noqa D103
|
|
324
|
+
pkg: Optional[List[str]] = None,
|
|
325
|
+
wheel: Optional[List[str]] = None):
|
|
326
|
+
from idmtools_platform_comps.utils.python_requirements_ac.requirements_to_asset_collection import RequirementsToAssetCollection
|
|
327
|
+
|
|
328
|
+
pkg_list = list(pkg)
|
|
329
|
+
wheel_list = [os.path.abspath(w) for w in wheel]
|
|
330
|
+
tags = dict()
|
|
331
|
+
for t in asset_tag:
|
|
332
|
+
parts = t.split(':')
|
|
333
|
+
tags[parts[0]] = parts[1]
|
|
334
|
+
|
|
335
|
+
p: COMPSPlatform = Platform(ctx.obj['config_block'])
|
|
336
|
+
pl = RequirementsToAssetCollection(p, requirements_path=requirement, pkg_list=pkg_list,
|
|
337
|
+
local_wheels=wheel_list, asset_tags=tags)
|
|
338
|
+
ac_id = pl.run()
|
|
339
|
+
print(ac_id)
|
|
340
|
+
|
|
341
|
+
@comps.command(help="""
|
|
342
|
+
\b
|
|
343
|
+
Check ac existing based on requirement file
|
|
344
|
+
Args:
|
|
345
|
+
pkg: package name (along with version)
|
|
346
|
+
wheel: package wheel file
|
|
347
|
+
""")
|
|
348
|
+
@click.argument('requirement', type=click.Path(exists=True), required=False)
|
|
349
|
+
@click.option('--pkg', multiple=True, help="Package used for override. Format: say, 'key==value'")
|
|
350
|
+
@click.option('--wheel', multiple=True, help="Local wheel file")
|
|
351
|
+
@click.pass_context
|
|
352
|
+
def ac_exist(ctx: click.Context, requirement: str = None, pkg: Optional[List[str]] = None, wheel: Optional[List[str]] = None): # noqa D103
|
|
353
|
+
# TODO rename this and move to a subcommand for all the requirements functions
|
|
354
|
+
from idmtools_platform_comps.utils.python_requirements_ac.requirements_to_asset_collection import RequirementsToAssetCollection
|
|
355
|
+
|
|
356
|
+
pkg_list = list(pkg)
|
|
357
|
+
wheel_list = [os.path.abspath(w) for w in wheel]
|
|
358
|
+
p: COMPSPlatform = Platform(ctx.obj['config_block'])
|
|
359
|
+
pl = RequirementsToAssetCollection(p, requirements_path=requirement, pkg_list=pkg_list, local_wheels=wheel_list)
|
|
360
|
+
# Check if ac with md5 exists
|
|
361
|
+
ac = pl.retrieve_ac_by_tag()
|
|
362
|
+
if ac:
|
|
363
|
+
print("AC exist: ", ac.id)
|
|
364
|
+
else:
|
|
365
|
+
print("AC doesn't exist")
|
|
366
|
+
|
|
367
|
+
@comps.group(help="Singularity commands")
|
|
368
|
+
def singularity(): # noqa D103
|
|
369
|
+
pass
|
|
370
|
+
|
|
371
|
+
@singularity.command(help="Build Singularity Image")
|
|
372
|
+
@click.option('--common-input', default=[], multiple=True, help="Files")
|
|
373
|
+
@click.option('--common-input-glob', default=[], multiple=True, help="File patterns")
|
|
374
|
+
@click.option('--transient-input', default=[], multiple=True, help="Transient Files (Paths)")
|
|
375
|
+
@click.option('--transient-input-glob', default=[], multiple=True, help="Transient Files Glob Patterns")
|
|
376
|
+
@click.argument('definition_file')
|
|
377
|
+
@click.option('--wait/--no-wait', default=True, help="Wait on item to finish")
|
|
378
|
+
@click.option('--tag', default=[], type=(str, str), multiple=True,
|
|
379
|
+
help="Extra Tags as Value Pairs for the Resulting AC")
|
|
380
|
+
@click.option('--workitem-tag', default=[], type=(str, str), multiple=True,
|
|
381
|
+
help="Extra Tags as Value Pairs for the WorkItem")
|
|
382
|
+
@click.option('--name', default=None, help="Name of WorkItem. If not provided, one will be generated")
|
|
383
|
+
@click.option('--force/--no-force', default=False, help="Force build, ignoring build context")
|
|
384
|
+
@click.option('--image-name', default=None, help="Name of resulting image")
|
|
385
|
+
@click.option('--id-file/--no-id-file', default=True,
|
|
386
|
+
help="Enable or disable writing out an ID file that points to the created asset collection")
|
|
387
|
+
@click.option('--id-filename', default=None,
|
|
388
|
+
help="Name of ID file to save build as. If not specified, and id-file is enabled, a name is calculated")
|
|
389
|
+
@click.option('--id-workitem/--no-id-workitem', default=True,
|
|
390
|
+
help="Enable or disable writing out an id file for the workitem")
|
|
391
|
+
@click.option('--id-workitem-failed/--no-id-workitem-failed', default=False,
|
|
392
|
+
help="Write id of the workitem even if it failed. You need to enable --id-workitem for this is be active")
|
|
393
|
+
@click.option('--id-workitem-filename', default=None,
|
|
394
|
+
help="Name of ID file to save workitem to. You need to enable --id-workitem for this is be active")
|
|
395
|
+
@click.pass_context
|
|
396
|
+
def build(ctx: click.Context, common_input, common_input_glob, transient_input, transient_input_glob, # noqa D103
|
|
397
|
+
definition_file, wait, tag, workitem_tag, name, force, image_name: str,
|
|
398
|
+
id_file: str, id_filename: str, id_workitem: bool, id_workitem_failed: bool, id_workitem_filename: str):
|
|
399
|
+
p: COMPSPlatform = Platform(ctx.obj['config_block'])
|
|
400
|
+
sb = SingularityBuildWorkItem(definition_file=definition_file, name=name, force=force, image_name=image_name)
|
|
401
|
+
|
|
402
|
+
if tag:
|
|
403
|
+
for name, value in tag:
|
|
404
|
+
sb.image_tags[name] = value
|
|
405
|
+
|
|
406
|
+
if workitem_tag:
|
|
407
|
+
for name, value in tag:
|
|
408
|
+
sb.tags[name] = value
|
|
409
|
+
|
|
410
|
+
# Add inputs from files
|
|
411
|
+
for assets, inputs in [(sb.assets, common_input), (sb.transient_assets, transient_input)]:
|
|
412
|
+
for file in inputs:
|
|
413
|
+
add_item(assets, file)
|
|
414
|
+
|
|
415
|
+
# And then from glob patterns
|
|
416
|
+
for assets, patterns in [(sb.assets, common_input_glob), (sb.transient_assets, transient_input_glob)]:
|
|
417
|
+
for pattern in patterns:
|
|
418
|
+
for file in glob.glob(pattern):
|
|
419
|
+
add_item(assets, file)
|
|
420
|
+
|
|
421
|
+
sb.run(wait_until_done=wait, platform=p)
|
|
422
|
+
if sb.succeeded and id_file:
|
|
423
|
+
if id_filename is None:
|
|
424
|
+
id_filename = sb.get_id_filename()
|
|
425
|
+
user_logger.info(f"Saving the Asset collection ID that contains the image to {id_filename}")
|
|
426
|
+
sb.asset_collection.to_id_file(id_filename, save_platform=True)
|
|
427
|
+
if id_workitem:
|
|
428
|
+
# TODO when we should use platform id but that need to be updated through the code base
|
|
429
|
+
if sb.succeeded and sb._uid is None:
|
|
430
|
+
user_logger.warning(
|
|
431
|
+
"Cannot save workitem id because an existing container was found with the same inputs. You can force run using --force, but it is recommended to use the container used.")
|
|
432
|
+
elif id_workitem_failed or sb.succeeded:
|
|
433
|
+
if id_workitem_filename is None:
|
|
434
|
+
id_workitem_filename = sb.get_id_filename(prefix="builder.")
|
|
435
|
+
user_logger.info(f"Saving the Builder Workitem ID that contains the image to {id_workitem_filename}")
|
|
436
|
+
sb.to_id_file(id_workitem_filename, save_platform=True)
|
|
437
|
+
sys.exit(0 if sb.succeeded else -1)
|
|
438
|
+
|
|
439
|
+
@singularity.command(help="Pull Singularity Image")
|
|
440
|
+
@click.argument('image_url')
|
|
441
|
+
@click.option('--wait/--no-wait', default=True, help="Wait on item to finish")
|
|
442
|
+
@click.option('--tag', default=[], type=(str, str), multiple=True,
|
|
443
|
+
help="Extra Tags as Value Pairs for the Resulting AC")
|
|
444
|
+
@click.option('--workitem-tag', default=[], type=(str, str), multiple=True,
|
|
445
|
+
help="Extra Tags as Value Pairs for the WorkItem")
|
|
446
|
+
@click.option('--name', default=None, help="Name of WorkItem. If not provided, one will be generated")
|
|
447
|
+
@click.option('--force/--no-force', default=False, help="Force build, ignoring build context")
|
|
448
|
+
@click.option('--image-name', default=None, help="Name of resulting image")
|
|
449
|
+
@click.option('--id-file/--no-id-file', default=True, help="Enable or disable writing out an id file")
|
|
450
|
+
@click.option('--id-filename', default=None,
|
|
451
|
+
help="Name of ID file to save build as. If not specified, and id-file is enabled, a name is calculated")
|
|
452
|
+
@click.option('--id-workitem/--no-id-workitem', default=True,
|
|
453
|
+
help="Enable or disable writing out an id file for the workitem")
|
|
454
|
+
@click.option('--id-workitem-failed/--no-id-workitem-failed', default=False,
|
|
455
|
+
help="Write id of the workitem even if it failed. You need to enable --id-workitem for this is be active")
|
|
456
|
+
@click.option('--id-workitem-filename', default=None,
|
|
457
|
+
help="Name of ID file to save workitem to. You need to enable --id-workitem for this is be active")
|
|
458
|
+
@click.pass_context
|
|
459
|
+
def pull(ctx: click.Context, image_url, wait, tag, workitem_tag, name, force, image_name: str, id_file: str, # noqa D103
|
|
460
|
+
id_filename: str,
|
|
461
|
+
id_workitem: bool, id_workitem_failed: bool, id_workitem_filename: str):
|
|
462
|
+
p: COMPSPlatform = Platform(ctx.obj['config_block'])
|
|
463
|
+
sb = SingularityBuildWorkItem(image_url=image_url, force=force, image_name=image_name)
|
|
464
|
+
sb.name = f"Pulling {image_url}" if name is None else name
|
|
465
|
+
|
|
466
|
+
if tag:
|
|
467
|
+
for name, value in tag:
|
|
468
|
+
sb.image_tags[name] = value
|
|
469
|
+
|
|
470
|
+
if workitem_tag:
|
|
471
|
+
for name, value in tag:
|
|
472
|
+
sb.tags[name] = value
|
|
473
|
+
|
|
474
|
+
sb.run(wait_until_done=wait, platform=p)
|
|
475
|
+
if sb.succeeded and id_file:
|
|
476
|
+
if id_filename is None:
|
|
477
|
+
id_filename = sb.get_id_filename()
|
|
478
|
+
user_logger.info(f"Saving ID to {id_filename}")
|
|
479
|
+
sb.asset_collection.to_id_file(id_filename, save_platform=True)
|
|
480
|
+
|
|
481
|
+
if id_workitem and sb.succeeded and sb._uid is None:
|
|
482
|
+
user_logger.warning(
|
|
483
|
+
"Cannot save workitem id because an existing container was found with the same inputs. You can force run using --force, but it is recommended to use the container used.")
|
|
484
|
+
elif id_workitem_failed or sb.succeeded:
|
|
485
|
+
if id_workitem_filename is None:
|
|
486
|
+
id_workitem_filename = sb.get_id_filename(prefix="builder.")
|
|
487
|
+
user_logger.info(f"Saving the Builder Workitem ID that contains the image to {id_workitem_filename}")
|
|
488
|
+
sb.to_id_file(id_workitem_filename, save_platform=True)
|
|
489
|
+
sys.exit(0 if sb.succeeded else -1)
|
|
490
|
+
|
|
491
|
+
except ImportError as e:
|
|
492
|
+
logger.warning(f"COMPS CLI not enabled because a dependency is missing. Most likely it is either click or idmtools cli {e.args}")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Define the comps cli spec.
|
|
2
|
+
|
|
3
|
+
Notes:
|
|
4
|
+
- We eventually need to deprecate this
|
|
5
|
+
|
|
6
|
+
Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
|
|
7
|
+
"""
|
|
8
|
+
try: # since cli is not required but we always try to load file, wrap in try except
|
|
9
|
+
from typing import NoReturn
|
|
10
|
+
|
|
11
|
+
from idmtools.registry.plugin_specification import get_description_impl
|
|
12
|
+
from idmtools_cli.iplatform_cli import IPlatformCLI, PlatformCLISpecification, get_platform_cli_impl, \
|
|
13
|
+
get_additional_commands_impl
|
|
14
|
+
|
|
15
|
+
class CompsCLI(IPlatformCLI):
|
|
16
|
+
"""Defines our CLI interface for COMPS using IPlatformCLI."""
|
|
17
|
+
|
|
18
|
+
def get_experiment_status(self, *args, **kwargs) -> NoReturn:
|
|
19
|
+
"""Experiment status command."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def get_simulation_status(self, *args, **kwargs) -> NoReturn:
|
|
23
|
+
"""Simulation status command."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def get_platform_information(self) -> dict:
|
|
27
|
+
"""Platofrm info."""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
class COMPSCLISpecification(PlatformCLISpecification):
|
|
31
|
+
"""Provides plugin definition for CompsCLI."""
|
|
32
|
+
|
|
33
|
+
@get_platform_cli_impl
|
|
34
|
+
def get(self, configuration: dict) -> CompsCLI:
|
|
35
|
+
"""Get our CLI plugin with config."""
|
|
36
|
+
return CompsCLI(**configuration)
|
|
37
|
+
|
|
38
|
+
@get_additional_commands_impl
|
|
39
|
+
def get_additional_commands(self) -> NoReturn:
|
|
40
|
+
"""Get our CLI commands."""
|
|
41
|
+
import idmtools_platform_comps.cli.comps # noqa: F401
|
|
42
|
+
|
|
43
|
+
@get_description_impl
|
|
44
|
+
def get_description(self) -> str:
|
|
45
|
+
"""Get COMPS CLI plugin description."""
|
|
46
|
+
return "Provides CLI commands for the COMPS Platform"
|
|
47
|
+
except ImportError:
|
|
48
|
+
pass
|