ibm-watsonx-orchestrate 1.0.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 +28 -0
- ibm_watsonx_orchestrate/agent_builder/__init__.py +0 -0
- ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +5 -0
- ibm_watsonx_orchestrate/agent_builder/agents/agent.py +27 -0
- ibm_watsonx_orchestrate/agent_builder/agents/assistant_agent.py +28 -0
- ibm_watsonx_orchestrate/agent_builder/agents/external_agent.py +28 -0
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +204 -0
- ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +27 -0
- ibm_watsonx_orchestrate/agent_builder/connections/connections.py +123 -0
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +260 -0
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base.py +27 -0
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +59 -0
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +243 -0
- ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +4 -0
- ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +36 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +332 -0
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +195 -0
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +162 -0
- ibm_watsonx_orchestrate/agent_builder/utils/__init__.py +0 -0
- ibm_watsonx_orchestrate/agent_builder/utils/pydantic_utils.py +149 -0
- ibm_watsonx_orchestrate/cli/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +192 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +660 -0
- ibm_watsonx_orchestrate/cli/commands/channels/channels_command.py +15 -0
- ibm_watsonx_orchestrate/cli/commands/channels/channels_controller.py +16 -0
- ibm_watsonx_orchestrate/cli/commands/channels/types.py +15 -0
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_command.py +32 -0
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +141 -0
- ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +43 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +307 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +517 -0
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +78 -0
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +189 -0
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +9 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +79 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +201 -0
- ibm_watsonx_orchestrate/cli/commands/login/login_command.py +17 -0
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +128 -0
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +623 -0
- ibm_watsonx_orchestrate/cli/commands/settings/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/settings/observability/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/settings/observability/langfuse/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/settings/observability/langfuse/langfuse_command.py +175 -0
- ibm_watsonx_orchestrate/cli/commands/settings/observability/observability_command.py +11 -0
- ibm_watsonx_orchestrate/cli/commands/settings/settings_command.py +10 -0
- ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +85 -0
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +564 -0
- ibm_watsonx_orchestrate/cli/commands/tools/types.py +10 -0
- ibm_watsonx_orchestrate/cli/config.py +226 -0
- ibm_watsonx_orchestrate/cli/main.py +32 -0
- ibm_watsonx_orchestrate/client/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/agents/agent_client.py +46 -0
- ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +38 -0
- ibm_watsonx_orchestrate/client/agents/external_agent_client.py +38 -0
- ibm_watsonx_orchestrate/client/analytics/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/analytics/llm/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/analytics/llm/analytics_llm_client.py +50 -0
- ibm_watsonx_orchestrate/client/base_api_client.py +113 -0
- ibm_watsonx_orchestrate/client/base_service_instance.py +10 -0
- ibm_watsonx_orchestrate/client/client.py +71 -0
- ibm_watsonx_orchestrate/client/client_errors.py +359 -0
- ibm_watsonx_orchestrate/client/connections/__init__.py +10 -0
- ibm_watsonx_orchestrate/client/connections/connections_client.py +162 -0
- ibm_watsonx_orchestrate/client/connections/utils.py +27 -0
- ibm_watsonx_orchestrate/client/credentials.py +123 -0
- ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +46 -0
- ibm_watsonx_orchestrate/client/local_service_instance.py +91 -0
- ibm_watsonx_orchestrate/client/service_instance.py +73 -0
- ibm_watsonx_orchestrate/client/tools/tool_client.py +41 -0
- ibm_watsonx_orchestrate/client/utils.py +95 -0
- ibm_watsonx_orchestrate/docker/compose-lite.yml +595 -0
- ibm_watsonx_orchestrate/docker/default.env +125 -0
- ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0-py3-none-any.whl +0 -0
- ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0.tar.gz +0 -0
- ibm_watsonx_orchestrate/docker/start-up.sh +61 -0
- ibm_watsonx_orchestrate/docker/tempus/common-config.yaml +1 -0
- ibm_watsonx_orchestrate/run/__init__.py +0 -0
- ibm_watsonx_orchestrate/run/connections.py +40 -0
- ibm_watsonx_orchestrate/utils/__init__.py +0 -0
- ibm_watsonx_orchestrate/utils/logging/__init__.py +0 -0
- ibm_watsonx_orchestrate/utils/logging/logger.py +26 -0
- ibm_watsonx_orchestrate/utils/logging/logging.yaml +18 -0
- ibm_watsonx_orchestrate/utils/utils.py +15 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/METADATA +34 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/RECORD +89 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/WHEEL +4 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/entry_points.txt +2 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,517 @@
|
|
1
|
+
import logging
|
2
|
+
import requests
|
3
|
+
import json
|
4
|
+
import rich
|
5
|
+
import yaml
|
6
|
+
import sys
|
7
|
+
import typer
|
8
|
+
|
9
|
+
from typing import List
|
10
|
+
from ibm_watsonx_orchestrate.client.utils import is_local_dev
|
11
|
+
from ibm_watsonx_orchestrate.agent_builder.connections.types import (
|
12
|
+
ConnectionEnvironment,
|
13
|
+
ConnectionConfiguration,
|
14
|
+
ConnectionSecurityScheme,
|
15
|
+
ConnectionType,
|
16
|
+
IdpConfigData,
|
17
|
+
IdpConfigDataBody,
|
18
|
+
AppConfigData,
|
19
|
+
BasicAuthCredentials,
|
20
|
+
BearerTokenAuthCredentials,
|
21
|
+
APIKeyAuthCredentials,
|
22
|
+
# OAuth2AuthCodeCredentials,
|
23
|
+
# OAuth2ClientCredentials,
|
24
|
+
# OAuth2ImplicitCredentials,
|
25
|
+
# OAuth2PasswordCredentials,
|
26
|
+
OAuthOnBehalfOfCredentials,
|
27
|
+
KeyValueConnectionCredentials,
|
28
|
+
CREDENTIALS,
|
29
|
+
IdentityProviderCredentials,
|
30
|
+
OAUTH_CONNECTION_TYPES
|
31
|
+
|
32
|
+
)
|
33
|
+
|
34
|
+
from ibm_watsonx_orchestrate.client.connections import get_connections_client, get_connection_type
|
35
|
+
|
36
|
+
logger = logging.getLogger(__name__)
|
37
|
+
|
38
|
+
def _validate_connections_spec_content(content: dict) -> None:
|
39
|
+
spec_version = content.get("spec_version")
|
40
|
+
kind = content.get("kind")
|
41
|
+
app_id = content.get("app_id")
|
42
|
+
environments = content.get("environments")
|
43
|
+
|
44
|
+
if not spec_version:
|
45
|
+
logger.error("No 'spec_version' found in provided spec file. Please ensure the spec file is in the correct format")
|
46
|
+
sys.exit(1)
|
47
|
+
if not kind:
|
48
|
+
logger.error("No 'kind' found in provided spec file. Please ensure the spec file is in the correct format")
|
49
|
+
sys.exit(1)
|
50
|
+
if not app_id:
|
51
|
+
logger.error("No 'app_id' found in provided spec file. Please ensure the spec file is in the correct format")
|
52
|
+
sys.exit(1)
|
53
|
+
if not environments or not len(environments):
|
54
|
+
logger.error("No 'environments' found in provided spec file. Please ensure the spec file is in the correct format")
|
55
|
+
sys.exit(1)
|
56
|
+
|
57
|
+
if kind != "connection":
|
58
|
+
logger.error("Field 'kind' must have a value of 'connection'. Please ensure the spec file is a valid connection spec.")
|
59
|
+
sys.exit(1)
|
60
|
+
|
61
|
+
def _create_connection_from_spec(content: dict) -> None:
|
62
|
+
if not content:
|
63
|
+
logger.error("No spec content provided. Please verify the input file is not empty")
|
64
|
+
sys.exit(1)
|
65
|
+
|
66
|
+
client = get_connections_client()
|
67
|
+
|
68
|
+
_validate_connections_spec_content(content=content)
|
69
|
+
|
70
|
+
app_id = content.get("app_id")
|
71
|
+
existing_app = client.get(app_id=app_id)
|
72
|
+
if not existing_app:
|
73
|
+
add_connection(app_id=app_id)
|
74
|
+
|
75
|
+
environments = content.get("environments")
|
76
|
+
for environment in environments:
|
77
|
+
if is_local_dev() and environment != ConnectionEnvironment.DRAFT:
|
78
|
+
logger.warning(f"Local development does not support any environments other than 'draft'. The provided '{environment}' environment configuration will be ignored.")
|
79
|
+
continue
|
80
|
+
config = environments.get(environment)
|
81
|
+
config["environment"] = environment
|
82
|
+
config["app_id"] = app_id
|
83
|
+
config["environment"] = environment
|
84
|
+
config = ConnectionConfiguration.model_validate(config)
|
85
|
+
add_configuration(config)
|
86
|
+
|
87
|
+
def _parse_file(file: str) -> None:
|
88
|
+
if file.endswith('.yaml') or file.endswith('.yml') or file.endswith(".json"):
|
89
|
+
with open(file, 'r') as f:
|
90
|
+
if file.endswith(".json"):
|
91
|
+
content = json.load(f)
|
92
|
+
else:
|
93
|
+
content = yaml.load(f, Loader=yaml.SafeLoader)
|
94
|
+
_create_connection_from_spec(content=content)
|
95
|
+
else:
|
96
|
+
raise ValueError("file must end in .json, .yaml or .yml")
|
97
|
+
|
98
|
+
def _format_token_headers(header_list: List) -> dict:
|
99
|
+
if not header_list or len(header_list) == 0:
|
100
|
+
return None
|
101
|
+
|
102
|
+
header = dict()
|
103
|
+
|
104
|
+
for header_string in header_list:
|
105
|
+
split_header = header_string.split(":")
|
106
|
+
if len(split_header) != 2:
|
107
|
+
logger.error(f"Provided header '{header_string}' is not in the correct format. Please format headers as 'key: value'")
|
108
|
+
sys.exit(1)
|
109
|
+
header_key, header_value = split_header
|
110
|
+
header_key = header_key.strip()
|
111
|
+
header_value = header_value.strip()
|
112
|
+
|
113
|
+
header[header_key] = header_value
|
114
|
+
|
115
|
+
return header
|
116
|
+
|
117
|
+
def _validate_connection_params(type: ConnectionType, **args) -> None:
|
118
|
+
|
119
|
+
if type == ConnectionType.BASIC_AUTH and (
|
120
|
+
args.get('username') is None or args.get('password') is None
|
121
|
+
):
|
122
|
+
raise typer.BadParameter(
|
123
|
+
f"Missing flags --username (-u) and --password (-p) are both required for type {type}"
|
124
|
+
)
|
125
|
+
|
126
|
+
if type == ConnectionType.BEARER_TOKEN and (
|
127
|
+
args.get('token') is None
|
128
|
+
):
|
129
|
+
raise typer.BadParameter(
|
130
|
+
f"Missing flags --token is required for type {type}"
|
131
|
+
)
|
132
|
+
|
133
|
+
if type == ConnectionType.API_KEY_AUTH and (
|
134
|
+
args.get('api_key') is None
|
135
|
+
):
|
136
|
+
raise typer.BadParameter(
|
137
|
+
f"Missing flags --api-key is required for type {type}"
|
138
|
+
)
|
139
|
+
|
140
|
+
# if type in OAUTH_CONNECTION_TYPES and args.get('client_id') is None:
|
141
|
+
# raise typer.BadParameter(
|
142
|
+
# f"Missing flags --client-id is required for type {type}"
|
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
|
+
# )
|
154
|
+
|
155
|
+
# if type in (OAUTH_CONNECTION_TYPES.difference({ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH_ON_BEHALF_OF_FLOW})) and args.get('auth_url') is None:
|
156
|
+
# raise typer.BadParameter(
|
157
|
+
# f"Missing flags --auth-url is required for type {type}"
|
158
|
+
# )
|
159
|
+
|
160
|
+
if type == ConnectionType.OAUTH_ON_BEHALF_OF_FLOW and (
|
161
|
+
args.get('client_id') is None
|
162
|
+
):
|
163
|
+
raise typer.BadParameter(
|
164
|
+
f"Missing flags --client-id is required for type {type}"
|
165
|
+
)
|
166
|
+
|
167
|
+
if type == ConnectionType.OAUTH_ON_BEHALF_OF_FLOW and (
|
168
|
+
args.get('token_url') is None
|
169
|
+
):
|
170
|
+
raise typer.BadParameter(
|
171
|
+
f"Missing flags --token-url is required for type {type}"
|
172
|
+
)
|
173
|
+
|
174
|
+
if type == ConnectionType.OAUTH_ON_BEHALF_OF_FLOW and (
|
175
|
+
args.get('grant_type') is None
|
176
|
+
):
|
177
|
+
raise typer.BadParameter(
|
178
|
+
f"Missing flags --grant-type is required for type {type}"
|
179
|
+
)
|
180
|
+
|
181
|
+
def _parse_entry(entry: str) -> dict[str,str]:
|
182
|
+
split_entry = entry.split('=')
|
183
|
+
if len(split_entry) != 2:
|
184
|
+
message = f"The entry '{entry}' is not in the expected form '<key>=<value>'"
|
185
|
+
logger.error(message)
|
186
|
+
exit(1)
|
187
|
+
return {split_entry[0]: split_entry[1]}
|
188
|
+
|
189
|
+
def _get_credentials(type: ConnectionType, **kwargs):
|
190
|
+
match type:
|
191
|
+
case ConnectionType.BASIC_AUTH:
|
192
|
+
return BasicAuthCredentials(
|
193
|
+
username=kwargs.get("username"),
|
194
|
+
password=kwargs.get("password")
|
195
|
+
)
|
196
|
+
case ConnectionType.BEARER_TOKEN:
|
197
|
+
return BearerTokenAuthCredentials(
|
198
|
+
token=kwargs.get("token")
|
199
|
+
)
|
200
|
+
case ConnectionType.API_KEY_AUTH:
|
201
|
+
return APIKeyAuthCredentials(
|
202
|
+
api_key=kwargs.get("api_key")
|
203
|
+
)
|
204
|
+
# case ConnectionType.OAUTH2_AUTH_CODE:
|
205
|
+
# return OAuth2AuthCodeCredentials(
|
206
|
+
# authorization_url=kwargs.get("auth_url"),
|
207
|
+
# client_id=kwargs.get("client_id"),
|
208
|
+
# client_secret=kwargs.get("client_secret"),
|
209
|
+
# token_url=kwargs.get("token_url")
|
210
|
+
# )
|
211
|
+
# case ConnectionType.OAUTH2_CLIENT_CREDS:
|
212
|
+
# return OAuth2ClientCredentials(
|
213
|
+
# client_id=kwargs.get("client_id"),
|
214
|
+
# client_secret=kwargs.get("client_secret"),
|
215
|
+
# token_url=kwargs.get("token_url")
|
216
|
+
# )
|
217
|
+
# case ConnectionType.OAUTH2_IMPLICIT:
|
218
|
+
# return OAuth2ImplicitCredentials(
|
219
|
+
# authorization_url=kwargs.get("auth_url"),
|
220
|
+
# client_id=kwargs.get("client_id"),
|
221
|
+
# )
|
222
|
+
# case ConnectionType.OAUTH2_PASSWORD:
|
223
|
+
# return OAuth2PasswordCredentials(
|
224
|
+
# authorization_url=kwargs.get("auth_url"),
|
225
|
+
# client_id=kwargs.get("client_id"),
|
226
|
+
# client_secret=kwargs.get("client_secret"),
|
227
|
+
# token_url=kwargs.get("token_url")
|
228
|
+
# )
|
229
|
+
case ConnectionType.OAUTH_ON_BEHALF_OF_FLOW:
|
230
|
+
return OAuthOnBehalfOfCredentials(
|
231
|
+
client_id=kwargs.get("client_id"),
|
232
|
+
access_token_url=kwargs.get("token_url"),
|
233
|
+
grant_type=kwargs.get("grant_type")
|
234
|
+
)
|
235
|
+
case ConnectionType.KEY_VALUE:
|
236
|
+
env = {}
|
237
|
+
for entry in kwargs.get('entries', []):
|
238
|
+
env.update(_parse_entry(entry))
|
239
|
+
|
240
|
+
return KeyValueConnectionCredentials(
|
241
|
+
env
|
242
|
+
)
|
243
|
+
case _:
|
244
|
+
raise ValueError(f"Invalid type '{type}' selected")
|
245
|
+
|
246
|
+
|
247
|
+
def add_configuration(config: ConnectionConfiguration) -> None:
|
248
|
+
client = get_connections_client()
|
249
|
+
app_id = config.app_id
|
250
|
+
environment = config.environment
|
251
|
+
|
252
|
+
try:
|
253
|
+
existing_configuration = client.get_config(app_id=app_id, env=environment)
|
254
|
+
if existing_configuration:
|
255
|
+
logger.info(f"Existing connection '{app_id}' with environment '{environment}' found. Updating configuration")
|
256
|
+
should_delete_credentials = False
|
257
|
+
|
258
|
+
if existing_configuration.security_scheme != config.security_scheme:
|
259
|
+
should_delete_credentials = True
|
260
|
+
logger.warning(f"Detected a change in auth type from '{existing_configuration.security_scheme}' to '{config.security_scheme}'. The associated credentials will be removed.")
|
261
|
+
elif existing_configuration.auth_type != config.auth_type:
|
262
|
+
should_delete_credentials = True
|
263
|
+
logger.warning(f"Detected a change in oauth flow from '{existing_configuration.auth_type}' to '{config.auth_type}'. The associated credentials will be removed.")
|
264
|
+
elif existing_configuration.preference != config.preference:
|
265
|
+
should_delete_credentials = True
|
266
|
+
logger.warning(f"Detected a change in preference/type from '{existing_configuration.preference}' to '{config.preference}'. The associated credentials will be removed.")
|
267
|
+
elif existing_configuration.sso != config.sso:
|
268
|
+
logger.warning(f"Detected a change in sso from '{existing_configuration.sso}' to '{config.sso}'. The associated credentials will be removed.")
|
269
|
+
should_delete_credentials = True
|
270
|
+
|
271
|
+
if should_delete_credentials:
|
272
|
+
try:
|
273
|
+
existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_sso=existing_configuration.sso)
|
274
|
+
if existing_credentials:
|
275
|
+
client.delete_credentials(app_id=app_id, env=environment, use_sso=existing_configuration.sso)
|
276
|
+
except:
|
277
|
+
logger.error(f"Error removing credentials for connection '{app_id}' in environment '{environment}'. No changes have been made to the configuration.")
|
278
|
+
sys.exit(1)
|
279
|
+
|
280
|
+
client.update_config(app_id=app_id, env=environment, payload=config.model_dump(exclude_none=True))
|
281
|
+
logger.info(f"Configuration successfully updated for '{environment}' environment of connection '{app_id}'.")
|
282
|
+
else:
|
283
|
+
logger.info(f"Creating configuration for connection '{app_id}' in the '{environment}' environment")
|
284
|
+
client.create_config(app_id=app_id, payload=config.model_dump())
|
285
|
+
logger.info(f"Configuration successfully created for '{environment}' environment of connection '{app_id}'.")
|
286
|
+
|
287
|
+
except requests.HTTPError as e:
|
288
|
+
response = e.response
|
289
|
+
response_text = response.text
|
290
|
+
logger.error(response_text)
|
291
|
+
exit(1)
|
292
|
+
|
293
|
+
def add_credentials(app_id: str, environment: ConnectionEnvironment, use_sso: bool, credentials: CREDENTIALS) -> None:
|
294
|
+
client = get_connections_client()
|
295
|
+
try:
|
296
|
+
existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_sso=use_sso)
|
297
|
+
if use_sso:
|
298
|
+
payload = {
|
299
|
+
"app_credentials": credentials.model_dump(exclude_none=True)
|
300
|
+
}
|
301
|
+
else:
|
302
|
+
payload = {
|
303
|
+
"runtime_credentials": credentials.model_dump(exclude_none=True)
|
304
|
+
}
|
305
|
+
|
306
|
+
logger.info(f"Setting credentials for environment '{environment}' on connection '{app_id}'")
|
307
|
+
if existing_credentials:
|
308
|
+
client.update_credentials(app_id=app_id, env=environment, use_sso=use_sso, payload=payload)
|
309
|
+
else:
|
310
|
+
client.create_credentials(app_id=app_id,env=environment, use_sso=use_sso, payload=payload)
|
311
|
+
logger.info(f"Credentials successfully set for '{environment}' environment of connection '{app_id}'")
|
312
|
+
except requests.HTTPError as e:
|
313
|
+
response = e.response
|
314
|
+
response_text = response.text
|
315
|
+
logger.error(response_text)
|
316
|
+
exit(1)
|
317
|
+
|
318
|
+
def add_identity_provider(app_id: str, environment: ConnectionEnvironment, idp: IdentityProviderCredentials):
|
319
|
+
client = get_connections_client()
|
320
|
+
|
321
|
+
try:
|
322
|
+
existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_sso=True)
|
323
|
+
|
324
|
+
payload = {
|
325
|
+
"idp_credentials": idp.model_dump()
|
326
|
+
}
|
327
|
+
|
328
|
+
logger.info(f"Setting identity provider for environment '{environment}' on connection '{app_id}'")
|
329
|
+
if existing_credentials:
|
330
|
+
client.update_credentials(app_id=app_id, env=environment, use_sso=True, payload=payload)
|
331
|
+
else:
|
332
|
+
client.create_credentials(app_id=app_id,env=environment, use_sso=True, payload=payload)
|
333
|
+
logger.info(f"Identity provider successfully set for '{environment}' environment of connection '{app_id}'")
|
334
|
+
except requests.HTTPError as e:
|
335
|
+
response = e.response
|
336
|
+
response_text = response.text
|
337
|
+
logger.error(response_text)
|
338
|
+
exit(1)
|
339
|
+
|
340
|
+
def add_connection(app_id: str) -> None:
|
341
|
+
client = get_connections_client()
|
342
|
+
|
343
|
+
try:
|
344
|
+
logger.info(f"Creating connection '{app_id}'")
|
345
|
+
request = {"app_id": app_id}
|
346
|
+
client.create(payload=request)
|
347
|
+
logger.info(f"Successfully created connection '{app_id}'")
|
348
|
+
except requests.HTTPError as e:
|
349
|
+
response = e.response
|
350
|
+
response_text = response.text
|
351
|
+
status_code = response.status_code
|
352
|
+
try:
|
353
|
+
if status_code == 409:
|
354
|
+
response_text = f"Failed to create connection. A connection with the App ID '{app_id}' already exists. Please select a diffrent App ID or delete the existing resource."
|
355
|
+
else:
|
356
|
+
resp = json.loads(response_text)
|
357
|
+
response_text = resp.get('detail')
|
358
|
+
except:
|
359
|
+
pass
|
360
|
+
logger.error(response_text)
|
361
|
+
exit(1)
|
362
|
+
|
363
|
+
def remove_connection(app_id: str) -> None:
|
364
|
+
client = get_connections_client()
|
365
|
+
|
366
|
+
try:
|
367
|
+
logger.info(f"Removing connection '{app_id}'")
|
368
|
+
client.delete(app_id=app_id)
|
369
|
+
logger.info(f"Connection '{app_id}' successfully removed")
|
370
|
+
except requests.HTTPError as e:
|
371
|
+
response = e.response
|
372
|
+
response_text = response.text
|
373
|
+
logger.error(response_text)
|
374
|
+
exit(1)
|
375
|
+
|
376
|
+
def list_connections(environment: ConnectionEnvironment | None, verbose: bool = False) -> None:
|
377
|
+
client = get_connections_client()
|
378
|
+
|
379
|
+
connections = client.list()
|
380
|
+
|
381
|
+
if verbose:
|
382
|
+
connections_list = []
|
383
|
+
for conn in connections:
|
384
|
+
connections_list.append(json.loads(conn.model_dump_json()))
|
385
|
+
|
386
|
+
rich.print_json(json.dumps(connections_list, indent=4))
|
387
|
+
else:
|
388
|
+
non_configured_table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True, title="*Non-Configured")
|
389
|
+
draft_table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True, title="Draft")
|
390
|
+
live_table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True, title="Live")
|
391
|
+
columns = ["App ID", "Auth Type", "Type", "Credentials Set"]
|
392
|
+
for column in columns:
|
393
|
+
draft_table.add_column(column, justify='center', no_wrap=True)
|
394
|
+
live_table.add_column(column, justify='center', no_wrap=True)
|
395
|
+
non_configured_table.add_column(column, justify='center', no_wrap=True)
|
396
|
+
|
397
|
+
for conn in connections:
|
398
|
+
if conn.environment is None:
|
399
|
+
non_configured_table.add_row(
|
400
|
+
conn.app_id,
|
401
|
+
"n/a",
|
402
|
+
"n/a",
|
403
|
+
"❌"
|
404
|
+
)
|
405
|
+
continue
|
406
|
+
|
407
|
+
connection_type = get_connection_type(security_scheme=conn.security_scheme, auth_type=conn.auth_type)
|
408
|
+
|
409
|
+
if conn.environment == ConnectionEnvironment.DRAFT:
|
410
|
+
draft_table.add_row(
|
411
|
+
conn.app_id,
|
412
|
+
connection_type,
|
413
|
+
conn.preference,
|
414
|
+
"✅" if conn.credentials_entered else "❌"
|
415
|
+
)
|
416
|
+
elif conn.environment == ConnectionEnvironment.LIVE:
|
417
|
+
live_table.add_row(
|
418
|
+
conn.app_id,
|
419
|
+
connection_type,
|
420
|
+
conn.preference,
|
421
|
+
"✅" if conn.credentials_entered else "❌"
|
422
|
+
)
|
423
|
+
if environment is None and len(non_configured_table.rows):
|
424
|
+
rich.print(non_configured_table)
|
425
|
+
if environment == ConnectionEnvironment.DRAFT or (environment == None and len(draft_table.rows)):
|
426
|
+
rich.print(draft_table)
|
427
|
+
if environment == ConnectionEnvironment.LIVE or (environment == None and len(live_table.rows)):
|
428
|
+
rich.print(live_table)
|
429
|
+
if environment == None and not len(draft_table.rows) and not len(live_table.rows) and not len(non_configured_table.rows):
|
430
|
+
logger.info("No connections found. You can create connections using `orchestrate connections add`")
|
431
|
+
|
432
|
+
def import_connection(file: str) -> None:
|
433
|
+
_parse_file(file=file)
|
434
|
+
|
435
|
+
def configure_connection(**kwargs) -> None:
|
436
|
+
if is_local_dev() and kwargs.get("environment") != ConnectionEnvironment.DRAFT:
|
437
|
+
logger.error(f"Cannot create configuration for environment '{kwargs.get('environment')}'. Local development does not support any environments other than 'draft'.")
|
438
|
+
sys.exit(1)
|
439
|
+
|
440
|
+
idp_config_body = None
|
441
|
+
if kwargs.get("idp_token_type") or kwargs.get("idp_token_use"):
|
442
|
+
idp_config_body = IdpConfigDataBody(
|
443
|
+
requested_token_type=kwargs.get("idp_token_type"),
|
444
|
+
requested_token_use=kwargs.get("idp_token_use")
|
445
|
+
)
|
446
|
+
|
447
|
+
|
448
|
+
idp_config_data = None
|
449
|
+
if idp_config_body or kwargs.get("idp_token_header"):
|
450
|
+
idp_config_data = IdpConfigData(
|
451
|
+
header=_format_token_headers(kwargs.get("idp_token_header")),
|
452
|
+
body=idp_config_body
|
453
|
+
)
|
454
|
+
|
455
|
+
app_config_data = AppConfigData() if kwargs.get("sso", False) else None
|
456
|
+
if kwargs.get("app_token_header"):
|
457
|
+
app_config_data = AppConfigData(
|
458
|
+
header=_format_token_headers(kwargs.get("app_token_header"))
|
459
|
+
)
|
460
|
+
|
461
|
+
kwargs["idp_config_data"] = idp_config_data
|
462
|
+
kwargs["app_config_data"] = app_config_data
|
463
|
+
|
464
|
+
config = ConnectionConfiguration.model_validate(kwargs)
|
465
|
+
|
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
|
+
add_configuration(config)
|
472
|
+
|
473
|
+
def set_credentials_connection(
|
474
|
+
app_id: str,
|
475
|
+
environment: ConnectionEnvironment,
|
476
|
+
**kwargs
|
477
|
+
) -> None:
|
478
|
+
client = get_connections_client()
|
479
|
+
|
480
|
+
config = client.get_config(app_id=app_id, env=environment)
|
481
|
+
if not config:
|
482
|
+
logger.error(f"No configuration '{environment}' found for connection '{app_id}'. Please create the connection using `orchestrate connections add --app-id {app_id}` then add a configuration `orchestrate connections configure --app-id {app_id} --environment {environment} ...`")
|
483
|
+
sys.exit(1)
|
484
|
+
|
485
|
+
sso_enabled = config.sso
|
486
|
+
conn_type = get_connection_type(security_scheme=config.security_scheme, auth_type=config.auth_type)
|
487
|
+
|
488
|
+
_validate_connection_params(type=conn_type, **kwargs)
|
489
|
+
credentials = _get_credentials(type=conn_type, **kwargs)
|
490
|
+
|
491
|
+
add_credentials(app_id=app_id, environment=environment, use_sso=sso_enabled, credentials=credentials)
|
492
|
+
|
493
|
+
def set_identity_provider_connection(
|
494
|
+
app_id: str,
|
495
|
+
environment: ConnectionEnvironment,
|
496
|
+
**kwargs
|
497
|
+
) -> None:
|
498
|
+
client = get_connections_client()
|
499
|
+
|
500
|
+
config = client.get_config(app_id=app_id, env=environment)
|
501
|
+
if not config:
|
502
|
+
logger.error(f"No configuration '{environment}' found for connection '{app_id}'. Please create the connection using `orchestrate connections add --app-id {app_id}` then add a configuration `orchestrate connections configure --app-id {app_id} --environment {environment} ...`")
|
503
|
+
sys.exit(1)
|
504
|
+
|
505
|
+
sso_enabled = config.sso
|
506
|
+
security_scheme = config.security_scheme
|
507
|
+
|
508
|
+
if security_scheme != ConnectionSecurityScheme.OAUTH2:
|
509
|
+
logger.error(f"Identity providers cannot be set for non-OAuth connection types. The connections specified is of type '{security_scheme}'")
|
510
|
+
sys.exit(1)
|
511
|
+
|
512
|
+
if not sso_enabled:
|
513
|
+
logger.error(f"Cannot set Identity Provider when 'sso' is false in configuration. Please enable sso for connection '{app_id}' in environment '{environment}' and try again.")
|
514
|
+
sys.exit(1)
|
515
|
+
|
516
|
+
idp = IdentityProviderCredentials.model_validate(kwargs)
|
517
|
+
add_identity_provider(app_id=app_id, environment=environment, idp=idp)
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import logging
|
2
|
+
import typer
|
3
|
+
from typing_extensions import Annotated
|
4
|
+
from ibm_watsonx_orchestrate.cli.commands.environment import environment_controller
|
5
|
+
from ibm_watsonx_orchestrate.cli.commands.environment.types import EnvironmentAuthType
|
6
|
+
from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
|
7
|
+
from ibm_watsonx_orchestrate.client.utils import is_local_dev
|
8
|
+
|
9
|
+
logger = logging.getLogger(__name__)
|
10
|
+
|
11
|
+
environment_app = typer.Typer(no_args_is_help=True)
|
12
|
+
|
13
|
+
|
14
|
+
@environment_app.command(name="activate")
|
15
|
+
def activate_env(
|
16
|
+
name: Annotated[
|
17
|
+
str,
|
18
|
+
typer.Argument(),
|
19
|
+
],
|
20
|
+
apikey: Annotated[
|
21
|
+
str,
|
22
|
+
typer.Option(
|
23
|
+
"--apikey", "-a", help="WXO API Key. Leave Blank if developing locally"
|
24
|
+
),
|
25
|
+
] = None,
|
26
|
+
registry: Annotated[
|
27
|
+
RegistryType,
|
28
|
+
typer.Option("--registry", help="Which registry to use when importing python tools", hidden=True),
|
29
|
+
] = None,
|
30
|
+
test_package_version_override: Annotated[
|
31
|
+
str,
|
32
|
+
typer.Option("--test-package-version-override", help="Which prereleased package version to reference when using --registry testpypi", hidden=True),
|
33
|
+
] = None
|
34
|
+
):
|
35
|
+
environment_controller.activate(name=name, apikey=apikey, registry=registry, test_package_version_override=test_package_version_override)
|
36
|
+
|
37
|
+
|
38
|
+
@environment_app.command(name="add")
|
39
|
+
def add_env(
|
40
|
+
name: Annotated[
|
41
|
+
str,
|
42
|
+
typer.Option("--name", "-n", help="Name of the environment you wish to create"),
|
43
|
+
],
|
44
|
+
url: Annotated[
|
45
|
+
str,
|
46
|
+
typer.Option("--url", "-u", help="URL for the watsonX Orchestrate instance"),
|
47
|
+
],
|
48
|
+
activate: Annotated[
|
49
|
+
bool,
|
50
|
+
typer.Option("--activate", "-a", help="Activate the newly created environment"),
|
51
|
+
] = False,
|
52
|
+
iam_url: Annotated[
|
53
|
+
str,
|
54
|
+
typer.Option(
|
55
|
+
"--iam-url", "-i", help="The URL for the IAM token authentication", hidden=True
|
56
|
+
),
|
57
|
+
] = None,
|
58
|
+
type: Annotated[
|
59
|
+
EnvironmentAuthType,
|
60
|
+
typer.Option("--type", "-t", help="The type of auth you wish to use"),
|
61
|
+
] = None
|
62
|
+
):
|
63
|
+
environment_controller.add(name=name, url=url, should_activate=activate, iam_url=iam_url, type=type)
|
64
|
+
|
65
|
+
|
66
|
+
@environment_app.command(name="remove")
|
67
|
+
def remove_env(
|
68
|
+
name: Annotated[
|
69
|
+
str,
|
70
|
+
typer.Option("--name", "-n", help="Name of the environment you wish to create"),
|
71
|
+
],
|
72
|
+
):
|
73
|
+
environment_controller.remove(name=name)
|
74
|
+
|
75
|
+
|
76
|
+
@environment_app.command(name="list")
|
77
|
+
def list_envs():
|
78
|
+
environment_controller.list_envs()
|