• paramiko模块


    paramiko在远程执行python脚本时,脚本中的输出内容可能会通过stderr这个管道输出出来,所以直接用paramiko的SSHClient类中的exec_command方法执行,通过读stderr管道中有无输出来判断命令是否成功执行的方式是行不通的。所以用更底层一些的Channel类的recv_exit_status方法判断执行退出码更好一些。

    我们先来一个示例,简单认识一下paramiko模块的使用

    # 用户名和密码方式
    # import paramiko
    # ssh = paramiko.SSHClient()
    # ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # ssh.connect(hostname='192.168.16.72', port=22, username='root', password='redhat')
    # stdin, stdout, stderr = ssh.exec_command('ifconfig')
    # result = stdout.read()
    # ssh.close()
    # print(result)
    
    # 公钥私钥方式
    # import paramiko
    # private_key = paramiko.RSAKey.from_private_key_file('/home/auto/.ssh/id_rsa')
    # ssh = paramiko.SSHClient()
    # ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # ssh.connect(hostname='c1.salt.com', port=22, username='wupeiqi', pkey=private_key)
    # stdin, stdout, stderr = ssh.exec_command('df')
    # result = stdout.read()
    # ssh.close()

     在举一个示例,较深入认识一下paramiko模块的使用

    首先定义几个异常:

    # coding: utf-8
    import os.path
    
    from paramiko import SSHClient, AutoAddPolicy, AuthenticationException
    
    
    class ConnectError(Exception):
        """
        连接错误时抛出的异常
        """
        pass
    
    class RemoteExecError(Exception):
        """
        远程执行命令,失败时抛出的异常
        """
        pass
    
    class SCPError(Exception):
        """
        远程下发文件时抛出的异常
        """
        pass
    
    class Remote(object):
        def __init__(self, host, username, password=None, port=22, key_filename=None):
            self.host = host
            self.username = username
            self.password = password
            self.port = port
            self.key_filename = key_filename
            self._ssh = None
    
        def _connect(self):
            self._ssh = SSHClient()
            self._ssh.set_missing_host_key_policy(AutoAddPolicy())
            try:
                if self.key_filename:
                    self._ssh.connect(self.host, username=self.username, port=self.port, key_filename=self.key_filename)
                else:
                    self._ssh.connect(self.host, username=self.username, password=self.password, port=self.port)
            except AuthenticationException: 
                self._ssh = None
                raise ConnectionError('连接失败,请确认用户名、密码、端口或密钥文件是否有效')
            except Exception as e:
                self._ssh = None
                raise ConnectionError('连接时出现意料外的错误:%s' % e)
    
        def get_ssh(self):
            if not self._ssh:
                self._connect()
            return self._ssh
    

    实例化SSHClient类,通过它的connect()方法获取SSH连接。

    需要注意的是,远程访问的主机若是第一次连接,属于未知设备需要认证,通过set_missing_host_key_policy()方法设置一种策略,这里使用的是AutoAddPolicy()

    这里的_connect支持两种方式登录,一种是提供主机的用户名密码,另一种是通过密钥文件。在连接时检查如果指定了密钥文件则使用这种方式登录,否则通过用户名密码登录。

    _connect()虽然是实际的建立连接的方法,但实际对外接口是get_ssh(),如果已经有建立好的SSH连接直接返回,避免重复建立连接。

    class Remote(object):
        ...
    
        
        def ssh(self, cmd, root_password=None, get_pty=False, super=False):
            cmd = self._prepare_cmd(cmd, root_password, super)
            stdout = self._exec(cmd, get_pty)
            return stdout
    
        def _prepare_cmd(self, cmd, root_password=None, super=False):
            if self.username != 'root' and super:
                if root_password:
                    cmd = "echo '{}'|su - root -c '{}'".format(root_password, cmd)
                else:
                    cmd = "echo '{}'|sudo -p '' -S su - root -c '{}'".format(self.password, cmd)
            return cmd
    
        def _exec(self, cmd, gty_pty=False):
            channel = self.get_ssh().get_transport().open_session()
            if get_pty:
                channel.get_pty()
            channel.exec_command(cmd)
            stdout = channel.makefile('r', -1).readlines()
            stderr = channel.makefile_stderr('r', -1).readlines()
            ret_code = channel.recv_exit_status()
            if ret_code:
                msg = ''.join(stderr) if stderr else ''.join(stdout)
                raise RemoteExecError(msg)
            return stdout
    

    在远程执行某些命令时,可能需要管理员权限,这种时候需要做一些判断,首先判断登录提供的用户名如果不是root,则需要对命令做一些修改。这里的修改有两种情况,一是,该普通用户本身就有sudo权限,只需要把执行的命令加到sudo之后执行就可以,还有一种是普通用户没有sudo权限,需要通过su先切换到root身份之后再执行,这种情况下需要提供root密码。

    还有一点要注意的是get_pty这个参数,实际在远程执行sudo命令时,一般主机都会需要通过tty才能执行,通过把get_pty值设置为True,可以模拟tty,但是随之而来也会有一个问题,如果是远程执行一个需要长期运行的进程,例如启动nginx服务,当远程命令执行后SSH退出之后,此次运行的所有程序也会随之结束,所以在需要通过远程命令运行某些服务或程序时,是不能指定get_pty参数的;但同时,如果是普通用户远程登录,是没有权限执行service命令的。建议的一种方式是修改/etc/sudoers配置文件,注释掉Defaults requiretty这行。

    class Remote(object):
        ...
    
        def scp(self, local_file, remote_path):
            if not os.path.exists(local_file):
                raise SCPError("Local %s isn't exists" % local_file)
            if not os.path.isfile(local_file):
                raise SCPError("%s is not a File" % local_file)
            sftp = self.get_ssh().open_sftp()
            try:
                sftp.put(local_file, remote_path)
            except Exception as e:
                raise SCPError(e)
    

      使用

    # coding: utf-8
    from remote_client import RemoteClient
    
    rc = RemoteClient('10.1.100.1', 'test', 'test_pass')
    rc.ssh('whoami')   # [u'test
    ']
    rc.scp('/tmp/test.out', '/tmp/test.out')
    

      

  • 相关阅读:
    实体类字段格式校验
    .Net Core之自定义中间件
    创建型之【单例模式】
    Linux下安装Apollo (Quick Start)
    Linux下安装MySQL你又踩过多少坑【宇宙最全教程】
    C#之Expression表达式目录树
    创建型之【建造者模式】
    [LeetCode] 1675. Minimize Deviation in Array
    [LeetCode] 1996. The Number of Weak Characters in the Game
    [LeetCode] 1523. Count Odd Numbers in an Interval Range
  • 原文地址:https://www.cnblogs.com/c491873412/p/7615956.html
Copyright © 2020-2023  润新知