o
    wOiD                     @   s   d Z ddlZzddlmZ W n ey   ddlmZ Y nw ddlZddl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mZ ddlZzddlmZ W n ey_   ddlmZ Y nw eeZG dd	 d	eZdS )
zp
.. module:: python-etcd
   :synopsis: A python etcd client.

.. moduleauthor:: Jose Plana <jplana@gmail.com>


    N)HTTPException)	HTTPError)ReadTimeoutErrorwraps)urlparsec                   @   s   e Zd ZdZdZdZdZdZedZ	edZ
edZd	Z	
																					dlddZdd Zdd Zdd Zedd Zedd Zedd  Zed!d" Zed#d$ Zed%d& Zed'd( Zed)d* Zed+d, Zed-d. Zed/d0 Zed1d2 Zed3d4 Zdmd6d7Zed8d9 Z ed:d; Z!ed<d= Z"d>d? Z#d@dA Z$dndBdCZ%dDdE Z&dFdG Z'dHdI Z(dodJdKZ)dodLdMZ*dpdNdOZ+dpdPdQZdRdS Z,dqdTdUZ-dodVdWZ.dXdY Z/edZd[ Z0d\d] Z1dpd^d_Z2d`da Z3e3dodbdcZ4e3dodddeZ5dfdg Z6dhdi Z7djdk Z8d	S )rClientzB
    Client for etcd, the distributed log service using raft.
    GETPUTPOSTDELETE)	prevValue	prevIndex	prevExistrefresh)	recursivewait	waitIndexsortedquorum)r   r   N	127.0.0.1  /v2<   ThttpF
   /_locksc              
      s  |dur%z  |}W n ty$ } ztd|| W Y d}~nd}~ww | _dd t|ts=g  _ j|| _n|sItd t	
d fdd|D  _ jd	 _| _| _| _| _| _| _| _d
|i} jd	kr~ j|d< |rt|tr|d	 |d< |d |d< n||d< |	r|	|d< tj|d< d _d _|
r|r|
 _| _n|
rtd n|rtd tjdddi| _td j  jr jstt jt jB  _ j jv r j  j td j d  _! _"dS )a  
        Initialize the client.

        Args:
            host (mixed):
                           If a string, IP to connect to.
                           If a tuple ((host, port), (host, port), ...)

            port (int):  Port used to connect to etcd.

            srv_domain (str): Domain to search the SRV record for cluster autodiscovery.

            version_prefix (str): Url or version prefix in etcd url (default=/v2).

            read_timeout (int):  max seconds to wait for a read.

            allow_redirect (bool): allow the client to connect to other nodes.

            protocol (str):  Protocol used to connect to etcd.

            cert (mixed):   If a string, the whole ssl client certificate;
                            if a tuple, the cert and key file names.

            ca_cert (str): The ca certificate. If pressent it will enable
                           validation.

            username (str): username for etcd authentication.

            password (str): password for etcd authentication.

            allow_reconnect (bool): allow the client to reconnect to another
                                    etcd server in the cluster in the case the
                                    default one does not respond.

            use_proxies (bool): we are using a list of proxies to which we connect,
                                 and don't want to connect to the original etcd cluster.

            expected_cluster_id (str): If a string, recorded as the expected
                                       UUID of the cluster (rather than
                                       learning it from the first request),
                                       reads will raise EtcdClusterIdChanged
                                       if they receive a response with a
                                       different cluster ID.
            per_host_pool_size (int): specifies maximum number of connections to pool
                                      by host. By default this will use up to 10
                                      connections.
            lock_prefix (str): Set the key prefix at etcd when client to lock object.
                                      By default this will be use /_locks.
        Nz-Could not discover the etcd hosts from %s: %sc                 S   s   d| ||f S )Nz
