Entries filed under: Blog

Back to Index

The Myth of the One-Off

Posted on
By
Garrett Honeycutt
in
Blog, Opinion and Interview
Responses
2 Comments »

An argument that I regularly hear from people regarding the adoption of
configuration management tools is that their systems are unique and
comprised of many one-offs. In this article I will address the one-off myth
and discuss why your systems are not beautiful snowflakes.

If you deploy a one-off system, you lack the testing systems to support it.
Most organizations have at least two levels of pre-production environments,
such as Dev and QA. Is it really wise to deploy a system without another
environment in which to test?

Systems fail. It is not “if” but “when”. If your one-off was important enough
to build the first time, is it not just as important to have around after it
fails? Disaster recovery is important and when the time comes that you need it
is not the time to have to figure out how you are going to rebuild that
one-off. This is where you can save your organization and yourself instead of
being stressed out and under pressure that your system failed.

Another argument I hear in defense of the one-off is that “it is only temporary.”
This always makes me laugh. Ask anyone who has been in the field for awhile and
I’m sure they can point you towards “temporary” systems that have been deployed
for ages. We deploy systems so that people can use them and once they do, it is
extremely hard to take them back.

One-off’s are notorious for not having good
documentation or supporting the ability to reproduce them. As the person building those
systems you are probably responsible for keeping them up. The lack of
documentation, the inability to reproduce the system, and no environment for
testing all add up to those late night “something is on fire” calls and
extended downtime. As the creator you get blamed for the shoddy work to boot.

Even systems that do completely different things still share many
common attributes. They generally need host name resolution, authentication services,
kernel tuning, text editors, shells, basic utilities, etc. Once you start
modeling these services and breaking down what it means to be a system on your
network, you will start to see that they are not so unique after all.

Next time someone prompts you to build a one-off, you can explain the drawbacks
and build it right. You will be doing your organization and yourself a big
favor.

12 Tips for Growing Your Start-Up

Posted on
By
michelle
in
Blog, Company, Tips
Responses
0 Comments

You’ve got a great idea, and you’re starting a tech company. What’s next? Luke recently addressed that question with twelve helpful opinions in a Software CEO feature: Startup, Funding, Design, and Hiring Tips from Puppet Labs CEO.

A few of our favorites:

Tip #1: Companies are organic beasts. Don’t expect to tame them.

Interestingly, Kanies doesn’t have formal education in software development; his Bachelor’s Degree, from Reed College, is in chemistry.

“Science degrees are one of the best ways to learn how to think,” Kanies says. “Running a company is all about understanding the system of running your company.

“The company I’m trying to run is more like an organic being than anything I’ve ever seen. You can’t just push it one way or another and get the expected result; it very much has a life of its own.”

Tip #8: Use your services people for usability testing.

“We have a professional services team that works with people all the time,” Kanies says. “They sit down with brand-new customers to watch how the software is used. If it sucks, our customers don’t just suffer, we do — because it takes our services people 10 times longer than it should.

“When I first started selling, this is exactly what I was doing: I’d work with the customer during the day, then go back to my hotel room at night and sand off the rough edges.”

If this topic is especially near and dear to your heart, come up to Portland this June and catch Luke’s “Diary of an Open Source Sysadmin Entrepreneur” session at Open Source Bridge.

Modeling Class Composition with Parameterized Classes

Posted on
By
Dan Bode
in
Blog, How to, Tips
Responses
3 Comments »

Parameterized classes are a huge step forward in the Puppet language. They allow configurations to be composed as a collection of well-defined interfaces, expose those interfaces to introspection, and eliminate any order dependencies commonly encountered with dynamic scoping and include.

Below, I will share my experiences and thoughts about how to model configurations with parameterized classes.

The main difference between parameterized classes and include is the singleton nature of parameterized classes. Like other resources, they can only be declared once to prevent potential parameter conflicts.

Because of this, classes should generally avoid declaring other classes.

