Version 1.1.2
キーワード “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, ”MAY”, and “OPTIONAL”は、本書ではRFC2119に記載されるように解釈されるべきである。
このスタイルガイドは、バージョン2.6.xが対象です。バージョン2.6.0以降で利用できるようになったいくつかの機能に基づいています。
Puppet Labsは、お客様や地域社会のためのモジュールを開発しています。これらのモジュールは、モジュールのデザインとスタイルのためのベスト・プラクティスであるべきです。 これらのモジュールは組織を超えて多くの人々によって開発されているので一貫したパターン、デザイン・スタイルを確保する基準が必要でした。
スタイルマニュアルは全ての状況はカバーできません。判断が必要なときには下記一般的な考え方に留意してください。
- 可読性
もしあなたが効果的に等しい2つの案で選ぶ必要が有る場合はより読みやすい方を選択しましょう。これは、主観的ですが3月後に自分のコードを理解する事ができれば、それは素晴らしい事でしょう
- 継承は避けるべき
一般的に、継承は読むのが大変なコードにつながる。 継承のほとんどのユースケースは、リソース属性を構成するために使用できるクラスパラメータを抽出することによって置き換えることができる。 詳細は、クラスの継承のセクションを参照してください。
- モジュールには、1を必要とせずに、ENCと協力しなければならない
内部調査では、ENCは要求されるべきではないというコンセンサスの近くに生じた。 同時に、我々が書くすべてのモジュールは、ENCとうまく動作するはずです。
- 一般的にクラスは他のクラスを宣言するべきではありません
できるだけノードスコープに閉じたクラスを宣言します。 他のクラスを必要とするクラスは、それらを直接宣言するべきではありませんし、他の手段で宣言されていない場合、エラーが発生するようにする必要があります。 (インクルード機能は、複数クラスの宣言が許可されていますがそれは親スコープによる非決定スコープがアサインされた事によりもたらされた結果です。今のこと宣言に関しては保守的ですが、今後、クラスのマルチ宣言は決定することができれば、この理念を再検討可能性があります。)
すべてのモジュールは、メタデータはModulefileデータファイルで定義され、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'
Modulefile形式のより完全なガイドでは、puppet-module-toolのREADMEに記載されています。
このスタイルガイドはバージョン管理されるため、モジュールはスタイルガイドの特定のバージョンに準拠できるようになります。将来、puppet-module toolはmodulefileの中にメタデータを埋め込み関連するスタイルガイドのバージョンを可能となる。また、メタデータの自動チェックを使用する事ができるようになる。
このスタイルガイドに準拠したモジュールマニフェストは以下の通り
- 2つのスペースのソフトタブを使用する
- リテラルにタブ文字を使用しない
- 末尾の空白は含めない
- 1行は80文字を超えないようにすべき
- attributesのブロックの中にコンマ矢印(=>)を揃えるべき
puppetの言語は複数のコメントの種類が許可されていますが、シャープ記号(#)を推奨します。 なぜなら一般的に最もテストエディタやコードの字句解析でわかりやすいからです。
- 「 # ... 」をコメントとして使うべき
- 「// ... 」や「 /* ... */」をコメントとして使うべきでない
変数が含まれていないすべての文字列はシングルクオートで囲む必要があります。変数の展開が必要とされる場合、ダブルクオートを使用する必要があります。シングルクオートが含まれている場合、ダブルクオートは文字列を読みやすくするために用いることができる。文字列が英数字や単語リソースのタイトルで無い場合はクオートはオブションです。
文字列で補間する時、すべての変数は括弧で囲む必要があります。
良い例
"/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}"
すべてのリソースのタイトルは引用符で囲む必要があります。 (puppetはスペースはハイフンが含まれていない場合は引用符で囲まれていないリソースタイトルをサポートしています。しかし一貫した見た目をまもるため避ける必要があります。)
良い例
package { 'openssh': ensure => present }
悪い例
package { openssh: ensure => present }
リソースの属性/値リストの全てのカンマ矢印(=>)が揃っている必要があります。 矢印は、最も長い属性名の前に1スペース配置してください
良い例
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',
}
(この推奨事項は、読みやすさのためだけにあり、puppetはリソースを同期する際の属性の順序を無視する)
マニュフェストの中でリソースタイプでなく論理的な関係によりリソースをグループ化すべきである。中括弧の中で複数リソースを宣言するためのセミコロンの使用は、読みやすさが改善するまれなケースを除いて推奨されません。
良い例
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',
}
ファイルモードは、3桁でなく4桁の数字として表現されるべきである。 さらに、ファイルのモードは数値のかわりにシングルクオートで囲まれた文字列として指定する必要があります。
良い例
file { '/var/log/syslog':
ensure => present,
mode => '0644',
}
悪い例
file { '/var/log/syslog':
ensure => present,
mode => 644,
}
リソースのデフォルトは、制御された方法で使用されるべきである マニュフェストのエコシステムの先頭でのみ宣言される必要があります。具体的には次の通りです。
- site.ppのトップスコープ
- 他のクラスによって継承されることがなく、別のクラスで宣言することもないクラス。
リソースのデフォルトの伝搬がダイナミックスコープを介して、デフォルトが宣言された場所から遠いところで予測できない影響を与える事に起因している。
良い例
# /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,
}
リソース宣言で条件文を混在しないでください。 データ割り当てのための条件文を使用するときは、リソース宣言から条件付きコードを分離する必要があります。
良い例
$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ステートメントは、デフォルトのケースを持っている必要があります。 また、プラットフォームの予測できない場合にモジュールがで使用されたデフォルトケースでは カタログのコンパイルに失敗する必要があります もしデフォルトケースで何もしたくない場合、明確化のため「default: {}」を明示する
明示的に値が一致しないときにfailし、コンパイルをカタログするセレクタの場合、デフォルトの選択は省略されるべきである
次の例では、推奨スタイルに従っています。
case $::operatingsystem {
centos: {
$version = '1.2.3'
}
solaris: {
$version = '3.2.1'
}
default: {
fail("Module ${module_name} is not supported on ${::operatingsystem}")
}
}
すべてのクラスとリソースタイプの定義は、そのモジュールのマニフェストディレクトリ内の別々のファイルである必要があります。
例:
# /etc/puppetlabs/puppet/modules/apache/manifests
# init.pp
class apache { }
# ssl.pp
class apache::ssl { }
# virtual_host.pp
define apache::virtual_host () { }
これはinit.pp内ですべてのクラスと定義を宣言する場合と機能的に同じですが、モジュールの構造を強調し、すべてが読みやすくなります。
クラスは、一貫性のある構造とスタイルで構成される必要があります。 下のリストにこれらの項目のそれぞれについて、「こうであるべき」という暗黙の声明があります。 「may」は「Xの場合は、こうあるべき」と解釈されるべきである
- クラスとパラメータを定義すべき
- 任意のクラスのパラメータを検証し、パラメータが無効な場合のカタログのコンパイルに失敗すべき
- 最も一般的な検証されたパラメータをデフォルトとすべき
- ローカル変数を宣言する(may)
- 「Class['apache'] -> Class['local_yum']」で他のクラスとの関係を宣言する(may)
- リソースをオーバーライドする(may)
- リソースのデフォルトを宣言する(may)
- カスタムタイプは、コアタイプの前に定義し、リソース宣言する(may)
- 条件文の内側にリソース関係を宣言(may)
次の例では、推奨スタイルに従っています。
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,
}
}
チェーン構文とのリレーションの宣言は「左から右」方向に使用されるべきである。
良い例
Package['httpd'] -> Service['httpd']
悪い例
Service['httpd'] <- Package['httpd']
可能な場合には、リレーションシップ宣言にmetaparametersを選ぶべきです。 サブクラス化するときは、動作をオーバーライドすることが必要であり、metaparametersは望ましくない一例である。 この状況では、条件文の内側に関係の宣言を使用する必要があります。
クラスと定義されたリソースタイプは、他のクラス内で定義されてはならない。
悪い例
class apache {
class ssl { ... }
}
悪い例
class apache {
define config() { ... }
}
継承は、モジュール内で使用することができるが、モジュールの名前空間を超えて使用することはできません。モジュール間の依存関係がモジュール方式の概念に違反していない、ステートメントやリレーションの宣言等含まれた移植性のある形で満たされる必要があります
良い例
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 { ... }
一般的に代替案が実行可能であるとき継承は避けるべきである。 たとえば、サービスを停止する際に既存のクラスの関係をオーバライドするのに継承を使用する代わりに、 パラメータと条件式の宣言と単一のクラスを使って考えてみましょう。
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のトレーニングに提供された例に基づいています.)
要約:
- クラスの継承は、リソースの属性をオーバーライドするためにのみ有用である。他のユースケースは他のより良い方法で実施される
- metaparametersの関係をオーバーライドする必要がある場合は、 継承のかわりに条件式の宣言を持つ単一のクラスを使用すべき
- 多くの場合、さらには他の属性(例えばensure、enable)の挙動は変数と条件ロジックを利用して切り替えるべきです
トップスコープ変数を使用する場合、パペット·モジュールは、明示的、偶発的スコープの問題を防ぐために、空の名前空間を指定する必要があります。
良い例
$::operatingsystem
悪い例
$operatingsystem
変数を定義するときには、文字、数字、アンダースコアを使用する必要があります。特にダッシュは利用するべきではありません。
良い例
$foo_bar123
悪い例
$foo-bar123
パラメータ化されたクラスと定義されているリソースタイプの宣言では、指定が必須なパラメータはオプションのパラメータ(デフォルトパラメータ)の前に表示される必要があります。
良い例
class ntp (
$servers,
$options = "iburst",
$multicast = false
) {}
悪い例
class ntp (
$options = "iburst",
$servers,
$multicast = false
) {}
クラスのパラメータを受け入れるモジュールを記述する場合、デフォルト値はクラスの宣言時にパラメータでオプション指定する手段が提供されるべき
例:
class ntp(
$server = 'UNSET'
) {
include ntp::params
$_server = $server ? {
'UNSET' => $::ntp::params::server,
default => $server,
}
notify { 'ntp':
message => "server=[$_server]",
}
}
クラスがこのように宣言されている理由は、Puppet の2.6.xバージョンと完全に互換性を保つためです。以下の方法はPuppet 2.6.2およびそれ以前と互換性がないため、使用しないべき。
class ntp(
$server = $ntp::params::server
) inherits ntp::params {
notify { 'ntp':
message => "server=[$server]",
}
}
他に留意すべき事:
- メンテナンス性のためローカルスコープの変数は「_ 」プレフィックスを使用すべきである
- 名前空間の衝突を避けるために、モジュールのparamsクラスの値を引いたときに、完全修飾名前空間の変数を使用すべき
- エンドユーザーはモジュールが正しく機能するためにする必要はありませんので、paramsのクラスを宣言すべき
puppet2.7がより広く採用されて、2.6.Xのような多くのバージョンとモジュールの互換性が最大の関心事でなくなった場合はこの推奨されるパターンが緩和されることがある
この差分は、2つの一般的に使用されるパターンがどのように他への切り替えるかの変更を示している。
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]",
}
}
すべてのマニフェストは、testsディレクトリ内に対応するテストマニュフェストを持っている必要があります。
modulepath/apache/manifests/{init,ssl}.pp
modulepath/apache/tests/{init,ssl}.pp
テストマニフェストは、クラスまたは定義されたリソースタイプを宣言する方法の明確な例を提供する必要があります。また、テストマニフェストはスタンドアロンの制限された環境で動作を確認するため必要なクラスを宣言する必要があります。
クラスと定義されているリソースタイプはRDocのマークアップを使用してインラインで文書化する必要があります。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ツールでドキュメントが抽出できるようになります。