%s://%s:%d )protocolhostportr   r   ?/home/ubuntu/.local/lib/python3.10/site-packages/etcd/client.pyuri   s   zClient.__init__.<locals>.uriz0List of hosts incompatible with allow_reconnect.zFA list of hosts to connect to was given, but reconnection not allowed?c                    s   g | ]} j g|R  qS r   	_protocol).0connselfr"   r   r!   
<listcomp>   s    z#Client.__init__.<locals>.<listcomp>r   maxsizetimeout	cert_file   key_fileca_certs	cert_reqszHUsername provided without password, both are required for authenticationzHPassword provided without username, both are required for authentication	num_poolsr   zNew etcd client created for %sz Machines cache initialised to %sr   )#	_discover	Exception_logerrorr$   
isinstancetuple_machines_cache	_base_urietcdEtcdExceptionpopexpected_cluster_idversion_prefix_read_timeout_allow_redirect_use_proxies_allow_reconnect_lock_prefixsslCERT_REQUIREDusernamepasswordwarningurllib3PoolManagerr   debugbase_urilistsetmachinesremove_version_cluster_version)r(   r   r    
srv_domainr>   read_timeoutallow_redirectr   certca_certrF   rG   allow_reconnectuse_proxiesr=   per_host_pool_sizelock_prefixekwr   r'   r!   __init__3   sx   F








zClient.__init__c              	   C   sL   t | jj| j| jd |  | j| jdj	
d}|d | _|d | _dS )zF
        Sets the version information provided by the server.
        z/versionheadersr+   redirectutf-8
etcdserveretcdclusterN)jsonloadsr   request_MGETr9   _get_headersrT   rU   datadecoderQ   rR   )r(   version_infor   r   r!   _set_version_info   s   

zClient._set_version_infoc                 C   sd   d |}tj|d}g }|D ]}||jjdd|jf qt	d| t
|s.tdt|S )Nz_etcd._tcp.{}SRVT)omit_final_dotzFound %sz0The SRV record is present but no host were found)formatdnsresolverqueryappendtargetto_textr    r4   rK   len
ValueErrorr7   )r(   domainsrv_nameanswershostsanswerr   r   r!   r2      s   
zClient._discoverc                 C   s4   | j durz| j   W dS  ty   Y dS w dS )zClean up open connectionsN)r   clearReferenceErrorr(   r   r   r!   __del__   s   
zClient.__del__c                 C      | j S )z*URI used by the client to connect to etcd.)r9   r   r   r   r!   rL         zClient.base_uric                 C   s   t | jjdd S )zNode to connect  etcd.:r   )r   r9   netlocsplitr   r   r   r!   r      s   zClient.hostc                 C   s   t t| jjdd S )zPort to connect etcd.r   r-   )intr   r9   r   r   r   r   r   r!   r      s   zClient.portc                 C   r   )zProtocol used to connect etcd.r#   r   r   r   r!   r     r   zClient.protocolc                 C   r   )zMax seconds to wait for a read.)r?   r   r   r   r!   rT     r   zClient.read_timeoutc                 C   r   )z+Allow the client to connect to other nodes.)r@   r   r   r   r!   rU     r   zClient.allow_redirectc                 C   r   )z6Get the key prefix at etcd when client to lock object.)rC   r   r   r   r!   r[     r   zClient.lock_prefixc              
   C   s   z2| j | j d }| jj| j||  | j| jd}dd | |j	
ddD }td| |W S  tttjfyk } z)td| j | j| | jrb| jd	| _ td
| j  | jW  Y d}~S tdd}~ww )z
        Members of the cluster.

        Returns:
            list. str with all the nodes in the cluster.

        >>> print client.machines
        ['http://127.0.0.1:4001', 'http://127.0.0.1:4002']
        z	/machinesr_   c                 S   s   g | ]}|  qS r   )strip)r%   noder   r   r!   r)   /  s    z#Client.machines.<locals>.<listcomp>rb   ,zRetrieved list of machines: %sz,Failed to get list of machines from %s%s: %rr   zRetrying on %sNzVCould not get the list of servers, maybe you provided the wrong host(s) to connect to?)r9   r>   r   rg   rh   ri   rT   rU   _handle_server_responserj   rk   r   r4   rK   r   r   socketr5   r8   r<   inforO   r:   r;   )r(   r"   responserO   r\   r   r   r!   rO     s2   

