9
9
10
10
from sagemaker .hyperpod .cli .constants .init_constants import (
11
11
USAGE_GUIDE_TEXT ,
12
- CFN
12
+ CFN ,
13
+ CRD
13
14
)
14
15
from sagemaker .hyperpod .cluster_management .hp_cluster_stack import HpClusterStack
15
16
import json
22
23
validate_config_against_model ,
23
24
filter_validation_errors_for_user_input ,
24
25
display_validation_results ,
25
- extract_user_provided_args_from_cli ,
26
26
build_config_from_schema ,
27
27
save_template
28
28
)
29
29
30
30
@click .command ("init" )
31
31
@click .argument ("template" , type = click .Choice (list (TEMPLATES .keys ())))
32
32
@click .argument ("directory" , type = click .Path (file_okay = False ), default = "." )
33
- @click .option ("--namespace" , "-n" , default = "default" , help = "Namespace, default to default" )
34
33
@click .option ("--version" , "-v" , default = "1.0" , help = "Schema version" )
35
34
@generate_click_command (require_schema_fields = False )
36
35
def init (
37
36
template : str ,
38
37
directory : str ,
39
- namespace : str ,
40
38
version : str ,
41
39
model_config , # Pydantic model from decorator
42
40
):
@@ -102,7 +100,7 @@ def init(
102
100
# 3) Build config dict + comment map, then write config.yaml
103
101
try :
104
102
# Use the common function to build config from schema
105
- full_cfg , comment_map = build_config_from_schema (template , namespace , version , model_config )
103
+ full_cfg , comment_map = build_config_from_schema (template , version , model_config )
106
104
107
105
save_config_yaml (
108
106
prefill = full_cfg ,
@@ -144,16 +142,15 @@ def init(
144
142
def reset ():
145
143
"""
146
144
Reset the current directory's config.yaml to an "empty" scaffold:
147
- all schema keys set to default values (but keeping the template and namespace ).
145
+ all schema keys set to default values (but keeping the template and version ).
148
146
"""
149
147
dir_path = Path ("." ).resolve ()
150
148
151
149
# 1) Load and validate config
152
150
data , template , version = load_config (dir_path )
153
- namespace = data .get ("namespace" , "default" )
154
151
155
152
# 2) Build config with default values from schema
156
- full_cfg , comment_map = build_config_from_schema (template , namespace , version )
153
+ full_cfg , comment_map = build_config_from_schema (template , version )
157
154
158
155
# 3) Overwrite config.yaml
159
156
try :
@@ -175,67 +172,65 @@ def reset():
175
172
@click .command ("configure" )
176
173
@generate_click_command (
177
174
require_schema_fields = False , # flags are all optional
178
- auto_load_config = True , # load template/namespace/ version from config.yaml
175
+ auto_load_config = True , # load template/version from config.yaml
179
176
)
180
177
@click .pass_context
181
178
def configure (ctx , model_config ):
182
179
"""
183
- Update any subset of fields in ./config.yaml by passing
184
- --<field> flags. E.g.
185
-
186
- hyp configure --model-name my-model --instance-type ml.m5.large
187
- hyp configure --namespace production
188
- hyp configure --namespace test --stage gamma
189
- """
190
- # Extract namespace from command line arguments manually
191
- import sys
192
- namespace = None
193
- args = sys .argv
194
- for i , arg in enumerate (args ):
195
- if arg in ['--namespace' , '-n' ] and i + 1 < len (args ):
196
- namespace = args [i + 1 ]
197
- break
180
+ Update any subset of fields in ./config.yaml by passing --<field> flags.
181
+
182
+ This command allows you to modify specific configuration fields without having
183
+ to regenerate the entire config or fix unrelated validation issues. Only the
184
+ fields you explicitly provide will be validated, making it easy to update
185
+ configurations incrementally.
186
+
187
+ Examples:
188
+
189
+ # Update a single field
190
+ hyp configure --hyperpod-cluster-name my-new-cluster
191
+
192
+ # Update multiple fields at once
193
+ hyp configure --instance-type ml.g5.xlarge --endpoint-name my-endpoint
194
+
195
+ # Update complex fields with JSON
196
+ hyp configure --tags '{"Environment": "prod", "Team": "ml"}'
198
197
198
+ """
199
199
# 1) Load existing config without validation
200
200
dir_path = Path ("." ).resolve ()
201
201
data , template , version = load_config (dir_path )
202
202
203
- # Use provided namespace or fall back to existing config namespace
204
- config_namespace = namespace if namespace is not None else data .get ("namespace" , "default" )
205
-
206
- # 2) Extract ONLY the user's input arguments by checking what was actually provided
207
- provided_args = extract_user_provided_args_from_cli ()
203
+ # 2) Determine which fields the user actually provided
204
+ # Use Click's parameter source tracking to identify command-line provided parameters
205
+ user_input_fields = set ()
208
206
209
- # Filter model_config to only include user-provided fields
210
- all_model_data = model_config .model_dump (exclude_none = True ) if model_config else {}
211
- user_input = {k : v for k , v in all_model_data .items () if k in provided_args }
207
+ if ctx and hasattr (ctx , 'params' ) and model_config :
208
+ # Check which parameters were provided via command line (not defaults)
209
+ for param_name , param_value in ctx .params .items ():
210
+ # Skip if the parameter source indicates it came from default
211
+ param_source = ctx .get_parameter_source (param_name )
212
+ if param_source and param_source .name == 'COMMANDLINE' :
213
+ user_input_fields .add (param_name )
212
214
213
- if not user_input and namespace is None :
215
+ if not user_input_fields :
214
216
click .secho ("⚠️ No arguments provided to configure." , fg = "yellow" )
215
217
return
216
218
217
219
# 3) Build merged config with user input
218
220
full_cfg , comment_map = build_config_from_schema (
219
221
template = template ,
220
- namespace = config_namespace ,
221
222
version = version ,
222
223
model_config = model_config ,
223
224
existing_config = data
224
225
)
225
226
226
- # 4) Validate the merged config and filter errors for user input fields only
227
+ # 4) Validate the merged config, but only check user-provided fields
227
228
all_validation_errors = validate_config_against_model (full_cfg , template , version )
228
-
229
- # Include namespace in user input fields if it was provided
230
- user_input_fields = set (user_input .keys ())
231
- if namespace is not None :
232
- user_input_fields .add ("namespace" )
233
-
234
229
user_input_errors = filter_validation_errors_for_user_input (all_validation_errors , user_input_fields )
235
230
236
231
is_valid = display_validation_results (
237
232
user_input_errors ,
238
- success_message = "User input is valid!" if user_input_errors else "Merged configuration is valid !" ,
233
+ success_message = "User input is valid!" if user_input_errors else "Configuration updated successfully !" ,
239
234
error_prefix = "Invalid input arguments:"
240
235
)
241
236
@@ -376,6 +371,22 @@ def submit(region):
376
371
from sagemaker .hyperpod .cli .commands .cluster_stack import create_cluster_stack_helper
377
372
create_cluster_stack_helper (config_file = f"{ out_dir } /config.yaml" ,
378
373
region = region )
374
+ else :
375
+ dir_path = Path ("." ).resolve ()
376
+ data , template , version = load_config (dir_path )
377
+ namespace = data .get ("namespace" , "default" )
378
+ registry = TEMPLATES [template ]["registry" ]
379
+ model = registry .get (version )
380
+ if model :
381
+ filtered_config = {
382
+ k : v for k , v in data .items ()
383
+ if k not in ('template' , 'version' ) and v is not None
384
+ }
385
+ flat = model (** filtered_config )
386
+ domain = flat .to_domain ()
387
+ domain .create (namespace = namespace )
388
+
389
+
379
390
except Exception as e :
380
391
click .secho (f"❌ Failed to sumbit the command: { e } " , fg = "red" )
381
392
sys .exit (1 )
0 commit comments