Puppet文档:语言指南

原文地址:Language Guide

Puppet语言通过资源描述的方式管理我们的机器,它让这一切工作都变得简单而有效。本指南展示了Puppet语言是如何工作的,以及Puppet语言的一些基础概念。学习Puppet语言非常重要,它是帮助你理解Puppet如何管理你的机器的关键。

Puppet语言相比其它编程语言而言是相当简单的。阅读本指南,也可以帮助你了解大量其它人已经写好的Puppet模块。Modules页提供了关于模块的更多信息和链接。

名称中可接受的字符集

变量名只能够包含字母数字和下划线,大小写敏感。连字符是不允许的,有些Puppet版本允许它们,这是一个Bug。

类名,模块名,the names of defined,和自定义的资源类型被限制为只能使用小写字母,数字和下划线,且必须以小写字母开头,也就是必须匹配表达式[a-z][a-z0-9_]*。有些名称违反了这些限制,但是目前能够正常工作,这是不建议的。连字符是严重反对使用的,在新版本里面它会让类里面的变量不能够正常工作。

类和资源类型定义可以使用命名空间分割符(::)。Class and defined resource type names can use :: as a namespace separator, which is both semantically useful and a means of directing the behavior of the module autoloader. The final segment of a qualified variable name must obey the restrictions on variable names, and the preceding segments must obey the restrictions on class names.

Parameters used in parameterized classes and defined resource types can include alphanumeric characters and underscores, cannot begin with an underscore, and are case-sensitive. In practice, they should be treated as though they were under the same restrictions as class names in order to maximize future compatibility.

There is no practical restriction on resource names.

Any word that the syntax uses for special meaning is a reserved word, meaning you cannot use it for variable or type names. Words like truedefineinherits, and class are all reserved. If you ever need to use a reserved word as a value, be sure to quote it.

资源

Puppet的基础是资源。资源描述了系统的多个方面,例如一个文件,一个服务,一个包,甚至是一个自定义的资源。我们稍后会降到如何使用"defines"和"classes"将资源聚合在一起,如何使用"moudles"将"defines"和“classes”再组合在以前。但现在我们一切从资源开始。

每个资源都有一个类型、标题和其它很多的属性 - Puppet的资源能够支持各种各样的属性,它们中的很多都有对应的默认值,所以不用担心要为每一个都指定值。

可以在"References"找到Puppet支持的所有资源类型,它们的可用属性,以及很多的文档。

下面是一个简单的资源使用例子,定义了一个文件的权限和所有者:

file { '/etc/passwd':
    owner => 'root',
    group => 'root',
    mode  => '0644',
}

这段代码在所有的机器上都能够执行,可以用它检查密码文件是否配置正确。

冒号前面的内容是资源的标题,必须是唯一的,能够被其它的资源引用。标题后面是一系列的属性和它们的值。

绝大多数的资源都有这样一个属性(经常被定义为name),当没有为它指定值时默认与资源的标题相同。在Puppet内部,这被称之为"namevar"。例如file资源,path属性默认与标题相同。资源的namevar几乎都是唯一的,只有exec资源和notify资源是例外。

对简单资源来说,不用明确地指定name或path属性,让Puppet自动设置成同title属性,这样就足够了。但是有些拥有很长名称的资源,或者在不同的操作系统拥有不同的文件名,这时候通常会使用更容易理解的符号标题:

file { 'sshdconfig':
    path => $operatingsystem ? {
        solaris => '/usr/local/etc/ssh/sshd_config',
        default => '/etc/ssh/sshd_config',
    },
    owner => 'root',
    group => 'root',
    mode  => '0644',
}

使用sshdconfig作为标题,使得该file资源更容易被其它资源应用,因为无论是何种操作系统,它的标题都是相同的。例如,我们增加一个依赖该file资源的服务:

service { 'sshd':
    subscribe => File['sshdconfig'],
}

这会使得sshd服务在sshdconfig被改变时重新启动。注意,当我们引用一个资源时,需要大写资源类型的首字母,例如File[sshdconfig]。当看到一个大写的资源类型,需要明白那实际上就是一个资源引用。小写字母是用来定义的。资源只能被定义一次,重复定义相同的资源会导致错误。这是Puppet非常重要的特性,使得能够按照模块化非常好的组织配置信息。

 

一个资源能够依赖多个资源吗?从Puppet 0.24.6开始可以安装下面的方式指定多个依赖:

