Dockerコンテナー内でansible_specを実行したらエラー

# Dockerコンテナー内でansible_specを実行したらエラー

## はじめに

Docker使ってAnsible開発しつつテストコード書いてたら、
以下エラーが出てハマったので備忘録的に残しておきます。

```bash
$ bundle exec rake all
rake aborted!
NoMethodError: private method `select' called for nil:NilClass
/workspace/rakefile:6:in `<top (required)>'
/usr/local/rbenv/versions/2.6.1/bin/bundle:23:in `load'
/usr/local/rbenv/versions/2.6.1/bin/bundle:23:in `<main>'
(See full trace by running task with --trace)
```

## 環境

前提としてWindows 10 + Docker for Windowsを使い、コンテナー内でAnsibleを開発。
Ansibleの定義ファイルはWindowsディスク上において、Volume mountでコンテナーにマウント。

### Ansibleのディレクトリ構成

```bash
$ tree
.
|____.ansiblespec
|____.rspec
|____Gemfile
|____Gemfile.lock
|____group_vars
| |____webserver.yml
|____hosts
|____Rakefile
|____README.md
|____roles
| |____configure_nginx
| | |____spec
| | | |____configure_nginx_spec.rb
| | |____tasks
| | | |____main.yml
| | |____templates
| | | |____nginx.conf.j2
| |____install_nginx
| | |____spec
| | | |____install_nginx_spec.rb
| | |____tasks
| | | |____main.yml
|____site.yml
|____spec
| |____spec_helper.rb
```

## 原因

インベントリーファイルである `hosts` に実行権限があったのでエラーとなっていた。  
WindowsディレクトリをVolume mountすると、権限が `777` になり、
`ansible_spec` が[動的インベントリー](https://docs.ansible.com/ansible/2.9_ja/user_guide/intro_dynamic_inventory.html)と勘違いしてエラーになる。

```bash
$ # 今回のインベントリーファイルは静的インベントリーです。
$ ls -l hosts
-rwxrwxrwx 1 root root 17 Jun 22 10:37 hosts
$ cat hosts
[webserver]
127.0.0.1
```

## 対処

Ansibleのファイルを全体的にコンテナー内にコピーし、  `hosts` の権限を `644` にする。
全部コピーだと大変なのである程度はシンボリックにした。

```bash
mkdir -p /tmp/ansible
cp -p /workspace/hosts /tmp/ansible
chmod 644 /tmp/ansible//hosts
ln -s /workspace/site.yml /tmp/ansible/site.yml
ln -s /workspace/Gemfile /tmp/ansible/Gemfile
ln -s /workspace/Gemfile.lock /tmp/ansible/Gemfile.lock
ln -s /workspace/Rakefile /tmp/ansible/Rakefile
ln -s /workspace/group_vars /tmp/ansible/group_vars
ln -s /workspace/roles /tmp/ansible/roles
ln -s /workspace/spec /tmp/ansible/spec
ln -s /workspace/.ansiblespec /tmp/ansible/.ansiblespec
ln -s /workspace/.rspec /tmp/ansible/.rspec
cd /tmp/ansible
```

## 原因の詳細

スタックトレースを覗いてみる。

```bash
$ bundle exec rake all --trace 2>&1 | head
rake aborted!
NoMethodError: private method `select' called for nil:NilClass
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/ansible_spec-0.3.1/lib/ansible_spec/load_ansible.rb:133:in `get_dynamic_inventory'
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/ansible_spec-0.3.1/lib/ansible_spec/load_ansible.rb:24:in `load_targets'
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/ansible_spec-0.3.1/lib/ansible_spec/load_ansible.rb:425:in `get_properties'
/workspace/rakefile:6:in `<top (required)>'
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/rake-13.0.1/lib/rake/rake_module.rb:29:in `load'
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/rake-13.0.1/lib/rake/rake_module.rb:29:in `load_rakefile'
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/rake-13.0.1/lib/rake/application.rb:703:in `raw_load_rakefile'
/usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/rake-13.0.1/lib/rake/application.rb:104:in `block in load_rakefile'
```

上から順番に見ていったのですが、今回関係あったのはこの部分。
> /usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/ansible_spec-0.3.1/lib/ansible_spec/load_ansible.rb:24:in `load_targets'

該当のソースを覗いてみると23行目でinventoryファイルに実行権限があるかどうかチェックしている。

```bash
$ cat -n /usr/local/rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/ansible_spec-0.3.1/lib/ansible_spec/load_ansible.rb | head -n 30
     1  # -*- coding: utf-8 -*-
     2  require 'hostlist_expression'
     3  require 'oj'
     4  require 'open3'
     5  require 'yaml'
     6  require 'inifile'
     7  require 'ansible_spec/vendor/hash'
     8  if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.1')
     9    require 'ansible/vault'
    10  end
    11
    12  module AnsibleSpec
    13    # param: inventory file of Ansible
    14    # param: return_type 'groups' or 'groups_parent_child_relationships'
    15    # return: Hash {"group" => ["192.168.0.1","192.168.0.2"]}
    16    # return: Hash {"group" => [{"name" => "192.168.0.1","uri" => "192.168.0.1", "port" => 22},...]}
    17    # return: Hash {"pg" => ["server", "databases"]}
    18    def self.load_targets(file, return_type = 'groups')
    19      if not ['groups', 'groups_parent_child_relationships'].include?(return_type)
    20        raise ArgumentError, "Variable return_type must be value 'groups' or 'groups_parent_child_relationships'"
    21      end
    22
    23      if File.executable?(file)
    24        return get_dynamic_inventory(file)
    25      end
    26      f = File.open(file).read
    27      groups = Hash.new
    28      group = ''
    29      hosts = Hash.new
    30      hosts.default = Hash.new
```

これは、[動的インベントリー](https://docs.ansible.com/ansible/2.9_ja/user_guide/intro_dynamic_inventory.html)を判定するために行っており、
今回の`hosts`ファイルは静的インベントリーなので実行権限を外してあげればいい。

## 余談

今だったらWSL2上で開発すればいいのですが、仕事で使うWindows端末がまだWSL2に対応したバージョンでなかったのでこんなことしました。
調べてもあんまり出てこなかったので同じような人が助かれば幸いです。
rubyとか全然触ってないのでスキル持ってればハマることはなかったかも。。。