├── .gitignore ├── LICENSE ├── README.textile ├── disks-and-directories ├── README.md ├── manifests │ └── site.pp └── modules │ └── hadoop │ └── manifests │ ├── basedir.pp │ └── disk.pp ├── exported-expiration ├── README.rdoc ├── manifests │ └── site.pp ├── modules │ └── example │ │ └── manifests │ │ ├── expiringhost.pp │ │ └── exported │ │ └── expiringhost.pp └── runashost.sh ├── exported-resource-filtering ├── 1. Export stuff. ├── 2. testpuppet.rb (ruby puppet dsl) ├── 3. Run it. └── 4. Show it ├── function-returns-hash ├── README.md └── helloworld │ └── plugins │ └── puppet │ └── parser │ └── functions │ └── helloworld.rb ├── function-with-lookupvar ├── README.md └── helloworld │ └── plugins │ └── puppet │ └── parser │ └── functions │ └── helloworld.rb ├── inheritless-override ├── README └── example.pp ├── manage-remote-hack ├── README.rdoc └── puppet-package-over-ssh.rb ├── masterless ├── README.md ├── manifests │ └── site.pp └── modules │ └── os │ ├── files │ └── motd │ └── manifests │ └── init.pp ├── nodeless-puppet ├── README.rdoc ├── manifests │ └── site.pp └── modules │ └── truth │ ├── lib │ └── puppet │ │ └── parser │ │ └── functions │ │ └── has_role.rb │ └── manifests │ └── enforcer.pp ├── stages-example ├── README.rdoc └── stages-example.pp ├── swedishchef ├── README.md └── modules │ └── swedishchef │ └── plugins │ └── puppet │ └── provider │ └── package │ └── chef.rb ├── unmanaged-file-notify ├── README.rdoc ├── unmanaged-notify-puppet25.pp └── unmanaged-notify-puppet26.pp └── where-art-thou ├── README.md └── whereareyou ├── manifests └── init.pp └── templates └── example.erb /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | *.sqlite 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Jordan Sissel 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. Puppet Examples 2 | 3 | This project aims to document and demonstrate several uses of puppet. 4 | 5 | The main goal is to have a repository for me to publish any practical puppet 6 | patterns I find useful. I also will put some more esoteric and experimental 7 | tricks and hacks here. Lastly, I will try to put common "how to" answers that I 8 | find asked in the #puppet IRC channel (on freenode). 9 | 10 | h2. What do we have? 11 | 12 | h3. Practical 13 | 14 | * exported-expiration - an example of how to expire exported resources that 15 | haven't checked in recently. 16 | * nodeless-puppet - a method for managing puppet nodes purely through facts and 17 | properties. No 'node' definitions, no external node classifier, etc. 18 | 19 | h3. FAQ 20 | 21 | * unamanged-file-notify - FAQ: "How do I notify a service (or other resource) 22 | when I manually update a config file?" 23 | * stages-example - FAQ: "How do I use stages?" or "Stages aren't working for me" 24 | 25 | h3. Experimental 26 | 27 | * manage-remote-hack: Manage remote servers with puppet - this hack tricks puppet into doing 28 | package mangement over ssh, allowing you to manage remote servers that do not 29 | have puppet (or ruby) installed. 30 | -------------------------------------------------------------------------------- /disks-and-directories/README.md: -------------------------------------------------------------------------------- 1 | # Disks and Directories 2 | 3 | output: 4 | 5 | % sudo rm -rf /tmp/hadoop-example 6 | % sudo puppet apply --modulepath modules manifests/site.pp 7 | notice: Scope(Class[Hadoop::Basedir]): basedir: /tmp/hadoop-example 8 | notice: Scope(Hadoop::Disk[/dev/sdb1]): default: /tmp/hadoop-example/sdb1 9 | notice: Scope(Hadoop::Disk[/dev/sdc1]): default: /tmp/hadoop-example/sdc1 10 | notice: Scope(Hadoop::Disk[/dev/sdd1]): default: /tmp/hadoop-example/sdd1 11 | notice: Scope(Hadoop::Disk[/dev/sde1]): default: /tmp/hadoop-example/sde1 12 | notice: /Stage[main]/Hadoop::Basedir/File[/tmp/hadoop-example]/ensure: created 13 | notice: /Stage[main]//Hadoop::Disk[/dev/sdc1]/File[/tmp/hadoop-example/sdc1]/ensure: created 14 | notice: /Stage[main]//Hadoop::Disk[/dev/sdc1]/File[/tmp/hadoop-example/sdc1/mapred]/ensure: created 15 | notice: /Stage[main]//Hadoop::Disk[/dev/sdc1]/File[/tmp/hadoop-example/sdc1/hdfs]/ensure: created 16 | notice: /Stage[main]//Hadoop::Disk[/dev/sdb1]/File[/tmp/hadoop-example/sdb1]/ensure: created 17 | notice: /Stage[main]//Hadoop::Disk[/dev/sdb1]/File[/tmp/hadoop-example/sdb1/hdfs]/ensure: created 18 | notice: /Stage[main]//Hadoop::Disk[/dev/sdb1]/File[/tmp/hadoop-example/sdb1/mapred]/ensure: created 19 | notice: /Stage[main]//Hadoop::Disk[/dev/sde1]/File[/tmp/hadoop-example/sde1]/ensure: created 20 | notice: /Stage[main]//Hadoop::Disk[/dev/sde1]/File[/tmp/hadoop-example/sde1/hdfs]/ensure: created 21 | notice: /Stage[main]//Hadoop::Disk[/dev/sde1]/File[/tmp/hadoop-example/sde1/mapred]/ensure: created 22 | notice: /Stage[main]//Hadoop::Disk[/dev/sdd1]/File[/tmp/hadoop-example/sdd1]/ensure: created 23 | notice: /Stage[main]//Hadoop::Disk[/dev/sdd1]/File[/tmp/hadoop-example/sdd1/hdfs]/ensure: created 24 | notice: /Stage[main]//Hadoop::Disk[/dev/sdd1]/File[/tmp/hadoop-example/sdd1/mapred]/ensure: created 25 | 26 | -------------------------------------------------------------------------------- /disks-and-directories/manifests/site.pp: -------------------------------------------------------------------------------- 1 | $basedir = "/tmp/hadoop-example" 2 | class { "hadoop::basedir": path => $basedir } 3 | 4 | hadoop::disk { 5 | [ "/dev/sdb1", "/dev/sdc1", "/dev/sdd1", "/dev/sde1" ]: 6 | basedir => $basedir; 7 | } 8 | -------------------------------------------------------------------------------- /disks-and-directories/modules/hadoop/manifests/basedir.pp: -------------------------------------------------------------------------------- 1 | class hadoop::basedir($path="/srv/hadoop") { 2 | notice("basedir: $path") 3 | file { 4 | "$path": 5 | owner => "hadoop", 6 | ensure => directory; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /disks-and-directories/modules/hadoop/manifests/disk.pp: -------------------------------------------------------------------------------- 1 | define hadoop::disk($basedir="/srv/hadoop", $dir=undef) { 2 | include hadoop::basedir 3 | 4 | # the namevar for this should be the device name, like /dev/sdb1, or somesuch 5 | 6 | if ($dir == undef) { 7 | # default dir is the name of the device (sdb1 for /dev/sdb1) 8 | $real_dir = inline_template("<%= File.join(basedir, File.basename(title)) %>") 9 | notice("default: $real_dir") 10 | } else { 11 | $real_dir = "$basedir/$dir" 12 | notice("got: $real_dir") 13 | } 14 | 15 | 16 | # You don't really need to control the mounts from here, but you can if you want. 17 | #mount { 18 | #"$path": 19 | #atboot => true, 20 | #device => $title, 21 | #ensure => "mounted", 22 | #options => "noatime", 23 | #require => File[$path]; 24 | #} 25 | 26 | file { 27 | "$real_dir": 28 | ensure => directory, 29 | owner => "hadoop", 30 | require => Class["hadoop::basedir"]; 31 | "$real_dir/mapred": 32 | #require => Mount["$path"], 33 | owner => "hadoop", 34 | ensure => directory; 35 | "$real_dir/hdfs": 36 | #require => Mount["$path"], 37 | owner => "hadoop", 38 | ensure => directory; 39 | 40 | # Put other paths here... 41 | #"$real_dir/mapred/tmp": 42 | # ensure => directory, 43 | # owner => "hadoop", 44 | # require => Mount["$path"]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /exported-expiration/README.rdoc: -------------------------------------------------------------------------------- 1 | == Exported Resources that expire 2 | 3 | === Why? 4 | 5 | When a host is removed from your infrastructure or otherwise ceases to function 6 | (intentionally) as a particular role, it's useful to notify the rest of 7 | your infrastructure - your monitoring, for example. In a pure-puppet solution, 8 | you would do this using exported resources. One node could export the fact 9 | that it was an apache server and could inform nagios how to monitor it, and 10 | your nagios node(s) would import these resources and configure themselves 11 | to monitor appropriately. 12 | 13 | However, it's not simple to remove exported resources from puppet's 14 | storeconfig. Further, it's quite often that you want to not only remove the 15 | resource from the database but also from the hosts those resources were 16 | exported to, right? We want nagios to stop monitoring that server that was 17 | decommissioned. 18 | 19 | This demo gives an example of using an exported custom define with a timestamp 20 | attribute to signal 'expired' resources as a way of implementing such a 21 | solution. 22 | 23 | === How it works 24 | 25 | I only use exported custom defines. That is, I never directly export a file, 26 | host, nagios_service, or package. I always wrap them in custom defines and 27 | then export that. Why? Because this lets me change the implementation of what 28 | is exported without waiting for more puppet runs and because I can add cool 29 | things like timestamps or other attributes not normally available in whatever 30 | actual resource I am using. 31 | 32 | In this example, I wrap the 'host' resource with 33 | example::exported::expiringhost. That define (example::exported::expiringhost) 34 | wraps example::expiringhost and exports it. 35 | 36 | This lets nodes simply do this: 37 | example::exported::expiringhost { 38 | "$fqdn": ip => $ipaddress_eth0 39 | } 40 | 41 | And that define will export @@example::expiringhost with a timestamp of now. 42 | 43 | === About this demo 44 | 45 | The script 'runashost.sh' runs puppet in masterless mode (applies a puppet 46 | manifest itself) and lets you spoof the hostname and the ip address of the 47 | machine to puppet so you can easily test this with multiple 'hosts' from the 48 | same machine. 49 | 50 | Also, since I want this demo to be able to run as a normal user for testing and 51 | standalone, I choose sqlite3 to put storeconfigs in and I use the 'host' 52 | resource's "target" attribute to point at /tmp/expiring-hosts-example-output 53 | 54 | === Example run 55 | 56 | As mentioned above, the 'hosts' file managed by this demo is: 57 | /tmp/expiring-hosts-example-output 58 | 59 | Operations: (T is time since start in seconds) 60 | * (T=0) Add a host 'one.example.com' 61 | * (T=15) Add a host 'two.example.com' 62 | * (T=70) Add a host 'three.example.com' 63 | 64 | After the 3rd host is added (at T=70), puppet will have expired 65 | 'one.example.com' for being too old and the hosts file will only contain 66 | 'two.example.com' and 'three.example.com' 67 | 68 | % ./runashost.sh one.example.com 10.0.0.1 69 | + export FACTER_fqdn=one.example.com 70 | + export FACTER_ipaddress_eth0=10.0.0.1 71 | + dbflags=--dbadapter sqlite3 --dblocation storeconfigs.sqlite 72 | + puppet --certname one.example.com --node_name fqdn --verbose --dbadapter sqlite3 --dblocation storeconfigs.sqlite --storeconfigs --modulepath modules manifests/site.pp 73 | info: Connecting to sqlite3 database: /home/jls/projects/puppet-examples/exported-expiration/storeconfigs.sqlite 74 | info: Caching facts for one.example.com 75 | notice: Scope(Example::Exported::Expiringhost[one.example.com]): Exporting host: one.example.com => 10.0.0.1 76 | notice: Scope(Example::Expiringhost[one.example.com]): Found recently-active [one.example.com] (age: 0.2286) 77 | info: Caching catalog for one.example.com 78 | info: Applying configuration version '1288853587' 79 | 80 | % ./runashost.sh two.example.com 10.0.0.2 81 | + export FACTER_fqdn=two.example.com 82 | + export FACTER_ipaddress_eth0=10.0.0.2 83 | + dbflags=--dbadapter sqlite3 --dblocation storeconfigs.sqlite 84 | + puppet --certname two.example.com --node_name fqdn --verbose --dbadapter sqlite3 --dblocation storeconfigs.sqlite --storeconfigs --modulepath modules manifests/site.pp 85 | info: Connecting to sqlite3 database: /home/jls/projects/puppet-examples/exported-expiration/storeconfigs.sqlite 86 | info: Caching facts for two.example.com 87 | notice: Scope(Example::Exported::Expiringhost[two.example.com]): Exporting host: two.example.com => 10.0.0.2 88 | notice: Scope(Example::Expiringhost[one.example.com]): Found recently-active [one.example.com] (age: 22.197815) 89 | notice: Scope(Example::Expiringhost[two.example.com]): Found recently-active [two.example.com] (age: 0.298657) 90 | info: Caching catalog for two.example.com 91 | info: Applying configuration version '1288853608' 92 | notice: /Stage[main]//Node[default]/Example::Exported::Expiringhost[two.example.com]/Example::Expiringhost[two.example.com]/Host[two.example.com]/ip: ip changed '10.0.0.1' to '10.0.0.2' 93 | info: FileBucket adding /tmp/expiring-hosts-example-output as {md5}0cb3c66936c6ebfe7434a5979b3d6f34 94 | 95 | % ./runashost.sh three.example.com 10.0.0.3 96 | + export FACTER_fqdn=three.example.com 97 | + export FACTER_ipaddress_eth0=10.0.0.3 98 | + dbflags=--dbadapter sqlite3 --dblocation storeconfigs.sqlite 99 | + puppet --certname three.example.com --node_name fqdn --verbose --dbadapter sqlite3 --dblocation storeconfigs.sqlite --storeconfigs --modulepath modules manifests/site.pp 100 | info: Connecting to sqlite3 database: /home/jls/projects/puppet-examples/exported-expiration/storeconfigs.sqlite 101 | info: Caching facts for three.example.com 102 | notice: Scope(Example::Exported::Expiringhost[three.example.com]): Exporting host: three.example.com => 10.0.0.3 103 | notice: Scope(Example::Expiringhost[one.example.com]): Expiring resource [one.example.com] due to age > 60 (actual: 72.42003) 104 | notice: Scope(Example::Expiringhost[two.example.com]): Found recently-active [two.example.com] (age: 50.465772) 105 | notice: Scope(Example::Expiringhost[three.example.com]): Found recently-active [three.example.com] (age: 0.500446) 106 | info: Caching catalog for three.example.com 107 | info: Applying configuration version '1288853659' 108 | notice: /Stage[main]//Node[default]/Example::Expiringhost[one.example.com]/Host[one.example.com]/ensure: removed 109 | info: FileBucket adding /tmp/expiring-hosts-example-output as {md5}b02db36d645e335ee0d147de653c2004 110 | notice: /Stage[main]//Node[default]/Example::Exported::Expiringhost[three.example.com]/Example::Expiringhost[three.example.com]/Host[three.example.com]/ip: ip changed '10.0.0.2' to '10.0.0.3' 111 | 112 | % cat /tmp/expiring-hosts-example-output 113 | # HEADER: This file was autogenerated at Wed Nov 03 23:54:21 -0700 2010 114 | # HEADER: by puppet. While it can still be managed manually, it 115 | # HEADER: is definitely not recommended. 116 | 10.0.0.2 two.example.com 117 | 10.0.0.3 three.example.com 118 | 119 | # Now, a few minutes later, rerun 'three.example.com' and the first two hosts 120 | # (one.example.com, two.example.com) will have expired from age. 121 | % ./runashost.sh three.example.com 10.0.0.3 122 | + export FACTER_fqdn=three.example.com 123 | + export FACTER_ipaddress_eth0=10.0.0.3 124 | + dbflags=--dbadapter sqlite3 --dblocation storeconfigs.sqlite 125 | + puppet --certname three.example.com --node_name fqdn --verbose --dbadapter sqlite3 --dblocation storeconfigs.sqlite --storeconfigs --modulepath modules manifests/site.pp 126 | info: Connecting to sqlite3 database: /home/jls/projects/puppet-examples/exported-expiration/storeconfigs.sqlite 127 | notice: Scope(Example::Exported::Expiringhost[three.example.com]): Exporting host: three.example.com => 10.0.0.3 128 | notice: Scope(Example::Expiringhost[one.example.com]): Expiring resource [one.example.com] due to age > 60 (actual: 591.973301) 129 | notice: Scope(Example::Expiringhost[two.example.com]): Expiring resource [two.example.com] due to age > 60 (actual: 570.424178) 130 | notice: Scope(Example::Expiringhost[three.example.com]): Found recently-active [three.example.com] (age: 1.489464) 131 | info: Caching catalog for three.example.com 132 | info: Applying configuration version '1288854178' 133 | notice: /Stage[main]//Node[default]/Example::Expiringhost[two.example.com]/Host[two.example.com]/ensure: removed 134 | info: FileBucket adding /tmp/expiring-hosts-example-output as {md5}a65177536c8f9d89044e0fa2d8cedfd9 135 | 136 | # Voila! 137 | % cat /tmp/expiring-hosts-example-output 138 | # HEADER: This file was autogenerated at Thu Nov 04 00:03:00 -0700 2010 139 | # HEADER: by puppet. While it can still be managed manually, it 140 | # HEADER: is definitely not recommended. 141 | 10.0.0.3 three.example.com 142 | 143 | Notice how puppet sees that 'one.example.com' and 'two.example.com' have 144 | timestamps that are older and as a result we consider them expired. 145 | 146 | You can use this to purge old/unused exported resources of any kind: file, 147 | user, package, service, nagios_service, etc. 148 | 149 | === Conclusion 150 | 151 | Using timestamps makes for a cool demo, but it may not be practical in 152 | production. Obviously, 60 seconds is way too short, but a reasonable age like 153 | an hour, or a day, or a week, could work for your infrastructure. However, what 154 | if your puppet deployment is broken or hosts just can't check in for some reason? 155 | 156 | To solve the above, you probably want to use something more explicit than 157 | 'timestamp' as an expiry value. You could support an attribute 'expired' to 158 | flag deletion instead of computing age by timestamp. Setting 'expired' would 159 | be an external action perhaps by a central truth source or by a human - today, 160 | though, this would require manipulating the database backing the storeconfigs 161 | as there is no interface in puppet (yet) for manipulating exported resources 162 | outside of a puppet run. 163 | -------------------------------------------------------------------------------- /exported-expiration/manifests/site.pp: -------------------------------------------------------------------------------- 1 | node default { 2 | 3 | # We wrap 'example::expiringhost' with this custom define so we don't 4 | # have to think about timestamps. 5 | example::exported::expiringhost { 6 | $fqdn: ip => $ipaddress_eth0; 7 | } 8 | 9 | Example::Expiringhost <<| |>> 10 | } 11 | -------------------------------------------------------------------------------- /exported-expiration/modules/example/manifests/expiringhost.pp: -------------------------------------------------------------------------------- 1 | define example::expiringhost($ip, $timestamp) { 2 | 3 | # Calculate the age of this resource by comparing 'now' against $timestamp 4 | $age = inline_template("<%= require 'time'; Time.now - Time.parse(timestamp) %>") 5 | 6 | # Max age, in seconds. 7 | $maxage = 60 8 | 9 | if $age > $maxage { 10 | $expired = true 11 | notice("Expiring resource $class[$name] due to age > $maxage (actual: $age)") 12 | } else { 13 | $expired = false 14 | notice("Found recently-active $class[$name] (age: $age)") 15 | } 16 | 17 | # I set target to a /tmp path so you can run this example as non-root. 18 | # In production, you probabyl won't set target as it defaults to /etc/hosts 19 | # (or wherever puppet thinks your platform wants it) 20 | host { 21 | $name: 22 | ip => $ip, 23 | target => "/tmp/expiring-hosts-example-output", 24 | ensure => $expired ? { true => absent, false => present }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /exported-expiration/modules/example/manifests/exported/expiringhost.pp: -------------------------------------------------------------------------------- 1 | define example::exported::expiringhost($ip) { 2 | notice("Exporting host: $name => $ip") 3 | @@example::expiringhost { 4 | $name: 5 | ip => $ip, 6 | timestamp => inline_template("<%= Time.now.strftime('%Y-%m-%dT%H:%M:%S%z') %>"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /exported-expiration/runashost.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$#" -ne 2 ] ; then 4 | echo "Usage: $0 " 5 | echo "This lets you fake a puppet run as the given host with the ip set as ipaddress_eth0" 6 | exit 1 7 | fi 8 | 9 | set -x 10 | 11 | # I set FACTER_fqdn and FACTER_ipaddress_eth0 so we can easily pretend to be another 12 | # host. This allows us to show this demo without requiring a run on different hosts. 13 | export FACTER_fqdn=$1 14 | export FACTER_ipaddress_eth0=$2 15 | 16 | # We could use mysql... 17 | #dbflags="--dbadapter mysql --dbuser puppet --dbpassword puppet --dbserver snack.home" 18 | 19 | # But for this example let's stay local and use sqlite. 20 | # You'll need the 'sqlite3-ruby' and 'activerecord' gem installed 21 | # for exported resources to sqlite db to work. 22 | dbflags="--dbadapter sqlite3 --dblocation storeconfigs.sqlite" 23 | 24 | # Also set --certname here so we are really able to masquerade as another host 25 | # Setting 'node_name' to fqdn makes puppet pull the identifier used for the 26 | # node name (for exported resources, etc) from the fqdn fact. 27 | # In practice, you don't need to se these flags (--certname, --node_name, and 28 | # the FACTER_* overrides), but I need them for the purposes of this demo. 29 | 30 | puppet --certname $FACTER_fqdn --node_name fqdn --verbose \ 31 | $dbflags --storeconfigs --modulepath modules manifests/site.pp 32 | -------------------------------------------------------------------------------- /exported-resource-filtering/1. Export stuff.: -------------------------------------------------------------------------------- 1 | FACTER_fqdn=host1 puppet --certname host1 --node_name fqdn --storeconfigs --dblocation /tmp/puppet.sqlite -e '@@nagios_hostgroup { "mygroup": notes => "Hello"; }' 2 | FACTER_fqdn=host2 puppet --certname host2 --node_name fqdn --storeconfigs --dblocation /tmp/puppet.sqlite -e '@@nagios_hostgroup { "mygroup": notes => "Hello"; }' 3 | -------------------------------------------------------------------------------- /exported-resource-filtering/2. testpuppet.rb (ruby puppet dsl): -------------------------------------------------------------------------------- 1 | 2 | # Define a class that provides a bunch of Nagios_hostgroup resources. 3 | # These resources are defined by already-exported Nagios_hostgroups. 4 | # This will also go through and prune out duplicate exports. 5 | require "puppet/rails/resource" 6 | 7 | hostclass :happy do 8 | found = Set.new 9 | Puppet::Rails::Resource.find_all_by_restype("Nagios_hostgroup").each do |resource| 10 | # Skip duplicate resource titles 11 | key = [resource.restype, resource.title] 12 | next if found.include?(key) 13 | found << key 14 | 15 | # Build a hash of parameters for this resource 16 | params = {} 17 | resource.param_values.each do |pv| 18 | name = pv.param_name.name 19 | params[name] = pv.value 20 | end 21 | 22 | # Override the target file for this example 23 | params[:target] = "/tmp/example_nagios_config" 24 | 25 | # Now create the resource. 26 | nagios_hostgroup resource.title, params 27 | end 28 | end 29 | 30 | node :default do 31 | include "happy" 32 | end 33 | -------------------------------------------------------------------------------- /exported-resource-filtering/3. Run it.: -------------------------------------------------------------------------------- 1 | % puppet apply --verbose --storeconfig --dblocation /tmp/puppet.sqlite testpuppet.rb 2 | info: Connecting to sqlite3 database: /tmp/puppet.sqlite 3 | info: Caching catalog for snack.home 4 | info: Applying configuration version '1291931649' 5 | notice: /Stage[main]/Happy/Nagios_hostgroup[mygroup]/ensure: created 6 | -------------------------------------------------------------------------------- /exported-resource-filtering/4. Show it: -------------------------------------------------------------------------------- 1 | snack(~) % cat /tmp/example_nagios_config 2 | # HEADER: This file was autogenerated at Thu Dec 09 13:54:10 -0800 2010 3 | # HEADER: by puppet. While it can still be managed manually, it 4 | # HEADER: is definitely not recommended. 5 | define hostgroup { 6 | notes Hello 7 | hostgroup_name mygroup 8 | } 9 | -------------------------------------------------------------------------------- /function-returns-hash/README.md: -------------------------------------------------------------------------------- 1 | # Functions returning a hash 2 | 3 | From this directory, run: 4 | 5 | puppet apply --modulepath . -e '$myvar = helloworld() notice($myvar["Hello"])' 6 | 7 | Output: 8 | 9 | notice: Scope(Class[main]): World 10 | 11 | -------------------------------------------------------------------------------- /function-returns-hash/helloworld/plugins/puppet/parser/functions/helloworld.rb: -------------------------------------------------------------------------------- 1 | # Example puppet function that returns a hash. 2 | 3 | module Puppet::Parser::Functions 4 | newfunction(:helloworld, :type => :rvalue) do |args| 5 | return { "Hello" => "World" } 6 | end # puppet function helloworld 7 | end # module Puppet::Parser::Functions 8 | 9 | -------------------------------------------------------------------------------- /function-with-lookupvar/README.md: -------------------------------------------------------------------------------- 1 | # Functions invoking lookupvar 2 | 3 | From this directory, run: 4 | 5 | puppet apply --modulepath . -e '$fizzle = "testing" notice(helloworld())' 6 | 7 | Output: 8 | 9 | notice: Scope(Class[main]): testing 10 | 11 | -------------------------------------------------------------------------------- /function-with-lookupvar/helloworld/plugins/puppet/parser/functions/helloworld.rb: -------------------------------------------------------------------------------- 1 | # Example puppet function that returns a hash. 2 | 3 | module Puppet::Parser::Functions 4 | newfunction(:helloworld, :type => :rvalue) do |args| 5 | return lookupvar("fizzle") 6 | end # puppet function helloworld 7 | end # module Puppet::Parser::Functions 8 | 9 | -------------------------------------------------------------------------------- /inheritless-override/README: -------------------------------------------------------------------------------- 1 | Lots of folks have problems with class inheritance in puppet. Fundamentally, I think this is a symptom of a greater problem that: 2 | * puppet classes are singletons in an OOP-sense 3 | * inheritance behavior smells undefined if class b and c are included, and both inherit 'a' 4 | * variable scoping in puppet is confusing 5 | * most folks want 'include' when they say 'inherit' 6 | 7 | So, if you *really* want overrides, but can't stand inheritance, this example 8 | is a solution. It demonstrates one class (bar) including another (foo) and 9 | overriding a resource value from the included class. 10 | 11 | % puppet apply example.pp 12 | notice: /Stage[main]/Foo/File[/tmp/hello]/ensure: defined content as '{md5}09421fa0d48df35164511391b40b2406' 13 | 14 | % cat /tmp/hello 15 | override! 16 | 17 | 18 | -------------------------------------------------------------------------------- /inheritless-override/example.pp: -------------------------------------------------------------------------------- 1 | class foo { 2 | file { 3 | "/tmp/hello": 4 | content => "Hello world\n"; 5 | } 6 | } 7 | 8 | class bar { 9 | include foo 10 | 11 | File <| title == "/tmp/hello" |> { 12 | content => "override!\n" 13 | } 14 | } 15 | 16 | include bar 17 | -------------------------------------------------------------------------------- /manage-remote-hack/README.rdoc: -------------------------------------------------------------------------------- 1 | == Manage remote servers over ssh with puppet 2 | 3 | This hack shows how to trick puppet into managing packes on remote machines. 4 | 5 | This is cool because you can use it to manage servers that do not have puppet 6 | installed at all - it's all handled over ssh connections and remote command 7 | execution. You could use this, for example, to bootstrap a brand new server 8 | that has no puppet at all! 9 | 10 | === How it works 11 | 12 | This ruby script overrides Puppet::Util::execute which is how puppet executes 13 | packager commands like apt-get, apt-cache, yum, rpm, gem, etc. I take the command 14 | and prefix it with 'ssh $host sudo' so it runs all the commands on the remote 15 | server. 16 | 17 | === Limitations 18 | 19 | I only hack support for things that use external commands (like packager 20 | tools), so things like file resources won't work, though it shouldn't be too 21 | hard to hack that, too. 22 | -------------------------------------------------------------------------------- /manage-remote-hack/puppet-package-over-ssh.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "puppet" 3 | 4 | $host = ARGV.shift 5 | name = ARGV.shift 6 | action = ARGV.shift 7 | 8 | # Monkeypatch so our executed commands get sent over ssh. 9 | module Puppet::Util 10 | alias original_execute execute 11 | 12 | def execute(command, *args) 13 | # Shell escapings 14 | newcommand = command.collect { |arg| arg.to_s.gsub(/["$\\]/) { "\\#{$&}" } } \ 15 | .collect { |arg| "\"#{arg}\"" } 16 | 17 | # ssh to our host, run sudo. 18 | newcommand = ["ssh", $host, "sudo", *newcommand] 19 | original_execute(newcommand, *args) 20 | end 21 | end 22 | 23 | begin 24 | package = Puppet::Type.type(:package).new(:name => name) 25 | provider = package.provider 26 | # Should be 'install' or 'uninstall' 27 | provider.send action 28 | 29 | puts("#{package} #{action} status: #{provider.properties.inspect}") 30 | rescue Exception => e 31 | puts("Could not #{action} package #{package}: #{e}") 32 | end 33 | 34 | -------------------------------------------------------------------------------- /masterless/README.md: -------------------------------------------------------------------------------- 1 | # Masterless Example 2 | 3 | Here's how to run this: 4 | 5 | puppet apply --modulepath ./modules manifests/site.pp 6 | 7 | This is running puppet local (aka, masterless), with no puppet master. 8 | 9 | File urls are relative to the local puppet, too. 10 | 11 | Stuff like this; 12 | 13 | puppet:///modules/foo/bar.txt 14 | 15 | Will be relative to --modulepath, in the above example, as: 16 | 17 | ./modules/foo/files/bar.txt 18 | 19 | ## Running it: 20 | 21 | % sudo puppet apply --modulepath ./modules manifests/site.pp 22 | notice: /Stage[main]/Os/File[/etc/motd]/ensure: defined content as '{md5}9f4ac4b9fdc3de446fe89b9374229950' 23 | 24 | ## Masterless Features 25 | 26 | * storeconfigs still works; requires MySQL if you want nodes to actually share 27 | data (sqlite is local to each puppet node in masterless) 28 | * files can come from the local puppet, too. You just need to ship them to 29 | /etc/puppet/modules or wherever your moduledir is. 30 | 31 | I still use file resources just like I would with a master/agent configuration 32 | in puppet. puppet:/// urls, as mentioned, still work as expected :) 33 | -------------------------------------------------------------------------------- /masterless/manifests/site.pp: -------------------------------------------------------------------------------- 1 | node default { 2 | include os 3 | } 4 | -------------------------------------------------------------------------------- /masterless/modules/os/files/motd: -------------------------------------------------------------------------------- 1 | What MOTD is complete without figlet? 2 | 3 | _ __ _ _ _ __ _ __ ___| |_| | 4 | | '_ \| | | | '_ \| '_ \ / _ \ __| | 5 | | |_) | |_| | |_) | |_) | __/ |_|_| 6 | | .__/ \__,_| .__/| .__/ \___|\__(_) 7 | |_| |_| |_| 8 | 9 | 10 | -------------------------------------------------------------------------------- /masterless/modules/os/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class os { 2 | file { 3 | "/etc/motd": 4 | ensure => file, 5 | source => "puppet:///modules/os/motd"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /nodeless-puppet/README.rdoc: -------------------------------------------------------------------------------- 1 | == Nodeless puppet 2 | 3 | You can run this example without any preconfigured puppet infrastructure. You 4 | only need puppet installed. It uses puppet's standalone feature ('puppet apply' 5 | in puppet >= 2.6.0. 6 | 7 | === Background 8 | 9 | Defining nodes using puppet's node syntax doesn't scale very well, and it may 10 | even duplicate data - You might have an asset or config database with the same 11 | information. To try and solve that, puppet allows you to specify an external 12 | tool to service node information lookup called an external node classifier: 13 | 14 | External nodes docs: http://docs.puppetlabs.com/guides/external_nodes.html 15 | 16 | When I deployed an external node classifier, I ended up putting some business 17 | logic into the classifier script. For example, if the host had a role 18 | "loadbalancer" or "loadbalancer-dev" I wanted to include the class "haproxy", 19 | otherwise include "haproxy::disable". 20 | 21 | === Why? 22 | 23 | The problem with this scenario is that if you break your node classifier 24 | (syntax error or other problems), your puppet infrastructure stops working. You 25 | can't even push a new classifier to your master with puppet - it's a fix 26 | that requires manual work to repair *or* non-puppet tools to automate the fix. 27 | 28 | I came up with as solution - use facts and properties only and put *all* of the 29 | logic inside puppet manifests. 30 | 31 | I believe the node classifier should not have any business logic in it. It should 32 | exist only to find a list of properties (and classes) for a given node, and ship 33 | them to puppet in YAML. Further, in this example, we would only have the node 34 | classifier report properties and one class, "truth::enforcer". 35 | 36 | This works awesomely for applying roles and for applying anticlasses (What's an 37 | anticlass? See the bottom of this doc) 38 | 39 | === How it works 40 | 41 | I have a single 'node' definition in my site.pp: 42 | 43 | node default { 44 | include truth::enforcer 45 | } 46 | 47 | Then I have a module 'truth' that provides the truth::enforcer class, which 48 | looks like this: 49 | 50 | class truth::enforcer { 51 | if has_role("loadbalancer") { 52 | include loadbalancer::service 53 | } else { 54 | # Otherwise, this machine is not a loadbalancer 55 | include loadbalancer::remove 56 | } 57 | } 58 | In the above example, "has_role" is actually a custom parser function I wrote 59 | that checks a few facts to determine if a host has a given role. That function 60 | (has_role) is included in this example - see 61 | modules/truth/plugins/puppet/parser/functions/has_role.rb 62 | 63 | I believe putting business logic in puppet manifests is the best way to achieve 64 | turnup *and* turndown in your infrastructure. Further, it concentrates the 65 | "configuration" in one place. 66 | 67 | You can specify truth elsewhere (what machines you have, what roles are on each 68 | machine, etc) - but keep the logic setup/teardown logic inside puppet. 69 | 70 | === Anticlass ? 71 | 72 | In puppet, a class applies some configuration. It's common to want to be able 73 | to reverse a class or in general, clean up and remove any changes you want. 74 | In discussions with fellow puppet users, the name 'anticlass' is often given to 75 | classes that exist to uninstall or clean up previous work. 76 | 77 | For example, you might have a class that installs apache, configures vhosts, 78 | makes sure it's running, etc etc, and then an anticlass that uninstalls apache. 79 | 80 | Code example: 81 | 82 | # Very simple class for installing apache 83 | class apache { 84 | package { 85 | "apache2": ensure => present; 86 | } 87 | } 88 | 89 | # This is the 'anticlass' for apache 90 | class apache::remove { 91 | package { 92 | "apache2": ensure => absent; 93 | } 94 | 95 | file { 96 | # Remove any leftover config files. 97 | "/etc/apache2": 98 | ensure => absent, 99 | force => true; 100 | 101 | # Remove any leftover apache logs. 102 | "/var/log/apache2": 103 | ensure => absent, 104 | force => true; 105 | } 106 | } 107 | 108 | 109 | Now, in your truth::enforcer, you could have: 110 | 111 | class truth::enforcer { 112 | if has_role("frontend") { 113 | include frontend # presumably, includes 'apache' 114 | } 115 | 116 | if has_role("monitor") { 117 | include nagios # if your monitor is nagios, and wants apache! 118 | } 119 | 120 | if !has_role("frontend") and !has_role("monitor") { 121 | include apache::remove 122 | } 123 | } 124 | 125 | === Example Running 126 | 127 | Facter lets you pass facts using the environment. Any environment values 128 | beginning FACTER_ will turn in to facts. For example, FACTER_foo=hello will set 129 | $foo == "hello" in puppet and facter. 130 | 131 | For running, I start puppet from this directory (the nodeless-puppet 132 | directory). If you don't, you'll have to specify the full path to the 'modules' 133 | and 'manifests/site.pp' paths. 134 | 135 | # Try a first run, with no server_tags and thus no roles: 136 | % puppet --modulepath ./modules manifests/site.pp 137 | notice: Scope(Class[Truth::Enforcer]): I am not a loadbalancer 138 | notice: Scope(Class[Truth::Enforcer]): I am not a database 139 | notice: Scope(Class[Truth::Enforcer]): I am a hadoop client 140 | notice: Scope(Class[Truth::Enforcer]): I am not a hadoop-worker 141 | notice: Scope(Class[Truth::Enforcer]): I am not a hadoop-master 142 | 143 | Our truth enforcer finds no active roles for this server, but because we have 144 | extra logic in our configuration - it would configure itself as a hadoop 145 | client. 146 | 147 | # Try a second run. Be a load balancer: 148 | % FACTER_server_tags="role:loadbalancer=true" puppet --modulepath ./modules manifests/site.pp 149 | notice: Scope(Class[Truth::Enforcer]): I am a loadbalancer 150 | notice: Scope(Class[Truth::Enforcer]): I am not a database 151 | notice: Scope(Class[Truth::Enforcer]): I am a hadoop client 152 | notice: Scope(Class[Truth::Enforcer]): I am not a hadoop-worker 153 | notice: Scope(Class[Truth::Enforcer]): I am not a hadoop-master 154 | 155 | Now it knows it should configure itself as a loadbalancer. Easy. All fact-driven! 156 | 157 | How about testing the error condition in our config - We reject attempts to be 158 | both a "hadoop-master" and "hadoop-worker": 159 | 160 | % FACTER_server_tags="role:hadoop-master=true,role:hadoop-worker=true" puppet --modulepath ./modules manifests/site.pp 161 | notice: Scope(Class[Truth::Enforcer]): I am not a loadbalancer 162 | notice: Scope(Class[Truth::Enforcer]): I am not a database 163 | Cannot be both hadoop-worker and hadoop-master. $server_tags is 'role:hadoop-master=true,role:hadoop-worker=true' at /home/jls/projects/puppet-examples/nodeless-puppet/./modules/truth/manifests/enforcer.pp:21 on node snack.home 164 | % echo $? 165 | 1 166 | 167 | Puppet fails out and exits nonzero because we said the configuration attempted 168 | was invalid. Awesome! 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /nodeless-puppet/manifests/site.pp: -------------------------------------------------------------------------------- 1 | node default { 2 | include truth::enforcer 3 | } 4 | -------------------------------------------------------------------------------- /nodeless-puppet/modules/truth/lib/puppet/parser/functions/has_role.rb: -------------------------------------------------------------------------------- 1 | # Add a puppet parser function called 'has_role' 2 | # * Takes 1 argument, the role name. 3 | # * Expects a fact 'server_tags' to be a comma-delimited string containing roles 4 | # 5 | # We use rightscale, which supports "tagging" a server with any number of tags 6 | # The tags are of the format: namespace:predicate=value 7 | # http://support.rightscale.com/12-Guides/RightScale_Methodologies/Tagging 8 | # 9 | # This function expects the fact 'server_tags' to be comma-delimited 10 | # Each value in server_tags must be of the format described above. 11 | # Roles are expected to be of format: "role:=true" 12 | # For example, the role 'loadbalancer' is "role:loadbalancer=true" 13 | # 14 | 15 | module Puppet::Parser::Functions 16 | newfunction(:has_role, :type => :rvalue) do |args| 17 | if (args.is_a? String) 18 | args = [args] 19 | end 20 | role = args[0] 21 | roles = lookupvar("server_tags").split(",").grep(/^role:/) 22 | roletag_re = /^role:#{role}(?:=.+)?$/ 23 | has_role = (roles.grep(roletag_re).length > 0) 24 | return has_role 25 | end # puppet function has_role 26 | end # module Puppet::Parser::Functions 27 | -------------------------------------------------------------------------------- /nodeless-puppet/modules/truth/manifests/enforcer.pp: -------------------------------------------------------------------------------- 1 | class truth::enforcer { 2 | 3 | # Now conditionally include things based on properties and facts 4 | if has_role("loadbalancer") { 5 | # include loadbalancer 6 | notice("I am a loadbalancer") 7 | } else { 8 | notice("I am not a loadbalancer") 9 | } 10 | 11 | if has_role("db") { 12 | notice("I am a database") 13 | } else { 14 | notice("I am not a database") 15 | } 16 | 17 | ## Practical hadoop example -- 18 | # You can even have logic here to reject configurations you 19 | # say are invalid. 20 | if has_role("hadoop-worker") and has_role("hadoop-master") { 21 | fail("Cannot be both hadoop-worker and hadoop-master. \$server_tags is '$server_tags'") 22 | } 23 | 24 | # All non-hadoop machines should get a special config that makes them able to 25 | # send jobs to the hadoop cluster. 26 | if !has_role("hadoop-worker") and !has_role("hadoop-master") { 27 | notice("I am a hadoop client") 28 | } 29 | 30 | if has_role("hadoop-worker") { 31 | notice("I am a hadoop-worker") 32 | } else { 33 | notice("I am not a hadoop-worker") 34 | } 35 | 36 | if has_role("hadoop-master") { 37 | notice("I am a hadoop-master") 38 | } else { 39 | notice("I am not a hadoop-master") 40 | } 41 | 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /stages-example/README.rdoc: -------------------------------------------------------------------------------- 1 | == Simple example using stages. 2 | 3 | Running it: 4 | 5 | % puppet apply stages-example.pp 6 | notice: foo 7 | notice: /Stage[one]/Foo/Notify[foo]/message: defined 'message' as 'foo' 8 | notice: bar 9 | notice: /Stage[two]/Bar/Notify[bar]/message: defined 'message' as 'bar' 10 | -------------------------------------------------------------------------------- /stages-example/stages-example.pp: -------------------------------------------------------------------------------- 1 | class foo { 2 | notify { 3 | "foo": ; 4 | } 5 | } 6 | 7 | class bar { 8 | notify { 9 | "bar": ; 10 | } 11 | } 12 | 13 | 14 | node default { 15 | stage { 16 | "one": ; 17 | "two": ; 18 | } 19 | 20 | class { 21 | "foo": stage => "one"; 22 | "bar": stage => "two"; 23 | } 24 | 25 | Stage["one"] -> Stage["two"] 26 | } 27 | -------------------------------------------------------------------------------- /swedishchef/README.md: -------------------------------------------------------------------------------- 1 | # Puppet + Chef == Bork! Bork! Bork! 2 | 3 | This puppet module adds a custom provider for the 'package' type that uses chef. 4 | 5 | Why not? Let's cook. 6 | 7 | It would be pretty trivial to use any of the other chef stuff as a puppet 8 | provider. With enough cleverness, you could likely run entire chef cookbooks 9 | from puppet with whatever 'chef_cookbook' type/provider you want to write. 10 | 11 | How'd I do this? 12 | 13 | First, the puppet docs on writing custom providers is a mixed bag of good and 14 | meh. I used the existing puppet code, masterzen's [network-device 15 | provider](https://github.com/masterzen/puppet/tree/feature%2Fnetwork-device/lib/puppet/provider) 16 | code, and some of the puppet 'custome type and provider' docs. 17 | 18 | Second, for calling chef from Ruby, I used this gist by adamhjk as a starting point: 19 | 20 | ## Where's the code hiding? 21 | 22 | It's in this git repo. 23 | 24 | I implement a custom provider (called "chef") for the existing "package" 25 | resource type. 26 | 27 | The code is in a module, like this: 28 | 29 | modules/swedishchef/plugins/puppet/provider/package/chef.rb 30 | 31 | ## swedishchef in action. 32 | 33 | ### Install nagios3 34 | 35 | % sudo puppet apply --modulepath ./modules -e ' 36 | package { 37 | "nagios3": 38 | provider => "chef", 39 | ensure => present; 40 | }' 41 | 42 | notice: Puppet::Type::Package::ProviderChef: Initializing chef... 43 | [Sat, 28 May 2011 00:47:39 -0700] INFO: Installing package[nagios3] version 3.2.0-4ubuntu2 44 | notice: /Stage[main]//Package[nagios3]/ensure: created 45 | 46 | % dpkg-query --show nagios3 47 | nagios3 3.2.0-4ubuntu2 48 | 49 | ### Uninstall nagios3 50 | 51 | % sudo puppet apply --modulepath modules -e 'package { "nagios3": provider => "chef", ensure => absent; }' 52 | notice: Puppet::Type::Package::ProviderChef: Initializing chef... 53 | [Sat, 28 May 2011 01:03:48 -0700] INFO: Removing package[nagios3] 54 | notice: /Stage[main]//Package[nagios3]/ensure: removed 55 | 56 | ### Try to install an invalid version 57 | 58 | Chef will toss an exception if you try to install a package for a version that 59 | doesn't exist. 60 | 61 | % sudo puppet apply --modulepath modules -e 'package { "nagios3": provider => "chef", ensure => "42"; }' 62 | notice: Puppet::Type::Package::ProviderChef: Initializing chef... 63 | [Sat, 28 May 2011 01:04:29 -0700] INFO: Installing package[nagios3] version 42 64 | err: /Stage[main]//Package[nagios3]/ensure: change from purged to 42 failed: Could not update: apt-get -q -y install nagios3=42 returned 100, expected 0 at line 1 65 | 66 | ### Install a proper version 67 | 68 | % apt-cache policy nagios3 | grep Candidate 69 | Candidate: 3.2.0-4ubuntu2 70 | % sudo puppet apply --modulepath modules -e 'package { "nagios3": provider => "chef", ensure => "3.2.0-4ubuntu2"; }' 71 | notice: Puppet::Type::Package::ProviderChef: Initializing chef... 72 | [Sat, 28 May 2011 01:05:58 -0700] INFO: Installing package[nagios3] version 3.2.0-4ubuntu2 73 | notice: /Stage[main]//Package[nagios3]/ensure: ensure changed 'purged' to '3.2.0-4ubuntu2' 74 | 75 | 76 | -------------------------------------------------------------------------------- /swedishchef/modules/swedishchef/plugins/puppet/provider/package/chef.rb: -------------------------------------------------------------------------------- 1 | # I tried namespacing this like 'swedischef::package' but puppet has some 2 | # trouble with that and reports: 3 | # Could not autoload swedishchef::package: wrong constant name 4 | # Swedishchef::package 5 | # 6 | 7 | require "rubygems" 8 | require 'puppet/provider/package' 9 | 10 | Puppet::Type.type(:package).provide(:chef, :parent => Puppet::Provider::Package) do 11 | desc "Whatever" 12 | 13 | # Enable versionable "ensure" parameter. See the package type for more info. 14 | has_feature :versionable 15 | 16 | def self.setup_chef 17 | require "chef" 18 | require "chef/client" 19 | require "chef/run_context" 20 | 21 | Chef::Config[:solo] = true 22 | Chef::Config[:log_level] = :info 23 | Chef::Log.level(:info) 24 | 25 | notice("Initializing chef...") 26 | chef_client = Chef::Client.new 27 | chef_client.run_ohai 28 | chef_client.build_node 29 | @@chef_context = Chef::RunContext.new(chef_client.node, 30 | Chef::CookbookCollection.new) 31 | 32 | end # def self.setup_chef 33 | 34 | # initialize a chef context, see above. 35 | setup_chef 36 | 37 | # Uhh.. what should we return here? 38 | def self.instances 39 | # Don't list instances. Always make chef run. 40 | return [] 41 | end # def self.instances 42 | 43 | def query 44 | # The 'package' type sort of treats this method like 'exists?' 45 | # But it expects a hash in return. 46 | # 47 | # The hash seems to need a ':ensure' key that indicates the current status 48 | # on the system. Abuse our fake 'exists?' for this. 49 | return { 50 | :ensure => exists? ? :present : :purged 51 | } 52 | end 53 | 54 | def install 55 | chefpackage = Chef::Resource::Package.new(resource[:name], @@chef_context) 56 | chefpackage.version(resource[:ensure]) if resource[:ensure].is_a?(String) 57 | chefpackage.run_action(:install) 58 | end # def create 59 | 60 | def update 61 | self.install 62 | end 63 | 64 | def uninstall 65 | chefpackage = Chef::Resource::Package.new(resource[:name], @@chef_context) 66 | chefpackage.run_action(:remove) 67 | end # def destroy 68 | 69 | def exists? 70 | # Fake exists? so we always run chef. 71 | # Return 'true' if ensure => absent (to force 'destroy') 72 | # Return 'false' otherwise (to force 'create') 73 | return @resource.should(:ensure) == :absent 74 | end 75 | 76 | end # Puppet::Type :foo 77 | -------------------------------------------------------------------------------- /unmanaged-file-notify/README.rdoc: -------------------------------------------------------------------------------- 1 | == Unmanaged files and notification 2 | 3 | A common question in the #puppet irc channel on freenode is: "How can I have puppet notify a service when I manually update a file?" 4 | 5 | While I believe this "manual update" portion is an antipattern, in some cases 6 | (development server, whatever) it is desirable, and frankly I don't know your 7 | infrastructure and I don't know all of your motives, so it's worth showing how 8 | this is done :) 9 | 10 | The code in this directory implements it. The gist of it is that you have 11 | puppet copy a local file and notify when that copy occurs. 12 | 13 | === Running it 14 | 15 | # Start fresh: 16 | % rm -f /tmp/flag /tmp/original 17 | 18 | % echo "foo" > /tmp/original 19 | % puppet apply unmanaged-notify.pp 20 | notice: /Stage[main]//File[/tmp/flag]/ensure: defined content as '{md5}d3b07384d113edec49eaa6238ad5ff00' 21 | notice: /Stage[main]//Exec[hello world]: Triggered 'refresh' from 1 events 22 | 23 | # Run it again with no changes, no notify should happen: 24 | % puppet apply unmanaged-notify.pp 25 | 26 | 27 | # Now change /tmp/original again 28 | % echo "bar" > /tmp/original 29 | % puppet apply unmanaged-notify.pp 30 | notice: /Stage[main]//File[/tmp/flag]/content: content changed '{md5}d3b07384d113edec49eaa6238ad5ff00' to '{md5}c157a79031e1c40f85931829bc5fc552' 31 | notice: /Stage[main]//Exec[hello world]: Triggered 'refresh' from 1 events 32 | 33 | -------------------------------------------------------------------------------- /unmanaged-file-notify/unmanaged-notify-puppet25.pp: -------------------------------------------------------------------------------- 1 | # Manually manage /tmp/original 2 | # Each puppet run will copy it to /tmp/flag if there's a change and notify 3 | # the exec when it changes. 4 | # 5 | # The idea here is you might need (in some case) to manually manage a file outside 6 | # of puppet (in this case, "/tmp/original"). Using this example, you can make puppet 7 | # signal other parts of your catalog based on changes to that file. 8 | 9 | file { 10 | # This will, when different, copy /tmp/original to /tmp/flag and notify our 11 | # exec. 12 | "/tmp/flag": 13 | source => "file:///tmp/original", 14 | notify => Exec["hello world"]; 15 | } 16 | 17 | exec { 18 | "hello world": 19 | command => "/bin/echo hello world", 20 | refreshonly => true; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /unmanaged-file-notify/unmanaged-notify-puppet26.pp: -------------------------------------------------------------------------------- 1 | # Manually manage /tmp/original 2 | # Each puppet run observe the contents and, on change, notify the exec when it 3 | # changes. 4 | # 5 | # The idea here is you might need (in some case) to manually manage a file outside 6 | # of puppet (in this case, "/tmp/original"). Using this example, you can make puppet 7 | # signal other parts of your catalog based on changes to that file. 8 | # 9 | # This version of the unmanaged-notify example requires puppet >= 2.6.0 and the 10 | # 'audit' feature: 11 | # http://projects.puppetlabs.com/projects/1/wiki/Release_Notes#Audit+Metaparameter 12 | 13 | file { 14 | # You manually manage this file, puppet will track the contents and notify 15 | # your exec on change. 16 | "/tmp/original": 17 | audit => content, 18 | notify => Exec["hello world"]; 19 | } 20 | 21 | exec { 22 | "hello world": 23 | command => "/bin/echo hello world", 24 | refreshonly => true; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /where-art-thou/README.md: -------------------------------------------------------------------------------- 1 | # Find where a module is loaded from 2 | 3 | This trick uses a template, $settings::modulepath, and __FILE__ from a template 4 | to find the location of a template (and thus its module) relative to the puppet 5 | module path. 6 | 7 | 8 | From this directory, run: 9 | 10 | % puppet apply --modulepath . -e 'include whereareyou' 11 | 12 | Output: 13 | 14 | notice: Scope(Class[Whereareyou]): [".", "whereareyou/templates/example.erb"] 15 | 16 | The output above is ["module path for this file", "file path relative to the modulepath"] 17 | 18 | Another example run with multiple module paths: 19 | 20 | % puppet apply --modulepath /tmp:../where-art-thou -e 'include whereareyou' 21 | notice: Scope(Class[Whereareyou]): ["../where-art-thou", "whereareyou/templates/example.erb"] 22 | 23 | -------------------------------------------------------------------------------- /where-art-thou/whereareyou/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class whereareyou { 2 | notice(template("whereareyou/example.erb")) 3 | } 4 | -------------------------------------------------------------------------------- /where-art-thou/whereareyou/templates/example.erb: -------------------------------------------------------------------------------- 1 | <%= 2 | # Split modulepath by ":" 3 | scope.lookupvar("settings::modulepath").split(":").collect do |path| 4 | # Fully resolve the path so things like '.' become full path names like 5 | # /path/to/modules 6 | expandedpath = File.expand_path(path) 7 | 8 | # Check if this file (this template file) is in the path being examined 9 | if __FILE__.start_with?(expandedpath) 10 | # If it is, return the current module path (unexpanded, and the path 11 | # in it relative to this template file 12 | [path, __FILE__.gsub(expandedpath + "/", "")] 13 | else 14 | # Otherwise return nil 15 | nil 16 | end 17 | # Then select the first non-nil entry 18 | end.select { |f| !f.nil? }.first 19 | %> 20 | --------------------------------------------------------------------------------