You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix broken links and update community resources in bgtm.md
- Replace PDK references with voxbox (https://github.com/voxpupuli/container-voxbox)
- Replace broken PDK liquid template link with direct URL
- Fix all http:// → https:// for Forge, rspec-puppet, Vagrant, and Serverspec URLs
- Fix section TOC anchors to match actual heading IDs
- Change code fences from 'ruby' to 'puppet' for Puppet code examples; add 'ruby' hint to RSpec/Rakefile examples
- Fix extra backtick on closing rspec code fence
- Fix containment code block indentation
- Remove Puppet 3.4 anchor pattern paragraph (not relevant for OpenVox 8)
- Fix missing space in documenting section prose
- Update community resources: replace Puppet Slack/IRC with Vox Pupuli Slack and #voxpupuli on Libera.Chat
- Remove all {:.concept}/{:.section} kramdown markers unused by theme
Signed-off-by: Michael Harp <mike@mikeharp.com>
Learn how to create fantastic modules by following module best practices [standards and architecture](./style_guide.html).
11
10
12
11
Contributors to this guide have spent years creating Puppet modules, falling into every pitfall, trap, and mistake you could hope to make. This guide is intended to help you avoid our mistakes through an approachable introduction to module best practices.
13
12
14
13
Before you begin, you should be familiar with Puppet such that you have a basic understanding of the Puppet [language](./lang_summary.html), you know what constitutes a [class](./lang_classes.html), and you understand the basic module [structure](./modules_fundamentals.html).
15
14
16
-
{:.concept}
17
15
## Giving your module purpose
18
16
19
17
Before you begin writing your module, you must define what it will do. Defining the range of your module's work helps you create concise modules that are easy to work with.
@@ -30,23 +28,21 @@ To help plan your module appropriately, consider:
30
28
31
29
It is standard practice for Puppet users to have 200 or more modules in an environment. Simple is better. Each module in your environment should contain related resources that enable it to accomplish a task. Create multiple modules for more complex needs. The practice of having many small, focused modules promotes code reuse and turns modules into building blocks.
32
30
33
-
As an example, let's take a look at the [`puppetlabs-puppetdb`](http://forge.puppet.com/puppetlabs/puppetdb) module. This module deals solely with the the setup, configuration, and management of PuppetDB. However, PuppetDB stores its data in a PostgreSQL database. Rather than having the module manage PostgreSQL, the author included the [`puppetlabs-postgresql`](http://forge.puppet.com/puppetlabs/postgresql) module as a dependency, leveraging the postgresql module's classes and resources to build out the right configuration for PuppetDB. Similarly, the `puppetdb-module` needs to manipulate the `puppet.conf` file in order to operate PuppetDB. Instead of having the `puppetdb-module` handle `puppet.conf` changes internally, the author used the [`puppetlabs-inifile`](http://forge.puppet.com/puppetlabs/inifile) module to enable `puppetlabs-puppetdb` to make only the required edits to `puppet.conf`.
31
+
As an example, let's take a look at the [`puppetlabs-puppetdb`](https://forge.puppet.com/puppetlabs/puppetdb) module. This module deals solely with the the setup, configuration, and management of PuppetDB. However, PuppetDB stores its data in a PostgreSQL database. Rather than having the module manage PostgreSQL, the author included the [`puppetlabs-postgresql`](https://forge.puppet.com/puppetlabs/postgresql) module as a dependency, leveraging the postgresql module's classes and resources to build out the right configuration for PuppetDB. Similarly, the `puppetdb-module` needs to manipulate the `puppet.conf` file in order to operate PuppetDB. Instead of having the `puppetdb-module` handle `puppet.conf` changes internally, the author used the [`puppetlabs-inifile`](https://forge.puppet.com/puppetlabs/inifile) module to enable `puppetlabs-puppetdb` to make only the required edits to `puppet.conf`.
34
32
35
-
{:.concept}
36
33
## Structuring your module
37
34
38
35
The ideal module manages a single piece of software from installation through setup, configuration, and service management.
39
36
40
37
This section covers:
41
38
42
-
*[2a. How to design your module's classes](#a-class-design).
43
-
*[2b. How to develop useful parameters](#b-parameters).
44
-
*[2c. How best to order your classes (rather than resources)](#c-ordering).
45
-
*[2d. How to leverage and utilize dependencies](#d-dependencies).
39
+
*[How to design your module's classes](#class-design).
40
+
*[How to develop useful parameters](#parameters).
41
+
*[How best to order your classes (rather than resources)](#ordering).
42
+
*[How to leverage and utilize dependencies](#dependencies).
46
43
47
-
To demonstrate a real-world best practices standard module, we will walk through the structure of the [puppetlabs-ntp](http://forge.puppet.com/puppetlabs/ntp) module.
44
+
To demonstrate a real-world best practices standard module, we will walk through the structure of the [puppetlabs-ntp](https://forge.puppet.com/puppetlabs/ntp) module.
48
45
49
-
{:.concept}
50
46
### Class design
51
47
52
48
A good module is comprised of small, self-contained classes that each do only one thing. Classes within a module are similar to functions in programming, using parameters to perform related steps that create a coherent whole.
@@ -57,14 +53,13 @@ In terms of class structure we recommend the following (more detail below):
57
53
58
54
![module class structure][structure]
59
55
60
-
{:.section}
61
56
#### `module`
62
57
63
58
The main class of any module must share the name of the module and be located in the `init.pp` file. The name and location of the main module class is extremely important, as it guides the [autoloader](./lang_namespaces.html#autoloader-behavior) behavior. The main class of a module is its interface point and ought to be the only parameterized class if possible. Limiting the parameterized classes to just the main class allows you to control usage of the entire module with the inclusion of a single class. This class should provide sensible defaults so that a user can get going with `include module`.
64
59
65
60
For instance, the main `ntp` class in the `ntp` module looks like this:
66
61
67
-
```ruby
62
+
```puppet
68
63
class ntp (
69
64
Boolean $broadcastclient,
70
65
Stdlib::Absolutepath $config,
@@ -81,14 +76,13 @@ class ntp (
81
76
...
82
77
```
83
78
84
-
{:.section}
85
79
#### `module::install`
86
80
87
81
The install class must be located in the `install.pp` file. It should contain all of the resources related to getting the software that the module manages onto the node.
88
82
89
83
The install class must be named `module::install`, as in the `ntp` module:
90
84
91
-
```ruby
85
+
```puppet
92
86
class ntp::install {
93
87
94
88
if $ntp::package_manage {
@@ -101,14 +95,13 @@ class ntp::install {
101
95
}
102
96
```
103
97
104
-
{:.section}
105
98
#### `module::config`
106
99
107
100
The resources related to configuring the installed software should be placed in a config class. The config class must be named `module::config` and must be located in the `config.pp` file.
108
101
109
102
For example, see the `module::config` class in the `ntp` module:
110
103
111
-
```ruby
104
+
```puppet
112
105
class ntp::config {
113
106
114
107
#The servers-netconfig file overrides NTP config on SLES 12, interfering with our configuration.
@@ -143,14 +136,13 @@ class ntp::config {
143
136
...
144
137
```
145
138
146
-
{:.section}
147
139
#### `module::service`
148
140
149
141
The remaining service resources, and anything else related to the running state of the software, should be contained in the service class. The service class must be named `module::service` and must be located in the `service.pp` file.
150
142
151
143
For example:
152
144
153
-
```ruby
145
+
```puppet
154
146
class ntp::service {
155
147
156
148
if ! ($ntp::service_ensure in [ 'running', 'stopped' ]) {
@@ -171,14 +163,12 @@ class ntp::service {
171
163
}
172
164
```
173
165
174
-
{:.concept}
175
166
### Parameters
176
167
177
168
Parameters form the public API of your module.
178
169
179
170
They are the most important interface you expose, and you should take care to balance to the number and variety of parameters so that users can customize their interactions with the module. Below, we walk through best practices for naming and developing parameters.
180
171
181
-
{:.section}
182
172
#### Naming parameters
183
173
184
174
Naming consistency is imperative for community comprehension and assists in troubleshooting and collaborating on module development.
@@ -187,7 +177,7 @@ Best practices recommend the pattern of `thing_property` for naming parameters.
187
177
188
178
For example, in the `ntp` module
189
179
190
-
```ruby
180
+
```puppet
191
181
class ntp::install {
192
182
193
183
if $ntp::package_manage {
@@ -204,7 +194,6 @@ If you have a parameter that toggles an entire function on and off, the naming c
204
194
205
195
Consistent naming across modules helps with the readability and usability of your code.
206
196
207
-
{:.section}
208
197
#### Number of parameters
209
198
210
199
To maximize the usability of your module, make it flexible by adding parameters. Parameters enable users to customize their use of your module.
@@ -213,16 +202,15 @@ You must not hardcode data in your modules, and having more parameters is the be
213
202
214
203
Avoid adding parameters that allow you to override templates. When your parameters allow template overrides, users can override your template with a custom template that contains additional hardcoded parameters. Hardcoded parameters in templates inhibits flexibility over time. It is far better to create more parameters and then modify the original template, or have a parameter which accepts an arbitrary chunk of text added to the template, than it is to override the template with a customized one.
215
204
216
-
For an example of a module that capitalizes on offering many parameters, please see [puppetlabs-apache](http://forge.puppet.com/puppetlabs/apache).
205
+
For an example of a module that capitalizes on offering many parameters, please see [puppetlabs-apache](https://forge.puppet.com/puppetlabs/apache).
217
206
218
-
{:.concept}
219
207
### Ordering
220
208
221
209
Best practice is to base all order-related dependencies (such as `require` and `before`) on classes rather than resources. Class-based ordering allows you to shield the implementation details of each class from the other classes.
222
210
223
211
For example:
224
212
225
-
```ruby
213
+
```puppet
226
214
file { 'configuration':
227
215
ensure => present,
228
216
require => Class['module::install'],
@@ -231,7 +219,6 @@ For example:
231
219
232
220
Rather than making a `require` to several packages, the above ordering allows you to refactor and improve `module::install` without adjusting the manifests of other classes to match the changes.
233
221
234
-
{:.section}
235
222
#### Containment and anchoring
236
223
237
224
To allow other modules to form ordering relationships with your module, ensure that your main classes explicitly _contain_ any subordinate classes they declare.
@@ -240,76 +227,67 @@ Classes do not _automatically_ contain the classes they declare. This is because
240
227
241
228
For example, the `ntp` module uses containment in the main `ntp` class:
242
229
243
-
```
230
+
```puppet
244
231
contain ntp::install
245
-
contain ntp::config
246
-
contain ntp::service
247
-
Class['::ntp::install'] ->
248
-
Class['::ntp::config'] ~>
249
-
Class['::ntp::service']
232
+
contain ntp::config
233
+
contain ntp::service
234
+
Class['::ntp::install'] ->
235
+
Class['::ntp::config'] ~>
236
+
Class['::ntp::service']
250
237
```
251
238
252
-
Containment is supported in Puppet 3.4 and later. To support versions prior to Puppet 3.4 (or Puppet Enterprise 3.2), you must use the [anchor pattern][anchor] to hold those classes in place. Anchoring requires [puppetlabs-stdlib](http://forge.puppet.com/puppetlabs/stdlib).
253
-
254
-
{:.concept}
255
239
### Dependencies
256
240
257
241
If your module's functionality depends on another module, then you must list these dependencies and include them directly.
258
242
259
243
This means you must `include x` in the main class to ensure the dependency is included in the catalog. You must also add the dependency to the module's [metadata.json](./style_guide.html#module-metadata) and `.fixtures.yml`. (`.fixtures.yml` is a file used exclusively by RSpec to pull in dependencies required to successfully run unit tests.)
260
244
261
-
{:.concept}
262
245
## Testing your module
263
246
264
247
Ensure that the module works in a variety of conditions, and that the options and parameters of your module work together to an appropriate end result.
265
248
266
-
We recommend several testing frameworks available to help you write unit and acceptance tests. Some of these tools are already included in the Puppet Development Kit (PDK; see the [PDK][pdk] documentation for details.
249
+
We recommend several testing frameworks available to help you write unit and acceptance tests. Some of these tools are available in [container-voxbox][container-voxbox], a container with a full Puppet development toolchain.
267
250
268
-
{:.section}
269
251
### rspec-puppet
270
252
271
253
The `rspec-puppet` gem provides a unit-testing framework for Puppet. It extends RSpec to allow the testing framework to understand Puppet catalogs, the artifact it specializes in testing. You can write tests, as in the below example, to test that aspects of your module work as intended.
272
254
273
-
```
255
+
```ruby
274
256
it { should contain_file('configuration') }
275
-
````
257
+
```
276
258
277
259
RSpec lets you provide facts, like `osfamily`, in order to test the module in various scenarios.
278
260
279
261
A typical use of RSpec is to iterate over a list of operating systems, asserting that the package and service should exist in the catalog for every operating system your module supports.
280
262
281
-
To learn more, see [http://rspec-puppet.com/](http://rspec-puppet.com/).
263
+
To learn more, see [https://rspec-puppet.com/](https://rspec-puppet.com/).
282
264
283
-
{:.section}
284
265
### puppetlabs-spec-helper
285
266
286
267
The [puppetlabs-spec-helper](https://github.com/puppetlabs/puppetlabs_spec_helper) gem automates some of the tasks required to test modules.
287
268
288
269
This is especially useful in conjunction with `rspec-puppet`, as `puppetlabs-spec-helper` provides default Rake tasks that allow you to standardize testing across modules. It also provides some code to connect `rspec-puppet` with modules. Add it to the Gemfile of the project, and then add the following line to the Rakefile:
289
270
290
-
```
271
+
```ruby
291
272
require'puppetlabs_spec_helper/rake_tasks'
292
273
```
293
274
294
-
{:.section}
295
275
### Beaker-rspec
296
276
297
277
[Beaker-rspec](https://github.com/puppetlabs/beaker-rspec) is an acceptance/integration testing framework.
298
278
299
-
It provisions one or more virtual machines on various hypervisors (such as [Vagrant](http://www.vagrantup.com/)) and then checks the result of applying your module in a realistic environment.
279
+
It provisions one or more virtual machines on various hypervisors (such as [Vagrant](https://www.vagrantup.com/)) and then checks the result of applying your module in a realistic environment.
300
280
301
-
{:.section}
302
281
#### serverspec
303
282
304
-
[Serverspec](http://serverspec.org/) provides additional testing constructs (such as `be_running` and `be_installed`) for beaker-rspec. It allows you to abstract away details of the underlying distribution when testing. It lets you write tests like:
283
+
[Serverspec](https://serverspec.org/) provides additional testing constructs (such as `be_running` and `be_installed`) for beaker-rspec. It allows you to abstract away details of the underlying distribution when testing. It lets you write tests like:
305
284
306
285
describe service('httpd') do
307
286
it { should be_running }
308
287
end
309
288
310
289
It then knows how to translate `be_running` into shell commands for different distributions.
311
290
312
-
{:.concept}
313
291
## Versioning your module
314
292
315
293
Modules, like any other piece of software, must be versioned and released when changes are made. Use semantic versioning, which sets out specific rules for when to increment major and minor versions.
@@ -318,38 +296,33 @@ After you've decided on the new version number, adjust the version number in the
318
296
319
297
This allows you to create a list of dependencies in the `metadata.json` file of your modules with specific versions of dependent modules, which ensures your module isn't used with an old dependency that won't work. Versioning also enables workflow management by allowing you to easily use different versions of modules in different environments.
320
298
321
-
{:.concept}
322
299
## Documenting your module
323
300
324
301
We recommend that you document your module with a README explaining how your module works and a Reference section detailing information about your module's classes, defined types, functions, and resource types and providers.
325
302
326
-
For guidance, see our modules documentation [guide](./modules_documentation.html)and the [documentation](./style_guide.html#module-documentation) section of the Puppet Language Style Guide.
303
+
For guidance, see our modules documentation [guide](./modules_documentation.html)and the [documentation](./style_guide.html#module-documentation) section of the OpenVox Language Style Guide.
327
304
328
-
{:.concept}
329
305
## Releasing your module
330
306
331
-
We encourage you to publish your modules on the [Puppet Forge](http://forge.puppet.com).
307
+
We encourage you to publish your modules on the [Puppet Forge](https://forge.puppet.com).
332
308
333
309
Sharing your modules allows other users to write improvements to the modules you make available and contribute them back to you, effectively giving you free improvements to your modules.
334
310
335
311
Additionally, publishing your modules to the Forge helps foster community among Puppet users, and allows other Puppet community members to download and use your module. If the Puppet community routinely releases and iterates on modules on the Forge, the quality of available modules increases dramatically and gives you access to more modules to download and modify for your own purposes. Details on how to publish modules to the Forge can be found [here](./modules_publishing.html).
336
312
337
-
{:.concept}
338
313
## Community Resources
339
314
340
315
For beginning module authors, a variety of community resources are available.
341
316
342
317
[Module basics](./modules_fundamentals.html)
343
318
344
-
[Puppet Development Kit][pdk]
345
-
346
-
[Puppet Language Style Guide](./style_guide.html)
319
+
[container-voxbox][container-voxbox]
347
320
348
-
[The Forge](http://forge.puppet.com)
321
+
[OpenVox Language Style Guide](./style_guide.html)
349
322
350
-
The [puppet-users mailing list](https://groups.google.com/forum/#!forum/puppet-users)
323
+
[The Forge](https://forge.puppet.com)
351
324
352
-
`#puppet` on IRC
325
+
[Vox Pupuli on Slack](https://voxpupuli.org/slack/)
353
326
354
-
[Puppet Community on Slack](https://slack.puppet.com/)
0 commit comments