fabricでリモートファイルからのファイル取得(get)が失敗した原因調査と代替案メモ

fabrciでリモートサーバーからファイルを取得する場合や逆にリモートサーバーからファイルを取得する場合、fabricの **get関数** もしくは **put関数** を使うことで実施が可能です。

[Python製デプロイツール Fabricを初めて使う際に役立つTips](http://dekokun.github.io/posts/2013-04-07.html)

ただ、実際に試してみたところ、エラーが発生しうまくできなかったのでその原因調査と代替案についてのメモ

# 結論

+ fabricのput,get関数はsftpを利用する
+ 上記よりput,get関数はリモートサーバーでsftpが有効化されていないと利用できない
+ local関数でscpを使えば同じことが出来る
+ scpを普通に実行するとパスワードが聞かれて面倒な場合、expectコマンドを使って自動化ができる

# エラー発生

sshログイン出来るサーバーで以下の様なfabfileを作成しました。

```lang:fabfile.py
from fabric.api import *

env.user = 'who'
env.password = 'bar'

def remote():
  print("Execute %s" % env.host)
  print("host_string %s" % env.host_string)
  get('/home/foo/hoge', 'fuga')
```

しかし、実行すると以下のようにエラーが。。。

```bash
$fab -H 172.1.1.1 remote
[172.1.1.1] Executing task 'remote'
Execute 172.1.1.1
host_string 172.1.1.1
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.2-py2.7.egg/fabric/main.py", line 743, in main
    *args, **kwargs
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.2-py2.7.egg/fabric/tasks.py", line 387, in execute
    multiprocessing
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.2-py2.7.egg/fabric/tasks.py", line 277, in _execute
    return task.run(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.2-py2.7.egg/fabric/tasks.py", line 174, in run
    return self.wrapped(*args, **kwargs)
  File "/home/vagrant/fabric/get-files/fabtest.py", line 11, in remote
    get('/home/t_furuno/hoge', 'fuga')
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.2-py2.7.egg/fabric/network.py", line 649, in host_prompting_wrapper
    return func(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.2-py2.7.egg/fabric/operations.py", line 536, in get
    ftp = SFTP(env.host_string)
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.2-py2.7.egg/fabric/sftp.py", line 33, in __init__
    self.ftp = connections[host_string].open_sftp()
  File "/usr/local/lib/python2.7/dist-packages/paramiko/client.py", line 439, in open_sftp
    return self._transport.open_sftp_client()
  File "/usr/local/lib/python2.7/dist-packages/paramiko/transport.py", line 905, in open_sftp_client
    return SFTPClient.from_transport(self)
  File "/usr/local/lib/python2.7/dist-packages/paramiko/sftp_client.py", line 131, in from_transport
    chan.invoke_subsystem('sftp')
  File "/usr/local/lib/python2.7/dist-packages/paramiko/channel.py", line 60, in _check
    return func(self, *args, **kwds)
  File "/usr/local/lib/python2.7/dist-packages/paramiko/channel.py", line 254, in invoke_subsystem
    self._wait_for_event()
  File "/usr/local/lib/python2.7/dist-packages/paramiko/channel.py", line 1086, in _wait_for_event
    raise e
paramiko.ssh_exception.SSHException: Channel closed.
Disconnecting from 172.1.1.1... done.
```

ぐぬぬ。。

# 原因

上記ログを見ると分かるように内部的には **sftp** を使ってファイル転送を行っているようなので試しにエラーが発生したサーバーに対してコマンドでsftpを実行してみました。

```bash
$$ sftp foo@172.1.1.1
foo@172.1.1.1's password:
subsystem request failed on channel 0
Couldn't read packet: Connection reset by peer
```

失敗しました。。。
調べるとsshdプロセスが起動していてもsftpのみ無効化するという事ができるようです。

[SFTPを有効・無効化する方法](http://linux.just4fun.biz/Linux%E7%92%B0%E5%A2%83%E8%A8%AD%E5%AE%9A/SFTP%E3%81%AE%E6%9C%89%E5%8A%B9%E3%83%BB%E7%84%A1%E5%8A%B9%E5%8C%96.html)

ということでリモートサーバーに入ってsshd_configを確認した所、確かにsftpが無効化されていました。。もし、sftpの有効化が許されている場合には上記設定を変更し、sshdを再起動することで対応が可能になるかと思います。

# sftpの有効化が出来ない場合

理由があってsftpの有効化が出来ない場合にはしょうがないので以下のようにlocal関数で自分のマシンからscpコマンドを実行するようにすればファイルの取得が可能です。

```bash
local('scp %s@%s:/home/foo/hoge %s/' %(env.user, env.host, env.host))
```

ただし、普通にscpコマンドを実行するのでパスワード入力が求められて面倒です。その場合、別途expectコマンドを使ってパスワードを自動入力するシェルを作成し、これをfabricから実行すればパスワードを都度入力する必要がなく、ファイル取得を行うことができます。

[Expect コマンドでパスワードなしでログインする方法(telnet,ssh,scp)](http://futurismo.biz/archives/1272)

```lang:autoscp.sh
#!/bin/bash

if [ "$1" != "" ]; then
    hostname=$1
    username=$2
    password=$3
    src=$4
    dst=$5
else
    echo "Error: Input argument"
    exit 1
fi

expect -c "
set timeout 20
spawn scp $username@$hostname:$src $dst

# login
expect password:
send \"$password\n\"

expect $
"
exit 0
```

fabfileは以下のように指定すればOK

```fabfile.py
local("./autoscp.sh %s %s %s /home/t_furuno/hoge ." %(env.host, env.user, env.password))
```