o
    0                     @   s  d Z ddlmZ ddlmZ ddlmZ ddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlmZ ddlmZ ddlmZ dd	lmZ dd
lmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlZddlmZ G dd dejZG dd dejZG dd dejZ ze
j!Z"W n e#y   G dd dejZ!e!Z"G dd dZ$Y nw dd Z%g dZ&dd Z'dd  Z(dPd!d"Z)d#d$ Z*d%d& Z+d'd( Z,d)d* Z-G d+d, d,e.Z/ej0d-d. Z1ej0d/d0 Z2				dQd1d2Z3		3			dRd4d5Z4dPd6d7Z5d3ej6fd8d9Z7d3ej6ej8j6d3fd:d;Z9d<d= Z:		3			dRd>d?Z;		3				3dSd@dAZ<dPdBdCZ=dDdE Z>dFdG Z?dHdI Z@dJdK ZAdLdM ZBdNdO ZCdS )Tz6Functions to help with shelling out to other commands.    )absolute_import)division)unicode_literalsN)
argv_utils)config)
exceptions)log)
properties)named_configs)encoding)parallel)	platforms)mapc                   @      e Zd ZdZdS )OutputStreamProcessingExceptionz>Error class for errors raised during output stream processing.N__name__
__module____qualname____doc__ r   r   @/tmp/google-cloud-sdk/lib/googlecloudsdk/core/execution_utils.pyr   0       r   c                           e Zd ZdZ fddZ  ZS )PermissionErrorz'User does not have execute permissions.c                       t t| dj|d d S )Nz_{err}
Please verify that you have execute permission for all files in your CLOUD SDK bin folder)err)superr   __init__format)selferror	__class__r   r   r   7   s
   
zPermissionError.__init__r   r   r   r   r   __classcell__r   r   r"   r   r   4       r   c                       r   )InvalidCommandErrorz Command entered cannot be found.c                    r   )Nz{cmd}: command not found)cmd)r   r'   r   r   )r    r(   r"   r   r   r   @   s   

zInvalidCommandError.__init__r$   r   r   r"   r   r'   =   r&   r'   c                   @   r   )TimeoutExpiredzDSimulate subprocess.TimeoutExpired on old (<3.3) versions of Python.Nr   r   r   r   r   r)   N   r   r)   c                   @   s*   e Zd ZdZdd Zd	ddZdd ZdS )
SubprocessTimeoutWrapperaQ  Forwarding wrapper for subprocess.Popen, adds timeout arg to wait.

    subprocess.Popen.wait doesn't provide a timeout in versions < 3.3. This
    class wraps subprocess.Popen, adds a backported wait that includes the
    timeout arg, and forwards other calls to the underlying subprocess.Popen.

    Callers generally shouldn't use this class directly: Subprocess will
    return either a subprocess.Popen or SubprocessTimeoutWrapper as
    appropriate based on the available version of subprocesses.

    See
    https://docs.python.org/3/library/subprocess.html#subprocess.Popen.wait.
    c                 C   s
   || _ d S N)proc)r    r,   r   r   r   r   c   s   
z!SubprocessTimeoutWrapper.__init__Nc                 C   sj   |du r	| j  S t }|| }d}| j  }|du r3t |kr%t t| | j  }|du s|S )a$  Busy-wait for wrapped process to have a return code.

      Args:
        timeout: int, Seconds to wait before raising TimeoutExpired.

      Returns:
        int, The subprocess return code.

      Raises:
        TimeoutExpired: if subprocess doesn't finish before the given timeout.
      Ng{Gz?)r,   waittimepollr)   sleep)r    timeoutnowlaterdelayretr   r   r   r-   g   s   



zSubprocessTimeoutWrapper.waitc                 C   s   t | j|S r+   )getattrr,   )r    namer   r   r   __getattr__   s   z$SubprocessTimeoutWrapper.__getattr__r+   )r   r   r   r   r   r-   r8   r   r   r   r   r*   T   s
    