service { 'sshd':
    require => File['sshdconfig', 'sshconfig', 'authorized_keys']
}

元参数

Puppet提供了一些被称为"元参数(metaparameters)"的全局属性。元参数能够象其它属性一样使用,所有的资源类型都支持元参数。

在上面的例子中就使用了两个元参数:subscribe和require,都是用来定义资源之间关系的。可以在“Metaparameter Reference”所有元参数的列表。

资源默认值

有时候需要为某类资源指定一个默认的参数。Puppet提供实现这样功能的语法,使用没有标题的大写资源首字母方式。例如,为所有的执行命令设置默认的path参数:

Exec { path => '/usr/bin:/bin:/usr/sbin:/sbin' }
exec { 'echo this works': }

第一行代码为exec资源提供一个默认的path参数。exec资源需要一个完整的限定路径或者能够通过path参数找到具体的可执行文件。资源定义时若有必要可以覆盖path的默认设置。这种情况下我们可以为所有的配置指定一个默认path参数,特殊情况时覆盖它就可以了。

Puppet默认值支持所有的资源类型。

默认值不是全局的 - 它只在当前范围和当前范围下面有效。如果你想为所有的配置设置默认值,唯一选择是在任意类的外面定义它们。

Resource Collections

Aggregation is a powerful concept in Puppet. There are two ways to combine multiple resources into one easier to use resource: Classes and defined resource types. Classes model fundamental aspects of nodes, they say “this node IS a webserver” or “this node is one of these”. In programming terminology classes are singletons — they only ever get evaluated once per node.

Defined resource types, on the other hand, can be reused many times on the same node. They essentially work as if you created your own Puppet type just by using the language. They are meant to be evaluated multiple times, with different inputs each time. This means you can pass variable values into the defines.

Both classes and defines are very useful and you should make use of them when building out your puppet infrastructure.

类定义以class关键字开始,内容放在花括号里面。下面是一个管理两个文件的简单类例子:

class unix {
    file {
        '/etc/passwd':
            owner => 'root',
            group => 'root',
            mode  => '0644';
        '/etc/shadow':
            owner => 'root',
            group => 'root',
            mode  => '0440';
    }
}

这里使用了一种简便写法。下面的例子是相同的:

class unix {
    file { '/etc/passwd':
        owner => 'root',
        group => 'root',
        mode  => '0644',
    }
    file { '/etc/shadow':
        owner => 'root',
        group => 'root',
        mode  => '0440',
    }
}
 
Puppet类支持简单的对象继承,因而可以扩展父类的功能而不需要将将父类全部复制粘贴。继承也允许子类覆盖父类的资源定义。Puppet类只能够从单一父类继承,不能够从多个父类继承,这被称为"单继承"。
class freebsd inherits unix {
    File['/etc/passwd'] { group => 'wheel' }
    File['/etc/shadow'] { group => 'wheel' }
}

如果子类需要取消父类的某些设置,可以这样处理:

class freebsd inherits unix {
    File['/etc/passwd'] { group => undef }
}

执行时,代理节点会包含/etc/shadow文件,群组属性变为wheel;同时也会包含/etc/passwd文件,群组属性保持不变。

在Puppet 0.24或更高版本,可以使用多重覆盖:
class freebsd inherits unix {
    File['/etc/passwd', '/etc/shadow'] { group => 'wheel' }
}

在Puppet 0.23.1或更高版本,可以使用‘+>’ (‘plusignment’) 操作符为资源参数添加值。

class apache {
    service { 'apache': require => Package['httpd'] }
}

class apache-ssl inherits apache {
    Service['apache'] { require +> File['apache.pem'] }
}

这个例子使得apache服务除了需要httpd包外,还需要apache.pem文件。

为了增加多重需求,还可以使用数组方式:

class apache {
    service { 'apache': require => Package['httpd'] }
}

class apache-ssl inherits apache {
    Service['apache'] { require +> [ File['apache.pem'], File['/etc/httpd/conf/httpd.conf'] ] }
}

这个例子中require参数等同于:

[Package['httpd'], File['apache.pem'], File['/etc/httpd/conf/httpd.conf']]

同资源一样,类与类之间也可以使用'require'参数建立关联:

class apache {
    service { 'apache': require => Class['squid'] }
}

这个例子使用require参数使得apache类依赖于squid类。

在Puppet 0.24.6或更高版本,可以像下面一样指定多重关联:

class apache {
    service { 'apache':
        require => Class['squid', 'xml', 'jakarta'],
    }
}

