def __init__(self, client): self._client = client tkg_s_config = Configuration() tkg_s_config.host = f"{self._client.get_cloudapi_uri()}/1.0.0/" tkg_s_config.verify_ssl = self._client._verify_ssl_certs self._tkg_s_client = ApiClient(configuration=tkg_s_config) jwt_token = self._client.get_access_token() self._tkg_s_client.set_default_header( cli_constants.TKGRequestHeaderKey.AUTHORIZATION, f"Bearer {jwt_token}") # get highest supported api version api_version = self._client.get_api_version() supported_api_versions = self._client.get_supported_versions_list() max_api_version = utils.get_max_api_version(supported_api_versions) # api_version = self._client.get_api_version() # use api_version 37.0.0-alpha if VCD 10.3 is used. if VCDApiVersion(max_api_version) >= VCDApiVersion( ApiVersion.VERSION_36.value): # noqa: E501 api_version = '37.0.0-alpha' self._tkg_s_client.set_default_header( cli_constants.TKGRequestHeaderKey.ACCEPT, # noqa: E501 f"application/json;version={api_version}") # noqa: E501 self._tkg_s_client_api = TkgClusterApi(api_client=self._tkg_s_client)
def __init__(self, base_url: str, token: str, api_version: str, logger_debug: logging.Logger, logger_wire: logging.Logger, verify_ssl: bool = True, is_sys_admin: bool = False): if not base_url.endswith('/'): base_url += '/' self._base_url = base_url self._headers = { "Authorization": f"Bearer {token}", "Accept": f"application/json;version={api_version}", "Content-Type": "application/json" } self._verify_ssl = verify_ssl self.LOGGER = logger_debug self.LOGGER_WIRE = logger_wire self._last_response = None self.is_sys_admin = is_sys_admin self._api_version = api_version self._vcd_api_version = VCDApiVersion(api_version)
def force_delete_entity( self, entity_id: str, is_request_async=False ) -> Union[dict, Tuple[dict, dict]]: # noqa: E501 """Delete the defined entity without invoking hooks. :param str entity_id: Id of the entity. :param bool is_request_async: The request is intended to be asynchronous if this flag is set, href of the task is returned in addition to the response body :return: response body or response body and response headers :rtype: Union[dict, Tuple[dict, dict]] """ # response will be a tuple (response_body, response_header) if # is_request_async is true. Else, it will be just response_body vcd_api_version = self._cloudapi_client.get_vcd_api_version() resource_url_relative_path = f"{CloudApiResource.ENTITIES}/{entity_id}" if vcd_api_version >= VCDApiVersion( vcd_client.ApiVersion.VERSION_36.value): # noqa: E501 resource_url_relative_path += "?invokeHooks=false" response = self._cloudapi_client.do_request( method=RequestMethod.DELETE, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=resource_url_relative_path, # noqa: E501 return_response_headers=is_request_async) if is_request_async: return response[0], response[1][HttpResponseHeader.LOCATION.value] return response
def get_runtime_rde_version_by_vcd_api_version(vcd_api_version: str) -> str: major_vcd_api_version = VCDApiVersion(vcd_api_version).major val = def_constants.MAP_VCD_API_VERSION_TO_RUNTIME_RDE_VERSION.get( major_vcd_api_version) # noqa: E501 if not val: val = '0.0.0' return val
def _raise_error_if_amqp_not_supported( is_mqtt_used, default_api_version, logger=NULL_LOGGER ): """Raise error if CSE is configured with AMQP but AMQP bus not supported. AMQP cannot be used if CSE 3.1 or above is configured with 10.3 or above. :param bool is_mqtt_used: boolean indicating if mqtt is used :param str default_api_version: max VCD API version used by CSE. :raises: AmqpError """ if not is_mqtt_used and VCDApiVersion(default_api_version) > \ _MAX_API_VERSION_TO_RUN_CSE_WITH_AMQP_IN_NON_LEGACY_MODE: msg = f"Cannot use AMQP message bus when working with api version" \ f" {default_api_version}. Please upgrade to MQTT." logger.error(msg) raise AmqpError(msg)
def delete_entity( self, entity_id: str, invoke_hooks: bool = False, is_request_async=False ) -> Union[dict, Tuple[dict, dict]]: # noqa: E501 """Delete the defined entity. :param str entity_id: Id of the entity. :param bool invoke_hooks: set to true if hooks need to be invoked :param bool is_request_async: The request is intended to be asynchronous if this flag is set, href of the task is returned in addition to the response body :return: response body or response body and response headers :rtype: Union[dict, Tuple[dict, dict]] """ # response will be a tuple (response_body, response_header) if # is_request_async is true. Else, it will be just response_body vcd_api_version = self._cloudapi_client.get_vcd_api_version() resource_url_relative_path = f"{CloudApiResource.ENTITIES}/{entity_id}" # TODO: Also include any persona having Administrator:FullControl # on CSE:nativeCluster if (vcd_api_version >= VCDApiVersion( vcd_client.ApiVersion.VERSION_36.value) # noqa: E501 and self._cloudapi_client.is_sys_admin and not invoke_hooks): resource_url_relative_path += f"?invokeHooks={str(invoke_hooks).lower()}" # noqa: E501 response = self._cloudapi_client.do_request( method=RequestMethod.DELETE, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=resource_url_relative_path, # noqa: E501 return_response_headers=is_request_async) if is_request_async: # if request is async, return the location header as well # TODO: Use the Http response status code to decide which # header name to use for task_href # 202 - location header, # 200 - xvcloud-task-location needs to be used return response[0], response[1][HttpResponseHeader.LOCATION.value] return response
def _override_client(ctx) -> None: """Replace the vcd client in ctx.obj with new one. New vcd client takes the CSE server_api_version as api_version param. Save profile also with 'cse_server_api_version' for subsequent commands. :param <click.core.Context> ctx: click context """ profiles = Profiles.load() # if the key CSE_SERVER_RUNNING is not present in the profiles.yaml, # we make an assumption that CSE server is running is_cse_server_running = profiles.get(CSE_SERVER_RUNNING, default=True) cse_server_api_version = profiles.get(CSE_SERVER_API_VERSION) if not is_cse_server_running: CLIENT_LOGGER.debug( "CSE server not running as per profile, restricting CLI to only TKG operations." ) # noqa: E501 restrict_cli_to_tkg_s_operations() ctx.obj['profiles'] = profiles return # Get server_api_version; save it in profiles if doesn't exist if not cse_server_api_version: try: system = syst.System(ctx.obj['client']) sys_info = system.get_info() is_pre_cse_3_1_server = False if CSE_SERVER_LEGACY_MODE not in sys_info or \ CSE_SERVER_SUPPORTED_API_VERSIONS not in sys_info: is_pre_cse_3_1_server = True if not is_pre_cse_3_1_server: is_cse_server_running_in_legacy_mode = \ sys_info.get(CSE_SERVER_LEGACY_MODE) cse_server_supported_api_versions = \ set(sys_info.get(CSE_SERVER_SUPPORTED_API_VERSIONS)) cse_client_supported_api_versions = \ set(shared_constants.SUPPORTED_VCD_API_VERSIONS) common_supported_api_versions = \ list(cse_server_supported_api_versions.intersection( cse_client_supported_api_versions)) # ToDo: Instead of float use proper version comparison if is_cse_server_running_in_legacy_mode: common_supported_api_versions = \ [VCDApiVersion(x) for x in common_supported_api_versions # noqa: E501 if VCDApiVersion(x) < VcdApiVersionObj.VERSION_35.value] # noqa: E501 else: common_supported_api_versions = \ [VCDApiVersion(x) for x in common_supported_api_versions # noqa: E501 if VCDApiVersion(x) >= VcdApiVersionObj.VERSION_35.value] # noqa: E501 cse_server_api_version = \ str(max(common_supported_api_versions)) CLIENT_LOGGER.debug( f"Server api versions : {cse_server_supported_api_versions}, " # noqa: E501 f"Client api versions : {cse_client_supported_api_versions}, " # noqa: E501 f"Server in Legacy mode : {is_cse_server_running_in_legacy_mode}, " # noqa: E501 f"Selected api version : {cse_server_api_version}.") else: cse_server_api_version = \ sys_info.get(CSE_SERVER_API_VERSION) CLIENT_LOGGER.debug( "Pre CSE 3.1 server detected. Selected api version : " f"{cse_server_api_version}.") profiles.set(CSE_SERVER_API_VERSION, cse_server_api_version) profiles.set(CSE_SERVER_RUNNING, True) profiles.save() except (requests.exceptions.Timeout, CseResponseError) as err: CLIENT_LOGGER.error(err, exc_info=True) CLIENT_LOGGER.debug( "Request to CSE server timed out. Restricting CLI to only TKG operations." ) # noqa: E501 profiles.set(CSE_SERVER_RUNNING, False) restrict_cli_to_tkg_s_operations() ctx.obj['profiles'] = profiles profiles.save() return client = Client(profiles.get('host'), api_version=cse_server_api_version, verify_ssl_certs=profiles.get('verify'), log_file='vcd.log', log_requests=profiles.get('log_request'), log_headers=profiles.get('log_header'), log_bodies=profiles.get('log_body')) client.rehydrate_from_token(token=profiles.get('token'), is_jwt_token=profiles.get('is_jwt_token')) ctx.obj['client'] = client ctx.obj['profiles'] = profiles
def add_additional_details_to_config( config: Dict, vcd_host: str, vcd_username: str, vcd_password: str, verify_ssl: bool, is_legacy_mode: bool, is_mqtt_exchange: bool, log_wire: bool, log_wire_file: str ): """Update config dict with computed key-value pairs. :param dict config: :param str vcd_host: :param str vcd_username: :param str vcd_password: :param bool verify_ssl: :param bool is_legacy_mode: :param bool is_mqtt_exchange: :param bool log_wire: :param str log_wire_file: :return: the updated config file :rtype: dict """ # Compute common supported api versions by the CSE server and vCD sysadmin_client = None try: sysadmin_client = Client( vcd_host, verify_ssl_certs=verify_ssl, log_file=log_wire_file, log_requests=log_wire, log_headers=log_wire, log_bodies=log_wire ) sysadmin_client.set_credentials( BasicLoginCredentials( vcd_username, SYSTEM_ORG_NAME, vcd_password ) ) vcd_supported_api_versions = \ set(sysadmin_client.get_supported_versions_list()) cse_supported_api_versions = set(SUPPORTED_VCD_API_VERSIONS) common_supported_api_versions = \ list(cse_supported_api_versions.intersection(vcd_supported_api_versions)) # noqa: E501 common_supported_api_versions.sort() if is_legacy_mode: common_supported_api_versions = \ [x for x in common_supported_api_versions if VCDApiVersion(x) < VcdApiVersionObj.VERSION_35.value] else: common_supported_api_versions = \ [x for x in common_supported_api_versions if VCDApiVersion(x) >= VcdApiVersionObj.VERSION_35.value] config['service']['supported_api_versions'] = \ common_supported_api_versions finally: if sysadmin_client: sysadmin_client.logout() # Convert legacy_mode flag in service_section to corresponding # feature flags if 'feature_flags' not in config: config['feature_flags'] = {} config['feature_flags']['legacy_api'] = str_to_bool(is_legacy_mode) config['feature_flags']['non_legacy_api'] = \ not str_to_bool(is_legacy_mode) # Compute the default api version as the max supported version # Also compute the RDE version in use max_vcd_api_version_supported: str = get_max_api_version(config['service']['supported_api_versions']) # noqa: E501 config['service']['default_api_version'] = max_vcd_api_version_supported config['service']['rde_version_in_use'] = semantic_version.Version( rde_utils.get_runtime_rde_version_by_vcd_api_version( max_vcd_api_version_supported ) ) # Update the config dict with telemetry specific key value pairs update_with_telemetry_settings( config_dict=config, vcd_host=vcd_host, vcd_username=vcd_username, vcd_password=vcd_password, verify_ssl=verify_ssl, is_mqtt_exchange=is_mqtt_exchange ) return config
from pyvcloud.vcd.vcd_api_version import VCDApiVersion import container_service_extension.common.constants.shared_constants as shared_constants # noqa: E501 CLUSTER_ACL_LIST_FIELDS = [ shared_constants.AccessControlKey.ACCESS_LEVEL_ID, shared_constants.AccessControlKey.MEMBER_ID, shared_constants.AccessControlKey.USERNAME ] # ACL Path ACTION_CONTROL_ACCESS_PATH = '/action/controlAccess/' DEF_INTERFACE_ID_PREFIX = 'urn:vcloud:interface' DEF_NATIVE_ENTITY_TYPE_NSS = 'nativeCluster' DEF_ENTITY_TYPE_ID_PREFIX = 'urn:vcloud:type' DEF_API_MIN_VERSION = VCDApiVersion('35.0') DEF_SCHEMA_DIRECTORY = 'cse_def_schema' DEF_ERROR_MESSAGE_KEY = 'message' DEF_RESOLVED_STATE = 'RESOLVED' PAYLOAD_VERSION_PREFIX = 'cse.vmware.com/' PAYLOAD_VERSION_2_0 = PAYLOAD_VERSION_PREFIX + 'v2.0' PAYLOAD_VERSION_2_1 = PAYLOAD_VERSION_PREFIX + 'v2.1' @unique class Vendor(str, Enum): CSE = 'cse' VMWARE = 'vmware'
def update_entity( self, entity_id: str, entity: DefEntity = None, invoke_hooks=False, is_request_async=False, changes: dict = None ) -> Union[DefEntity, Tuple[DefEntity, dict]]: # noqa: E501 """Update entity instance. :param str entity_id: Id of the entity to be updated. :param DefEntity entity: Modified entity to be updated. :param bool invoke_hooks: Value indicating whether hook-based-behaviors need to be triggered or not. :param bool is_request_async: The request is intended to be asynchronous if this flag is set, href of the task is returned in addition to the response body :param dict changes: dictionary of changes for the rde. The key indicates the field updated, e.g. 'entity.status'. The value indicates the value for this field. :return: Updated entity or Updated entity and response headers :rtype: Union[DefEntity, Tuple[DefEntity, dict]] The 'changes' parameter takes precedence over 'entity', and at least one of these fields is required. Also, 'changes' is required when api version >= 36.0; this allows getting the entity so that the current entity is not overwritten. etag usage is only supported for api version >= 36.0. """ if entity is None and changes is None: raise Exception("at least 'entity' or 'changes' must not be None") vcd_api_version = self._cloudapi_client.get_vcd_api_version() api_at_least_36: bool = vcd_api_version >= VCDApiVersion( vcd_client.ApiVersion.VERSION_36.value) # noqa: E501 if api_at_least_36 and changes is None: raise Exception("changes must be specified when client has api " "version >= 36.0") resource_url_relative_path = f"{CloudApiResource.ENTITIES}/{entity_id}" # TODO Float conversions must be changed to Semantic versioning. # TODO: Also include any persona having Administrator:FullControl # on CSE:nativeCluster if api_at_least_36 and self._cloudapi_client.is_sys_admin and not invoke_hooks: # noqa: E501 resource_url_relative_path += f"?invokeHooks={str(invoke_hooks).lower()}" # noqa: E501 for _ in range(server_constants.MAX_RDE_UPDATE_ATTEMPTS): # get entity etag = "" if changes: entity, etag = self._form_updated_entity(entity_id, changes) payload: dict = entity.to_dict() entity_kind = payload.get('entity', {}).get('kind', "") # Prevent users with rights <= EDIT/VIEW on CSE:NATIVECLUSTER from # updating "private" property of RDE "status" section # TODO: Replace sys admin check with FULL CONTROL rights check on # CSE:NATIVECLUSTER. Users with no FULL CONTROL rights cannot # update private property of entity->status. if not self._cloudapi_client.is_sys_admin: # HACK: TKGm uses the principle of least privileges and # does not use a ClusterAuthor role at all. Instead it only # uses a ClusterAdmin role which always has FC rights. # Hence it does not escalate to sys-admin and hence fails # this check. Hence skip TKGm clusters. # The correct fix for this is to implement the # to-do mentioned above. That is covered by VCDA-2969. if entity_kind != shared_constants.ClusterEntityKind.TKG_M.value: # noqa: E501 payload.get('entity', {}).get('status', {}).pop('private', None) # noqa: E501 # if request is async, return the task href in # x_vmware_vcloud_task_location header # TODO: Use the Http response status code to decide which # header name to use for task_href # 202 - location header, # 200 - xvcloud-task-location needs to be used try: additional_request_headers = { "If-Match": etag } if api_at_least_36 else None # noqa: E501 response_body, headers = self._cloudapi_client.do_request( method=RequestMethod.PUT, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=resource_url_relative_path, payload=payload, return_response_headers=True, additional_request_headers=additional_request_headers) def_entity = DefEntity(**response_body) if is_request_async: return def_entity, headers[ HttpResponseHeader.X_VMWARE_VCLOUD_TASK_LOCATION. value] # noqa: E501 return def_entity except Exception: last_cloudapi_response = self._cloudapi_client.get_last_response( ) # noqa: E501 if last_cloudapi_response.status_code == codes.precondition_failed: # noqa: E501 continue raise raise Exception( f"failed to update RDE after {server_constants.MAX_RDE_UPDATE_ATTEMPTS} attempts" ) # noqa: E501