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でない場合は無くても問題は無い。
文字列の中の変数は{}括弧で囲んだほうが良い。
"/etc/${file}.conf"
"${::operatingsystem} is not supported by ${module_name}"
"/etc/$file.conf"
"$::operatingsystem is not supported by $module_name"
変数のみの場合はクォートしないほうが良い。
mode => $my_mode
mode => "$my_mode"
mode => "${my_mode}"
Resource
Resourceタイトル
リソースは必ずクォートしたほうが良い。(Puppetはスペースやハイフンを含まないタイトルであれば問題はないが、look-and-feelの統一の観点からもやらない方が良い。)
package { 'openssh': ensure => present }
package { openssh: ensure => present }
アローの整列
Resoureの属性/Value指定のためのアロー(=>
)はResourceブロック無いでそろえるほうが良い。
結果的に、最も長いパラメータもしくは属性で位置が決まる。
exec { 'blah':
path => '/usr/bin',
cwd => '/tmp',
}
exec { 'test':
subscribe => File['/etc/test'],
refreshonly => true,
}
exec { 'blah':
path => '/usr/bin',
cwd => '/tmp',
}
exec { 'test':
subscribe => File['/etc/test'],
refreshonly => true,
}
属性値の順番
ensure
属性を最初に書く。
file { '/tmp/readme.txt':
ensure => file,
owner => '0',
group => '0',
mode => '0644',
}
なお、属性の表示順序は単純な読みやすさのためで処理結果への影響は無い。
簡略表記
マニフェスト中のResourceの表記順序は意味的な関係で決定すべき。Resource Type種別でのまとめたりしないこと。ひとつの{}内で複数Resourceを;
で繋いで書く方式は大抵読みやすさを損なうのでしないこと。
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',
}
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 => link
とtarget
属性を使って書く。ensure
にリンク先を書く方式は使わない。
file { '/var/log/syslog':
ensure => link,
target => '/var/log/messages',
}
file { '/var/log/syslog':
ensure => '/var/log/messages',
}
ファイルのパーミッション
4桁でのクォート有記述が推奨。
file { '/var/log/syslog':
ensure => present,
mode => '0644',
}
file { '/var/log/syslog':
ensure => present,
mode => 644,
}
Resource Defaults
Resource Defaultsを定義してよい箇所は以下の2つのみ。
- トップレベルスコープ(in site.pp)
- 他のクラス宣言をせず、継承もされないクラス
Resource Defaultsが動的スコープで予期せぬ場所から使われるのを防ぐため。
# /etc/puppetlabs/puppet/manifests/site.pp:
File {
mode => '0644',
owner => 'root',
group => 'root',
}
# /etc/puppetlabs/puppet/modules/ssh/manifests/init.pp
File {
mode => '0600',
owner => 'nobody',
group => 'nogroup',
}
class {'ssh::client':
ensure => present,
}
条件分岐
Resource宣言をシンプルに保つ
Resource宣言内に条件分岐を入れない。条件分岐したい場合は、Resource宣言の外側で行う。
$file_mode = $::operatingsystem ? {
debian => '0007',
redhat => '0776',
fedora => '0007',
}
file { '/tmp/readme.txt':
content => "Hello World\n",
mode => $file_mode,
}
file { '/tmp/readme.txt':
mode => $::operatingsystem ? {
debian => '0777',
redhat => '0776',
fedora => '0007',
}
}
Case文とSelectorsのDefault分岐
Case文には default ケースを極力持たせる。defaultケースは想定外の値が入力された場合を考えて、failさせるのが良い。defaultケースで何をしない場合も、それを明確にするためにdefalt: {}
を記載する。
Selectorの場合、defaultセレクションはカタログコンパイルを意図的に失敗させたいときのみ記載する。
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は一貫性のあるスタイルで作成すべきである。(以下のリスト中の各項はそれぞれしかるべき場所で実施されることを期待している。)
- クラスとパラメータを定義する。
- クラスパラメータはすべてチェックし、有効でないものがある場合はカタログコンパイルがfail()するようにする。
- defaultパラメータはもっとも一般的なケースの値にする。
- (必要であれば)ローカル変数を宣言する。
- (必要であれば)他のクラスとの
Class['apache'] -> Class['local_yum']
関係を記述する。(翻訳者感想: クラス中で他のクラスへの順序関係を書くようにすると再利用が面倒で仕方ないイメージがあるのだが。) - (必要であれば) リソース宣言のオーバーライドをする。(翻訳者感想: リソース宣言のオーバーライドは構成管理コードの読みやすさを悪化させるので使わないほうが良いと思うのだが。)
- (必要であれば) Resource Defaultsを宣言する。
- (必要であれば) Resourceを宣言する。(カスタムResourceなどがある場合はこのまえに有効になるようにしておくこと。)
- (必要であれば) 条件に依存するような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を使う場合は矢印は常に右向きが推奨。
Package['httpd'] -> Service['httpd']
Service['httpd'] <- Package['httpd']
可能であれば、順序関係はメタパラメータのrequireやbeforeを使って記述するのが推奨。
子クラスでresourceの挙動を変えたい場合は条件分岐にあらかじめ親クラスのメタパラメータ部分を設計しておく。
(翻訳者感想:矢印の向きはともかく無理やりResourceの中に順序関係の記述を押し込むのも微妙に思えるが。)
Classの中のClassやDefined Typeの宣言
ClassやDefined Typeの宣言は他Classの中ではしない。
class ssl { ... }
}
class apache {
define config() { ... }
}
Classの継承
Classの継承は同一モジュール内でのみ行うのが推奨。これは再利用性のため。他モジュールクラスとの順序関係定義やinclude等で何とかすることが推奨。
class ssh { ... }
class ssh::client inherits ssh { ... }
class ssh::server inherits ssh { ... }
class ssh::server::solaris inherits ssh::server { ... }
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モジュールは明示的に意図しない動的名前解決で他の変数を使わないように::
を先頭につけるべきである。
$::operatingsystem
$operatingsystem
変数のフォーマット
変数名は文字、数字、アンダースコア_
のみを使い、特に-
は使わないようにすべし。
$foo_bar123
$foo-bar123
Classパラメータの記載順序
ParameterizedクラスやDefined Type Resourceの宣言時や定義時、 必須パラメータはオプショナルなものの前に記載する。
class ntp (
$servers,
$options = "iburst",
$multicast = false
) {}
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: 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 {
}
# == 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でのドキュメント化が容易である。