＜Ansible＞「copy」モジュールの使い方

#はじめに
この記事では、Ansibleの`copy`モジュールについて説明します。
`copy`モジュールは、その名の通り`cp`コマンドと同じ機能を果たすもので、ファイルをコピーします。
ディレクトリごとファイルをコピーすることはできますが、空のディレクトリだけコピーすることはできません。
※公式ドキュメントは、[こちら](https://docs.ansible.com/ansible/latest/modules/copy_module.html)から確認できます
# パラメータ（アーギュメント）
パラメータに値を指定することで、処理を細かく制御することができます。

|パラメータ  |選択肢：<font color="MediumBlue">デフォルト</font>  |機能  |
|---|:-:|---|
|**attributes**  |－|ファイルの属性を指定する。|
|**backup**  |**選択肢：** <br> **・**<font color="MediumBlue">**no**←</font> <br> **・**yes  |`yes`の場合、上書きされるファイルのバックアップを作成する。|
|**checksum**  |－|SHA1チェックサムを指定する。|
|**content**  |－|コピー先ファイルに記述する内容を指定する。|
|**decrypt**  |**選択肢：<br>**・no<br>**・**<font color="MediumBlue">**yes**←</font>|`yes`の場合、暗号化されたファイルを復号化する。|
|**dest**  |－|コピー先を指定する。|
|**directory_mode**  |－|ディレクトリのパーミッションを指定する。|
|**follow**  |**選択肢：** <br> **・**<font color="MediumBlue">**no**←</font> <br> **・**yes  |`yes`の場合、`dest`で指定したリンクに従う。|
|**force**|**選択肢：<br>**・no<br>**・**<font color="MediumBlue">**yes**←</font>|`yes`の場合、`dest`で指定したファイルの有無にかかわらず処理が実行される。|
|**group**|－|グループ名を指定する。|
|**local_follow**  |**選択肢：<br>**・no<br>**・**<font color="MediumBlue">**yes**←</font>|コピー元で指定したリンクに従う。|
|**mode**  |－  |ファイルのパーミッションを指定する。|
|**owner**  |－  |ユーザー名を指定する。|
|**remote_src**  |**選択肢：** <br> **・**<font color="MediumBlue">**no**←</font> <br> **・**yes  |`no`の場合、`src`で指定したファイルをコントロールノードで探す。`yes`の場合、`src`で指定したファイルをターゲットノードで探す。|
|**selevel**  |**デフォルト：** <font color="MediumBlue">"s0"</font>  |SELinuxファイルコンテキストのセキュリティレベルを指定する。|
|**serole**  |－  |SELinuxファイルコンテキストのロール識別子を指定する。|
|**setype**  |－  |SELinuxファイルコンテキストのタイプ識別子を指定する。|
|**seuser**  |－  |SELinuxファイルコンテキストのユーザー識別子を指定する。|
|**src**  |－  |コピー元を指定する。|
|**unsafe_writes**  |**選択肢：** <br> **・**<font color="MediumBlue">**no**←</font> <br> **・**yes  |`yes`の場合、アトミックでない書込みを強制する。|
|**validate**  |－  |コピーする前に実行する検証コマンドを指定する。|
#①`dest`, `src`, `content`
ここからは、`copy`モジュールのパラメータをどのように使えばよいのか、Playbookを使って説明していきます。
まず、ファイルをコピーする上で必要なのが`dest`です。
これを使ってコピー先となるファイル/ディレクトリを指定します。
コピー元となるファイル/ディレクトリは、`src`を使って指定します。
`src`を使用しない場合、`content`でコピー先ファイルに記述する内容を指定することもできます。
まずは、`src`を使用したPlaybookです。

```YAML
- name: srcにコピー元を指定
  copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
```
このPlaybookを実行することで、`src`で指定したコントロールノードのファイルが`dest`で指定したコピー先にコピーされます。
これは、コピー先に指定するファイルの有無に関係なく実行されます。
ファイルが既に存在する場合は上書きされ、存在しない場合は新たに作成されます。

`content`を使用したPlaybookは以下のとおりです。

```YAML
- name: contentで記述内容指定
  copy:
    content: '# This file was moved to /etc/other.conf'
    dest: /etc/mine.conf
```
コピー先ファイルの中身は、`content`で指定した値が記述されています。
#②`force`
ファイルの有無によって処理の成否を制御できるパラメータがあります。それが`force`です。
これは`cp`コマンドの`-f`オプションと同じ効果を持ちます。
デフォルトでは`yes`ですが、`no`にすることでコピー先に指定したファイルが存在しない場合は処理が失敗し、コピーされません。
`force`を使用したPlaybookは以下のとおりです。

```YAML
- name: 上書きのみ実行する
  copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    force: no
```
#③`remote_src`
`src`で指定したファイルが、コントロールノードのファイルであるか、もしくはターゲットノードのファイルであるか指定することができます。
そのパラメータが`remote_src`です。デフォルト値の`no`の場合、`src`で指定したファイルをコントロールノードで探します。`yes`の場合、`src`で指定したファイルをターゲットノードで探します。
つまり、Playbookに`remote_src`を記述しない場合はコントロールノードとターゲットノードとの間でコピーが実行され、`remote_src`の値が`yes`の場合はターゲットノード内でコピーが実行されます。
`remote_src`を使用したPlaybookは以下のとおりです。

```YAML
- name: ターゲットノード内でのコピー
  copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    remote_src: yes
```
#④`owner`, `group`, `mode`
ファイルの所有者、グループ、権限（パーミッション）を変更したいときは`owner`, `group`, `mode`を使います。
これは`chown`コマンド、`chgrp`コマンド、`chmod`コマンドと同じ効果を持ちます。
パーミッションの指定方法は、記号、数字どちらも対応しています。
`owner`, `group`, `mode`を使用したPlaybookは以下のとおりです。

```YAML
- name: 所有者、グループ、パーミッション変更（記号指定①）
  copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    owner: ec2-user
    group: ec2-user
    mode: u=rw,g=r,o=r
```
```YAML
- name: 所有者、グループ、パーミッション変更（記号指定②）
  copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    owner: ec2-user
    group: ec2-user
    mode: u-rw,g+wx,o-r
```
```YAML
- name: 所有者、グループ、パーミッション変更（数字指定①）
  copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    owner: ec2-user
    group: ec2-user
    mode: 0600
```
```YAML
- name: 所有者、グループ、パーミッション変更（数字指定②）
  copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    owner: ec2-user
    group: ec2-user
    mode: '600'
```
数字指定の場合、先頭に「0」をつけるか、もしくは引用符を使ってください。

`mode`の値を`preserve`にすることで、パーミッションが保持された状態でコピーできます。

```YAML
- name: パーミッションの保持
  copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    mode: preserve
```
#⑤`directory_mode`
Playbookを実行することによって新規で作成される全てのディレクトリの、「パーミッション」を指定できます。
`mode`と異なる点ですが、`mode`がコピー先ファイルのパーミッションを指定するのに対し、`directory_mode`はコピー先で新規に作成されるディレクトリのパーミッションを指定します。

例えば、下記のPlaybookを実行するとします。

```YAML
- name: 新規で作成されるディレクトリのパーミッション一括指定
  copy:
    src: /srv/myfiles
    dest: /etc/qiita/copy
    directory_mode: 0444
```
#####コントロールノードのディレクトリ構成

```
/srv
└── myfiles
    └── foo.conf
```
#####ターゲットノードのディレクトリ構成

```
/etc
```
すると、ターゲットノードにファイル「foo.conf」がコピーされるのですが、etc配下に新規で作成されるディレクトリのみ、パーミッションが`directory_mode`で指定した`0444`になります。
#####Playbook実行後のターゲットノードのディレクトリ構成
※括弧内の数値は`directory_mode`によって設定されたパーミッションです

```
/etc
└── qiita（0444）
    └── copy（0444）
        └── myfiles（0444）
            └── foo.conf
```
実際に手を動かした方が理解しやすいと思うので、是非、試してみてください。
#⑥`attributes`
ファイルの属性を変更したいときは`attributes`を使います。
これは`chattr`コマンドと同じ効果を持ちます。
`attributes`を使用したPlaybookは以下のとおりです。

```YAML
- name: 属性変更
  copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    attributes: '+acdijstuADS'
```
ターゲットノードで`lsattr`コマンドを実行すると、ファイルの属性が変更されていることを確認できます。

```
$ lsattr /etc/foo.conf
suS-iadAc-j-t-e---- /etc/foo.conf
```
#⑦`backup`
ファイルのバックアップを作成したいときは`backup`を使います。
`backup`を使用したPlaybookは以下のとおりです。

```YAML
- name: バックアップ作成
  copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    backup: yes
```
コピー元のファイルを上書きしてPlaybookを実行するたび、コピー先でバックアップが作成されます。
#⑧`checksum`
SHA1チェックサムを指定したいときは`checksum`を使います。
SHA1チェックサムは、`sha1sum`コマンドで確認します。

```
# sha1sum /srv/myfiles/foo.conf
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  /srv/myfiles/foo.conf
```
表示されたコピー元ファイルのSHA1チェックサムを、`checksum`の値として設定します。
`checksum`を使用したPlaybookは以下のとおりです。

```YAML
- name: SHA1チェックサム指定
  copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    checksum: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
ただし、デフォルトで`src`のファイルのチェックサムと照らし合わせてくれるので、あまりこのパラメータを使う機会はないと思います。
#⑨`decrypt`
暗号化されたファイルをコピーする際、コピー先で自動復号化する必要がない場合、`decrypt`の値を`no`に設定します。

まず、このパラメータの機能を確認するためにコピー元となるファイルを暗号化します。
このパラメータで自動復号化できるファイルの拡張子は「yml」と「json」の2つのみです。
下記コマンドを実行するとパスワードの入力が求められるので、パスワードを設定してください。
※詳細は[こちら](https://docs.ansible.com/ansible/2.9_ja/user_guide/vault.html)から確認お願いします

```
# ansible-vault encrypt /srv/myfiles/foo.yml
```
ここで、`decrypt`の値が`no`の場合と`yes`の場合を比較したいと思います。
#####①`decrypt`の値が`no`の場合
Playbookです。

```YAML
- name: 暗号化した状態のままコピー
  copy:
    src: /srv/myfiles/foo.yml
    dest: /etc/foo.conf
    decrypt: no
```
Playbook「copy.yml」を下記コマンドで実行すると、ファイルが暗号化された状態のままコピーされます。

```
# ansible-playbook copy.yml
```
#####②`decrypt`の値が`yes`の場合
Playbookです。

```YAML
- name: 自動復号化
  copy:
    src: /srv/myfiles/foo.yml
    dest: /etc/foo.conf
    decrypt: yes
```
`decrypt`はデフォルトの値が`yes`なので、わざわざ明示的に書く必要はありません。
書いても書かなくても効果は同じです。

自動復号化する場合は、下記コマンドでPlaybook「copy.yml」を実行する必要があります。

```
# ansible-playbook --vault-id @prompt copy.yml
```
パスワードを求められるので、暗号化の際に設定したパスワードを入力してください。
コピー先のファイルが自動で復号化されます。
#⑩`follow`, `local_follow`
`follow`は、値を`yes`にすることで`dest`にリンクを指定できます。

まず、このパラメータの機能を確認するために、ターゲットノードでシンボリックリンクを作成します。

```
# ln -s /path/to/file /path/to/link
```
`follow`の値が`yes`のPlaybookを実行すると、コピー元のファイル /etc/foo.conf が /path/to/file に上書きされます。

```YAML
- name: destで指定したリンクに従う
  copy:
    src: /etc/foo.conf
    dest: /path/to/link  # link to /path/to/file
    follow: yes
```
`follow`の値が`no`のPlaybookを実行すると、記述内容のとおり、/etc/foo.conf をコピーした /path/to/link が新規で作成されます。

```YAML
- name: destで指定したリンクに従わない
  copy:
    src: /etc/foo.conf
    dest: /path/to/link  # link to /path/to/file
    follow: no
```
`local_follow`は設定した値によってコピー元で作成したシンボリックリンクに従うかどうか制御できると思ったのですが、値を`no`にしてもコピーは実行できたので、このパラメータの正しい使い方はまだ理解できていません。
#⑪`seuser`, `serole`, `setype`, `selevel`
これらのパラメータに値を設定することで、SELinuxファイルコンテキストを制御できます。
#⑫`unsafe_writes`
デフォルト値の`no`の場合、/proc/sys/net/ipv4/conf/all/forwarding に書込みはできないのですが、`yes`にすることでアトミックでない書込みを強制することができます。

```YAML
- name: 書込みを強制する
  copy:
    content: 1
    dest: /proc/sys/net/ipv4/conf/all/forwarding
    unsafe_writes: yes
```
#⑬`validate`
`validate`には、コピーする前に実行する検証コマンドを指定します。
`validate`を使用したPlaybookは以下のとおりです。

```YAML
- name: コピー前に検証する
  copy:
    src: /etc/sudoers
    dest: /etc/sudoers.edit
    validate: /usr/sbin/visudo -csf %s
```
# あとがき
基本的なモジュールであるにもかかわらず、ほとんどのパラメータの使い方を分かっていませんでした。そこで、時間をかけて調べてみようと思ったのが、この記事を作成するきっかけでした。
全てを理解することは難しかったのですが、他のモジュールにも同じようなパラメータはあるので、時間をかけて調べることができたのは良かったと思っています。
今後も、モジュールの調査を続けます。