zClient.machinesc                 C   sb   i | _ z%| | jd | jjd}t|}|d D ]	}|| j |d < q| j W S    t	d)z
        A more structured view of peers in the cluster.

        Note that while we have an internal DS called _members, accessing the public property will call etcd.
        z/membersrb   membersidz@Could not get the members list, maybe the cluster has gone away?)
_membersapi_executer>   rh   rj   rk   re   rf   r:   r;   )r(   rj   resmemberr   r   r!   r   D  s   

zClient.membersc              
   C   s^   zt | | jd | jjd}| j|d d  W S  ty. } zt	
d| d}~ww )z
        Returns:
            dict. the leader of the cluster.

        >>> print client.leader
        {"id":"ce2a822cea30bfca","name":"default","peerURLs":["http://localhost:2380","http://localhost:7001"],"clientURLs":["http://127.0.0.1:4001"]}
        z/stats/selfrb   
leaderInfoleaderzCannot get leader data: %sN)re   rf   r   r>   rh   rj   rk   r   r3   r:   r;   )r(   r   r\   r   r   r!   r   W  s   	zClient.leaderc                 C   s   |   S )zJ
        Returns:
            dict. the stats of the local server
        _statsr   r   r   r!   statsi  s   zClient.statsc                 C   
   |  dS )zD
        Returns:
            dict. the stats of the leader
        r   r   r   r   r   r!   leader_statsq     
zClient.leader_statsc                 C   r   )zE
        Returns:
           dict. the stats of the kv store
        storer   r   r   r   r!   store_statsy  r   zClient.store_statsr(   c              	   C   sJ   |  | jd | | jjd}zt|W S  ttfy$   t	
dw )z. Internal method to access the stats endpointsz/stats/rb   z&Cannot parse json data in the response)r   r>   rh   rj   rk   re   rf   	TypeErrorrx   r:   r;   )r(   whatrj   r   r   r!   r     s   
zClient._statsc                 C      | j s|   | j S )z"
        Version of etcd.
        )rQ   rm   r   r   r   r!   version  s   zClient.versionc                 C   r   )z.
        Version of the etcd cluster.
        )rR   rm   r   r   r   r!   cluster_version  s   zClient.cluster_versionc                 C   s
   | j d S )z$
        REST key endpoint.
        z/keys)r>   r   r   r   r!   key_endpoint  s   
zClient.key_endpointc                 C   s(   z|  | W dS  tjy   Y dS w )zm
        Check if a key is available in the cluster.

        >>> print 'key' in client
        True
        TF)getr:   EtcdKeyNotFoundr(   keyr   r   r!   __contains__  s   
zClient.__contains__c                 C   s   | ds
d|}|S )N/z/{})
startswithrp   r   r   r   r!   _sanitize_key  s   

