Python

Paramiko + scp 導入 ~ SSH接続 ~ SCPでファイル転送

More than 1 year has passed since last update.


概要


  1. ssh接続からのコマンド送受信

  2. scpでのファイル取得

補足.

(A) PC側 paramiko + scp 導入

(B) パッケージ依存関係の調査

(C) インストール済みのパッケージのバージョンの調査

(D) パッケージ依存関係の可視化


やりたいこと


  1. ssh接続からのコマンド送受信

    PC(SSH Client) ---sshでコマンド実行(mkdir, tar, ...)--> ターゲット(SSH Host)


  2. scpでのファイル取得

    PC(SSH Client) <---scpでファイルを取得(xxx.tar.gz, ...)---------- ターゲット(SSH Host)


PC(SSH Client)

Windows 7 (64bit)

Python 3.5.2 - Anaconda 4.2.0 (64-bit)

ターゲット(SSH Host)

IPアドレスだけがわかっている(host名不明))

sshでrootログインできる

scpできる

(・・・という処理と調査に参照したサイトを備忘録としてまとめた)


コード


1. ssh接続からのコマンド送受信

192.168.1.100に対してrootでログインして、testというディレクトリを作成したい場合


ssh_mkdir1.py

import paramiko

with paramiko.SSHClient() as ssh:
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname='192.168.1.100', port=22, username='root', password='')
stdin, stdout, stderr = ssh.exec_command('mkdir test')

短くかけていい感じ

なお、ssh.connect()でpassword か private key (もしくは両方)が必要とのこと。

SSH - Python with paramiko issue - Stack Overflow


You should provide either a password or a private key (or both), otherwise the SSH client does not know how to authenticate with the login data.


コメント + α をつけると以下のような感じです。


ssh_mkdir.py

import paramiko

# ssh clientオブジェクト(ssh)を作る・・・
with paramiko.SSHClient() as ssh:

# どうやってhostname & keyを登録するのかわからないので、AutoAddPolicy()としておく
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# ssh接続する
ssh.connect(hostname='192.168.1.100', port=22, username='root', password='')

# mkdir を実行する
stdin, stdout, stderr = ssh.exec_command('mkdir test')

# 実行結果のstdoutとstderrを読み出す
for o in stdout:
print(o)
for e in stderr:
print(e)



2. scpでのファイル取得


xxx.tar.gzを取得したい場合


scp.py

import paramiko

import scp

with paramiko.SSHClient() as ssh:
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname='192.168.1.100', port=22, username='root', password='')

# scp clientオブジェクト生成
with scp.SCPClient(ssh.get_transport()) as scp:
# scpに対してgetするだけでファイルが取得できる
scp.get('xxx.tar.gz','xxx.tar.gz')



ターゲットにtest.py(10個のファイルを作成するスクリプト)をscpでコピーして実行する場合


test.py

# -*- coding: utf-8 -*-

for n in range(10):
with open("test" + str(n), "w+") as f:
f.write("test" + str(n))


scp2.py

# -*- coding: utf-8 -*-

import paramiko
import scp

with paramiko.SSHClient() as ssh:
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname='192.168.1.100', port=22, username='root', password='')

stdin, stdout, stderr = ssh.exec_command('mkdir test_dir')
for o in stdout:
print(o.strip())

with scp.SCPClient(ssh.get_transport()) as scp:
scp.put('test.py','test_dir/test.py')

stdin, stdout, stderr = ssh.exec_command('cd test_dir;python test.py')
for o in stdout:
print(o.strip())

stdin, stdout, stderr = ssh.exec_command('cd test_dir;ls')
for o in stdout:
print(o.strip())


for o in stdoutなどで処理待ちを入れないと、lsやpython test.pyの部分で同期が取れないようだ。


  • フォルダ作成前にscp.put()が実行される

  • sh.exec_command('cd test_dir;ls')のタイミングで、python test.pyが未完了であり、期待したフォルダが表示されない。

といったことがあった


最終的な形

これをマージして以下のようにした。


get_logs.py

# -*- coding: utf-8 -*-

import paramiko
import scp
import pathlib
from datetime import datetime
import sys

