openbayes-cli 0.28.2__tar.gz → 0.28.3__tar.gz
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.
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/PKG-INFO +1 -1
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/gear_client.py +16 -1
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/job_run_client.py +39 -25
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/commands/gear.py +33 -25
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/commands/hpc.py +32 -23
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/templates/openbayes_zh-Hans.yaml +12 -5
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/gear_run_usecase.py +30 -25
- openbayes_cli-0.28.3/bayes/usercases/secret_parameter_usecase.py +110 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/pyproject.toml +1 -1
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/README.md +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/__init__.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/__init__.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/base.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/dataset_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/dataset_list_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/dataset_upload_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/dataset_version_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/minio_storage_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/org_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/resource_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/runtime_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/ssh_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/status_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/user_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/commands/__init__.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/commands/data.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/commands/org.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/commands/ssh.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/error.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/__init__.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/dataset.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/dataset_version.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/__init__.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/bayes_file.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/data_bindings.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/openbayes_gear.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/openbayes_ignore.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/openbayes_yaml.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/settings.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/party.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/resource.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/runtime.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/root.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/templates/__init__.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/templates/openbayesignore.yaml +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/__init__.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/archive_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/auth_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/dataset_list_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/dataset_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/dataset_version_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/disk_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/gear_download_usecese.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/gear_logs_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/gear_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/minio_storage_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/open_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/org_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/resource_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/runtime_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/ssh_usecase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/switch_usercase.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/upload_state_manager.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/utils/__init__.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/utils/add_global_param.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/utils.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/openbayestool/__init__.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/openbayestool/config.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/openbayestool/http_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/openbayestool/jwt_client.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/openbayestool/tracker.py +0 -0
- {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/openbayestool/updater.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import sys
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import List, Optional, Union, Tuple
|
|
4
|
+
from typing import Any, List, Optional, Union, Tuple
|
|
5
5
|
|
|
6
6
|
import requests
|
|
7
7
|
from gql.transport.exceptions import TransportQueryError
|
|
@@ -61,6 +61,12 @@ class SourceCode(BaseModel):
|
|
|
61
61
|
id: str
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
class JobParameter(BaseModel):
|
|
65
|
+
name: str
|
|
66
|
+
value: Optional[Any] = None
|
|
67
|
+
isSecret: bool = False
|
|
68
|
+
|
|
69
|
+
|
|
64
70
|
class Job(BaseModel):
|
|
65
71
|
id: Optional[str] = None
|
|
66
72
|
name: Optional[str] = None
|
|
@@ -77,6 +83,8 @@ class Job(BaseModel):
|
|
|
77
83
|
dataBindings: Optional[List[Union[DataBinding, DatasetBinding, JobOutputBinding]]] = Field(default_factory=list)
|
|
78
84
|
sourceCode: Optional[SourceCode] = None
|
|
79
85
|
command: Optional[str] = None
|
|
86
|
+
rawCommand: Optional[str] = None
|
|
87
|
+
parameters: Optional[List[JobParameter]] = Field(default_factory=list)
|
|
80
88
|
children: Optional[List['Job']] = Field(default_factory=list)
|
|
81
89
|
batchParentId: Optional[str] = None
|
|
82
90
|
|
|
@@ -435,17 +443,24 @@ def get_job_by_id(client: BayesGQLClient, id, party_name):
|
|
|
435
443
|
name
|
|
436
444
|
id
|
|
437
445
|
}
|
|
446
|
+
parameters {
|
|
447
|
+
name
|
|
448
|
+
value
|
|
449
|
+
isSecret
|
|
450
|
+
}
|
|
438
451
|
... on Task {
|
|
439
452
|
sourceCode {
|
|
440
453
|
id
|
|
441
454
|
}
|
|
442
455
|
command
|
|
456
|
+
rawCommand
|
|
443
457
|
}
|
|
444
458
|
... on BatchTask {
|
|
445
459
|
sourceCode {
|
|
446
460
|
id
|
|
447
461
|
}
|
|
448
462
|
command
|
|
463
|
+
rawCommand
|
|
449
464
|
size
|
|
450
465
|
children {
|
|
451
466
|
id
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import sys
|
|
3
3
|
from enum import Enum
|
|
4
|
-
from typing import Tuple, Optional, List
|
|
5
|
-
|
|
6
|
-
import pydantic
|
|
4
|
+
from typing import Any, Dict, Tuple, Optional, List
|
|
7
5
|
import typer
|
|
8
6
|
from gql.transport.exceptions import TransportQueryError
|
|
9
7
|
from pydantic import BaseModel, Field
|
|
@@ -53,6 +51,12 @@ class TagInput(BaseModel):
|
|
|
53
51
|
name: Optional[str] = None
|
|
54
52
|
|
|
55
53
|
|
|
54
|
+
class JobParameterInput(BaseModel):
|
|
55
|
+
name: str
|
|
56
|
+
value: Optional[Any] = None
|
|
57
|
+
isSecret: Optional[bool] = False
|
|
58
|
+
|
|
59
|
+
|
|
56
60
|
class VariableInput(BaseModel):
|
|
57
61
|
mode: str
|
|
58
62
|
projectId: str
|
|
@@ -63,6 +67,7 @@ class VariableInput(BaseModel):
|
|
|
63
67
|
newWorkspace: Optional[NewWorkspaceInput] = None
|
|
64
68
|
newBatchWorkspace: Optional[BatchWorkSpace] = None
|
|
65
69
|
dataBindings: Optional[List[DataBinding]] = None
|
|
70
|
+
parameters: Optional[List[JobParameterInput]] = None
|
|
66
71
|
tagNames: Optional[List[TagInput]] = None
|
|
67
72
|
|
|
68
73
|
|
|
@@ -115,14 +120,30 @@ class CreateHypertuningInput(BaseModel):
|
|
|
115
120
|
goal: str
|
|
116
121
|
algorithm: str
|
|
117
122
|
parameterSpecs: List[ParameterSpecInput]
|
|
123
|
+
parameters: Optional[List[JobParameterInput]] = None
|
|
118
124
|
|
|
119
125
|
|
|
120
|
-
def
|
|
126
|
+
def build_job_parameters(parameters: Optional[Dict[str, Any]]) -> List[JobParameterInput]:
|
|
121
127
|
if not parameters:
|
|
122
|
-
return
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
return []
|
|
129
|
+
|
|
130
|
+
result: List[JobParameterInput] = []
|
|
131
|
+
for name, raw_value in parameters.items():
|
|
132
|
+
if isinstance(raw_value, dict) and (
|
|
133
|
+
"value" in raw_value or "isSecret" in raw_value or "is_secret" in raw_value
|
|
134
|
+
):
|
|
135
|
+
result.append(
|
|
136
|
+
JobParameterInput(
|
|
137
|
+
name=name,
|
|
138
|
+
value=raw_value.get("value"),
|
|
139
|
+
isSecret=bool(raw_value.get("is_secret", raw_value.get("isSecret", False))),
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
result.append(JobParameterInput(name=name, value=raw_value, isSecret=False))
|
|
145
|
+
|
|
146
|
+
return result
|
|
126
147
|
|
|
127
148
|
|
|
128
149
|
def get_graphql_binding_datasets(datasets: List[str]) -> List[DataBinding]:
|
|
@@ -164,6 +185,7 @@ def create(party_name, client: BayesGQLClient, mode, project_id, datasets: List[
|
|
|
164
185
|
resource=resource
|
|
165
186
|
)
|
|
166
187
|
input_data.tagNames = [TagInput(name="BUSINESS_CHANNEL_ML")]
|
|
188
|
+
input_data.parameters = build_job_parameters(parameters)
|
|
167
189
|
|
|
168
190
|
upper_mode = mode.upper()
|
|
169
191
|
if upper_mode == DefaultJobModeTask or upper_mode == DefaultBatchJobModeTask:
|
|
@@ -171,14 +193,14 @@ def create(party_name, client: BayesGQLClient, mode, project_id, datasets: List[
|
|
|
171
193
|
input_data.newBatchTask = BatchTask(
|
|
172
194
|
size=node_count,
|
|
173
195
|
code=code_id,
|
|
174
|
-
command=
|
|
196
|
+
command=command
|
|
175
197
|
)
|
|
176
198
|
input_data.mode = "BATCH_TASK"
|
|
177
199
|
else:
|
|
178
200
|
input_data.mode = "TASK"
|
|
179
201
|
input_data.newTask = NewTaskInput(
|
|
180
202
|
code=code_id,
|
|
181
|
-
command=
|
|
203
|
+
command=command
|
|
182
204
|
)
|
|
183
205
|
elif upper_mode == DefaultJobModeWorkSpace or upper_mode == DefaultBatchJobModeWorkSpace:
|
|
184
206
|
if node_count > 1:
|
|
@@ -289,11 +311,6 @@ class DataBindingInput(BaseModel):
|
|
|
289
311
|
bindingAuth: Optional[BindingAuth] = None
|
|
290
312
|
|
|
291
313
|
|
|
292
|
-
class JobParameterInput(BaseModel):
|
|
293
|
-
name: str
|
|
294
|
-
value: pydantic.Json
|
|
295
|
-
|
|
296
|
-
|
|
297
314
|
class RestartWorkspaceInput(BaseModel):
|
|
298
315
|
dataBindings: List[DataBindingInput]
|
|
299
316
|
runtime: str
|
|
@@ -312,7 +329,8 @@ class RestartBatchWorkspaceInput(BaseModel):
|
|
|
312
329
|
|
|
313
330
|
|
|
314
331
|
def create_hypertuning(party_name, client: BayesGQLClient, project_id, datasets, runtime, resource, code_id,
|
|
315
|
-
command, hyper_tuning: HyperTuning
|
|
332
|
+
command, hyper_tuning: HyperTuning,
|
|
333
|
+
parameters: Optional[Dict[str, Any]] = None):
|
|
316
334
|
# 判断hyper_tuning的参数,不为空字符串和None
|
|
317
335
|
if Utils.is_empty_or_none(hyper_tuning.hyperparameter_metric) or Utils.is_empty_or_none(
|
|
318
336
|
hyper_tuning.algorithm) or Utils.is_empty_or_none(hyper_tuning.goal):
|
|
@@ -344,7 +362,8 @@ def create_hypertuning(party_name, client: BayesGQLClient, project_id, datasets,
|
|
|
344
362
|
algorithm=hyper_tuning_algorithm,
|
|
345
363
|
parameterSpecs=parameter_specs_input,
|
|
346
364
|
sideMetrics=hyper_tuning.side_metrics,
|
|
347
|
-
parallelCount=max(DEFAULT_PARALLEL_COUNT, hyper_tuning.parallel_count)
|
|
365
|
+
parallelCount=max(DEFAULT_PARALLEL_COUNT, hyper_tuning.parallel_count),
|
|
366
|
+
parameters=build_job_parameters(parameters) or None,
|
|
348
367
|
)
|
|
349
368
|
|
|
350
369
|
if datasets and len(datasets) > 0:
|
|
@@ -436,20 +455,15 @@ def get_restart_job_graphql_dataBindings(dataBindings: List[str]) -> DataBinding
|
|
|
436
455
|
if len(list_parts) == 2:
|
|
437
456
|
name = list_parts[0]
|
|
438
457
|
path = list_parts[1]
|
|
439
|
-
|
|
440
|
-
binding_type = "OUTPUT" if name.endswith("/output") else None
|
|
458
|
+
# bindingType 由服务器根据 name 是否以 "output" 结尾自动推导,无需客户端传递
|
|
441
459
|
result.append(DataBindingInput(
|
|
442
460
|
name=name,
|
|
443
461
|
path=path,
|
|
444
462
|
bindingAuth=BindingAuthType.READ_ONLY,
|
|
445
|
-
bindingType=binding_type
|
|
446
463
|
))
|
|
447
464
|
elif len(list_parts) == 3:
|
|
448
465
|
name = list_parts[0]
|
|
449
466
|
path = list_parts[1]
|
|
450
|
-
|
|
451
|
-
binding_type = "OUTPUT" if name.endswith("/output") else None
|
|
452
|
-
|
|
453
467
|
auth = list_parts[2].upper()
|
|
454
468
|
if auth == "RO" or auth == "READ_ONLY":
|
|
455
469
|
result.append(DataBindingInput(
|
|
@@ -488,7 +502,7 @@ def restart_workspace(client: BayesGQLClient, party_name, jid, runtime, resource
|
|
|
488
502
|
dataBindings=get_restart_job_graphql_dataBindings(dataBindings) if dataBindings else []
|
|
489
503
|
)
|
|
490
504
|
|
|
491
|
-
variables = {"userId": party_name, "jobId": jid, "input": input_data.model_dump()}
|
|
505
|
+
variables = {"userId": party_name, "jobId": jid, "input": input_data.model_dump(exclude_none=True)}
|
|
492
506
|
|
|
493
507
|
try:
|
|
494
508
|
response = client.exec(query, variables)
|
|
@@ -538,7 +552,7 @@ def restart_batch_workspace(client: BayesGQLClient, party_name, jid, runtime, re
|
|
|
538
552
|
dataBindings=get_restart_job_graphql_dataBindings(dataBindings) if dataBindings else []
|
|
539
553
|
)
|
|
540
554
|
|
|
541
|
-
variables = {"userId": party_name, "jobId": jid, "input": input_data.model_dump()}
|
|
555
|
+
variables = {"userId": party_name, "jobId": jid, "input": input_data.model_dump(exclude_none=True)}
|
|
542
556
|
|
|
543
557
|
try:
|
|
544
558
|
response = client.exec(query, variables)
|
|
@@ -10,7 +10,7 @@ from bayes.model.file.openbayes_yaml import OpenBayesYamlSettings, FILE_NAME, DE
|
|
|
10
10
|
from bayes.model.file.settings import BayesEnvConfig, BayesSettings
|
|
11
11
|
from bayes.model.party import ModeEnum
|
|
12
12
|
from bayes.usercases import auth_usecase, gear_usecase, open_usecase, resource_usecase, runtime_usecase, \
|
|
13
|
-
gear_logs_usecase, gear_download_usecese, gear_run_usecase, minio_storage_usecase
|
|
13
|
+
gear_logs_usecase, gear_download_usecese, gear_run_usecase, minio_storage_usecase, secret_parameter_usecase
|
|
14
14
|
from bayes.utils import Utils
|
|
15
15
|
|
|
16
16
|
app = typer.Typer()
|
|
@@ -218,22 +218,31 @@ def run(
|
|
|
218
218
|
print("准备创建 task,执行命令不能为空")
|
|
219
219
|
raise typer.Exit(code=1)
|
|
220
220
|
|
|
221
|
+
# 在上传源码前校验并解析 secret 参数
|
|
222
|
+
# source: env 写法会在此读取本地环境变量;若环境变量缺失则在此 fail-fast,不浪费上传时间
|
|
223
|
+
raw_parameters = yaml_settings.configuration.parameters if yaml_settings.configuration else {}
|
|
224
|
+
try:
|
|
225
|
+
resolved_parameters = secret_parameter_usecase.resolve_secret_parameters(raw_parameters)
|
|
226
|
+
except ValueError as e:
|
|
227
|
+
print(f"参数校验失败:{e}")
|
|
228
|
+
raise typer.Exit(code=1)
|
|
229
|
+
|
|
221
230
|
# 根据模式设置 source_code_id
|
|
222
231
|
if mode == ModeEnum.task or mode == ModeEnum.hypertuning:
|
|
223
232
|
print("正在上传源代码...")
|
|
224
233
|
source_code_id = minio_storage_usecase.upload_source_code(party_name, str(current_path), "TEMPORARY")
|
|
225
|
-
# source_code_id = minio_storage_usecase.upload_source_code(party_name, "/Users/haohao/test-cli2", "TEMPORARY")
|
|
226
234
|
if source_code_id is None:
|
|
227
235
|
print("源代码上传失败,无法继续创建容器")
|
|
228
236
|
raise typer.Exit(code=1)
|
|
229
237
|
else:
|
|
230
238
|
# workspace 模式下设置为空字符串
|
|
231
239
|
source_code_id = ""
|
|
232
|
-
|
|
240
|
+
|
|
233
241
|
print("正在向服务器请求创建容器...")
|
|
234
242
|
|
|
235
243
|
frontend_value, job_id, err = gear_run_usecase.create(party_name, current_path, "", mode, yaml_settings.configuration,
|
|
236
|
-
data, node, env, resource, source_code_id, task_command, message
|
|
244
|
+
data, node, env, resource, source_code_id, task_command, message,
|
|
245
|
+
parameters_override=resolved_parameters)
|
|
237
246
|
|
|
238
247
|
if err is not None and frontend_value == "":
|
|
239
248
|
print(f"create err:{err}")
|
|
@@ -309,14 +318,12 @@ def restart(ctx: typer.Context,
|
|
|
309
318
|
data: List[str] = typer.Option([], "-d", "--data", help="绑定数据"),
|
|
310
319
|
env: str = typer.Option("", "-e", "--env", help="选择镜像"),
|
|
311
320
|
resource: str = typer.Option("", "-r", "--resource", help="选择算力"),
|
|
312
|
-
follow: bool = typer.Option(False, "-f", "--follow", help="跟踪容器运行;
|
|
321
|
+
follow: bool = typer.Option(False, "-f", "--follow", help="跟踪容器运行;workspace 进入运行后退出"),
|
|
313
322
|
message: str = typer.Option("", "-m", "--message", help="执行描述"),
|
|
314
|
-
open_browser: bool = typer.Option(False, "-o", "--open", help="
|
|
315
|
-
node: int = typer.Option(-1, "-n", "--node", help="指定运行节点数量"),
|
|
316
|
-
extra_args: Optional[List[str]] = typer.Argument(None, help="task command 额外参数")
|
|
323
|
+
open_browser: bool = typer.Option(False, "-o", "--open", help="成功重启后,在浏览器打开"),
|
|
317
324
|
):
|
|
318
325
|
"""
|
|
319
|
-
|
|
326
|
+
重启 workspace 容器(仅支持 workspace / batch_workspace)
|
|
320
327
|
|
|
321
328
|
用法:
|
|
322
329
|
bayes gear restart [任务编码] [选项]
|
|
@@ -327,16 +334,20 @@ def restart(ctx: typer.Context,
|
|
|
327
334
|
|
|
328
335
|
-e, --env string 选择镜像
|
|
329
336
|
|
|
330
|
-
-f, --follow [可选] 跟踪容器运行;
|
|
337
|
+
-f, --follow [可选] 跟踪容器运行;workspace 进入运行后退出
|
|
331
338
|
|
|
332
339
|
-h, --help 查看 restart 的帮助
|
|
333
340
|
|
|
334
341
|
-m, --message string 执行描述
|
|
335
342
|
|
|
336
|
-
-o, --open [可选]
|
|
343
|
+
-o, --open [可选] 重启后,在浏览器打开
|
|
337
344
|
|
|
338
345
|
-r, --resource string 选择算力
|
|
339
346
|
|
|
347
|
+
注意:
|
|
348
|
+
restart 仅支持 workspace / batch_workspace 类型。
|
|
349
|
+
如需重新执行 task,请在项目目录使用 `bayes gear run task`。
|
|
350
|
+
|
|
340
351
|
"""
|
|
341
352
|
# 检查用户是否已登录
|
|
342
353
|
login = auth_usecase.check_login()
|
|
@@ -360,17 +371,17 @@ def restart(ctx: typer.Context,
|
|
|
360
371
|
print("请输入正确的任务编码")
|
|
361
372
|
raise typer.Exit(code=1)
|
|
362
373
|
|
|
374
|
+
if not job.is_workspace():
|
|
375
|
+
print("错误:restart 仅支持 workspace / batch_workspace,不支持 task / batch_task。")
|
|
376
|
+
print("如需重新执行 task,请在项目目录使用 `bayes gear run task`。")
|
|
377
|
+
raise typer.Exit(code=1)
|
|
378
|
+
|
|
363
379
|
project_name = job.project.name
|
|
364
380
|
frontend_url = job.get_link_value("frontend")
|
|
365
381
|
open_url = Utils.replace_last_id(frontend_url, job.id)
|
|
366
382
|
|
|
367
|
-
task_command = ""
|
|
368
|
-
if extra_args:
|
|
369
|
-
task_command = " ".join(extra_args)
|
|
370
|
-
|
|
371
383
|
try:
|
|
372
|
-
new_frontend_value, job_id = gear_run_usecase.restart(id, party_name, data, env, resource
|
|
373
|
-
message)
|
|
384
|
+
new_frontend_value, job_id = gear_run_usecase.restart(id, party_name, data, env, resource)
|
|
374
385
|
except Exception as e:
|
|
375
386
|
print(f"e:{e}")
|
|
376
387
|
print("重启失败")
|
|
@@ -378,22 +389,19 @@ def restart(ctx: typer.Context,
|
|
|
378
389
|
print(f"打开网页 {open_url} 可查看容器 {project_name} 的详细信息")
|
|
379
390
|
raise typer.Exit(code=1)
|
|
380
391
|
|
|
381
|
-
print("
|
|
382
|
-
if new_frontend_value
|
|
383
|
-
|
|
392
|
+
print("容器重启中...")
|
|
393
|
+
new_open_url = Utils.replace_last_id(new_frontend_value, job_id) if new_frontend_value else None
|
|
394
|
+
if new_open_url:
|
|
384
395
|
print(f"打开网页 {new_open_url} 可查看容器 {project_name} 的详细信息")
|
|
385
396
|
|
|
386
397
|
if message != "":
|
|
387
398
|
gear_usecase.update_job_description(party_name, job_id, message)
|
|
388
399
|
|
|
389
|
-
if open_browser:
|
|
400
|
+
if open_browser and new_open_url:
|
|
390
401
|
open_usecase.open_browser(new_open_url)
|
|
391
402
|
|
|
392
403
|
if follow:
|
|
393
|
-
|
|
394
|
-
gear_usecase.follow_status(job_id, party_name, stop_with_running=True)
|
|
395
|
-
else:
|
|
396
|
-
gear_usecase.follow_logs_until_finished(job_id, party_name)
|
|
404
|
+
gear_usecase.follow_status(job_id, party_name, stop_with_running=True)
|
|
397
405
|
gear_usecase.print_last_status(job_id, party_name)
|
|
398
406
|
|
|
399
407
|
|
|
@@ -10,7 +10,7 @@ from bayes.model.file.openbayes_yaml import OpenBayesYamlSettings, FILE_NAME, DE
|
|
|
10
10
|
from bayes.model.file.settings import BayesEnvConfig, BayesSettings
|
|
11
11
|
from bayes.model.party import ModeEnum
|
|
12
12
|
from bayes.usercases import auth_usecase, gear_usecase, open_usecase, resource_usecase, runtime_usecase, \
|
|
13
|
-
gear_logs_usecase, gear_download_usecese, gear_run_usecase, minio_storage_usecase
|
|
13
|
+
gear_logs_usecase, gear_download_usecese, gear_run_usecase, minio_storage_usecase, secret_parameter_usecase
|
|
14
14
|
from bayes.utils import Utils
|
|
15
15
|
|
|
16
16
|
app = typer.Typer()
|
|
@@ -219,6 +219,15 @@ def run(
|
|
|
219
219
|
print("准备创建 task,执行命令不能为空")
|
|
220
220
|
raise typer.Exit(code=1)
|
|
221
221
|
|
|
222
|
+
# 在上传源码前校验并解析 secret 参数
|
|
223
|
+
# source: env 写法会在此读取本地环境变量;若环境变量缺失则在此 fail-fast,不浪费上传时间
|
|
224
|
+
raw_parameters = yaml_settings.configuration.parameters if yaml_settings.configuration else {}
|
|
225
|
+
try:
|
|
226
|
+
resolved_parameters = secret_parameter_usecase.resolve_secret_parameters(raw_parameters)
|
|
227
|
+
except ValueError as e:
|
|
228
|
+
print(f"参数校验失败:{e}")
|
|
229
|
+
raise typer.Exit(code=1)
|
|
230
|
+
|
|
222
231
|
# 根据模式设置 source_code_id
|
|
223
232
|
if mode == ModeEnum.task or mode == ModeEnum.hypertuning:
|
|
224
233
|
print("正在上传源代码...")
|
|
@@ -236,7 +245,8 @@ def run(
|
|
|
236
245
|
frontend_value, job_id, err = gear_run_usecase.create(party_name, current_path, "", mode,
|
|
237
246
|
yaml_settings.configuration,
|
|
238
247
|
data, node, env, resource, source_code_id, task_command,
|
|
239
|
-
message
|
|
248
|
+
message,
|
|
249
|
+
parameters_override=resolved_parameters)
|
|
240
250
|
|
|
241
251
|
if err is not None and frontend_value == "":
|
|
242
252
|
print(f"create err:{err}")
|
|
@@ -310,14 +320,12 @@ def restart(ctx: typer.Context,
|
|
|
310
320
|
data: List[str] = typer.Option([], "-d", "--data", help="绑定数据"),
|
|
311
321
|
env: str = typer.Option("", "-e", "--env", help="选择镜像"),
|
|
312
322
|
resource: str = typer.Option("", "-r", "--resource", help="选择算力"),
|
|
313
|
-
follow: bool = typer.Option(False, "-f", "--follow", help="跟踪容器运行;
|
|
323
|
+
follow: bool = typer.Option(False, "-f", "--follow", help="跟踪容器运行;workspace 进入运行后退出"),
|
|
314
324
|
message: str = typer.Option("", "-m", "--message", help="执行描述"),
|
|
315
|
-
open_browser: bool = typer.Option(False, "-o", "--open", help="
|
|
316
|
-
node: int = typer.Option(-1, "-n", "--node", help="指定运行节点数量"),
|
|
317
|
-
extra_args: Optional[List[str]] = typer.Argument(None, help="task command 额外参数")
|
|
325
|
+
open_browser: bool = typer.Option(False, "-o", "--open", help="成功重启后,在浏览器打开"),
|
|
318
326
|
):
|
|
319
327
|
"""
|
|
320
|
-
|
|
328
|
+
重启 workspace 容器(仅支持 workspace / batch_workspace)
|
|
321
329
|
|
|
322
330
|
用法:
|
|
323
331
|
bayes hpc restart [任务编码] [选项]
|
|
@@ -328,16 +336,20 @@ def restart(ctx: typer.Context,
|
|
|
328
336
|
|
|
329
337
|
-e, --env string 选择镜像
|
|
330
338
|
|
|
331
|
-
-f, --follow [可选] 跟踪容器运行;
|
|
339
|
+
-f, --follow [可选] 跟踪容器运行;workspace 进入运行后退出
|
|
332
340
|
|
|
333
341
|
-h, --help 查看 restart 的帮助
|
|
334
342
|
|
|
335
343
|
-m, --message string 执行描述
|
|
336
344
|
|
|
337
|
-
-o, --open [可选]
|
|
345
|
+
-o, --open [可选] 重启后,在浏览器打开
|
|
338
346
|
|
|
339
347
|
-r, --resource string 选择算力
|
|
340
348
|
|
|
349
|
+
注意:
|
|
350
|
+
restart 仅支持 workspace / batch_workspace 类型。
|
|
351
|
+
如需重新执行 task,请在项目目录使用 `bayes hpc run task`。
|
|
352
|
+
|
|
341
353
|
"""
|
|
342
354
|
# 检查用户是否已登录
|
|
343
355
|
login = auth_usecase.check_login()
|
|
@@ -361,17 +373,17 @@ def restart(ctx: typer.Context,
|
|
|
361
373
|
print("请输入正确的任务编码")
|
|
362
374
|
raise typer.Exit(code=1)
|
|
363
375
|
|
|
376
|
+
if not job.is_workspace():
|
|
377
|
+
print("错误:restart 仅支持 workspace / batch_workspace,不支持 task / batch_task。")
|
|
378
|
+
print("如需重新执行 task,请在项目目录使用 `bayes hpc run task`。")
|
|
379
|
+
raise typer.Exit(code=1)
|
|
380
|
+
|
|
364
381
|
project_name = job.project.name
|
|
365
382
|
frontend_url = job.get_link_value("frontend")
|
|
366
383
|
open_url = Utils.replace_last_id(frontend_url, job.id)
|
|
367
384
|
|
|
368
|
-
task_command = ""
|
|
369
|
-
if extra_args:
|
|
370
|
-
task_command = " ".join(extra_args)
|
|
371
|
-
|
|
372
385
|
try:
|
|
373
|
-
new_frontend_value, job_id = gear_run_usecase.restart(id, party_name, data, env, resource
|
|
374
|
-
message)
|
|
386
|
+
new_frontend_value, job_id = gear_run_usecase.restart(id, party_name, data, env, resource)
|
|
375
387
|
except Exception as e:
|
|
376
388
|
print(f"e:{e}")
|
|
377
389
|
print("重启失败")
|
|
@@ -379,22 +391,19 @@ def restart(ctx: typer.Context,
|
|
|
379
391
|
print(f"打开网页 {open_url} 可查看容器 {project_name} 的详细信息")
|
|
380
392
|
raise typer.Exit(code=1)
|
|
381
393
|
|
|
382
|
-
print("
|
|
383
|
-
if new_frontend_value
|
|
384
|
-
|
|
394
|
+
print("容器重启中...")
|
|
395
|
+
new_open_url = Utils.replace_last_id(new_frontend_value, job_id) if new_frontend_value else None
|
|
396
|
+
if new_open_url:
|
|
385
397
|
print(f"打开网页 {new_open_url} 可查看容器 {project_name} 的详细信息")
|
|
386
398
|
|
|
387
399
|
if message != "":
|
|
388
400
|
gear_usecase.update_job_description(party_name, job_id, message)
|
|
389
401
|
|
|
390
|
-
if open_browser:
|
|
402
|
+
if open_browser and new_open_url:
|
|
391
403
|
open_usecase.open_browser(new_open_url)
|
|
392
404
|
|
|
393
405
|
if follow:
|
|
394
|
-
|
|
395
|
-
gear_usecase.follow_status(job_id, party_name, stop_with_running=True)
|
|
396
|
-
else:
|
|
397
|
-
gear_usecase.follow_logs_until_finished(job_id, party_name)
|
|
406
|
+
gear_usecase.follow_status(job_id, party_name, stop_with_running=True)
|
|
398
407
|
gear_usecase.print_last_status(job_id, party_name)
|
|
399
408
|
|
|
400
409
|
|
|
@@ -43,15 +43,22 @@ command: ""
|
|
|
43
43
|
node: 1
|
|
44
44
|
|
|
45
45
|
## parameters
|
|
46
|
-
# 支持 key / value
|
|
47
|
-
#
|
|
46
|
+
# 支持 key / value 形式的参数,会通过 createJob / restartJob 的 GraphQL `parameters` 字段提交给服务端。
|
|
47
|
+
# 对于 task,非敏感参数会由服务端追加到 command 后面;敏感参数只会作为环境变量注入,不会出现在 command 中。
|
|
48
|
+
#
|
|
49
|
+
# secret 参数支持通过本地环境变量注入,避免明文出现在文件里:
|
|
48
50
|
#
|
|
49
51
|
# parameters:
|
|
50
|
-
# input: /input0
|
|
51
52
|
# epochs: 5
|
|
53
|
+
# HF_TOKEN:
|
|
54
|
+
# is_secret: true
|
|
55
|
+
# source: env # 从本地环境变量读取
|
|
56
|
+
# env: HF_TOKEN # 对应的环境变量名
|
|
57
|
+
#
|
|
58
|
+
# 运行前先在终端执行:export HF_TOKEN=hf_xxx
|
|
59
|
+
# CLI 会在上传源码前读取该变量,若变量不存在则立即报错退出。
|
|
52
60
|
#
|
|
53
|
-
#
|
|
54
|
-
# 并且会在执行命令后面追加 `--input=/input0 --epochs=5`
|
|
61
|
+
# workspace / batch workspace 重启时,CLI 会自动保留已有敏感参数。
|
|
55
62
|
#
|
|
56
63
|
parameters: {}
|
|
57
64
|
|
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import List, Tuple, Dict, Any, Optional
|
|
3
3
|
|
|
4
|
-
import typer
|
|
5
|
-
|
|
6
4
|
from bayes.client import job_run_client, gear_client
|
|
7
5
|
from bayes.client.base import BayesGQLClient
|
|
8
6
|
from bayes.error import Error
|
|
9
7
|
from bayes.model.file.openbayes_yaml import OpenBayesYaml
|
|
10
8
|
from bayes.model.file.settings import BayesEnvConfig, BayesSettings, TypeHyperTuning, DefaultJobModeWorkSpace, \
|
|
11
|
-
DefaultBatchJobModeWorkSpace
|
|
9
|
+
DefaultBatchJobModeWorkSpace
|
|
12
10
|
from bayes.model.party import ModeEnum
|
|
13
11
|
from bayes.usercases import gear_usecase
|
|
14
12
|
|
|
15
13
|
|
|
14
|
+
def _build_restart_parameters(job: gear_client.Job) -> List[job_run_client.JobParameterInput]:
|
|
15
|
+
result: List[job_run_client.JobParameterInput] = []
|
|
16
|
+
for parameter in job.parameters or []:
|
|
17
|
+
result.append(
|
|
18
|
+
job_run_client.JobParameterInput(
|
|
19
|
+
name=parameter.name,
|
|
20
|
+
value=None if parameter.isSecret else parameter.value,
|
|
21
|
+
isSecret=parameter.isSecret,
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
return result
|
|
25
|
+
|
|
26
|
+
|
|
16
27
|
def create(party_name, directory, project_id, mode, yaml: OpenBayesYaml, datasets: List[str], node_count: int, runtime,
|
|
17
|
-
resource, code_id, command, message
|
|
28
|
+
resource, code_id, command, message,
|
|
29
|
+
parameters_override: Optional[Dict[str, Any]] = None) -> Tuple[str, str, Exception]:
|
|
18
30
|
if not project_id:
|
|
19
31
|
project_id, _, project_name, err = gear_usecase.read(directory)
|
|
20
32
|
if err:
|
|
@@ -40,7 +52,9 @@ def create(party_name, directory, project_id, mode, yaml: OpenBayesYaml, dataset
|
|
|
40
52
|
node_count = 1
|
|
41
53
|
|
|
42
54
|
parameters: Dict[str, Any] = {}
|
|
43
|
-
if
|
|
55
|
+
if parameters_override is not None:
|
|
56
|
+
parameters = parameters_override
|
|
57
|
+
elif yaml is not None and yaml.parameters is not None:
|
|
44
58
|
parameters = yaml.parameters
|
|
45
59
|
|
|
46
60
|
if code_id is None and (mode == ModeEnum.task or mode == ModeEnum.hypertuning):
|
|
@@ -56,7 +70,8 @@ def create(party_name, directory, project_id, mode, yaml: OpenBayesYaml, dataset
|
|
|
56
70
|
try:
|
|
57
71
|
hypertuning = job_run_client.create_hypertuning(party_name, gql_client, project_id, datasets, runtime,
|
|
58
72
|
resource,
|
|
59
|
-
code_id, command, yaml.hyper_tuning
|
|
73
|
+
code_id, command, yaml.hyper_tuning,
|
|
74
|
+
parameters=parameters)
|
|
60
75
|
frontend_value = hypertuning.get_link_value("frontend")
|
|
61
76
|
job_id = hypertuning.id
|
|
62
77
|
except Exception as e:
|
|
@@ -79,13 +94,13 @@ def create(party_name, directory, project_id, mode, yaml: OpenBayesYaml, dataset
|
|
|
79
94
|
return frontend_value, job_id, None
|
|
80
95
|
|
|
81
96
|
|
|
82
|
-
def restart(jid, party_name, data, runtime, resource
|
|
97
|
+
def restart(jid, party_name, data, runtime, resource) -> Tuple[str, str]:
|
|
83
98
|
default_env: Optional[BayesEnvConfig] = BayesSettings().default_env
|
|
84
99
|
gql_client = BayesGQLClient(default_env.graphQL, default_env.token)
|
|
85
100
|
|
|
86
101
|
job = gear_client.get_job_by_id(gql_client, jid, party_name)
|
|
87
102
|
if job is None:
|
|
88
|
-
|
|
103
|
+
raise Error(f"job {jid} not found")
|
|
89
104
|
|
|
90
105
|
mode = job.mode
|
|
91
106
|
|
|
@@ -97,30 +112,20 @@ def restart(jid, party_name, data, runtime, resource, task_command, nodeCount, m
|
|
|
97
112
|
if resource == "":
|
|
98
113
|
resource = job.resource.name
|
|
99
114
|
|
|
115
|
+
restart_parameters = _build_restart_parameters(job)
|
|
100
116
|
frontend_value = ""
|
|
101
117
|
job_id = ""
|
|
102
118
|
if mode == DefaultJobModeWorkSpace:
|
|
103
|
-
job = job_run_client.restart_workspace(
|
|
119
|
+
job = job_run_client.restart_workspace(
|
|
120
|
+
gql_client, party_name, jid, runtime, resource, data, restart_parameters
|
|
121
|
+
)
|
|
104
122
|
frontend_value = job.get_link_value("frontend")
|
|
105
123
|
job_id = job.id
|
|
106
124
|
elif mode == DefaultBatchJobModeWorkSpace:
|
|
107
|
-
job = job_run_client.restart_batch_workspace(
|
|
125
|
+
job = job_run_client.restart_batch_workspace(
|
|
126
|
+
gql_client, party_name, jid, runtime, resource, data, restart_parameters
|
|
127
|
+
)
|
|
108
128
|
frontend_value = job.get_link_value("frontend")
|
|
109
129
|
job_id = job.id
|
|
110
|
-
# task 或者 batch task create
|
|
111
|
-
elif mode == DefaultJobModeTask or DefaultBatchJobModeTask:
|
|
112
|
-
if task_command == "" or task_command is None:
|
|
113
|
-
if job.command == "" or job.command is None:
|
|
114
|
-
print("task command 不能为空")
|
|
115
|
-
raise typer.Exit(code=1)
|
|
116
|
-
else:
|
|
117
|
-
task_command = job.command
|
|
118
|
-
|
|
119
|
-
if nodeCount == -1:
|
|
120
|
-
nodeCount = job.children_count()
|
|
121
|
-
|
|
122
|
-
frontend_value, job_id, _ = create(party_name, "", job.project.id, mode, None, data, nodeCount, runtime, resource, job.sourceCode.id, task_command, message)
|
|
123
130
|
|
|
124
131
|
return frontend_value, job_id
|
|
125
|
-
|
|
126
|
-
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def resolve_secret_parameters(parameters: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
|
8
|
+
"""
|
|
9
|
+
在源码上传前解析 openbayes.yaml 的 parameters 字段。
|
|
10
|
+
|
|
11
|
+
处理两类 secret 写法:
|
|
12
|
+
- 推荐写法:is_secret: true + source: env + env: VAR_NAME
|
|
13
|
+
→ 从本地环境变量读取真实值,替换成 {value: <真实值>, is_secret: true}
|
|
14
|
+
→ 若环境变量不存在或为空,立即抛出 ValueError(调用方应在上传前 fail-fast)
|
|
15
|
+
- 兼容写法:is_secret: true + value: <明文>
|
|
16
|
+
→ 打印迁移提示,只含参数名,不含值,放行继续
|
|
17
|
+
- 非法组合(source + value 同时存在、非 secret 参数带 source 等)
|
|
18
|
+
→ 立即抛出 ValueError
|
|
19
|
+
|
|
20
|
+
非 secret 的标量参数不做任何处理,直接透传。
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
parameters: openbayes.yaml 中解析出的 parameters 字典,可为 None
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
解析后的参数字典,source: env 已替换为真实值
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValueError: YAML 配置非法,或环境变量缺失/为空
|
|
30
|
+
"""
|
|
31
|
+
if not parameters:
|
|
32
|
+
return parameters or {}
|
|
33
|
+
|
|
34
|
+
resolved: Dict[str, Any] = {}
|
|
35
|
+
|
|
36
|
+
for name, raw_value in parameters.items():
|
|
37
|
+
# 非 dict 的标量参数(普通非敏感参数),直接透传
|
|
38
|
+
if not isinstance(raw_value, dict):
|
|
39
|
+
resolved[name] = raw_value
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
is_secret = bool(raw_value.get("is_secret", raw_value.get("isSecret", False)))
|
|
43
|
+
source = raw_value.get("source")
|
|
44
|
+
value = raw_value.get("value")
|
|
45
|
+
env_var_name = raw_value.get("env")
|
|
46
|
+
|
|
47
|
+
# 校验:source 只允许出现在 is_secret=true 的参数上
|
|
48
|
+
if source is not None and not is_secret:
|
|
49
|
+
raise ValueError(
|
|
50
|
+
f"参数 '{name}' 配置了 source 但 is_secret 不为 true,这是非法配置,"
|
|
51
|
+
f"请将 is_secret 设为 true 或移除 source 字段"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# 校验:source 与 value 互斥
|
|
55
|
+
if source is not None and value is not None:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"参数 '{name}' 不能同时配置 source 和 value,"
|
|
58
|
+
f"推荐使用 source: env,移除 value 字段"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# 校验:v1 仅支持 source: env
|
|
62
|
+
if source is not None and source != "env":
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"参数 '{name}' 的 source 值 '{source}' 不受支持,目前只支持 source: env"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if source == "env":
|
|
68
|
+
# 推荐写法:从环境变量读取真实值
|
|
69
|
+
if not env_var_name:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"参数 '{name}' 配置了 source: env 但缺少 env 字段,"
|
|
72
|
+
f"请补充 env: <环境变量名>"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
actual_value = os.environ.get(env_var_name)
|
|
76
|
+
# 环境变量不存在或为空字符串,均视为失败
|
|
77
|
+
if not actual_value:
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f"参数 '{name}' 需要环境变量 '{env_var_name}',但该变量不存在或为空,"
|
|
80
|
+
f"请先执行 export {env_var_name}=<your_value>"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
resolved[name] = {
|
|
84
|
+
"value": actual_value,
|
|
85
|
+
"is_secret": True,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
elif is_secret and value is not None:
|
|
89
|
+
# 兼容旧写法:明文 secret,打印迁移提示,放行
|
|
90
|
+
sep = typer.style("─" * 52, fg=typer.colors.YELLOW)
|
|
91
|
+
title = typer.style(
|
|
92
|
+
f"⚠ 警告:参数 '{name}' 的 secret 值以明文写入了 openbayes.yaml",
|
|
93
|
+
fg=typer.colors.YELLOW,
|
|
94
|
+
bold=True,
|
|
95
|
+
)
|
|
96
|
+
typer.echo(f"\n{sep}", err=True)
|
|
97
|
+
typer.echo(title, err=True)
|
|
98
|
+
typer.echo(" 该文件可能随源码一起上传至服务端,建议改用环境变量方式:\n", err=True)
|
|
99
|
+
typer.echo(f" {name}:", err=True)
|
|
100
|
+
typer.echo(f" is_secret: true", err=True)
|
|
101
|
+
typer.echo(f" source: env", err=True)
|
|
102
|
+
typer.echo(f" env: {name}", err=True)
|
|
103
|
+
typer.echo(f"{sep}\n", err=True)
|
|
104
|
+
resolved[name] = raw_value
|
|
105
|
+
|
|
106
|
+
else:
|
|
107
|
+
# 其他 dict 形式参数(如只有 is_secret: true 但没有 value/source),直接透传
|
|
108
|
+
resolved[name] = raw_value
|
|
109
|
+
|
|
110
|
+
return resolved
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|