zClient._sanitize_keyc                 K   s   t d||||| | |}i }|dur||d< |dur!||d< |r.|r*tdd|d< | D ]\}}	|| jv rNt|	tkrJ|	rEdpFd||< q2|	||< q2|rT| j	pV| j
}
d	|v rb|d	 | }n| j| }| j||
|d
}| |S )a9  
        Writes the value for a key, possibly doing atomic Compare-and-Swap

        Args:
            key (str):  Key.

            value (object):  value to set

            ttl (int):  Time in seconds of expiration (optional).

            dir (bool): Set to true if we are writing a directory; default is false.

            append (bool): If true, it will post to append the new value to the dir, creating a sequential key. Defaults to false.

            Other parameters modifying the write method are accepted:


            prevValue (str): compare key to this value, and swap only if corresponding (optional).

            prevIndex (int): modify key only if actual modifiedIndex matches the provided one (optional).

            prevExist (bool): If false, only create key; if true, only update key.

            refresh (bool): since 2.3.0, If true, only update the ttl, prev key must existed(prevExist=True).

        Returns:
            client.EtcdResult

        >>> print client.write('/key', 'newValue', ttl=60, prevExist=False).value
        'newValue'

        z,Writing %s to key %s ttl=%s dir=%s append=%sNvaluettlz&Cannot create a directory with a valuetruedirfalse	_endpointparams)r4   rK   r   r:   r;   items_comparison_conditionstypebool_MPOST_MPUTr   r   _result_from_response)r(   r   r   r   r   rt   kwdargsr   kvmethodpathr   r   r   r!   write  s6   !




zClient.writec                 K   s"   d|d< | j d|d|dd|S )a[  
        (Since 2.3.0) Refresh the ttl of a key without notifying watchers.

        Keys in etcd can be refreshed without notifying watchers,
        this can be achieved by setting the refresh to true when updating a TTL

        You cannot update the value of a key when refreshing it

        @see: https://github.com/coreos/etcd/blob/release-2.3/Documentation/api.md#refreshing-key-ttl

        Args:
            key (str):  Key.

            ttl (int):  Time in seconds of expiration (optional).

            Other parameters modifying the write method are accepted as `EtcdClient.write`.
        Tr   N)r   r   r   r   r   r   )r(   r   r   r   r   r   r!   r     s   zClient.refreshc                 C   sJ   t d|j|j |j|jdd}|js|j|d< | j|j|jfi |S )a  
        Updates the value for a key atomically. Typical usage would be:

        c = etcd.Client()
        o = c.read("/somekey")
        o.value += 1
        c.update(o)

        Args:
            obj (etcd.EtcdResult):  The object that needs updating.

        zUpdating %s to %s.T)r   r   r   r   )r4   rK   r   r   r   r   modifiedIndexr   )r(   objr   r   r   r!   update  s   
zClient.updatec                 K   s   t d|| | |}i }| D ] \}}|| jv r2t|tkr*|r%dp&d||< q|dur2|||< q|dd}| j| j	| | j
||d}| |S )a  
        Returns the value of the key 'key'.

        Args:
            key (str):  Key.

            Recognized kwd args

            recursive (bool): If you should fetch recursively a dir

            wait (bool): If we should wait and return next time the key is changed

            waitIndex (int): The index to fetch results from.

            sorted (bool): Sort the output keys (alphanumerically)

            timeout (int):  max seconds to wait for a read.

        Returns:
            client.EtcdResult (or an array of client.EtcdResult if a
            subtree is queried)

        Raises:
            KeyValue:  If the key doesn't exists.

            urllib3.exceptions.TimeoutError: If timeout is reached.

        >>> print client.get('/key').value
        'value'

        z$Issuing read for key %s with args %sr   r   Nr+   r   r+   )r4   rK   r   r   _read_optionsr   r   r   r   r   rh   r   )r(   r   r   r   r   r   r+   r   r   r   r!   read&  s     


zClient.readc                 K   s   t d|||| | |}i }|dur|rdpd|d< |dur(|r$dp%d|d< | jD ]}||v r7|| ||< q+t d| | j| j| | j|d}| |S )	a  
        Removed a key from etcd.

        Args:

            key (str):  Key.

            recursive (bool): if we want to recursively delete a directory, set
                              it to true

            dir (bool): if we want to delete a directory, set it to true

            prevValue (str): compare key to this value, and swap only if
                             corresponding (optional).

            prevIndex (int): modify key only if actual modifiedIndex matches the
                             provided one (optional).

        Returns:
            client.EtcdResult

        Raises:
            KeyValue:  If the key doesn't exists.

        >>> print client.delete('/key').key
        '/key'

        z-Deleting %s recursive=%s dir=%s extra args=%sNr   r   r   r   zCalculated params = %sr   )r4   rK   r   _del_conditionsr   r   _MDELETEr   )r(   r   r   r   r   kwdsr   r   r   r   r!   deleteX  s$   


zClient.deletec                 K   s   | j d|||d|jS )a  
        Remove specified key from etcd and return the corresponding value.

        Args:

            key (str):  Key.

            recursive (bool): if we want to recursively delete a directory, set
                              it to true

            dir (bool): if we want to delete a directory, set it to true

            prevValue (str): compare key to this value, and swap only if
                             corresponding (optional).

            prevIndex (int): modify key only if actual modifiedIndex matches the
                             provided one (optional).

        Returns:
            client.EtcdResult

        Raises:
            KeyValue:  If the key doesn't exists.

        >>> print client.pop('/key').value
        'value'

        )r   r   r   Nr   )r   