r*   c                  C   s,   t tjd} | r| S tj}|std|S )z<Gets the path to the Python interpreter that should be used.CLOUDSDK_PYTHONz!Could not find Python executable.)r   GetEncodedValueosenvironsys
executable
ValueError)cloudsdk_python
python_binr   r   r   GetPythonExecutable   s   rB   )ashbashbusyboxdashkshmkshpdkshshc                  C   s\   ddg} t tjd}|rtj|tv r| d| | D ]}tj|r)|  S qt	d)a  Gets the path to the Shell that should be used.

  First tries the current environment $SHELL, if set, then `bash` and `sh`. The
  first of these that is found is used.

  The shell must be Borne-compatible, as the commands that we execute with it
  are often bash/sh scripts.

  Returns:
    str, the path to the shell

  Raises:
    ValueError: if no Borne compatible shell is found
  z	/bin/bashz/bin/shSHELLr   zmYou must set your 'SHELL' environment variable to a valid Borne-compatible shell executable to use this tool.)
r   r:   r;   r<   pathbasename_BORNE_COMPATIBLE_SHELLSinsertisfiler?   )shells
user_shellshellr   r   r   _GetShellExecutable   s   rS   c                 G   s<   g }| r	| |  |r|| | | |t| |S r+   )appendextendlist)interpreterinterpreter_argsexecutable_pathargs	tool_argsr   r   r   _GetToolArgs   s   


r\   c              
   C   sx   | du r	t tj} t| } t| dd tjD ]}|D ]}t| | |j	ddd qqt| t
jtj j | S )a  Generate the environment that should be used for the subprocess.

  Args:
    env: {str, str}, An existing environment to augment.  If None, the current
      environment will be cloned and used as the base for the subprocess.

  Returns:
    The modified env.
  NCLOUDSDK_WRAPPER1F)requiredvalidate)dictr;   r<   r   	EncodeEnvSetEncodedValuer	   VALUESEnvironmentNameGetr   CLOUDSDK_ACTIVE_CONFIG_NAMEr
   ConfigurationStoreActiveConfigr7   )envspr   r   r   
GetToolEnv   s   




rm   c                 O   sf   t |t dg }|rtdd||dpt }ttj	dd}|
 }t||| g|R  S )a  Constructs an argument list for calling the Python interpreter.

  Args:
    executable_path: str, The full path to the Python main file.
    *args: args for the command
    **kwargs: python: str, path to Python executable to use (defaults to
      automatically detected)

  Returns:
    An argument list to execute the Python interpreter

  Raises:
    TypeError: if an unexpected keyword argument is passed
  pythonz<ArgsForPythonTool() got unexpected keyword arguments '[{0}]'z, CLOUDSDK_PYTHON_ARGS )set	TypeErrorr   joingetrB   r   r:   r;   r<   splitr\   )rY   rZ   kwargsunexpected_argumentspython_executablepython_args_strpython_argsr   r   r   ArgsForPythonTool   s   r{   c                 G   s   t ddg| g|R  S )zConstructs an argument list for calling the cmd interpreter.

  Args:
    executable_path: str, The full path to the cmd script.
    *args: args for the command

  Returns:
    An argument list to execute the cmd interpreter
  r(   z/cr\   rY   rZ   r   r   r   ArgsForCMDTool  s   
r~   c                 G   s   t dd| g|R  S )a  Constructs an argument list for an executable.

   Can be used for calling a native binary or shell executable.

  Args:
    executable_path: str, The full path to the binary.
    *args: args for the command

  Returns:
    An argument list to execute the native binary
  Nr|   r}   r   r   r   ArgsForExecutableTool  s   r   c                   C   s&   t jstddt d S tt S )z*Constructs an argument list to run gcloud.Nr   )r=   r>   r\   r   GetDecodedArgvr{   r   
GcloudPathr   r   r   r   ArgsForGcloud$  s   r   c                   @   s    e Zd ZdZdd Zdd ZdS )_ProcessHolderz@Process holder that can handle signals raised during processing.c                 C   s   d | _ d | _d S r+   )processsignum)r    r   r   r   r   0  s   
z_ProcessHolder.__init__c                 C   sH   || _ | jr tdj|| jjd | j du r"| j  dS dS dS )zHandle the intercepted signal.z!Subprocess [{pid}] got [{signum}])r   pidN)r   r   r   debugr   r   r/   	terminate)r    r   unused_framer   r   r   Handler4  s   z_ProcessHolder.HandlerN)r   r   r   r   r   r   r   r   r   r   r   -  s    r   c               	   k   sT    t tj}tj|  zdV  W tj  tj| dS tj  tj| w )z.Temporarily set process environment variables.N)ra   r;   r<   updateclear)env_varsold_environr   r   r   
ReplaceEnvH  s   