A declaration of a class in a single location precludes its declaration elsewhere, creating a tight coupling between the two classes which can result in class incompatibilities and a loss of encapsulation.

Example:

A class called tomcat may depend on a class called Java.

class tomcat {
  class { 'java': }
  ...
}

Another class called activemq may also depend on Java.

class activemq {
  class { 'java': }}

now these classes are incompatible:

class { ['activemq', 'tomcat']: }

Any attempt to install both tomcat and activemq on a node would result in the error message:

Duplicate definition: Class[Java] is already defined …

The best approach is to push these kinds of dependencies towards the Node.

Each class can specify that they have a dependency on the class without having to actually declare it:

class tomcat {
  Class['java'] -> Class['tomcat']}
 
class activemq {
  Class['java'] -> Class['activemq']
}

This allows for a reasonable error message in cases where the dependency is not satisfied. The consumer of a module gets an actionable error:

Could not find resource 'Class[Java]' for relationship on 'Class[Tomcat]'

Which alerts them to the fact that the java class should be declared somewhere in their code.

node mynode {
  class { ['java', 'activemq', 'tomcat']: }
}

The ability for the consumer of these modules to specify their own Java class becomes even more attractive in the case where the class is configurable.

node mynode {
  class { 'java':
    java_home => '/home/dan/java_home/'
  }
  class { ['activemq', 'tomcat']: }
}

Class Composition Exceptions

As we saw above, a class should generally not compose its dependencies because it causes tight coupling.

There are some cases where tight coupling between classes is exactly what we want.

In cases where tight coupling is appropriate, I like to think about it as the delegation of a class as being the single point of authority over another class. Any other components that need the functionality of the declared class would now have to go through the delegated class.

Example 1

Consider the pattern in Puppet where a module is composed of classes representing the package, configuration, and service.

This allows for greater re-usability of modules by allowing the consumer to select which parts of the configuration they want to perform.

In this pattern it makes sense to also have a single class that represents the default behavior of installing the package, configuring and starting the service.

class ssh::client(
  $permitrootlogin = 'true'
) {
  Class['ssh::package'] -> Class['ssh::config']
  Class['ssh::config'] ~> Class['ssh::service']
  class { 'ssh::package': }
  class { 'ssh::config': permitrootlogin => $permitrootlogin }
  class { 'ssh:service': }
}

In this case, other parts of our Puppet configuration are prohibited from being able to directly declare the individual package, config, or service classes. Any attempt to do so would result in an error which is exactly the behavior that we want.

class { 'ssh::client': permitrootlogin => true}
class { 'ssh::config': permitrootlogin => false }
Duplicate definition: Class[Ssh::Config]

Example 2

It is also acceptable to compose classes in order to create classes used for assignment of roles to nodes.

Nodes can often be thought of as being classified by two main aspects, its platform (or base OS configuration) and by its role.

Since in general nodes can be thought about as having a single base OS configuration as well as role (where a role could be composed of multiple roles), these high level classes should be management as a composition of other classes without creating conflicts.

Known Issues

