背景
Javaアプリケーションでwarファイルを作ったらデプロイしたいわけです。
しかし、某システムがデプロイに事実上FTP(S)しかまともに出来ない残念仕様なので、FTP(S)で色々やらないといけないわけです。
そしてFTPは色々コマンドが実行できるはずなんですが、困ったことにftpクライアントが入ってない環境なんですね。
代替として目を付けたのがcurlでした。
やり方
なんとcurlは-Q
--quote
オプションでファイルを送る前後でFTPの操作ができます。
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"
で調べると軒並み「アップデートしなさい」という対応がでてくるので、多分そういうことなんでしょう。(思考放棄)