Edited at

Puppetスタイルガイド(翻訳)

More than 3 years have passed since last update.

PuppetLabs - DocsのCreate Commons(CC BY-SA)に基づきPuppetLabs - Style guideを翻訳・公開する。

一部重要そうでない部分については翻訳スキップする。


用語について

RFC2119に従い述語を記載する。

(翻訳者注: MUST以外はいまいち文章的に収まりが悪いので結構変えてしまいました。ごめんなさい。)


Puppetバージョン

このスタイルガイドは2.6系Puppetに特化している。ここでの推奨スタイルは2.6以降のバージョンで採用される予定のものも含んでいる。


スタイルガイドが必要な理由

PuppetLabsが顧客やコミュニティ向けに提供するモジュールのデザインはベストプラクティスに基づく必要がある。モジュールはさまざまなメンバにより開発されるので、本家のリファレンスは一貫したデザインパターンを満たすことが要求される。


マニフェストの哲学

すべての状況に対応できるスタイルガイドの作成は難しい。もし、どのようにすべきか判断がつかない場合は以下の指針に従うと良い。


  • 読みやすさ重視


  • 継承はしないほうが良い


    • 継承は大抵の場合、可読性を落とす。継承が必要なほとんどの場合は、パラメタ化したクラスにすることで避けられる。詳しくはクラスの継承を参照。



  • モジュールはENCと常に併用できなくてはならない



  • クラス定義の中でのクラス宣言はしないほうが良い


    • クラス宣言は極力Nodeスコープとすること。内部で他のクラス宣言するくらいなら、クラスの外側で依存クラス宣言がされていなかったら失敗をするように作るようにしたほうが良い。(`includeでのクラス宣言は複数の箇所にあっても良いことになっているが、そのクラス内のパラメタがどうなるかが予測できなくなる。この問題に根本的解決ができたら方針は変わるかもしれない。現時点では保守的な記述とすべきである。)



(翻訳者注: 哲学の割りにえらい細かい話おおいな。ENCって..。最後のはcontainの登場で既に話が変わってきている?)


モジュールのメタデータ

すべてのモジュールはmetadata.jsonファイルを用意し、メタデータを書かなくてはならない。


メタデータ例

name 'myuser-mymodule'

version '0.0.1'
author 'Author of the module - for shared modules this is Puppet Labs'
summary 'One line description of the module'
description 'Longer description of the module including an example'
license 'The license the module is release under - generally GPLv2 or Apache'
project_page 'The URL where the module source is located'
dependency 'otheruser/othermodule', '>= 1.2.3'

さらに詳しい解説はpuppet-module-tool-READMEを参照。


スタイルのバージョン付け(翻訳スキップ)


スペース、インデント、空白文字

マニフェストは以下のルールを守ること。


  • インデントは半角スペース2個としなくてはならない

node test {

include os
}


  • 制御文字のタブは使っては成らない。

  • 行末に余計なスペースを入れてはならない。

  • 行幅は80文字を超えないほうがよい。

  • Resource定義ブロックの=>は整列したほうがよい。


コメント

コメントスタイルは#this is commentが推奨。

// this is comment/* this is comment */は使わない。


クォート

変数を含まない文字列はシングルクォート(')で極力囲む。変数を含む文字列のみダブルクォート(")で囲む。ただし、シングルクォートを含む文字列を表記する場合に、ダブルクォートを使うのは許容する。なお、クォートは文字列がアルファベットと数字のみかつResource titleでない場合は無くても問題は無い。

文字列の中の変数は{}括弧で囲んだほうが良い。


Good

"/etc/${file}.conf"

"${::operatingsystem} is not supported by ${module_name}"


Bad

"/etc/$file.conf"

"$::operatingsystem is not supported by $module_name"

変数のみの場合はクォートしないほうが良い。


Good

    mode => $my_mode



Bad

    mode => "$my_mode"

mode => "${my_mode}"


Resource


Resourceタイトル

リソースは必ずクォートしたほうが良い。(Puppetはスペースやハイフンを含まないタイトルであれば問題はないが、look-and-feelの統一の観点からもやらない方が良い。)


Good

    package { 'openssh': ensure => present }



Bad

    package { openssh: ensure => present }



アローの整列

Resoureの属性/Value指定のためのアロー(=>)はResourceブロック無いでそろえるほうが良い。

結果的に、最も長いパラメータもしくは属性で位置が決まる。


Good

    exec { 'blah':

path => '/usr/bin',
cwd => '/tmp',
}

exec { 'test':
subscribe => File['/etc/test'],
refreshonly => true,
}



Bad

    exec { 'blah':

path => '/usr/bin',
cwd => '/tmp',
}

exec { 'test':
subscribe => File['/etc/test'],
refreshonly => true,
}



属性値の順番

ensure属性を最初に書く。


Good

    file { '/tmp/readme.txt':

ensure => file,
owner => '0',
group => '0',
mode => '0644',
}

なお、属性の表示順序は単純な読みやすさのためで処理結果への影響は無い。


簡略表記

マニフェスト中のResourceの表記順序は意味的な関係で決定すべき。Resource Type種別でのまとめたりしないこと。ひとつの{}内で複数Resourceを;で繋いで書く方式は大抵読みやすさを損なうのでしないこと。


Good

    file { '/tmp/a':

content => 'a',
}

exec { 'change contents of a':
command => 'sed -i.bak s/a/A/g /tmp/a',
}

file { '/tmp/b':
content => 'b',
}

exec { 'change contents of b':
command => 'sed -i.bak s/b/B/g /tmp/b',
}



Bad

    file {

"/tmp/a":
content => "a";
"/tmp/b":
content => "b";
}

exec {
"change contents of a":
command => "sed -i.bak s/b/B/g /tmp/a";
"change contents of b":
command => "sed -i.bak s/b/B/g /tmp/b";
}



シンボリックリンク

ensure => linktarget属性を使って書く。ensureにリンク先を書く方式は使わない。


Good

    file { '/var/log/syslog':

ensure => link,
target => '/var/log/messages',
}


Bad

    file { '/var/log/syslog':

ensure => '/var/log/messages',
}


ファイルのパーミッション

4桁でのクォート有記述が推奨。


Good

    file { '/var/log/syslog':

ensure => present,
mode => '0644',
}


Bad

    file { '/var/log/syslog':

ensure => present,
mode => 644,
}


Resource Defaults

Resource Defaultsを定義してよい箇所は以下の2つのみ。


  • トップレベルスコープ(in site.pp)

  • 他のクラス宣言をせず、継承もされないクラス

Resource Defaultsが動的スコープで予期せぬ場所から使われるのを防ぐため。


Good

    # /etc/puppetlabs/puppet/manifests/site.pp:

File {
mode => '0644',
owner => 'root',
group => 'root',
}


Bad

    # /etc/puppetlabs/puppet/modules/ssh/manifests/init.pp

File {
mode => '0600',
owner => 'nobody',
group => 'nogroup',
}

class {'ssh::client':
ensure => present,
}



条件分岐


Resource宣言をシンプルに保つ

Resource宣言内に条件分岐を入れない。条件分岐したい場合は、Resource宣言の外側で行う。


Good

    $file_mode = $::operatingsystem ? {

debian => '0007',
redhat => '0776',
fedora => '0007',
}

file { '/tmp/readme.txt':
content => "Hello World\n",
mode => $file_mode,
}



Bad

    file { '/tmp/readme.txt':

mode => $::operatingsystem ? {
debian => '0777',
redhat => '0776',
fedora => '0007',
}
}


Case文とSelectorsのDefault分岐

Case文には default ケースを極力持たせる。defaultケースは想定外の値が入力された場合を考えて、failさせるのが良い。defaultケースで何をしない場合も、それを明確にするためにdefalt: {}を記載する。

Selectorの場合、defaultセレクションはカタログコンパイルを意図的に失敗させたいときのみ記載する。


Good

    case $::operatingsystem {

centos: {
$version = '1.2.3'
}
solaris: {
$version = '3.2.1'
}
default: {
fail("Module ${module_name} is not supported on ${::operatingsystem}")
}
}


Classes


ファイルの分割

すべてのクラスおよびリソースタイプ定義は別ファイルに分けなくてはならない。

    # /etc/puppetlabs/puppet/modules/apache/manifests


# init.pp
class apache { }
# ssl.pp
class apache::ssl { }
# virtual_host.pp
define apache::virtual_host () { }

init.ppにすべてのクラスを書くのもわかり易いといえばわかりやすいが、ファイルを分けたほうがモジュールが構造的に何をやっているかを把握する上では良い。


Classの内部構成

Classは一貫性のあるスタイルで作成すべきである。(以下のリスト中の各項はそれぞれしかるべき場所で実施されることを期待している。)


  1. クラスとパラメータを定義する。

  2. クラスパラメータはすべてチェックし、有効でないものがある場合はカタログコンパイルがfail()するようにする。

  3. defaultパラメータはもっとも一般的なケースの値にする。

  4. (必要であれば)ローカル変数を宣言する。

  5. (必要であれば)他のクラスとのClass['apache'] -> Class['local_yum']関係を記述する。(翻訳者感想: クラス中で他のクラスへの順序関係を書くようにすると再利用が面倒で仕方ないイメージがあるのだが。)

  6. (必要であれば) リソース宣言のオーバーライドをする。(翻訳者感想: リソース宣言のオーバーライドは構成管理コードの読みやすさを悪化させるので使わないほうが良いと思うのだが。)

  7. (必要であれば) Resource Defaultsを宣言する。

  8. (必要であれば) Resourceを宣言する。(カスタムResourceなどがある場合はこのまえに有効になるようにしておくこと。)

  9. (必要であれば) 条件に依存するようなResourceの関係を記述する。

上記にしたがったClassの推奨スタイルは以下。

class myservice($ensure='running') {

if $ensure in [ running, stopped ] {
$_ensure = $ensure
} else {
fail('ensure parameter must be running or stopped')
}

case $::operatingsystem {
centos: {
$package_list = 'openssh-server'
}
solaris: {
$package_list = [ SUNWsshr, SUNWsshu ]
}
default: {
fail("Module ${module_name} does not support ${::operatingsystem}")
}
}

$variable = 'something'

Package { ensure => present, }

File { owner => '0', group => '0', mode => '0644' }

package { $package_list: }

file { "/tmp/${variable}":
ensure => present,
}

service { 'myservice':
ensure => $_ensure,
hasstatus => true,
}
}

(翻訳者感想: 基本モジュールにclass宣言は追い出すのが現在のPuppetのベストプラクティスとなっている以上、あまりこのような構成が良いとは思えない..。)


順序関係の宣言

Chain Arrowを使う場合は矢印は常に右向きが推奨。


Good

Package['httpd'] -> Service['httpd']



Bad

Service['httpd'] <- Package['httpd']


可能であれば、順序関係はメタパラメータのrequireやbeforeを使って記述するのが推奨。

子クラスでresourceの挙動を変えたい場合は条件分岐にあらかじめ親クラスのメタパラメータ部分を設計しておく。

(翻訳者感想:矢印の向きはともかく無理やりResourceの中に順序関係の記述を押し込むのも微妙に思えるが。)


Classの中のClassやDefined Typeの宣言

ClassやDefined Typeの宣言は他Classの中ではしない。


Bad

      class ssl { ... }

}


Also_bad

    class apache {

define config() { ... }
}


Classの継承

Classの継承は同一モジュール内でのみ行うのが推奨。これは再利用性のため。他モジュールクラスとの順序関係定義やinclude等で何とかすることが推奨。


Good

    class ssh { ... }

class ssh::client inherits ssh { ... }

class ssh::server inherits ssh { ... }

class ssh::server::solaris inherits ssh::server { ... }



Bad

    class ssh inherits server { ... }

class ssh::client inherits workstation { ... }

class wordpress inherits apache { ... }


継承は一般的に他の方法があるのであれば避けるべき。たとえば、サービスを停止する挙動を変えたいのであれば継承を使うのではなく、ensure パラメータと条件分岐を使って内部挙動を変えるほうが望ましい。

 class bluetooth($ensure=present, $autoupgrade=false) {

# Validate class parameter inputs. (Fail early and fail hard)

if ! ($ensure in [ "present", "absent" ]) {
fail("bluetooth ensure parameter must be absent or present")
}

if ! ($autoupgrade in [ true, false ]) {
fail("bluetooth autoupgrade parameter must be true or false")
}

# Set local variables based on the desired state

if $ensure == "present" {
$service_enable = true
$service_ensure = running
if $autoupgrade == true {
$package_ensure = latest
} else {
$package_ensure = present
}
} else {
$service_enable = false
$service_ensure = stopped
$package_ensure = absent
}

# Declare resources without any relationships in this section

package { [ "bluez-libs", "bluez-utils"]:
ensure => $package_ensure,
}

service { hidd:
enable => $service_enable,
ensure => $service_ensure,
status => "source /etc/init.d/functions; status hidd",
hasstatus => true,
hasrestart => true,
}

# Finally, declare relations based on desired behavior

if $ensure == "present" {
Package["bluez-libs"] -> Package["bluez-utils"]
Package["bluez-libs"] ~> Service[hidd]
Package["bluez-utils"] ~> Service[hidd]
} else {
Service["hidd"] -> Package["bluez-utils"]
Package["bluez-utils"] -> Package["bluez-libs"]
}
}

(この例はPuppet Masterトレーニングコースのbluetoothの管理の例の抜粋。)

まとめると、


  • Class継承はResourceの属性をオーバライドしたい場合のみ有用。それ以外の場合は別の方策を考えるべき。

  • もし、関係性メタパラメータ(requireやbefore)を書き換えたいのであれば、条件分岐とクラスパラメータなどを使って制御したほうが良い。

  • 大体の場合、継承を使わなくてもensureやenable属性をつかって挙動を変更できる。


変数のスコープ

facter変数(facts)を含むトップスコープ変数を使うとき、Puppetモジュールは明示的に意図しない動的名前解決で他の変数を使わないように::を先頭につけるべきである。


Good

    $::operatingsystem



Bad

    $operatingsystem



変数のフォーマット

変数名は文字、数字、アンダースコア_のみを使い、特に-は使わないようにすべし。


Good

    $foo_bar123



Bad

    $foo-bar123



Classパラメータの記載順序

ParameterizedクラスやDefined Type Resourceの宣言時や定義時、 必須パラメータはオプショナルなものの前に記載する。


Good

    class ntp (

$servers,
$options = "iburst",
$multicast = false
) {}


Bad

    class ntp (

$options = "iburst",
$servers,
$multicast = false
) {}


ClassパラメータのDefault

Classパラメータをとるモジュールを作る際、default値はオプショナルなパラメータに対しては設定すべきである。

例えば、以下。

 class ntp(

$server = 'UNSET'
) {

include ntp::params

$_server = $server ? {
'UNSET' => $::ntp::params::server,
default => $server,
}

notify { 'ntp':
message => "server=[$_server]",
}

}

理由はpuppet2.6.x以前への後方互換性のため。

以下の書き方は2.6.2以前ではうまく動かない。

class ntp(

$server = $ntp::params::server
) inherits ntp::params {

notify { 'ntp':
message => "server=[$server]",
}

}

これ以外の「すべし」は以下。



  • _から始まる変数を完全なローカルな変数にする。

  • モジュールのparams Classから値を取得する場合はフルパス表記で取得する。(確実にparams Classから取得するため。)

  • モジュールのユーザがモジュールのために??しないようにparams Classを設計する。

上記の「すべし」はPuppet 2.7以上では遵守しなくても良いし、puppet 2.6以前への互換性をそこまで意識する必要もなくなってくるだろう。

2.7と2.6のdiffを表示すると以下となる。(-が2.7、+が2.6)

diff --git a/manifests/init.pp b/manifests/init.pp

index c16c3a0..7923ccb 100644
--- a/manifests/init.pp
+++ b/manifests/init.pp
@@ -12,9 +12,14 @@
#
class paramstest (
$mandatory,
- $param = $paramstest::params::param
-) inherits paramstest::params {
+ $param = 'UNSET'
+) {
+ include paramstest::params
+ $\_param = $param ? {
+ 'UNSET' => $::paramstest::params::param,
+ default => $param,
+ }
notify { 'TEST':
- message => " param=[$param] mandatory=[$mandatory]",
+ message => " param=[$\_param] mandatory=[$mandatory]",
}
}

(翻訳者注: もはや2.6は過去の遺物なので2.7以降の記法に沿ったほうが良い。)


テスト

すべてのマニフェストは対応するテストマニフェストをtestsディレクトリに持つようにする。

modulepath/apache/manifests/{init,ssl}.pp

modulepath/apache/tests/{init,ssl}.pp

テストマニフェストにはClassやDefined Typeの使い方(宣言の仕方)の例を書く。加えて、puppet applyコマンドでこのマニフェストが動くように他のClassの宣言を書く。


Puppet Doc

ClassやDefined TypeはRDoc markupを使ってインラインでドキュメント化されるべきである。puppet docコマンドでドキュメントを参照できるようにするためにもドキュメントはぜひとも用意されたい。


Classドキュメントの例

  # == Class: example_class

#
# Full description of class example_class here.
#
# === Parameters
#
# Document parameters here.
#
# [*ntp_servers*]
# Explanation of what this parameter affects and what it defaults to.
# e.g. "Specify one or more upstream ntp servers as an array."
#
# === Variables
#
# Here you should define a list of variables that this module would require.
#
# [*enc_ntp_servers*]
# Explanation of how this variable affects the funtion of this class and if it
# has a default. e.g. "The parameter enc_ntp_servers must be set by the
# External Node Classifier as a comma separated list of hostnames." (Note,
# global variables should not be used in preference to class parameters as of
# Puppet 2.6.)
#
# === Examples
#
# class { 'example_class':
# ntp_servers => [ 'pool.ntp.org', 'ntp.local.company.com' ]
# }
#
# === Authors
#
# Author Name <author@example.com>
#
# === Copyright
#
# Copyright 2011 Your name here, unless otherwise noted.
#
class example_class {

}



DefinedTypeのドキュメント例

    # == Define: example_resource

#
# Full description of defined resource type example_resource here.
#
# === Parameters
#
# Document parameters here
#
# [*namevar*]
# If there is a parameter that defaults to the value of the title string
# when not explicitly set, you must always say so. This parameter can be
# referred to as a "namevar," since it's functionally equivalent to the
# namevar of a core resource type.
#
# [*basedir*]
# Description of this variable. For example, "This parameter sets the
# base directory for this resource type. It should not contain a trailing
# slash."
#
# === Examples
#
# Provide some examples on how to use this type:
#
# example_class::example_resource { 'namevar':
# basedir => '/tmp/src',
# }
#
# === Authors
#
# Author Name <author@example.com>
#
# === Copyright
#
# Copyright 2011 Your name here, unless otherwise noted.
#
define example_class::example_resource($basedir) {

このようにしておくとpuppet doc toolでのドキュメント化が容易である。