workato-platform-cli 1.0.0rc5.dev5__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.
- workato_platform_cli/__init__.py +135 -0
- workato_platform_cli/_version.py +34 -0
- workato_platform_cli/cli/__init__.py +126 -0
- workato_platform_cli/cli/commands/__init__.py +0 -0
- workato_platform_cli/cli/commands/api_clients.py +627 -0
- workato_platform_cli/cli/commands/api_collections.py +497 -0
- workato_platform_cli/cli/commands/assets.py +82 -0
- workato_platform_cli/cli/commands/connections.py +1205 -0
- workato_platform_cli/cli/commands/connectors/__init__.py +0 -0
- workato_platform_cli/cli/commands/connectors/command.py +178 -0
- workato_platform_cli/cli/commands/connectors/connector_manager.py +351 -0
- workato_platform_cli/cli/commands/data_tables.py +345 -0
- workato_platform_cli/cli/commands/guide.py +315 -0
- workato_platform_cli/cli/commands/init.py +229 -0
- workato_platform_cli/cli/commands/profiles.py +364 -0
- workato_platform_cli/cli/commands/projects/__init__.py +0 -0
- workato_platform_cli/cli/commands/projects/command.py +513 -0
- workato_platform_cli/cli/commands/projects/project_manager.py +338 -0
- workato_platform_cli/cli/commands/properties.py +174 -0
- workato_platform_cli/cli/commands/pull.py +327 -0
- workato_platform_cli/cli/commands/push/__init__.py +0 -0
- workato_platform_cli/cli/commands/push/command.py +320 -0
- workato_platform_cli/cli/commands/recipes/__init__.py +0 -0
- workato_platform_cli/cli/commands/recipes/command.py +847 -0
- workato_platform_cli/cli/commands/recipes/validator.py +1740 -0
- workato_platform_cli/cli/commands/workspace.py +73 -0
- workato_platform_cli/cli/containers.py +80 -0
- workato_platform_cli/cli/resources/data/connection-data.json +7364 -0
- workato_platform_cli/cli/resources/data/picklist-data.json +3706 -0
- workato_platform_cli/cli/resources/docs/README.md +178 -0
- workato_platform_cli/cli/resources/docs/actions.md +452 -0
- workato_platform_cli/cli/resources/docs/block-structure.md +424 -0
- workato_platform_cli/cli/resources/docs/connections-parameters.md +11946 -0
- workato_platform_cli/cli/resources/docs/data-mapping.md +779 -0
- workato_platform_cli/cli/resources/docs/formulas/array-list-formulas.md +1276 -0
- workato_platform_cli/cli/resources/docs/formulas/conditions.md +102 -0
- workato_platform_cli/cli/resources/docs/formulas/date-formulas.md +798 -0
- workato_platform_cli/cli/resources/docs/formulas/number-formulas.md +507 -0
- workato_platform_cli/cli/resources/docs/formulas/other-formulas.md +419 -0
- workato_platform_cli/cli/resources/docs/formulas/string-formulas.md +1353 -0
- workato_platform_cli/cli/resources/docs/formulas.md +214 -0
- workato_platform_cli/cli/resources/docs/naming-conventions.md +163 -0
- workato_platform_cli/cli/resources/docs/recipe-deployment-workflow.md +352 -0
- workato_platform_cli/cli/resources/docs/recipe-fundamentals.md +179 -0
- workato_platform_cli/cli/resources/docs/triggers.md +360 -0
- workato_platform_cli/cli/utils/__init__.py +10 -0
- workato_platform_cli/cli/utils/config/__init__.py +33 -0
- workato_platform_cli/cli/utils/config/manager.py +1001 -0
- workato_platform_cli/cli/utils/config/models.py +89 -0
- workato_platform_cli/cli/utils/config/profiles.py +491 -0
- workato_platform_cli/cli/utils/config/workspace.py +113 -0
- workato_platform_cli/cli/utils/exception_handler.py +531 -0
- workato_platform_cli/cli/utils/gitignore.py +32 -0
- workato_platform_cli/cli/utils/ignore_patterns.py +44 -0
- workato_platform_cli/cli/utils/spinner.py +63 -0
- workato_platform_cli/cli/utils/version_checker.py +237 -0
- workato_platform_cli/client/__init__.py +0 -0
- workato_platform_cli/client/workato_api/__init__.py +202 -0
- workato_platform_cli/client/workato_api/api/__init__.py +15 -0
- workato_platform_cli/client/workato_api/api/api_platform_api.py +2875 -0
- workato_platform_cli/client/workato_api/api/connections_api.py +1807 -0
- workato_platform_cli/client/workato_api/api/connectors_api.py +840 -0
- workato_platform_cli/client/workato_api/api/data_tables_api.py +604 -0
- workato_platform_cli/client/workato_api/api/export_api.py +621 -0
- workato_platform_cli/client/workato_api/api/folders_api.py +621 -0
- workato_platform_cli/client/workato_api/api/packages_api.py +1197 -0
- workato_platform_cli/client/workato_api/api/projects_api.py +590 -0
- workato_platform_cli/client/workato_api/api/properties_api.py +620 -0
- workato_platform_cli/client/workato_api/api/recipes_api.py +1379 -0
- workato_platform_cli/client/workato_api/api/users_api.py +285 -0
- workato_platform_cli/client/workato_api/api_client.py +807 -0
- workato_platform_cli/client/workato_api/api_response.py +21 -0
- workato_platform_cli/client/workato_api/configuration.py +601 -0
- workato_platform_cli/client/workato_api/docs/APIPlatformApi.md +844 -0
- workato_platform_cli/client/workato_api/docs/ApiClient.md +46 -0
- workato_platform_cli/client/workato_api/docs/ApiClientApiCollectionsInner.md +30 -0
- workato_platform_cli/client/workato_api/docs/ApiClientApiPoliciesInner.md +30 -0
- workato_platform_cli/client/workato_api/docs/ApiClientCreateRequest.md +46 -0
- workato_platform_cli/client/workato_api/docs/ApiClientListResponse.md +32 -0
- workato_platform_cli/client/workato_api/docs/ApiClientResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/ApiCollection.md +38 -0
- workato_platform_cli/client/workato_api/docs/ApiCollectionCreateRequest.md +32 -0
- workato_platform_cli/client/workato_api/docs/ApiEndpoint.md +41 -0
- workato_platform_cli/client/workato_api/docs/ApiKey.md +36 -0
- workato_platform_cli/client/workato_api/docs/ApiKeyCreateRequest.md +32 -0
- workato_platform_cli/client/workato_api/docs/ApiKeyListResponse.md +32 -0
- workato_platform_cli/client/workato_api/docs/ApiKeyResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/Asset.md +39 -0
- workato_platform_cli/client/workato_api/docs/AssetReference.md +37 -0
- workato_platform_cli/client/workato_api/docs/Connection.md +44 -0
- workato_platform_cli/client/workato_api/docs/ConnectionCreateRequest.md +35 -0
- workato_platform_cli/client/workato_api/docs/ConnectionUpdateRequest.md +34 -0
- workato_platform_cli/client/workato_api/docs/ConnectionsApi.md +526 -0
- workato_platform_cli/client/workato_api/docs/ConnectorAction.md +33 -0
- workato_platform_cli/client/workato_api/docs/ConnectorVersion.md +32 -0
- workato_platform_cli/client/workato_api/docs/ConnectorsApi.md +249 -0
- workato_platform_cli/client/workato_api/docs/CreateExportManifestRequest.md +29 -0
- workato_platform_cli/client/workato_api/docs/CreateFolderRequest.md +30 -0
- workato_platform_cli/client/workato_api/docs/CustomConnector.md +35 -0
- workato_platform_cli/client/workato_api/docs/CustomConnectorCodeResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/CustomConnectorCodeResponseData.md +29 -0
- workato_platform_cli/client/workato_api/docs/CustomConnectorListResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/DataTable.md +34 -0
- workato_platform_cli/client/workato_api/docs/DataTableColumn.md +37 -0
- workato_platform_cli/client/workato_api/docs/DataTableColumnRequest.md +37 -0
- workato_platform_cli/client/workato_api/docs/DataTableCreateRequest.md +31 -0
- workato_platform_cli/client/workato_api/docs/DataTableCreateResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/DataTableListResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/DataTableRelation.md +30 -0
- workato_platform_cli/client/workato_api/docs/DataTablesApi.md +172 -0
- workato_platform_cli/client/workato_api/docs/DeleteProject403Response.md +29 -0
- workato_platform_cli/client/workato_api/docs/Error.md +29 -0
- workato_platform_cli/client/workato_api/docs/ExportApi.md +175 -0
- workato_platform_cli/client/workato_api/docs/ExportManifestRequest.md +35 -0
- workato_platform_cli/client/workato_api/docs/ExportManifestResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/ExportManifestResponseResult.md +36 -0
- workato_platform_cli/client/workato_api/docs/Folder.md +35 -0
- workato_platform_cli/client/workato_api/docs/FolderAssetsResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/FolderAssetsResponseResult.md +29 -0
- workato_platform_cli/client/workato_api/docs/FolderCreationResponse.md +35 -0
- workato_platform_cli/client/workato_api/docs/FoldersApi.md +176 -0
- workato_platform_cli/client/workato_api/docs/ImportResults.md +32 -0
- workato_platform_cli/client/workato_api/docs/OAuthUrlResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/OAuthUrlResponseData.md +29 -0
- workato_platform_cli/client/workato_api/docs/OpenApiSpec.md +30 -0
- workato_platform_cli/client/workato_api/docs/PackageDetailsResponse.md +35 -0
- workato_platform_cli/client/workato_api/docs/PackageDetailsResponseRecipeStatusInner.md +30 -0
- workato_platform_cli/client/workato_api/docs/PackageResponse.md +33 -0
- workato_platform_cli/client/workato_api/docs/PackagesApi.md +364 -0
- workato_platform_cli/client/workato_api/docs/PicklistRequest.md +30 -0
- workato_platform_cli/client/workato_api/docs/PicklistResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/PlatformConnector.md +36 -0
- workato_platform_cli/client/workato_api/docs/PlatformConnectorListResponse.md +32 -0
- workato_platform_cli/client/workato_api/docs/Project.md +32 -0
- workato_platform_cli/client/workato_api/docs/ProjectsApi.md +173 -0
- workato_platform_cli/client/workato_api/docs/PropertiesApi.md +186 -0
- workato_platform_cli/client/workato_api/docs/Recipe.md +58 -0
- workato_platform_cli/client/workato_api/docs/RecipeConfigInner.md +33 -0
- workato_platform_cli/client/workato_api/docs/RecipeConnectionUpdateRequest.md +30 -0
- workato_platform_cli/client/workato_api/docs/RecipeListResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/RecipeStartResponse.md +31 -0
- workato_platform_cli/client/workato_api/docs/RecipesApi.md +367 -0
- workato_platform_cli/client/workato_api/docs/RuntimeUserConnectionCreateRequest.md +34 -0
- workato_platform_cli/client/workato_api/docs/RuntimeUserConnectionResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/RuntimeUserConnectionResponseData.md +30 -0
- workato_platform_cli/client/workato_api/docs/SuccessResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/UpsertProjectPropertiesRequest.md +29 -0
- workato_platform_cli/client/workato_api/docs/User.md +48 -0
- workato_platform_cli/client/workato_api/docs/UsersApi.md +84 -0
- workato_platform_cli/client/workato_api/docs/ValidationError.md +30 -0
- workato_platform_cli/client/workato_api/docs/ValidationErrorErrorsValue.md +28 -0
- workato_platform_cli/client/workato_api/exceptions.py +216 -0
- workato_platform_cli/client/workato_api/models/__init__.py +83 -0
- workato_platform_cli/client/workato_api/models/api_client.py +185 -0
- workato_platform_cli/client/workato_api/models/api_client_api_collections_inner.py +89 -0
- workato_platform_cli/client/workato_api/models/api_client_api_policies_inner.py +89 -0
- workato_platform_cli/client/workato_api/models/api_client_create_request.py +138 -0
- workato_platform_cli/client/workato_api/models/api_client_list_response.py +101 -0
- workato_platform_cli/client/workato_api/models/api_client_response.py +91 -0
- workato_platform_cli/client/workato_api/models/api_collection.py +110 -0
- workato_platform_cli/client/workato_api/models/api_collection_create_request.py +97 -0
- workato_platform_cli/client/workato_api/models/api_endpoint.py +117 -0
- workato_platform_cli/client/workato_api/models/api_key.py +102 -0
- workato_platform_cli/client/workato_api/models/api_key_create_request.py +93 -0
- workato_platform_cli/client/workato_api/models/api_key_list_response.py +101 -0
- workato_platform_cli/client/workato_api/models/api_key_response.py +91 -0
- workato_platform_cli/client/workato_api/models/asset.py +124 -0
- workato_platform_cli/client/workato_api/models/asset_reference.py +110 -0
- workato_platform_cli/client/workato_api/models/connection.py +173 -0
- workato_platform_cli/client/workato_api/models/connection_create_request.py +99 -0
- workato_platform_cli/client/workato_api/models/connection_update_request.py +97 -0
- workato_platform_cli/client/workato_api/models/connector_action.py +100 -0
- workato_platform_cli/client/workato_api/models/connector_version.py +99 -0
- workato_platform_cli/client/workato_api/models/create_export_manifest_request.py +91 -0
- workato_platform_cli/client/workato_api/models/create_folder_request.py +89 -0
- workato_platform_cli/client/workato_api/models/custom_connector.py +117 -0
- workato_platform_cli/client/workato_api/models/custom_connector_code_response.py +91 -0
- workato_platform_cli/client/workato_api/models/custom_connector_code_response_data.py +87 -0
- workato_platform_cli/client/workato_api/models/custom_connector_list_response.py +95 -0
- workato_platform_cli/client/workato_api/models/data_table.py +107 -0
- workato_platform_cli/client/workato_api/models/data_table_column.py +125 -0
- workato_platform_cli/client/workato_api/models/data_table_column_request.py +130 -0
- workato_platform_cli/client/workato_api/models/data_table_create_request.py +99 -0
- workato_platform_cli/client/workato_api/models/data_table_create_response.py +91 -0
- workato_platform_cli/client/workato_api/models/data_table_list_response.py +95 -0
- workato_platform_cli/client/workato_api/models/data_table_relation.py +90 -0
- workato_platform_cli/client/workato_api/models/delete_project403_response.py +87 -0
- workato_platform_cli/client/workato_api/models/error.py +87 -0
- workato_platform_cli/client/workato_api/models/export_manifest_request.py +107 -0
- workato_platform_cli/client/workato_api/models/export_manifest_response.py +91 -0
- workato_platform_cli/client/workato_api/models/export_manifest_response_result.py +112 -0
- workato_platform_cli/client/workato_api/models/folder.py +110 -0
- workato_platform_cli/client/workato_api/models/folder_assets_response.py +91 -0
- workato_platform_cli/client/workato_api/models/folder_assets_response_result.py +95 -0
- workato_platform_cli/client/workato_api/models/folder_creation_response.py +110 -0
- workato_platform_cli/client/workato_api/models/import_results.py +93 -0
- workato_platform_cli/client/workato_api/models/o_auth_url_response.py +91 -0
- workato_platform_cli/client/workato_api/models/o_auth_url_response_data.py +87 -0
- workato_platform_cli/client/workato_api/models/open_api_spec.py +96 -0
- workato_platform_cli/client/workato_api/models/package_details_response.py +126 -0
- workato_platform_cli/client/workato_api/models/package_details_response_recipe_status_inner.py +99 -0
- workato_platform_cli/client/workato_api/models/package_response.py +109 -0
- workato_platform_cli/client/workato_api/models/picklist_request.py +89 -0
- workato_platform_cli/client/workato_api/models/picklist_response.py +88 -0
- workato_platform_cli/client/workato_api/models/platform_connector.py +116 -0
- workato_platform_cli/client/workato_api/models/platform_connector_list_response.py +101 -0
- workato_platform_cli/client/workato_api/models/project.py +93 -0
- workato_platform_cli/client/workato_api/models/recipe.py +174 -0
- workato_platform_cli/client/workato_api/models/recipe_config_inner.py +100 -0
- workato_platform_cli/client/workato_api/models/recipe_connection_update_request.py +89 -0
- workato_platform_cli/client/workato_api/models/recipe_list_response.py +95 -0
- workato_platform_cli/client/workato_api/models/recipe_start_response.py +91 -0
- workato_platform_cli/client/workato_api/models/runtime_user_connection_create_request.py +97 -0
- workato_platform_cli/client/workato_api/models/runtime_user_connection_response.py +91 -0
- workato_platform_cli/client/workato_api/models/runtime_user_connection_response_data.py +89 -0
- workato_platform_cli/client/workato_api/models/success_response.py +87 -0
- workato_platform_cli/client/workato_api/models/upsert_project_properties_request.py +88 -0
- workato_platform_cli/client/workato_api/models/user.py +151 -0
- workato_platform_cli/client/workato_api/models/validation_error.py +102 -0
- workato_platform_cli/client/workato_api/models/validation_error_errors_value.py +143 -0
- workato_platform_cli/client/workato_api/rest.py +213 -0
- workato_platform_cli/client/workato_api/test/__init__.py +0 -0
- workato_platform_cli/client/workato_api/test/test_api_client.py +94 -0
- workato_platform_cli/client/workato_api/test/test_api_client_api_collections_inner.py +52 -0
- workato_platform_cli/client/workato_api/test/test_api_client_api_policies_inner.py +52 -0
- workato_platform_cli/client/workato_api/test/test_api_client_create_request.py +75 -0
- workato_platform_cli/client/workato_api/test/test_api_client_list_response.py +114 -0
- workato_platform_cli/client/workato_api/test/test_api_client_response.py +104 -0
- workato_platform_cli/client/workato_api/test/test_api_collection.py +72 -0
- workato_platform_cli/client/workato_api/test/test_api_collection_create_request.py +57 -0
- workato_platform_cli/client/workato_api/test/test_api_endpoint.py +75 -0
- workato_platform_cli/client/workato_api/test/test_api_key.py +64 -0
- workato_platform_cli/client/workato_api/test/test_api_key_create_request.py +56 -0
- workato_platform_cli/client/workato_api/test/test_api_key_list_response.py +78 -0
- workato_platform_cli/client/workato_api/test/test_api_key_response.py +68 -0
- workato_platform_cli/client/workato_api/test/test_api_platform_api.py +101 -0
- workato_platform_cli/client/workato_api/test/test_asset.py +67 -0
- workato_platform_cli/client/workato_api/test/test_asset_reference.py +62 -0
- workato_platform_cli/client/workato_api/test/test_connection.py +81 -0
- workato_platform_cli/client/workato_api/test/test_connection_create_request.py +59 -0
- workato_platform_cli/client/workato_api/test/test_connection_update_request.py +56 -0
- workato_platform_cli/client/workato_api/test/test_connections_api.py +73 -0
- workato_platform_cli/client/workato_api/test/test_connector_action.py +59 -0
- workato_platform_cli/client/workato_api/test/test_connector_version.py +58 -0
- workato_platform_cli/client/workato_api/test/test_connectors_api.py +52 -0
- workato_platform_cli/client/workato_api/test/test_create_export_manifest_request.py +88 -0
- workato_platform_cli/client/workato_api/test/test_create_folder_request.py +53 -0
- workato_platform_cli/client/workato_api/test/test_custom_connector.py +76 -0
- workato_platform_cli/client/workato_api/test/test_custom_connector_code_response.py +54 -0
- workato_platform_cli/client/workato_api/test/test_custom_connector_code_response_data.py +52 -0
- workato_platform_cli/client/workato_api/test/test_custom_connector_list_response.py +82 -0
- workato_platform_cli/client/workato_api/test/test_data_table.py +88 -0
- workato_platform_cli/client/workato_api/test/test_data_table_column.py +72 -0
- workato_platform_cli/client/workato_api/test/test_data_table_column_request.py +64 -0
- workato_platform_cli/client/workato_api/test/test_data_table_create_request.py +82 -0
- workato_platform_cli/client/workato_api/test/test_data_table_create_response.py +90 -0
- workato_platform_cli/client/workato_api/test/test_data_table_list_response.py +94 -0
- workato_platform_cli/client/workato_api/test/test_data_table_relation.py +54 -0
- workato_platform_cli/client/workato_api/test/test_data_tables_api.py +45 -0
- workato_platform_cli/client/workato_api/test/test_delete_project403_response.py +51 -0
- workato_platform_cli/client/workato_api/test/test_error.py +52 -0
- workato_platform_cli/client/workato_api/test/test_export_api.py +45 -0
- workato_platform_cli/client/workato_api/test/test_export_manifest_request.py +69 -0
- workato_platform_cli/client/workato_api/test/test_export_manifest_response.py +68 -0
- workato_platform_cli/client/workato_api/test/test_export_manifest_response_result.py +66 -0
- workato_platform_cli/client/workato_api/test/test_folder.py +64 -0
- workato_platform_cli/client/workato_api/test/test_folder_assets_response.py +80 -0
- workato_platform_cli/client/workato_api/test/test_folder_assets_response_result.py +78 -0
- workato_platform_cli/client/workato_api/test/test_folder_creation_response.py +64 -0
- workato_platform_cli/client/workato_api/test/test_folders_api.py +45 -0
- workato_platform_cli/client/workato_api/test/test_import_results.py +58 -0
- workato_platform_cli/client/workato_api/test/test_o_auth_url_response.py +54 -0
- workato_platform_cli/client/workato_api/test/test_o_auth_url_response_data.py +52 -0
- workato_platform_cli/client/workato_api/test/test_open_api_spec.py +54 -0
- workato_platform_cli/client/workato_api/test/test_package_details_response.py +64 -0
- workato_platform_cli/client/workato_api/test/test_package_details_response_recipe_status_inner.py +52 -0
- workato_platform_cli/client/workato_api/test/test_package_response.py +58 -0
- workato_platform_cli/client/workato_api/test/test_packages_api.py +59 -0
- workato_platform_cli/client/workato_api/test/test_picklist_request.py +53 -0
- workato_platform_cli/client/workato_api/test/test_picklist_response.py +52 -0
- workato_platform_cli/client/workato_api/test/test_platform_connector.py +94 -0
- workato_platform_cli/client/workato_api/test/test_platform_connector_list_response.py +106 -0
- workato_platform_cli/client/workato_api/test/test_project.py +57 -0
- workato_platform_cli/client/workato_api/test/test_projects_api.py +45 -0
- workato_platform_cli/client/workato_api/test/test_properties_api.py +45 -0
- workato_platform_cli/client/workato_api/test/test_recipe.py +124 -0
- workato_platform_cli/client/workato_api/test/test_recipe_config_inner.py +55 -0
- workato_platform_cli/client/workato_api/test/test_recipe_connection_update_request.py +54 -0
- workato_platform_cli/client/workato_api/test/test_recipe_list_response.py +134 -0
- workato_platform_cli/client/workato_api/test/test_recipe_start_response.py +54 -0
- workato_platform_cli/client/workato_api/test/test_recipes_api.py +59 -0
- workato_platform_cli/client/workato_api/test/test_runtime_user_connection_create_request.py +59 -0
- workato_platform_cli/client/workato_api/test/test_runtime_user_connection_response.py +56 -0
- workato_platform_cli/client/workato_api/test/test_runtime_user_connection_response_data.py +54 -0
- workato_platform_cli/client/workato_api/test/test_success_response.py +52 -0
- workato_platform_cli/client/workato_api/test/test_upsert_project_properties_request.py +52 -0
- workato_platform_cli/client/workato_api/test/test_user.py +85 -0
- workato_platform_cli/client/workato_api/test/test_users_api.py +38 -0
- workato_platform_cli/client/workato_api/test/test_validation_error.py +52 -0
- workato_platform_cli/client/workato_api/test/test_validation_error_errors_value.py +50 -0
- workato_platform_cli/client/workato_api_README.md +205 -0
- workato_platform_cli-1.0.0rc5.dev5.dist-info/METADATA +185 -0
- workato_platform_cli-1.0.0rc5.dev5.dist-info/RECORD +306 -0
- workato_platform_cli-1.0.0rc5.dev5.dist-info/WHEEL +4 -0
- workato_platform_cli-1.0.0rc5.dev5.dist-info/entry_points.txt +2 -0
- workato_platform_cli-1.0.0rc5.dev5.dist-info/licenses/LICENSE +7 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Data models for configuration management."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field, field_validator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ProjectInfo(BaseModel):
|
|
7
|
+
"""Data model for project information"""
|
|
8
|
+
|
|
9
|
+
id: int = Field(..., description="Project ID")
|
|
10
|
+
name: str = Field(..., description="Project name")
|
|
11
|
+
folder_id: int | None = Field(None, description="Associated folder ID")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConfigData(BaseModel):
|
|
15
|
+
"""Data model for configuration file data"""
|
|
16
|
+
|
|
17
|
+
project_id: int | None = Field(None, description="Project ID")
|
|
18
|
+
project_name: str | None = Field(None, description="Project name")
|
|
19
|
+
project_path: str | None = Field(
|
|
20
|
+
None, description="Relative path to project (workspace only)"
|
|
21
|
+
)
|
|
22
|
+
folder_id: int | None = Field(None, description="Folder ID")
|
|
23
|
+
profile: str | None = Field(None, description="Profile override")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RegionInfo(BaseModel):
|
|
27
|
+
"""Data model for region information"""
|
|
28
|
+
|
|
29
|
+
region: str = Field(..., description="Region code")
|
|
30
|
+
name: str = Field(..., description="Human-readable region name")
|
|
31
|
+
url: str | None = Field(None, description="Base URL for the region")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ProfileData(BaseModel):
|
|
35
|
+
"""Data model for a single profile"""
|
|
36
|
+
|
|
37
|
+
region: str = Field(
|
|
38
|
+
..., description="Region code (us, eu, jp, sg, au, il, trial, custom)"
|
|
39
|
+
)
|
|
40
|
+
region_url: str = Field(..., description="Base URL for the region")
|
|
41
|
+
workspace_id: int = Field(..., description="Workspace ID")
|
|
42
|
+
|
|
43
|
+
@field_validator("region")
|
|
44
|
+
def validate_region(cls, v: str) -> str: # noqa: N805
|
|
45
|
+
"""Validate region code"""
|
|
46
|
+
valid_regions = {"us", "eu", "jp", "sg", "au", "il", "trial", "custom"}
|
|
47
|
+
if v not in valid_regions:
|
|
48
|
+
raise ValueError(f"Invalid region code: {v}")
|
|
49
|
+
return v
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def region_name(self) -> str:
|
|
53
|
+
"""Get human-readable region name from region code"""
|
|
54
|
+
region_info = AVAILABLE_REGIONS.get(self.region)
|
|
55
|
+
return region_info.name if region_info else f"Unknown ({self.region})"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ProfilesConfig(BaseModel):
|
|
59
|
+
"""Data model for profiles file (~/.workato/profiles)"""
|
|
60
|
+
|
|
61
|
+
current_profile: str | None = Field(None, description="Currently active profile")
|
|
62
|
+
profiles: dict[str, ProfileData] = Field(
|
|
63
|
+
default_factory=dict, description="Profile definitions"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Available Workato regions
|
|
68
|
+
AVAILABLE_REGIONS = {
|
|
69
|
+
"us": RegionInfo(region="us", name="US Data Center", url="https://www.workato.com"),
|
|
70
|
+
"eu": RegionInfo(
|
|
71
|
+
region="eu", name="EU Data Center", url="https://app.eu.workato.com"
|
|
72
|
+
),
|
|
73
|
+
"jp": RegionInfo(
|
|
74
|
+
region="jp", name="JP Data Center", url="https://app.jp.workato.com"
|
|
75
|
+
),
|
|
76
|
+
"sg": RegionInfo(
|
|
77
|
+
region="sg", name="SG Data Center", url="https://app.sg.workato.com"
|
|
78
|
+
),
|
|
79
|
+
"au": RegionInfo(
|
|
80
|
+
region="au", name="AU Data Center", url="https://app.au.workato.com"
|
|
81
|
+
),
|
|
82
|
+
"il": RegionInfo(
|
|
83
|
+
region="il", name="IL Data Center", url="https://app.il.workato.com"
|
|
84
|
+
),
|
|
85
|
+
"trial": RegionInfo(
|
|
86
|
+
region="trial", name="Developer Sandbox", url="https://app.trial.workato.com"
|
|
87
|
+
),
|
|
88
|
+
"custom": RegionInfo(region="custom", name="Custom URL", url=None),
|
|
89
|
+
}
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
"""Profile management for multiple Workato environments."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import threading
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
import asyncclick as click
|
|
12
|
+
import inquirer
|
|
13
|
+
import keyring
|
|
14
|
+
|
|
15
|
+
from keyring.backend import KeyringBackend
|
|
16
|
+
from keyring.compat import properties
|
|
17
|
+
from keyring.errors import KeyringError, NoKeyringError
|
|
18
|
+
|
|
19
|
+
from .models import AVAILABLE_REGIONS, ProfileData, ProfilesConfig, RegionInfo
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _validate_url_security(url: str) -> tuple[bool, str]:
|
|
23
|
+
"""Validate URL security - only allow HTTP for localhost,
|
|
24
|
+
require HTTPS for others."""
|
|
25
|
+
if not url.startswith(("http://", "https://")):
|
|
26
|
+
return False, "URL must start with http:// or https://"
|
|
27
|
+
|
|
28
|
+
parsed = urlparse(url)
|
|
29
|
+
|
|
30
|
+
# Allow HTTP only for localhost/127.0.0.1
|
|
31
|
+
if parsed.scheme == "http":
|
|
32
|
+
hostname = parsed.hostname
|
|
33
|
+
if hostname not in ("localhost", "127.0.0.1", "::1"):
|
|
34
|
+
return (
|
|
35
|
+
False,
|
|
36
|
+
"HTTP URLs are only allowed for localhost. Use HTTPS for other hosts.",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return True, ""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _set_secure_permissions(path: Path) -> None:
|
|
43
|
+
"""Best-effort attempt to set secure file permissions."""
|
|
44
|
+
with contextlib.suppress(OSError):
|
|
45
|
+
path.chmod(0o600)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class _WorkatoFileKeyring(KeyringBackend):
|
|
49
|
+
"""Fallback keyring that stores secrets in a local JSON file."""
|
|
50
|
+
|
|
51
|
+
@properties.classproperty
|
|
52
|
+
def priority(self) -> float:
|
|
53
|
+
return 0.1
|
|
54
|
+
|
|
55
|
+
def __init__(self, storage_path: Path) -> None:
|
|
56
|
+
super().__init__()
|
|
57
|
+
self._storage_path = storage_path
|
|
58
|
+
self._lock = threading.Lock()
|
|
59
|
+
self._ensure_storage_initialized()
|
|
60
|
+
|
|
61
|
+
def _ensure_storage_initialized(self) -> None:
|
|
62
|
+
self._storage_path.parent.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
if not self._storage_path.exists():
|
|
64
|
+
self._storage_path.write_text("{}", encoding="utf-8")
|
|
65
|
+
_set_secure_permissions(self._storage_path)
|
|
66
|
+
|
|
67
|
+
def _load_data(self) -> dict[str, dict[str, str]]:
|
|
68
|
+
try:
|
|
69
|
+
raw = self._storage_path.read_text(encoding="utf-8")
|
|
70
|
+
except FileNotFoundError:
|
|
71
|
+
return {}
|
|
72
|
+
except OSError:
|
|
73
|
+
return {}
|
|
74
|
+
|
|
75
|
+
if not raw.strip():
|
|
76
|
+
return {}
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
loaded = json.loads(raw)
|
|
80
|
+
except json.JSONDecodeError:
|
|
81
|
+
return {}
|
|
82
|
+
|
|
83
|
+
if isinstance(loaded, dict):
|
|
84
|
+
# Ensure nested dictionaries
|
|
85
|
+
normalized: dict[str, dict[str, str]] = {}
|
|
86
|
+
for service, usernames in loaded.items():
|
|
87
|
+
if isinstance(usernames, dict):
|
|
88
|
+
normalized[service] = {
|
|
89
|
+
str(username): str(password)
|
|
90
|
+
for username, password in usernames.items()
|
|
91
|
+
}
|
|
92
|
+
return normalized
|
|
93
|
+
return {}
|
|
94
|
+
|
|
95
|
+
def _save_data(self, data: dict[str, dict[str, str]]) -> None:
|
|
96
|
+
serialized = json.dumps(data, indent=2)
|
|
97
|
+
self._storage_path.write_text(serialized, encoding="utf-8")
|
|
98
|
+
_set_secure_permissions(self._storage_path)
|
|
99
|
+
|
|
100
|
+
def get_password(self, service: str, username: str) -> str | None:
|
|
101
|
+
with self._lock:
|
|
102
|
+
data = self._load_data()
|
|
103
|
+
return data.get(service, {}).get(username)
|
|
104
|
+
|
|
105
|
+
def set_password(self, service: str, username: str, password: str) -> None:
|
|
106
|
+
with self._lock:
|
|
107
|
+
data = self._load_data()
|
|
108
|
+
data.setdefault(service, {})[username] = password
|
|
109
|
+
self._save_data(data)
|
|
110
|
+
|
|
111
|
+
def delete_password(self, service: str, username: str) -> None:
|
|
112
|
+
with self._lock:
|
|
113
|
+
data = self._load_data()
|
|
114
|
+
usernames = data.get(service)
|
|
115
|
+
if usernames and username in usernames:
|
|
116
|
+
del usernames[username]
|
|
117
|
+
if not usernames:
|
|
118
|
+
del data[service]
|
|
119
|
+
self._save_data(data)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ProfileManager:
|
|
123
|
+
"""Manages profiles file configuration"""
|
|
124
|
+
|
|
125
|
+
def __init__(self) -> None:
|
|
126
|
+
"""Initialize profile manager"""
|
|
127
|
+
self.global_config_dir = Path.home() / ".workato"
|
|
128
|
+
self.profiles_file = self.global_config_dir / "profiles"
|
|
129
|
+
self.keyring_service = "workato-platform-cli"
|
|
130
|
+
self._fallback_token_file = self.global_config_dir / "token_store.json"
|
|
131
|
+
self._using_fallback_keyring = False
|
|
132
|
+
self._ensure_keyring_backend()
|
|
133
|
+
|
|
134
|
+
def _ensure_keyring_backend(self, force_fallback: bool = False) -> None:
|
|
135
|
+
"""Ensure a usable keyring backend is available for storing tokens."""
|
|
136
|
+
if os.environ.get("WORKATO_DISABLE_KEYRING", "").lower() == "true":
|
|
137
|
+
self._using_fallback_keyring = False
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
if force_fallback:
|
|
141
|
+
fallback_keyring = _WorkatoFileKeyring(self._fallback_token_file)
|
|
142
|
+
keyring.set_keyring(fallback_keyring)
|
|
143
|
+
self._using_fallback_keyring = True
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
backend = keyring.get_keyring()
|
|
148
|
+
except Exception:
|
|
149
|
+
backend = None
|
|
150
|
+
|
|
151
|
+
backend_priority = getattr(backend, "priority", 0) if backend else 0
|
|
152
|
+
backend_module = getattr(backend, "__class__", type("", (), {})).__module__
|
|
153
|
+
|
|
154
|
+
if (
|
|
155
|
+
backend_priority
|
|
156
|
+
and backend_priority > 0
|
|
157
|
+
and not str(backend_module).startswith("keyring.backends.fail")
|
|
158
|
+
):
|
|
159
|
+
# Perform a quick health check to ensure the backend is usable.
|
|
160
|
+
test_service = f"{self.keyring_service}-self-test"
|
|
161
|
+
test_username = "__workato__"
|
|
162
|
+
with contextlib.suppress(NoKeyringError, KeyringError, Exception):
|
|
163
|
+
backend = backend or keyring.get_keyring()
|
|
164
|
+
backend.set_password(test_service, test_username, "0")
|
|
165
|
+
backend.delete_password(test_service, test_username)
|
|
166
|
+
self._using_fallback_keyring = False
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
fallback_keyring = _WorkatoFileKeyring(self._fallback_token_file)
|
|
170
|
+
keyring.set_keyring(fallback_keyring)
|
|
171
|
+
self._using_fallback_keyring = True
|
|
172
|
+
|
|
173
|
+
def _is_keyring_enabled(self) -> bool:
|
|
174
|
+
"""Check if keyring usage is enabled"""
|
|
175
|
+
return os.environ.get("WORKATO_DISABLE_KEYRING", "").lower() != "true"
|
|
176
|
+
|
|
177
|
+
def _get_token_from_keyring(self, profile_name: str) -> str | None:
|
|
178
|
+
"""Get API token from keyring for the given profile"""
|
|
179
|
+
if not self._is_keyring_enabled():
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
pw: str | None = keyring.get_password(self.keyring_service, profile_name)
|
|
184
|
+
return pw
|
|
185
|
+
except NoKeyringError:
|
|
186
|
+
if not self._using_fallback_keyring:
|
|
187
|
+
self._ensure_keyring_backend(force_fallback=True)
|
|
188
|
+
if self._using_fallback_keyring:
|
|
189
|
+
with contextlib.suppress(NoKeyringError, KeyringError, Exception):
|
|
190
|
+
token: str | None = keyring.get_password(
|
|
191
|
+
self.keyring_service, profile_name
|
|
192
|
+
)
|
|
193
|
+
return token
|
|
194
|
+
return None
|
|
195
|
+
except KeyringError:
|
|
196
|
+
if not self._using_fallback_keyring:
|
|
197
|
+
self._ensure_keyring_backend(force_fallback=True)
|
|
198
|
+
if self._using_fallback_keyring:
|
|
199
|
+
with contextlib.suppress(NoKeyringError, KeyringError, Exception):
|
|
200
|
+
fallback_token: str | None = keyring.get_password(
|
|
201
|
+
self.keyring_service, profile_name
|
|
202
|
+
)
|
|
203
|
+
return fallback_token
|
|
204
|
+
return None
|
|
205
|
+
except Exception:
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
def _store_token_in_keyring(self, profile_name: str, token: str) -> bool:
|
|
209
|
+
"""Store API token in keyring for the given profile"""
|
|
210
|
+
if not self._is_keyring_enabled():
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
keyring.set_password(self.keyring_service, profile_name, token)
|
|
215
|
+
return True
|
|
216
|
+
except NoKeyringError:
|
|
217
|
+
if not self._using_fallback_keyring:
|
|
218
|
+
self._ensure_keyring_backend(force_fallback=True)
|
|
219
|
+
if self._using_fallback_keyring:
|
|
220
|
+
with contextlib.suppress(NoKeyringError, KeyringError, Exception):
|
|
221
|
+
keyring.set_password(self.keyring_service, profile_name, token)
|
|
222
|
+
return True
|
|
223
|
+
return False
|
|
224
|
+
except KeyringError:
|
|
225
|
+
if not self._using_fallback_keyring:
|
|
226
|
+
self._ensure_keyring_backend(force_fallback=True)
|
|
227
|
+
if self._using_fallback_keyring:
|
|
228
|
+
with contextlib.suppress(NoKeyringError, KeyringError, Exception):
|
|
229
|
+
keyring.set_password(self.keyring_service, profile_name, token)
|
|
230
|
+
return True
|
|
231
|
+
return False
|
|
232
|
+
except Exception:
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
def _delete_token_from_keyring(self, profile_name: str) -> bool:
|
|
236
|
+
"""Delete API token from keyring for the given profile"""
|
|
237
|
+
if not self._is_keyring_enabled():
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
keyring.delete_password(self.keyring_service, profile_name)
|
|
242
|
+
return True
|
|
243
|
+
except NoKeyringError:
|
|
244
|
+
if not self._using_fallback_keyring:
|
|
245
|
+
self._ensure_keyring_backend(force_fallback=True)
|
|
246
|
+
if self._using_fallback_keyring:
|
|
247
|
+
with contextlib.suppress(NoKeyringError, KeyringError, Exception):
|
|
248
|
+
keyring.delete_password(self.keyring_service, profile_name)
|
|
249
|
+
return True
|
|
250
|
+
return False
|
|
251
|
+
except KeyringError:
|
|
252
|
+
if not self._using_fallback_keyring:
|
|
253
|
+
self._ensure_keyring_backend(force_fallback=True)
|
|
254
|
+
if self._using_fallback_keyring:
|
|
255
|
+
with contextlib.suppress(NoKeyringError, KeyringError, Exception):
|
|
256
|
+
keyring.delete_password(self.keyring_service, profile_name)
|
|
257
|
+
return True
|
|
258
|
+
return False
|
|
259
|
+
except Exception:
|
|
260
|
+
return False
|
|
261
|
+
|
|
262
|
+
def _ensure_global_config_dir(self) -> None:
|
|
263
|
+
"""Ensure global config directory exists with proper permissions"""
|
|
264
|
+
self.global_config_dir.mkdir(exist_ok=True, mode=0o700)
|
|
265
|
+
|
|
266
|
+
def load_profiles(self) -> ProfilesConfig:
|
|
267
|
+
"""Load profiles configuration from file"""
|
|
268
|
+
if not self.profiles_file.exists():
|
|
269
|
+
return ProfilesConfig(current_profile=None, profiles={})
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
with open(self.profiles_file) as f:
|
|
273
|
+
data = json.load(f)
|
|
274
|
+
if not isinstance(data, dict):
|
|
275
|
+
raise ValueError("Invalid profiles file")
|
|
276
|
+
config: ProfilesConfig = ProfilesConfig.model_validate(data)
|
|
277
|
+
return config
|
|
278
|
+
except (json.JSONDecodeError, ValueError):
|
|
279
|
+
return ProfilesConfig(current_profile=None, profiles={})
|
|
280
|
+
|
|
281
|
+
def save_profiles(self, profiles_config: ProfilesConfig) -> None:
|
|
282
|
+
"""Save profiles configuration to file with secure permissions"""
|
|
283
|
+
self._ensure_global_config_dir()
|
|
284
|
+
|
|
285
|
+
# Write to temp file first, then rename for atomic operation
|
|
286
|
+
temp_file = self.profiles_file.with_suffix(".tmp")
|
|
287
|
+
with open(temp_file, "w") as f:
|
|
288
|
+
json.dump(profiles_config.model_dump(exclude_none=True), f, indent=2)
|
|
289
|
+
|
|
290
|
+
# Set secure permissions (only user can read/write)
|
|
291
|
+
temp_file.chmod(0o600)
|
|
292
|
+
|
|
293
|
+
# Atomic rename
|
|
294
|
+
temp_file.rename(self.profiles_file)
|
|
295
|
+
|
|
296
|
+
def get_profile(self, profile_name: str) -> ProfileData | None:
|
|
297
|
+
"""Get profile data by name"""
|
|
298
|
+
profiles_config = self.load_profiles()
|
|
299
|
+
return profiles_config.profiles.get(profile_name)
|
|
300
|
+
|
|
301
|
+
def set_profile(
|
|
302
|
+
self, profile_name: str, profile_data: ProfileData, token: str | None = None
|
|
303
|
+
) -> None:
|
|
304
|
+
"""Set or update a profile"""
|
|
305
|
+
profiles_config = self.load_profiles()
|
|
306
|
+
profiles_config.profiles[profile_name] = profile_data
|
|
307
|
+
self.save_profiles(profiles_config)
|
|
308
|
+
|
|
309
|
+
# Store token in keyring if provided
|
|
310
|
+
if not token or self._store_token_in_keyring(profile_name, token):
|
|
311
|
+
return
|
|
312
|
+
|
|
313
|
+
if self._is_keyring_enabled():
|
|
314
|
+
raise ValueError(
|
|
315
|
+
"Failed to store token in keyring. "
|
|
316
|
+
"Please check your system keyring setup."
|
|
317
|
+
)
|
|
318
|
+
else:
|
|
319
|
+
raise ValueError(
|
|
320
|
+
"Keyring is disabled. "
|
|
321
|
+
"Please set WORKATO_API_TOKEN environment variable instead."
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def delete_profile(self, profile_name: str) -> bool:
|
|
325
|
+
"""Delete a profile by name"""
|
|
326
|
+
profiles_config = self.load_profiles()
|
|
327
|
+
if profile_name not in profiles_config.profiles:
|
|
328
|
+
return False
|
|
329
|
+
|
|
330
|
+
del profiles_config.profiles[profile_name]
|
|
331
|
+
|
|
332
|
+
# If this was the current profile, clear it
|
|
333
|
+
if profiles_config.current_profile == profile_name:
|
|
334
|
+
profiles_config.current_profile = None
|
|
335
|
+
|
|
336
|
+
# Delete token from keyring
|
|
337
|
+
self._delete_token_from_keyring(profile_name)
|
|
338
|
+
|
|
339
|
+
self.save_profiles(profiles_config)
|
|
340
|
+
return True
|
|
341
|
+
|
|
342
|
+
def get_current_profile_name(
|
|
343
|
+
self, project_profile_override: str | None = None
|
|
344
|
+
) -> str | None:
|
|
345
|
+
"""Get current profile name, considering project override"""
|
|
346
|
+
# Priority order:
|
|
347
|
+
# 1. Project-specific profile override
|
|
348
|
+
# 2. Environment variable WORKATO_PROFILE
|
|
349
|
+
# 3. Global current profile setting
|
|
350
|
+
|
|
351
|
+
if project_profile_override:
|
|
352
|
+
return project_profile_override
|
|
353
|
+
|
|
354
|
+
env_profile = os.environ.get("WORKATO_PROFILE")
|
|
355
|
+
if env_profile:
|
|
356
|
+
return env_profile
|
|
357
|
+
|
|
358
|
+
profiles_config = self.load_profiles()
|
|
359
|
+
return profiles_config.current_profile
|
|
360
|
+
|
|
361
|
+
def set_current_profile(self, profile_name: str | None) -> None:
|
|
362
|
+
"""Set the current profile in global config"""
|
|
363
|
+
profiles_config = self.load_profiles()
|
|
364
|
+
profiles_config.current_profile = profile_name
|
|
365
|
+
self.save_profiles(profiles_config)
|
|
366
|
+
|
|
367
|
+
def get_current_profile_data(
|
|
368
|
+
self, project_profile_override: str | None = None
|
|
369
|
+
) -> ProfileData | None:
|
|
370
|
+
"""Get current profile data"""
|
|
371
|
+
profile_name = self.get_current_profile_name(project_profile_override)
|
|
372
|
+
if not profile_name:
|
|
373
|
+
return None
|
|
374
|
+
return self.get_profile(profile_name)
|
|
375
|
+
|
|
376
|
+
def list_profiles(self) -> dict[str, ProfileData]:
|
|
377
|
+
"""Get all available profiles"""
|
|
378
|
+
profiles_config = self.load_profiles()
|
|
379
|
+
return profiles_config.profiles.copy()
|
|
380
|
+
|
|
381
|
+
def resolve_environment_variables(
|
|
382
|
+
self, project_profile_override: str | None = None
|
|
383
|
+
) -> tuple[str | None, str | None]:
|
|
384
|
+
"""Resolve API token and host with environment variable override support"""
|
|
385
|
+
# Check for environment variable overrides first (highest priority)
|
|
386
|
+
env_token = os.environ.get("WORKATO_API_TOKEN")
|
|
387
|
+
env_host = os.environ.get("WORKATO_HOST")
|
|
388
|
+
|
|
389
|
+
if env_token and env_host:
|
|
390
|
+
return env_token, env_host
|
|
391
|
+
|
|
392
|
+
# Fall back to profile-based configuration
|
|
393
|
+
profile_name = self.get_current_profile_name(project_profile_override)
|
|
394
|
+
if not profile_name:
|
|
395
|
+
return None, None
|
|
396
|
+
|
|
397
|
+
profile_data = self.get_profile(profile_name)
|
|
398
|
+
if not profile_data:
|
|
399
|
+
return None, None
|
|
400
|
+
|
|
401
|
+
# Get token from keyring or env var, use profile data for host
|
|
402
|
+
api_token = env_token or self._get_token_from_keyring(profile_name)
|
|
403
|
+
api_host = env_host or profile_data.region_url
|
|
404
|
+
|
|
405
|
+
return api_token, api_host
|
|
406
|
+
|
|
407
|
+
def validate_credentials(
|
|
408
|
+
self, project_profile_override: str | None = None
|
|
409
|
+
) -> tuple[bool, list[str]]:
|
|
410
|
+
"""Validate that credentials are available from environment or profile"""
|
|
411
|
+
api_token, api_host = self.resolve_environment_variables(
|
|
412
|
+
project_profile_override
|
|
413
|
+
)
|
|
414
|
+
missing_items = []
|
|
415
|
+
|
|
416
|
+
if not api_token:
|
|
417
|
+
missing_items.append("API token (WORKATO_API_TOKEN or profile credentials)")
|
|
418
|
+
if not api_host:
|
|
419
|
+
missing_items.append("API host (WORKATO_HOST or profile region)")
|
|
420
|
+
|
|
421
|
+
return len(missing_items) == 0, missing_items
|
|
422
|
+
|
|
423
|
+
async def select_region_interactive(
|
|
424
|
+
self, profile_name: str | None = None
|
|
425
|
+
) -> RegionInfo | None:
|
|
426
|
+
"""Interactive region selection"""
|
|
427
|
+
regions = list(AVAILABLE_REGIONS.values())
|
|
428
|
+
|
|
429
|
+
click.echo()
|
|
430
|
+
|
|
431
|
+
# Create choices for inquirer
|
|
432
|
+
choices = []
|
|
433
|
+
for region in regions:
|
|
434
|
+
if region.region == "custom":
|
|
435
|
+
choice_text = "Custom URL"
|
|
436
|
+
else:
|
|
437
|
+
choice_text = f"{region.name} ({region.url})"
|
|
438
|
+
|
|
439
|
+
choices.append(choice_text)
|
|
440
|
+
|
|
441
|
+
questions = [
|
|
442
|
+
inquirer.List(
|
|
443
|
+
"region",
|
|
444
|
+
message="Select your Workato region",
|
|
445
|
+
choices=choices,
|
|
446
|
+
),
|
|
447
|
+
]
|
|
448
|
+
|
|
449
|
+
answers = inquirer.prompt(questions)
|
|
450
|
+
if not answers: # User cancelled
|
|
451
|
+
return None
|
|
452
|
+
|
|
453
|
+
# Find the selected region by index
|
|
454
|
+
selected_choice = answers["region"]
|
|
455
|
+
selected_index = choices.index(selected_choice)
|
|
456
|
+
selected_region = regions[selected_index]
|
|
457
|
+
|
|
458
|
+
# Handle custom URL
|
|
459
|
+
if selected_region.region == "custom":
|
|
460
|
+
click.echo()
|
|
461
|
+
|
|
462
|
+
# Get selected profile's custom URL as default
|
|
463
|
+
profile_data = None
|
|
464
|
+
if profile_name:
|
|
465
|
+
profile_data = self.get_profile(profile_name)
|
|
466
|
+
else:
|
|
467
|
+
profile_data = self.get_current_profile_data()
|
|
468
|
+
|
|
469
|
+
current_url = "https://www.workato.com" # fallback default
|
|
470
|
+
if profile_data and profile_data.region == "custom":
|
|
471
|
+
current_url = profile_data.region_url
|
|
472
|
+
|
|
473
|
+
custom_url = await click.prompt(
|
|
474
|
+
"Enter your custom Workato base URL",
|
|
475
|
+
type=str,
|
|
476
|
+
default=current_url,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
# Validate URL security
|
|
480
|
+
is_valid, error_msg = _validate_url_security(custom_url)
|
|
481
|
+
if not is_valid:
|
|
482
|
+
click.echo(f"❌ {error_msg}")
|
|
483
|
+
return None
|
|
484
|
+
|
|
485
|
+
# Parse URL and keep only scheme + netloc (strip any path components)
|
|
486
|
+
parsed = urlparse(custom_url)
|
|
487
|
+
custom_url = f"{parsed.scheme}://{parsed.netloc}"
|
|
488
|
+
|
|
489
|
+
return RegionInfo(region="custom", name="Custom URL", url=custom_url)
|
|
490
|
+
|
|
491
|
+
return selected_region
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Workspace and project directory management."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import asyncclick as click
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class WorkspaceManager:
|
|
12
|
+
"""Manages workspace root detection and validation"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, start_path: Path | None = None):
|
|
15
|
+
self.start_path = start_path or Path.cwd()
|
|
16
|
+
|
|
17
|
+
def find_nearest_workatoenv(self) -> Path | None:
|
|
18
|
+
"""Find the nearest .workatoenv file by traversing up the directory tree"""
|
|
19
|
+
current = self.start_path.resolve()
|
|
20
|
+
|
|
21
|
+
while current != current.parent:
|
|
22
|
+
workatoenv_file = current / ".workatoenv"
|
|
23
|
+
if workatoenv_file.exists():
|
|
24
|
+
return current
|
|
25
|
+
current = current.parent
|
|
26
|
+
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
def find_workspace_root(self) -> Path:
|
|
30
|
+
"""Find workspace root by traversing up for .workatoenv file
|
|
31
|
+
with project_path"""
|
|
32
|
+
current = self.start_path.resolve()
|
|
33
|
+
|
|
34
|
+
while current != current.parent:
|
|
35
|
+
workatoenv_file = current / ".workatoenv"
|
|
36
|
+
if workatoenv_file.exists():
|
|
37
|
+
try:
|
|
38
|
+
with open(workatoenv_file) as f:
|
|
39
|
+
data = json.load(f)
|
|
40
|
+
# Workspace root has project_path pointing to a project
|
|
41
|
+
if "project_path" in data and data["project_path"]:
|
|
42
|
+
return current
|
|
43
|
+
# If no project_path, this might be a project directory itself
|
|
44
|
+
elif "project_id" in data and not data.get("project_path"):
|
|
45
|
+
# This is a project directory, continue searching up
|
|
46
|
+
pass
|
|
47
|
+
except (json.JSONDecodeError, OSError):
|
|
48
|
+
pass
|
|
49
|
+
current = current.parent
|
|
50
|
+
|
|
51
|
+
# If no workspace found, current directory becomes workspace root
|
|
52
|
+
return self.start_path
|
|
53
|
+
|
|
54
|
+
def is_in_project_directory(self) -> bool:
|
|
55
|
+
"""Check if current directory is a project directory"""
|
|
56
|
+
workatoenv_file = self.start_path / ".workatoenv"
|
|
57
|
+
if not workatoenv_file.exists():
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
with open(workatoenv_file) as f:
|
|
62
|
+
data = json.load(f)
|
|
63
|
+
# Project directory has project_id but no project_path
|
|
64
|
+
return "project_id" in data and not data.get("project_path")
|
|
65
|
+
except (json.JSONDecodeError, OSError):
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
def validate_not_in_project(self) -> None:
|
|
69
|
+
"""Validate that we're not running from within a project directory"""
|
|
70
|
+
if self.is_in_project_directory():
|
|
71
|
+
workspace_root = self.find_workspace_root()
|
|
72
|
+
if workspace_root != self.start_path:
|
|
73
|
+
click.echo(f"❌ Run init from workspace root: {workspace_root}")
|
|
74
|
+
else:
|
|
75
|
+
click.echo("❌ Cannot run init from within a project directory")
|
|
76
|
+
sys.exit(1)
|
|
77
|
+
|
|
78
|
+
def validate_project_path(self, project_path: Path, workspace_root: Path) -> None:
|
|
79
|
+
"""Validate project path follows our rules"""
|
|
80
|
+
# Convert to absolute paths for comparison
|
|
81
|
+
abs_project_path = project_path.resolve()
|
|
82
|
+
abs_workspace_root = workspace_root.resolve()
|
|
83
|
+
|
|
84
|
+
# Project cannot be in workspace root directly
|
|
85
|
+
if abs_project_path == abs_workspace_root:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
"Projects cannot be created in workspace root directly. "
|
|
88
|
+
"Use a subdirectory."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Project must be within workspace
|
|
92
|
+
try:
|
|
93
|
+
abs_project_path.relative_to(abs_workspace_root)
|
|
94
|
+
except ValueError as e:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"Project path must be within workspace root: {abs_workspace_root}"
|
|
97
|
+
) from e
|
|
98
|
+
|
|
99
|
+
# Check for nested projects by looking for .workatoenv in parent directories
|
|
100
|
+
current = abs_project_path.parent
|
|
101
|
+
while current != abs_workspace_root and current != current.parent:
|
|
102
|
+
if (current / ".workatoenv").exists():
|
|
103
|
+
try:
|
|
104
|
+
with open(current / ".workatoenv") as f:
|
|
105
|
+
data = json.load(f)
|
|
106
|
+
if "project_id" in data:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"Cannot create project within another "
|
|
109
|
+
f"project: {current}"
|
|
110
|
+
)
|
|
111
|
+
except (json.JSONDecodeError, OSError):
|
|
112
|
+
pass
|
|
113
|
+
current = current.parent
|