Automate Multi-Source Code Manager during Puppet Enterprise Install

I’ve been a fan of Puppet Enterprise’s Code Manager since it shipped in PE 2015.3.  When we originally converted our deployments from zack/r10k to CM, we had just a single control repo with all our code environments as branches.  Later, we realized we would streamline our workflow by separating hieradata from control code into its own repo, with different security and its own branches, each consumed by different PE deployments.

In preparing to migrate our PE deployments to Azure, I’ve been developing automation and scripting around the operations involved to avoid misconfigurations as much as possible.  One of the areas I’ve been wanting to automate is Code Manager setup.  Puppet’s documentation on the subject is great, but the current (2016.4.2) PE installer doesn’t allow for CM configuration that involve multiple source repositories or proxy servers.  Complex configurations have to be set up after install.  I figured out how to do this in a streamlined fashion by creating a temporary, local hiera data directory using a relative path at the bottom of the hierarchy and injecting Code Manager configuration data into its common.yaml.

Note that because Code Manager is only configured to deploy per-branch environments to the PE Master’s $codedir, the example I have here deploys our hieradata repository’s branches to $codedir and they will appear as environments named “hiera_<branchname>”.  They can be safely ignored in the PE Console, and should not be assigned as an environment to any node groups.

Preparation

After Puppet Enterprise setup, place an SSH private key with read-only access to your source repos in the location recommended in the PE documentation.

On the PE master, create /var/tmp/codemgr_key.pp (insert your own private key’s contents):

# /var/tmp/codemgr_key.pp
$codemgr_private_key = '-----BEGIN RSA PRIVATE KEY-----
<private key contents>
-----END RSA PRIVATE KEY-----'

file { '/etc/puppetlabs/puppetserver/ssh':
  ensure => 'directory',
  group  => 'root',
  owner  => 'root',
  mode   => '0755',
}

file { '/etc/puppetlabs/puppetserver/ssh/id-control_repo.rsa':
  ensure  => 'file',
  group   => 'pe-puppet',
  owner   => 'pe-puppet',
  mode    => '0400',
  content => $codemgr_private_key,
}

Create the Code Manager key by running (on the PE master):

sudo puppet apply /var/tmp/codemgr_key.pp

Make sure to delete /var/tmp/codemgr_key.pp when done if you don’t want to leave your key contents in that directory.

Run this as root on the PE master to set up a Code Manager deployment user and retrieve an authentication token for it:

#!/bin/bash
# Edit as appropriate for your deployment
# Environment variables
CERT="$(puppet agent --configprint hostcert)"
KEY="$(puppet agent --configprint hostprivkey)"
CACERT="$(puppet agent --configprint localcacert)"
SERVER="$(puppet agent --configprint server)"

# Use Puppet's curl
alias curl='/opt/puppetlabs/puppet/bin/curl'

# Install jq
puppet resource package jq ensure=installed

# Make a root .puppetlabs directory for token
mkdir /root/.puppetlabs

# Create deployment user
curl -k -X POST https://localhost:4433/rbac-api/v1/users \
  --cert $CERT --key $KEY --cacert $CACERT \
  -H "Content-Type: application/json" \
  -d '{"login":"deployment", "email":"puppet@mycompany.com", "display_name":"Code Manager Service Account", "role_ids": [4], "password":"puppetlabs"}'

# Request an authentication token and store in /root/.puppetlabs/token
curl -k -X POST https://localhost:4433/rbac-api/v1/auth/token \
  --cert $CERT --key $KEY --cacert $CACERT \
  -H "Content-Type: application/json" \
  -d '{"login":"deployment", "password":"puppetlabs", "lifetime":"10y", "label":"PE Master token"}' | \
  jq -r '.token' > /root/.puppetlabs/token

Inject Local Hieradata

#!/bin/bash
# Remove proxy setting if not required.
PROXY=http://proxy.mycompany.com:8080
mkdir /etc/puppetlabs/code/hieradata
cat > /etc/puppetlabs/code/hieradata/common.yaml << COMMONYAML
---
puppet_enterprise::master::code_manager::git_settings:
 private-key: '/etc/puppetlabs/puppetserver/ssh/id-control_repo.rsa'
puppet_enterprise::master::code_manager::proxy: '$PROXY'
puppet_enterprise::master::code_manager::sources:
 puppet:
 remote: "<clone URL for control repo>"
 prefix: false
 hiera:
 remote: "<clone URL for hieradata repo>"
 prefix: true
