diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index f676ae3..943fc00 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 1 matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/main/cloudfoundry_client/client.py b/main/cloudfoundry_client/client.py index 77cc556..411b775 100644 --- a/main/cloudfoundry_client/client.py +++ b/main/cloudfoundry_client/client.py @@ -52,18 +52,27 @@ class Info: def __init__( self, - api_v2_version: str, + api_v2_url: str, + api_v3_url: str, authorization_endpoint: str, api_endpoint: str, doppler_endpoint: Optional[str], log_stream_endpoint: Optional[str], ): - self.api_v2_version = api_v2_version + self._api_v2_url = api_v2_url + self._api_v3_url = api_v3_url self.authorization_endpoint = authorization_endpoint self.api_endpoint = api_endpoint self.doppler_endpoint = doppler_endpoint self.log_stream_endpoint = log_stream_endpoint + @property + def api_v2_url(self) -> Optional[str]: + return self._api_v2_url + + @property + def api_v3_url(self) -> Optional[str]: + return self._api_v3_url class NetworkingV1External(object): def __init__(self, target_endpoint: str, credential_manager: "CloudFoundryClient"): @@ -83,18 +92,18 @@ def __init__(self, target_endpoint: str, credential_manager: "CloudFoundryClient self.service_plans = ServicePlanManagerV2(target_endpoint, credential_manager) # Default implementations self.event = EventManager(target_endpoint, credential_manager) - self.organizations = EntityManagerV2(target_endpoint, credential_manager, "/v2/organizations") - self.private_domains = EntityManagerV2(target_endpoint, credential_manager, "/v2/private_domains") + self.organizations = EntityManagerV2(target_endpoint, credential_manager, "/organizations") + self.private_domains = EntityManagerV2(target_endpoint, credential_manager, "/private_domains") self.routes = RouteManager(target_endpoint, credential_manager) - self.services = EntityManagerV2(target_endpoint, credential_manager, "/v2/services") - self.shared_domains = EntityManagerV2(target_endpoint, credential_manager, "/v2/shared_domains") + self.services = EntityManagerV2(target_endpoint, credential_manager, "/services") + self.shared_domains = EntityManagerV2(target_endpoint, credential_manager, "/shared_domains") self.spaces = SpaceManagerV2(target_endpoint, credential_manager) - self.stacks = EntityManagerV2(target_endpoint, credential_manager, "/v2/stacks") + self.stacks = EntityManagerV2(target_endpoint, credential_manager, "/stacks") self.user_provided_service_instances = EntityManagerV2( - target_endpoint, credential_manager, "/v2/user_provided_service_instances" + target_endpoint, credential_manager, "/user_provided_service_instances" ) - self.security_groups = EntityManagerV2(target_endpoint, credential_manager, "/v2/security_groups") - self.users = EntityManagerV2(target_endpoint, credential_manager, "/v2/users") + self.security_groups = EntityManagerV2(target_endpoint, credential_manager, "/security_groups") + self.users = EntityManagerV2(target_endpoint, credential_manager, "/users") # Resources implementation used by push operation self.resources = ResourceManager(target_endpoint, credential_manager) @@ -146,8 +155,6 @@ def __init__(self, target_endpoint: str, client_id: str = "cf", client_secret: s self.login_hint = kwargs.get("login_hint") target_endpoint_trimmed = target_endpoint.rstrip("/") info = self._get_info(target_endpoint_trimmed, proxy, verify=verify) - if not info.api_v2_version.startswith("2."): - raise AssertionError("Only version 2 is supported for now. Found %s" % info.api_v2_version) service_information = ServiceInformation( None, "%s/oauth/token" % info.authorization_endpoint, client_id, client_secret, [], verify ) @@ -156,8 +163,16 @@ def __init__(self, target_endpoint: str, client_id: str = "cf", client_secret: s proxies=proxy, user_agent=kwargs.get("user_agent", "cf-python-client") ) - self.v2 = V2(target_endpoint_trimmed, self) - self.v3 = V3(target_endpoint_trimmed, self) + self._v2 = ( + V2(info.api_v2_url, self) + if info.api_v2_url is not None + else None + ) + self._v3 = ( + V3(info.api_v3_url, self) + if info.api_v3_url is not None + else None + ) self._doppler = ( DopplerClient( info.doppler_endpoint, @@ -181,6 +196,18 @@ def __init__(self, target_endpoint: str, client_id: str = "cf", client_secret: s self.networking_v1_external = NetworkingV1External(target_endpoint_trimmed, self) self.info = info + @property + def v2(self) -> V2: + if self._v2 is None: + raise NotImplementedError("No V2 endpoint for this instance") + return self._v2 + + @property + def v3(self) -> V3: + if self._v3 is None: + raise NotImplementedError("No V3 endpoint for this instance") + return self._v3 + @property def doppler(self) -> DopplerClient: if self._doppler is None: @@ -194,7 +221,6 @@ def rlpgateway(self): if self._rlpgateway is None: raise NotImplementedError("No RLP gateway endpoint for this instance") else: - return self._rlpgateway def _get_info(self, target_endpoint: str, proxy: Optional[dict] = None, verify: bool = True) -> Info: @@ -206,8 +232,11 @@ def _get_info(self, target_endpoint: str, proxy: Optional[dict] = None, verify: root_links = root_info["links"] logging = root_links.get("logging") log_stream = root_links.get("log_stream") + cloud_controller_v2 = root_links.get("cloud_controller_v2") + cloud_controller_v3 = root_links.get("cloud_controller_v3") return Info( - root_links["cloud_controller_v2"]["meta"]["version"], + cloud_controller_v2["href"] if cloud_controller_v2 is not None else None, + cloud_controller_v3["href"] if cloud_controller_v3 is not None else None, self._resolve_login_endpoint(root_links), target_endpoint, logging.get("href") if logging is not None else None, diff --git a/main/cloudfoundry_client/common_objects.py b/main/cloudfoundry_client/common_objects.py index 9c3aa0f..ab0b18b 100644 --- a/main/cloudfoundry_client/common_objects.py +++ b/main/cloudfoundry_client/common_objects.py @@ -1,5 +1,5 @@ import json -from typing import Callable, TypeVar, Generic, List, Union +from typing import Callable, TypeVar, Generic, List, Union, Optional class Request(dict): @@ -21,7 +21,7 @@ def __init__(self, *args, **kwargs): class Pagination(Generic[ENTITY]): def __init__(self, first_page: JsonObject, total_result: int, - next_page_loader: Callable[[JsonObject], Union[None, JsonObject]], + next_page_loader: Callable[[JsonObject], Optional[JsonObject]], resources_accessor: Callable[[JsonObject], List[JsonObject]], instance_creator: Callable[[JsonObject], ENTITY]): self._first_page = first_page diff --git a/main/cloudfoundry_client/v2/apps.py b/main/cloudfoundry_client/v2/apps.py index cf68ed8..d060bff 100644 --- a/main/cloudfoundry_client/v2/apps.py +++ b/main/cloudfoundry_client/v2/apps.py @@ -77,7 +77,7 @@ class AppManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): super(AppManager, self).__init__( - target_endpoint, client, "/v2/apps", lambda pairs: Application(target_endpoint, client, pairs) + target_endpoint, client, "/apps", lambda pairs: Application(target_endpoint, client, pairs) ) def get_stats(self, application_guid: str) -> Dict[str, JsonObject]: diff --git a/main/cloudfoundry_client/v2/buildpacks.py b/main/cloudfoundry_client/v2/buildpacks.py index 958a283..8e1276b 100644 --- a/main/cloudfoundry_client/v2/buildpacks.py +++ b/main/cloudfoundry_client/v2/buildpacks.py @@ -8,7 +8,7 @@ class BuildpackManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(BuildpackManager, self).__init__(target_endpoint, client, "/v2/buildpacks") + super(BuildpackManager, self).__init__(target_endpoint, client, "/buildpacks") def update(self, buildpack_guid: str, parameters: dict) -> Entity: return super(BuildpackManager, self)._update(buildpack_guid, parameters) diff --git a/main/cloudfoundry_client/v2/entities.py b/main/cloudfoundry_client/v2/entities.py index d9ace97..a272e8b 100644 --- a/main/cloudfoundry_client/v2/entities.py +++ b/main/cloudfoundry_client/v2/entities.py @@ -68,7 +68,7 @@ def _list(self, requested_path: str, entity_builder: Optional[EntityBuilder] = N lambda page: page["resources"], lambda json_object: current_builder(list(json_object.items()))) - def _next_page(self, current_page: JsonObject) -> Union[None, JsonObject]: + def _next_page(self, current_page: JsonObject) -> Optional[JsonObject]: next_url = current_page.get("next_url") if next_url is None: return None diff --git a/main/cloudfoundry_client/v2/events.py b/main/cloudfoundry_client/v2/events.py index 9822165..cdec234 100644 --- a/main/cloudfoundry_client/v2/events.py +++ b/main/cloudfoundry_client/v2/events.py @@ -8,7 +8,7 @@ class EventManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(EventManager, self).__init__(target_endpoint, client, "/v2/events") + super(EventManager, self).__init__(target_endpoint, client, "/events") def list_by_type(self, event_type: str) -> Generator[Entity, None, None]: return self._list(self.entity_uri, type=event_type) diff --git a/main/cloudfoundry_client/v2/jobs.py b/main/cloudfoundry_client/v2/jobs.py index 68bdaed..f1a6f53 100644 --- a/main/cloudfoundry_client/v2/jobs.py +++ b/main/cloudfoundry_client/v2/jobs.py @@ -12,4 +12,4 @@ def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): self.client = client def get(self, job_guid: str) -> JsonObject: - return self.client.get("%s/v2/jobs/%s" % (self.target_endpoint, job_guid)).json(object_pairs_hook=JsonObject) + return self.client.get("%s/jobs/%s" % (self.target_endpoint, job_guid)).json(object_pairs_hook=JsonObject) diff --git a/main/cloudfoundry_client/v2/resources.py b/main/cloudfoundry_client/v2/resources.py index d962daa..2b0dae3 100644 --- a/main/cloudfoundry_client/v2/resources.py +++ b/main/cloudfoundry_client/v2/resources.py @@ -12,5 +12,5 @@ def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): self.client = client def match(self, items: List[dict]) -> List[JsonObject]: - response = self.client.put("%s/v2/resource_match" % self.client.info.api_endpoint, json=items) + response = self.client.put("%s/resource_match" % self.client.info.api_endpoint, json=items) return response.json(object_pairs_hook=JsonObject) diff --git a/main/cloudfoundry_client/v2/routes.py b/main/cloudfoundry_client/v2/routes.py index 1e4d52c..bf9d425 100644 --- a/main/cloudfoundry_client/v2/routes.py +++ b/main/cloudfoundry_client/v2/routes.py @@ -8,7 +8,7 @@ class RouteManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(RouteManager, self).__init__(target_endpoint, client, "/v2/routes") + super(RouteManager, self).__init__(target_endpoint, client, "/routes") def create_tcp_route(self, domain_guid: str, space_guid: str, port: Optional[int] = None) -> Entity: request = self._request(domain_guid=domain_guid, space_guid=space_guid) diff --git a/main/cloudfoundry_client/v2/service_bindings.py b/main/cloudfoundry_client/v2/service_bindings.py index 00fc625..95f9f5b 100644 --- a/main/cloudfoundry_client/v2/service_bindings.py +++ b/main/cloudfoundry_client/v2/service_bindings.py @@ -8,7 +8,7 @@ class ServiceBindingManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(ServiceBindingManager, self).__init__(target_endpoint, client, "/v2/service_bindings") + super(ServiceBindingManager, self).__init__(target_endpoint, client, "/service_bindings") def create(self, app_guid: str, instance_guid: str, parameters: Optional[dict] = None, name: Optional[str] = None) -> Entity: request = self._request(app_guid=app_guid, service_instance_guid=instance_guid) diff --git a/main/cloudfoundry_client/v2/service_brokers.py b/main/cloudfoundry_client/v2/service_brokers.py index abe44c7..afc2c1a 100644 --- a/main/cloudfoundry_client/v2/service_brokers.py +++ b/main/cloudfoundry_client/v2/service_brokers.py @@ -8,7 +8,7 @@ class ServiceBrokerManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(ServiceBrokerManager, self).__init__(target_endpoint, client, "/v2/service_brokers") + super(ServiceBrokerManager, self).__init__(target_endpoint, client, "/service_brokers") def create( self, broker_url: str, broker_name: str, auth_username: str, auth_password: str, space_guid: Optional[str] = None diff --git a/main/cloudfoundry_client/v2/service_instances.py b/main/cloudfoundry_client/v2/service_instances.py index f17fa7f..edeac74 100644 --- a/main/cloudfoundry_client/v2/service_instances.py +++ b/main/cloudfoundry_client/v2/service_instances.py @@ -10,7 +10,7 @@ class ServiceInstanceManager(EntityManager): list_query_parameters = ["page", "results-per-page", "order-direction", "return_user_provided_service_instances"] def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(ServiceInstanceManager, self).__init__(target_endpoint, client, "/v2/service_instances") + super(ServiceInstanceManager, self).__init__(target_endpoint, client, "/service_instances") def create( self, diff --git a/main/cloudfoundry_client/v2/service_keys.py b/main/cloudfoundry_client/v2/service_keys.py index cb87c3e..4f4da97 100644 --- a/main/cloudfoundry_client/v2/service_keys.py +++ b/main/cloudfoundry_client/v2/service_keys.py @@ -8,7 +8,7 @@ class ServiceKeyManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(ServiceKeyManager, self).__init__(target_endpoint, client, "/v2/service_keys") + super(ServiceKeyManager, self).__init__(target_endpoint, client, "/service_keys") def create(self, service_instance_guid: str, name: str, parameters: Optional[dict] = None) -> Entity: request = self._request(service_instance_guid=service_instance_guid, name=name) diff --git a/main/cloudfoundry_client/v2/service_plan_visibilities.py b/main/cloudfoundry_client/v2/service_plan_visibilities.py index 4d7d513..47bcedf 100644 --- a/main/cloudfoundry_client/v2/service_plan_visibilities.py +++ b/main/cloudfoundry_client/v2/service_plan_visibilities.py @@ -8,7 +8,7 @@ class ServicePlanVisibilityManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(ServicePlanVisibilityManager, self).__init__(target_endpoint, client, "/v2/service_plan_visibilities") + super(ServicePlanVisibilityManager, self).__init__(target_endpoint, client, "/service_plan_visibilities") def create(self, service_plan_guid: str, organization_guid: str) -> Entity: request = self._request() diff --git a/main/cloudfoundry_client/v2/service_plans.py b/main/cloudfoundry_client/v2/service_plans.py index 9a94aa8..8c8e766 100644 --- a/main/cloudfoundry_client/v2/service_plans.py +++ b/main/cloudfoundry_client/v2/service_plans.py @@ -9,7 +9,7 @@ class ServicePlanManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(ServicePlanManager, self).__init__(target_endpoint, client, "/v2/service_plans") + super(ServicePlanManager, self).__init__(target_endpoint, client, "/service_plans") def create_from_resource_file(self, path: str) -> Entity: raise NotImplementedError("No creation allowed") diff --git a/main/cloudfoundry_client/v2/spaces.py b/main/cloudfoundry_client/v2/spaces.py index 6f6254a..b00743c 100644 --- a/main/cloudfoundry_client/v2/spaces.py +++ b/main/cloudfoundry_client/v2/spaces.py @@ -8,7 +8,7 @@ class SpaceManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(SpaceManager, self).__init__(target_endpoint, client, "/v2/spaces") + super(SpaceManager, self).__init__(target_endpoint, client, "/spaces") def delete_unmapped_routes(self, space_guid: str): url = "%s%s/%s/unmapped_routes" % (self.target_endpoint, self.entity_uri, space_guid) diff --git a/main/cloudfoundry_client/v3/apps.py b/main/cloudfoundry_client/v3/apps.py index 1453575..338c86c 100644 --- a/main/cloudfoundry_client/v3/apps.py +++ b/main/cloudfoundry_client/v3/apps.py @@ -9,7 +9,7 @@ class AppManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(AppManager, self).__init__(target_endpoint, client, "/v3/apps") + super(AppManager, self).__init__(target_endpoint, client, "/apps") def restart(self, application_guid: str): return super(AppManager, self)._post("%s%s/%s/actions/restart" % (self.target_endpoint, diff --git a/main/cloudfoundry_client/v3/buildpacks.py b/main/cloudfoundry_client/v3/buildpacks.py index f6a1284..f8930b2 100644 --- a/main/cloudfoundry_client/v3/buildpacks.py +++ b/main/cloudfoundry_client/v3/buildpacks.py @@ -8,7 +8,7 @@ class BuildpackManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(BuildpackManager, self).__init__(target_endpoint, client, "/v3/buildpacks") + super(BuildpackManager, self).__init__(target_endpoint, client, "/buildpacks") def create( self, diff --git a/main/cloudfoundry_client/v3/domains.py b/main/cloudfoundry_client/v3/domains.py index d1f5db3..0077115 100644 --- a/main/cloudfoundry_client/v3/domains.py +++ b/main/cloudfoundry_client/v3/domains.py @@ -21,7 +21,7 @@ def __init__(self, target_endpoint: str, client: "CloudFoundryClient", **kwargs) class DomainManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(DomainManager, self).__init__(target_endpoint, client, "/v3/domains", Domain) + super(DomainManager, self).__init__(target_endpoint, client, "/domains", Domain) def create( self, @@ -44,7 +44,7 @@ def create( return super(DomainManager, self)._create(data) def list_domains_for_org(self, org_guid: str, **kwargs) -> Pagination[Entity]: - uri = "/v3/organizations/{guid}/domains".format(guid=org_guid) + uri = "/organizations/{guid}/domains".format(guid=org_guid) return self._list(uri, **kwargs) def update(self, domain_guid: str, meta_labels: Optional[dict] = None, meta_annotations: Optional[dict] = None) -> Domain: diff --git a/main/cloudfoundry_client/v3/entities.py b/main/cloudfoundry_client/v3/entities.py index 36ec5f4..343a8f1 100644 --- a/main/cloudfoundry_client/v3/entities.py +++ b/main/cloudfoundry_client/v3/entities.py @@ -188,7 +188,7 @@ def _entity(json_object: JsonObject) -> Entity: lambda p: p["resources"], _entity) - def _next_page(self, current_page: JsonObject) -> Union[None, JsonObject]: + def _next_page(self, current_page: JsonObject) -> Optional[JsonObject]: pagination = current_page.get("pagination") if ( pagination is None diff --git a/main/cloudfoundry_client/v3/feature_flags.py b/main/cloudfoundry_client/v3/feature_flags.py index 47c35d8..3c280ad 100644 --- a/main/cloudfoundry_client/v3/feature_flags.py +++ b/main/cloudfoundry_client/v3/feature_flags.py @@ -8,7 +8,7 @@ class FeatureFlagManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(FeatureFlagManager, self).__init__(target_endpoint, client, "/v3/feature_flags") + super(FeatureFlagManager, self).__init__(target_endpoint, client, "/feature_flags") def update(self, name: str, enabled: Optional[bool] = True, custom_error_message: Optional[str] = None) -> Entity: data = {"enabled": enabled, "custom_error_message": custom_error_message} diff --git a/main/cloudfoundry_client/v3/isolation_segments.py b/main/cloudfoundry_client/v3/isolation_segments.py index 67fa114..391e4d5 100644 --- a/main/cloudfoundry_client/v3/isolation_segments.py +++ b/main/cloudfoundry_client/v3/isolation_segments.py @@ -8,7 +8,7 @@ class IsolationSegmentManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(IsolationSegmentManager, self).__init__(target_endpoint, client, "/v3/isolation_segments") + super(IsolationSegmentManager, self).__init__(target_endpoint, client, "/isolation_segments") def create(self, name: str, meta_labels: Optional[dict] = None, meta_annotations: Optional[dict] = None) -> Entity: data = {"name": name, "metadata": {"labels": meta_labels, "annotations": meta_annotations}} diff --git a/main/cloudfoundry_client/v3/jobs.py b/main/cloudfoundry_client/v3/jobs.py index 80bd510..5fd10fb 100644 --- a/main/cloudfoundry_client/v3/jobs.py +++ b/main/cloudfoundry_client/v3/jobs.py @@ -15,7 +15,7 @@ class JobTimeout(Exception): class JobManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(JobManager, self).__init__(target_endpoint, client, "/v3/jobs") + super(JobManager, self).__init__(target_endpoint, client, "/jobs") def wait_for_job_completion( self, diff --git a/main/cloudfoundry_client/v3/organization_quotas.py b/main/cloudfoundry_client/v3/organization_quotas.py index abe5015..2779f9c 100644 --- a/main/cloudfoundry_client/v3/organization_quotas.py +++ b/main/cloudfoundry_client/v3/organization_quotas.py @@ -35,7 +35,7 @@ class DomainsQuota: class OrganizationQuotaManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super().__init__(target_endpoint, client, "/v3/organization_quotas") + super().__init__(target_endpoint, client, "/organization_quotas") def remove(self, guid: str, asynchronous: bool = True) -> Optional[str]: return super()._remove(guid, asynchronous) diff --git a/main/cloudfoundry_client/v3/organizations.py b/main/cloudfoundry_client/v3/organizations.py index 2086d31..e7b4639 100644 --- a/main/cloudfoundry_client/v3/organizations.py +++ b/main/cloudfoundry_client/v3/organizations.py @@ -8,7 +8,7 @@ class OrganizationManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(OrganizationManager, self).__init__(target_endpoint, client, "/v3/organizations") + super(OrganizationManager, self).__init__(target_endpoint, client, "/organizations") def create( self, name: str, suspended: bool, meta_labels: Optional[dict] = None, meta_annotations: Optional[dict] = None diff --git a/main/cloudfoundry_client/v3/processes.py b/main/cloudfoundry_client/v3/processes.py index cb632f3..f7ee291 100644 --- a/main/cloudfoundry_client/v3/processes.py +++ b/main/cloudfoundry_client/v3/processes.py @@ -8,4 +8,4 @@ class ProcessManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(ProcessManager, self).__init__(target_endpoint, client, "/v3/processes") + super(ProcessManager, self).__init__(target_endpoint, client, "/processes") diff --git a/main/cloudfoundry_client/v3/roles.py b/main/cloudfoundry_client/v3/roles.py index 9123d5a..ed466b9 100644 --- a/main/cloudfoundry_client/v3/roles.py +++ b/main/cloudfoundry_client/v3/roles.py @@ -8,7 +8,7 @@ class RoleManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(RoleManager, self).__init__(target_endpoint, client, "/v3/roles") + super(RoleManager, self).__init__(target_endpoint, client, "/roles") def remove(self, role_guid: str, asynchronous: bool = True) -> Optional[str]: return super(RoleManager, self)._remove(role_guid, asynchronous) diff --git a/main/cloudfoundry_client/v3/security_groups.py b/main/cloudfoundry_client/v3/security_groups.py index b134e03..fdde7a2 100644 --- a/main/cloudfoundry_client/v3/security_groups.py +++ b/main/cloudfoundry_client/v3/security_groups.py @@ -37,7 +37,7 @@ class GloballyEnabled: class SecurityGroupManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(SecurityGroupManager, self).__init__(target_endpoint, client, "/v3/security_groups") + super(SecurityGroupManager, self).__init__(target_endpoint, client, "/security_groups") def create(self, name: str, diff --git a/main/cloudfoundry_client/v3/service_brokers.py b/main/cloudfoundry_client/v3/service_brokers.py index c39795b..a354de9 100644 --- a/main/cloudfoundry_client/v3/service_brokers.py +++ b/main/cloudfoundry_client/v3/service_brokers.py @@ -8,7 +8,7 @@ class ServiceBrokerManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(ServiceBrokerManager, self).__init__(target_endpoint, client, "/v3/service_brokers") + super(ServiceBrokerManager, self).__init__(target_endpoint, client, "/service_brokers") def create( self, diff --git a/main/cloudfoundry_client/v3/service_credential_bindings.py b/main/cloudfoundry_client/v3/service_credential_bindings.py index d587b7a..6599686 100644 --- a/main/cloudfoundry_client/v3/service_credential_bindings.py +++ b/main/cloudfoundry_client/v3/service_credential_bindings.py @@ -9,7 +9,7 @@ class ServiceCredentialBindingManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): super(ServiceCredentialBindingManager, self).__init__(target_endpoint, client, - "/v3/service_credential_bindings") + "/service_credential_bindings") def create( self, diff --git a/main/cloudfoundry_client/v3/service_instances.py b/main/cloudfoundry_client/v3/service_instances.py index ad33614..695b0ef 100644 --- a/main/cloudfoundry_client/v3/service_instances.py +++ b/main/cloudfoundry_client/v3/service_instances.py @@ -9,7 +9,7 @@ class ServiceInstanceManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(ServiceInstanceManager, self).__init__(target_endpoint, client, "/v3/service_instances") + super(ServiceInstanceManager, self).__init__(target_endpoint, client, "/service_instances") def create( self, diff --git a/main/cloudfoundry_client/v3/service_offerings.py b/main/cloudfoundry_client/v3/service_offerings.py index 37f7d0c..c784ca2 100644 --- a/main/cloudfoundry_client/v3/service_offerings.py +++ b/main/cloudfoundry_client/v3/service_offerings.py @@ -8,7 +8,7 @@ class ServiceOfferingsManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(ServiceOfferingsManager, self).__init__(target_endpoint, client, "/v3/service_offerings") + super(ServiceOfferingsManager, self).__init__(target_endpoint, client, "/service_offerings") def update(self, guid: str, meta_labels: Optional[dict] = None, meta_annotations: Optional[dict] = None) -> Entity: payload = dict() diff --git a/main/cloudfoundry_client/v3/service_plans.py b/main/cloudfoundry_client/v3/service_plans.py index 257c3bf..9e27e15 100644 --- a/main/cloudfoundry_client/v3/service_plans.py +++ b/main/cloudfoundry_client/v3/service_plans.py @@ -8,7 +8,7 @@ class ServicePlanManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(ServicePlanManager, self).__init__(target_endpoint, client, "/v3/service_plans") + super(ServicePlanManager, self).__init__(target_endpoint, client, "/service_plans") def update( self, diff --git a/main/cloudfoundry_client/v3/spaces.py b/main/cloudfoundry_client/v3/spaces.py index f871a43..acb3597 100644 --- a/main/cloudfoundry_client/v3/spaces.py +++ b/main/cloudfoundry_client/v3/spaces.py @@ -8,7 +8,7 @@ class SpaceManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(SpaceManager, self).__init__(target_endpoint, client, "/v3/spaces") + super(SpaceManager, self).__init__(target_endpoint, client, "/spaces") def create(self, name: str, org_guid: str) -> Entity: return super(SpaceManager, self)._create(dict(name=name, relationships=dict(organization=ToOneRelationship(org_guid)))) diff --git a/main/cloudfoundry_client/v3/tasks.py b/main/cloudfoundry_client/v3/tasks.py index a7ce16e..1367348 100644 --- a/main/cloudfoundry_client/v3/tasks.py +++ b/main/cloudfoundry_client/v3/tasks.py @@ -8,7 +8,7 @@ class TaskManager(EntityManager): def __init__(self, target_endpoint: str, client: "CloudFoundryClient"): - super(TaskManager, self).__init__(target_endpoint, client, "/v3/tasks") + super(TaskManager, self).__init__(target_endpoint, client, "/tasks") def create( self, @@ -24,7 +24,7 @@ def create( request["disk_in_mb"] = disk_in_mb request["memory_in_mb"] = memory_in_mb request["droplet_guid"] = droplet_guid - return self._post("%s/v3/apps/%s/tasks" % (self.target_endpoint, application_guid), data=request) + return self._post("%s/apps/%s/tasks" % (self.target_endpoint, application_guid), data=request) def cancel(self, task_guid: str) -> Entity: - return self._post("%s/v3/tasks/%s/actions/cancel" % (self.target_endpoint, task_guid)) + return self._post("%s/tasks/%s/actions/cancel" % (self.target_endpoint, task_guid)) diff --git a/setup.py b/setup.py index ddcfa0a..a104087 100644 --- a/setup.py +++ b/setup.py @@ -26,10 +26,30 @@ def purge_sub_dir(path): shutil.rmtree(os.path.join(os.path.dirname(__file__), path)) -if "test" in sys.argv[1:]: - print("%s added" % os.path.join(os.getcwd(), "test")) - sys.path.append(os.path.join(os.getcwd(), "test")) +# temporary solution as test has been removed from setuptools +class TestCommand(Command): + description = "run tests" + user_options = [] + + def initialize_options(self): + pass + def finalize_options(self): + pass + + def run(self): + from unittest import TestLoader, TextTestRunner, main + sys.path.append(os.path.join(os.getcwd(), "test")) + loader = TestLoader() + suite = loader.discover( + start_dir=os.path.join(os.path.dirname(__file__), "test"), + pattern='test_*.py', + top_level_dir='./' + ) + runner = TextTestRunner() + test_result = runner.run(suite) + if not test_result.wasSuccessful(): + sys.exit(1) class GenerateCommand(Command): description = "generate protobuf class generation" @@ -75,11 +95,11 @@ def run(self): "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Communications", ], entry_points={ @@ -87,8 +107,7 @@ def run(self): "cloudfoundry-client = %s.main.main:main" % package_directory, ] }, - cmdclass=dict(generate=GenerateCommand), + cmdclass=dict(generate=GenerateCommand, test=TestCommand), package_dir={package_directory: "%s/%s" % (src_dir, package_directory)}, install_requires=[requirement.rstrip(" \r\n") for requirement in open("requirements.txt")], - test_suite="test", ) diff --git a/test/abstract_test_case.py b/test/abstract_test_case.py index a672e0a..975fc2e 100644 --- a/test/abstract_test_case.py +++ b/test/abstract_test_case.py @@ -30,8 +30,6 @@ class AbstractTestCase(object): TOKEN_ENDPOINT = "http://token.somewhere.org" DOPPLER_ENDPOINT = "wss://doppler.nd-cfapi.itn.ftgroup:443" LOG_STREAM_ENDPOINT = "https://log-stream.nd-cfapi.itn.ftgroup" - API_V2_VERSION = "2.141.0" - API_V3_VERSION = "3.76.0" @classmethod def mock_client_class(cls): @@ -43,33 +41,40 @@ def build_client(self): self.client = CloudFoundryClient(self.TARGET_ENDPOINT) @staticmethod - def _mock_info_calls(requests, with_doppler: bool = True, with_log_streams: bool = True): + def _mock_info_calls( + requests, + with_doppler: bool = True, + with_log_streams: bool = True, + with_v2: bool = True, + with_v3: bool = True + ): + links = { + "self": dict(href=AbstractTestCase.TARGET_ENDPOINT), + "cloud_controller_v2": dict( + href="%s/v2" % AbstractTestCase.TARGET_ENDPOINT, + meta=dict(version="2.141.0"), + ), + "cloud_controller_v3": dict( + href="%s/v3" % AbstractTestCase.TARGET_ENDPOINT, + meta=dict(version="3.76.0"), + ), + "logging": dict(href=AbstractTestCase.DOPPLER_ENDPOINT) if with_doppler else None, + "log_stream": dict(href=AbstractTestCase.LOG_STREAM_ENDPOINT) if with_log_streams else None, + "app_ssh": dict(href="ssh.nd-cfapi.itn.ftgroup:80"), + "uaa": dict(href="https://uaa.nd-cfapi.itn.ftgroup"), + "login": dict(href=AbstractTestCase.AUTHORIZATION_ENDPOINT), + "network_policy_v0": dict(href="https://api.nd-cfapi.itn.ftgroup/networking/v0/external"), + "network_policy_v1": dict(href="https://api.nd-cfapi.itn.ftgroup/networking/v1/external"), + } + if not with_v2: + del links["cloud_controller_v2"] + if not with_v3: + del links["cloud_controller_v3"] requests.get.side_effect = [ MockResponse( "%s/" % AbstractTestCase.TARGET_ENDPOINT, status_code=HTTPStatus.OK.value, - text=json.dumps( - dict( - links={ - "self": dict(href=AbstractTestCase.TARGET_ENDPOINT), - "cloud_controller_v2": dict( - href="%s/v2" % AbstractTestCase.TARGET_ENDPOINT, - meta=dict(version=AbstractTestCase.API_V2_VERSION), - ), - "cloud_controller_v3": dict( - href="%s/v3" % AbstractTestCase.TARGET_ENDPOINT, - meta=dict(version=AbstractTestCase.API_V3_VERSION), - ), - "logging": dict(href=AbstractTestCase.DOPPLER_ENDPOINT) if with_doppler else None, - "log_stream": dict(href=AbstractTestCase.LOG_STREAM_ENDPOINT) if with_log_streams else None, - "app_ssh": dict(href="ssh.nd-cfapi.itn.ftgroup:80"), - "uaa": dict(href="https://uaa.nd-cfapi.itn.ftgroup"), - "login": dict(href=AbstractTestCase.AUTHORIZATION_ENDPOINT), - "network_policy_v0": dict(href="https://api.nd-cfapi.itn.ftgroup/networking/v0/external"), - "network_policy_v1": dict(href="https://api.nd-cfapi.itn.ftgroup/networking/v1/external"), - } - ) - ), + text=json.dumps(dict(links=links)), ), ] diff --git a/test/test_client.py b/test/test_client.py index b53d6de..7039b90 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -42,6 +42,28 @@ def test_build_client_when_no_doppler(self): client = CloudFoundryClient(self.TARGET_ENDPOINT, token_format="opaque") self.assertRaises(NotImplementedError, lambda: client.doppler) + def test_build_client_when_no_v2(self): + requests = FakeRequests() + session = MockSession() + with patch("oauth2_client.credentials_manager.requests", new=requests), patch( + "cloudfoundry_client.client.requests", new=requests + ): + requests.Session.return_value = session + self._mock_info_calls(requests, with_v2=False) + client = CloudFoundryClient(self.TARGET_ENDPOINT, token_format="opaque") + self.assertRaises(NotImplementedError, lambda: client.v2) + + def test_build_client_when_no_v3(self): + requests = FakeRequests() + session = MockSession() + with patch("oauth2_client.credentials_manager.requests", new=requests), patch( + "cloudfoundry_client.client.requests", new=requests + ): + requests.Session.return_value = session + self._mock_info_calls(requests, with_v3=False) + client = CloudFoundryClient(self.TARGET_ENDPOINT, token_format="opaque") + self.assertRaises(NotImplementedError, lambda: client.v3) + def test_grant_password_request_with_token_format_opaque(self): requests = FakeRequests() session = MockSession() @@ -162,7 +184,9 @@ def test_get_info(self): self._mock_info_calls(requests) info = client._get_info(self.TARGET_ENDPOINT) self.assertEqual(info.api_endpoint, self.TARGET_ENDPOINT) - self.assertEqual(info.api_v2_version, self.API_V2_VERSION) + self.assertEqual(info.api_v2_version, "fuck") + self.assertEqual(info.api_v2_url, "%s/v2" % self.TARGET_ENDPOINT) + self.assertEqual(info.api_v3_url, "%s/v3" % self.TARGET_ENDPOINT) self.assertEqual(info.doppler_endpoint, self.DOPPLER_ENDPOINT) self.assertEqual(info.log_stream_endpoint, self.LOG_STREAM_ENDPOINT)