1616from urllib3 .exceptions import InsecureRequestWarning
1717from json import JSONDecodeError
1818from typing import Union
19+ import urllib .parse as urlparse
20+ from os import path
1921
2022import cdpcli
2123from cdpcli import VERSION as CDPCLI_VERSION
@@ -457,6 +459,20 @@ def _get_path(obj, path):
457459 return None
458460 return value
459461
462+ @staticmethod
463+ def encode_value (value ):
464+ if value :
465+ return urlparse .quote (value )
466+ return None
467+
468+ def expand_file_path (self , file_path ):
469+ if path .exists (file_path ):
470+ return path .expandvars (path .expanduser (file_path ))
471+ else :
472+ self .throw_error (
473+ CdpError ('Path [{}] not found' .format (file_path ))
474+ )
475+
460476 def wait_for_state (self , describe_func , params : dict , field : Union [str , None , list ] = 'status' ,
461477 state : Union [list , str , None ] = None , delay : int = 15 , timeout : int = 3600 ,
462478 ignore_failures : bool = False ):
@@ -538,8 +554,85 @@ def wait_for_state(self, describe_func, params: dict, field: Union[str, None, li
538554 CdpError ("Timeout waiting for function {0} with params [{1}] to return field {2} with state {3}"
539555 .format (describe_func .__name__ , str (params ), field , str (state ))))
540556
557+ def _scrub_inputs (self , inputs ):
558+ # Used in main call() function
559+ logging .debug ("Scrubbing inputs in payload" )
560+ # Remove unused submission values as the API rejects them
561+ payload = {x : y for x , y in inputs .items () if y is not None }
562+ # Remove and issue warning for empty string submission values as the API rejects them
563+ _ = [self .throw_warning (
564+ CdpWarning ('Removing empty string arg %s from submission' % x ))
565+ for x , y in payload .items () if y == '' ]
566+ payload = {x : y for x , y in payload .items () if y != '' }
567+ return payload
568+
569+ def _handle_paging (self , response , call_function , payload ):
570+ # Used in main call() function
571+ while 'nextToken' in response :
572+ token = response .pop ('nextToken' )
573+ next_page = call_function (
574+ ** payload , startingToken = token , pageSize = self .DEFAULT_PAGE_SIZE )
575+ for key in next_page .keys ():
576+ if isinstance (next_page [key ], str ):
577+ response [key ] = next_page [key ]
578+ elif isinstance (next_page [key ], list ):
579+ response [key ] += (next_page [key ])
580+ return response
581+
582+ def _handle_call_errors (self , err , squelch ):
583+ # Used in main call() function
584+ # Note that the cascade of behaviors here is designed to be convenient for Ansible module development
585+ parsed_err = CdpError (err )
586+ if self .debug :
587+ log = self .get_log ()
588+ parsed_err .update (sdk_out = log , sdk_out_lines = log .splitlines ())
589+ if self .strict_errors is True :
590+ self .throw_error (parsed_err )
591+ if isinstance (err , ClientError ):
592+ if squelch is not None :
593+ for item in squelch :
594+ if item .value in str (parsed_err .__getattribute__ (item .field )):
595+ warning = item .warning if item .warning is not None else str (parsed_err .violations )
596+ self .throw_warning (CdpWarning (warning ))
597+ return item .default
598+ return parsed_err
599+
600+ def _handle_redirect_call (self , client , call_function , payload , headers ):
601+ # cdpcli/extensions/redirect.py
602+ http , resp = client .make_api_call (
603+ client .meta .method_to_api_mapping [call_function ],
604+ payload ,
605+ allow_redirects = False
606+ )
607+ if not http .is_redirect :
608+ self .throw_error (CdpError ("Redirect headers supplied but no redirect URL from API call" ))
609+ redirect_url = http .headers .get ('Location' , None )
610+
611+ if redirect_url is not None :
612+ with open (self .expand_file_path (payload ['file' ]), 'rb' ) as f :
613+ http , full_response = client .make_request (
614+ operation_name = client .meta .method_to_api_mapping [call_function ],
615+ method = 'post' ,
616+ url_path = redirect_url ,
617+ headers = self ._scrub_inputs (inputs = headers ),
618+ body = f
619+ )
620+ else :
621+ self .throw_error (CdpError ("Redirect call attempted but redirect URL was empty" ))
622+ return full_response
623+
624+ def _handle_std_call (self , client , call_function , payload ):
625+ func_to_call = getattr (client , call_function )
626+ raw_response = func_to_call (** payload )
627+ if raw_response is not None and 'nextToken' in raw_response :
628+ logging .debug ("Found paged results in %s" % call_function )
629+ full_response = self ._handle_paging (raw_response , func_to_call , payload )
630+ else :
631+ full_response = raw_response
632+ return full_response
633+
541634 def call (self , svc : str , func : str , ret_field : str = None , squelch : ['Squelch' ] = None , ret_error : bool = False ,
542- ** kwargs : Union [dict , bool , str , list ]) -> Union [list , dict , 'CdpError' ]:
635+ redirect_headers : dict = None , ** kwargs : Union [dict , bool , str , list ]) -> Union [list , dict , 'CdpError' ]:
543636 """
544637 Wraps the call to an underlying CDP CLI Service, handles common errors, and parses output
545638
@@ -550,61 +643,34 @@ def call(self, svc: str, func: str, ret_field: str = None, squelch: ['Squelch']
550643 squelch (list(Squelch)): list of Descriptions of Error squelching options
551644 ret_error (bool): Whether to return the error object if generated,
552645 defaults to False and raise instead
553- **kwargs (dict): Keyword Args to be supplied to the Function, eg userId
646+ redirect_headers (dict): Dict of http submission headers for the call, triggers redirected upload call.
647+ **kwargs (dict): Keyword Args to be supplied to the Function, e.g. userId
554648
555649 Returns (dict, list, None): Output of CDP CLI Call
556650 """
557651 try :
558- call_function = getattr (self ._client (service = svc , parameters = kwargs ), func )
559652 if self .scrub_inputs :
560- # Remove unused submission values as the API rejects them
561- payload = {x : y for x , y in kwargs .items () if y is not None }
562- # Remove and issue warning for empty string submission values as the API rejects them
563- _ = [self .throw_warning (
564- CdpWarning ('Removing empty string arg %s from submission' % x ))
565- for x , y in payload .items () if y == '' ]
566- payload = {x : y for x , y in payload .items () if y != '' }
653+ payload = self ._scrub_inputs (inputs = kwargs )
567654 else :
568655 payload = kwargs
569656
570- resp = call_function ( ** payload )
657+ svc_client = self . _client ( service = svc , parameters = payload )
571658
572- if 'nextToken' in resp :
573- while 'nextToken' in resp :
574- logging .debug ("Found paged results in %s" % func )
575- token = resp .pop ('nextToken' )
576- next_page = call_function (
577- ** payload , startingToken = token , pageSize = self .DEFAULT_PAGE_SIZE )
578- for key in next_page .keys ():
579- if isinstance (next_page [key ], str ):
580- resp [key ] = next_page [key ]
581- elif isinstance (next_page [key ], list ):
582- resp [key ] += (next_page [key ])
659+ if redirect_headers is not None :
660+ full_response = self ._handle_redirect_call (svc_client , func , payload , redirect_headers )
661+ else :
662+ full_response = self ._handle_std_call (svc_client , func , payload )
583663
584664 if ret_field is not None :
585- if not resp :
665+ if not full_response :
586666 self .throw_warning (CdpWarning ('Call Response is empty, cannot return child field %s' % ret_field ))
587667 else :
588- return resp [ret_field ]
589-
590- return resp
668+ return full_response [ret_field ]
669+ return full_response
591670
592671 except Exception as err :
593- # Note that the cascade of behaviors here is designed to be convenient for Ansible module development
594- parsed_err = CdpError (err )
595- if self .debug :
596- log = self .get_log ()
597- parsed_err .update (sdk_out = log , sdk_out_lines = log .splitlines ())
598- if self .strict_errors is True :
599- self .throw_error (parsed_err )
600- if isinstance (err , ClientError ):
601- if squelch is not None :
602- for item in squelch :
603- if item .value in str (parsed_err .__getattribute__ (item .field )):
604- warning = item .warning if item .warning is not None else str (parsed_err .violations )
605- self .throw_warning (CdpWarning (warning ))
606- return item .default
607- if ret_error is True :
672+ parsed_err = self ._handle_call_errors (err , squelch )
673+ if ret_error is True or not isinstance (parsed_err , CdpError ):
608674 return parsed_err
609675 self .throw_error (parsed_err )
610676
0 commit comments