require不会自动地定义类,也就是说它可以使用多次,且兼容参数类,但是必须确保require中的类已经定义过。

 
Puppet还有一个require函数,在类定义时使用,可以自动创建类,使用方式同include函数。require函数与参数类一起使用时不是工作得很好。require函数大部分情况下是不需要的,类与类之间的依赖都可以用另外的方式实现。
 
 

参数类

在Puppet 2.6.0或更高版本,允许传递参数给类定义。
 
 
创建一个带参数的类:
class apache($version) {
    ... class contents ...
}
参数类不可以使用include函数创建,但可以使用替代语法:
node webserver {
    class { 'apache': version => '1.3.13' }
}
也可以为参数设置默认值:
class apache($version = '1.3.13', $home = '/var/www') {
    ... class contents ...
}

运行阶段

运行阶段功能是从Puppet 2.6.0被加入的。运行阶段为我们提供了另外一种控制Puppet资源执行顺序的方法。如果有大量的资源要执行,而且它们之间的执行顺序很重要,这将是一个很头痛的问题,尤其是明确地管理资源之间的关系。这种情况下,运行阶段让我们可以将一个类关联到某个运行阶段。Puppet确保了所有的运行阶段都能够以一个预期的顺序执行。

为了使用运行阶段功能,除了预先定义的主阶段外,其它的阶段都必须先定义才能使用。我们可以使用与定义资源关系相同的语法(例如 “->”和“<-“)为运行阶段指定执行顺序。运行阶段质之间关系确保了与之关联的类的执行顺序。

Puppet预定义了一个名为"main"的运行阶段,所有的类默认地都关联到该阶段,除非有明确的指定。在只有一个运行阶段情况下,Puppet的执行效果和以前版本是相同的,除非有明确地指定资源之间的关系,否则资源会以一种不可预期的方式被执行。

运行阶段的定义方法:

stage { 'first': before => Stage['main'] }
stage { 'last': require => Stage['main'] }

所有关联到first阶段的类都会在关联到main阶段的类之前被执行。所有关联到last阶段的类都会在关联到main阶段的类之后被执行。

运行阶段被定义后,可以使用"stage"参数关联一个类都某个阶段:

class {
    'apt-keys': stage => first;
    'sendmail': stage => main;
    'apache':   stage => last;
}

关联apt-keys类的所有资源到first阶段,关联sendmain类的所有资源到main阶段,关联apache类的所有资源到last阶段。这段简短的定义确保了apt-keys类的所有资源都会在sendmain类的所有资源之前执行,apache类的所有资源都会在sendmain类的所有资源之后执行。

资源类型(Defined Resource Types)

资源类型与类使用相同的格式,区别是以define关键字开头(不是class),支持参数但不支持继承。假设我们要在同一电脑上创建多个源代码仓库,这种情况下就可以定义一个资源类型,而不是类:
 
define svn_repo($path) {
    exec { "/usr/bin/svnadmin create ${path}/${title}":
        unless => "/bin/test -d ${path}",
    }
}

svn_repo { 'puppet_repo': path => '/var/svn_puppet' }
svn_repo { 'other_repo':  path => '/var/svn_other' }

