Gradleを使ってwarファイルをTomcatにデプロイしたいという場面に於いて、warファイルを$CATALINA_BASE/webapps
にコピーする手法をよく見かけるが、それとは異なる手法としてTomcatにデフォルトで入っているManager AppをGradleから使う術を記しておく。
warファイルのコピーは単純だがGradleから見てコピー先がローカルなのかネットワーク上なのかDockerコンテナ内なのかを意識する必要がある。一方HTTPクライアントからManager Appを叩く分にはそのような環境差異を意識する必要はなく複数の異なる環境でもビルド・スクリプトを共有しやすい。
前準備
まずはManager AppをGUIレスで使えるようにする。
Tomcatをインストールした直後はManager Appを始めとしたプリインストールのアプリは$CATALINA_BASE/webapps.dist
にありデフォルトで無効になっている。これを$CATALINA_BASE/webapps
から見えるようにする。
下はシンボリック・リンクを用いた例。
ln -s \ $CATALINA_BASE/webapps.dist/manager \ $CATALINA_BASE/webapps/
続いて$CATALINA_BASE/conf/tomcat-user.xml
を編集しmanager-scriptロールを持つユーザを作成する。
<tomcat-users xmlns="http://tomcat.apache.org/xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd" version="1.0"> <role rolename="manager-script"/> <user username="USERNAME" password="PASSWORD" roles="manager-script"/> </tomcat-users>
WebブラウザからGUIでManager Appを使う為のmanager-guiというロールもあるが同一ユーザにmanager-guiとmanager-scriptを一緒に持たせないことが推奨されている*1。(もしGUIも使うのであれば)GUI用とスクリプト用でユーザを分けるのが良いだろう。
最後にManager Appへの接続を許可するアクセス元アドレスの範囲を必要に応じて$CATALINA_BASE/conf/[enginename]/[hostname]/manager.xml
に設定する。
デフォルトの設定は$CATALINA_BASE/webapps.dist/manager/META-INF/context.xml
にありループバック・アドレスからのみを許可している。ネットワーク上のサーバであれば管理セグメントの特定ノードからは許可する等といった変更がいるだろう。
下は192.168.1.*からのアクセスを許可する例。
<Context privileged="true" antiResourceLocking="false"> <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="192\.168\.1\.\d+" /> </Context>
以上の設定を済ましてTomcatを再起動すれば作成したユーザでリモート・デプロイが可能になる。
Gradleでデプロイ
下例ではHTTPクライアントとして個人的にGradleで使いやすいと感じているgradle-http-pluginでdeployタスクを定義してみた。
USERNAME
とPASSWORD
は上で作成したmanager-scriptユーザのもの。外部定義して環境毎にURLとユーザを切り替えれば複数環境でタスクを共有出来る。
import groovyx.net.http.NativeHandlers import io.github.httpbuilderng.http.HttpTask plugins { id 'war' id 'io.github.http-builder-ng.http-plugin' version '0.1.1' } task deploy(type: HttpTask, dependsOn: 'war') { config { request.uri = 'http://example.com:8080' request.auth.basic('USERNAME', 'PASSWORD') request.encoder('application/octet-stream', NativeHandlers.Encoders.&handleRawUpload) } put { request.uri.path = '/manager/text/deploy' request.uri.query = [path: "/${war.archiveBaseName.get()}", update: true] request.contentType = 'application/octet-stream' request.body = war.archiveFile.get().asFile response.success { fs, body -> def message = body as String println(message) if (message.startsWith('FAIL')) throw new Exception(message) } } }
ポイントとなるのはrequest.uri.query
に渡しているクエリパラメータ。
path:対象アプリのコンテキストパスを
/myapp
のようにスラッシュ始まりで指定する。GUIのManager Appやwar配置と違って省略することは出来ない。ROOTアプリケーションの場合は単に/
を指定する。ここでは/
+ warファイルのベース名を指定している。update:pathで指定したコンテキストパスにアプリがすでにあった場合の動作を制御する。
false
(デフォルト値)ならエラーとし、true
ならば既存アプリをアンデプロイした後warをデプロイする。繰り返し使用する使途ではtrue
固定で良いだろう。
その他に指定可能なパラメータやデプロイ以外に用意されているAPI(アンデプロイやアプリの一覧取得)は公式マニュアルが詳しい。
おまけ:curlでデプロイ
curlでも十分戦える。
curl --basic --user USERNAME:PASSWORD \ --upload-file build/libs/myapp.war \ 'http://example.com:8080/manager/text/deploy?path=/myapp&update=true'