_prev_node)r(   r   r   r   r   r   r   r!   r<     s   z
Client.popc                 C   s   | j ||||dS )a  
        Atomic test & set operation.
        It will check if the value of 'key' is 'prev_value',
        if the the check is correct will change the value for 'key' to 'value'
        if the the check is false an exception will be raised.

        Args:
            key (str):  Key.
            value (object):  value to set
            prev_value (object):  previous value.
            ttl (int):  Time in seconds of expiration (optional).

        Returns:
            client.EtcdResult

        Raises:
            ValueError: When the 'prev_value' is not the current value.

        >>> print client.test_and_set('/key', 'new', 'old', ttl=60).value
        'new'

        )r   r   r   )r(   r   r   
prev_valuer   r   r   r!   test_and_set  s   zClient.test_and_setc                 C   s   | j |||dS )ao  
        Compatibility: sets the value of the key 'key' to the value 'value'

        Args:
            key (str):  Key.
            value (object):  value to set
            ttl (int):  Time in seconds of expiration (optional).

        Returns:
            client.EtcdResult

        Raises:
           etcd.EtcdException: when something weird goes wrong.

        )r   r   )r(   r   r   r   r   r   r!   rN     s   z
Client.setc                 C   s
   |  |S )a  
        Returns the value of the key 'key'.

        Args:
            key (str):  Key.

        Returns:
            client.EtcdResult

        Raises:
            KeyError:  If the key doesn't exists.

        >>> print client.get('/key').value
        'value'

        )r   r   r   r   r!   r     s   
z
Client.getc                 C   s8   t d|| |r| j|d|||dS | j|d||dS )a  
        Blocks until a new event has been received, starting at index 'index'

        Args:
            key (str):  Key.

            index (int): Index to start from.

            timeout (int):  max seconds to wait for a read.

        Returns:
            client.EtcdResult

        Raises:
            KeyValue:  If the key doesn't exist.

            etcd.EtcdWatchTimedOut: If timeout is reached.

        >>> print client.watch('/key').value
        'value'

        z!About to wait on key %s, index %sT)r   r   r+   r   )r   r+   r   )r4   rK   r   )r(   r   indexr+   r   r   r   r!   watch  s   
zClient.watchc                 c   s,    |}	 | j ||d|d}|jd }|V  q)a  
        Generator that will yield changes from a key.
        Note that this method will block forever until an event is generated.

        Args:
            key (str):  Key to subcribe to.
            index (int):  Index from where the changes will be received.

        Yields:
            client.EtcdResult

        >>> for event in client.eternal_watch('/subcription_key'):
        ...     print event.value
        ...
        value1
        value2

        Tr   )r   r+   r   r-   )r   r   )r(   r   r   r   local_indexr   r   r   r!   eternal_watch  s   
zClient.eternal_watchc                 O      t d)Nz*Lock primitives were removed from etcd 2.0NotImplementedError)r(   argskwargsr   r   r!   get_lock  s   zClient.get_lockc                 C   r   )Nz.Election primitives were removed from etcd 2.0r   r   r   r   r!   election!  s   zClient.electionc              
   C   s   |j }z
t|d}W n tttfy# } ztd| d}~ww ztj	di |}|j
dkr5d|_|| |W S  tyO } ztd| d}~ww )z, Creates an EtcdResult from json dictionary rb   z&Server response was not valid JSON: %rN   Tz$Unable to decode server response: %rr   )rj   re   rf   rk   r   rx   UnicodeErrorr:   r;   
EtcdResultstatusnewKeyparse_headersr3   )r(   r   raw_responser   r\   rr   r   r!   r   %  s*   

zClient._result_from_responsec                 C   sT   t d| j z| j }W n ty!   t d tjd|dw t d| |S )zA Selects the next server in the list, refreshes the server list. z7Selection next machine in cache. Available machines: %sz,Machines cache is empty, no machines to try.zNo more machines in the clustercausezSelected new etcd server %s)	r4   rK   r8   r<   
IndexErrorr5   r:   EtcdConnectionFailedr   )r(   r   machr   r   r!   _next_server7  s   
zClient._next_serverc                    s   t  d fdd	}|S )Nc           	   
      s  d}|d u r	| j }|dkrd }|dstd|sd}z | ||||d}| | |j}W ny tttjfy } zOt	|t
