curlでFTPのファイル送信前後でFTPコマンドを実行する

### 背景

Javaアプリケーションでwarファイルを作ったらデプロイしたいわけです。
しかし、某システムがデプロイに事実上FTP(S)しかまともに出来ない残念仕様なので、FTP(S)で色々やらないといけないわけです。

そしてFTPは色々コマンドが実行できるはずなんですが、困ったことにftpクライアントが入ってない環境なんですね。

代替として目を付けたのがcurlでした。

### やり方

なんとcurlは`-Q` `--quote`オプションでファイルを送る前後でFTPの操作ができます。

https://curl.haxx.se/docs/manpage.html#-Q

`curl --help`だけ見ると`-Q`は**送る前に実行**する以外には書いてないんですが、上記のドキュメント通り、FTPコマンドの先頭に`-(ハイフン)`を付けると**送った後に実行**するFTPコマンドが書けます。（`man`コマンド？入ってないですね。。。）
また、FTPコマンド失敗時はそのままだとそこで終了してしまうのですが、`*(アスタリスク)`を先頭につけると、成否を無視してくれます。

やってみましょう。

ユースケースとしては、一時ファイルとしてアップロードした後、リネームする感じです。
FTPはアップロード時は、アップロード先のファイルを置き換えるのではなく、上書きするようにしてアップロードします。なので、アップロード最中のファイルを見てしまう可能性があります。これが嫌な場合に使います。

例えば以下のようなシェルスクリプトを書きます。
これは引数で取るようにしてますが、Jenkins等なら特定のファイルパスで決め打ちでもよいでしょう。

```
#!/bin/bash

# usage) bash this.sh FILE FTP_FILEPATH
#   eg.) bash this.sh test.dat /path/to/test.dat

set -ue

UPLOAD_FILEPATH=$1
FTP_FILEPATH=$2

FTP_HOST=ftp.host.dummy
FTP_USER=ftp-user
FTP_PASS=ftp-password

# 一時ファイル
TEMP_FTP_FILEPATH="${FTP_FILEPATH}.temp"


# curl コマンド。--quoteの内容が肝。
curl \
  -v --fail --ftp-ssl \
  -T "${UPLOAD_FILEPATH}" \
  -u "${FTP_USER}:${FTP_PASS}" \
  "ftps://${FTP_HOST}${TEMP_FTP_FILEPATH}" \
  --quote "-*DELE $FTP_FILEPATH" \
  --quote "-RNFR $TEMP_FTP_FILEPATH" \
  --quote "-RNTO $FTP_FILEPATH"
```

`--quote`を複数並べるとその順序で実行してくれるようです。

また、`DELE`する理由は、`RNTO`は既に存在する場合に失敗する可能性があったため書いています。
環境によっては置き換えが出来る場合があるので、その場合は`DELE`の行は不要です。


### （おまけ）nssのバージョンによっては失敗する

FTPS接続するときに、TLSの1.2以上でないと接続できないようなサーバの時に遭遇。

手元でcurlコマンドが動くことを確認してから、別の環境で実行したら以下のようなログ(`-v`オプション付き)が出て、失敗していた。


```
curl 7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.15.3 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Protocols: tftp ftp telnet dict ldap ldaps http file https ftps scp sftp 
Features: GSS-Negotiate IDN IPv6 Largefile NTLM SSL libz 
* About to connect() to xxxxxxxxx.net port 990 (#0)
*   Trying XXX.XXX.XXX.XXX... connected
* Connected to xxxxxxxxx.net (XXX.XXX.XXX.XXX) port 990 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* NSS error -5938
* Closing connection #0
* SSL connect error
```

手元や他のサーバのcurlではうまくいくのと、接続先はTLSの1.2以上でないとNGというのはわかっていたので、多分バージョンが古くてTLS1.2に対応できてないとかそういうのかな、と考えた。


```
# cat /etc/redhat-release 
CentOS release 6.6 (Final)
```

古い。Curlのバージョンを見てみましょう。

```
# curl --version
curl 7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.15.3 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Protocols: tftp ftp telnet dict ldap ldaps http file https ftps scp sftp 
Features: GSS-Negotiate IDN IPv6 Largefile NTLM SSL libz 
```

古い。NSSを使っているらしいので、NSSのバージョンも見る。


```
# yum list nss curl
読み込んだプラグイン:fastestmirror
Loading mirror speeds from cached hostfile
 * base: ftp.tsukuba.wide.ad.jp
 * extras: ftp.tsukuba.wide.ad.jp
 * updates: ftp.tsukuba.wide.ad.jp
インストール済みパッケージ
curl.x86_64                                                                       7.19.7-37.el6_5.3                                                                        @anaconda-CentOS-201410241409.x86_64/6.6
nss.x86_64                                                                        3.16.1-14.el6                                                                            @anaconda-CentOS-201410241409.x86_64/6.6
利用可能なパッケージ
curl.x86_64                                                                       7.19.7-53.el6_9                                                                          base                                    
nss.i686                                                                          3.36.0-8.el6                                                                             base                                    
nss.x86_64                                                                        3.36.0-8.el6                                                                             base    
```

古い。試しにnssだけアップデート。

```
yum update -y nss
```

これで動いた。


なんでダメかはよくわからなかったけど、`nss tls1.2`とか`"NSS error -5938"`で調べると軒並み「アップデートしなさい」という対応がでてくるので、多分そういうことなんでしょう。（思考放棄）