Puppet4.xではじめるサーバ設定自動化 7（ファイル変更）

Puppet4.xではじめるサーバ設定自動化の、ファイル変更編です。
全体的な目次は、[ここ](http://qiita.com/kijibato/items/ae510dfa91884bdd9f61)を参照してください。
主にCentOS 7を例に紹介しているので、その他OSについては読み替えてください。

※記載されている例は、その内容に関わる部分のみ記載しています。なので実際試す場合、親ディレクトリ作成、httpdインストールを別途行う必要があります。

# ファイル変更

## 基本
ファイルを変更する場合、Puppetのマニフェストでfileリソースで定義します。
/tmp/hello_puppet.txtを変更する場合の例です。指定のパスにファイルがない場合は、ファイルが作成されます。

```puppet:file01.pp
file { '/tmp/hello_puppet.txt':
  ensure  => "file",
  owner   => "root",
  group   => "root",
  mode    => "0644",
  content => "hello puppet\n",
}

```

- fileの右側のタイトルに、ファイルのパスを指定
- ensure属性に、fileを指定
- owner属性に、ファイルの所有ユーザを指定
- group属性に、ファイルの所有グループを指定
- mode属性に、ファイルのパーミッッション(アクセス権限)を指定
- content属性に、ファイルの中身を指定。

ファイル変更の場合、ensure属性は必須でないですが、ensure属性にfileを指定します。なお、ensure属性にabsentを指定すると、ファイルの削除ができます。ただ、ファイル削除する場合は慎重に。
**content属性は、ファイルの中身です。**ファイルの中身が数行程度であれば、上記例のように直接記載することもできます。

## テンプレートファイルで置き換える
あらかじめ用意したファイルに置き換える場合、テンプレートとなるファイルをcontent属性に指定します。

```puppet:file02.pp
file { "/etc/httpd/conf/httpd.conf":
  ensure  => "file",
  owner   => "root",
  group   => "root",
  mode    => "0644",
  content => template("apache/httpd.conf.erb"),
}

```

content属性に、ファイルの中身を直接記載せず、template関数(template())にテンプレートにするファイルのパスを指定します。拡張子erbが付いている理由は次回。例のfile02.ppを実行する場合、テンプレートにするファイルを次のように配置します。

```
.
|-- file02.pp
`-- modules
    `-- apache
        `-- templates
            `-- httpd.conf.erb

※puppet applyの場合のディレクトリ構成
```

下記コマンドで実行します。※Puppetだと、**テンプレートファイルと同じ内容のファイルがすでに指定のパスにある場合、ファイルは変更されません。**

```
/opt/puppetlabs/bin/puppet apply file02.pp --modulepath=modules -t
```

**modulepathオプションで、どこにテンプレートファイルを置いてあるか指定します。**例では、カレントディレクトリのmodulesディレクトリの下にテンプレートファイルがあるので、modulesを相対パスで指定してます。ちなみに、絶対パスでもよいです。

テンプレートファイルの置き場所は、ちょっとわかりにくいですが、modulepathオプションで指定するディレクトリの下に、モジュール名のディレクトリがあって、さらにその下にtemplatesディレクトリがあって、さらにその下にテンプレートファイルがあるイメージです。
（※**modulesディレクトリの2個下に、templatesという名前のディレクトリがあるのが必須**で、それより下のディレクトリ構成は自由です。）

## テンプレートファイルをサーバごとに変える

同じマニフェストファイルを実行して、**サーバごとに配布する設定ファイルを変えたい場合、content属性のtemplate関数に渡すパスを変えることで実現できます**。

```puppet:file03.pp
#(1)
$httpd_tmpl = $hostname ? {
    "web01"  => "apache/web01/httpd.conf.erb",
    /^web.*/ => "apache/web/httpd.conf.erb",
    default  => "apache/default/httpd.conf.erb",
}

#(2)

file { "/etc/httpd/conf/httpd.conf":
  ensure  => "file",
  owner   => "root",
  group   => "root",
  mode    => "0644",
  content => template($httpd_tmpl),
}

```

この例では、(1)でサーバのホスト名を条件に、テンプレートファイルのパスを指定しています。ホスト名が、

- web01の場合、apache/web01/httpd.conf.erb
- 正規表現/^web.*/に一致する場合、apache/web/httpd.conf.erb
- どれにも当てはまらない場合、apache/default/httpd.conf.erb

としています。(2)のcontentに、(1)で決まったテンプレートのパスを設定しています。なお、file03.ppを実行する場合、次のようなディレクトリ構成で、ファイルを用意しておきます。

```
.
|-- file03.pp
`-- modules
    `-- apache
        `-- templates
            |-- default
            |   `-- httpd.conf.erb
            |-- web
            |   `-- httpd.conf.erb
            `-- web01
                `-- httpd.conf.erb

※puppet applyの場合のディレクトリ構成
```

## ファイル変更前にシンタックスチェックする

**ファイル変更前にシンタックスチェックをしたい場合、validate_cmd属性にシンタックスチェック用コマンドを指定します。**チェック用コマンドは、問題がない場合に0を返すコマンドを指定します。シンタックスチェックで問題があった場合、ファイル置き換え前にエラーになります。ちなみに**ドライランのnoopだと、残念ながらエラーになりません。**

httpd.confのチェックをする場合の例です。チェックコマンドは、CentOS7の場合です。

```puppet:file04.pp
file { "/etc/httpd/conf/httpd.conf":
  ensure       => "file",
  owner        => "root",
  group        => "root",
  mode         => "0644",
  content      => template("apache/httpd.conf.erb"),
  validate_cmd => "/usr/sbin/httpd -t -f %",
}

```

validate_cmd属性のコマンドの%は、コマンド実行時に配布するファイル名に置き換えられます。なお、コマンドは、フルパスで記載が必要です。validate_cmd属性は、puppet3.5以降で対応しています。

配布前にシンタックスチェックする機能は、設定ファイル変更直後に自動でリロードするようなミドルウェアなどで役立ちます。配布時チェックすることで、シンタックスエラーによるサービス影響を防げます。

実際に、シンタックスエラー時のログ例の一部です。

```
Error: Execution of '/usr/sbin/httpd -t -f /etc/httpd/conf/httpd.conf20160502-229-11sah4d' returned 1: AH00526: Syntax error on line 103 of /etc/httpd/conf/httpd.conf20160502-229-11sah4d:
<Directory> directive missing closing '>'
Error: /Stage[main]/Main/File[/etc/httpd/conf/httpd.conf]/content: change from {md5}f5e7449c0f17bc856e86011cb5d152ba to {md5}3cd7eaeebfd2554f9d26c0fcd5031a9d failed: Execution of '/usr/sbin/httpd -t -f /etc/httpd/conf/httpd.conf20160502-229-11sah4d' returned 1: AH00526: Syntax error on line 103 of /etc/httpd/conf/httpd.conf20160502-229-11sah4d:
<Directory> directive missing closing '>'

```

### XMLのvalidate_cmd
参考までに、XML用のチェックコマンド例です。Rubyで正常に読めるかで判定しています。

```puppet:file05.pp
file { "/tmp/puppet_sample.xml":
  ensure       => "file",
  owner        => "root",
  group        => "root",
  mode         => "0644",
  content      => template("sample/puppet_sample.xml.erb"),
  validate_cmd => "/opt/puppetlabs/puppet/bin/ruby -e \"require 'rexml/document'; REXML::Document.new(open('%'))\"",
}

```

### YAMLのvalidate_cmd
参考までに、YAML用のチェックコマンド例です。Rubyで正常に読めるかで判定しています。

```puppet:file06.pp
file { "/tmp/puppet_sample.yaml":
  ensure       => "file",
  owner        => "root",
  group        => "root",
  mode         => "0644",
  content      => template("sample/puppet_sample.yaml.erb"),
  validate_cmd => "/opt/puppetlabs/puppet/bin/ruby -e \"require 'yaml'; YAML.load_file('%')\"",
}

```

## バイナリーファイルを配布する

バイナリーファイルを配布する場合、配布するバイナリーファイルをsource属性に指定します。

```puppet:file07.pp
file { "/opt/gitbucket/4.0/gitbucket.war":
  ensure => "file",
  owner  => "root",
  group  => "root",
  mode   => "0644",
  source => "puppet:///modules/gitbucket/gitbucket.war",
}

```

source属性に、配布するバイナリーファイルのパスを指定します。template関数の指定と違って、puppet:///modulesがつくのに注意です。file07.ppを実行する場合、配布するファイルを次のように配置ます。

```
.
|-- file07.pp
`-- modules
    `-- gitbucket
        `-- files
            `-- gitbucket.war

※puppet applyの場合のディレクトリ構成
```

下記コマンドで実行します。

```
/opt/puppetlabs/bin/puppet apply file07.pp --modulepath=modules -t
```

**modulepathオプションで、どこにバイナリーファイルが置いてあるか指定します。**例では、カレントディレクトリのmodulesディレクトリの下にバイナリーファイルがあるので、modulesを相対パスで指定してます。ちなみに、絶対パスでもよいです。

バイナリーファイルの置き場所は、ちょっとわかりにくいですが、modulepathオプションで指定するディレクトリの下に、モジュール名のディレクトリがあって、さらにその下にfilesディレクトリがあって、さらにその下にバイナリーファイルがあるイメージです。
（※**modulesディレクトリの2個下に、filesという名前のディレクトリがあるのが必須**で、それより下のディレクトリ構成は自由です。）

## ファイルをwebから取得する

**Puppet 4.4.0**から、fileリソースで直接HTTP,HTTPS経由のファイル取得ができるようになりました。source属性に、対象のURLを記載します。

```puppet:file08.pp
file { "/opt/pssh/pssh-2.3.1.tar.gz":
  ensure => "file",
  owner  => "root",
  group  => "root",
  mode   => "0644",
  source => "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/parallel-ssh/pssh-2.3.1.tar.gz",
}

```

ただし、直接ダウンロードできない場合（※接続先で、302リダイレクトされるとダメ？）もあるようなので、**うまくいかない場合は、execリソースのwgetコマンドで代用します。**

次の例では、creates属性で配布先にファイルがないことをチェックしてから、commandのwgetコマンドを実行しています。もし、ダウンロードに時間がかかりそうな場合、timeout属性でタイムアウトを十分長い時間に設定しておきます。デフォルト300秒を、例では600秒に変更。timeout属性を0に設定すると、タイムアウトしない設定になります。

```puppet:file09.pp
exec { "wget gitbucket.war":
  command => "wget https://github.com/gitbucket/gitbucket/releases/download/4.0/gitbucket.war -P /opt/gitbucket/4.0",
  creates => "/opt/gitbucket/4.0/gitbucket.war", 
  path    => "/usr/bin",
  timeout => "600",
}

```

## ファイルがすでにある場合変更しない

すでにファイルがあった場合に、ファイルの中身を変更したくないときは、replace属性にfalseを指定します。ファイルがないときだけ、ファイルを作りたい場合に使います。

```puppet:file10.pp
file { '/tmp/hello_puppet.txt':
  ensure  => "file",
  owner   => "root",
  group   => "root",
  mode    => "0644",
  content => "hello puppet\n",
  replace => "false", 
}

```

# 参考
fileリソースの詳しい情報は、下記参照です。

- https://docs.puppet.com/puppet/latest/reference/type.html#file

テンプレートファイルをサーバごとに分岐させている部分は、下記参照です。

- https://docs.puppet.com/puppet/latest/reference/lang_conditional.html