puppet_enterprise::profile::master::code_manager_auto_configure: true
puppet_enterprise::profile::master::file_sync_enabled: true
COMMONYAML
chown pe-puppet:pe-puppet /etc/puppetlabs/code/hieradata/common.yaml
mv /etc/puppetlabs/puppet/hiera.yaml /etc/puppetlabs/puppet/hiera.yaml.old
cat > /etc/puppetlabs/puppet/hiera.yaml << HIERAYAML
---
:backends:
 - yaml
:hierarchy:
 - "nodes/%{::trusted.certname}"
 - common
  - "../../../hieradata/common"

:yaml:
# datadir is empty here, so hiera uses its defaults:
# - /etc/puppetlabs/code/environments/%{environment}/hieradata on *nix
# - %CommonAppData%\PuppetLabs\code\environments\%{environment}\hieradata on Windows
# When specifying a datadir, make sure the directory exists.
 :datadir:
HIERAYAML
# Restart pe-puppetserver to pick up new hiera configuration
puppet resource service pe-puppetserver ensure=stopped
puppet resource service pe-puppetserver ensure=running

Configure Code Manager

Perform a Puppet Agent run as root to configure Code Manager:

sudo puppet agent -t

You should see output indicating the setup of File Sync and Code Manager is being done:

Notice: /Stage[main]/Puppet_enterprise::Master::Puppetserver/Pe_hocon_setting[jruby-puppet.environment-class-cache-enabled]/ensure: created
Info: /Stage[main]/Puppet_enterprise::Master::Puppetserver/Pe_hocon_setting[jruby-puppet.environment-class-cache-enabled]: Scheduling refresh of Service[pe-puppetserver]
Notice: /Stage[main]/Puppet_enterprise::Master/Pe_ini_setting[puppetconf environment_timeout setting]/value: value changed '0' to 'unlimited'
Info: /Stage[main]/Puppet_enterprise::Master/Pe_ini_setting[puppetconf environment_timeout setting]: Scheduling refresh of Service[pe-puppetserver]
Notice: /Stage[main]/Pe_r10k::Config/File[r10k.yaml]/ensure: defined content as '{md5}1a51daddd57c58615646202f69847914'
Notice: /Stage[main]/Puppet_enterprise::Master::Code_manager/File[/opt/puppetlabs/server/data/code-manager/]/mode: mode changed '0700' to '0750'
Notice: /Stage[main]/Puppet_enterprise::Master::Code_manager/File[/etc/puppetlabs/puppetserver/conf.d/code-manager.conf]/ensure: created
Notice: /Stage[main]/Puppet_enterprise::Master::Code_manager/Pe_hocon_setting[webserver.code-manager.client-auth]/ensure: created
[...]
Info: Computing checksum on file /etc/puppetlabs/puppetserver/bootstrap.cfg
Info: /Stage[main]/Puppet_enterprise::Profile::Master/Puppet_enterprise::Trapperkeeper::Bootstrap_cfg[certificate-authority-service]/Pe_concat[/etc/puppetlabs/puppetserver/bootstrap.cfg]/File[/etc/puppetlabs/puppetserver/bootstrap.cfg]: Filebucketed /etc/puppetlabs/puppetserver/bootstrap.cfg to puppet with sum 49aa06818b9c496279e2543e57bfe6ab
Notice: /Stage[main]/Puppet_enterprise::Profile::Master/Puppet_enterprise::Trapperkeeper::Bootstrap_cfg[certificate-authority-service]/Pe_concat[/etc/puppetlabs/puppetserver/bootstrap.cfg]/File[/etc/puppetlabs/puppetserver/bootstrap.cfg]/content: content changed '{md5}49aa06818b9c496279e2543e57bfe6ab' to '{md5}7144f0a9d7e805620f6a90c8e1a9d55f'
Info: Pe_concat[/etc/puppetlabs/puppetserver/bootstrap.cfg]: Scheduling refresh of Service[pe-puppetserver]
Info: Puppet_enterprise::Trapperkeeper::Bootstrap_cfg[certificate-authority-service]: Scheduling refresh of Service[pe-puppetserver]
Notice: /Stage[main]/Puppet_enterprise::Master::Puppetserver/Service[pe-puppetserver]: Triggered 'refresh' from 44 events
Notice: Applied catalog in 43.61 seconds

At this point, you should be able to perform your first deployment with Code Manager:

puppet code deploy --all --wait

Follow the Code Manager documentation for further instructions on settings up webhooks, etc.