r   c              
   c   s6    t  | |}zd V  W t  | | d S t  | | w r+   )signal)signohandlerold_handlerr   r   r   _ReplaceSignalT  s
   r   c              
   K   s$  |rt j|d< |rt j|d< |rt j|d< z| r$t| tr$dd | D } t j| fdt|di|}W n$ tyW } z|jtjkrFt	|j
|jtjkrRt| d  d	}~ww ||_|jd	urj| d	u rj|  t|tjru|d
}tttj|j|d\}	}
|r||	 |r||
 |jS )zSee Exec docstring.stdoutstderrstdinc                 S      g | ]}t |qS r   r   Encode.0ar   r   r   
<listcomp>q      z_Exec.<locals>.<listcomp>rj   rj   r   Nutf-8)input)
subprocessPIPE
isinstancerV   Popenrm   OSErrorerrnoEACCESr   strerrorENOENTr'   r   r   r/   r   six	text_typeencoder   r   Decodecommunicate
returncode)rZ   process_holderrj   out_funcerr_funcin_strextra_popen_kwargsrl   r   r   r   r   r   r   _Exec]  s<   


 


r   Fc           	   	   K   s   t d|  t }tt tjrMttj	|j
, ttj|j
 t| |||||fi |}W d   n1 s8w   Y  W d   n1 sGw   Y  nt| |||||fi |}|rc|jdu rc|S t| dS )a  Emulates the os.exec* set of commands, but uses subprocess.

  This executes the given command, waits for it to finish, and then exits this
  process with the exit code of the child process.

  Args:
    args: [str], The arguments to execute.  The first argument is the command.
    env: {str: str}, An optional environment for the child process.
    no_exit: bool, True to just return the exit code of the child instead of
      exiting.
    out_func: str->None, a function to call with the stdout of the executed
      process. This can be e.g. log.file_only_logger.debug or log.out.write.
    err_func: str->None, a function to call with the stderr of the executed
      process. This can be e.g. log.file_only_logger.debug or log.err.write.
    in_str: bytes or str, input to send to the subprocess' stdin.
    **extra_popen_kwargs: Any additional kwargs will be passed through directly
      to subprocess.Popen

  Returns:
    int, The exit code of the child if no_exit is True, else this method does
    not return.

  Raises:
    PermissionError: if user does not have execute permission for cloud sdk bin
    files.
    InvalidCommandError: if the command entered cannot be found.
  Executing command: %sN)r   r   r   r   	threadingcurrent_thread_MainThreadr   r   SIGTERMr   SIGINTr   r   r=   exit)	rZ   rj   no_exitr   r   r   r   r   ret_valr   r   r   Exec  s$   "r   c              
   K   s   z| rt | trdd | D } tj| fdt|di|}W n$ tyB } z|jtjkr1t|j	|jtj