为资源类型创建资源时,参数($path)以资源属性的方式(path => '/var/svn_puppet)使用,同时在资源类型的内部也可以作为变量使用。参数可以使用等号(=)设置默认值,这样当为资源类型创建资源时就可以不需要强制指定。

资源类型可以使用一些内置的变量,例如$name和$title,当资源被创建时这两个变量被设置成资源的标题属性。在Puppet 2.6.5或更高版本,$name和$title变量也可以作为参数的默认值:

define svn_repo($path = "/var/${name}") {...}

创建资源时的参数在资源类型内部都可以作为变量使用:

define svn_repo($path) {
    exec { "create_repo_${name}":
        command => "/usr/bin/svnadmin create ${path}/${title}",
        unless  => "/bin/test -d ${path}",
    }
    if $require {
        Exec["create_repo_${name}"] {
            require +> $require,
        }
    }
}

svn_repo { 'puppet':
    path    => '/var/svn',
    require => Package['subversion'],
}

这不是一个很专业的例子,大多数时候我们都知道subversion还需要svn签出,但它演示了require的用法和其它参数做为变量的使用。

资源类型象类一样在名称里面可以使用命名空间分隔符(::)。当引用一个资源类型实例时,必须大写所有类型名称的手字母(例如:Apache::Vhost['wordpress'])。

类 vs. 资源类型

类和资源类型定义方式很相似,但使用方法却很不同。

资源类型在同一主机可以重用,允许存在多个实例。Defined types are used to define reusable objects which will have multiple instances on a given host, so they cannot include any resources that will only have one instance. For instance, multiple uses of the same define cannot create the same file.

另一方面,类确保了它必定是单实例的 - 可以包含它们很多次,但都是指向同一个实例。

通常,服务会被定义成一个类,服务包、配置文件和守护进程都放在一起,因为同一主机大多数情况只有服务的一个实例。

资源类型用来管理诸如虚拟主机类似可以有多个的资源,或者用来包装一些重复的定义以减少输入。

模块

可以(强烈建议)将很多的类,资源类型和资源定义封装在一起作为一个模块使用。模块是一种可以共享(portable)的配置信息集合,例如,一个模块可以包含配置Postfix或Apache服务所需要的全部资源。参阅"模块"可以获取更详细的信息。

链接资源

从Puppet 2.6.0开始,资源之间可以通过关系定义从而将它们链接在一起。

按照下面的方式定义"before"关系:

File['/etc/ntp.conf'] -> Service['ntpd']

要求ntp配置文件必须在ntpd服务之前被执行。

按照下面的方式定义"notify"关系:

File['/etc/ntp.conf'] ~> Service['ntpd']

要求ntp配置文件必须在ntpd服务之前被执行,同时ntpd服务改变时也通知ntp配置文件。

可以在同一行指定多重关系,从而形成关系链:

Package['ntp'] -> File['/etc/ntp.conf'] -> Service['ntpd']

首先执行ntp包,然后执行ntp配置文件,最后执行ntpd服务。

有时也不需要所有的箭头都指向同一方向: 

File['/etc/ntp.conf'] -> Service['ntpd'] <- Package['ntp']

执行完ntp包和ntp配置文件后再执行ntpd服务。注意,使用这种语法定义关系必须是在相邻资源之间。例如,ntp包和ntp配置文件之间互相没有直接关系,Puppet有可能在执行ntp包之前就先执行ntp配置文件,这不是我们所期望的结果。这种语法提高了一些代码的可读性。

也可以在资源定义时指定关系,上面的例子可以修改成:

package { 'ntp': } -> file { '/etc/ntp.conf': }

确保在执行ntp配置文件之前先执行ntp包。

But wait! There’s more! You can also specify a collection on either side of the relationship marker:

    yumrepo { 'localyumrepo': .... }
    package { 'ntp': provider => yum, ... }
    Yumrepo <| |> -> Package <| provider == yum |>

This manages all yum repository resources before managing all package resources that explicitly specify the yum provider. (Note that it will not work for package resources that don’t specify a provider but end up using Yum — since this relationship is created during catalog compilation, it can only act on attributes visible to the parser, not properties that must be read from the target system.)

This, finally, provides easy many to many relationships in Puppet, but it also opens the door to massive dependency cycles. This last feature is a very powerful stick, and you can considerably hurt yourself with it. In particular, watch out when using virtual resources, as the collection operator realizes resources as a side-effect.

节点

理解了资源、类、资源类型和模块,就已经学会了Puppet的大部分内容,剩下的就是节点了。

节点定义也象类定义一样,支持继承。Puppet服务器包含一个节点定义列表,当一个节点(执行Puppet客户端的主机)连接到Puppet主进程,就按照它的名字在列表中查找。找到的节点信息被执行,然后发送给节点主机(Puppet客户端).

节点名字可以是短的主机名,也可以是完整的限定域名(full qualified domain name,FQDN). 有些名字,特别是FQDN,必须要用引号包含起来,最好的方式是所有名字都引起来。例如:

node 'www.testing.com' {
    include common
    include apache, squid
}

上面的代码定义了一个名为"www.testing.com"的节点,包含common, apache和squid类。

也可以用逗号分割多个节点,为它们指定相同的配置:

node 'www.testing.com', 'www2.testing.com', 'www3.testing.com' {
    include common
    include apache, squid
}

上面的代码定义了三个相同配置的节点: www.testing.comwww2.testing.com和www3.testing.com。

使用正则表达式匹配节点

从Puppet 0.25.0或更高版本,节点可以使用正则表达式进行匹配。例如:

node /^www\d+$/ {
    include common
}

上面的代码匹配所有名称以www开头,后面有一个或多个数字的主机。

另外一个例子:

node /^(foo|bar)\.testing\.com$/ {
    include common
}

上面的代码匹配test.com网域中名称为foo或bar的主机。

如果同一个配置文件中,与当前连接客户端匹配的有使用主机名的节点定义,还有多个使用正则表达式的节点定义,Puppet会如何处理呢?

  • 如果存在使用主机名的节点定义,它会被优先使用。
  • 否则,执行第一个匹配的正则表达式节点定义。

节点继承

节点支持受限的继承。和类一样,节点只允许单继承:

node 'www2.testing.com' inherits 'www.testing.com' {
    include loadbalancer
}

www2.testing.com节点继承了www.testing.com节点的所有配置,另外还包含loadbalancer类。

默认节点

如果创建一个名为default的节点,它将作为默认配置被其它找不到匹配的所有节点使用。

 

外部节点

有时候我们也许已经拥有了一些主机,它们正担负着一定的任务,例如LDAP,版本控制,或者数据库。这时候我们需要传递一些变量给这些节点。

这个时候,就需要在节点定义的时候编写一些"外部节点"脚本。更多信息参阅相关章节。

我们已经讲述了诸如排序和分组这样的特性,现在来学习一些更多的内容。

Puppet不是一门编程语言,它以模型的方式描述我们的IT架构。这就是我们要做到全部事情,避免了要写大量的编程代码。

引号

大部分时候,不需要用引号将字符串引起来。任何以字母开头的字母数字字符串(连字符也是允许的)都可以省略引号。

变量插入

到目前为止,我们在自定义资源类型中用到了变量。如果要在字符串中使用这些变量,必须使用双引号,不能用单引号。单引号字符串不会解析其中的变量(interpolation),双引号可以。字符串中的变量也可以使用{}括起来,这样子更容易理解:

$value = "${one}${two}"

双引号字符串中的引号("或')和美元符号($)有特殊的意义,若要把它们放到双引号字符串中,必须在前面加转义字符(\)。如果要将转义字符(\)放到双引号中,需要这样用:\\.

单引号字符串只支持两种转义序列:单引号(\')和反斜杠(\\)。除此之外,单引号里面的任何字符都可以直接处理。

建议不需要变量插入的字符串都使用单引号,需要使用变量插入的时候才使用双引号。"Style Guide"会详细的讨论这些,包含例子。

大写

资源大写有三种主要使用方式:

引用:当需要引用一个已经定义的资源,通常是为了依赖另外一个资源,我们就必须大写资源名:

require => File['sshdconfig']

继承:在一个资源子类中覆盖父类的定义,需要大写资源名。如果使用小写就会出现一个错误的结果。

设置默认值:使用没有标题的大写资源,实际上是为该类资源设置默认值。先前的例子就为需要执行的命令设置了默认的path。

注意自定义资源类型中命名空间,需要大写所有部分,例如Apache::Vhost['wordpress']。

数组

在前面的类和资源例子中有提到,Puppet在很多情况下都允许使用数组。数组的定义方式如下:

[ 'one', 'two', 'three' ]

可以使用索引存取数组中的某个项目:

$foo = [ 'one', 'two', 'three' ]
notice $foo[1]

有几个属性成员,例如'host'中的'alias',就可以接受数组作为它们的值。拥有多个别名的host资源象下面的这样:

host { 'one.example.com':
    ensure => present,
    alias  => [ 'satu', 'dua', 'tiga' ],
    ip     => '192.168.100.1',
}

这段代码会将主机'one.example.com'加入到主机列表,该主机拥有三个别名:'satu','dua'和'tiga'。

如果一个资源依赖多个其它资源,也可以使用数组按下面的方式定义:

resource { 'baz':
    require  => [ Package['foo'], File['bar'] ],
}

另一个使用数组的例子是多次调用一个自定义资源类型,例如:

define php::pear() {
    package { "php-${name}": ensure => installed }
}

php::pear { ['ldap', 'mysql', 'ps', 'snmp', 'sqlite', 'tidy', 'xmlrpc']: }

当然,上面的使用方法也适用于内置资源类型:

file { [ 'foo', 'bar', 'foobar' ]:
    owner => 'root',
    group => 'root',
    mode  => '0600',
}

哈希

从2.6.0开始,Puppet开始支持哈希功能。Puppet哈希定义类似Ruby:

{ key1 => val1, key2 => val2, ... }

哈希键是字符串,但是值可以是任何可能的RHS值,例如函数调用,变量,等等。

可以分配一个变量给哈希:

$myhash = { key1 => 'myval', key2 => $b }

递归的存取哈希成员(这也适用于数组): 

$myhash = { key => { subkey => 'b' }}
notice($myhash[key][subkey])

可以用哈希成员作为资源标题,或者参数的缺省定义,或者资源的参数。

变量

Puppet也象大多数我们熟悉的编程语言一样支持变量。Puppet变量用美元符号($)标明:

$content = 'some content\n'

file { '/tmp/testing': content => $content }

Puppet语言是一门定义语言,意思是说它的范围和分配规则很多时候和通常的命令语言是不同的。主要的不同体现在一个范围内我们不能改变变量的值,因为那依赖与语句在文件中的顺序才能决定它的值。在定义语言中顺序是不重要的。

$user = root
file { '/etc/passwd':
    owner => $user,
}

$user = bin
file { '/bin':
    owner   => $user,
    recurse => true,
}

不能为变量重新赋值,替换的处理方式是使用内置条件:

$group = $operatingsystem ? {
    solaris => 'sysadmin',
    default => 'wheel',
}

一个范围中,每个变量只能被赋值一次。当然,我们可以在非重叠范围为同一个变量赋另外的值。例如,设置顶级配置:

node a {
    $setting = 'this'
    include class_using_setting
}

node b {
    $setting = 'that'
    include class_using_setting
}

上面的例子,节点"a"和节点"b"有不同的范围,这不是为同一个变量重新赋值。

变量范围

范围规定了变量在什么地方有效。Unlike early programming languages like BASIC, variables are only valid and accessible in certain places in a program. 同一个变量名在不同的范围,不是指向同一个值。

类和节点开始一个新范围。Puppet现在动态的定义范围,也就是说创建范围层次结构是依据代码执行的地方,而不是代码定义的地方。

Variable Scope

Scoping may initially seem like a foreign concept, though in reality it is pretty simple. A scope defines where a variable is valid. Unlike early programming languages like BASIC, variables are only valid and accessible in certain places in a program. Using the same variable name in different parts of the language do not refer to the same value.

Classes and nodes introduce new scopes. Puppet is currently dynamically scoped, which means that scope hierarchies are created based on where the code is evaluated instead of where the code is defined.

For example:

    $test = 'top'
    class myclass {
      exec { "/bin/echo ${test}": logoutput => true }
    }

    class other {
      $test = 'other'
      include myclass
    }

    include other

In this case, there’s a top-level scope, a new scope for other, and the a scope below that for myclass. When this code is evaluated, $test evaluates to other, not top.

Qualified Variables

Puppet supports qualification of variables inside a class. This allows you to use variables defined in other classes.

For example:

    class myclass {
      $test = 'content'
    }

    class anotherclass {
      $other = $myclass::test
    }

In this example, the value of the $other variable evaluates to content. Qualified variables are read-only — you cannot set a variable’s value from other class.

Variable qualification is dependent on the evaluation order of your classes. Class myclass must be evaluated before class anotherclass for variables to be set correctly.

Facts as Variables

In addition to user-defined variables, the facts generated by Facter are also available as variables. This allows values that you would see by running facter on a client system within Puppet manifests and also within Puppet templates. To use a fact as a variable prefix the name of the fact with $. For example, the value of the operatingsystem and puppetversion facts would be available as the variables$operatingsystem and $puppetversion.

Variable Expressions

In Puppet 0.24.6 and later, arbitrary expressions can be assigned to variables, for example:

    $inch_to_cm = 2.54
    $rack_length_cm = 19 * $inch_to_cm
    $gigabyte = 1024 * 1024 * 1024
    $can_update = ($ram_gb * $gigabyte) > 1 << 24

See the Expression section later on this page for further details of the expressions that are now available.

Appending to Variables

In Puppet 0.24.6 and later, values can be appended to array variables:

    $ssh_users = [ 'myself', 'someone' ]

    class test {
      $ssh_users += ['someone_else']
    }

Here the $ssh_users variable contains an array with the elements myself and someone. Using the variable append syntax, +=, we added another element, someone_else to the array.

Please note, variables cannot be modified in the same scope because of the declarative nature of Puppet. As a result, $ssh_users contains the element ‘someone_else’ only in the scope of class test and not outside scopes. Resources outside of this scope will “see” the original array containing only myself and someone.

原文地址:https://www.cnblogs.com/eastson/p/2605960.html