├── manifests └── site.pp ├── environment.conf ├── .travis.yml ├── bootstrap ├── manifests │ ├── puppet.pp │ ├── mcollective.pp │ ├── r10k.pp │ └── hiera.pp ├── r10k.yaml ├── hiera.yaml ├── puppet.conf └── pupa ├── Gemfile ├── Puppetfile ├── .gitignore ├── LICENSE ├── Rakefile └── README.md /manifests/site.pp: -------------------------------------------------------------------------------- 1 | Exec { 2 | path => $facts['path'], 3 | logoutput => 'on_failure', 4 | } 5 | 6 | hiera_include('classes', []) 7 | -------------------------------------------------------------------------------- /environment.conf: -------------------------------------------------------------------------------- 1 | modulepath = ./dist:./modules:$basemodulepath 2 | config_version = '/usr/bin/git --git-dir $codedir/environments/$environment/.git rev-parse HEAD' 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: false 3 | language: ruby 4 | script: "bundle exec rake test SPEC_OPTS='--format documentation'" 5 | rvm: 6 | - 2.4.1 7 | notifications: 8 | email: false 9 | -------------------------------------------------------------------------------- /bootstrap/manifests/puppet.pp: -------------------------------------------------------------------------------- 1 | file { '/etc/puppetlabs/puppet/puppet.conf': 2 | ensure => 'file', 3 | owner => 'root', 4 | group => 'root', 5 | mode => '0644', 6 | source => "${pupadir}/puppet.conf", 7 | } 8 | -------------------------------------------------------------------------------- /bootstrap/r10k.yaml: -------------------------------------------------------------------------------- 1 | cachedir: '/var/cache/r10k' 2 | 3 | sources: 4 | puppet: 5 | remote: 'https://github.com/daenney/pupa.git' 6 | basedir: '/etc/puppetlabs/code/environments' 7 | prefix: false 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'r10k', '~>2.0' 4 | gem 'hiera', '>=3.2.1' 5 | gem 'hiera-eyaml', '~>2.0' 6 | gem 'facter', '~>2.4' 7 | gem 'puppet', '~>5.0' 8 | 9 | gem 'puppet-lint', '~>2.0' 10 | gem 'puppetlabs_spec_helper', '~>2.2' 11 | -------------------------------------------------------------------------------- /Puppetfile: -------------------------------------------------------------------------------- 1 | forge 'http://forge.puppetlabs.com' 2 | 3 | # Libraries 4 | mod 'puppetlabs/stdlib', '4.17.0' 5 | mod 'puppetlabs/concat', '4.0.1' 6 | mod 'herculesteam/augeasproviders_shellvar', '2.2.1' 7 | mod 'herculesteam/augeasproviders_core', '2.1.3' 8 | mod 'mmckinst/hash2stuff', '1.0.1' 9 | -------------------------------------------------------------------------------- /bootstrap/hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 5 3 | defaults: 4 | datadir: data 5 | data_hash: yaml_data 6 | hierarchy: 7 | - name: "Per-node data (yaml version)" 8 | path: "nodes/%{::trusted.certname}.yaml" 9 | - name: "Other YAML hierarchy levels" 10 | paths: 11 | - "common.yaml" 12 | -------------------------------------------------------------------------------- /bootstrap/puppet.conf: -------------------------------------------------------------------------------- 1 | [main] 2 | bindaddress = 127.0.0.1 3 | ca = false 4 | daemonize = false 5 | default_schedules = false 6 | ignoreschedules = true 7 | onetime = true 8 | report = false 9 | storeconfigs = false 10 | strict_variables = true 11 | usecacheonfailure = false 12 | waitforcert = 0 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built packages 2 | pkg/* 3 | 4 | # Should run on multiple platforms so don't check in 5 | Gemfile.lock 6 | 7 | # Tests 8 | **/spec/fixtures/* 9 | !**/spec/fixtures/hiera 10 | **/coverage/* 11 | 12 | # Third-party 13 | vendor/* 14 | .bundle/* 15 | 16 | # rbenv 17 | .ruby-version 18 | 19 | # hiera-eyaml 20 | keys/* 21 | -------------------------------------------------------------------------------- /bootstrap/manifests/mcollective.pp: -------------------------------------------------------------------------------- 1 | file { '/etc/puppetlabs/mcollective': 2 | ensure => 'directory', 3 | owner => 'root', 4 | group => 'root', 5 | mode => '0644', 6 | purge => true, 7 | recurse => true, 8 | } 9 | 10 | file { '/etc/puppetlabs/mcollective/server.cfg': 11 | ensure => 'file', 12 | owner => 'root', 13 | group => 'root', 14 | mode => '0644', 15 | } 16 | 17 | file { '/etc/puppetlabs/mcollective/client.cfg': 18 | ensure => 'file', 19 | owner => 'root', 20 | group => 'root', 21 | mode => '0644', 22 | } 23 | -------------------------------------------------------------------------------- /bootstrap/manifests/r10k.pp: -------------------------------------------------------------------------------- 1 | package { 'r10k': 2 | ensure => '2.0.2', 3 | provider => 'puppet_gem', 4 | } 5 | 6 | file { '/etc/puppetlabs/r10k': 7 | ensure => 'directory', 8 | owner => 'root', 9 | group => 'root', 10 | mode => '0644', 11 | } 12 | 13 | file { '/var/cache/r10k': 14 | ensure => 'directory', 15 | owner => 'root', 16 | group => 'root', 17 | mode => '0644', 18 | } 19 | 20 | file { '/etc/puppetlabs/r10k/r10k.yaml': 21 | ensure => 'file', 22 | owner => 'root', 23 | group => 'root', 24 | mode => '0644', 25 | source => "${pupadir}/r10k.yaml", 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 Daniele Sluijters 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /bootstrap/manifests/hiera.pp: -------------------------------------------------------------------------------- 1 | package { 'hiera-eyaml': 2 | ensure => '2.1.0', 3 | provider => 'puppet_gem', 4 | } 5 | 6 | package { 'deep_merge': 7 | ensure => '1.1.1', 8 | provider => 'puppet_gem', 9 | } 10 | 11 | $base_path = '/etc/puppetlabs' 12 | 13 | file { "${base_path}/puppet/eyaml": 14 | ensure => directory, 15 | owner => 'root', 16 | group => 'root', 17 | mode => '0500', 18 | } 19 | 20 | file { "${base_path}/puppet/hiera.yaml": 21 | ensure => absent, 22 | } 23 | 24 | # Set up production environment 25 | $code_path = "${base_path}/code" 26 | $env_path = "${code_path}/environments" 27 | $prod_path = "${env_path}/production" 28 | $env_setup = [ 29 | $code_path, 30 | $env_path, 31 | $prod_path, 32 | "${prod_path}/modules", 33 | "${prod_path}/manifests", 34 | "${prod_path}/data", 35 | ] 36 | 37 | $env_setup.each | String $path | { 38 | file { $path: 39 | ensure => directory, 40 | owner => 'root', 41 | group => 'root', 42 | mode => '0755', 43 | } 44 | } 45 | 46 | file { "${prod_path}/hiera.yaml": 47 | ensure => 'file', 48 | owner => 'root', 49 | group => 'root', 50 | mode => '0644', 51 | source => "${pupadir}/hiera.yaml", 52 | } 53 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require 'puppetlabs_spec_helper/rake_tasks' 3 | require 'puppet-lint/tasks/puppet-lint' 4 | 5 | # This is needed until https://github.com/rodjek/puppet-lint/commit/efb19675 gets released 6 | # Need to add the bootstrap/ directory to the exclude paths 7 | Rake::Task[:lint].clear 8 | PuppetLint::RakeTask.new :lint do |config| 9 | config.pattern = 'dist/**/*.pp' 10 | config.disable_checks = ['documentation', '140chars'] 11 | config.with_filename = true 12 | config.fail_on_warnings = true 13 | config.log_format = '%{filename} - %{message}' 14 | config.with_context = true 15 | config.fix = false 16 | config.show_ignored = false 17 | config.relative = true 18 | end 19 | 20 | task(:lint_output) { puts '---> lint'} 21 | Rake::Task[:lint].enhance [:lint_output] 22 | 23 | desc "Run specs for every module in dist/" 24 | task :dist_spec do 25 | dirs = Pathname.new('dist').children.select { |c| c.directory? } 26 | dirs.each do |mod| 27 | Dir.chdir(mod) do 28 | if File.directory?('spec') 29 | puts "---> spec: #{mod}" 30 | system "rake spec" 31 | end 32 | end 33 | end 34 | end 35 | 36 | desc "Run syntax, lint and spec tests" 37 | task :test => [ 38 | :syntax, 39 | :lint, 40 | :dist_spec, 41 | ] 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pupa [![Travis](https://img.shields.io/travis/daenney/pupa.svg?style=flat-square)](https://travis-ci.org/daenney/pupa) 2 | 3 | Pupa is a few things: 4 | 5 | * Heavily inspired by [puppetinabox](https://github.com/puppetinabox/documentation) 6 | * Named after a [haunted doll](http://www.nightwatchparanormal.com/pupa-the-haunted-doll.html) 7 | * Licensed under the [Apache License, Version 2.0](LICENSE) 8 | * A [script](bootstrap/pupa) to bootstrap a masterless (puppet apply) installation of Puppet 5 9 | with r10k 10 | 11 | ## pupa (the script) 12 | 13 | The script in `bootstrap/` and the associated configuration there will take care 14 | of getting your server ready to rumble with Puppet 5. It will use the Puppet 5 15 | AIO packages to install Puppet and will use the version of Ruby and `gem` 16 | provided by that package in `/opt/puppetlabs` to install additional dependencies 17 | like r10k. 18 | 19 | Contrary to previous version this repository no longer contains any of my 20 | customisations. It does in the `Puppetfile` include a few "must have" libraries 21 | and still includes a complete `Rakefile` to help you get going with 22 | development. From there on out you're on your own but there's a lot of very 23 | good [documentation available for Puppet 5](https://docs.puppet.com/puppet/5.0/). 24 | 25 | **This has been designed to work with Debian/Ubuntu systems**. It will go up in 26 | flames if you try to run this on anything else including other Debian 27 | derivatives for which the [Puppetlabs APT repository](https://apt.puppetlabs.com) 28 | does not provide a [Puppet 5](https://puppet.com/blog/puppet-5-platform-released) 29 | release package. 30 | 31 | What this script will attempt to do: 32 | 33 | * Complain loudly at you if something we need is missing 34 | * Fetch the release deb from the Puppetlabs APT mirror 35 | * Install it (so that we get new sources.list.d entries) 36 | * Refresh the APT sources 37 | * Install the `puppet-agent` package (Puppet, cfacter, Facter, mcollective, 38 | Hiera) 39 | * Install r10k and hiera-eyaml 40 | * Write configuration for: 41 | * puppet: Set up some defaults for masterless mode 42 | * hiera: Set up the hierarchy 43 | * mcollective: Disables it 44 | * r10k: Set up where to clone environments from and how to deploy them 45 | 46 | ### idempotency 47 | 48 | Pupa tries to be fairly intelligent. If you have a look at the code you'll see 49 | that it does its best not to install packages or refresh sources if it detects 50 | it's not needed. 51 | 52 | Once Puppet is installed we use it to configure the rest of the system for us. 53 | Though we incur a slight runtime penalty from calling Puppet a couple of times 54 | we gain all the benefits, including idempotency. 55 | 56 | This means this script can be run in a continuous loop not ever taking any 57 | action that modifies the system except if its source files have been updated. 58 | 59 | ### usage 60 | 61 | In order to use Pupa: 62 | 63 | * fork this repository 64 | * edit `bootstrap/r10k.yaml` and make the `remote` key point to your fork 65 | * edit `bootstrap/hiera.yaml` to your liking 66 | * edit `bootstrap/puppet.conf` to your liking 67 | * `git clone` your fork to your server (over HTTPS is easiest and requires no 68 | authentication if it's a public repo) 69 | * run `boostrap/pupa` 70 | 71 | Assuming nothing blew up you now have a functioning Puppet 5 installation. If 72 | something did go wrong you can tell Pupa to be a bit more verbose and instead 73 | run `PUPA_LOGLEVEL=DEBUG bootstrap/pupa`. 74 | 75 | ## Developing 76 | 77 | The provided `Gemfile` will take care of installing Puppet, Facter, Hiera, r10k 78 | etc. locally for you so you can work on manifests, run the tests and so forth. 79 | 80 | ``` 81 | $ bundle install 82 | Fetching gem metadata from https://rubygems.org/........... 83 | Fetching version metadata from https://rubygems.org/... 84 | Fetching dependency metadata from https://rubygems.org/.. 85 | Resolving dependencies... 86 | [..] 87 | Bundle complete! 4 Gemfile dependencies, 17 gems now installed. 88 | Use `bundle show [gemname]` to see where a bundled gem is installed. 89 | ``` 90 | 91 | The `Gemfile` also installs `puppet-lint` and the `puppetlabs_spec_helper` 92 | gems. It used to include a number of additional plugins but most of these 93 | only signal support for puppet-lint 1.x breaking the installation of the 94 | bundle. 95 | 96 | Please take a look at the [puppet-lint plugin list on Vox Pupuli](https://voxpupuli.org/plugins/#puppet-lint) 97 | and pick which seem useful to you. Add those to the `Gemfile` but first check 98 | on Rubygems if they support puppet-lint 2.x. 99 | 100 | A number of tasks are defined by the `Rakefile`: 101 | 102 | ``` 103 | $ rake -vT 104 | rake beaker # Run beaker acceptance tests 105 | [..] 106 | rake validate # Check syntax of Ruby files and call :syntax and :metadata 107 | ``` 108 | 109 | You'll most likely want to use the `test` task: 110 | 111 | ``` 112 | $ rake test 113 | ---> syntax:manifests 114 | ---> syntax:templates 115 | ---> syntax:hiera:yaml 116 | ---> lint 117 | ---> spec: dist/profile 118 | [..] 119 | 120 | Finished in 1 second (files took 0.77317 seconds to load) 121 | 15 examples, 0 failures 122 | ``` 123 | 124 | ## Contributing 125 | 126 | Contributions are always very welcome. Any change however should be motivated 127 | with a good explanation of why this change is necessary and generally have 128 | commits that: 129 | 130 | * Are written in the imperative style 131 | * Where a single commit represents a single feature 132 | * Have a short subject 133 | * Include as much text as needed in the body to motivate this change 134 | -------------------------------------------------------------------------------- /bootstrap/pupa: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### - Bash Strcit mode - ### 4 | set -euo pipefail 5 | IFS=$'\n\t' 6 | ### - Bash Strict mode - ### 7 | 8 | if test "${BASH_VERSION%%.*}" -lt 4; then 9 | echo "This script requires Bash version 4 or higher" 10 | exit 1 11 | fi 12 | 13 | ### - Logging - ### 14 | LOGLEVEL=${PUPA_LOGLEVEL:-INFO} 15 | declare -A LOG_AT 16 | LOG_AT['DEBUG']=5 17 | LOG_AT['INFO']=4 18 | LOG_AT['WARN']=3 19 | LOG_AT['ERR']=2 20 | LOG_AT['CRIT']=1 21 | 22 | if tty -s;then 23 | typeset -r RED=${RED:-$(tput setaf 1)} 24 | typeset -r GREEN=${GREEN:-$(tput setaf 2)} 25 | typeset -r YLW=${YLW:-$(tput setaf 3)} 26 | typeset -r BLUE=${BLUE:-$(tput setaf 4)} 27 | typeset -r PURPLE=${PURPLE:-$(tput setaf 5)} 28 | typeset -r CYAN=${CYAN:-$(tput setaf 6)} 29 | typeset -r RESET=${RESET:-$(tput sgr0)} 30 | typeset -r BOLD=${BOLD:-$(tput bold)} 31 | else 32 | typeset -r RED= 33 | typeset -r GREEN= 34 | typeset -r YLW= 35 | typeset -r BLUE= 36 | typeset -r PURPLE= 37 | typeset -r CYAN= 38 | typeset -r RESET= 39 | typeset -r BOLD= 40 | fi 41 | 42 | function timestamp { 43 | # Current time including UTC offset 44 | date +'%H:%M:%S %z' 45 | } 46 | 47 | function log { 48 | # Print a message on the console 49 | if test $# -eq 0; then 50 | log "log() needs at least a message to log" ERR 51 | return 52 | fi 53 | msg=${1} 54 | level=${2:-INFO} 55 | 56 | if test "${level}" == 'INFO'; then 57 | lcolour=$GREEN 58 | elif test "${level}" == 'WARN'; then 59 | lcolour=$YELLOW 60 | elif test "${level}" == 'ERR'; then 61 | lcolour=$RED 62 | elif test "${level}" == 'CRIT'; then 63 | lcolour=$PURPLE 64 | elif test "${level}" == 'DEBUG'; then 65 | lcolour=$BLUE 66 | fi 67 | # Check at which level we're configured to log 68 | if test ${LOG_AT[${LOGLEVEL}]} -ge ${LOG_AT[${level}]}; then 69 | echo "${BOLD}$(timestamp) ${lcolour}$(printf %-5s "${level}")${RESET} ${msg}" | fold -w120 -s | sed '2~1s/^/ /' 70 | fi 71 | } 72 | ### - Logging - ### 73 | 74 | ### - Testing prereqs - ### 75 | function debugging { 76 | # Check if we're in DEBUG mode 77 | test ${LOG_AT[${LOGLEVEL}]} -eq ${LOG_AT['DEBUG']} 78 | } 79 | 80 | if [ "${EUID}" -ne 0 ]; then 81 | # Ensure we're running as root/superuser 82 | log "Bootstrapping script needs to run as ${RED}root${RESET}" ERR 83 | exit 1 84 | fi 85 | command -v lsb_release >/dev/null 2>&1 || { log >&2 "lsb_release is not installed, ${PURPLE}aborting${RESET}" CRIT; exit 1; } 86 | command -v curl >/dev/null 2>&1 || { log >&2 "curl is not installed, ${PURPLE}aborting${RESET}" CRIT; exit 1; } 87 | command -v git >/dev/null 2>&1 || { log >&2 "git is not installed, ${PURPLE}aborting${RESET}" CRIT; exit 1; } 88 | ### - Testing prereqs - ### 89 | 90 | # Find the OS codename 91 | distr=$(lsb_release --id -s) 92 | rel=$(lsb_release --codename -s) 93 | # Version of Puppet Platform 94 | platform_ver=5 95 | # Where this script is located 96 | pupa_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P) 97 | # Puppetlabs bin/ dir 98 | plbin='/opt/puppetlabs/puppet/bin' 99 | pletc='/etc/puppetlabs' 100 | 101 | function papply { 102 | if debugging; then 103 | log "Going to execute: ${plbin}/puppet apply ${1}" DEBUG 104 | FACTER_PUPADIR="${pupa_dir}" "${plbin}/puppet" apply "${1}" "--prerun_command=''" "--postrun_command=''" 105 | else 106 | FACTER_PUPADIR="${pupa_dir}" "${plbin}/puppet" apply "${1}" "--prerun_command=''" "--postrun_command=''" > /dev/null 2>&1 107 | fi 108 | } 109 | 110 | function install_puppetrepo { 111 | if test ! -f '/etc/apt/sources.list.d/puppet5.list'; then 112 | log "Fetching Puppet ${platform_ver} release package for ${distr^} ${rel^}" 113 | if debugging; then 114 | curl -L -O "https://apt.puppetlabs.com/puppet${platform_ver}-release-${rel}.deb" 115 | else 116 | curl -s -L -O "https://apt.puppetlabs.com/puppet${platform_ver}-release-${rel}.deb" 117 | fi 118 | log "Installing Puppetlabs APT sources entries for ${distr^} ${rel^}" 119 | if debugging; then 120 | dpkg -i "puppet${platform_ver}-release-${rel}.deb" 121 | else 122 | dpkg -i "puppet${platform_ver}-release-${rel}.deb" >/dev/null 2>&1 123 | fi 124 | else 125 | log "Puppetlabs APT source entries already installed, skipping" 126 | fi 127 | } 128 | 129 | function refresh_apt_sources { 130 | log "Refreshing APT sources" 131 | if debugging; then 132 | apt update 133 | else 134 | apt update -qq > /dev/null 2>&1 135 | fi 136 | log "Done refreshing APT sources" 137 | } 138 | 139 | function setup_puppet { 140 | log "Installing and configuring Puppet" 141 | if test ! -f "${plbin}/puppet"; then 142 | refresh_apt_sources 143 | if debugging; then 144 | apt install puppet-agent 145 | else 146 | DEBIAN_FRONTEND=noninteractive apt install -qq --yes --force-yes puppet-agent > /dev/null 2>&1 147 | fi 148 | fi 149 | papply "${pupa_dir}/manifests/puppet.pp" 150 | log "Successfully installed and configured Puppet" 151 | } 152 | 153 | function setup_hiera { 154 | log "Installing and configuring Hiera" 155 | papply "${pupa_dir}/manifests/hiera.pp" 156 | log "Successfully installed and configured Hiera" 157 | } 158 | 159 | function setup_r10k { 160 | log "Installing and configuring r10k" 161 | papply "${pupa_dir}/manifests/r10k.pp" 162 | log "Successfully installed and configured r10k" 163 | } 164 | 165 | function setup_mcollective { 166 | log "Disabling mcollective" 167 | papply "${pupa_dir}/manifests/mcollective.pp" 168 | log "Successfully disabled mcollective" 169 | } 170 | 171 | function main { 172 | install_puppetrepo 173 | setup_puppet 174 | setup_hiera 175 | setup_r10k 176 | setup_mcollective 177 | } 178 | 179 | log "Bootstrapping Puppet on ${YLW}$(hostname --fqdn)${RESET}" 180 | main 181 | log "Done configuring Puppet on ${YLW}$(hostname --fqdn)${RESET}" 182 | log "Please ensure to ${RED}create/upload${RESET} the hiera-eyaml keys in ${pletc}/puppet/eyaml" 183 | log "First run '${plbin}/r10k deploy environment -pv' to deploy your code" 184 | log "And then run '${plbin}/puppet apply ${pletc}/code/environments/production/manifests/site.pp' to kick of a Puppet run" 185 | exit 0 186 | --------------------------------------------------------------------------------