A Basic Guide for New Marionettists
Published: 2020-05-24 15:31:07

---
title: A Basic Guide for New Marionettists
published: true
date: 
tags: 
canonical_url: https://mmap.page/dive-into/puppet/
---

## TLDR

This article only covers the so-called masterless mode, a.k.a. `puppet apply`,because the [master-slave mode](https://puppet.com/docs/puppet/6.7/architecture.html) is [unethical](https://bugs.python.org/issue34605) and [bolt](https://puppet.com/products/bolt) sounds violent. On the other hand, `puppet apply` plays the role of both master and slave, which is considered not unlikely inoffensive.

Puppet uses a declarative DSL, i.e. describing the state of puppets. Below is an example from [the official document](https://puppet.com/docs/puppet/6.7/intro_puppet_language_and_code.html):

```
case $operatingsystem {
  centos, redhat: { $service_name = 'ntpd' }
  debian, ubuntu: { $service_name = 'ntp' }
}

package { 'ntp':
  ensure => installed,
}

service { 'ntp':
  name => $service_name,
  ensure => running,
  enable => true,
  subscribe => File['ntp.conf'],
}

file { 'ntp.conf':
  path => '/etc/ntp.conf',
  ensure => file,
  require => Package['ntp'],
  source => "puppet:///modules/ntp/ntp.conf",
}

```

This guide applies to Puppet 6.7.

## Quick Start

Install the `puppet-agent` package (which provides `puppet apply`),following the [instructions at official document](https://puppet.com/docs/puppet/6.7/install_agents.html).

As an oversimplified example, I wrote a script to ensure git is installed:

```
# filename: laptop.pp
package {'git':
  ensure => installed
}

```

Validate its syntax:

```
puppet parser validate laptop.pp

```

In practice, this is mainly for CI,since a decent editor should have warned you if you made some syntax mistakes.

Rehearse the play (dry run):

```
puppet apply --noop laptop.pp

```

It’s show time: (run as root since installing package usually requires root permission)

```
puppet apply laptop.pp

```

You can also pass a directory instead of a file to `puppet apply`.But in this case Puppet combines all files under that directory as one script,without isolated scope.Thus I prefer use one file.

## Puppet DSL

### Style

The Puppet community prefers `snake_case` and two spaces soft tab.

### Selector Expression

Selector expression returns a value.For example, the case “statement” in the beginning code sample:

```
case $operatingsystem {
  centos, redhat: { $service_name = 'ntpd' }
  debian, ubuntu: { $service_name = 'ntp' }
}

```

can be rewritten as:

```
$service_name = $operatingsystem ? {
  centos, redhat => 'ntpd',
  debian, ubuntu => 'ntp',
}

```

Strictly speaking, they are not semantically equivalent.If the os failed to match, the former will do nothing,while the later will refuse to compile.

However, the `if` and `case` “statements” are actually expressions,returning the value of the last expression in the executed blockor `undef` when no block was executed.

### Puppet Defaults

Puppet DSL has a wired syntax (capitalizing type name) for declaring defaults for a specific type of puppet:

```
# Set default values for the path attribute of the exec puppet.
Exec {
  path => '/usr/bin:/bin:/usr/sbin:/sbin',
}

```

The more wired part is, puppet defaults are **dynamically scoped**!

Therefore, using per-expression defaults is preferred.Below is an example from [the official document](https://puppet.com/docs/puppet/6.7/lang_resources.html#resource-declaration-default-attributes):

```
file {
  default:
    ensure => file,
    owner => "root",
    group => "wheel",
    mode => "0600",
  ;
  ['ssh_host_dsa_key', 'ssh_host_key', 'ssh_host_rsa_key']:
    # use all defaults
  ;
  ['ssh_config', 'ssh_host_dsa_key.pub', 'ssh_host_key.pub', 'ssh_host_rsa_key.pub', 'sshd_config']:
    # override mode
    mode => "0644",
  ;
}

```

### Puppet Collector

This is sometimes called the “spaceship operator”,which selects a group of puppets via attribute searching:

```
User <| groups == 'wheel' |>

```

Besides `==`, there are other operators, `!=`, `and`, and `or`.

### Lazy Puppet

The at (`@`) prefix marks a puppet declaration as lazy:

```
@user {'ubuntu':
  uid => 1000,
  comment => 'default user',
  group => wheel,
}

```

Lazy puppets will not be applied until:

1. explicitly realized, e.g. `realize User['ubuntu']`, or
2. implicitly realized by a matched collector, e.g. `User <| group == wheel |>`.

## Glossary

This guide uses different terms to the official Puppet documentation.These are just my personal preference.For example, the _script_ term used in this guide is called **manifest** in the official Puppet documentation.In my opinion, _script_ makes more sense than **manifest**.In fact, [chef](https://www.chef.io/) refers to the similar notation as [recipe](https://docs.chef.io/recipes.html).

- **agent** Puppet actually uses the term agent instead of _slave_, but this is just a euphemism.
- **catalog** In master _slave_ mode, catalog are compiled by master with _stage_ specific information. In `puppet apply` mode, it is just unimportant mid-product.
- **class** A _set_ of puppets. And a **subclass** is _an extended set_ of puppets base on another _set_ of puppets, a.k.a. a class `inherits` another class.
- **environment** A _play_.
- **fact** A _detail_ about the _stage_.
- **Facter** The _stage manager_ can tell you _details_ about the _stage_.
- **filebucket** A repository for file backups.
- **Hiera** A built-in key-value database for puppet.
- **lambda** In Puppet, a lambda can only be used in function calls. That is, it can only be passed to functions, but cannot be assigned to variables. To some extent, it is more like Ruby’s block.
- **manifest** The _script_ used by a marionettist. Puppet refers to the entry point as **main manifest** or **site manifest**. `puppet apply` only cares about the _script_ passed to it on the command line.
- **node** The _stage_ for the show. In `puppet apply` mode, the local machine is the only _stage_.
- **resource** A _puppet_ controlled by the marionettist. Puppet uses the term **virtual resource** to refer to _lazy puppet_.