log_dir = str(datetime.now().strftime('log_%Y%m%d_%H%M%S'))
log_tar_gz = log_dir + '.tar.gz'

remote_path = log_tar_gz
local_path = str(pathlib.Path(__file__).resolve().parent.joinpath(log_tar_gz))

collect_log_cmds = [
'mkdir ' + log_dir,
# To Do : log_dirにログを集める処理を何かしら書く
'tar -zcvf ' + log_tar_gz + ' ' + log_dir
]

cleanup_cmds = [
'rm -r ' + log_dir,
'rm ' + log_tar_gz
]

def check_result(stdin, stdout, stderr):
result = True
for o in stdout:
if o:
print(" " + o.strip())
for e in stderr:
if e:
print(" " + e.strip())
result = False
return result

def exec_command(ssh, cmds):
result = True
for cmd in cmds:
print(" " + cmd)
stdin, stdout, stderr = ssh.exec_command(cmd)
if check_result(stdin, stdout, stderr):
pass
else:
print("ERROR -- " + cmd)
result = False
return result

with paramiko.SSHClient() as ssh:
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname='192.168.1.100', port=22, username='root', password='')

print("start -- collect_log")
if exec_command(ssh, collect_log_cmds):
print("done -- collect_log)
else:
sys.exit()

print("start -- scp get " + remote_path)
with scp.SCPClient(ssh.get_transport()) as scp:
scp.get(remote_path,local_path)

if pathlib.Path(local_path).is_file():
print("done -- scp get " + local_path)
else:
print("ERROR -- scp get " + local_path)
sys.exit()

print("start -- cleanup")
if exec_command(ssh, cleanup_cmds):
print("done -- cleanup")
else:
print("ERROR -- cleanup")
sys.exit()



補足


(A) PC側 paramiko + scp 導入


paramiko 2.2.1 パッケージ依存関係

paramiko.png


  • 図の赤丸がインストールされていなかったパッケージ

  • 調査方法は「補足(B)~(D)」を参照


パッケージ取得



  • pypiにブラウザでアクセスして.whlをダウンロードした

required
used
.whl

bcrypt(>=3.1.3) 
bcrypt 3.1.3
bcrypt-3.1.3-cp35-cp35m-win_amd64.whl

pynacl(>=1.0.1)
pynacl 1.1.2
PyNaCl-1.1.2-cp35-cp35m-win_amd64.whl

paramiko(2.2.1) 
paramiko 2.2.1
paramiko-2.2.1-py2.py3-none-any.whl

scp(0.10.2) 
scp 0.10.2
scp-0.10.2-py2.py3-none-any.whl


  • bcryptの.whlはどれなのかわからなかったので、環境に合わないものをいれたときのエラーでOK/NGを判断した(トライアンドエラーで解決した。)


パッケージインストール


  • pip install <ダウンロードした.whl>で実施

> pip install bcrypt-3.1.3-cp35-cp35m-win_amd64.whl

・・・
> pip install PyNaCl-1.1.2-cp35-cp35m-win_amd64.whl
・・・
> pip install paramiko-2.2.1-py2.py3-none-any.whl
・・・・
> pip install scp-0.10.2-py2.py3-none-any.whl
・・・・


(B) パッケージ依存関係の調査


  • pip installを実行してごりごり調べた

  • 「Collecting bcrypt>=3.1.3 (from paramiko==2.2.1)」といったメッセージで出てくる

> pip install paramiko-2.2.1-py2.py3-none-any.whl

Collecting bcrypt>=3.1.3 (from paramiko==2.2.1)
Retrying (Retry(total=4, connect=None, read=None, redirect=None)) after connection broken by 'NewConnectionError('<pip._vendor.requests.packages.urllib3.connection.VerifiedHTTPSConnection object at 0x02DB4A30>: Failed to establish a new connection: [Errno 11004] getaddrinfo failed',)': /simple/bcrypt/
・・・


  • paramikoのインストールが可能になると、以下のようになる

> pip install paramiko-2.2.1-py2.py3-none-any.whl

Requirement already satisfied: bcrypt>=3.1.3 in ・・・\lib\site-packages (from paramiko==2.2.1)
Requirement already satisfied: cryptography>=1.1 in ・・・\lib\site-packages (from paramiko==2.2.1)
Requirement already satisfied: six in ・・・\lib\site-packages (from pynacl>=1.0.1->paramiko==2.2.1)
Requirement already satisfied: cffi>=1.4.1 in ・・・\lib\site-packages (from pynacl>=1.0.1->paramiko==2.2.1)
Requirement already satisfied: idna>=2.0 in ・・・\ib\site-packages (from cryptography>=1.1->paramiko==2.2.1)
Requirement already satisfied: setuptools>=11.3 in in ・・・\lib\site-packages\setuptools-27.2.0-py3.6.egg (from cryptography>=1.1->paramiko==2.2.1)
Requirement already satisfied: pycparser in ・・・\ib\site-packages (from cffi>=1.4.1->pynacl>=1.0.1->paramiko==2.2.1)
Installing collected packages: pynacl, bcrypt, paramiko

あとでわかったこと・・・ .whlは.zipである。


  • paramiko-2.2.1-py2.py3-none-any.whlを.zipに変換して解凍する。

  • paramiko-2.2.1.dist-infoにMETADATAが入っている。

  • METADATAをテキストエディタで開くと、Requires-Distに書いてある。

ので、調査しようと思えばできそう。


paramiko-2.2.1.dist-info/METADATA

Metadata-Version: 2.0

Name: paramiko
Version: 2.2.1
・・・
Requires-Dist: bcrypt (>=3.1.3)
Requires-Dist: cryptography (>=1.1)
Requires-Dist: pynacl (>=1.0.1)
Requires-Dist: pyasn1 (>=0.1.7)
・・・


(C) インストール済みのパッケージのバージョンの調査


  • pip freezeをすると一覧でだいたい取れる

> pip freeze

alabaster==0.7.9
anaconda-client==1.6.0
anaconda-navigator==1.5
・・・略・・・


ipython

In [1]: import setuptools

In [2]: setuptools.__version__
Out[2]: '27.2.0'

In [3]: import pycparser

In [4]: pycparser.__version__
Out[4]: '2.14'



(D) パッケージ依存関係の可視化


  • 補足(B)と(C)の結果から、dotをテキストエディタでごりごり書いた。


  • pipdeptreeを使えばもう少し楽にできそう。


paramiko_depend.dot

digraph pramiko {

scp->paramiko
paramiko->bcrypt;
paramiko->pynacl;
paramiko->pyasn1;
paramiko->cryptography;
bcrypt->cffi;
bcrypt->six;
cryptography->idna;
cryptography->setuptools;
cffi->pycparser;
scp[label="scp\n(0.10.2)", color = "red"];
paramiko[label="paramiko\n(2.2.1)", color = "red"];
bcrypt[label="bcrypt\n(>=3.1.3)", color = "red"];
pynacl[label="pynacl\n(>=1.0.1)", color = "red"];
pyasn1[label="pyasn1\n(0.1.9)"];
cryptography[label="cryptography\n(1.5)"];
cffi[label="cffi\n(1.7.0)"];
six[label="six\n(1.10.0)"]
idna[label="idna\n(2.1)"]
setuptools[label="setuptools\n(27.2.0)"]
pycparser[label="pycparser\n(2.14)"]
}


  • Graphvizでpng化(Graphviz2.38\bin\dot.exe)

> dot.exe -Tpng paramiko_depend.dot > paramiko_depend.png


参考

paramiko 2.2.1 : Python Package Index

scp 0.10.2 : Python Package Index

Paramiko Release

Paramikoでコマンドの送信、結果の記録を自動で行う - Qiita

SSH - Python with paramiko issue - Stack Overflow

Paramikoを使ってシェルスクリプトをリモートホストに流しこむ - Qiita

paramiko を使って Python で SFTP/SCPを行う - Librabuc

Pythonでscp - 面白コンテンツ探求日記

SSH - Python with paramiko issue - Stack Overflow

【Python】pipとwheel – Qiita

pythonパッケージの依存関係をgraphvizで可視化 – Qiita

pythonモジュールのバージョンは - Drkcore

DOT言語入門 | You Look Too Cool