Ansibleで、サーバー上の特定ディレクトリにあるファイル名一覧を取得する
個人のサーバーでgit運用したくて、Gitbucket を立ててるんです。
それで、この前 Gitbucket の更新をするときにプラグインファイルも更新をかけたのですが、
困ったことに、プラグインをダウンロードするだけのAnsibleタスクだと
古いバージョンのプラグインファイルもディレクトリに残ってしまってバグるんです。
なので、古いプラグインファイルを削除するためにどうしようかと考えて、
ダウンロードするプラグインファイル名一覧と
現在サーバーにあるプラグインファイル名一覧を比較して、それが異なる場合には、
ダウンロード前にサーバー上のプラグインディレクトリを一度削除することにしました。
やり方が少しややこしいのでここに書き残しておきます。
(Ansibleで配列を扱うのは苦労が多いので、AnsibleでがんばるよりはFabricなりCapistranoなりでデプロイを別におこなうのが本当は健全)
1. もともとのタスク構成
こうなってました。
- name: create plugins directory become: true become_user: "{{ main_user_name }}" become_method: sudo file: path: "{{ gitbucket_plugins_dir }}" state: directory - name: download plugins become: true become_user: "{{ main_user_name }}" become_method: sudo get_url: url: "{{ item.url }}" dest: "{{ gitbucket_plugins_dir }}" mode: 0755 with_items: "{{ gitbucket_plugins }}" notify: restart gitbucket
変数 gitbucket_plugins
は以下のとおりです。
gitbucket_plugins: - name: emoji url: https://github.com/gitbucket/gitbucket-emoji-plugin/releases/download/4.4.0/gitbucket-emoji-plugin_2.12-4.4.0.jar - name: gist url: https://github.com/gitbucket/gitbucket-gist-plugin/releases/download/4.9.0/gitbucket-gist-plugin_2.12-4.9.0.jar
そしてサーバー上には path/to/plugins/
ディレクトリに以下のファイルが入っているとします。
gitbucket-emoji-plugin_2.12-4.4.0.jar gitbucket-gist-plugin_2.12-4.8.0.jar
このままタスクを実行すると、path/to/plugins/
ディレクトリには
- gitbucket-emoji-plugin_2.12-4.4.0.jar
- gitbucket-gist-plugin_2.12-4.8.0.jar
- gitbucket-gist-plugin_2.12-4.9.0.jar
が存在してしまい、バージョン違いのプラグインが同居して挙動がおかしくなってしまう。そういうわけです。
2. map で解決
そういうわけで、create plugins directory の前に以下のようなタスクを定義することで解決させました。
- name: check current plugins become: true become_user: root find: path: "{{ gitbucket_plugins_dir }}" register: current_plugins - set_fact: current_plugins_list: > {{ current_plugins.files | map(attribute='path') | map('basename') | list | sort }} install_plugins_list: > {{ gitbucket_plugins | map(attribute='url') | map('basename') | list | sort }} - set_fact: is_gitbucket_plugins_updated: > {{ current_plugins_list != install_plugins_list }} - name: remove plugins directory (when plugins updated) become: true become_user: "{{ main_user_name }}" become_method: sudo file: path: "{{ gitbucket_plugins_dir }}" state: absent when: is_gitbucket_plugins_updated
強引な力技ですね・・・(笑)
では、内容を説明していきます。
まず check current plugins にて find を用い、その結果を変数 current_plugins
に渡します。
current_plugins.files は、サーバー上のプラグインディレクトリにあるファイルすべての stat を配列で持ちます。
[ { 'path': 'path/to/plugins/gitbucket-emoji-plugin_2.12-4.4.0.jar', 'mode': '0755', 'size': ...後略 }, { 'path': 'path/to/plugins/gitbucket-gist-plugin_2.12-4.8.0.jar', 'mode': ...後略 } ]
欲しいのはファイル名だけですから、配列の各要素に対してキー名を指定してそこだけ取り出します。
それが map(attribute=‘path’) の部分です。
この Filter をかけることで、配列は以下のようになります。
[ 'path/to/plugins/gitbucket-emoji-plugin_2.12-4.4.0.jar', 'path/to/plugins/gitbucket-gist-plugin_2.12-4.8.0.jar' ]
そしてこの path からファイル名の部分だけ取り出すため、
Ansible側で用意されている basename Filter を使います。
map と組み合わせることにより、各要素に対して basename Filter をかけることができ、配列は以下のようになります。
[ 'gitbucket-emoji-plugin_2.12-4.4.0.jar', 'gitbucket-gist-plugin_2.12-4.8.0.jar' ]
そしてその後に list Filter をかけます。
さっきまで『配列は以下のようになります』と書いていましたが、実際には map Filter が返すのはmapオブジェクトなので、
list Filter をかけなければ配列にはなりません。
最後に、sort による辞書順の並び替えをしておいて、
このあとの配列の比較に備えます。
この配列を変数 current_plugins_list
として保存します。
ダウンロードする予定のプラグイン一覧にも似たような処理をかけて
変数 install_plugins_list
に配列を保存します。
そしてこの2つの変数の値が異なるときは
変数 is_gitbucket_plugins_updated
は True となり、
サーバー上のプラグインディレクトリの削除がおこなわれる。という仕組みです。
3. 感想
だいぶ無理矢理やってて、魔術じみてますね。
Ansibleで配列使うのはしんどいですが、こういうことができるんだなーと思って書きました。
もう少しがんばれば、プラグインフォルダーを削除するのでなく、
不要になったプラグインファイルだけを削除する、というふうに書けそうな気がしますが、
中途半端なところで妥協しています。
個人サーバーなので複雑なことしたくなくて、アプリケーションの展開までAnsibleにやらせてますが、
結果Ansibleのレシピが複雑になってるので、ちゃんとデプロイツール使ったほうがいいなという気がしてくるこの頃です……。