# A system to construct files using fragments from other files or templates.
#
# This requires at least puppet 0.25 to work correctly as we use some
# enhancements in recursive directory management and regular expressions
# to do the work here.
#
# USAGE:
# The basic use case is as below:
#
# concat{"/etc/named.conf":
#    notify => Service["named"]
# }
#
# concat::fragment{"foo.com_config":
#    target  => "/etc/named.conf",
#    order   => 10,
#    content => template("named_conf_zone.erb")
# }
#
# # add a fragment not managed by puppet so local users
# # can add content to managed file
# concat::fragment{"foo.com_user_config":
#    target  => "/etc/named.conf",
#    order   => 12,
#    ensure  => "/etc/named.conf.local"
# }
#
# This will use the template named_conf_zone.erb to build a single
# bit of config up and put it into the fragments dir.  The file
# will have an number prefix of 10, you can use the order option
# to control that and thus control the order the final file gets built in.
#
# SETUP:
# The class concat::setup uses the fact concat_basedir to define the variable
# $concatdir, where all the temporary files and fragments will be
# durably stored. The fact concat_basedir will be set up on the client to
# <Puppet[:vardir]>/concat, so you will be able to run different setup/flavours
# of puppet clients.
# However, since this requires the file lib/facter/concat_basedir.rb to be
# deployed on the clients, so you will have to set "pluginsync = true" on
# both the master and client, at least for the first run.
#
# There's some regular expression magic to figure out the puppet version but
# if you're on an older 0.24 version just set $puppetversion = 24
#
# Before you can use any of the concat features you should include the
# class concat::setup somewhere on your node first.
#
# DETAIL:
# We use a helper shell script called concatfragments.sh that gets placed
# in <Puppet[:vardir]>/concat/bin to do the concatenation.  While this might
# seem more complex than some of the one-liner alternatives you might find on
# the net we do a lot of error checking and safety checks in the script to avoid
# problems that might be caused by complex escaping errors etc.
#
# LICENSE:
# Apache Version 2
#
# LATEST:
# http://github.com/ripienaar/puppet-concat/
#
# CONTACT:
# R.I.Pienaar <rip@devco.net>
# Volcane on freenode
# @ripienaar on twitter
# www.devco.net


# Sets up so that you can use fragments to build a final config file,
#
# OPTIONS:
#  - mode       The mode of the final file
#  - owner      Who will own the file
#  - group      Who will own the file
#  - force      Enables creating empty files if no fragments are present
#  - warn       Adds a normal shell style comment top of the file indicating
#               that it is built by puppet
#  - backup     Controls the filebucketing behavior of the final file and
#               see File type reference for its use.  Defaults to 'puppet'
#
# ACTIONS:
#  - Creates fragment directories if it didn't exist already
#  - Executes the concatfragments.sh script to build the final file, this script will create
#    directory/fragments.concat.   Execution happens only when:
#    * The directory changes
#    * fragments.concat != final destination, this means rebuilds will happen whenever
#      someone changes or deletes the final file.  Checking is done using /usr/bin/cmp.
#    * The Exec gets notified by something else - like the concat::fragment define
#  - Copies the file over to the final destination using a file resource
#
# ALIASES:
#  - The exec can notified using Exec["concat_/path/to/file"] or Exec["concat_/path/to/directory"]
#  - The final file can be referened as File["/path/to/file"] or File["concat_/path/to/file"]
define concat($mode = '0644', $owner = $::id, $group = $concat::setup::root_group, $warn = false, $force = false, $backup = 'puppet', $gnu = undef, $order='alpha') {
  $safe_name   = regsubst($name, '/', '_', 'G')
  $concatdir   = $concat::setup::concatdir
  $version     = $concat::setup::majorversion
  $fragdir     = "${concatdir}/${safe_name}"
  $concat_name = 'fragments.concat.out'
  $default_warn_message = '# This file is managed by Puppet. DO NOT EDIT.'

  case $warn {
    'true',true,yes,on:   { $warnmsg = $default_warn_message }
    'false',false,no,off: { $warnmsg = '' }
    default:              { $warnmsg = $warn }
  }

  $warnmsg_escaped = regsubst($warnmsg, "'", "'\\\\''", 'G')
  $warnflag = $warnmsg_escaped ? {
    ''      => '',
    default => "-w '${warnmsg_escaped}'"
  }

  case $force {
    'true',true,yes,on: { $forceflag = '-f' }
    'false',false,no,off: { $forceflag = '' }
    default: { fail("Improper 'force' value given to concat: ${force}") }
  }

  case $order {
    numeric: { $orderflag = '-n' }
    alpha: { $orderflag = '' }
    default: { fail("Improper 'order' value given to concat: ${order}") }
  }

  File{
    owner  => $::id,
    group  => $group,
    mode   => $mode,
    backup => $backup
  }

  file{$fragdir:
    ensure   => directory;

  "${fragdir}/fragments":
    ensure   => directory,
    recurse  => true,
    purge    => true,
    force    => true,
    ignore   => ['.svn', '.git', '.gitignore'],
    source   => $version ? {
      24      => 'puppet:///concat/null',
      default => undef,
    },
    notify   => Exec["concat_${name}"];

  "${fragdir}/fragments.concat":
    ensure   => present;

  "${fragdir}/${concat_name}":
    ensure   => present;

  $name:
    ensure   => present,
    source   => "${fragdir}/${concat_name}",
    owner    => $owner,
    group    => $group,
    checksum => md5,
    mode     => $mode,
    alias    => "concat_${name}";
  }

  exec{"concat_${name}":
    notify    => File[$name],
    subscribe => File[$fragdir],
    alias     => "concat_${fragdir}",
    require   => [ File[$fragdir], File["${fragdir}/fragments"], File["${fragdir}/fragments.concat"] ],
    unless    => "${concat::setup::concatdir}/bin/concatfragments.sh -o ${fragdir}/${concat_name} -d ${fragdir} -t ${warnflag} ${forceflag} ${orderflag}",
    command   => "${concat::setup::concatdir}/bin/concatfragments.sh -o ${fragdir}/${concat_name} -d ${fragdir} ${warnflag} ${forceflag} ${orderflag}",
  }
  if $::id == 'root' {
    Exec["concat_${name}"]{
      user      => root,
      group     => $group,
    }
  }
}