kr=t| d  d}~ww t }||_|jdurX| du rX|  zt|W S  tyh   | Y S w )a  Run subprocess.Popen with optional timeout and custom env.

  Returns a running subprocess. Depending on the available version of the
  subprocess library, this will return either a subprocess.Popen or a
  SubprocessTimeoutWrapper (which forwards calls to a subprocess.Popen).
  Callers should catch TIMEOUT_EXPIRED_ERR instead of
  subprocess.TimeoutExpired to be compatible with both classes.

  Args:
    args: [str], The arguments to execute.  The first argument is the command.
    env: {str: str}, An optional environment for the child process.
    **extra_popen_kwargs: Any additional kwargs will be passed through directly
      to subprocess.Popen

  Returns:
    subprocess.Popen or SubprocessTimeoutWrapper, The running subprocess.

  Raises:
    PermissionError: if user does not have execute permission for cloud sdk bin
    files.
    InvalidCommandError: if the command entered cannot be found.
  c                 S   r   r   r   r   r   r   r   r     r   zSubprocess.<locals>.<listcomp>rj   r   r   N)r   rV   r   r   rm   r   r   r   r   r   r   r'   r   r   r   r/   r   r*   	NameError)rZ   rj   r   rl   r   r   r   r   r   
Subprocess  s,    


r   c                 C   sx   |r| j n| j}|j}	 | pd}|s+|  dur+z|  W dS  ty*   Y dS w |d}|d}|r;|| q)z<Process output stream from a running subprocess in realtime.T    Nr   
)r   r   readliner/   closer   decoderstrip)r,   r   r   streamstream_readerlineline_strr   r   r   _ProcessStreamHandler  s"   



r   c                 C   s   g }g }t ddY 	 | j pd}| j pd}|s$|s$|  dur$n7|r?|r-|| |d}	|s9|	dn|	}	||	 |rZ|rH|| |d}
|sT|
dn|
}
||
 qW d   n1 sew   Y  | j||fS )z6Log stdout and stderr output from running sub-process.r^   )PYTHONUNBUFFEREDTr   Nr   r   )	r   r   r   r   r/   rT   r   r   r   )r,   rawstdout_handlerstderr_handlercapturer   r   out_lineerr_lineout_strerr_strr   r   r   _StreamSubprocessOutput  s0   



r   c                 C   s   | r[d}t | dr| j}nt | dr| j}|du s|  du r#|   z-| jr0| jjs0| j  | jr<| jjs<| j  | j	rK| j	jsN| j	  W dS W dS W dS  t
yZ   Y dS w dS )z$Kill process and close open streams.Nr   exitcode)hasattrr   r   r/   r   r   closedr   r   r   r   )r,   coder   r   r   _KillProcIfRunning)  s(   



r   c                 K   sx  t d|  t|d}t }ttj|j ttj|j |p#t j	}|p)t j
j	}	|r1tj|d< z| r@t| tr@dd | D } tj| f|tjtjd|}
|
|_|rt|d}z|
j| |
j  W n' ty } z|jtjks}|jtjkr~nt|
 t|W Y d}~nd}~ww z1td	"}|t |
d
|f}|t |
d|	f}|!  |!  W d   n1 sw   Y  W n t"y } zt|
 t|d}~ww W n$ ty } z|jtj#krt$|j%|jtj&krt'| d  d}~ww |j(durt|
 |
j)}W d   n	1 sw   Y  W d   n	1 s%w   Y  |r5|j(du r5|S t*+| dS )a  Emulates the os.exec* set of commands, but uses subprocess.

  This executes the given command, waits for it to finish, and then exits this
  process with the exit code of the child process. Allows realtime processing of
  stderr and stdout from subprocess using threads.

  Args:
    args: [str], The arguments to execute.  The first argument is the command.
    env: {str: str}, An optional environment for the child process.
    no_exit: bool, True to just return the exit code of the child instead of
      exiting.
    out_func: str->None, a function to call with each line of the stdout of the
      executed process. This can be e.g. log.file_only_logger.debug or
      log.out.write.
    err_func: str->None, a function to call with each line of the stderr of
      the executed process. This can be e.g. log.file_only_logger.debug or
      log.err.write.
    in_str: bytes or str, input to send to the subprocess' stdin.
    **extra_popen_kwargs: Any additional kwargs will be passed through directly
      to subprocess.Popen

  Returns:
    int, The exit code of the child if no_exit is True, else this method does
    not return.

  Raises:
    PermissionError: if user does not have execute permission for cloud sdk bin
    files.
    InvalidCommandError: if the command entered cannot be found.
  r   r   r   c                 S   r   r   r   r   r   r   r   r   w  r   z+ExecWithStreamingOutput.<locals>.<listcomp>rj   r   r   r   N   FTr   ),r   r   rm   r   r   r   r   r   r   Printstatusr   r   r   rV   r   r   r   r   r   r   writer   r   r   EPIPEEINVALr   r   r   GetPool
