diff --git a/docs/_openvox_8x/bgtm.md b/docs/_openvox_8x/bgtm.md index 8d61592da..e8ca19aa6 100644 --- a/docs/_openvox_8x/bgtm.md +++ b/docs/_openvox_8x/bgtm.md @@ -4,8 +4,6 @@ title: Beginner's guide to writing modules --- [structure]: ./images/bgtmclassstructure.png -[anchor]: ./lang_containment.html#anchor-pattern-containment-for-compatibility-with-puppet--340 -[pdk]: {{pdk}}/pdk.html Learn how to create fantastic modules by following module best practices [standards and architecture](./style_guide.html). @@ -13,7 +11,6 @@ Contributors to this guide have spent years creating Puppet modules, falling int 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). -{:.concept} ## Giving your module purpose 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 +27,21 @@ To help plan your module appropriately, consider: 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. -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`. +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`. -{:.concept} ## Structuring your module The ideal module manages a single piece of software from installation through setup, configuration, and service management. This section covers: -* [2a. How to design your module's classes](#a-class-design). -* [2b. How to develop useful parameters](#b-parameters). -* [2c. How best to order your classes (rather than resources)](#c-ordering). -* [2d. How to leverage and utilize dependencies](#d-dependencies). +* [How to design your module's classes](#class-design). +* [How to develop useful parameters](#parameters). +* [How best to order your classes (rather than resources)](#ordering). +* [How to leverage and utilize dependencies](#dependencies). -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. +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. -{:.concept} ### Class design 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 +52,13 @@ In terms of class structure we recommend the following (more detail below): ![module class structure][structure] -{:.section} #### `module` 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`. For instance, the main `ntp` class in the `ntp` module looks like this: -```ruby +```puppet class ntp ( Boolean $broadcastclient, Stdlib::Absolutepath $config, @@ -81,14 +75,13 @@ class ntp ( ... ``` -{:.section} #### `module::install` 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. The install class must be named `module::install`, as in the `ntp` module: -``` ruby +```puppet class ntp::install { if $ntp::package_manage { @@ -101,14 +94,13 @@ class ntp::install { } ``` -{:.section} #### `module::config` 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. For example, see the `module::config` class in the `ntp` module: -``` ruby +```puppet class ntp::config { #The servers-netconfig file overrides NTP config on SLES 12, interfering with our configuration. @@ -143,14 +135,13 @@ class ntp::config { ... ``` -{:.section} #### `module::service` 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. For example: -``` ruby +```puppet class ntp::service { if ! ($ntp::service_ensure in [ 'running', 'stopped' ]) { @@ -171,14 +162,12 @@ class ntp::service { } ``` -{:.concept} ### Parameters Parameters form the public API of your module. 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. -{:.section} #### Naming parameters Naming consistency is imperative for community comprehension and assists in troubleshooting and collaborating on module development. @@ -187,7 +176,7 @@ Best practices recommend the pattern of `thing_property` for naming parameters. For example, in the `ntp` module -``` ruby +```puppet class ntp::install { if $ntp::package_manage { @@ -204,7 +193,6 @@ If you have a parameter that toggles an entire function on and off, the naming c Consistent naming across modules helps with the readability and usability of your code. -{:.section} #### Number of parameters 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 +201,15 @@ You must not hardcode data in your modules, and having more parameters is the be 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. -For an example of a module that capitalizes on offering many parameters, please see [puppetlabs-apache](http://forge.puppet.com/puppetlabs/apache). +For an example of a module that capitalizes on offering many parameters, please see [puppetlabs-apache](https://forge.puppet.com/puppetlabs/apache). -{:.concept} ### Ordering 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. For example: -``` ruby +```puppet file { 'configuration': ensure => present, require => Class['module::install'], @@ -231,7 +218,6 @@ For example: 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. -{:.section} #### Containment and anchoring 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 +226,73 @@ Classes do not _automatically_ contain the classes they declare. This is because For example, the `ntp` module uses containment in the main `ntp` class: -``` +```puppet contain ntp::install - contain ntp::config - contain ntp::service - Class['::ntp::install'] -> - Class['::ntp::config'] ~> - Class['::ntp::service'] +contain ntp::config +contain ntp::service +Class['::ntp::install'] -> +Class['::ntp::config'] ~> +Class['::ntp::service'] ``` -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). - -{:.concept} ### Dependencies If your module's functionality depends on another module, then you must list these dependencies and include them directly. 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.) -{:.concept} ## Testing your module 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. -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. +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)](https://www.puppet.com/docs/pdk/latest/pdk.html). + +> **Note:** The open source version of PDK is no longer supported by Puppet. It may still be functional, but no further updates or bug fixes will be provided. -{:.section} ### rspec-puppet 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. -``` +```ruby it { should contain_file('configuration') } -```` +``` -RSpec lets you provide facts, like `osfamily`, in order to test the module in various scenarios. +RSpec lets you provide facts, like `os['family']`, in order to test the module in various scenarios. 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. -To learn more, see [http://rspec-puppet.com/](http://rspec-puppet.com/). +To learn more, see the [rspec-puppet documentation](https://puppetlabs.github.io/rspec-puppet/). -{:.section} ### puppetlabs-spec-helper The [puppetlabs-spec-helper](https://github.com/puppetlabs/puppetlabs_spec_helper) gem automates some of the tasks required to test modules. +> **Note:** This gem is no longer needed for any Vox Pupuli modules. + 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: -``` +```ruby require 'puppetlabs_spec_helper/rake_tasks' ``` -{:.section} ### Beaker-rspec [Beaker-rspec](https://github.com/puppetlabs/beaker-rspec) is an acceptance/integration testing framework. -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. +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. -{:.section} #### serverspec -[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: +[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: - describe service('httpd') do - it { should be_running } - end +```ruby +describe service('httpd') do + it { should be_running } +end +``` It then knows how to translate `be_running` into shell commands for different distributions. -{:.concept} ## Versioning your module 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 +301,30 @@ After you've decided on the new version number, adjust the version number in the 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. -{:.concept} ## Documenting your module 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. -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. +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. -{:.concept} ## Releasing your module -We encourage you to publish your modules on the [Puppet Forge](http://forge.puppet.com). +We encourage you to publish your modules on the [Puppet Forge](https://forge.puppet.com). 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. -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). +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 in the [module publishing guide](./modules_publishing.html). -{:.concept} ## Community Resources For beginning module authors, a variety of community resources are available. [Module basics](./modules_fundamentals.html) -[Puppet Development Kit][pdk] - -[Puppet Language Style Guide](./style_guide.html) - -[The Forge](http://forge.puppet.com) - -The [puppet-users mailing list](https://groups.google.com/forum/#!forum/puppet-users) +[OpenVox Language Style Guide](./style_guide.html) -`#puppet` on IRC +[The Forge](https://forge.puppet.com) -[Puppet Community on Slack](https://slack.puppet.com/) +[Vox Pupuli community channels](https://voxpupuli.org/connect/) +[puppetmodule.info](https://www.puppetmodule.info) — open source Puppet module documentation server, generating fresh docs for Puppet modules and popular Git repositories