rZ|ddkrZt	|trZtd tjd	| |d
td| j| | jrvtd | j|d
| _d}d}ntd tjd| |d
W Y d }~nd }~w tjy } zt|  d }~w   td  |r| js| j| _| j| j |r| |S )NFr   r   zPath does not start with /r   r   r   zWatch timed out.zWatch timed out: %rr   zRequest to server %s failed: %rz1Reconnection allowed, looking for another server.Tz!Reconnection disabled, giving up.z#Connection to etcd failed due to %rz'Unexpected request failure, re-raising.)rT   r   rx   _check_cluster_idrj   r   r   r   r5   r6   dictr   r   r4   rK   r:   EtcdWatchTimedOutr9   rB   r   r   r   EtcdClusterIdChangedrH   	exceptionrA   rO   r8   rP   r   )	r(   r   r   r   r+   r   some_request_failed_r\   payloadr   r!   wrapperF  sj   









8z%Client._wrap_request.<locals>.wrapperNNr   )r   r   r   r   r!   _wrap_requestE  s   EzClient._wrap_requestc              
   C   s   | j | }|| jks|| jkr| jj||||| j|  ddS || jks)|| jkr:| jj	||||d| j|  ddS t
d|)z Executes the query. F)r+   fieldsra   r`   preload_content)r   r+   encode_multipartra   r`   r   zHTTP method {} not supported)r9   rh   r   r   rg   rU   ri   r   r   request_encode_bodyr:   r;   rp   )r(   r   r   r   r+   urlr   r   r!   r     s2   
	
zClient.api_executec              	   C   s@   | j | }t|}|  }d|d< | jj||||| j|ddS )Nzapplication/jsonzContent-TypeF)bodyr+   ra   r`   r   )r9   re   dumpsri   r   urlopenrU   )r(   r   r   r   r+   r   json_payloadr`   r   r   r!   api_execute_json  s   

zClient.api_execute_jsonc                 C   s\   | d}|std d S | jo|| jk}| j}|| _|r,| j  td||d S )Nzx-etcd-cluster-idz*etcd response did not contain a cluster IDz.The UUID of the cluster changed from {} to {}.)		getheaderr4   rH   r=   r   r~   r:   r   rp   )r(   r   
cluster_id
id_changedold_expected_cluster_idr   r   r!   r     s    


zClient._check_cluster_idc              	   C   sh   |j dv r|S |jd}zt|}|j |d< W n ttfy+   dt|d}Y nw tj	
| dS )z Handles the server response )   r   rb   r   zBad response)messager   N)r   rj   rk   re   rf   r   rx   strr:   	EtcdErrorhandle)r(   r   respr   r   r   r!   r     s   


zClient._handle_server_responsec                 C   s.   | j r| jrd| j | jf}tj|dS i S )Nr   )
basic_auth)rF   rG   joinrI   make_headers)r(   credentialsr   r   r!   ri     s   zClient._get_headers)r   r   Nr   r   Tr   NNNNFFNr   r   r   )NFFr   )N)NNN)9__name__
__module____qualname____doc__rh   r   r   r   rN   r   r   r   r   r^   rm   r2   r   propertyrL   r   r    r   rT   rU   r[   rO   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   r   r   r   r   r   ri   r   r   r   r!   r   #   s    
 "	







)





	

	

A
2
0
 




Ir   )r  logginghttp.clientr   ImportErrorhttplibr   rI   urllib3.exceptionsr   r   re   rD   dns.resolverrq   	functoolsr   r:   r   urllib.parse	getLoggerr  r4   objectr   r   r   r   r!   <module>   s.    