ApplyAsyncr   rf   	Exceptionr   r   r   r   r'   r   r   r=   r   )rZ   rj   r   r   r   r   r   r   out_handler_funcerr_handler_funcrl   excpoolstd_out_futurestd_err_futureer   r   r   r   r   ExecWithStreamingOutput>  s   %




8r   c                 K   s"  t d|  t|d}t }ttj|j ttj|j |p"t j	}	|p(t j
j	}
|r0tj|d< z}| r?t| tr?dd | D } tj| f|tjtjd|}|rt|d}z|j| |j  W n' ty } z|jtjksy|jtjkrznt| t|W Y d}~nd}~ww z
t||	|
|d	 W n ty } zt| t|d}~ww W n$ ty } z|jtjkrt|j |jtj!krt"| d
  d}~ww ||_#|j$durt| |j%}W d   n1 sw   Y  W d   n1 sw   Y  |r
|j$du r
|S t&'| dS )a|  Emulates the os.exec* set of commands, but uses subprocess.

  This executes the given command, waits for it to finish, and then exits this
  process with the exit code of the child process. Allows realtime processing of
  stderr and stdout from subprocess without threads.

  Args:
    args: [str], The arguments to execute.  The first argument is the command.
    env: {str: str}, An optional environment for the child process.
    no_exit: bool, True to just return the exit code of the child instead of
      exiting.
    out_func: str->None, a function to call with each line of the stdout of the
      executed process. This can be e.g. log.file_only_logger.debug or
      log.out.write.
    err_func: str->None, a function to call with each line of the stderr of
      the executed process. This can be e.g. log.file_only_logger.debug or
      log.err.write.
    in_str: bytes or str, input to send to the subprocess' stdin.
    raw_output: bool, stream raw lines of output perserving line
      endings/formatting.
    **extra_popen_kwargs: Any additional kwargs will be passed through directly
      to subprocess.Popen

  Returns:
    int, The exit code of the child if no_exit is True, else this method does
    not return.

  Raises:
    PermissionError: if user does not have execute permission for cloud sdk bin
    files.
    InvalidCommandError: if the command entered cannot be found.
  r   r   r   c                 S   r   r   r   r   r   r   r   r     r   z6ExecWithStreamingOutputNonThreaded.<locals>.<listcomp>r   r   N)r   r   r   r   )(r   r   rm   r   r   r   r   r   r   r   r   r   r   r   rV   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r'   r   r   r   r=   r   )rZ   rj   r   r   r   r   
raw_outputr   r   r   r   rl   r   r   r   r   r   r   r   "ExecWithStreamingOutputNonThreaded  sx   (





4r   c                    s&   dj  pdd  fdd}t|S )ar  Run a section of code with CTRL-C disabled.

  When in this context manager, the ctrl-c signal is caught and a message is
  printed saying that the action cannot be cancelled.

  Args:
    stream: the stream to write to if SIGINT is received
    message: str, optional: the message to write

  Returns:
    Context manager that is uninterruptible during its lifetime.
  z

{message}

z#This operation cannot be cancelled.)messagec                    s      d S r+   )r   )unused_signalr   r   r   r   r   _Handler   s   z(UninterruptibleSection.<locals>._Handler)r   CtrlCSection)r   r   r   r   r   r   UninterruptibleSection  s
   r   c                  C   s   dd } t | S )z<Run a section of code where CTRL-C raises KeyboardInterrupt.c                 S   s   ~ ~t r+   )KeyboardInterrupt)r   framer   r   r   r   '  s   z)RaisesKeyboardInterrupt.<locals>._Handler)r   )r   r   r   r   RaisesKeyboardInterrupt%  s   r   c                 C   s   t tj| S )a  Run a section of code with CTRL-C redirected handler.

  Args:
    handler: func(), handler to call if SIGINT is received. In every case
      original Ctrl-C handler is not invoked.

  Returns:
    Context manager that redirects ctrl-c handler during its lifetime.
  )r   r   r   )r   r   r   r   r   -  s   
