diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f46440f99971c70e3258e04bcbac90379f431e47..b4fffe8144a786c8ec85a3c63028d73f2fa28177 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,6 @@ stages: - check-code - - compile + - tests check-code: image: ninfra/puppet-checker:0.0.1 @@ -17,7 +17,7 @@ compile-profiles: variables: GIT_SUBMODULE_STRATEGY: recursive USE_PUPPETDB: 'false' - stage: compile + stage: tests script: - 'echo "127.0.1.1 $( facter fqdn )" >> /etc/hosts' - 'service puppet-master start' @@ -29,3 +29,9 @@ compile-profiles: done < <( find ./profile/manifests/ -name "*.pp" -a -exec grep ^class "{}" \; | awk "{ print \$2 }" );' tags: - docker + +enc-test: + image: debian:stable + stage: tests + script: + - '${CI_PROJECT_DIR}/profile/files/puppet/puppet_node_classifier --run-tests' diff --git a/profile/files/puppet/puppet_node_classifier b/profile/files/puppet/puppet_node_classifier new file mode 100755 index 0000000000000000000000000000000000000000..6dbe8dbd3858050fa07d34eeb73d78a22d057462 --- /dev/null +++ b/profile/files/puppet/puppet_node_classifier @@ -0,0 +1,133 @@ +#!/bin/bash +# +# Puppet External Node Classifier +# ------------------------------- +# +# This script parses FQDNs in the following format: +# +# ROLE-ENV-TAG.DOMAIN +# +# And returns the proper classes and environment accordingly: +# +# - ROLE should be alpha-num + '_' and the class returned will be +# role::ROLE. +# +# - ENV: should be alpha-num + '_' and, if it is a prefix of one of +# "production", "staging" or "development", then the corresponding one is +# returned as an environment for the node. Otherwise, ENV itself is +# returned. +# +# - TAG: should be alpha-num + '_' + '+' and should add enough info to make +# sure the FQDN is unique in your infrastructure. +# +# If a FQDN doesn't match as above, no special information is returned. +# +# See: https://puppet.com/docs/puppet/5.5/nodes_external.html + +if [ ${#} -ne 1 ]; then + echo "Usage: ${0} NODE" + exit 1 +fi + +PREFIXED_ENVS='production staging development' + +get_environment() { + + # Return one of the PREFIXED_ENVS defined above if ENV is a prefix of + # one of them, otherwise just return ENV itself. + + prefix=${1} + environment="" + for env in ${PREFIXED_ENVS}; do + if [[ ${env} == ${prefix}* ]]; then + environment=${env} + break + fi + done + if [ -z "${environment}" ]; then + environment="${prefix}" + fi + echo ${environment} +} + +main() { + + # Test whether the given FQDN matches ROLE-ENV-TAG.DOMAIN, and act + # accordingly. + + # 1. role 2. environment 3. tag/id + regex="([[:alnum:]_]+)-([[:alnum:]_]+)-([[:alnum:]_-]+)((\.[[:alnum:]_-]+)?)+$" + fqdn=${1} + + # we're only interested in matching FQDNs + if [[ ! ${fqdn} =~ ${regex} ]]; then + echo "classes:" + exit 0 + fi + + echo "classes:" + echo " - role::${BASH_REMATCH[1]}" + echo "environment: $( get_environment ${BASH_REMATCH[2]} )" +} + +run_tests() { + + # If FQDN = ROLE-ENV-TAG.DOMAIN, we expect `main` to output: + # + # ----------8<---------- + # classes: + # - role::${ROLE} + # environment: ${REAL_ENV} + # ----------8<---------- + # + # where REAL_ENV is either one of PREFIXED_ENVS as defined above (if + # ENV is a prefix of one of them) or just ENV otherwise. + # + # If FQDN does not fit the model, we expect just an empty `classes` + # hash: + # + # ----------8<---------- + # classes: + # ----------8<---------- + + set -ex + + # Test uses of environment prefixes. + + output=$( main otherrole-prod-some-tag.example.com ) + [ "${output}" == $'classes:\n - role::otherrole\nenvironment: production' ] || exit 1 + + output=$( main thirdrole-stag-some-tag.example.com ) + [ "${output}" == $'classes:\n - role::thirdrole\nenvironment: staging' ] || exit 1 + + output=$( main yetanotherrole-dev-some-tag.example.com ) + [ "${output}" == $'classes:\n - role::yetanotherrole\nenvironment: development' ] || exit 1 + + # Test uses of full environment names. + + output=$( main otherrole-prod-some-tag.example.com ) + [ "${output}" == $'classes:\n - role::otherrole\nenvironment: production' ] || exit 1 + + output=$( main thirdrole-stag-some-tag.example.com ) + [ "${output}" == $'classes:\n - role::thirdrole\nenvironment: staging' ] || exit 1 + + output=$( main yetanotherrole-dev-some-tag.example.com ) + [ "${output}" == $'classes:\n - role::yetanotherrole\nenvironment: development' ] || exit 1 + + # Test using an arbitrary environment + + output=$( main somerole-someenv-some-tag.example.com ) + [ "${output}" == $'classes:\n - role::somerole\nenvironment: someenv' ] || exit 1 + + # Test using something that doesn't match ROLE-ENV-TAG.DOMAIN + + output=$( main arbitrary-fqdn.example.com ) + [ "${output}" == $'classes:' ] || exit 1 + +} + +if [ ${1} == '--run-tests' ]; then + run_tests +else + main ${1} +fi diff --git a/profile/manifests/puppet/master.pp b/profile/manifests/puppet/master.pp index 473f3326fb2e55788312a7d0c198964f719d20de..e062a2080ccc3bdbde78e6a0a3411453403c979c 100644 --- a/profile/manifests/puppet/master.pp +++ b/profile/manifests/puppet/master.pp @@ -62,7 +62,7 @@ class profile::puppet::master ( server => true, server_reports => 'store,puppetdb', server_foreman => false, - server_external_nodes => '', + server_external_nodes => '/usr/local/bin/puppet_node_classifier', server_git_repo => true, server_git_repo_path => '/var/lib/gitolite3/repositories/puppet.git', server_git_repo_user => 'gitolite3', @@ -183,4 +183,13 @@ class profile::puppet::master ( require => Class['puppet'], } + # External Node Classifier + file { '/usr/local/bin/puppet_node_classifier': + ensure => file, + source => "puppet://modules/profile/puppet/puppet_node_classifier", + owner => root, + group => root, + mode => '0755'; + } + }