ibm-watsonx-orchestrate 1.5.1__py3-none-any.whl → 1.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ibm_watsonx_orchestrate/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/agent.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +58 -4
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +34 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
- ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +38 -31
- ibm_watsonx_orchestrate/agent_builder/model_policies/types.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/models/types.py +0 -1
- ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +41 -3
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +2 -1
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +14 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +18 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +153 -21
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -22
- ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +61 -61
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +29 -4
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +74 -8
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +312 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +171 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +31 -25
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +6 -6
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +17 -8
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +147 -21
- ibm_watsonx_orchestrate/cli/commands/server/types.py +2 -1
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -6
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +111 -32
- ibm_watsonx_orchestrate/cli/config.py +2 -0
- ibm_watsonx_orchestrate/cli/main.py +6 -0
- ibm_watsonx_orchestrate/client/agents/agent_client.py +83 -9
- ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +3 -3
- ibm_watsonx_orchestrate/client/agents/external_agent_client.py +2 -2
- ibm_watsonx_orchestrate/client/base_api_client.py +11 -10
- ibm_watsonx_orchestrate/client/connections/connections_client.py +49 -14
- ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
- ibm_watsonx_orchestrate/client/credentials.py +4 -0
- ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
- ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +2 -2
- ibm_watsonx_orchestrate/client/service_instance.py +42 -1
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
- ibm_watsonx_orchestrate/client/utils.py +37 -2
- ibm_watsonx_orchestrate/docker/compose-lite.yml +252 -81
- ibm_watsonx_orchestrate/docker/default.env +40 -15
- ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/__init__.py +3 -2
- ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +77 -0
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/events.py +6 -1
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/flow.py +85 -92
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/types.py +15 -6
- ibm_watsonx_orchestrate/flow_builder/utils.py +215 -0
- ibm_watsonx_orchestrate/run/connections.py +4 -4
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/METADATA +2 -1
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/RECORD +69 -62
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +0 -144
- ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +0 -115
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/__init__.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/data_map.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/constants.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/node.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/resources/flow_status.openapi.yml +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -15,19 +15,19 @@ from ibm_watsonx_orchestrate.agent_builder.connections.types import (
|
|
15
15
|
ConnectionType,
|
16
16
|
IdpConfigData,
|
17
17
|
IdpConfigDataBody,
|
18
|
-
AppConfigData,
|
18
|
+
AppConfigData,
|
19
19
|
BasicAuthCredentials,
|
20
20
|
BearerTokenAuthCredentials,
|
21
21
|
APIKeyAuthCredentials,
|
22
22
|
# OAuth2AuthCodeCredentials,
|
23
|
-
|
23
|
+
OAuth2ClientCredentials,
|
24
24
|
# OAuth2ImplicitCredentials,
|
25
25
|
# OAuth2PasswordCredentials,
|
26
26
|
OAuthOnBehalfOfCredentials,
|
27
27
|
KeyValueConnectionCredentials,
|
28
28
|
CREDENTIALS,
|
29
29
|
IdentityProviderCredentials,
|
30
|
-
OAUTH_CONNECTION_TYPES
|
30
|
+
OAUTH_CONNECTION_TYPES, OAuth2AuthCodeCredentials
|
31
31
|
|
32
32
|
)
|
33
33
|
|
@@ -80,7 +80,6 @@ def _create_connection_from_spec(content: dict) -> None:
|
|
80
80
|
config = environments.get(environment)
|
81
81
|
config["environment"] = environment
|
82
82
|
config["app_id"] = app_id
|
83
|
-
config["environment"] = environment
|
84
83
|
config = ConnectionConfiguration.model_validate(config)
|
85
84
|
add_configuration(config)
|
86
85
|
|
@@ -137,40 +136,31 @@ def _validate_connection_params(type: ConnectionType, **args) -> None:
|
|
137
136
|
f"Missing flags --api-key is required for type {type}"
|
138
137
|
)
|
139
138
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
# if type in (OAUTH_CONNECTION_TYPES.difference({ConnectionType.OAUTH2_IMPLICIT, ConnectionType.OAUTH_ON_BEHALF_OF_FLOW})) and args.get('client_secret') is None:
|
146
|
-
# raise typer.BadParameter(
|
147
|
-
# f"Missing flags --client-secret is required for type {type}"
|
148
|
-
# )
|
149
|
-
|
150
|
-
# if type in (OAUTH_CONNECTION_TYPES.difference({ConnectionType.OAUTH2_IMPLICIT})) and args.get('token_url') is None:
|
151
|
-
# raise typer.BadParameter(
|
152
|
-
# f"Missing flags --token-url is required for type {type}"
|
153
|
-
# )
|
139
|
+
if type in {ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH2_AUTH_CODE} and args.get('client_secret') is None:
|
140
|
+
raise typer.BadParameter(
|
141
|
+
f"Missing flags --client-secret is required for type {type}"
|
142
|
+
)
|
154
143
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
144
|
+
if type in {ConnectionType.OAUTH2_AUTH_CODE} and args.get('auth_url') is None:
|
145
|
+
raise typer.BadParameter(
|
146
|
+
f"Missing flags --auth-url is required for type {type}"
|
147
|
+
)
|
159
148
|
|
160
|
-
if type
|
149
|
+
if type in {ConnectionType.OAUTH_ON_BEHALF_OF_FLOW, ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH2_AUTH_CODE} and (
|
161
150
|
args.get('client_id') is None
|
162
151
|
):
|
163
152
|
raise typer.BadParameter(
|
164
153
|
f"Missing flags --client-id is required for type {type}"
|
165
154
|
)
|
166
155
|
|
167
|
-
if type
|
156
|
+
if type in {ConnectionType.OAUTH_ON_BEHALF_OF_FLOW, ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH2_AUTH_CODE} and (
|
168
157
|
args.get('token_url') is None
|
169
158
|
):
|
170
159
|
raise typer.BadParameter(
|
171
160
|
f"Missing flags --token-url is required for type {type}"
|
172
161
|
)
|
173
162
|
|
163
|
+
|
174
164
|
if type == ConnectionType.OAUTH_ON_BEHALF_OF_FLOW and (
|
175
165
|
args.get('grant_type') is None
|
176
166
|
):
|
@@ -201,19 +191,21 @@ def _get_credentials(type: ConnectionType, **kwargs):
|
|
201
191
|
return APIKeyAuthCredentials(
|
202
192
|
api_key=kwargs.get("api_key")
|
203
193
|
)
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
194
|
+
case ConnectionType.OAUTH2_AUTH_CODE:
|
195
|
+
return OAuth2AuthCodeCredentials(
|
196
|
+
authorization_url=kwargs.get("auth_url"),
|
197
|
+
client_id=kwargs.get("client_id"),
|
198
|
+
client_secret=kwargs.get("client_secret"),
|
199
|
+
token_url=kwargs.get("token_url"),
|
200
|
+
scopes=kwargs.get("scopes")
|
201
|
+
)
|
202
|
+
case ConnectionType.OAUTH2_CLIENT_CREDS:
|
203
|
+
return OAuth2ClientCredentials(
|
204
|
+
client_id=kwargs.get("client_id"),
|
205
|
+
client_secret=kwargs.get("client_secret"),
|
206
|
+
token_url=kwargs.get("token_url"),
|
207
|
+
scopes=kwargs.get("scopes")
|
208
|
+
)
|
217
209
|
# case ConnectionType.OAUTH2_IMPLICIT:
|
218
210
|
# return OAuth2ImplicitCredentials(
|
219
211
|
# authorization_url=kwargs.get("auth_url"),
|
@@ -268,11 +260,14 @@ def add_configuration(config: ConnectionConfiguration) -> None:
|
|
268
260
|
logger.warning(f"Detected a change in sso from '{existing_configuration.sso}' to '{config.sso}'. The associated credentials will be removed.")
|
269
261
|
should_delete_credentials = True
|
270
262
|
|
263
|
+
existing_conn_type = get_connection_type(security_scheme=existing_configuration.security_scheme, auth_type=existing_configuration.auth_type)
|
264
|
+
use_app_credentials = existing_conn_type in OAUTH_CONNECTION_TYPES
|
265
|
+
|
271
266
|
if should_delete_credentials:
|
272
267
|
try:
|
273
|
-
existing_credentials = client.get_credentials(app_id=app_id, env=environment,
|
268
|
+
existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_app_credentials=use_app_credentials)
|
274
269
|
if existing_credentials:
|
275
|
-
client.delete_credentials(app_id=app_id, env=environment,
|
270
|
+
client.delete_credentials(app_id=app_id, env=environment, use_app_credentials=use_app_credentials)
|
276
271
|
except:
|
277
272
|
logger.error(f"Error removing credentials for connection '{app_id}' in environment '{environment}'. No changes have been made to the configuration.")
|
278
273
|
sys.exit(1)
|
@@ -290,11 +285,11 @@ def add_configuration(config: ConnectionConfiguration) -> None:
|
|
290
285
|
logger.error(response_text)
|
291
286
|
exit(1)
|
292
287
|
|
293
|
-
def add_credentials(app_id: str, environment: ConnectionEnvironment,
|
288
|
+
def add_credentials(app_id: str, environment: ConnectionEnvironment, use_app_credentials: bool, credentials: CREDENTIALS) -> None:
|
294
289
|
client = get_connections_client()
|
295
290
|
try:
|
296
|
-
existing_credentials = client.get_credentials(app_id=app_id, env=environment,
|
297
|
-
if
|
291
|
+
existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_app_credentials=use_app_credentials)
|
292
|
+
if use_app_credentials:
|
298
293
|
payload = {
|
299
294
|
"app_credentials": credentials.model_dump(exclude_none=True)
|
300
295
|
}
|
@@ -305,9 +300,9 @@ def add_credentials(app_id: str, environment: ConnectionEnvironment, use_sso: bo
|
|
305
300
|
|
306
301
|
logger.info(f"Setting credentials for environment '{environment}' on connection '{app_id}'")
|
307
302
|
if existing_credentials:
|
308
|
-
client.update_credentials(app_id=app_id, env=environment,
|
303
|
+
client.update_credentials(app_id=app_id, env=environment, use_app_credentials=use_app_credentials, payload=payload)
|
309
304
|
else:
|
310
|
-
client.create_credentials(app_id=app_id,env=environment,
|
305
|
+
client.create_credentials(app_id=app_id,env=environment, use_app_credentials=use_app_credentials, payload=payload)
|
311
306
|
logger.info(f"Credentials successfully set for '{environment}' environment of connection '{app_id}'")
|
312
307
|
except requests.HTTPError as e:
|
313
308
|
response = e.response
|
@@ -319,7 +314,7 @@ def add_identity_provider(app_id: str, environment: ConnectionEnvironment, idp:
|
|
319
314
|
client = get_connections_client()
|
320
315
|
|
321
316
|
try:
|
322
|
-
existing_credentials = client.get_credentials(app_id=app_id, env=environment,
|
317
|
+
existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_app_credentials=True)
|
323
318
|
|
324
319
|
payload = {
|
325
320
|
"idp_credentials": idp.model_dump()
|
@@ -327,9 +322,9 @@ def add_identity_provider(app_id: str, environment: ConnectionEnvironment, idp:
|
|
327
322
|
|
328
323
|
logger.info(f"Setting identity provider for environment '{environment}' on connection '{app_id}'")
|
329
324
|
if existing_credentials:
|
330
|
-
client.update_credentials(app_id=app_id, env=environment,
|
325
|
+
client.update_credentials(app_id=app_id, env=environment, use_app_credentials=True, payload=payload)
|
331
326
|
else:
|
332
|
-
client.create_credentials(app_id=app_id,env=environment,
|
327
|
+
client.create_credentials(app_id=app_id,env=environment, use_app_credentials=True, payload=payload)
|
333
328
|
logger.info(f"Identity provider successfully set for '{environment}' environment of connection '{app_id}'")
|
334
329
|
except requests.HTTPError as e:
|
335
330
|
response = e.response
|
@@ -375,12 +370,14 @@ def remove_connection(app_id: str) -> None:
|
|
375
370
|
|
376
371
|
def list_connections(environment: ConnectionEnvironment | None, verbose: bool = False) -> None:
|
377
372
|
client = get_connections_client()
|
378
|
-
|
379
373
|
connections = client.list()
|
380
|
-
|
374
|
+
is_local = is_local_dev()
|
375
|
+
|
381
376
|
if verbose:
|
382
377
|
connections_list = []
|
383
378
|
for conn in connections:
|
379
|
+
if is_local and conn.environment == ConnectionEnvironment.LIVE:
|
380
|
+
continue
|
384
381
|
connections_list.append(json.loads(conn.model_dump_json()))
|
385
382
|
|
386
383
|
rich.print_json(json.dumps(connections_list, indent=4))
|
@@ -388,11 +385,17 @@ def list_connections(environment: ConnectionEnvironment | None, verbose: bool =
|
|
388
385
|
non_configured_table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True, title="*Non-Configured")
|
389
386
|
draft_table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True, title="Draft")
|
390
387
|
live_table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True, title="Live")
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
388
|
+
default_args = {"justify": "center", "no_wrap": True}
|
389
|
+
column_args = {
|
390
|
+
"App ID": {"overflow": "fold"},
|
391
|
+
"Auth Type": {},
|
392
|
+
"Type": {},
|
393
|
+
"Credentials Set/ Connected": {}
|
394
|
+
}
|
395
|
+
for column in column_args:
|
396
|
+
draft_table.add_column(column,**default_args, **column_args[column])
|
397
|
+
live_table.add_column(column,**default_args, **column_args[column])
|
398
|
+
non_configured_table.add_column(column,**default_args, **column_args[column])
|
396
399
|
|
397
400
|
for conn in connections:
|
398
401
|
if conn.environment is None:
|
@@ -413,7 +416,7 @@ def list_connections(environment: ConnectionEnvironment | None, verbose: bool =
|
|
413
416
|
conn.preference,
|
414
417
|
"✅" if conn.credentials_entered else "❌"
|
415
418
|
)
|
416
|
-
elif conn.environment == ConnectionEnvironment.LIVE:
|
419
|
+
elif conn.environment == ConnectionEnvironment.LIVE and not is_local:
|
417
420
|
live_table.add_row(
|
418
421
|
conn.app_id,
|
419
422
|
connection_type,
|
@@ -437,6 +440,7 @@ def configure_connection(**kwargs) -> None:
|
|
437
440
|
logger.error(f"Cannot create configuration for environment '{kwargs.get('environment')}'. Local development does not support any environments other than 'draft'.")
|
438
441
|
sys.exit(1)
|
439
442
|
|
443
|
+
|
440
444
|
idp_config_body = None
|
441
445
|
if kwargs.get("idp_token_type") or kwargs.get("idp_token_use"):
|
442
446
|
idp_config_body = IdpConfigDataBody(
|
@@ -463,11 +467,6 @@ def configure_connection(**kwargs) -> None:
|
|
463
467
|
|
464
468
|
config = ConnectionConfiguration.model_validate(kwargs)
|
465
469
|
|
466
|
-
# TODO: Remove once Oauth is supported on local
|
467
|
-
if config.security_scheme == ConnectionSecurityScheme.OAUTH2 and is_local_dev():
|
468
|
-
logger.error("Use of OAuth connections unsupported for local development at this time.")
|
469
|
-
sys.exit(1)
|
470
|
-
|
471
470
|
add_configuration(config)
|
472
471
|
|
473
472
|
def set_credentials_connection(
|
@@ -484,11 +483,12 @@ def set_credentials_connection(
|
|
484
483
|
|
485
484
|
sso_enabled = config.sso
|
486
485
|
conn_type = get_connection_type(security_scheme=config.security_scheme, auth_type=config.auth_type)
|
486
|
+
use_app_credentials = conn_type in OAUTH_CONNECTION_TYPES
|
487
487
|
|
488
488
|
_validate_connection_params(type=conn_type, **kwargs)
|
489
489
|
credentials = _get_credentials(type=conn_type, **kwargs)
|
490
490
|
|
491
|
-
add_credentials(app_id=app_id, environment=environment,
|
491
|
+
add_credentials(app_id=app_id, environment=environment, use_app_credentials=use_app_credentials, credentials=credentials)
|
492
492
|
|
493
493
|
def set_identity_provider_connection(
|
494
494
|
app_id: str,
|
@@ -5,6 +5,7 @@ from ibm_watsonx_orchestrate.cli.commands.environment import environment_control
|
|
5
5
|
from ibm_watsonx_orchestrate.cli.commands.environment.types import EnvironmentAuthType
|
6
6
|
from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
|
7
7
|
from ibm_watsonx_orchestrate.client.utils import is_local_dev
|
8
|
+
import sys
|
8
9
|
|
9
10
|
logger = logging.getLogger(__name__)
|
10
11
|
|
@@ -20,7 +21,19 @@ def activate_env(
|
|
20
21
|
apikey: Annotated[
|
21
22
|
str,
|
22
23
|
typer.Option(
|
23
|
-
"--
|
24
|
+
"--api-key", "-a", help="WXO or CPD API Key. Leave Blank if developing locally. For CPD, either a Passoword or Apikey is accepted for CPD, but not both."
|
25
|
+
),
|
26
|
+
] = None,
|
27
|
+
username: Annotated[
|
28
|
+
str,
|
29
|
+
typer.Option(
|
30
|
+
"--username", "-u", help="Username specifically for CPD Environments."
|
31
|
+
),
|
32
|
+
] = None,
|
33
|
+
password: Annotated[
|
34
|
+
str,
|
35
|
+
typer.Option(
|
36
|
+
"--password", "-p", help="Password specifically for CPD Environments. Either a Passoword or Apikey is accepted for CPD, but not both."
|
24
37
|
),
|
25
38
|
] = None,
|
26
39
|
registry: Annotated[
|
@@ -32,7 +45,7 @@ def activate_env(
|
|
32
45
|
typer.Option("--test-package-version-override", help="Which prereleased package version to reference when using --registry testpypi", hidden=True),
|
33
46
|
] = None
|
34
47
|
):
|
35
|
-
environment_controller.activate(name=name, apikey=apikey, registry=registry, test_package_version_override=test_package_version_override)
|
48
|
+
environment_controller.activate(name=name, apikey=apikey,username=username, password=password, registry=registry, test_package_version_override=test_package_version_override)
|
36
49
|
|
37
50
|
|
38
51
|
@environment_app.command(name="add")
|
@@ -58,9 +71,21 @@ def add_env(
|
|
58
71
|
type: Annotated[
|
59
72
|
EnvironmentAuthType,
|
60
73
|
typer.Option("--type", "-t", help="The type of auth you wish to use"),
|
61
|
-
] = None
|
74
|
+
] = None,
|
75
|
+
insecure: Annotated[
|
76
|
+
bool,
|
77
|
+
typer.Option("--insecure", help="Ignore SSL validation errors. Used for CPD Environments only"),
|
78
|
+
] = False,
|
79
|
+
verify: Annotated[
|
80
|
+
str,
|
81
|
+
typer.Option("--verify", help="Path to SSL Cert. Used for CPD Environments only"),
|
82
|
+
] = None,
|
62
83
|
):
|
63
|
-
|
84
|
+
if insecure and verify:
|
85
|
+
logger.error("Please choose either '--insecure' or '--verify' but not both.")
|
86
|
+
sys.exit(1)
|
87
|
+
|
88
|
+
environment_controller.add(name=name, url=url, should_activate=activate, iam_url=iam_url, type=type, insecure=insecure, verify=verify)
|
64
89
|
|
65
90
|
|
66
91
|
@environment_app.command(name="remove")
|
@@ -2,6 +2,7 @@ import logging
|
|
2
2
|
import rich
|
3
3
|
import jwt
|
4
4
|
import getpass
|
5
|
+
import sys
|
5
6
|
|
6
7
|
from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
|
7
8
|
from ibm_watsonx_orchestrate.cli.config import (
|
@@ -17,14 +18,15 @@ from ibm_watsonx_orchestrate.cli.config import (
|
|
17
18
|
ENV_IAM_URL_OPT,
|
18
19
|
ENVIRONMENTS_SECTION_HEADER,
|
19
20
|
PROTECTED_ENV_NAME,
|
20
|
-
ENV_AUTH_TYPE, PYTHON_REGISTRY_HEADER, PYTHON_REGISTRY_TYPE_OPT, PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT,
|
21
|
+
ENV_AUTH_TYPE, PYTHON_REGISTRY_HEADER, PYTHON_REGISTRY_TYPE_OPT, PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT, BYPASS_SSL, VERIFY,
|
21
22
|
DEFAULT_CONFIG_FILE_CONTENT
|
22
23
|
)
|
23
24
|
from ibm_watsonx_orchestrate.client.client import Client
|
24
25
|
from ibm_watsonx_orchestrate.client.client_errors import ClientError
|
26
|
+
from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient, ClientAPIException
|
25
27
|
from ibm_watsonx_orchestrate.client.credentials import Credentials
|
26
28
|
from threading import Lock
|
27
|
-
from ibm_watsonx_orchestrate.client.utils import is_local_dev, check_token_validity
|
29
|
+
from ibm_watsonx_orchestrate.client.utils import is_local_dev, check_token_validity, is_cpd_env
|
28
30
|
from ibm_watsonx_orchestrate.cli.commands.environment.types import EnvironmentAuthType
|
29
31
|
|
30
32
|
logger = logging.getLogger(__name__)
|
@@ -42,7 +44,33 @@ def _decode_token(token: str, is_local: bool = False) -> dict:
|
|
42
44
|
logger.error("Invalid token format")
|
43
45
|
raise e
|
44
46
|
|
45
|
-
|
47
|
+
|
48
|
+
def _validate_token_functionality(token: str, url: str) -> None:
|
49
|
+
'''
|
50
|
+
Validates a token by making a request to GET /agents
|
51
|
+
|
52
|
+
Args:
|
53
|
+
token: A JWT token
|
54
|
+
url: WXO instance URL
|
55
|
+
'''
|
56
|
+
is_cpd = is_cpd_env(url)
|
57
|
+
if is_cpd is True:
|
58
|
+
agent_client = AgentClient(base_url=url, api_key=token, is_local=is_local_dev(url), verify=False)
|
59
|
+
else:
|
60
|
+
agent_client = AgentClient(base_url=url, api_key=token, is_local=is_local_dev(url))
|
61
|
+
agent_client.api_key = token
|
62
|
+
|
63
|
+
try:
|
64
|
+
agent_client.get()
|
65
|
+
except ClientAPIException as e:
|
66
|
+
if e.response.status_code >= 400:
|
67
|
+
reason = e.response.reason
|
68
|
+
logger.error(f"Failed to authenticate to provided instance '{url}'. Reason: '{reason}'. Please ensure provider URL and API key are valid.")
|
69
|
+
sys.exit(1)
|
70
|
+
raise e
|
71
|
+
|
72
|
+
|
73
|
+
def _login(name: str, apikey: str = None, username: str = None, password: str = None) -> None:
|
46
74
|
cfg = Config()
|
47
75
|
auth_cfg = Config(AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE)
|
48
76
|
|
@@ -55,14 +83,43 @@ def _login(name: str, apikey: str = None) -> None:
|
|
55
83
|
except (KeyError, AttributeError):
|
56
84
|
auth_type = None
|
57
85
|
|
86
|
+
username = username
|
87
|
+
apikey = apikey
|
88
|
+
password = password
|
89
|
+
|
90
|
+
if is_cpd_env(url):
|
91
|
+
if username is None:
|
92
|
+
username = getpass.getpass("Please enter CPD Username: ")
|
93
|
+
|
94
|
+
if not apikey and not password:
|
95
|
+
apikey = getpass.getpass("Enter CPD API key (or leave blank to use password): ")
|
96
|
+
if not apikey and not password:
|
97
|
+
password = getpass.getpass("Enter CPD password (or leave blank if you used API key): ")
|
98
|
+
|
99
|
+
if apikey and password:
|
100
|
+
logger.error("For CPD, please use either an Apikey or a Password but not both.")
|
101
|
+
sys.exit(1)
|
102
|
+
|
103
|
+
if not apikey and not password:
|
104
|
+
logger.error("For CPD, you must provide either an API key or a password.")
|
105
|
+
sys.exit(1)
|
106
|
+
|
58
107
|
|
59
|
-
if apikey
|
108
|
+
if not apikey and not password and not is_local and auth_type != "cpd":
|
60
109
|
apikey = getpass.getpass("Please enter WXO API key: ")
|
61
110
|
|
62
111
|
try:
|
63
|
-
creds = Credentials(
|
112
|
+
creds = Credentials(
|
113
|
+
url=url,
|
114
|
+
api_key=apikey,
|
115
|
+
username=username,
|
116
|
+
password=password,
|
117
|
+
iam_url=iam_url,
|
118
|
+
auth_type=auth_type
|
119
|
+
)
|
64
120
|
client = Client(creds)
|
65
121
|
token = _decode_token(client.token, is_local)
|
122
|
+
_validate_token_functionality(token=token.get(AUTH_MCSP_TOKEN_OPT), url=url)
|
66
123
|
with lock:
|
67
124
|
auth_cfg.save(
|
68
125
|
{
|
@@ -74,7 +131,7 @@ def _login(name: str, apikey: str = None) -> None:
|
|
74
131
|
except ClientError as e:
|
75
132
|
raise ClientError(e)
|
76
133
|
|
77
|
-
def activate(name: str, apikey: str=None, registry: RegistryType=None, test_package_version_override=None) -> None:
|
134
|
+
def activate(name: str, apikey: str=None, username: str=None, password: str=None, registry: RegistryType=None, test_package_version_override=None) -> None:
|
78
135
|
cfg = Config()
|
79
136
|
auth_cfg = Config(AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE)
|
80
137
|
env_cfg = cfg.read(ENVIRONMENTS_SECTION_HEADER, name)
|
@@ -92,7 +149,7 @@ def activate(name: str, apikey: str=None, registry: RegistryType=None, test_pack
|
|
92
149
|
existing_token = existing_auth_config.get(AUTH_MCSP_TOKEN_OPT) if existing_auth_config else None
|
93
150
|
|
94
151
|
if not check_token_validity(existing_token) or is_local:
|
95
|
-
_login(name=name, apikey=apikey)
|
152
|
+
_login(name=name, apikey=apikey, username=username, password=password)
|
96
153
|
|
97
154
|
with lock:
|
98
155
|
cfg.write(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT, name)
|
@@ -104,8 +161,11 @@ def activate(name: str, apikey: str=None, registry: RegistryType=None, test_pack
|
|
104
161
|
cfg.write(PYTHON_REGISTRY_HEADER, PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT, test_package_version_override)
|
105
162
|
|
106
163
|
logger.info(f"Environment '{name}' is now active")
|
164
|
+
is_cpd = is_cpd_env(url)
|
165
|
+
if is_cpd:
|
166
|
+
logger.warning("Support for CPD clusters is currently an early access preview")
|
107
167
|
|
108
|
-
def add(name: str, url: str, should_activate: bool=False, iam_url: str=None, type: EnvironmentAuthType=None) -> None:
|
168
|
+
def add(name: str, url: str, should_activate: bool=False, iam_url: str=None, type: EnvironmentAuthType=None, insecure: bool=None, verify: str=None) -> None:
|
109
169
|
if name == PROTECTED_ENV_NAME:
|
110
170
|
logger.error(f"The name '{PROTECTED_ENV_NAME}' is a reserved environment name. Please select a diffrent name or use `orchestrate env activate {PROTECTED_ENV_NAME}` to swap to '{PROTECTED_ENV_NAME}'")
|
111
171
|
return
|
@@ -124,6 +184,12 @@ def add(name: str, url: str, should_activate: bool=False, iam_url: str=None, typ
|
|
124
184
|
cfg.write(ENVIRONMENTS_SECTION_HEADER, name, {ENV_IAM_URL_OPT: iam_url})
|
125
185
|
if type:
|
126
186
|
cfg.write(ENVIRONMENTS_SECTION_HEADER, name, {ENV_AUTH_TYPE: str(type)})
|
187
|
+
if insecure:
|
188
|
+
cfg.write(ENVIRONMENTS_SECTION_HEADER, name, {BYPASS_SSL: insecure})
|
189
|
+
cfg.write(ENVIRONMENTS_SECTION_HEADER, name, {VERIFY: 'None'})
|
190
|
+
if verify:
|
191
|
+
cfg.write(ENVIRONMENTS_SECTION_HEADER, name, {VERIFY: verify})
|
192
|
+
cfg.write(ENVIRONMENTS_SECTION_HEADER, name, {BYPASS_SSL: False})
|
127
193
|
|
128
194
|
|
129
195
|
logger.info(f"Environment '{name}' has been created")
|