r   c                 C   s  d}t | dr| j}nt | dr| j}|durdS tj tjjkrRtjddddt	
| jgtjtjd}| \}}|jd	krNt|rPtd
| j||dS dS tttj}d|d< tjg dtjtj|d}| \}}|d}|jd	krtd| ji }| dD ]&}td| \}	}
t|	}	t|
}
||	}|s|
g||	< q||
 q| jg}| jg}|r| }||}|r| | | | |s|D ]}
t!|
 qdS )a  Kills a subprocess using an OS specific method when python can't do it.

  This also kills all processes rooted in this process.

  Args:
    p: the Popen or multiprocessing.Process object to kill

  Raises:
    RuntimeError: if it fails to kill the process
  Nr   r   taskkillz/Fz/Tz/PID)r   r   r   z:Failed to call taskkill on pid {0}
stdout: {1}
stderr: {2}zen_US.UTF-8LANG)psz-e-ozppid=r   zpid=)r   r   rj   r   z*Failed to get subprocesses of process: {0}
z\s*(\d+)\s+(\d+))"r   r   r   r   OperatingSystemCurrentWINDOWSr   r   r   r   r   r   r   _IsTaskKillErrorRuntimeErrorr   r   rb   ra   r;   r<   r   stripru   rematchgroupsintrt   rT   poprU   _KillPID)rl   r   taskkill_processr   r   new_envget_pids_processpid_mapr   ppidr   childrenall_pids
to_processcurrentr   r   r   KillSubprocess:  sl   









r  c                 C   sF   d}t df}|D ]	}|| v r dS q
|D ]
}|| r  dS qdS )zReturns whether the stderr output of taskkill indicates it failed.

  Args:
    stderr: the string error output of the taskkill command

  Returns:
    True iff the stderr is considered to represent an actual error.
  )zAccess is denied.z)The operation attempted is not supported.z)There is no running instance of the task.z6There is no running instance of the task to terminate.zThe process "\d+" not found\.FT)r  compilesearch)r   non_error_reasonsnon_error_patternsreasonpatternr   r   r   r    s   
r  c              
   C   s   z/t | tj t d }t |k r&t| sW dS td t |k st | tj W dS  tyW } zd|j	vrLt
t d  W Y d}~dS W Y d}~dS d}~ww )zKills the given process with SIGTERM, then with SIGKILL if it doesn't stop.

  Args:
    pid: The process id of the process to check.
     Ng?zNo such process   )r;   killr   r   r.   _IsStillRunningr0   SIGKILLr   r   r   reraiser=   exc_info)r   deadliner!   r   r   r   r    s   

 r  c              
   C   s|   zt | t j\}}||fdkrW dS W dS  ty= } zd|jvr2tt d  W Y d}~dS W Y d}~dS d}~ww )zDetermines if the given pid is still running.

  Args:
    pid: The process id of the process to check.

  Returns:
    bool, True if it is still running.
  )r   r   TzNo child processesr  NF)	r;   waitpidWNOHANGr   r   r   r!  r=   r"  )r   
actual_pidr   r!   r   r   r   r    s   	

r  r+   )NNNN)NFNNN)NFNNNF)Dr   
__future__r   r   r   
contextlibr   r;   r  r   r   r=   r   r.   googlecloudsdk.corer   r   r   r   r	   "googlecloudsdk.core.configurationsr
   googlecloudsdk.core.utilr   r   r   r   	six.movesr   Errorr   r   r'   r)   TIMEOUT_EXPIRED_ERRAttributeErrorr*   rB   rM   rS   r\   rm   r{   r~   r   r   objectr   contextmanagerr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r   r   r   r   <module>   s   	
:
!	



1

=.

k

iV