There are a few issues with the approaches mentioned above:

  1. The above syntax
    Class['java'] -> Class['tomcat']

    not only marks the class as being a dependency, but also specifies order dependencies. There is no way to mark a class as being required without effecting ordering.

  2. If this pattern becomes pervasive, it would be nice to have a keyword (like Self) that can be used to make relationships to the containing class.

    (#5824)

    This becomes particularly useful for defined resources:

    define apache::vhost() {
      Class['apache']->Apache::Vhost[$name]
    }
  3. Puppet disallows redeclaration of parameterized classes, even in cases where there are no conflicting parameters or even when there are no parameters at all. This makes it more difficult for classes to satisfy their own dependencies.

    (#7487)

Faces, Actions, Options, and Facades, Oh My!

Posted on
By
Daniel Pittman
in
Blog, How to, Tips
Responses
3 Comments »

This follows on from my previous post on Faces and starts to break down the components that make up the Faces system.

The fundamental components here are:

  1. Facades – the ways you can interact with your Faces and Actions.
  2. Faces – these represent a coherent subsystem, or modelled object or entity.
  3. Actions – these represent things you can do to the modelled object or entity.
  4. Options – these modify the way that actions work

Over the next few posts I will be breaking down these components and talking about what they mean, how they are used, and where we expect them to go in the future.

Please keep in mind, though, that Faces are a very new feature. We love the idea of having a good, accessible API in Ruby, as well as through the command line, and we are really looking forward to see what the creative community around Puppet can do with them.

When you are working through the issues, let us know what works, what doesn’t, and what we could do better by e-mailing the puppet-users list with “Faces” somewhere in your subject line. We are really looking forward to turning this into something super-useful, but we need to know what makes it useful for you for that to work.

Facades: Ways of Using Faces

Faces, and their Actions, provide a standard Ruby API over our models of the network, and the things you can command Puppet to do. That, alone, would provide a huge amount of value, but it isn’t the end of the story.

As part of Faces we have developed a Facade: every Face can be accessed, and every action invoked, from the command line. We map the arguments and options for the Face and Action down into command line options you can pass.

We see the Facades as one of the greatest strengths of Puppet Faces. They provide a huge amount of functionality for free today, and we expect them to do a whole lot more tomorrow. Adding a Facade that integration with MCollective, or that provides a generic HTTP RPC mechanism, would add huge value without needing to change the individual Faces.

The use of Facades also forces our Faces to support introspection: enough that you can write code that reasons about, and influences, the contained code. That also means that we can expect Faces to be able to effectively interact with each other using the same introspection mechanisms—code that manages code is easy, and routine, within Faces.

An Example of Face Introspection

One of the best examples of the power of Face introspection is the puppet help Face. This is just a regular Face that loads up other Faces and Actions, then uses introspection to fill out a documentation template with the Options, Actions, Faces, and other relevant details.

The help Face is 124 lines long, but a lot of that is backwards compatibility support for our older model applications, or documentation on
the Face itself. (Fun tip: puppet help help help will tell you all about the help Action of the help Face. ;)

Once we have dealt with all the legacy support, the actual meat of the function to get access to the face and action (less error handling) is only:

version ||= :current            # a sensible default...
face      = Puppet::Face[facename, version]
action    = face.get_action(actionname) if actionname

Once we have that we pass it off to the appropriate template for displaying global, Face, or Action help. The template uses some basic introspection to build up the help:

% action.options.map(&:get_option).each do |option|
 *  - <%= option.name %> - <%= option.summary %>
% end

In addition to the name and summary we can pull out details like a generated synopsis of the Action or Face, required arguments to each action, and other details required to invoke the system…

Stay tuned for the next installment, “About Faces, until we go in the right direction.”

Puppet 2.7.0 RC 2 is Released!

Posted on
By
Daniel Pittman
in
Blog, General News, Open Source, product release
Responses
0 Comments

We are happy to announce the second release candidate for Puppet 2.7.0.

This moves us firmly toward a final release of the 2.7.0 series, including 38 maintenance changes to improve the robustness of the software, and 85 distinct bug fixes resolving issues found with the RC or the new features included.

Notably, we have added a large set of improvements to the pkgutil package provider, resolving #4528, and making it better, faster, stronger, and harder working.

We have updated the default ACL on the puppet master, allowing nodes to request information about themselves from the configured node terminus. This exposes data from the ENC, as well as any static node configuration, to the same node; by default you still cannot query information across nodes without changing the ACL rules. This allows interesting changes, including the ability to inspect the ENC data from the client when using the new Faces-based agent.

Finally, we have made substantial usability improvements to the Faces; this fills out the set of plumbing features we expected to make available to Face authors in the initial release, resolves a swath of bugs found during testing, and adds documentation to all Faces and actions accessible through the puppet help subcommand.

Run puppet help to get started with the new help system; it includes all the details you need to explore the available subcommands and actions.

We hope that this is the final step in delivering a functional and capable Puppet 2.7.0 to the community. The second release candidate for Puppet 2.7.0 can be downloaded here.

Design Pattern for Dealing with Data

Posted on
By
Garrett Honeycutt
in
Blog, How to, Tips
Responses
10 Comments »

The goal of this design pattern is to keep configurable data in one place and make it friendly to an ENC. This addresses the problem of module reusability. If you have are keeping data in your module, then someone else cannot just reuse your module without making changes to the code. This pattern solves that problem allowing for reusable modules.

A best practice is to have a module named ‘common’ that contains code that is used by all of your nodes. This class is then included in all of your nodes.

node 'www1.foo.tld' {
 $env = 'prod'
 include common
}
 
node 'www2.foo.tld' {
 $env = 'prod'
 include common
}

I have begun using a subclass of common, named common::data that contains data about your systems and environments. It is called from the common class
as well as any class that needs to access that data.

class common {
 include common::data
}
 
 
 
class common::data {
 
 # Time - you should have 2n+1 time servers
 $ntpServerList = [ '0.rhel.pool.ntp.org', '1.rhel.pool.ntp.org',
'2.rhel.pool.ntp.org' ]
 
 # RHN
 $satelliteServer = 'satellite.foo.tld'
 
 # LSB Provider Name - http://www.lanana.org/lsbreg/providers/providers.txt
 $lsbProvider = "puppetlabs"
 
 # address to which commits to the puppet code will be delivered
 $puppetCommitsMailAddress = 'puppet-commits@foo.tld'
 
 # DNS
 $dnssearchpath = 'foo.tld'
 
 # ensure that the $env variable has been set
 # valid values are: 'lab', 'dev', 'qa', 'prod'
 if ! ( $env in [ 'lab', 'dev', 'qa', 'prod' ] ) {
   fail("common::data env must be one of: 'lab', 'dev', 'qa', or 'prod'")
 } # fi
 
 # environment specific data
 case $env {
   'lab': {
     $nameservers = [ '10.1.1.200', '10.1.1.201' ]
     $appFiler = '10.1.1.123'
   } # lab:
   'dev': {
     $nameservers = [ '10.1.2.200', '10.1.2.201' ]
     $appFiler = '10.1.2.123'
   } # dev:
   'qa': {
     $nameservers = [ '10.1.3.200', '10.1.3.201' ]
     $appFiler = '10.1.3.123'
   } # qa:
   'prod': {
     $nameservers = [ '10.1.4.200', '10.1.4.201' ]
     $appFiler = '10.1.4.123'
   } # prod:
   default: {
     fail("common::data env must be one of: 'lab', 'dev', 'qa', or 'prod'")
   } # default:
 } # case $env
 
 $contactEmail = $env ? {
   # default should be non-prod systems.
   default => 'preprod-root-list@foo.tld',
   # mail for prod systems
   prod    => 'root-list@foo.tld',
 } # $contactEmail
} # class common::data

From the above code you see that we are setting site wide data as well as using conditionals to set more specific data, specifically handling
environments using a case statement around a $env variable You could also base this on data center or whatever is specific to your setup.

Now to use that code in your manifests.

class dnsclient {
 
 include common::data
 
 $nameservers   = $common::data::nameservers
 $dnssearchpath = $common::data::dnssearchpath
 
 if ! $common::data::nameservers {
   fail("The variable 'common::data::nameservers' must be set")
 } # fi
 
 if ! $common::data::dnssearchpath {
   fail("The variable 'common::data::dnssearchpath' must be set")
 } # fi
 
 file { '/etc/resolv.conf':
   content => template('dnsclient/resolv.conf.erb'),
 } # file
} # class dnsclient

We are including common::data to ensure that the data is set before we attempt to access it. Next we verify that the variables contain data and fail the catalog if they do not. We can then use the variables in the template as follows.

resolv.conf.erb

# This file is being maintained by Puppet.
# DO NOT EDIT
 
search <%= dnssearchpath %>
<% nameservers.each do |nameserver| -%>
nameserver <%= nameserver %>
<% end -%>

This gives you one entry point in your code to specify data as opposed to repeating those variables in every node definition or storing them in the individual modules, since that would make them harder to re-use and share.

I encourage you to use this design pattern to make your modules reusable while allowing for data to be stored in one central location.

DevOps Survey: Join the Conversation

Posted on
By
michelle
in
Blog, Community, DevOps, Opinion and Interview
Responses
0 Comments

“DevOps” is an oft-discussed term—new discipline or just hype; movement or toolset? Whether you’re a DevOps devotee or detractor, we want to know what the state of DevOps is in your organization. Become a part of the spirited debate by sharing your thoughts in the first Puppet Labs DevOps survey!

The survey takes no longer than ten minutes to complete, and participation enters you into a drawing for some fabulous prizes. Three participants will receive $100 Amazon gift cards, and 25 lucky folks will win snazzy new Puppet t-shirts. And of course, all participants will get to reap the benefits of the survey results.

We’re hoping to hear from as many people as possible, so please encourage other interested folks to take the survey. Share it on facebook, tweet about it, or just bring it up around the water cooler.

Take the Survey

Puppet 2.6.8 Release

Posted on
By
Max Martin
in
Blog, Community, General News, product release
Responses
0 Comments

This week we released Puppet 2.6.8, the latest maintenance release in our 2.6 series. This release addresses several issues users have had with previous Puppet 2.6 releases, as well as adding in a couple of nifty new features:

  • (#2311) The deprecated darwinports provider has been replaced by a new MacPorts provider. This allows Puppet users managing Mac OS X systems to specify UNIX packages in their manifests to be built, installed, and managed by MacPorts.
  • (#4844) Puppet now features a Shell provider for the Exec type that allows users to specify inline shell code within a manifest, rather than referring to an external shell script file. The previous functionality of the Exec provider has been transferred to a POSIX provider for the Exec type.
  • (#5670) In previous 2.6 versions, a bug caused failing resources to notify other resources that depended on them, causing them to be trigged erroneously. As of 2.6.8 this behavior has been fixed, and failing resources will not cause refreshes in any resources than depend on them.
  • (#5477) Previously, if a catalog referred to a file that did not exist when it was compiled, it would not detect the file’s creation or any subsequent changes to it. This caused particular problems if the site.pp manifest did not exist yet when a puppetmaster’s catalog was compiled, requiring the master to be restarted. The fix we have implemented in 2.6.8 resolves this issue, so that puppetmasters will detect the creation of a site.pp file.

Relicensing Puppet to Apache 2.0

Posted on
By
luke
in
Blog, Community, Company, General News, Open Source, Opinion and Interview
Responses
4 Comments »

As most of you realize by now, Puppet 2.7 was released under the Apache 2.0 license instead of under the GPL, and Facter has already been released under the Apache license. My goal in this post is to explain why, and what effects you might expect to see as a result.

We’ve been talking about the possibility of this change for about two years, but it was only in the last six months that it’s been solidified as the right plan. For the vast majority of people, this change won’t affect you at all—Puppet is still open source, and under one of the most open licenses available. For a few of you, however, this license change will make it easier to embed Puppet into your software, ship it as part of a solution you’re building, or contribute code to it.

Our goal at Puppet Labs, and my goal since I started the company and the project, has been to have Puppet everywhere. I want every OS to ship with Puppet, I want every appliance to use Puppet, and I want every device that can’t run Puppet to speak its language and still integrate with it. Heck, I want to replace the package manifest formats with Puppet’s language. I’m a big believer in open source, but for strictly practical reasons. Puppet always had to be open source, because I couldn’t get the kind of ubiquity that I wanted with a purely commercial product—too much of our infrastructure is already open, and too many sysadmins understand the risks of locking yourself into software you can’t control and have no visibility into. Also, when I started the project I had no reputation and, um, no money; open source was a great way to bootstrap the company and project with very little expense.

With the GPL, realizing this goal of ubiquity was very difficult—some companies are comfortable with the GPL and have no problems including software released under it, but plenty of other companies are dreadfully afraid of the potential for it to force releasing of other code, whether or not that fear is rational. With Apache, these companies can focus on whether Puppet will make their solution better, and not worry about whether they’ll have to make significant business changes in order to use it. You won’t see me making arguments about freedom here (and you probably won’t have much luck engaging in such a conversation with me separately), but you have already seen that my practical perspective on this drives toward open source as much as anything else.

To me, the choice between GPL and Apache was never about which one is more free. It has always been about which one can best accomplish the goals of the project, and possibly which can do the best job of helping me fund those goals. In the end, choosing GPL means more companies that want to partner with us have to pay us, else their software must also be open, while choosing Apache means anyone can use, embed, and extend Puppet without having to worry about it affecting any other software. In other words, the choice between GPL and Apache for us as a company largely comes down to the GPL enabling fewer partnerships but some number of which that pay us, while Apache enables far more partnerships but few (if any) that pay us.

Puppet is primarily meant to be directly used by sysadmins. Switching to Apache, and hopefully seeing far more integrations and embedding, seems like a good trade-off. We give up business we might never have in exchange for relationships right now. Thus, the goal of ubiquity feels a bit closer.

I know this argument doesn’t persuade all of you, and I’ve already exchanged emails with one person who is convinced that this change means Puppet is no longer free software, but it is my sincere hope that we can quickly get back to writing and releasing software rather than talking about licenses. And, of course, it’s also my sincere hope that we see far more people embedding and integrating with Puppet.

As always, please contact me directly at luke@puppetlabs.com if you have any questions or concerns.

Puppet Faces: What the heck are Faces?

Posted on
By
Daniel Pittman
in
Blog, Community, General News, How to, product release, Tips
Responses
6 Comments »

Puppet Faces is one of the features I am proudest to deliver in the new Puppet 2.7.0 release. (Well, the RC series heading toward release, anyway.) They represent a new API for creating subcommands, as well as extending existing subcommands to add extra capabilities‚ without having to modify the supplied source code.

In this post, and the rest of the series, I am going to dive right on down deep into the technical details of Faces and how they can be used to program (and reprogram) a whole bunch of Puppet.

The key capabilities of Faces that make them important are:

  • Faces provide a defined, documented, robust API to the internals of Puppet.
  • They let you have that same robust, documented power for your extensions.
  • Faces have solid backward compatibility baked in to their core.
  • Faces give you access to a whole mess of capabilities for free.
  • You get a command line interface to your Face for free…
  • …and we have a whole lot more coming down the pipe, too.

Faces are all over backward compatibility!

While we were building the Faces system we put a whole lot of thought into how we can make it easy for you to extend Puppet ‚ and how to make sure that we don’t break your extensions when we need to change part of that API.

One of the first things you will run across is version numbers. When you refer to a Face, either to use it, or to extend it, you need to specify what version you are talking about:

    facts  = Puppet::Face[:facts, '0.0.1']
    status = Puppet::Face[:status, '0.0.1']

On the plus side, a Face is just a regular Ruby object with a whole bunch of behaviour and convention poured on top, so you can assign them to a local variable and then call it any number of times. So, don’t worry about having to type out that version number more than once in your code, eh.

Those version numbers show up when you are looking at writing or extending Faces, too; building up the face, or adding an action, requires you to specify what version of the Face it is built on:

    Puppet::Face.define(:node, '0.0.1') do
      action :example do
        option "--[no-]override"
        option "--fruit FLAVOUR"
        when_invoked |a, b, c, options|
          # Your code goes here...
        end
      end
    end

Now, the obvious question is why? Why do we force you to declare the version of the Face you are extending here? Our answer is pretty simple: because it means that we can change the current version of the face and things that you write will just keep working, unchanged.

I think that is one of the finest features we are delivering, a guarantee of backward compatibility in your extensions. Internally, we implement this by running your action in the context of the Face it was defined on: if you expect the 0.0.1 API we deliver, and things just work.

Obviously, if you want to use cool new features in a newer version of the API you have to migrate your code to handle any other changes … but at least the basics still work as you expected. Unless you are really clever…

    Puppet::Face.define(:example, '1.0.0') do
      action :wicked_smart do
        # documentation omitted for brevity...
        when_invoked do |alpha, beta, options|
          # This totally works, but don't tell anyone I told you.
          advanced_example = Puppet::Face[:example, '2.0.0']
          advanced_example.new_feature current_action_name(alpha, beta)
 
          # You can even do this, if you want ^W need to: ;)
          current_action_name(alpha, beta)
          advanced_example.current_action_name(beta, alpha)
        end
      end
    end

Anyway, that aside put aside for now, versioning is a huge part of why I think that Faces are cool: it is universal, it is powerful, and it lets us keep things working.

Our promise about versions

Any promise goes two ways, and versioning Faces like this is no exception. We don’t treat it lightly: we promise that in return for the extra little bit of work it takes to use the versioned API that we will try our level best to keep old API versions around and working for as long as possible.

(They might, heck, will‚ end up rewritten to just call up to the newer version of the API transparently, but you don’t need to care. We will keep the external API compatible so your code just works‚ the whole time.)

Which is pretty cool and all, but that isn’t it: we also promise to use our powers for evil, not just for good. Now that things you write are good and stable with versioning, we can release newer versions of the API sooner.

That means that you should see less time between a great idea and getting to use it. (Plus, you can reliably work out if it isn’t there on a machine, and fall back in your own code.)


Faces Versioning FAQ

Do I really have to specify a version number?

Technically, yes, but. We have two ways to do something smarter than giving the full version number. One is the ability to specify only part of the version number, and have the rest match “any version”:

  • “1.0.1″ will only match exactly version 1.0.1.
  • “1.0″ will match version 1.0.1, and also 1.0.2, but not 1.1.0.
  • “1″ will match anything with the major version 1.

We also offer, but do not encourage, the ability to refer to the :current version of a particular face, which will return the latest “officially blessed” version of the face found in your Ruby load path.

We don’t really recommend using it, though: saying “use :current” is the same as saying “please break when the API changes”. I don’t know about you, but that usually isn’t what I want to say when I write code ‚ even throw-away scripts. (I guess mostly because I noticed that I almost never get to throw those ones away, while the ones I expect to last? Pffft, gone.)

So, I would encourage you to always specify at least the major number of your version. It means less surprises, and less headaches, for you in the long run.

What do your version numbers mean?

The great thing about standards is that there are so many to choose from, as the saying goes, and version numbers are no different. Lots of people have their own ideas about what they mean, how they work, and how you should sort them.

Here at Puppet Labs we love inventing a new standard as much as the next programmer, but this time sense prevailed: we picked up the Semantic Versioning Specification and used that to define our Face versioning. (Plus, it was invented by one of the folks behind GitHub, and they are wicked-smart and all.)

Specifically, each digit has a defined meaning:

  • digit one, major: incremented for a backwards-incompatible API change.
  • digit two, minor: incremented for a backwards-compatible API change.
  • digit three, patch: incremented when change does not touch the API.

Not that you need to worry too much: if you want something simple, just use 1.0.0, 2.0.0, and so forth in your code, and pretend the rest of the digits don’t actually exist.