Skip to content

Commit 6de12d6

Browse files
feat(cli): support --with in ado create (#262)
* feat(cli): add --with to ado create * feat(cli): return identifier of the resource created in ado create commands * fix(cli): check create space options correctly * feat(cli): support --with samplestore in ado create space * feat(cli): support --with space and --with ac in ado create operation * chore: OperationFunctionConf was renamed to OperatorFunctionConf * feat(cli): allow overriding values and performing other manipulations in ado create space * test: add new tests for ado create space --with * refactor(examples): use default sample store * fix(cli): de-indent code * test(cli): add test for ado create operation --with space * test: do not use active ado context * refactor(test): move test * refactor(cli): reorder create parameters * docs(website): update cli documentation * chore(examples): remove sample store identifier Co-authored-by: Michael Johnston <[email protected]> Signed-off-by: Alessandro Pomponio <[email protected]> * fix(cli): override resources when using with Before it would just append to spaces and actuator configuration identifiers, causing issues * docs(cli): update help string for ado create --with Co-Authored-By: Michael Johnston <[email protected]> * docs(cli): update cli help strings * docs(cli): add missing space in --use-default-sample-store Co-authored-by: Michael Johnston <[email protected]> Signed-off-by: Alessandro Pomponio <[email protected]> --------- Signed-off-by: Alessandro Pomponio <[email protected]> Co-authored-by: Michael Johnston <[email protected]>
1 parent c7e1ee7 commit 6de12d6

File tree

13 files changed

+358
-34
lines changed

13 files changed

+358
-34
lines changed

examples/optimization_test_functions/space.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Copyright (c) IBM Corporation
22
# SPDX-License-Identifier: MIT
33

4-
sampleStoreIdentifier: dfe035
54
entitySpace:
65
- identifier: x2
76
propertyDomain:

orchestrator/cli/commands/create.py

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Copyright (c) IBM Corporation
22
# SPDX-License-Identifier: MIT
3-
3+
import os
44
import pathlib
55
from typing import Annotated
66

@@ -14,7 +14,10 @@
1414
)
1515
from orchestrator.cli.models.choice import HiddenPluralChoice, HiddenShorthandChoice
1616
from orchestrator.cli.models.parameters import AdoCreateCommandParameters
17-
from orchestrator.cli.models.types import AdoCreateSupportedResourceTypes
17+
from orchestrator.cli.models.types import (
18+
AdoCreateSupportedResourceTypes,
19+
AdoCreateWithResourceSupportedResourceTypes,
20+
)
1821
from orchestrator.cli.resources.actuator_configuration.create import (
1922
create_actuator_configuration,
2023
)
@@ -24,10 +27,14 @@
2427
from orchestrator.cli.resources.sample_store.create import create_sample_store
2528
from orchestrator.cli.utils.input.parsers import (
2629
parse_key_value_pairs,
30+
resource_shorthands_to_full_names,
2731
)
2832
from orchestrator.cli.utils.output.prints import (
2933
ERROR,
34+
HINT,
35+
bold,
3036
console_print,
37+
magenta,
3138
)
3239
from orchestrator.core import CoreResourceKinds
3340
from orchestrator.metastore.base import (
@@ -107,20 +114,9 @@ def create_resource(
107114
typer.Option(
108115
"--new-sample-store",
109116
help="Request and use a new, empty sample store. Available only for space and sample store. "
110-
"Ignored if --set or --use-latest are used.",
117+
"Ignored if --with or --use-latest are used.",
111118
),
112119
] = False,
113-
use_latest: Annotated[
114-
list[CoreResourceKinds] | None,
115-
typer.Option(
116-
show_default=False,
117-
click_type=HiddenShorthandChoice(CoreResourceKinds),
118-
help="""
119-
Reuse the latest identifier of a resource kind. Can be used multiple times.
120-
121-
Only supported for spaces and operations. Ignored if --set is used.""",
122-
),
123-
] = None,
124120
set_values: Annotated[
125121
list[str] | None,
126122
typer.Option(
@@ -136,13 +132,34 @@ def create_resource(
136132
""",
137133
),
138134
] = None,
135+
with_resources: Annotated[
136+
list[str] | None,
137+
typer.Option(
138+
"--with",
139+
help="""Specify additional resources to use or create via key=value pairs,
140+
where the key is a resource type and the value is a resource ID or a resource configuration file.
141+
142+
Supports creating spaces with sample stores and operations with spaces and actuator configurations.""",
143+
),
144+
] = None,
145+
use_latest: Annotated[
146+
list[CoreResourceKinds] | None,
147+
typer.Option(
148+
show_default=False,
149+
click_type=HiddenShorthandChoice(CoreResourceKinds),
150+
help="""
151+
Reuse the latest identifier of a resource kind. Can be used multiple times.
152+
153+
Only supported for spaces and operations. Ignored if --with is used.""",
154+
),
155+
] = None,
139156
use_default_sample_store: Annotated[
140157
bool,
141158
typer.Option(
142159
"--use-default-sample-store",
143160
rich_help_panel=CREATE_SPACE_PANEL_NAME,
144161
help="Request and use the default sample store. Available only for spaces. "
145-
"Ignored if --set, --use-latest, or --new-sample-store are used."
162+
"Ignored if --with, --use-latest, or --new-sample-store are used. "
146163
"Alias for --set sampleStoreIdentifier=default.",
147164
),
148165
] = False,
@@ -212,6 +229,7 @@ def create_resource(
212229

213230
ado_configuration: AdoConfiguration = ctx.obj
214231
override_values = parse_key_value_pairs(set_values)
232+
with_resources = parse_with_resource_options(with_resources)
215233

216234
parameters = AdoCreateCommandParameters(
217235
ado_configuration=ado_configuration,
@@ -222,6 +240,7 @@ def create_resource(
222240
resource_type=resource_type,
223241
use_default_sample_store=use_default_sample_store,
224242
use_latest=use_latest,
243+
with_resources=with_resources,
225244
)
226245

227246
method_mapping = {
@@ -255,3 +274,63 @@ def register_create_command(app: typer.Typer):
255274
options_metavar="[-f | --file <configuration>] [--set <path=document> ...] "
256275
"[--new-sample-store] [--dry-run]",
257276
)(create_resource)
277+
278+
279+
def parse_with_resource_options(
280+
user_provided_options: list[str] | None,
281+
) -> dict[CoreResourceKinds, pathlib.Path | str]:
282+
283+
parsed_options = {}
284+
if not user_provided_options:
285+
return parsed_options
286+
287+
# To simplify checking whether the resource type is supported
288+
supported_resource_types = {
289+
v.value for v in AdoCreateWithResourceSupportedResourceTypes
290+
}
291+
292+
for with_option in parse_key_value_pairs(user_provided_options):
293+
for resource_type, value in with_option.items():
294+
295+
resource_type = resource_shorthands_to_full_names(resource_type)
296+
if resource_type not in supported_resource_types:
297+
console_print(
298+
f"{ERROR}{bold(resource_type)} is not a supported resource type "
299+
f"for the {magenta('--with')} option.\n"
300+
f"{HINT}The only supported resource types are "
301+
f"[b cyan]{', '.join(supported_resource_types)}[/b cyan] and their shorthands.",
302+
stderr=True,
303+
)
304+
raise typer.Exit(1)
305+
306+
# Now we know the resource_type can be parsed as a CoreResourceKinds
307+
resource_type = CoreResourceKinds(resource_type)
308+
309+
# Since we are dealing with a dictionary, we can only support one --with option per resource type
310+
if resource_type in parsed_options:
311+
console_print(
312+
f"{ERROR}The {resource_type.value} resource type was specified more than once.",
313+
stderr=True,
314+
)
315+
raise typer.Exit(1)
316+
317+
# We allow users to provide either paths to a resource configuration file or a resource identifier.
318+
# In this function we only check whether it's one or the other (with validation on the path). Both
319+
# schema validation for configuration files and resource retrieval for ids will be performed in the
320+
# resource-specific create functions.
321+
#
322+
# If a dot or a path separator character is present in the value, we assume it must be a path and
323+
# fail if it doesn't exist, or it's not a file.
324+
value_must_be_path = "." in value or os.sep in value
325+
value_as_path = pathlib.Path(value)
326+
if value_as_path.exists() and value_as_path.is_file():
327+
parsed_options[resource_type] = value_as_path
328+
elif value_must_be_path:
329+
console_print(
330+
f"{ERROR}{value} does not exist or is not a file.", stderr=True
331+
)
332+
raise typer.Exit(1)
333+
else:
334+
parsed_options[resource_type] = value
335+
336+
return parsed_options

orchestrator/cli/models/parameters.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class AdoCreateCommandParameters(pydantic.BaseModel):
5454
resource_type: AdoCreateSupportedResourceTypes
5555
use_default_sample_store: bool
5656
use_latest: list[CoreResourceKinds] | None
57+
with_resources: dict[CoreResourceKinds, pathlib.Path | str] | None
5758

5859

5960
class AdoDeleteCommandParameters(pydantic.BaseModel):

orchestrator/cli/models/types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ class AdoCreateSupportedResourceTypes(Enum):
5353
SAMPLE_STORE = _SAMPLE_STORE_SINGULAR
5454

5555

56+
class AdoCreateWithResourceSupportedResourceTypes(Enum):
57+
ACTUATOR_CONFIGURATION = _ACTUATOR_CONFIGURATION_SINGULAR
58+
DISCOVERY_SPACE = _DISCOVERY_SPACE_SINGULAR
59+
SAMPLE_STORE = _SAMPLE_STORE_SINGULAR
60+
61+
5662
#################### ado delete ####################
5763
class AdoDeleteSupportedResourceTypes(Enum):
5864
ACTUATOR_CONFIGURATION = _ACTUATOR_CONFIGURATION_SINGULAR

orchestrator/cli/resources/actuator_configuration/create.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def create_actuator_configuration(parameters: AdoCreateCommandParameters):
4444

4545
if parameters.dry_run:
4646
console_print(ADO_CREATE_DRY_RUN_CONFIG_VALID, stderr=True)
47-
return
47+
return None
4848

4949
resource_to_be_created = ActuatorConfigurationResource(
5050
config=actuatorconfig_configuration
@@ -65,3 +65,5 @@ def create_actuator_configuration(parameters: AdoCreateCommandParameters):
6565
f"{magenta(resource_to_be_created.identifier)}",
6666
stderr=True,
6767
)
68+
69+
return resource_to_be_created.identifier

orchestrator/cli/resources/context/create.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def create_context(parameters: AdoCreateCommandParameters):
4040

4141
if parameters.dry_run:
4242
console_print(ADO_CREATE_DRY_RUN_CONFIG_VALID, stderr=True)
43-
return
43+
return None
4444

4545
destination = parameters.ado_configuration.project_context_path_for_context(
4646
context_configuration.project
@@ -80,3 +80,5 @@ def create_context(parameters: AdoCreateCommandParameters):
8080
f"\t{cyan('ado context ' + context_configuration.project)}",
8181
stderr=True,
8282
)
83+
84+
return context_configuration.project

orchestrator/cli/resources/discovery_space/create.py

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from rich.status import Status
88

99
from orchestrator.cli.models.parameters import AdoCreateCommandParameters
10+
from orchestrator.cli.resources.sample_store.create import create_sample_store
1011
from orchestrator.cli.utils.generic.wrappers import get_sql_store
1112
from orchestrator.cli.utils.output.prints import (
1213
ADO_CREATE_DRY_RUN_CONFIG_VALID,
@@ -30,14 +31,21 @@
3031

3132
def create_discovery_space(parameters: AdoCreateCommandParameters):
3233

33-
if (
34-
parameters.new_sample_store
35-
and parameters.use_latest
36-
and CoreResourceKinds.SAMPLESTORE in parameters.use_latest
37-
):
34+
# Fail early if there is an invalid combination of parameters
35+
mutually_exclusive_options = [
36+
parameters.new_sample_store,
37+
parameters.use_latest is not None
38+
and len(parameters.use_latest) >= 0
39+
and CoreResourceKinds.SAMPLESTORE in parameters.use_latest,
40+
parameters.with_resources is not None
41+
and CoreResourceKinds.SAMPLESTORE in parameters.with_resources,
42+
]
43+
44+
if sum(mutually_exclusive_options) >= 2:
3845
console_print(
39-
f"{ERROR}You can only set one of --new-sample-store "
40-
f"and --use-latest {CoreResourceKinds.SAMPLESTORE.value}",
46+
f"{ERROR}You can only set one of --new-sample-store, "
47+
f"--use-latest {CoreResourceKinds.SAMPLESTORE.value}, "
48+
f"--with {CoreResourceKinds.SAMPLESTORE.value}=path/id",
4149
stderr=True,
4250
)
4351
raise typer.Exit(1)
@@ -57,6 +65,35 @@ def create_discovery_space(parameters: AdoCreateCommandParameters):
5765
space_configuration = override_values_in_pydantic_model(
5866
model=space_configuration, override_values=parameters.override_values
5967
)
68+
69+
if (
70+
parameters.with_resources
71+
and CoreResourceKinds.SAMPLESTORE in parameters.with_resources
72+
):
73+
74+
if isinstance(parameters.with_resources[CoreResourceKinds.SAMPLESTORE], str):
75+
space_configuration.sampleStoreIdentifier = parameters.with_resources[
76+
CoreResourceKinds.SAMPLESTORE
77+
]
78+
else:
79+
from orchestrator.cli.models.types import AdoCreateSupportedResourceTypes
80+
81+
space_configuration.sampleStoreIdentifier = create_sample_store(
82+
AdoCreateCommandParameters(
83+
ado_configuration=parameters.ado_configuration,
84+
dry_run=False,
85+
new_sample_store=False,
86+
override_values=[],
87+
resource_configuration_file=parameters.with_resources[
88+
CoreResourceKinds.SAMPLESTORE
89+
],
90+
use_default_sample_store=False,
91+
use_latest=[],
92+
with_resources={},
93+
resource_type=AdoCreateSupportedResourceTypes.SAMPLE_STORE,
94+
)
95+
)
96+
6097
elif (
6198
parameters.use_latest and CoreResourceKinds.SAMPLESTORE in parameters.use_latest
6299
):
@@ -82,6 +119,7 @@ def create_discovery_space(parameters: AdoCreateCommandParameters):
82119
stderr=True,
83120
)
84121
space_configuration.sampleStoreIdentifier = latest_recorded_sample_store
122+
85123
elif parameters.new_sample_store:
86124

87125
# Replay experiments cannot use --new-sample-store
@@ -139,12 +177,13 @@ def create_discovery_space(parameters: AdoCreateCommandParameters):
139177
parameters.ado_configuration.latest_resource_ids[
140178
CoreResourceKinds.SAMPLESTORE
141179
] = sample_store_resource.identifier
180+
142181
elif parameters.use_default_sample_store:
143182
space_configuration.sampleStoreIdentifier = "default"
144183

145184
if parameters.dry_run:
146185
console_print(ADO_CREATE_DRY_RUN_CONFIG_VALID, stderr=True)
147-
return
186+
return None
148187

149188
if space_configuration.sampleStoreIdentifier == "default":
150189
info_message = (
@@ -212,3 +251,5 @@ def create_discovery_space(parameters: AdoCreateCommandParameters):
212251
console_print(
213252
f"{SUCCESS}Created space with identifier: {magenta(space.uri)}", stderr=True
214253
)
254+
255+
return space.uri

0 commit comments

Comments
 (0)