├── .rspec ├── examples ├── hello │ ├── metadata.yaml │ └── manifests │ │ └── init.pp ├── multi │ ├── metadata │ │ ├── node2.yaml │ │ ├── metadata.yaml │ │ └── node1.yaml │ ├── Puppetfile │ └── manifests │ │ └── init.pp ├── cache_primer │ ├── metadata.yaml │ ├── manifests │ │ └── init.pp │ ├── Puppetfile │ └── scripts │ │ └── cleanup_images.sh ├── file │ ├── metadata.yaml │ └── manifests │ │ └── init.pp ├── nginx │ ├── metadata.yaml │ ├── Puppetfile │ └── manifests │ │ └── init.pp ├── apache │ ├── metadata.yaml │ ├── Puppetfile │ └── manifests │ │ └── init.pp ├── elasticsearch │ ├── hiera.yaml │ ├── hieradata │ │ └── common.yaml │ ├── Puppetfile │ └── manifests │ │ └── init.pp └── master │ ├── Puppetfile │ └── code │ ├── environments │ └── production │ │ ├── Puppetfile │ │ ├── hieradata │ │ └── common.yaml │ │ ├── manifests │ │ └── site.pp │ │ └── environment.conf │ └── hiera.yaml ├── spec ├── fixtures │ ├── nginx │ │ ├── metadata.yaml │ │ ├── Puppetfile │ │ └── manifests │ │ │ └── init.pp │ └── invalid │ │ └── manifests │ │ └── init.pp ├── support │ ├── system_running_rkt.rb │ ├── system_running_docker.rb │ └── examples │ │ ├── autosign_capable_builder.rb │ │ ├── apt_cache_capable_builder.rb │ │ ├── http_proxy_capable_builder.rb │ │ └── imagebuilder.rb ├── acceptance │ ├── nodesets │ │ ├── centos-7-x64.yml │ │ └── default.yml │ └── image_build_spec.rb ├── unit │ ├── acifile_spec.rb │ ├── imagebuilder_spec.rb │ ├── dockerfile_spec.rb │ ├── deep_symbolize_keys_spec.rb │ ├── acibuilder_spec.rb │ ├── dockerbuilder_spec.rb │ ├── aci_face_spec.rb │ └── docker_face_spec.rb ├── spec_helper.rb └── spec_helper_acceptance.rb ├── .pmtignore ├── lib ├── puppet │ ├── application │ │ ├── aci.rb │ │ └── docker.rb │ └── face │ │ ├── aci.rb │ │ └── docker.rb └── puppet_x │ └── puppetlabs │ ├── deep_symbolize_keys.rb │ ├── acifile.rb │ ├── dockerfile.rb │ ├── imagebuilder_face.rb │ └── imagebuilder.rb ├── .gitignore ├── .travis.yml ├── MAINTAINERS ├── metadata.json ├── Guardfile ├── LICENSE ├── Gemfile ├── Rakefile ├── CHANGELOG.md ├── templates ├── build-aci.sh.erb └── Dockerfile.erb ├── .rubocop.yml └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /examples/hello/metadata.yaml: -------------------------------------------------------------------------------- 1 | image_name: puppet/hello 2 | -------------------------------------------------------------------------------- /examples/multi/metadata/node2.yaml: -------------------------------------------------------------------------------- 1 | image_name: puppet/node2 2 | -------------------------------------------------------------------------------- /spec/fixtures/nginx/metadata.yaml: -------------------------------------------------------------------------------- 1 | cmd: nginx 2 | expose: 80 3 | -------------------------------------------------------------------------------- /examples/multi/metadata/metadata.yaml: -------------------------------------------------------------------------------- 1 | cmd: nginx 2 | expose: 80 3 | -------------------------------------------------------------------------------- /examples/cache_primer/metadata.yaml: -------------------------------------------------------------------------------- 1 | image_name: puppet/cache_primer 2 | -------------------------------------------------------------------------------- /examples/multi/metadata/node1.yaml: -------------------------------------------------------------------------------- 1 | image_name: puppet/node1 2 | expose: 90 3 | -------------------------------------------------------------------------------- /examples/cache_primer/manifests/init.pp: -------------------------------------------------------------------------------- 1 | notify { 'This is the demo notify': } 2 | -------------------------------------------------------------------------------- /examples/file/metadata.yaml: -------------------------------------------------------------------------------- 1 | image_name: puppet/file 2 | cmd: cat /var/temp 3 | 4 | -------------------------------------------------------------------------------- /examples/nginx/metadata.yaml: -------------------------------------------------------------------------------- 1 | cmd: nginx 2 | expose: 80 3 | image_name: puppet/nginx 4 | -------------------------------------------------------------------------------- /.pmtignore: -------------------------------------------------------------------------------- 1 | junit/** 2 | log/**/** 3 | examples/master/code/environments/production/modules/** 4 | -------------------------------------------------------------------------------- /examples/apache/metadata.yaml: -------------------------------------------------------------------------------- 1 | cmd: "/usr/sbin/apache2,-DFOREGROUND" 2 | expose: 80 3 | image_name: puppet/apache 4 | -------------------------------------------------------------------------------- /spec/fixtures/invalid/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # Note the invalid syntax below 2 | package { 'php8': 3 | ensure: installed 4 | } 5 | -------------------------------------------------------------------------------- /examples/elasticsearch/hiera.yaml: -------------------------------------------------------------------------------- 1 | :hierarchy: 2 | - common 3 | :backends: 4 | - yaml 5 | :yaml: 6 | :datadir: '/hieradata' 7 | -------------------------------------------------------------------------------- /examples/file/manifests/init.pp: -------------------------------------------------------------------------------- 1 | file { '/var/temp': 2 | ensure => present, 3 | content => 'Hello Puppet and Docker', 4 | } 5 | -------------------------------------------------------------------------------- /lib/puppet/application/aci.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/face_base' 2 | 3 | class Puppet::Application::Aci < Puppet::Application::FaceBase 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/application/docker.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/face_base' 2 | 3 | class Puppet::Application::Docker < Puppet::Application::FaceBase 4 | end 5 | -------------------------------------------------------------------------------- /examples/apache/Puppetfile: -------------------------------------------------------------------------------- 1 | forge 'https://forgeapi.puppetlabs.com' 2 | 3 | mod 'puppetlabs/apache' 4 | mod 'puppetlabs/stdlib' 5 | mod 'puppetlabs/concat' 6 | -------------------------------------------------------------------------------- /examples/elasticsearch/hieradata/common.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | elasticsearch::version: 2.3.4 3 | elasticsearch::manage_repo: true 4 | elasticsearch::repo_version: 2.x 5 | elasticsearch::java_install: true 6 | -------------------------------------------------------------------------------- /examples/hello/manifests/init.pp: -------------------------------------------------------------------------------- 1 | file { '/var/puppet': 2 | ensure => directory, 3 | } 4 | 5 | file { '/var/puppet/hello': 6 | ensure => present, 7 | content => 'Hello Puppet and Docker', 8 | } 9 | -------------------------------------------------------------------------------- /examples/master/Puppetfile: -------------------------------------------------------------------------------- 1 | forge 'https://forgeapi.puppetlabs.com' 2 | 3 | mod 'DracoBlue/nginx' 4 | mod 'puppetlabs/stdlib' 5 | mod 'puppetlabs/concat' 6 | mod 'puppetlabs/apt' 7 | mod 'puppetlabs/dummy_service' 8 | -------------------------------------------------------------------------------- /examples/multi/Puppetfile: -------------------------------------------------------------------------------- 1 | forge 'https://forgeapi.puppetlabs.com' 2 | 3 | mod 'jfryman/nginx' 4 | mod 'puppetlabs/stdlib' 5 | mod 'puppetlabs/concat' 6 | mod 'puppetlabs/apt' 7 | mod 'puppetlabs/dummy_service' 8 | -------------------------------------------------------------------------------- /examples/nginx/Puppetfile: -------------------------------------------------------------------------------- 1 | forge 'https://forgeapi.puppetlabs.com' 2 | 3 | mod 'jfryman/nginx' 4 | mod 'puppetlabs/stdlib' 5 | mod 'puppetlabs/concat' 6 | mod 'puppetlabs/apt' 7 | mod 'puppetlabs/dummy_service' 8 | -------------------------------------------------------------------------------- /examples/cache_primer/Puppetfile: -------------------------------------------------------------------------------- 1 | forge 'https://forgeapi.puppetlabs.com' 2 | 3 | mod 'jfryman/nginx' 4 | mod 'puppetlabs/apt' 5 | mod 'puppetlabs/stdlib' 6 | mod 'puppetlabs/concat' 7 | mod 'puppetlabs/dummy_service' 8 | -------------------------------------------------------------------------------- /spec/fixtures/nginx/Puppetfile: -------------------------------------------------------------------------------- 1 | forge 'https://forgeapi.puppetlabs.com' 2 | 3 | mod 'jfryman/nginx' 4 | mod 'puppetlabs/stdlib' 5 | mod 'puppetlabs/concat' 6 | mod 'puppetlabs/apt' 7 | mod 'puppetlabs/dummy_service' 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.sw? 2 | /pkg 3 | /spec/fixtures/manifests 4 | /spec/fixtures/modules 5 | /.rspec_system 6 | /.vagrant 7 | /.bundle 8 | /vendor 9 | /Gemfile.lock 10 | /junit 11 | /log 12 | .yardoc 13 | coverage 14 | doc 15 | -------------------------------------------------------------------------------- /examples/master/code/environments/production/Puppetfile: -------------------------------------------------------------------------------- 1 | forge 'https://forgeapi.puppetlabs.com' 2 | 3 | mod 'jfryman/nginx' 4 | mod 'puppetlabs/stdlib' 5 | mod 'puppetlabs/concat' 6 | mod 'puppetlabs/apt' 7 | mod 'puppetlabs/dummy_service' 8 | -------------------------------------------------------------------------------- /spec/support/system_running_rkt.rb: -------------------------------------------------------------------------------- 1 | shared_examples 'a system running rkt' do 2 | %w[rkt acbuild].each do |command| 3 | describe command("#{command} version") do 4 | its(:exit_status) { should eq 0 } 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | cache: bundler 4 | bundler_args: --without development acceptance 5 | script: bundle exec rake test 6 | matrix: 7 | fast_finish: true 8 | include: 9 | - rvm: 2.1.6 10 | env: PUPPET_GEM_VERSION="~> 4.0" 11 | -------------------------------------------------------------------------------- /examples/elasticsearch/Puppetfile: -------------------------------------------------------------------------------- 1 | forge 'https://forgeapi.puppetlabs.com' 2 | 3 | mod 'elasticsearch/elasticsearch' 4 | mod 'richardc/datacat' 5 | mod 'puppetlabs/java' 6 | mod 'puppetlabs/java_ks' 7 | mod 'puppetlabs/apt' 8 | mod 'ceritsc/yum' 9 | mod 'puppetlabs/stdlib' 10 | mod 'puppetlabs/dummy_service' 11 | -------------------------------------------------------------------------------- /spec/support/system_running_docker.rb: -------------------------------------------------------------------------------- 1 | shared_examples 'a system running docker' do 2 | describe package('docker-engine') do 3 | it { is_expected.to be_installed } 4 | end 5 | 6 | describe service('docker') do 7 | it { is_expected.to be_enabled } 8 | it { is_expected.to be_running } 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "file_format": "This MAINTAINERS file format is described at https://github.com/puppetlabs/maintainers", 4 | "issues": "https://github.com/puppetlabs/puppetlabs-image_build/issues", 5 | "people": [ 6 | { 7 | "github": "garethr", 8 | "email": "gareth@puppet.com", 9 | "name": "Gareth Rushgrove" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /spec/acceptance/nodesets/centos-7-x64.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | centos-7: 3 | roles: 4 | - default 5 | platform: el-7-x86_64 6 | box: puppetlabs/centos-7.0-64-nocm 7 | box_url: https://vagrantcloud.com/puppetlabs/boxes/centos-7.0-64-nocm 8 | hypervisor: vagrant 9 | vagrant_memsize: 2048 10 | 11 | CONFIG: 12 | log_level: debug 13 | type: foss 14 | trace_limit: 200 15 | -------------------------------------------------------------------------------- /examples/apache/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class { '::apache': 2 | default_vhost => false, 3 | service_manage => false, 4 | use_systemd => false, 5 | } 6 | 7 | apache::vhost { 'localhost': 8 | port => '80', 9 | docroot => '/var/www/html', 10 | } 11 | 12 | file { '/var/www/html/index.html': 13 | ensure => present, 14 | content => 'Apache running on Docker; built by Puppet!', 15 | } 16 | -------------------------------------------------------------------------------- /spec/acceptance/nodesets/default.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | ubuntu-1404: 3 | roles: 4 | - default 5 | platform: ubuntu-1404-amd64 6 | box: puppetlabs/ubuntu-14.04-64-nocm 7 | box_url: https://vagrantcloud.com/puppetlabs/boxes/ubuntu-14.04-64-nocm 8 | hypervisor: vagrant 9 | vagrant_memsize: 2048 10 | 11 | CONFIG: 12 | log_level: debug 13 | type: foss 14 | trace_limit: 200 15 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/deep_symbolize_keys.rb: -------------------------------------------------------------------------------- 1 | class Object 2 | def deep_symbolize_keys 3 | if is_a? Hash 4 | return reduce({}) do |memo, (k, v)| 5 | memo.tap { |m| m[k.to_sym] = v.deep_symbolize_keys } 6 | end 7 | end 8 | if is_a? Array 9 | return each_with_object([]) do |v, memo| 10 | memo << v.deep_symbolize_keys 11 | end 12 | end 13 | self 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/acifile_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'puppet_x/puppetlabs/acifile' 3 | 4 | describe PuppetX::Puppetlabs::Acifile do 5 | context '#render' do 6 | it 'should return a string' do 7 | expect(subject.render).to be_a(String) 8 | end 9 | end 10 | 11 | context '#save' do 12 | it 'should return a file handle' do 13 | expect(subject.save).to be_a_kind_of(Tempfile) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /examples/master/code/hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | :backends: 3 | - yaml 4 | :hierarchy: 5 | - "nodes/%{::trusted.certname}" 6 | - common 7 | 8 | :yaml: 9 | # datadir is empty here, so hiera uses its defaults: 10 | # - /etc/puppetlabs/code/environments/%{environment}/hieradata on *nix 11 | # - %CommonAppData%\PuppetLabs\code\environments\%{environment}\hieradata on Windows 12 | # When specifying a datadir, make sure the directory exists. 13 | :datadir: 14 | -------------------------------------------------------------------------------- /spec/unit/imagebuilder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'puppet_x/puppetlabs/imagebuilder' 3 | 4 | describe PuppetX::Puppetlabs::ImageBuilder do 5 | let(:from) { 'debian:8' } 6 | let(:image_name) { 'puppet/sample' } 7 | let(:manifest) { Tempfile.new('manifest.pp') } 8 | let(:builder) { PuppetX::Puppetlabs::ImageBuilder.new(manifest.path, args) } 9 | let(:context) { builder.context } 10 | 11 | it_behaves_like 'an image builder' 12 | end 13 | -------------------------------------------------------------------------------- /examples/nginx/manifests/init.pp: -------------------------------------------------------------------------------- 1 | Service { 2 | provider => dummy 3 | } 4 | 5 | class { 'nginx': } 6 | 7 | nginx::resource::vhost { 'default': 8 | www_root => '/var/www/html', 9 | } 10 | 11 | file { '/var/www/html/index.html': 12 | ensure => present, 13 | content => 'Hello Puppet and Docker', 14 | } 15 | 16 | exec { 'Disable Nginx daemon mode': 17 | path => '/bin', 18 | command => 'echo "daemon off;" >> /etc/nginx/nginx.conf', 19 | unless => 'grep "daemon off" /etc/nginx/nginx.conf', 20 | } 21 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppetlabs-image_build", 3 | "version": "0.7.0", 4 | "author": "Puppet", 5 | "summary": "Build Docker and ACI images using Puppet code", 6 | "license": "Apache-2.0", 7 | "source": "https://github.com/puppetlabs/puppetlabs-image_build.git", 8 | "project_page": "https://github.com/puppetlabs/puppetlabs-image_build", 9 | "issues_url": "https://github.com/puppetlabs/puppetlabs-image_build/issues", 10 | "dependencies": [ 11 | 12 | ], 13 | "data_provider": null 14 | } 15 | -------------------------------------------------------------------------------- /spec/fixtures/nginx/manifests/init.pp: -------------------------------------------------------------------------------- 1 | Service { 2 | provider => dummy 3 | } 4 | 5 | class { 'nginx': } 6 | 7 | nginx::resource::vhost { 'default': 8 | www_root => '/var/www/html', 9 | } 10 | 11 | file { '/var/www/html/index.html': 12 | ensure => present, 13 | content => 'Hello Puppet and Docker', 14 | } 15 | 16 | exec { 'Disable Nginx daemon mode': 17 | path => '/bin', 18 | command => 'echo "daemon off;" >> /etc/nginx/nginx.conf', 19 | unless => 'grep "daemon off" /etc/nginx/nginx.conf', 20 | } 21 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | notification :off 2 | 3 | scope group: :test 4 | 5 | group :test do 6 | guard 'rake', task: 'test' do 7 | watch(%r{^manifests\/(.+)\.pp$}) 8 | watch(%r{^spec\/(.+)\.rb$}) 9 | watch(%r{^lib\/(.+)\.rb$}) 10 | watch(%r{^templates\/(.+)\.erb$}) 11 | end 12 | end 13 | 14 | group :critic do 15 | guard 'rake', task: 'rubycritic' do 16 | watch(%r{^manifests\/(.+)\.pp$}) 17 | watch(%r{^spec\/(.+)\.rb$}) 18 | watch(%r{^lib\/(.+)\.rb$}) 19 | watch(%r{^templates\/(.+)\.erb$}) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/unit/dockerfile_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'puppet_x/puppetlabs/dockerfile' 3 | 4 | describe PuppetX::Puppetlabs::Dockerfile do 5 | context '#render' do 6 | it 'should return a string' do 7 | expect(subject.render).to be_a(String) 8 | end 9 | 10 | it 'with no arguments should return a blank Dockerfile' do 11 | expect(subject.render).to be_empty 12 | end 13 | end 14 | 15 | context '#save' do 16 | it 'should return a file handle' do 17 | expect(subject.save).to be_a_kind_of(Tempfile) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /examples/master/code/environments/production/hieradata/common.yaml: -------------------------------------------------------------------------------- 1 | puppet_enterprise::profile::puppetdb::listen_address: '0.0.0.0' 2 | puppet_enterprise::profile::amq::broker::heap_mb: '96' 3 | puppet_enterprise::profile::master::java_args: 4 | Xmx: '192m' 5 | Xms: '32m' 6 | 'XX:MaxPermSize': '=96m' 7 | 'XX:PermSize': '=96m' 8 | puppet_enterprise::profile::puppetdb::java_args: 9 | Xmx: '96m' 10 | Xms: '32m' 11 | puppet_enterprise::profile::console::java_args: 12 | Xmx: '64m' 13 | Xms: '32m' 14 | puppet_enterprise::profile::console::delayed_job_workers: 1 15 | puppet_enterprise::profile::database::shared_buffers: '4MB' 16 | -------------------------------------------------------------------------------- /examples/multi/manifests/init.pp: -------------------------------------------------------------------------------- 1 | define webserver { 2 | Service { 3 | provider => dummy 4 | } 5 | 6 | class { 'nginx': } 7 | 8 | nginx::resource::vhost { 'default': 9 | www_root => '/var/www/html', 10 | } 11 | 12 | file { '/var/www/html/index.html': 13 | ensure => present, 14 | content => $title, 15 | } 16 | 17 | exec { 'Disable Nginx daemon mode': 18 | path => '/bin', 19 | command => 'echo "daemon off;" >> /etc/nginx/nginx.conf', 20 | unless => 'grep "daemon off" /etc/nginx/nginx.conf', 21 | } 22 | } 23 | 24 | 25 | node 'node1' { 26 | webserver { 'hello node 1': } 27 | } 28 | 29 | node 'node2' { 30 | webserver { 'hello node 2': } 31 | } 32 | -------------------------------------------------------------------------------- /examples/master/code/environments/production/manifests/site.pp: -------------------------------------------------------------------------------- 1 | define webserver { 2 | Service { 3 | provider => dummy 4 | } 5 | 6 | class { 'nginx': } 7 | 8 | file { '/var/www/html/index.html': 9 | ensure => present, 10 | content => $title, 11 | require => Class['nginx::package'], 12 | } 13 | 14 | exec { 'Disable Nginx daemon mode': 15 | path => '/bin', 16 | command => 'echo "daemon off;" >> /etc/nginx/nginx.conf', 17 | unless => 'grep "daemon off" /etc/nginx/nginx.conf', 18 | require => Class['nginx::package'], 19 | } 20 | } 21 | 22 | 23 | node /^node1/ { 24 | webserver { 'hello node 1': } 25 | } 26 | 27 | node /^node2/ { 28 | webserver { 'hello node 2': } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | puppetlabs/image_build 2 | 3 | Copyright (C) 2005-2017 Puppet, Inc. 4 | 5 | Puppet, Inc. can be contacted at: info@puppet.com 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | https://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/acifile.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | require 'ostruct' 3 | require 'tempfile' 4 | 5 | module PuppetX 6 | module Puppetlabs 7 | class Acifile < OpenStruct 8 | attr_accessor :template 9 | def initialize(hash = nil, &block) 10 | basepath = File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))) 11 | @template = File.join(basepath, 'templates', '/build-aci.sh.erb') 12 | super 13 | end 14 | 15 | def render 16 | ERB.new(IO.read(@template)).result(binding).gsub(%r{\n\n+}, "\n\n").strip 17 | end 18 | 19 | def save 20 | file = Tempfile.new('build-aci.sh', Dir.pwd) 21 | file.write(render) 22 | file.close 23 | file 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/dockerfile.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | require 'ostruct' 3 | require 'tempfile' 4 | 5 | module PuppetX 6 | module Puppetlabs 7 | class Dockerfile < OpenStruct 8 | attr_accessor :template 9 | def initialize(hash = nil, &block) 10 | basepath = File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))) 11 | @template = File.join(basepath, 'templates', '/Dockerfile.erb') 12 | super 13 | end 14 | 15 | def render 16 | ERB.new(IO.read(@template)).result(binding).gsub(%r{\n\n+}, "\n\n").strip 17 | end 18 | 19 | def save 20 | file = Tempfile.new('Dockerfile', Dir.pwd) 21 | file.write(render) 22 | file.close 23 | file 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | require 'simplecov' 3 | require 'simplecov-console' 4 | require 'coveralls' 5 | 6 | # automatically load any shared examples or contexts 7 | Dir['./spec/support/**/*.rb'].sort.each { |f| require f } 8 | 9 | SimpleCov.start do 10 | add_filter '/spec' 11 | formatter SimpleCov::Formatter::MultiFormatter.new([ 12 | Coveralls::SimpleCov::Formatter, 13 | SimpleCov::Formatter::HTMLFormatter, 14 | SimpleCov::Formatter::Console 15 | ]) 16 | end 17 | 18 | RSpec.configure do |config| 19 | config.mock_with :rspec 20 | end 21 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :test do 4 | gem 'coveralls', require: false 5 | gem 'metadata-json-lint' 6 | gem 'puppet', ENV['PUPPET_GEM_VERSION'] || '~> 4' 7 | gem 'puppetlabs_spec_helper' 8 | gem 'rake' 9 | gem 'rspec' 10 | gem 'rubocop' 11 | gem 'rubocop-rspec' 12 | gem 'simplecov' 13 | gem 'simplecov-console' 14 | end 15 | 16 | group :development do 17 | gem 'guard-rake' 18 | gem 'listen', '<3.1' 19 | gem 'maintainers' 20 | gem 'pry' 21 | gem 'puppet-blacksmith' 22 | gem 'r10k' 23 | gem 'rubycritic', require: false 24 | gem 'travis' 25 | gem 'travis-lint' 26 | gem 'yard' 27 | end 28 | 29 | group :acceptance do 30 | gem 'beaker', '~> 2.0' 31 | gem 'beaker-puppet_install_helper' 32 | gem 'beaker-rspec' 33 | gem 'beaker_spec_helper' 34 | end 35 | -------------------------------------------------------------------------------- /spec/unit/deep_symbolize_keys_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'puppet_x/puppetlabs/deep_symbolize_keys' 3 | 4 | describe 'deep_symbolize_keys' do 5 | describe Hash do 6 | it 'should have been monkey patched with deep_symbolize_keys' do 7 | expect(subject.respond_to?(:deep_symbolize_keys)) 8 | end 9 | it 'should have symbols for keys after deep_symbolize_keys is called' do 10 | expect({ 'a' => 1 }.deep_symbolize_keys).to eq(a: 1) 11 | end 12 | end 13 | 14 | describe Array do 15 | it 'should have been monkey patched with deep_symbolize_keys' do 16 | expect(subject.respond_to?(:deep_symbolize_keys)) 17 | end 18 | it 'should have symbols for keys after deep_symbolize_keys is called' do 19 | expect([{ 'a' => 1 }].deep_symbolize_keys).to eq([{ a: 1 }]) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/support/examples/autosign_capable_builder.rb: -------------------------------------------------------------------------------- 1 | shared_examples 'an autosign capable builder' do 2 | context 'with minimal arguments' do 3 | let(:args) do 4 | { 5 | from: from, 6 | image_name: image_name 7 | } 8 | end 9 | it 'should not pass an autosign token build argument' do 10 | expect(builder.send(:build_command)).not_to include('--build-arg AUTOSIGN_TOKEN=') 11 | end 12 | end 13 | 14 | context 'with an autosign token specified' do 15 | let(:token) { '12345abcde' } 16 | let(:args) do 17 | { 18 | from: from, 19 | image_name: image_name, 20 | autosign_token: token 21 | } 22 | end 23 | it 'should pass the token as a build argument' do 24 | expect(builder.send(:build_command)).to include("--build-arg AUTOSIGN_TOKEN=#{token}") 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/support/examples/apt_cache_capable_builder.rb: -------------------------------------------------------------------------------- 1 | shared_examples 'a builder capable of utilising an APT cache' do 2 | context 'with minimal arguments' do 3 | let(:args) do 4 | { 5 | from: from, 6 | image_name: image_name 7 | } 8 | end 9 | it 'should not pass a proxy as a build argument' do 10 | expect(builder.send(:build_command)).not_to include('--build-arg APT_PROXY=') 11 | end 12 | end 13 | 14 | context 'with an APT proxy specified' do 15 | let(:proxy) { 'http://example.com:3142' } 16 | let(:args) do 17 | { 18 | from: from, 19 | image_name: image_name, 20 | apt_proxy: proxy 21 | } 22 | end 23 | it 'should pass the proxy as a build argument' do 24 | expect(builder.send(:build_command)).to include("--build-arg APT_PROXY=#{proxy}") 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /examples/elasticsearch/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # It's common for systemd services to need to refresh systemctl 2 | # by always returning true we avoid issues with what in the context 3 | # of this module are unhelpful execs 4 | file { '/bin/systemctl': 5 | ensure => link, 6 | target => '/bin/true', 7 | } 8 | 9 | # In cases where included modules explicitly set the provider 10 | # the resource default won't work, so you may need to 11 | # drop down and use resource collectors 12 | Service <| |> { provider => dummy } 13 | 14 | class { 'elasticsearch': } 15 | elasticsearch::instance { 'es-01': 16 | status => 'unmanaged', 17 | } 18 | 19 | $elasticsearch_cmd = "#!/bin/bash -e\n/usr/share/elasticsearch/bin/elasticsearch -Des.default.path.conf=/etc/elasticsearch/es-01/ -Des.insecure.allow.root=true" 20 | file { '/docker-entrypoint.sh': 21 | content => $elasticsearch_cmd, 22 | mode => '0744', 23 | } 24 | 25 | -------------------------------------------------------------------------------- /spec/unit/acibuilder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'puppet_x/puppetlabs/imagebuilder' 3 | 4 | describe PuppetX::Puppetlabs::AciBuilder do 5 | let(:from) { 'debian:8' } 6 | let(:image_name) { 'puppet/sample' } 7 | let(:manifest) { Tempfile.new('manifest.pp') } 8 | let(:builder) { PuppetX::Puppetlabs::AciBuilder.new(manifest.path, args) } 9 | let(:context) { builder.context } 10 | 11 | it_behaves_like 'an image builder' 12 | 13 | context 'with minimal arguments' do 14 | let(:args) do 15 | { 16 | from: from, 17 | image_name: image_name 18 | } 19 | end 20 | 21 | it '#build_file should return a Acifile object' do 22 | expect(builder.build_file).to be_a(PuppetX::Puppetlabs::Acifile) 23 | end 24 | 25 | it 'should use bash as the default build command' do 26 | expect(builder.send(:build_command)).to start_with('bash') 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /examples/master/code/environments/production/environment.conf: -------------------------------------------------------------------------------- 1 | # Each environment can have an environment.conf file. Its settings will only 2 | # affect its own environment. See docs for more info: 3 | # https://docs.puppetlabs.com/puppet/latest/reference/config_file_environment.html 4 | 5 | # Any unspecified settings use default values; some of those defaults are based 6 | # on puppet.conf settings. 7 | 8 | # If these settings include relative file paths, they'll be resolved relative to 9 | # this environment's directory. 10 | 11 | # Allowed settings and default values: 12 | 13 | # modulepath = ./modules:$basemodulepath 14 | # manifest = (default_manifest from puppet.conf, which defaults to ./manifests) 15 | # config_version = (no script; Puppet will use the time the catalog was compiled) 16 | # environment_timeout = (environment_timeout from puppet.conf, which defaults to 0) 17 | # Note: unless you have a specific reason, we recommend only setting 18 | # environment_timeout in puppet.conf. 19 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | 4 | require 'rubocop/rake_task' 5 | require 'puppetlabs_spec_helper/rake_tasks' 6 | require 'puppet-lint/tasks/puppet-lint' 7 | require 'puppet-syntax/tasks/puppet-syntax' 8 | 9 | # These gems aren't always present, for instance 10 | # on Travis with --without development 11 | 12 | begin 13 | require 'puppet_blacksmith/rake_tasks' 14 | rescue LoadError # rubocop:disable Lint/HandleExceptions 15 | end 16 | 17 | begin 18 | require 'rubycritic/rake_task' 19 | RubyCritic::RakeTask.new do |task| 20 | task.paths = FileList['lib/**/*.rb'] 21 | task.options = '--format=console --no-browser' 22 | end 23 | rescue LoadError # rubocop:disable Lint/HandleExceptions 24 | end 25 | 26 | ignore_paths = ['contrib/**/*.pp', 'examples/**/*.pp', 'spec/**/*.pp', 'pkg/**/*.pp', 'vendor/**/*'] 27 | 28 | Rake::Task[:lint].clear 29 | PuppetLint::RakeTask.new :lint do |config| 30 | config.ignore_paths = ignore_paths 31 | end 32 | 33 | PuppetSyntax.exclude_paths = ignore_paths 34 | 35 | RuboCop::RakeTask.new 36 | 37 | task test: [ 38 | 'check:symlinks', 39 | 'check:dot_underscore', 40 | 'check:git_ignore', 41 | :lint, 42 | :syntax, 43 | :rubocop, 44 | :metadata_lint, 45 | :spec 46 | ] 47 | -------------------------------------------------------------------------------- /spec/support/examples/http_proxy_capable_builder.rb: -------------------------------------------------------------------------------- 1 | shared_examples 'a builder capable of utilising an HTTP proxy' do 2 | context 'with minimal arguments' do 3 | let(:args) do 4 | { 5 | from: from, 6 | image_name: image_name 7 | } 8 | end 9 | it 'should not pass a proxy as a build argument' do 10 | expect(builder.send(:build_command)).not_to include('--build-arg http_proxy=') 11 | end 12 | it 'should not pass an https proxy as a build argument' do 13 | expect(builder.send(:build_command)).not_to include('--build-arg https_proxy=') 14 | end 15 | end 16 | 17 | context 'with an HTTP and HTTPS proxy specified' do 18 | let(:http) { 'http://example.com:3142' } 19 | let(:https) { 'https://example.com:3143' } 20 | let(:args) do 21 | { 22 | from: from, 23 | image_name: image_name, 24 | http_proxy: http, 25 | https_proxy: https 26 | } 27 | end 28 | it 'should pass the proxy as a build argument' do 29 | expect(builder.send(:build_command)).to include("--build-arg http_proxy=#{http}") 30 | end 31 | it 'should pass the proxy as a build argument' do 32 | expect(builder.send(:build_command)).to include("--build-arg https_proxy=#{https}") 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/puppet/face/aci.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_x/puppetlabs/imagebuilder' 2 | require 'puppet_x/puppetlabs/imagebuilder_face' 3 | 4 | PuppetX::Puppetlabs::ImageBuilder::Face.define(:aci, '0.1.0') do 5 | summary 'Build Aci images and build scripts using Puppet code' 6 | 7 | action(:build) do 8 | summary 'Build an ACI image using Puppet' 9 | arguments '[]' 10 | default 11 | 12 | option '--autosign-token STRING' do 13 | summary 'An authentication token used for autosigning master-built images' 14 | end 15 | 16 | when_invoked do |*options| 17 | args = options.pop 18 | manifest = options.empty? ? 'manifests/init.pp' : options.first 19 | begin 20 | builder = PuppetX::Puppetlabs::AciBuilder.new(manifest, args) 21 | builder.build 22 | rescue PuppetX::Puppetlabs::BuildError => e 23 | raise "An error occured and the build process was interupted: #{e.message}" 24 | rescue PuppetX::Puppetlabs::InvalidContextError => e 25 | raise e.message 26 | end 27 | end 28 | end 29 | 30 | action(:script) do 31 | summary 'Output a shell script for building an ACI with Puppet' 32 | arguments '[]' 33 | when_invoked do |*options| 34 | args = options.pop 35 | manifest = options.empty? ? 'manifests/init.pp' : options.first 36 | begin 37 | builder = PuppetX::Puppetlabs::AciBuilder.new(manifest, args) 38 | builder.build_file.render 39 | rescue PuppetX::Puppetlabs::InvalidContextError => e 40 | raise e.message 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /examples/cache_primer/scripts/cleanup_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function log 4 | { 5 | echo -e "$1" 6 | } 7 | 8 | function error 9 | { 10 | log "$1" 11 | exit 2 12 | } 13 | 14 | function usage 15 | { 16 | log "Usage: $0 " 17 | log "\nTo see the current docker images : docker images" 18 | log "\nRun docker images to see what cache image and os image to retain" 19 | log "The os type and base image have to be explicit to protect the docker image cache." 20 | error "\nDeleting the cached image will result in longer build times" 21 | } 22 | 23 | function verify 24 | { 25 | local result=$1 26 | local error_msg=$2 27 | 28 | if [ ${result} -ne 0 ];then 29 | log "${error_msg}" 30 | fi 31 | } 32 | 33 | if [ $# -ne 2 ];then 34 | log "Wrong number of arguments, got [$#] expected [2]" 35 | usage 36 | fi 37 | 38 | OPT_BASE=$1 39 | OPT_OS=$2 40 | 41 | num_containers=$(docker ps -aq | wc -l) 42 | if [ ${num_containers} -gt 0 ];then 43 | log "Found ${num_containers} containers .. Removing" 44 | docker rm --force $(docker ps -aq) 45 | verify $? "Failed to remove the docker containers. Please run docker ps -aq and docker rm --force the results" 46 | fi 47 | 48 | num_images=$(docker images | grep -v REPOSITORY | grep -v ${OPT_BASE} | grep -v ${OPT_OS} | wc -l) 49 | if [ ${num_images} -gt 0 ];then 50 | log "Found ${num_images} images .. Removing" 51 | docker rmi $(docker images | grep -v REPOSITORY | grep -v ${OPT_BASE} | grep -v ${OPT_OS} | awk '{ print $3 }') 52 | verify $? "Failed to remove the docker images. Please run docker images and perform a docker rmi manually" 53 | fi 54 | 55 | log "Cleanup complete" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ##2017-07-26 - Version 0.6.0 2 | 3 | * Adds the ability to specify a Docker network for the docker build 4 | command 5 | * Can now skip the installation of Puppet if the base image already has 6 | it installed 7 | 8 | This release also fixes an issue where a failure in the Puppet run 9 | during image build would not propogate the error so the command line 10 | exits with a non-zero exit code. 11 | 12 | 13 | ##2017-07-25 - Version 0.5.0 14 | 15 | Adding a couple of user requested features, specifically the ability to 16 | disable the diff output, and also the ability to enable full debug 17 | output from the Puppet process run inside the container during build 18 | 19 | 20 | ##2017-07-25 - Version 0.4.0 21 | 22 | 23 | This release adds support for using the Puppet 5 repositories, and 24 | switches the default Puppet used to build images to Puppet 5. 25 | 26 | 27 | ##2017-03-22 - Version 0.3.0 28 | 29 | This release includes some minor features and several bug fixes, 30 | including: 31 | 32 | * Adds the ability to specify a list of volumes for the image 33 | * Fix a bug which meant building Centos images didn't work as expected 34 | 35 | Thanks to community member @luckyraul for the volumes addition, and to 36 | @aaron-grewell for reporting and testing the fix for the Centos issue. 37 | 38 | 39 | ##2017-02-21 - Version 0.2.0 40 | 41 | This release includes some minor features and several bug fixes, 42 | including: 43 | 44 | * Fail the build if Puppet apply fails 45 | * Support passing a directory of manifests as well as a single manifest 46 | * Correctly discover more debian based images 47 | * Initial support for passing in fact values 48 | * Retain packages useful for r10k and git based Puppet modules 49 | * Support passing an https proxy 50 | 51 | Thanks to community members @gerardkok, @luckyraul, @arnd, @McSlow, 52 | @jonnaladheeraj and @oc243 for input into this release. 53 | -------------------------------------------------------------------------------- /spec/spec_helper_acceptance.rb: -------------------------------------------------------------------------------- 1 | require 'beaker-rspec/spec_helper' 2 | require 'beaker-rspec/helpers/serverspec' 3 | require 'beaker/puppet_install_helper' 4 | require 'beaker_spec_helper' 5 | 6 | include BeakerSpecHelper 7 | 8 | # automatically load any shared examples or contexts 9 | Dir['./spec/support/**/*.rb'].sort.each { |f| require f } 10 | 11 | ENV['PUPPET_INSTALL_TYPE'] = ENV['PUPPET_INSTALL_TYPE'] || 'agent' 12 | run_puppet_install_helper unless ENV['BEAKER_provision'] == 'no' 13 | 14 | RSpec.configure do |c| 15 | proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) 16 | module_name = proj_root.split('-').last 17 | c.formatter = :documentation 18 | c.before :suite do 19 | puppet_module_install(source: proj_root, module_name: module_name) 20 | hosts.each do |host| 21 | BeakerSpecHelper.spec_prep(host) 22 | if fact_on(host, 'osfamily') == 'RedHat' 23 | on(host, 'sudo yum update -y -q') 24 | on(host, 'sudo systemctl stop firewalld') 25 | end 26 | on host, puppet('module', 'install', 'garethr-docker'), acceptable_exit_codes: [0, 1] 27 | on host, puppet('module', 'install', 'puppetlabs-rkt'), acceptable_exit_codes: [0, 1] 28 | end 29 | end 30 | end 31 | 32 | def apply_manifest_on_with_exit(host, manifest) 33 | # acceptable_exit_codes and expect_changes are passed because we want 34 | # detailed-exit-codes but want to make our own assertions about the 35 | # responses. Explicit is better than implicit. 36 | apply_manifest_on(host, manifest, acceptable_exit_codes: (0...256), 37 | expect_changes: true, 38 | debug: true) 39 | end 40 | 41 | def apply_manifest_with_exit(manifest) 42 | # acceptable_exit_codes and expect_changes are passed because we want 43 | # detailed-exit-codes but want to make our own assertions about the 44 | # responses. Explicit is better than implicit. 45 | apply_manifest(manifest, acceptable_exit_codes: (0...256), 46 | expect_changes: true, 47 | debug: true) 48 | end 49 | -------------------------------------------------------------------------------- /lib/puppet/face/docker.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_x/puppetlabs/imagebuilder' 2 | require 'puppet_x/puppetlabs/imagebuilder_face' 3 | 4 | PuppetX::Puppetlabs::ImageBuilder::Face.define(:docker, '0.1.0') do 5 | summary 'Build Docker images and Dockerfiles using Puppet code' 6 | 7 | option '--rocker' do 8 | summary 'Use Rocker as the build tool' 9 | default_to { false } 10 | end 11 | 12 | action(:build) do 13 | summary 'Build a Docker image from Puppet code' 14 | arguments '[]' 15 | default 16 | 17 | [ 18 | 'cgroup-parent STRING', 19 | 'cpu-period INT', 20 | 'cpu-quota INT', 21 | 'cpu-shares INT', 22 | 'cpuset-cpus STRING', 23 | 'cpuset-mems STRING', 24 | 'disable-content-trust', 25 | 'force-rm', 26 | 'isolation STRING', 27 | 'memory-limit STRING', 28 | 'memory-swap STRING', 29 | 'no-cache', 30 | 'pull', 31 | 'quiet', 32 | 'shm-size STRING', 33 | 'ulimit STRING' 34 | ].each do |value| 35 | option "--#{value}" do 36 | summary "#{value.split.first} argument passed to underlying build tool" 37 | end 38 | end 39 | 40 | option '--apt-proxy STRING' do 41 | summary 'A caching proxy for APT packages' 42 | end 43 | 44 | option '--http-proxy STRING' do 45 | summary 'An HTTP proxy to use for outgoing traffic during build' 46 | end 47 | 48 | option '--https-proxy STRING' do 49 | summary 'An HTTPS proxy to use for outgoing traffic during build' 50 | end 51 | 52 | option '--autosign-token STRING' do 53 | summary 'An authentication token used for autosigning master-built images' 54 | end 55 | 56 | option '--network STRING' do 57 | summary 'The Docker network to pass along to the docker build command' 58 | end 59 | 60 | when_invoked do |*options| 61 | args = options.pop 62 | # no-cache is a boolean option, but Puppet cunningly convert anything begining with no 63 | # to false. In thise case this option wants passing straight through to Docker build however 64 | args[:no_cache] = true if args.key? :no_cache 65 | manifest = options.empty? ? 'manifests/init.pp' : options.first 66 | begin 67 | builder = PuppetX::Puppetlabs::DockerBuilder.new(manifest, args) 68 | builder.build 69 | rescue PuppetX::Puppetlabs::BuildError => e 70 | raise "An error occured and the build process was interupted: #{e.message}" 71 | rescue PuppetX::Puppetlabs::InvalidContextError => e 72 | raise e.message 73 | end 74 | end 75 | end 76 | 77 | action(:dockerfile) do 78 | summary 'Generate a Dockerfile which will run the specified Puppet code' 79 | arguments '[]' 80 | when_invoked do |*options| 81 | args = options.pop 82 | manifest = options.empty? ? 'manifests/init.pp' : options.first 83 | begin 84 | builder = PuppetX::Puppetlabs::DockerBuilder.new(manifest, args) 85 | builder.build_file.render 86 | rescue PuppetX::Puppetlabs::InvalidContextError => e 87 | raise e.message 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/acceptance/image_build_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | 3 | describe 'image_build' do 4 | before(:all) do 5 | @pp = <<-EOS 6 | class { 'docker': } 7 | class { 'rkt': 8 | version => '1.13.0' 9 | } 10 | class { 'rkt::acbuild': 11 | version => '0.4.0' 12 | } 13 | EOS 14 | apply_manifest_with_exit(@pp) 15 | scp_to('default', 'spec/fixtures/nginx', '/tmp/nginx/') 16 | scp_to('default', 'spec/fixtures/invalid', '/tmp/invalid/') 17 | end 18 | 19 | it 'should have the puppet docker command installed' do 20 | expect(command('puppet docker --help').exit_status).to eq 0 21 | end 22 | 23 | it 'should have the puppet aci command installed' do 24 | expect(command('puppet aci --help').exit_status).to eq 0 25 | end 26 | 27 | it 'should successfully generate a dockerfile' do 28 | expect(command('cd /tmp/nginx; puppet docker dockerfile --image-name nginx').exit_status).to eq 0 29 | end 30 | 31 | it 'should successfully generate a aci build script' do 32 | expect(command('cd /tmp/nginx; puppet aci script manifests/init.pp --image-name nginx').exit_status).to eq 0 33 | end 34 | 35 | context 'running a docker build' do 36 | before(:all) do 37 | @exit_status = command('cd /tmp/nginx; puppet docker build --image-name nginx').exit_status 38 | end 39 | it 'should successfully run docker build' do 40 | expect(@exit_status).to eq 0 41 | end 42 | it 'should result in a base image being pulled' do 43 | expect(docker_image('ubuntu:16.04')).to exist 44 | end 45 | it 'should result in an image being created' do 46 | expect(docker_image('nginx:latest')).to exist 47 | end 48 | end 49 | 50 | context 'running a docker build with an invalid manifest' do 51 | before(:all) do 52 | @exit_status = command('cd /tmp/invalid; puppet docker build --image-name invalid').exit_status 53 | end 54 | it 'should exit with an error' do 55 | expect(@exit_status).to eq 1 56 | end 57 | it 'should not result in an image being created' do 58 | expect(docker_image('invalid:latest')).not_to exist 59 | end 60 | end 61 | 62 | context 'running a docker build with an alternative image' do 63 | before(:all) do 64 | @exit_status = command('cd /tmp/nginx; puppet docker build --image-name nginx-centos --from centos:6 --no-inventory').exit_status 65 | end 66 | it 'should successfully run docker build' do 67 | expect(@exit_status).to eq 0 68 | end 69 | it 'should result in a base image being pulled' do 70 | expect(docker_image('centos:6')).to exist 71 | end 72 | it 'should result in an image being created' do 73 | expect(docker_image('nginx-centos:latest')).to exist 74 | end 75 | end 76 | 77 | context 'running an aci build' do 78 | before(:all) do 79 | @exit_status = command('cd /tmp/nginx; puppet aci build --image-name nginx').exit_status 80 | end 81 | it 'should successfully run acbuild' do 82 | skip 83 | expect(@exit_status).to eq 0 84 | end 85 | it 'should generate an aci image' do 86 | skip 87 | expect(file('/tmp/nginx/nginx.aci')).to exist 88 | end 89 | end 90 | 91 | it_behaves_like 'a system running docker' 92 | it_behaves_like 'a system running rkt' 93 | end 94 | -------------------------------------------------------------------------------- /spec/unit/dockerbuilder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'puppet_x/puppetlabs/imagebuilder' 3 | 4 | describe PuppetX::Puppetlabs::DockerBuilder do 5 | let(:from) { 'debian:8' } 6 | let(:image_name) { 'puppet/sample' } 7 | let(:manifest) { Tempfile.new('manifest.pp') } 8 | let(:builder) { PuppetX::Puppetlabs::DockerBuilder.new(manifest.path, args) } 9 | let(:context) { builder.context } 10 | 11 | it_behaves_like 'an image builder' 12 | it_behaves_like 'an autosign capable builder' 13 | it_behaves_like 'a builder capable of utilising an HTTP proxy' 14 | it_behaves_like 'a builder capable of utilising an APT cache' 15 | 16 | context 'with minimal arguments' do 17 | let(:args) do 18 | { 19 | from: from, 20 | image_name: image_name 21 | } 22 | end 23 | 24 | it '#build_file should return a Dockerfile object' do 25 | expect(builder.build_file).to be_a(PuppetX::Puppetlabs::Dockerfile) 26 | end 27 | 28 | it 'should use docker build as the default build command' do 29 | expect(builder.send(:build_command)).to start_with('docker') 30 | end 31 | end 32 | 33 | context 'with an alternative build tool specified' do 34 | let(:args) do 35 | { 36 | from: from, 37 | image_name: image_name, 38 | rocker: true 39 | } 40 | end 41 | it 'should use rocker build rather than the default' do 42 | expect(builder.send(:build_command)).to start_with('rocker') 43 | end 44 | end 45 | 46 | context 'with an autosign token for use with rocker specified' do 47 | let(:token) { '12345abcde' } 48 | let(:args) do 49 | { 50 | from: from, 51 | image_name: image_name, 52 | autosign_token: token, 53 | rocker: true 54 | } 55 | end 56 | it 'should pass the token as a build argument' do 57 | expect(builder.send(:build_command)).to include("--build-arg AUTOSIGN_TOKEN=#{token}") 58 | end 59 | end 60 | 61 | context 'with an explicit network specified for docker build' do 62 | let(:network) { 'samplenetwork' } 63 | let(:args) do 64 | { 65 | from: from, 66 | image_name: image_name, 67 | network: network 68 | } 69 | end 70 | it 'should pass the network as an argument' do 71 | expect(builder.send(:build_command)).to include("--network #{network}") 72 | end 73 | end 74 | 75 | [ 76 | 'cgroup-parent', 77 | 'cpu-period', 78 | 'cpu-quota', 79 | 'cpu-shares', 80 | 'cpuset-cpus', 81 | 'cpuset-mems', 82 | 'isolation', 83 | 'memory-limit', 84 | 'memory-swap', 85 | 'shm-size', 86 | 'ulimit' 87 | ].each do |argument| 88 | context "when passing --#{argument}" do 89 | let(:value) { 'abcd' } 90 | let(:args) do 91 | Hash[ 92 | :from, from, 93 | :image_name, image_name, 94 | argument.tr('-', '_').to_sym, value, 95 | ] 96 | end 97 | it "should pass #{argument} to the underlying build tool" do 98 | expect(builder.send(:build_command)).to include("--#{argument}=#{value}") 99 | end 100 | end 101 | end 102 | 103 | [ 104 | 'disable-content-trust', 105 | 'force-rm', 106 | 'no-cache', 107 | 'pull', 108 | 'quiet' 109 | ].each do |argument| 110 | context "when passing --#{argument}" do 111 | let(:args) do 112 | Hash[ 113 | :from, from, 114 | :image_name, image_name, 115 | argument.tr('-', '_').to_sym, true, 116 | ] 117 | end 118 | it "should pass #{argument} to the underlying build tool" do 119 | expect(builder.send(:build_command)).to include("--#{argument}") 120 | end 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /spec/unit/aci_face_spec.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | 3 | require 'spec_helper' 4 | require 'puppet/face' 5 | 6 | describe Puppet::Face[:aci, '0.1.0'] do 7 | it 'has a default action of build' do 8 | expect(subject.get_action('build')).to be_default 9 | end 10 | 11 | it 'has a summary for the top level command' do 12 | expect(subject.summary).to be_a(String) 13 | end 14 | 15 | { 16 | inventory: true, 17 | show_diff: true, 18 | puppet_debug: false, 19 | hiera_config: 'hiera.yaml', 20 | hiera_data: 'hieradata', 21 | puppetfile: 'Puppetfile', 22 | config_file: 'metadata.yaml' 23 | }.each do |option, value| 24 | it "has sane defaults for #{option}" do 25 | expect(subject.get_option(option).default).to eq(value) 26 | end 27 | end 28 | 29 | %i[script build].each do |subcommand| 30 | describe "##{subcommand}" do 31 | it { is_expected.to respond_to subcommand } 32 | it { is_expected.to be_action subcommand } 33 | 34 | it 'should fail if not passed a manifest because default manifest does not exist' do 35 | expect(PuppetX::Puppetlabs::AciBuilder).to receive(:new).with('manifests/init.pp', any_args).and_call_original 36 | expect { subject.send(subcommand) }.to raise_exception(RuntimeError, %r{does not exist}) 37 | end 38 | 39 | it 'should fail if passed non existent manifest' do 40 | expect(PuppetX::Puppetlabs::AciBuilder).to receive(:new).with('not-a-real-file', any_args).and_call_original 41 | expect { subject.send(subcommand, 'not-a-real-file') }.to raise_exception(RuntimeError, %r{does not exist}) 42 | end 43 | 44 | it 'should have a default value for manifest if not passed explicitly' do 45 | image_builder = double 46 | allow(image_builder).to receive(:build) 47 | allow(image_builder).to receive_message_chain(:build_file, :render) 48 | expect(PuppetX::Puppetlabs::AciBuilder).to receive(:new).with('manifests/init.pp', any_args).and_return(image_builder) 49 | expect { subject.send(subcommand) }.not_to raise_error 50 | end 51 | end 52 | end 53 | 54 | describe '#build specific options' do 55 | let(:manifest) { Tempfile.new('manifest.pp') } 56 | it 'should catch issues with the underlying build' do 57 | expect_any_instance_of(PuppetX::Puppetlabs::AciBuilder).to receive(:build).and_raise(PuppetX::Puppetlabs::BuildError) 58 | expect { subject.build(manifest.path, image_name: 'sample') }.to raise_exception(RuntimeError, %r{the build process was interupted}) 59 | end 60 | 61 | it 'should not fail if passed a master even when default manifest does not exist' do 62 | expect(PuppetX::Puppetlabs::AciBuilder).to receive(:new).with('manifests/init.pp', any_args).and_call_original 63 | expect_any_instance_of(PuppetX::Puppetlabs::AciBuilder).to receive(:build) 64 | expect { subject.build(master: 'puppet', image_name: 'sample') }.not_to raise_error 65 | end 66 | 67 | it 'should run with the minimum options' do 68 | expect_any_instance_of(PuppetX::Puppetlabs::AciBuilder).to receive(:build) 69 | expect { subject.build(manifest.path, image_name: 'sample') }.not_to raise_error 70 | end 71 | end 72 | 73 | describe '#script specific options' do 74 | let(:manifest) { Tempfile.new('manifest.pp') } 75 | it 'should run with the minimum options' do 76 | expect_any_instance_of(PuppetX::Puppetlabs::Acifile).to receive(:render) 77 | expect { subject.script(manifest.path, image_name: 'sample') }.not_to raise_error 78 | end 79 | it 'should not fail if passed a master even when default manifest does not exist' do 80 | expect(PuppetX::Puppetlabs::AciBuilder).to receive(:new).with('manifests/init.pp', any_args).and_call_original 81 | expect_any_instance_of(PuppetX::Puppetlabs::Acifile).to receive(:render) 82 | expect { subject.script(master: 'puppet', image_name: 'sample') }.not_to raise_error 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/unit/docker_face_spec.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | 3 | require 'spec_helper' 4 | require 'puppet/face' 5 | 6 | describe Puppet::Face[:docker, '0.1.0'] do 7 | it 'has a default action of build' do 8 | expect(subject.get_action('build')).to be_default 9 | end 10 | 11 | it 'has a summary for the top level command' do 12 | expect(subject.summary).to be_a(String) 13 | end 14 | 15 | { 16 | inventory: true, 17 | show_diff: true, 18 | puppet_debug: false, 19 | rocker: false, 20 | label_schema: false, 21 | hiera_config: 'hiera.yaml', 22 | hiera_data: 'hieradata', 23 | puppetfile: 'Puppetfile', 24 | config_file: 'metadata.yaml' 25 | }.each do |option, value| 26 | it "has sane defaults for #{option}" do 27 | expect(subject.get_option(option).default).to eq(value) 28 | end 29 | end 30 | 31 | %i[dockerfile build].each do |subcommand| 32 | describe "##{subcommand}" do 33 | it { is_expected.to respond_to subcommand } 34 | it { is_expected.to be_action subcommand } 35 | 36 | it 'should fail if not passed a manifest because default manifest does not exist' do 37 | expect(PuppetX::Puppetlabs::DockerBuilder).to receive(:new).with('manifests/init.pp', any_args).and_call_original 38 | expect { subject.send(subcommand) }.to raise_exception(RuntimeError, %r{does not exist}) 39 | end 40 | 41 | it 'should fail if passed non existent manifest' do 42 | expect(PuppetX::Puppetlabs::DockerBuilder).to receive(:new).with('not-a-real-file', any_args).and_call_original 43 | expect { subject.send(subcommand, 'not-a-real-file') }.to raise_exception(RuntimeError, %r{does not exist}) 44 | end 45 | 46 | it 'should have a default value for manifest if not passed explicitly' do 47 | image_builder = double 48 | allow(image_builder).to receive(:build) 49 | allow(image_builder).to receive_message_chain(:build_file, :render) 50 | expect(PuppetX::Puppetlabs::DockerBuilder).to receive(:new).with('manifests/init.pp', any_args).and_return(image_builder) 51 | expect { subject.send(subcommand) }.not_to raise_error 52 | end 53 | end 54 | end 55 | 56 | describe '#build specific options' do 57 | let(:manifest) { Tempfile.new('manifest.pp') } 58 | it 'should catch issues with the underlying build' do 59 | expect_any_instance_of(PuppetX::Puppetlabs::DockerBuilder).to receive(:build).and_raise(PuppetX::Puppetlabs::BuildError) 60 | expect { subject.build(manifest.path, image_name: 'sample') }.to raise_exception(RuntimeError, %r{the build process was interupted}) 61 | end 62 | 63 | it 'should not fail if passed a master even when default manifest does not exist' do 64 | expect(PuppetX::Puppetlabs::DockerBuilder).to receive(:new).with('manifests/init.pp', any_args).and_call_original 65 | expect_any_instance_of(PuppetX::Puppetlabs::DockerBuilder).to receive(:build) 66 | expect { subject.build(master: 'puppet', image_name: 'sample') }.not_to raise_error 67 | end 68 | 69 | it 'should run with the minimum options' do 70 | expect_any_instance_of(PuppetX::Puppetlabs::DockerBuilder).to receive(:build) 71 | expect { subject.build(manifest.path, image_name: 'sample') }.not_to raise_error 72 | end 73 | end 74 | 75 | describe '#dockerfile specific options' do 76 | let(:manifest) { Tempfile.new('manifest.pp') } 77 | it 'should run with the minimum options' do 78 | expect_any_instance_of(PuppetX::Puppetlabs::Dockerfile).to receive(:render) 79 | expect { subject.dockerfile(manifest.path, image_name: 'sample') }.not_to raise_error 80 | end 81 | it 'should not fail if passed a master even when default manifest does not exist' do 82 | expect(PuppetX::Puppetlabs::DockerBuilder).to receive(:new).with('manifests/init.pp', any_args).and_call_original 83 | expect_any_instance_of(PuppetX::Puppetlabs::Dockerfile).to receive(:render) 84 | expect { subject.dockerfile(master: 'puppet', image_name: 'sample') }.not_to raise_error 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/imagebuilder_face.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/face' 2 | 3 | module PuppetX 4 | module Puppetlabs 5 | class ImageBuilder::Face < Puppet::Face 6 | option '--from STRING' do 7 | summary 'The base docker image to use for the resulting image' 8 | default_to { 'ubuntu:16.04' } 9 | end 10 | 11 | option '--maintainer STRING' do 12 | summary 'Name and email address for the maintainer of the resulting image' 13 | end 14 | 15 | option '--os STRING' do 16 | summary 'The operating system used by the image if not autodetected' 17 | end 18 | 19 | option '--os-version STRING' do 20 | summary 'The version of the operating system used by the image if not autodetected' 21 | end 22 | 23 | option '--puppet-agent-version STRING' do 24 | summary 'Version of the Puppet Agent package to install' 25 | default_to { '5.2.0' } 26 | end 27 | 28 | option '--r10k-version STRING' do 29 | summary 'Version of R10k to use for installing modules from Puppetfile' 30 | default_to { '2.5.5' } 31 | end 32 | 33 | option '--r10k-yaml BOOLEAN' do 34 | summary 'When yes, copies the r10k.yaml file to the container before running r10k' 35 | default_to { false } 36 | end 37 | 38 | option '--module-path PATH' do 39 | summary 'A path to a directory containing a set of modules to be copied into the image' 40 | end 41 | 42 | option '--expose STRING' do 43 | summary 'A list of ports to be exposed by the resulting image' 44 | end 45 | 46 | option '--volume STRING' do 47 | summary 'A list of volumes to be added to the resulting image' 48 | end 49 | 50 | option '--cmd STRING' do 51 | summary 'The default command to be executed by the resulting image' 52 | end 53 | 54 | option '--entrypoint STRING' do 55 | summary 'The default entrypoint for the resulting image' 56 | end 57 | 58 | option '--image-user STRING' do 59 | summary 'Specify a user to be used to run the container process' 60 | end 61 | 62 | option '--labels KEY=VALUE' do 63 | summary 'A set of labels to be applied to the resulting image' 64 | end 65 | 66 | option '--env KEY=VALUE' do 67 | summary 'A set of additional environment variables to be set in the resulting image' 68 | end 69 | 70 | option '--[no-]inventory' do 71 | summary 'Enable or disable the generation of an inventory file at /inventory.json' 72 | default_to { true } 73 | end 74 | 75 | option '--[no-]show-diff' do 76 | summary 'Enable or disable showing the diff when running Puppet to build the image' 77 | default_to { true } 78 | end 79 | 80 | option '--hiera-config STRING' do 81 | summary 'Hiera config file to use' 82 | default_to { 'hiera.yaml' } 83 | end 84 | 85 | option '--hiera-data STRING' do 86 | summary 'Hieradata directory to use' 87 | default_to { 'hieradata' } 88 | end 89 | 90 | option '--puppetfile STRING' do 91 | summary 'Enable use of Puppetfile to install dependencies during build' 92 | default_to { 'Puppetfile' } 93 | end 94 | 95 | option '--factfile STRING' do 96 | summary 'Enable use of factfile to install modules depending on facts during build' 97 | default_to { false } 98 | end 99 | 100 | option '--image-name STRING' do 101 | summary 'The name of the resulting image' 102 | end 103 | 104 | option '--config-file STRING' do 105 | summary 'A configuration file with all the metadata' 106 | default_to { 'metadata.yaml' } 107 | end 108 | 109 | option '--config-directory STRING' do 110 | summary 'A folder where metadata can be loaded from' 111 | default_to { 'metadata' } 112 | end 113 | 114 | option '--master STRING' do 115 | summary 'A Puppet Master to use for building images' 116 | end 117 | 118 | option '--label-schema' do 119 | summary 'Add label-schema compatible labels' 120 | default_to { false } 121 | end 122 | 123 | option '--skip-puppet-install' do 124 | summary 'If the base image already contains Puppet we can skip installing it' 125 | default_to { false } 126 | end 127 | 128 | option '--puppet-debug' do 129 | summary 'Pass the debug flag to the Puppet process used to build the container image' 130 | default_to { false } 131 | end 132 | 133 | option '--squash' do 134 | summary 'Automatically squash all layers in the generated image' 135 | default_to { false } 136 | end 137 | end 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /templates/build-aci.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ "$EUID" -ne 0 ]; then 5 | echo "This script uses functionality which requires root privileges" 6 | exit 1 7 | fi 8 | 9 | trap "{ export EXT=$?; acbuild --debug end && exit $EXT; }" EXIT 10 | 11 | acbuild --debug begin <% if from %>docker://<%= from %><% end %> 12 | 13 | <% if maintainer %> 14 | acbuild --debug annotation add maintainer "<%= maintainer %>" 15 | <% end %> 16 | 17 | acbuild --debug set-name <%= image_name %> 18 | 19 | acbuild --debug environment add FACTER_hostname <%= hostname %>.<%= DateTime.now.rfc3339.gsub(/[^0-9a-z]/i, '') %>.rktbuilder 20 | 21 | <% if environment %> 22 | <% environment.each do |name, value| %> 23 | export <%= name.upcase %>="<%= value %>" 24 | <% end %> 25 | <% end %> 26 | 27 | <% if labels %> 28 | <% labels.map { |pair| pair.split('=') }.each do |name, value| %> 29 | acbuild --debug label add <%= name %> "<%= value %>" 30 | <% end %> 31 | <% end %> 32 | 33 | <% unless skip_puppet_install %> 34 | <% if os %> 35 | <% if os == 'ubuntu' or os == 'debian' %> 36 | acbuild --debug run -- apt-get update 37 | acbuild --debug run -- apt-get install -y lsb-release wget 38 | acbuild --debug run -- wget <%= packagr_address %> 39 | acbuild --debug run -- dpkg -i <%= package_name %> 40 | acbuild --debug run -- rm puppetlabs-release-pc1-"$CODENAME".deb 41 | acbuild --debug run -- apt-get update 42 | acbuild --debug run -- apt-get install --no-install-recommends -y puppet-agent="$PUPPET_AGENT_VERSION"-1"$CODENAME" 43 | acbuild --debug run -- apt-get clean 44 | acbuild --debug run -- rm -rf /var/lib/apt/lists/* 45 | <% elsif os == 'centos' %> 46 | acbuild run -- rpm -Uvh <%= package_address %> 47 | acbuild run -- yum upgrade -y 48 | acbuild run -- yum update -y 49 | acbuild run -- yum install -y puppet-agent-"$PUPPET_AGENT_VERSION" 50 | acbuild run -- yum clean all 51 | <% elsif os == 'alpine' %> 52 | acbuild run -- apk add --update ca-certificates pciutils ruby ruby-irb ruby-rdoc 53 | acbuild run -- echo http://dl-4.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories 54 | acbuild run -- apk add --update shadow 55 | acbuild run -- rm -rf /var/cache/apk/* 56 | acbuild run -- gem install puppet:"$PUPPET_VERSION" facter:"$FACTER_VERSION" 57 | acbuild run -- <%= puppet_path %> module install puppetlabs-apk 58 | # Workaround for https://tickets.puppetlabs.com/browse/FACT-1351 59 | acbuild run -- rm /usr/lib/ruby/gems/2.3.0/gems/facter-"$FACTER_VERSION"/lib/facter/blockdevices.rb 60 | <% end %> 61 | <% end %> 62 | <% end %> 63 | 64 | <% if use_puppetfile && !master %> 65 | <% if os == 'ubuntu' or os == 'debian' %> 66 | acbuild run -- apt-get update 67 | acbuild run -- apt-get install -y git-core 68 | acbuild run -- <%= gem_path %> install r10k:"$R10K_VERSION" --no-ri --no-rdoc 69 | acbuild run -- apt-get clean 70 | acbuild run -- rm -rf /var/lib/apt/lists/* 71 | <% elsif os == 'centos' %> 72 | acbuild run -- yum update -y 73 | acbuild run -- yum install -y git 74 | acbuild run -- <%= gem_path %> install r10k:"$R10K_VERSION" --no-ri --no-rdoc 75 | acbuild run -- yum clean all 76 | <% elsif os == 'alpine' %> 77 | acbuild run -- apt-get update 78 | acbuild run -- apk add --update git 79 | acbuild run -- <%= gem_path %> install r10k:"$R10K_VERSION" --no-ri --no-rdoc 80 | acbuild run -- rm -rf /var/cache/apk/* 81 | <% end %> 82 | acbuild --debug copy <%= puppetfile %> /Puppetfile 83 | <% end %> 84 | 85 | <% if module_path && !master %> 86 | acbuild --debug copy <%= module_path %> /etc/puppetlabs/code/modules/ 87 | <% end %> 88 | 89 | <% if manifest && !master %> 90 | acbuild --debug copy <%= File.dirname(manifest) %> /<%= File.dirname(manifest) %> 91 | <% end %> 92 | 93 | <% if use_hiera && !master %> 94 | acbuild --debug copy <%= hiera_config %> /hiera.yaml 95 | acbuild --debug copy <%= hiera_data %> /hieradata 96 | <% end %> 97 | 98 | <% if autosign_token %>#ARG AUTOSIGN_TOKEN<% end %> 99 | 100 | <% if os %> 101 | <% if master %> 102 | <% if os == 'ubuntu' or os == 'debian' %> 103 | acbuild run -- apt-get update 104 | <% if autosign_token %>acbuild run -- printf 'custom_attributes:\n challengePassword: "%s"\n' $AUTOSIGN_TOKEN >> /etc/puppetlabs/puppet/csr_attributes.yaml<% end %> 105 | <% if master_is_ip %>acbuild run -- echo <%= master_host %> puppet >> /etc/hosts<% end %> 106 | acbuild run -- <%= puppet_path %> agent --server <% if master_is_ip %>puppet<% else %><%= master_host %><% end %><% if master_port %> --masterport <%= master_port %><% end %> --verbose <% if puppet_debug %>--debug <% end %>--onetime --no-daemonize <% if show_diff %>--show_diff <% end %>--summarize 107 | <% if autosign_token %>acbuild run -- rm /etc/puppetlabs/puppet/csr_attributes.yaml<% end %> 108 | acbuild run -- apt-get clean 109 | acbuild run -- rm -rf /var/lib/apt/lists/* 110 | <% elsif os == 'centos' %> 111 | acbuild run -- yum update -y 112 | <% if autosign_token %>acbuild run -- printf 'custom_attributes:\n challengePassword: "%s"\n' $AUTOSIGN_TOKEN >> /etc/puppetlabs/puppet/csr_attributes.yaml<% end %> 113 | <% if master_is_ip %>acbuild run -- echo <%= master_host %> puppet >> /etc/hosts<% end %> 114 | acbuild run -- <%= puppet_path %> agent --server <% if master_is_ip %>puppet<% else %><%= master_host %><% end %><% if master_port %> --masterport <%= master_port %><% end %> --verbose <% if puppet_debug %>--debug <% end %>--onetime --no-daemonize <% if show_diff %>--show_diff <% end %>--summarize 115 | <% if autosign_token %>acbuild run -- rm /etc/puppetlabs/puppet/csr_attributes.yaml<% end %> 116 | acbuild run -- yum clean all 117 | <% elsif os == 'alpine' %> 118 | acbuild run -- apk update && \ 119 | <% if autosign_token %>acbuild run -- printf 'custom_attributes:\n challengePassword: "%s"\n' $AUTOSIGN_TOKEN >> /etc/puppetlabs/puppet/csr_attributes.yaml<% end %> 120 | <% if master_is_ip %>acbuild run -- echo <%= master_host %> puppet >> /etc/hosts<% end %> 121 | acbuild run -- <%= puppet_path %> agent --server <% if master_is_ip %>puppet<% else %><%= master_host %><% end %><% if master_port %> --masterport <%= master_port %><% end %> --verbose <% if puppet_debug %>--debug <% end %>--onetime --no-daemonize <% if show_diff %>--show_diff <% end %>--summarize 122 | <% if autosign_token %>acbuild run -- rm /etc/puppetlabs/puppet/csr_attributes.yaml<% end %> 123 | acbuild run -- rm -rf /var/cache/apk/* 124 | <% end %> 125 | <% else %> 126 | <% if os == 'ubuntu' or os == 'debian' %> 127 | acbuild run -- apt-get update 128 | <% if use_puppetfile %>acbuild run -- <%= r10k_path %> puppetfile install --moduledir /etc/puppetlabs/code/modules<% end %> 129 | acbuild run -- <%= puppet_path %> apply <%= manifest %> --verbose <% if puppet_debug %>--debug <% end %><% if show_diff %>--show_diff <% end %>--summarize <% if use_hiera %>--hiera_config=/hiera.yaml<% end %> --app_management 130 | acbuild run -- apt-get clean 131 | acbuild run -- rm -rf /var/lib/apt/lists/* 132 | <% elsif os == 'centos' %> 133 | acbuild run -- yum update -y 134 | <% if use_puppetfile %>acbuild run -- <%= r10k_path %> puppetfile install --moduledir /etc/puppetlabs/code/modules<% end %> 135 | acbuild run -- <%= puppet_path %> apply <%= manifest %> --verbose <% if puppet_debug %>--debug <% end %><% if show_diff %>--show_diff <% end %>--summarize <% if use_hiera %>--hiera_config=/hiera.yaml<% end %> --app_management 136 | acbuild run -- yum clean all 137 | <% elsif os == 'alpine' %> 138 | acbuild run -- apk update 139 | <% if use_puppetfile %>acbuild run -- <%= r10k_path %> puppetfile install --moduledir /etc/puppetlabs/code/modules<% end %> 140 | acbuild run -- <%= puppet_path %> apply <%= manifest %> --verbose <% if puppet_debug %>--debug <% end %><% if show_diff %>--show_diff <% end %>--summarize <% if use_hiera %>--hiera_config=/hiera.yaml<% end %> --app_management 141 | acbuild run -- rm -rf /var/cache/apk/* 142 | <% end %> 143 | <% end %> 144 | <% end %> 145 | 146 | <% if inventory %> 147 | acbuild --debug annotation add com.puppet.inventory "/inventory.json" 148 | acbuild --debug run -- <%= puppet_path %> module install puppetlabs-inventory 149 | acbuild --debug run -- <%= puppet_path %> inventory all > /inventory.json 150 | <% end %> 151 | 152 | <% if expose %> 153 | <% expose.each do |port| %> 154 | acbuild --debug port add http tcp <%= port %> 155 | <% end %> 156 | <% end %> 157 | 158 | <% if cmd %>acbuild --debug set-exec -- <%= entrypoint.join(' ') %> <%= cmd.join(' ') %><% end %> 159 | 160 | acbuild --debug write --overwrite <%= image_name %>.aci 161 | -------------------------------------------------------------------------------- /templates/Dockerfile.erb: -------------------------------------------------------------------------------- 1 | <% if from %>FROM <%= from %><% end %> 2 | <% if maintainer %>MAINTAINER <%= maintainer %><% end %> 3 | 4 | <% if image_user && !image_user.empty? %>USER root<% end %> 5 | 6 | <% if environment %>ENV <% environment.each do |name, value| %><%= name.upcase %>="<%= value %>" <% end %><% end %> 7 | 8 | <% if rocker %>MOUNT /opt/puppetlabs /etc/puppetlabs /root/.gem<% end %> 9 | 10 | <% if labels && !labels.empty? %>LABEL <%= labels.map { |g| g.split('=') }.map { |k,v| "#{k}=\"#{v}\"" }.join(' ') %><% end %> 11 | 12 | <% if apt_proxy %>ARG APT_PROXY<% end %> 13 | 14 | <% unless skip_puppet_install %> 15 | <% if os %> 16 | <% if os == 'ubuntu' or os == 'debian' %> 17 | RUN <% if apt_proxy %>echo "Acquire::HTTP::Proxy \"$APT_PROXY\";" >> /etc/apt/apt.conf.d/01proxy && echo 'Acquire::HTTPS::Proxy "false";' >> /etc/apt/apt.conf.d/01proxy && <% end %>apt-get update && \ 18 | apt-get install --no-install-recommends -y lsb-release wget ca-certificates && \ 19 | wget <%= package_address %> && \ 20 | dpkg -i <%= package_name %> && \ 21 | rm <%= package_name %> && \ 22 | apt-get update && \ 23 | apt-get install --no-install-recommends -y puppet-agent="$PUPPET_AGENT_VERSION"-1"$CODENAME" && \ 24 | apt-get remove --purge -y wget && \ 25 | apt-get autoremove -y && \ 26 | apt-get clean && \ 27 | mkdir -p /etc/puppetlabs/facter/facts.d/ && \ 28 | rm -rf /var/lib/apt/lists/* <% if apt_proxy %>&& rm -f /etc/apt/apt.conf.d/01proxy<% end %> 29 | <% elsif os == 'centos' %> 30 | RUN rpm -Uvh <%= package_address %> && \ 31 | yum upgrade -y && \ 32 | yum update -y && \ 33 | yum install -y puppet-agent-"$PUPPET_AGENT_VERSION" && \ 34 | mkdir -p /etc/puppetlabs/facter/facts.d/ && \ 35 | yum clean all 36 | <% elsif os == 'alpine' %> 37 | RUN apk add --update \ 38 | ca-certificates \ 39 | pciutils \ 40 | ruby \ 41 | ruby-irb \ 42 | ruby-rdoc \ 43 | && \ 44 | echo http://dl-4.alpinelinux.org/alpine/edge/testing/ >> /etc/apk/repositories && \ 45 | apk add --update shadow && \ 46 | rm -rf /var/cache/apk/* && \ 47 | gem install puppet:"$PUPPET_VERSION" facter:"$FACTER_VERSION" && \ 48 | <%= puppet_path %> module install puppetlabs-apk 49 | 50 | # Workaround for https://tickets.puppetlabs.com/browse/FACT-1351 51 | RUN rm /usr/lib/ruby/gems/2.3.0/gems/facter-"$FACTER_VERSION"/lib/facter/blockdevices.rb 52 | <% end %> 53 | <% end %> 54 | <% end %> 55 | 56 | <% if use_factfile %> 57 | COPY <%= factfile %> /etc/puppetlabs/facter/facts.d/custom_facts.txt 58 | <% end %> 59 | 60 | <% if use_puppetfile && !master %> 61 | <% if os == 'ubuntu' or os == 'debian' %> 62 | RUN <% if apt_proxy %>echo "Acquire::HTTP::Proxy \"$APT_PROXY\";" >> /etc/apt/apt.conf.d/01proxy && echo 'Acquire::HTTPS::Proxy "false";' >> /etc/apt/apt.conf.d/01proxy && <% end %>apt-get update && \ 63 | apt-get install --no-install-recommends -y git-core && \ 64 | <%= gem_path %> install r10k:"$R10K_VERSION" --no-ri --no-rdoc && \ 65 | apt-get autoremove -y && \ 66 | apt-get clean && \ 67 | rm -rf /var/lib/apt/lists/* <% if apt_proxy %>&& rm -f /etc/apt/apt.conf.d/01proxy<% end %> 68 | <% elsif os == 'centos' %> 69 | RUN yum update -y && \ 70 | yum install -y git && \ 71 | <%= gem_path %> install r10k:"$R10K_VERSION" --no-ri --no-rdoc && \ 72 | yum clean all 73 | <% elsif os == 'alpine' %> 74 | RUN apk add --update git && \ 75 | <%= gem_path %> install r10k:"$R10K_VERSION" --no-ri --no-rdoc && \ 76 | rm -rf /var/cache/apk/* 77 | <% end %> 78 | <% if r10k_yaml %> 79 | COPY r10k.yaml /r10k.yaml 80 | <% end %> 81 | COPY <%= puppetfile %> /Puppetfile 82 | RUN <%= r10k_path %> puppetfile install --moduledir /etc/puppetlabs/code/modules 83 | <% end %> 84 | 85 | <% if module_path && !master %> 86 | COPY <%= module_path %> /etc/puppetlabs/code/modules/ 87 | <% end %> 88 | 89 | <% if manifest && !master %> 90 | COPY <%= File.dirname(manifest) %> /<%= File.dirname(manifest) %> 91 | <% end %> 92 | 93 | <% if use_hiera && !master %> 94 | COPY <%= hiera_config %> /hiera.yaml 95 | COPY <%= hiera_data %> /hieradata 96 | <% end %> 97 | 98 | <% if autosign_token %>ARG AUTOSIGN_TOKEN<% end %> 99 | 100 | <% if os %> 101 | <% if master %> 102 | <% if os == 'ubuntu' or os == 'debian' %> 103 | RUN <% if apt_proxy %>echo "Acquire::HTTP::Proxy \"$APT_PROXY\";" >> /etc/apt/apt.conf.d/01proxy && echo 'Acquire::HTTPS::Proxy "false";' >> /etc/apt/apt.conf.d/01proxy && <% end %>apt-get update && \ 104 | <% if autosign_token %>printf 'custom_attributes:\n challengePassword: "%s"\n' $AUTOSIGN_TOKEN >> /etc/puppetlabs/puppet/csr_attributes.yaml && <% end %><% if master_is_ip %>echo <%= master_host %> puppet >> /etc/hosts && <% end %>FACTER_hostname=<%= hostname %>.<%= DateTime.now.rfc3339.gsub(/[^0-9a-z]/i, '') %>.dockerbuilder <%= puppet_path %> agent --server <% if master_is_ip %>puppet<% else %><%= master_host %><% end %><% if master_port %> --masterport <%= master_port %><% end %> --verbose <% if puppet_debug %>--debug <% end %>--onetime --no-daemonize <% if show_diff %>--show_diff <% end %>--summarize<% if autosign_token %> && rm /etc/puppetlabs/puppet/csr_attributes.yaml<% end %> && \ 105 | apt-get autoremove -y && \ 106 | apt-get clean && \ 107 | rm -rf /var/lib/apt/lists/* <% if apt_proxy %>&& rm -f /etc/apt/apt.conf.d/01proxy<% end %> 108 | <% elsif os == 'centos' %> 109 | RUN yum update -y && \ 110 | <% if autosign_token %>printf 'custom_attributes:\n challengePassword: "%s"\n' $AUTOSIGN_TOKEN >> /etc/puppetlabs/puppet/csr_attributes.yaml && <% end %><% if master_is_ip %>echo <%= master_host %> puppet >> /etc/hosts && <% end %>FACTER_hostname=<%= hostname %>.<%= DateTime.now.rfc3339.gsub(/[^0-9a-z]/i, '') %>.dockerbuilder <%= puppet_path %> agent --server <% if master_is_ip %>puppet<% else %><%= master_host %><% end %><% if master_port %> --masterport <%= master_port %><% end %> --verbose <% if puppet_debug %>--debug <% end %>--onetime --no-daemonize <% if show_diff %>--show_diff <% end %>--summarize<% if autosign_token %> && rm /etc/puppetlabs/puppet/csr_attributes.yaml<% end %> && \ 111 | yum clean all 112 | <% elsif os == 'alpine' %> 113 | RUN apk update && \ 114 | <% if autosign_token %>printf 'custom_attributes:\n challengePassword: "%s"\n' $AUTOSIGN_TOKEN >> /etc/puppetlabs/puppet/csr_attributes.yaml && <% end %><% if master_is_ip %>echo <%= master_host %> puppet >> /etc/hosts && <% end %>FACTER_hostname=<%= hostname %>.<%= DateTime.now.rfc3339.gsub(/[^0-9a-z]/i, '') %>.dockerbuilder <%= puppet_path %> agent --server <% if master_is_ip %>puppet<% else %><%= master_host %><% end %><% if master_port %> --masterport <%= master_port %><% end %> --verbose <% if puppet_debug %>--debug <% end %>--onetime --no-daemonize <% if show_diff %>--show_diff <% end %>--summarize<% if autosign_token %> && rm /etc/puppetlabs/puppet/csr_attributes.yaml<% end %> && \ 115 | rm -rf /var/cache/apk/* 116 | <% end %> 117 | <% else %> 118 | <% if os == 'ubuntu' or os == 'debian' %> 119 | RUN <% if apt_proxy %>echo "Acquire::HTTP::Proxy \"$APT_PROXY\";" >> /etc/apt/apt.conf.d/01proxy && echo 'Acquire::HTTPS::Proxy "false";' >> /etc/apt/apt.conf.d/01proxy && <% end %>apt-get update && \ 120 | FACTER_hostname=<%= hostname %> <%= puppet_path %> apply <%= manifest %> --detailed-exitcodes --verbose <% if puppet_debug %>--debug <% end %><% if show_diff %>--show_diff <% end %>--summarize <% if use_hiera %>--hiera_config=/hiera.yaml<% end %> --app_management ; \ 121 | rc=$?; if [ $rc -ne 0 ] && [ $rc -ne 2 ]; then exit 1; fi && \ 122 | apt-get autoremove -y && \ 123 | apt-get clean && \ 124 | rm -rf /var/lib/apt/lists/* <% if apt_proxy %>&& rm -f /etc/apt/apt.conf.d/01proxy<% end %> 125 | <% elsif os == 'centos' %> 126 | RUN yum update -y && \ 127 | FACTER_hostname=<%= hostname %> <%= puppet_path %> apply <%= manifest %> --verbose <% if puppet_debug %>--debug <% end %><% if show_diff %>--show_diff <% end %>--summarize <% if use_hiera %>--hiera_config=/hiera.yaml<% end %> --app_management && \ 128 | yum clean all 129 | <% elsif os == 'alpine' %> 130 | RUN apk update && \ 131 | FACTER_hostname=<%= hostname %> <%= puppet_path %> apply <%= manifest %> --verbose <% if puppet_debug %>--debug <% end %><% if show_diff %>--show_diff <% end %>--summarize <% if use_hiera %>--hiera_config=/hiera.yaml<% end %> --app_management && \ 132 | rm -rf /var/cache/apk/* 133 | <% end %> 134 | <% end %> 135 | <% end %> 136 | 137 | <% if inventory %> 138 | LABEL com.puppet.inventory="/inventory.json" 139 | RUN <%= puppet_path %> module install puppetlabs-inventory && \ 140 | <%= puppet_path %> inventory all > /inventory.json 141 | <% end %> 142 | 143 | <% if expose && !expose.empty? %>EXPOSE <%= expose.join(' ') %><% end %> 144 | <% if volume && !volume.empty? %>VOLUME <%= volume.join(' ') %><% end %> 145 | 146 | <% if entrypoint && !entrypoint.empty? %>ENTRYPOINT <%= entrypoint.to_s %><% end %> 147 | <% if cmd && !cmd.empty? %>CMD <%= cmd.to_s %><% end %> 148 | 149 | <% if image_user && !image_user.empty? %>USER <%= image_user.to_s %><% end %> 150 | 151 | <% if rocker %>TAG <%= image_name %><% end %> 152 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | #require: rubocop-rspec 2 | AllCops: 3 | #TargetRubyVersion: 1.9 4 | Include: 5 | - ./**/*.rb 6 | Exclude: 7 | - vendor/**/* 8 | - .vendor/**/* 9 | - pkg/**/* 10 | - spec/fixtures/**/* 11 | - examples/master/**/* 12 | Lint/ConditionPosition: 13 | Enabled: True 14 | 15 | Lint/ElseLayout: 16 | Enabled: True 17 | 18 | Lint/UnreachableCode: 19 | Enabled: True 20 | 21 | Lint/UselessComparison: 22 | Enabled: True 23 | 24 | Lint/EnsureReturn: 25 | Enabled: True 26 | 27 | Lint/HandleExceptions: 28 | Enabled: True 29 | 30 | Lint/LiteralInCondition: 31 | Enabled: True 32 | 33 | Lint/ShadowingOuterLocalVariable: 34 | Enabled: True 35 | 36 | Lint/LiteralInInterpolation: 37 | Enabled: True 38 | 39 | Style/HashSyntax: 40 | Enabled: True 41 | 42 | Style/RedundantReturn: 43 | Enabled: True 44 | 45 | Lint/AmbiguousOperator: 46 | Enabled: True 47 | 48 | Lint/AssignmentInCondition: 49 | Enabled: True 50 | 51 | Layout/IndentHeredoc: 52 | Enabled: false 53 | 54 | Naming/HeredocDelimiterNaming: 55 | Enabled: false 56 | 57 | Layout/SpaceBeforeComment: 58 | Enabled: True 59 | 60 | Style/AndOr: 61 | Enabled: True 62 | 63 | Style/RedundantSelf: 64 | Enabled: True 65 | 66 | Metrics/BlockLength: 67 | Enabled: False 68 | 69 | # Method length is not necessarily an indicator of code quality 70 | Metrics/MethodLength: 71 | Enabled: False 72 | 73 | # Module length is not necessarily an indicator of code quality 74 | Metrics/ModuleLength: 75 | Enabled: False 76 | 77 | Style/WhileUntilModifier: 78 | Enabled: True 79 | 80 | Lint/AmbiguousRegexpLiteral: 81 | Enabled: True 82 | 83 | Security/Eval: 84 | Enabled: True 85 | 86 | Lint/BlockAlignment: 87 | Enabled: True 88 | 89 | Lint/DefEndAlignment: 90 | Enabled: True 91 | 92 | Lint/EndAlignment: 93 | Enabled: True 94 | 95 | Lint/DeprecatedClassMethods: 96 | Enabled: True 97 | 98 | Lint/Loop: 99 | Enabled: True 100 | 101 | Lint/ParenthesesAsGroupedExpression: 102 | Enabled: True 103 | 104 | Lint/RescueException: 105 | Enabled: True 106 | 107 | Lint/StringConversionInInterpolation: 108 | Enabled: True 109 | 110 | Lint/UnusedBlockArgument: 111 | Enabled: True 112 | 113 | Lint/UnusedMethodArgument: 114 | Enabled: True 115 | 116 | Lint/UselessAccessModifier: 117 | Enabled: True 118 | 119 | Lint/UselessAssignment: 120 | Enabled: True 121 | 122 | Lint/Void: 123 | Enabled: True 124 | 125 | Layout/AccessModifierIndentation: 126 | Enabled: True 127 | 128 | Naming/AccessorMethodName: 129 | Enabled: True 130 | 131 | Style/Alias: 132 | Enabled: True 133 | 134 | Layout/AlignArray: 135 | Enabled: True 136 | 137 | Layout/AlignHash: 138 | Enabled: True 139 | 140 | Layout/AlignParameters: 141 | Enabled: True 142 | 143 | Metrics/BlockNesting: 144 | Enabled: True 145 | 146 | Style/AsciiComments: 147 | Enabled: True 148 | 149 | Style/Attr: 150 | Enabled: True 151 | 152 | Style/BracesAroundHashParameters: 153 | Enabled: True 154 | 155 | Style/CaseEquality: 156 | Enabled: True 157 | 158 | Layout/CaseIndentation: 159 | Enabled: True 160 | 161 | Style/CharacterLiteral: 162 | Enabled: True 163 | 164 | Naming/ClassAndModuleCamelCase: 165 | Enabled: True 166 | 167 | Style/ClassAndModuleChildren: 168 | Enabled: False 169 | 170 | Style/ClassCheck: 171 | Enabled: True 172 | 173 | # Class length is not necessarily an indicator of code quality 174 | Metrics/ClassLength: 175 | Enabled: False 176 | 177 | Style/ClassMethods: 178 | Enabled: True 179 | 180 | Style/ClassVars: 181 | Enabled: True 182 | 183 | Style/WhenThen: 184 | Enabled: True 185 | 186 | Style/WordArray: 187 | Enabled: True 188 | 189 | Style/UnneededPercentQ: 190 | Enabled: True 191 | 192 | Layout/Tab: 193 | Enabled: True 194 | 195 | Layout/SpaceBeforeSemicolon: 196 | Enabled: True 197 | 198 | Layout/TrailingBlankLines: 199 | Enabled: True 200 | 201 | Layout/SpaceInsideBlockBraces: 202 | Enabled: True 203 | 204 | Layout/SpaceInsideBrackets: 205 | Enabled: True 206 | 207 | Layout/SpaceInsideHashLiteralBraces: 208 | Enabled: True 209 | 210 | Layout/SpaceInsideParens: 211 | Enabled: True 212 | 213 | Layout/LeadingCommentSpace: 214 | Enabled: True 215 | 216 | Layout/SpaceBeforeFirstArg: 217 | Enabled: True 218 | 219 | Layout/SpaceAfterColon: 220 | Enabled: True 221 | 222 | Layout/SpaceAfterComma: 223 | Enabled: True 224 | 225 | Layout/SpaceAfterMethodName: 226 | Enabled: True 227 | 228 | Layout/SpaceAfterNot: 229 | Enabled: True 230 | 231 | Layout/SpaceAfterSemicolon: 232 | Enabled: True 233 | 234 | Layout/SpaceAroundEqualsInParameterDefault: 235 | Enabled: True 236 | 237 | Layout/SpaceAroundOperators: 238 | Enabled: True 239 | 240 | Layout/SpaceBeforeBlockBraces: 241 | Enabled: True 242 | 243 | Layout/SpaceBeforeComma: 244 | Enabled: True 245 | 246 | Style/CollectionMethods: 247 | Enabled: True 248 | 249 | Layout/CommentIndentation: 250 | Enabled: True 251 | 252 | Style/ColonMethodCall: 253 | Enabled: True 254 | 255 | Style/CommentAnnotation: 256 | Enabled: True 257 | 258 | # 'Complexity' is very relative 259 | Metrics/CyclomaticComplexity: 260 | Enabled: False 261 | 262 | Naming/ConstantName: 263 | Enabled: True 264 | 265 | Style/Documentation: 266 | Enabled: False 267 | 268 | Style/DefWithParentheses: 269 | Enabled: True 270 | 271 | Style/PreferredHashMethods: 272 | Enabled: True 273 | 274 | Layout/DotPosition: 275 | EnforcedStyle: trailing 276 | 277 | Style/DoubleNegation: 278 | Enabled: True 279 | 280 | Style/EachWithObject: 281 | Enabled: True 282 | 283 | Layout/EmptyLineBetweenDefs: 284 | Enabled: True 285 | 286 | Layout/IndentArray: 287 | Enabled: True 288 | 289 | Layout/IndentHash: 290 | Enabled: True 291 | 292 | Layout/IndentationConsistency: 293 | Enabled: True 294 | 295 | Layout/IndentationWidth: 296 | Enabled: True 297 | 298 | Layout/EmptyLines: 299 | Enabled: True 300 | 301 | Layout/EmptyLinesAroundAccessModifier: 302 | Enabled: True 303 | 304 | Style/EmptyLiteral: 305 | Enabled: True 306 | 307 | # Configuration parameters: AllowURI, URISchemes. 308 | Metrics/LineLength: 309 | Enabled: False 310 | 311 | Style/MethodCallWithoutArgsParentheses: 312 | Enabled: True 313 | 314 | Style/MethodDefParentheses: 315 | Enabled: True 316 | 317 | Style/LineEndConcatenation: 318 | Enabled: True 319 | 320 | Layout/TrailingWhitespace: 321 | Enabled: True 322 | 323 | Style/StringLiterals: 324 | Enabled: True 325 | 326 | Style/TrailingCommaInArguments: 327 | Enabled: True 328 | 329 | Style/TrailingCommaInLiteral: 330 | Enabled: True 331 | 332 | Style/GlobalVars: 333 | Enabled: True 334 | 335 | Style/GuardClause: 336 | Enabled: False 337 | 338 | Style/IfUnlessModifier: 339 | Enabled: True 340 | 341 | Style/MultilineIfThen: 342 | Enabled: True 343 | 344 | Style/NegatedIf: 345 | Enabled: True 346 | 347 | Style/NegatedWhile: 348 | Enabled: True 349 | 350 | Style/Next: 351 | Enabled: True 352 | 353 | Style/SingleLineBlockParams: 354 | Enabled: True 355 | 356 | Style/SingleLineMethods: 357 | Enabled: True 358 | 359 | Style/SpecialGlobalVars: 360 | Enabled: True 361 | 362 | Style/TrivialAccessors: 363 | Enabled: True 364 | 365 | Style/UnlessElse: 366 | Enabled: True 367 | 368 | Style/VariableInterpolation: 369 | Enabled: True 370 | 371 | Naming/VariableName: 372 | Enabled: True 373 | 374 | Style/WhileUntilDo: 375 | Enabled: True 376 | 377 | Style/EvenOdd: 378 | Enabled: True 379 | 380 | Naming/FileName: 381 | Enabled: False 382 | 383 | Style/For: 384 | Enabled: True 385 | 386 | Style/Lambda: 387 | Enabled: True 388 | 389 | Naming/MethodName: 390 | Enabled: True 391 | 392 | Style/MultilineTernaryOperator: 393 | Enabled: True 394 | 395 | Style/NestedTernaryOperator: 396 | Enabled: True 397 | 398 | Style/NilComparison: 399 | Enabled: True 400 | 401 | Style/FormatString: 402 | Enabled: True 403 | 404 | Style/MultilineBlockChain: 405 | Enabled: True 406 | 407 | Style/Semicolon: 408 | Enabled: True 409 | 410 | Style/SignalException: 411 | Enabled: True 412 | 413 | Style/NonNilCheck: 414 | Enabled: True 415 | 416 | Style/Not: 417 | Enabled: True 418 | 419 | Style/NumericLiterals: 420 | Enabled: True 421 | 422 | Style/OneLineConditional: 423 | Enabled: True 424 | 425 | Naming/BinaryOperatorParameterName: 426 | Enabled: True 427 | 428 | Style/ParenthesesAroundCondition: 429 | Enabled: True 430 | 431 | Style/PercentLiteralDelimiters: 432 | Enabled: True 433 | 434 | Style/PerlBackrefs: 435 | Enabled: True 436 | 437 | Naming/PredicateName: 438 | Enabled: True 439 | 440 | Style/RedundantException: 441 | Enabled: True 442 | 443 | Style/SelfAssignment: 444 | Enabled: True 445 | 446 | Style/Proc: 447 | Enabled: True 448 | 449 | Style/RaiseArgs: 450 | Enabled: True 451 | 452 | Style/RedundantBegin: 453 | Enabled: True 454 | 455 | Style/RescueModifier: 456 | Enabled: True 457 | 458 | # based on https://github.com/voxpupuli/modulesync_config/issues/168 459 | Style/RegexpLiteral: 460 | EnforcedStyle: percent_r 461 | Enabled: True 462 | 463 | Lint/UnderscorePrefixedVariableName: 464 | Enabled: True 465 | 466 | Metrics/ParameterLists: 467 | Enabled: False 468 | 469 | Lint/RequireParentheses: 470 | Enabled: True 471 | 472 | Layout/SpaceBeforeFirstArg: 473 | Enabled: True 474 | 475 | Style/ModuleFunction: 476 | Enabled: True 477 | 478 | Lint/Debugger: 479 | Enabled: True 480 | 481 | Style/IfWithSemicolon: 482 | Enabled: True 483 | 484 | Style/Encoding: 485 | Enabled: True 486 | 487 | Style/BlockDelimiters: 488 | Enabled: True 489 | 490 | Layout/MultilineBlockLayout: 491 | Enabled: True 492 | 493 | # 'Complexity' is very relative 494 | Metrics/AbcSize: 495 | Enabled: False 496 | 497 | # 'Complexity' is very relative 498 | Metrics/PerceivedComplexity: 499 | Enabled: False 500 | 501 | Lint/UselessAssignment: 502 | Enabled: True 503 | 504 | Layout/ClosingParenthesisIndentation: 505 | Enabled: False 506 | 507 | # RSpec 508 | 509 | # We don't use rspec in this way 510 | #RSpec/DescribeClass: 511 | # Enabled: False 512 | 513 | # Example length is not necessarily an indicator of code quality 514 | #RSpec/ExampleLength: 515 | # Enabled: False 516 | 517 | #RSpec/NamedSubject: 518 | # Enabled: False 519 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/imagebuilder.rb: -------------------------------------------------------------------------------- 1 | require 'pty' 2 | require 'yaml' 3 | require 'resolv' 4 | require 'date' 5 | require 'English' 6 | 7 | require 'puppet_x/puppetlabs/deep_symbolize_keys' 8 | require 'puppet_x/puppetlabs/dockerfile' 9 | require 'puppet_x/puppetlabs/acifile' 10 | 11 | module PuppetX 12 | module Puppetlabs 13 | class BuildError < RuntimeError 14 | end 15 | 16 | class InvalidContextError < RuntimeError 17 | end 18 | 19 | class ImageBuilder 20 | attr_accessor :context 21 | 22 | def initialize(manifest, args) 23 | @context = args 24 | load_from_config_file 25 | add_manifest_to_context(manifest) 26 | labels_to_array 27 | add_label_schema_labels 28 | env_to_array 29 | cmd_to_array 30 | expose_to_array 31 | volume_to_array 32 | entrypoint_to_array 33 | determine_os 34 | determine_paths 35 | determine_if_using_puppetfile 36 | determine_if_using_factfile 37 | determine_if_using_hiera 38 | determine_environment_vars 39 | determine_repository_details 40 | determine_hostname 41 | determine_master_host_and_port 42 | determine_if_master_is_ip 43 | validate_context 44 | end 45 | 46 | def build 47 | run(build_command) 48 | end 49 | 50 | private 51 | 52 | def pty(cmd, &block) 53 | PTY.spawn(cmd, &block) 54 | $CHILD_STATUS 55 | end 56 | 57 | def run(command) 58 | status = pty(command) do |stdout, _stdin, pid| 59 | begin 60 | stdout.each { |line| print line } 61 | rescue Errno::EIO # rubocop:disable Lint/HandleExceptions 62 | end 63 | Process.wait(pid) 64 | end 65 | unless status.nil? || status.success? 66 | raise BuildError, 'The docker build process exited with a non-zero status code' 67 | end 68 | rescue PTY::ChildExited => e 69 | raise BuildError, e.message 70 | end 71 | 72 | def validate_context 73 | unless @context[:master] 74 | raise InvalidContextError, "specified file #{@context[:manifest]} does not exist" unless File.exist?(@context[:manifest]) 75 | end 76 | raise InvalidContextError, 'You must provide an image name, either on the command line or in the metadata file' unless @context[:image_name] 77 | end 78 | 79 | def add_manifest_to_context(manifest) 80 | @context[:manifest] = manifest 81 | end 82 | 83 | def find_metadata_file(file) 84 | if file.nil? 85 | false 86 | elsif File.file?(file) 87 | file 88 | elsif @context[:config_directory] && File.file?("#{@context[:config_directory]}/#{file}") 89 | "#{@context[:config_directory]}/#{file}" 90 | else 91 | false 92 | end 93 | end 94 | 95 | def determine_master_host_and_port 96 | if @context[:master] 97 | parts = @context[:master].split(':') 98 | if parts.length == 2 99 | @context[:master_port] = parts[1] 100 | @context[:master_host] = parts[0] 101 | else 102 | @context[:master_host] = @context[:master] 103 | end 104 | end 105 | end 106 | 107 | def determine_if_master_is_ip 108 | @context[:master_is_ip] = @context[:master_host] =~ Resolv::IPv4::Regex ? true : false 109 | end 110 | 111 | def host_config 112 | hostname = @context[:image_name].to_s.split('/').pop 113 | host_config = find_metadata_file("#{hostname}.yaml") 114 | host_metadata = {} 115 | if @context[:image_name] && host_config 116 | begin 117 | host_metadata = YAML.load_file(host_config).deep_symbolize_keys 118 | rescue Psych::SyntaxError 119 | raise InvalidContextError, "the metadata file #{host_config} does not appear to be valid YAML" 120 | end 121 | end 122 | host_metadata 123 | end 124 | 125 | def load_from_config_file 126 | default_config = find_metadata_file(@context[:config_file]) 127 | if @context[:config_file] && default_config 128 | begin 129 | metadata = YAML.load_file(default_config).deep_symbolize_keys 130 | rescue Psych::SyntaxError 131 | raise InvalidContextError, "the metadata file #{default_config} does not appear to be valid YAML" 132 | end 133 | @context = @context.merge(metadata).merge(host_config) if metadata.is_a?(Hash) 134 | end 135 | end 136 | 137 | def labels_to_array 138 | value_to_array(:labels) 139 | end 140 | 141 | def add_label_schema_labels 142 | if @context[:label_schema] 143 | @context[:labels].insert( 144 | -1, 145 | "org.label-schema.build-date=#{Time.now.utc.iso8601}", 146 | 'org.label-schema.schema-version=1.0' 147 | ) 148 | end 149 | end 150 | 151 | def env_to_array 152 | value_to_array(:env) 153 | end 154 | 155 | def expose_to_array 156 | value_to_array(:expose) 157 | end 158 | 159 | def volume_to_array 160 | value_to_array(:volume) 161 | end 162 | 163 | def cmd_to_array 164 | value_to_array(:cmd) 165 | end 166 | 167 | def entrypoint_to_array 168 | value_to_array(:entrypoint) 169 | end 170 | 171 | def value_to_array(value) 172 | @context[value] = @context[value].to_s.split(',') if @context[value].is_a?(String) || @context[value].is_a?(Integer) || @context[value].nil? 173 | end 174 | 175 | def determine_if_using_puppetfile 176 | @context[:use_puppetfile] = true if exists_and_is_file(:puppetfile) 177 | end 178 | 179 | def determine_if_using_factfile 180 | @context[:use_factfile] = true if exists_and_is_file(:factfile) 181 | end 182 | 183 | def determine_if_using_hiera 184 | if exists_and_is_file(:hiera_config) && exists_and_is_directory(:hiera_data) 185 | @context[:use_hiera] = true 186 | end 187 | end 188 | 189 | def determine_os 190 | if @context[:from] 191 | @context[:os], @context[:os_version] = @context[:from].split(':') unless @context[:os] 192 | end 193 | end 194 | 195 | def determine_paths 196 | @context[:puppet_path], 197 | @context[:gem_path], 198 | @context[:r10k_path] = case @context[:os] 199 | when 'alpine' 200 | ['/usr/bin/puppet', 'gem', 'r10k'] 201 | else 202 | ['/opt/puppetlabs/bin/puppet', '/opt/puppetlabs/puppet/bin/gem', '/opt/puppetlabs/puppet/bin/r10k'] 203 | end 204 | end 205 | 206 | def determine_environment_vars 207 | codename = nil 208 | puppet_version = nil 209 | facter_version = nil 210 | case @context[:os] 211 | when 'ubuntu' 212 | codename = case @context[:os_version] 213 | when 'latest', 'xenial', nil, %r{^16\.04} 214 | 'xenial' 215 | when 'trusty', %r{^14\.04} 216 | 'trusty' 217 | when 'precise', %r{^12\.04} 218 | 'precise' 219 | end 220 | when 'debian' 221 | codename = case @context[:os_version] 222 | when 'latest', 'stable', 'stable-slim', 'stable-backports', 'stretch', 'stretch-slim', 'stretch-backports', %r{^9} 223 | 'stretch' 224 | when 'oldstable', 'oldstable-slim', 'oldstable-backports', 'jessie', 'jessie-slim', 'jessie-backports', %r{^8} 225 | 'jessie' 226 | when 'sid', 'sid-slim' 227 | 'sid' 228 | when 'wheezy', %r{^7} 229 | 'wheezy' 230 | end 231 | when 'alpine' 232 | facter_version = '2.4.6' # latest version available as a gem 233 | puppet_version = case @context[:puppet_agent_version] 234 | when '1.8.2' 235 | '4.8.1' 236 | when '1.8.1' 237 | '4.8.1' 238 | when '1.8.0' 239 | '4.8.0' 240 | when '1.7.1' 241 | '4.7.0' 242 | when '1.7.0' 243 | '4.7.0' 244 | when '1.6.2' 245 | '4.6.2' 246 | when '1.6.1' 247 | '4.6.1' 248 | when '1.6.0' 249 | '4.6.0' 250 | when '1.5.3' 251 | '4.5.3' 252 | when '1.5.2' 253 | '4.5.2' 254 | when '1.5.1' 255 | '4.5.1' 256 | when '1.5.0' 257 | '4.5.0' 258 | when '1.4.2' 259 | '4.4.2' 260 | when '1.4.1' 261 | '4.4.1' 262 | when '1.4.0' 263 | '4.4.0' 264 | else 265 | '4.5.2' 266 | end 267 | when 'centos' # rubocop:disable Lint/EmptyWhen 268 | else 269 | raise InvalidContextError, 'puppet docker currently only supports Ubuntu, Debian, Alpine and Centos base images' 270 | end 271 | @context[:environment] = { 272 | puppet_agent_version: @context[:puppet_agent_version], 273 | r10k_version: @context[:r10k_version], 274 | codename: codename, 275 | puppet_version: puppet_version, 276 | facter_version: facter_version 277 | }.reject { |_name, value| value.nil? } 278 | unless @context[:env].nil? 279 | @context[:env].map { |pair| pair.split('=') }.each do |name, value| 280 | @context[:environment][name] = value 281 | end 282 | end 283 | end 284 | 285 | def determine_repository_details 286 | puppet5 = @context[:puppet_agent_version].to_f >= 5 287 | @context[:package_address], @context[:package_name] = case @context[:os] 288 | when 'ubuntu', 'debian' 289 | if puppet5 290 | [ 291 | 'https://apt.puppetlabs.com/puppet5-release-"$CODENAME".deb', 292 | 'puppet5-release-"$CODENAME".deb' 293 | ] 294 | else 295 | [ 296 | 'https://apt.puppetlabs.com/puppetlabs-release-pc1-"$CODENAME".deb', 297 | 'puppetlabs-release-pc1-"$CODENAME".deb' 298 | ] 299 | end 300 | when 'centos' 301 | if puppet5 302 | "https://yum.puppetlabs.com/puppet5/puppet5-release-el-#{@context[:os_version]}.noarch.rpm" 303 | else 304 | "https://yum.puppetlabs.com/puppetlabs-release-pc1-el-#{@context[:os_version]}.noarch.rpm" 305 | end 306 | end 307 | end 308 | 309 | def determine_hostname 310 | @context[:hostname] = @context[:image_name].split('/').pop if @context[:image_name] 311 | end 312 | 313 | def exists_and_is_file(value) 314 | @context[value] && File.file?(@context[value]) 315 | end 316 | 317 | def exists_and_is_directory(value) 318 | @context[value] && File.directory?(@context[value]) 319 | end 320 | end 321 | 322 | class DockerBuilder < ImageBuilder 323 | def build_file 324 | PuppetX::Puppetlabs::Dockerfile.new(@context) 325 | end 326 | 327 | def build_args 328 | %w[ 329 | cgroup_parent 330 | cpu_period 331 | cpu_quota 332 | cpu_shares 333 | cpuset_cpus 334 | cpuset_mems 335 | isolation 336 | memory_limit 337 | memory_swap 338 | shm_size 339 | ulimit 340 | disable_content_trust 341 | force_rm 342 | no_cache 343 | pull 344 | quiet 345 | ].reject { |arg| @context[arg.to_sym].nil? } 346 | end 347 | 348 | def string_args 349 | build_args.map do |arg| 350 | with_hyphen = arg.tr('_', '-') 351 | @context[arg.to_sym] == true ? "--#{with_hyphen}" : "--#{with_hyphen}=#{@context[arg.to_sym]}" 352 | end.join(' ') 353 | end 354 | 355 | def autosign_string 356 | @context[:autosign_token].nil? ? '' : "--build-arg AUTOSIGN_TOKEN=#{@context[:autosign_token]}" 357 | end 358 | 359 | def http_proxy_string 360 | @context[:http_proxy].nil? ? '' : "--build-arg http_proxy=#{@context[:http_proxy]}" 361 | end 362 | 363 | def https_proxy_string 364 | @context[:https_proxy].nil? ? '' : "--build-arg https_proxy=#{@context[:https_proxy]}" 365 | end 366 | 367 | def apt_proxy_string 368 | @context[:apt_proxy].nil? ? '' : "--build-arg APT_PROXY=#{@context[:apt_proxy]}" 369 | end 370 | 371 | def squash_string 372 | @context[:squash] ? '--squash' : '' 373 | end 374 | 375 | def command_build_args 376 | "#{autosign_string} #{apt_proxy_string} #{http_proxy_string} #{https_proxy_string} #{string_args} #{squash_string}" 377 | end 378 | 379 | def docker_network 380 | @context[:network].nil? ? '' : "--network #{@context[:network]}" 381 | end 382 | 383 | def build_command 384 | dockerfile_path = build_file.save.path 385 | if @context[:rocker] 386 | "rocker build #{command_build_args} -f #{dockerfile_path} ." 387 | else 388 | "docker build #{command_build_args} #{docker_network} -t #{@context[:image_name]} -f #{dockerfile_path} ." 389 | end 390 | end 391 | end 392 | 393 | class AciBuilder < ImageBuilder 394 | def build_file 395 | PuppetX::Puppetlabs::Acifile.new(@context) 396 | end 397 | 398 | def build_command 399 | "bash #{build_file.save.path}" 400 | end 401 | end 402 | end 403 | end 404 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # image_build 2 | 3 | [![Puppet 4 | Forge](https://img.shields.io/puppetforge/v/puppetlabs/image_build.svg)](https://forge.puppetlabs.com/puppetlabs/image_build) 5 | [![Build 6 | Status](https://secure.travis-ci.org/puppetlabs/puppetlabs-image_build.png)](https://travis-ci.org/puppetlabs/puppetlabs-image_build) 7 | [![Coverage 8 | Status](https://coveralls.io/repos/github/puppetlabs/puppetlabs-image_build/badge.svg?branch=master)](https://coveralls.io/github/puppetlabs/puppetlabs-image_build?branch=master) 9 | 10 | [Module description]: #module-description 11 | [Setup]: #setup 12 | [Usage]: #usage 13 | [Reference]: #reference 14 | [A hello world example]: #a-hello-world-example---nginx 15 | [Involving hiera]: #involving-hiera---elasticsearch 16 | [Building multiple images from one manifest]: #building-multiple-images-from-one-manifest 17 | [Using a Puppet Master]: #using-a-puppet-master 18 | [Minimizing image size with Rocker]: #minimizing-image-size-with-rocker 19 | [Building ACI images]: #building-aci-images 20 | [Limitations]: #limitations 21 | [Maintainers]: #maintainers 22 | 23 | #### Table of Contents 24 | 25 | 1. [Module description - What is the image_build module, and what does it 26 | do?][Module description] 27 | 2. [Setup - The basics of getting started with image_build][Setup] 28 | 3. [Usage - How to build Docker containers with Puppet][Usage] 29 | - [A hello world example][A hello world example] 30 | - [Involving hiera][Involving hiera] 31 | - [Building multiple images from one manifest][Building multiple images from one manifest] 32 | - [Using a Puppet Master][using a Puppet Master] 33 | - [Minimizing image size with Rocker][Minimizing image size with Rocker] 34 | - [Building ACI images][Building ACI images] 35 | 4. [Reference - Sample help output from the tool][Reference] 36 | 5. [Limitations - OS compatibility, etc.][Limitations] 37 | 6. [Maintainers - who maintains this project][Maintainers] 38 | 39 | 40 | ## Module description 41 | 42 | The basic purpose of `image_build` is to enable building various images, 43 | including Docker images, from Puppet code. There are two main cases 44 | where this can be useful: 45 | 46 | 1. You have an existing Puppet codebase and you're moving some of your 47 | services to using containers. By sharing the same code between 48 | container and non-container based infrastructure you can cut down on 49 | duplication of effort, and take advantage of work you've already 50 | done. 51 | 2. You're building a lot of images, but scaling Dockerfile means either 52 | a complex hierachy of images or copy-and-pasting snippets between 53 | many individual Dockerfiles. `image_build` allows for sharing common 54 | functionality as Puppet modules, and Puppet itself provides a rich 55 | domain-specific language for declarative composition of images. 56 | 57 | 58 | ## Setup 59 | 60 | `puppetlabs/image_build` is a Puppet Module and is available on the Forge. 61 | 62 | The following should work in most cases: 63 | 64 | ``` 65 | puppet module install puppetlabs/image_build 66 | ``` 67 | 68 | You don't need any additional gems installed unless you are looking to 69 | work on developing the module. All you need is a working Docker environment or 70 | `acbuild`, for which I'd recommend Docker for Mac or Docker for Windows 71 | or just installing Docker if you're on Linux. For acbuild you can use 72 | the [rkt module](https://forge.puppet.com/puppetlabs/rkt). 73 | 74 | ## Usage 75 | 76 | With the module installed you should have access to two new puppet 77 | commands; `puppet docker` and `puppet aci`. These have two subcommands, 78 | one will trigger a build of an image, the other can be used to output 79 | the intermediary dockerfile or shell script. 80 | 81 | The examples directory contains a set of examples for experimenting with. 82 | Simply open up `examples/nginx` and run: 83 | 84 | puppet docker build 85 | 86 | The above is the simplest example of a build. Some settings are provided 87 | in the accompanying `metadata.yaml` file, while others are defaults 88 | specific to the tool. You can change values in the metadata file (useful 89 | for version control) or you can override those values on the command 90 | line. 91 | 92 | puppet docker build --image-name puppet/sample --cmd nginx --expose 80 93 | 94 | See the full help page for other arguments for specifying different 95 | base images, setting a maintainer, using Rocker instead of Docker for the 96 | build and much more. 97 | 98 | puppet docker build --help 99 | 100 | You can also output the intermediary dockerfile using another 101 | subcommand. This is useful for both debugging and if you want to do 102 | something not natively supported by the tool. 103 | 104 | puppet docker dockerfile 105 | 106 | 107 | ### A hello world example - Nginx 108 | 109 | Lets see a simple hello world example. We'll create a Docker image 110 | running Nginx and serving a simple text file. 111 | 112 | First lets use a few Puppet modules from the Forge. We'll use the 113 | existing [nginx module](https://forge.puppet.com/puppet/nginx) and 114 | we'll specify it's dependencies. We're also using 115 | [dummy_service](https://forge.puppet.com/puppetlabs/dummy_service) to 116 | ignore service resources in the Nginx module. 117 | 118 | ``` 119 | $ cat Puppetfile 120 | forge 'https://forgeapi.puppetlabs.com' 121 | 122 | mod 'puppet/nginx' 123 | mod 'puppetlabs/stdlib' 124 | mod 'puppetlabs/concat' 125 | mod 'puppetlabs/apt' 126 | mod 'puppetlabs/dummy_service' 127 | ``` 128 | 129 | Then lets write a simple manifest. Disabling nginx daemon mode isn't 130 | supported by the module yet so we drop a file in place. Have a look at 131 | `manifests/init.pp`: 132 | 133 | ```puppet 134 | include 'dummy_service' 135 | 136 | class { 'nginx': } 137 | 138 | nginx::resource::vhost { 'default': 139 | www_root => '/var/www/html', 140 | } 141 | 142 | file { '/var/www/html/index.html': 143 | ensure => present, 144 | content => 'Hello Puppet and Docker', 145 | } 146 | 147 | exec { 'Disable Nginx daemon mode': 148 | path => '/bin', 149 | command => 'echo "daemon off;" >> /etc/nginx/nginx.conf', 150 | unless => 'grep "daemon off" /etc/nginx/nginx.conf', 151 | } 152 | ``` 153 | 154 | And finally lets store the metadata in a file rather than pass on the 155 | command line. Take a look at `metadata.yaml`: 156 | 157 | ```yaml 158 | cmd: nginx 159 | expose: 80 160 | image_name: puppet/nginx 161 | ``` 162 | 163 | Now lets build a Docker image. Note that you'll need docker available on 164 | your host to do so, along with the `image_build` module installed. 165 | 166 | ``` 167 | puppet docker build 168 | ``` 169 | 170 | And finally lets run our new image. We expose the webserver on port 8080 171 | to the local host. 172 | 173 | ``` 174 | $ docker run -d -p 8080:80 puppet/nginx 175 | 83d5fbe370e84d424c71c1c038ad1f5892fec579d28b9905cd1e379f9b89e36d 176 | $ curl http://0.0.0.0:8080 177 | Hello Puppet and Docker% 178 | ``` 179 | 180 | ### Involving hiera - Elasticsearch 181 | 182 | The Elasticsearch example is similar to the above, with a few additional 183 | features demonstrated. In particular the use of Hiera to provide 184 | additional context for the Puppet build. You can find this in the 185 | `examples/elasticsearch` directory. 186 | 187 | ``` 188 | puppet docker build manifests/init.pp --image-name puppet/es --expose 9200 --cmd /docker-entrypoint.sh 189 | ``` 190 | 191 | ### A note on options with multiple arguments 192 | 193 | Several of the arguments to `image_build` can take a list of values. 194 | This is done by passing in comma separated values. For instance, to 195 | specify an `ENTRYPOINT` like so: 196 | 197 | ``` 198 | ENTRYPOINT ["nginx", "-g", "daemon off"] 199 | ``` 200 | 201 | You can pass the following on the commandline: 202 | 203 | ``` 204 | --entrypoint nginx,'-g','daemon off' 205 | ``` 206 | 207 | ### Building multiple images from one manifest 208 | 209 | One advantage of using Puppet for building Docker images is you are 210 | removed from the need to have a single Dockerfile per image. Meaning a 211 | single repository of Puppet code can be used to describe multiple 212 | images. This makes ensuring all images use (for example) the same 213 | repositories or same hardening scripts much easier to enforce. Change 214 | code in one place and rebuild multiple images. 215 | 216 | Describing multiple images in Puppet is done using the existing `node` 217 | resource in your manifest. For instance: 218 | 219 | ```puppet 220 | node 'node1' { 221 | webserver { 'hello node 1': } 222 | } 223 | 224 | node 'node2' { 225 | webserver { 'hello node 2': } 226 | } 227 | ``` 228 | 229 | You can then select which image to build when running the build command, 230 | by explicitly passing the `image-name`. 231 | 232 | puppet docker build --image-name puppet/node1 233 | 234 | The match for the node resource in the Puppet code is done without the 235 | repository name, in this case the `puppet/` before `node1`. 236 | 237 | Note that you may want different metadata for different images. 238 | `image_build` will attempt to detect additional metadata in the 239 | `metadata` folder, and will merge items from `metadata/metadata.yaml` 240 | with node specific metadata, for instance from `metadata/node1.yaml` 241 | 242 | You can see an example of this settup in the `examples/multi` directory. 243 | 244 | 245 | ### Using a Puppet Master 246 | 247 | The above examples all use local manifests copied to the image during 248 | build, but `image_build` also supports using a Puppet Master. You can 249 | provide metadata via a local metadata file or directory, or by passing 250 | command line arguments to the build command as shown in the examples 251 | above. The only change is passing `--master` like so. 252 | 253 | puppet docker dockerfile --master puppet.example.com --image-name puppet/node1 --expose 80 --cmd nginx 254 | 255 | The hostname passed to the Puppet Master will take the form 256 | node1.{datetime}.dockerbuilder. This means you can match on that pattern 257 | in your manifests, for instance like so: 258 | 259 | ```puppet 260 | node /^node1/ { 261 | webserver { 'hello node 1': } 262 | } 263 | ``` 264 | 265 | A worked example is provided in the `examples/master` folder. You can 266 | either upload this to an existing Puppet Master or Puppet Enterprise 267 | install, or run a new local master using Docker. 268 | 269 | First install the dependent modules into the local environment: 270 | 271 | r10k puppetfile install --moduledir code/environments/production/modules 272 | 273 | Create an `autosign.conf` file with the following: 274 | 275 | ``` 276 | *.dockerbuilder.* 277 | ``` 278 | 279 | Then, from the `examples/master` folder, use Docker to run an instance 280 | of Puppet Server: 281 | 282 | docker run --name puppet -P --hostname puppet -v $(pwd)/code:/etc/puppetlabs/code -v $(pwd)/autosign.conf:/etc/puppetlabs/puppet/autosign.conf puppet/puppetserver-standalone 283 | 284 | Determine the port on which the Puppet Server is exposed locally: 285 | 286 | docker port puppet 287 | 288 | You'll also need the IP address of your local machine. Replace the {ip} 289 | and {port} in the following with your own values. 290 | 291 | puppet docker dockerfile --master {ip}:{port} --image-name puppet/node1 --expose 80 --cmd nginx 292 | 293 | This should use the code on the Puppet Master to build the image. 294 | 295 | 296 | ### Minimizing image size with Rocker 297 | 298 | `image_build` supports using the 299 | [Rocker](https://github.com/grammarly/rocker) build tool in place of the 300 | standard Docker build command. The Rocker output provides a little more 301 | detail about the build process, but also allows for mounting of folders 302 | at build time which minimizes the size of the resulting image. 303 | 304 | puppet docker build --rocker 305 | 306 | Note that when using Rocker the Puppet tools are not left in the final 307 | image, reducing it's file size. 308 | 309 | 310 | ### Building ACI images 311 | 312 | As well as Docker support, `image_build` also experimentally supports building 313 | [ACI](https://github.com/appc/spec/blob/master/spec/aci.md) compatible 314 | images for use with Rkt or other supported runtimes. This works in the 315 | same manner as above. The following command should generate a shell 316 | script which, when run, generates an ACI: 317 | 318 | puppet aci script 319 | 320 | And if you simply want to build the ACI directly you can just run: 321 | 322 | puppet aci build 323 | 324 | 325 | ## Reference 326 | 327 | ``` 328 | $ puppet docker --help 329 | USAGE: puppet docker [--from STRING] 330 | [--maintainer STRING] 331 | [--os STRING] 332 | [--os-version STRING] 333 | [--puppet-agent-version STRING] 334 | [--r10k-version STRING] 335 | [--module-path PATH] 336 | [--expose STRING] 337 | [--cmd STRING] 338 | [--entrypoint STRING] 339 | [--labels KEY=VALUE] 340 | [--rocker] 341 | [--[no-]inventory] 342 | [--hiera-config STRING] 343 | [--hiera-data STRING] 344 | [--image-user STRING] 345 | [--puppetfile STRING] 346 | [--image-name STRING] 347 | [--config-file STRING] 348 | [--config-directory STRING] 349 | [--master STRING] 350 | 351 | Build Docker images and Dockerfiles using Puppet code 352 | 353 | OPTIONS: 354 | --render-as FORMAT - The rendering format to use. 355 | --verbose - Whether to log verbosely. 356 | --debug - Whether to log debug information. 357 | --cmd STRING - The default command to be executed by the 358 | resulting image 359 | --config-directory STRING - A folder where metadata can be loaded from 360 | --config-file STRING - A configuration file with all the metadata 361 | --entrypoint STRING - The default entrypoint for the resulting 362 | image 363 | --expose STRING - A list of ports to be exposed by the 364 | resulting image 365 | --from STRING - The base docker image to use for the 366 | resulting image 367 | --hiera-config STRING - Hiera config file to use 368 | --hiera-data STRING - Hieradata directory to use 369 | --image-name STRING - The name of the resulting image 370 | --image-user STRING - Specify a user to be used to run the 371 | container process 372 | --[no-]inventory - Enable or disable the generation of an 373 | inventory file at /inventory.json 374 | --labels KEY=VALUE - A set of labels to be applied to the 375 | resulting image 376 | --maintainer STRING - Name and email address for the maintainer of 377 | the resulting image 378 | --master STRING - A Puppet Master to use for building images 379 | --module-path PATH - A path to a directory containing a set of 380 | modules to be copied into the image 381 | --network STRING - The Docker network to pass along to the 382 | docker build command 383 | --os STRING - The operating system used by the image if not 384 | autodetected 385 | --os-version STRING - The version of the operating system used by 386 | the image if not autodetected 387 | --puppet-agent-version STRING - Version of the Puppet Agent package to 388 | install 389 | --puppet-debug - Pass the debug flag to the Puppet process 390 | used to build the container image 391 | --puppetfile STRING - Enable use of Puppetfile to install 392 | dependencies during build 393 | --r10k-version STRING - Version of R10k to use for installing modules 394 | from Puppetfile 395 | --rocker - Use Rocker as the build tool 396 | --[no-]show-diff - Enable or disable showing the diff when 397 | running Puppet to build the image 398 | --skip-puppet-install - If the base image already contains Puppet we 399 | can skip installing it 400 | --volume STRING - A list of volumes to be added to the 401 | resulting image 402 | 403 | ACTIONS: 404 | build Build a Docker image from Puppet code 405 | dockerfile Generate a Dockerfile which will run the specified Puppet code 406 | 407 | See 'puppet man docker' or 'man puppet-docker' for full help. 408 | ``` 409 | 410 | ## Limitations 411 | 412 | The module currently does not support building Windows containers, or 413 | building containers from a Windows machine. We'll be adding support for 414 | these in the future. 415 | 416 | The inventory functionality does not work correctly on Centos 6 based 417 | images, so if you're using Centos 6 then you need to pass the 418 | `--no-inventory` flag. 419 | 420 | ## Maintainers 421 | 422 | This repository is maintained by: Gareth Rushgrove . 423 | -------------------------------------------------------------------------------- /spec/support/examples/imagebuilder.rb: -------------------------------------------------------------------------------- 1 | shared_examples 'an image builder' do 2 | context 'without any arguments' do 3 | let(:args) { {} } 4 | it 'should raise an error about missing operating system details' do 5 | expect { builder.context }.to raise_exception(PuppetX::Puppetlabs::InvalidContextError, %r{currently only supports}) 6 | end 7 | end 8 | 9 | context 'with minimal arguments' do 10 | let(:args) do 11 | { 12 | from: from, 13 | image_name: image_name 14 | } 15 | end 16 | it 'should not raise an error' do 17 | expect { context }.not_to raise_error 18 | end 19 | context 'should produce a context with' do 20 | it 'the original from value' do 21 | expect(context).to include(from: args[:from]) 22 | end 23 | it 'no user set' do 24 | expect(context[:image_user]).to be_nil 25 | end 26 | it 'the original image_name value' do 27 | expect(context).to include(image_name: args[:image_name]) 28 | end 29 | it 'an operating sytem inferred' do 30 | expect(context).to include(os: 'debian', os_version: '8') 31 | end 32 | it 'paths for Puppet binaries calculated' do 33 | expect(context).to include(:puppet_path, :gem_path, :r10k_path) 34 | end 35 | it 'the OS codename in an environment variable' do 36 | expect(context[:environment]).to include(codename: 'jessie') 37 | end 38 | it 'should expand the entrypoint to an array' do 39 | expect(context).to include(entrypoint: []) 40 | end 41 | end 42 | end 43 | 44 | context 'with label-schema set to true' do 45 | let(:args) do 46 | { 47 | from: from, 48 | image_name: image_name, 49 | label_schema: true 50 | } 51 | end 52 | it 'should by include some label-schema labels' do 53 | expect(context[:labels]).to include('org.label-schema.schema-version=1.0') 54 | end 55 | end 56 | 57 | context 'with a user set' do 58 | let(:user) { 'diane' } 59 | let(:args) do 60 | { 61 | from: from, 62 | image_name: image_name, 63 | image_user: user 64 | } 65 | end 66 | it 'should by include some label-schema labels' do 67 | expect(context[:image_user]).to eq(user) 68 | end 69 | end 70 | 71 | context 'with a single env value specified' do 72 | let(:args) do 73 | { 74 | from: from, 75 | image_name: image_name, 76 | env: 'KEY=value' 77 | } 78 | end 79 | it 'should expand the env to an array' do 80 | expect(context).to include(env: ['KEY=value']) 81 | end 82 | it 'should add the env to the environment used by the image' do 83 | expect(context[:environment]).to include('KEY' => 'value') 84 | end 85 | end 86 | 87 | context 'with a single label specified' do 88 | let(:args) do 89 | { 90 | from: from, 91 | image_name: image_name, 92 | labels: 'KEY=value' 93 | } 94 | end 95 | it 'should expand the labels to an array' do 96 | expect(context[:labels]).to include('KEY=value') 97 | end 98 | end 99 | 100 | context 'with a version greater than 5 specified' do 101 | let(:args) do 102 | { 103 | from: from, 104 | image_name: image_name, 105 | puppet_agent_version: 5 106 | } 107 | end 108 | it 'should use the correct package URL' do 109 | expect(context[:package_address]).to eq('https://apt.puppetlabs.com/puppet5-release-"$CODENAME".deb') 110 | end 111 | end 112 | 113 | context 'with a version less than 5 specified' do 114 | let(:args) do 115 | { 116 | from: from, 117 | image_name: image_name, 118 | puppet_agent_version: '1.10.5' 119 | } 120 | end 121 | it 'should use the correct package URL' do 122 | expect(context[:package_address]).to eq('https://apt.puppetlabs.com/puppetlabs-release-pc1-"$CODENAME".deb') 123 | end 124 | end 125 | 126 | context 'with a version less than 5 specified for a centos image' do 127 | let(:args) do 128 | { 129 | from: 'centos:7', 130 | image_name: image_name, 131 | puppet_agent_version: '1.10.5' 132 | } 133 | end 134 | it 'should use the correct package URL' do 135 | expect(context[:package_address]).to eq('https://yum.puppetlabs.com/puppetlabs-release-pc1-el-7.noarch.rpm') 136 | end 137 | end 138 | 139 | context 'with a master specified' do 140 | let(:master) { 'puppet.example.com' } 141 | let(:args) do 142 | { 143 | from: from, 144 | image_name: image_name, 145 | master: master 146 | } 147 | end 148 | it 'should set master host' do 149 | expect(context).to include(master_host: master) 150 | end 151 | it 'should not think master is an IP address' do 152 | expect(context).to include(master_is_ip: false) 153 | end 154 | end 155 | 156 | context 'with a master and port specified' do 157 | let(:port) { '9090' } 158 | let(:master) { 'puppet.example.com' } 159 | let(:args) do 160 | { 161 | from: from, 162 | image_name: image_name, 163 | master: "#{master}:#{port}" 164 | } 165 | end 166 | it 'should set master host' do 167 | expect(context).to include(master_host: master) 168 | end 169 | it 'should set master port' do 170 | expect(context).to include(master_port: port) 171 | end 172 | end 173 | 174 | context 'with a master specified as an IP address' do 175 | let(:master) { '192.168.0.9' } 176 | let(:args) do 177 | { 178 | from: from, 179 | image_name: image_name, 180 | master: master 181 | } 182 | end 183 | it 'should set master host' do 184 | expect(context).to include(master_host: master) 185 | end 186 | it 'should regonise master is an IP address' do 187 | expect(context).to include(master_is_ip: true) 188 | end 189 | end 190 | 191 | context 'with multiple label specified' do 192 | let(:args) do 193 | { 194 | from: from, 195 | image_name: image_name, 196 | labels: 'KEY=value,KEY2=value2' 197 | } 198 | end 199 | it 'should expand the labels to an array' do 200 | expect(context[:labels]).to include('KEY=value', 'KEY2=value2') 201 | end 202 | end 203 | 204 | context 'with multiple label specified in a config file' do 205 | let(:configfile) do 206 | file = Tempfile.new('metadata.yaml') 207 | file.write <<-EOF 208 | --- 209 | from: #{from} 210 | image_name: #{image_name} 211 | labels: 212 | - KEY=value 213 | - KEY2=value2 214 | EOF 215 | file.close 216 | file 217 | end 218 | let(:args) { { config_file: configfile.path } } 219 | it 'should expand the labels to an array' do 220 | expect(context[:labels]).to include('KEY=value', 'KEY2=value2') 221 | end 222 | end 223 | 224 | context 'with a single port specified' do 225 | let(:args) do 226 | { 227 | from: from, 228 | image_name: image_name, 229 | expose: 80 230 | } 231 | end 232 | it 'should expand the port to an array' do 233 | expect(context).to include(expose: ['80']) 234 | end 235 | end 236 | 237 | context 'with multiple ports specified' do 238 | let(:args) do 239 | { 240 | from: from, 241 | image_name: image_name, 242 | expose: '90,91' 243 | } 244 | end 245 | it 'should expand the labels to an array' do 246 | expect(context[:expose]).to include('90', '91') 247 | end 248 | end 249 | 250 | context 'with a single volume specified' do 251 | let(:args) do 252 | { 253 | from: from, 254 | image_name: image_name, 255 | volume: '/var/www' 256 | } 257 | end 258 | it 'should expand the volume to an array' do 259 | expect(context).to include(volume: ['/var/www']) 260 | end 261 | end 262 | 263 | context 'with multiple volume specified' do 264 | let(:args) do 265 | { 266 | from: from, 267 | image_name: image_name, 268 | volume: '/var/www,/var/lib' 269 | } 270 | end 271 | it 'should expand the volume to an array' do 272 | expect(context[:volume]).to include('/var/www', '/var/lib') 273 | end 274 | end 275 | 276 | context 'with multiple label specified in a config file' do 277 | let(:configfile) do 278 | file = Tempfile.new('metadata.yaml') 279 | file.write <<-EOF 280 | --- 281 | from: #{from} 282 | image_name: #{image_name} 283 | expose: 284 | - 92 285 | - 93 286 | EOF 287 | file.close 288 | file 289 | end 290 | let(:args) { { config_file: configfile.path } } 291 | it 'should expand the ports to an array' do 292 | expect(context[:expose]).to include(92, 93) 293 | end 294 | end 295 | 296 | context 'with a single cmd specified' do 297 | let(:args) do 298 | { 299 | from: from, 300 | image_name: image_name, 301 | cmd: 'nginx' 302 | } 303 | end 304 | it 'should expand the cmd to an array' do 305 | expect(context).to include(cmd: ['nginx']) 306 | end 307 | end 308 | 309 | context 'with multiple commands specified' do 310 | let(:args) do 311 | { 312 | from: from, 313 | image_name: image_name, 314 | cmd: 'nginx,run' 315 | } 316 | end 317 | it 'should expand the labels to an array' do 318 | expect(context[:cmd]).to include('nginx', 'run') 319 | end 320 | end 321 | 322 | context 'with multiple commands specified in a config file' do 323 | let(:configfile) do 324 | file = Tempfile.new('metadata.yaml') 325 | file.write <<-EOF 326 | --- 327 | from: #{from} 328 | image_name: #{image_name} 329 | cmd: 330 | - nginx 331 | - run 332 | EOF 333 | file.close 334 | file 335 | end 336 | let(:args) { { config_file: configfile.path } } 337 | it 'should expand the commands to an array' do 338 | expect(context[:cmd]).to include('nginx', 'run') 339 | end 340 | end 341 | 342 | context 'with a single entrypoint specified' do 343 | let(:args) do 344 | { 345 | from: from, 346 | image_name: image_name, 347 | entrypoint: 'bash' 348 | } 349 | end 350 | it 'should expand the entrypoint to an array' do 351 | expect(context).to include(entrypoint: ['bash']) 352 | end 353 | end 354 | 355 | context 'with multiple entrypoints specified' do 356 | let(:args) do 357 | { 358 | from: from, 359 | image_name: image_name, 360 | entrypoint: 'bash,-x' 361 | } 362 | end 363 | it 'should expand the entrypoints to an array' do 364 | expect(context[:entrypoint]).to include('bash', '-x') 365 | end 366 | end 367 | 368 | context 'with multiple entrypoints specified in a config file' do 369 | let(:configfile) do 370 | file = Tempfile.new('metadata.yaml') 371 | file.write <<-EOF 372 | --- 373 | from: #{from} 374 | image_name: #{image_name} 375 | entrypoint: 376 | - bash 377 | - -x 378 | EOF 379 | file.close 380 | file 381 | end 382 | let(:args) { { config_file: configfile.path } } 383 | it 'should expand the entrypoints to an array' do 384 | expect(context[:entrypoint]).to include('bash', '-x') 385 | end 386 | end 387 | 388 | context 'with a Puppetfile provided' do 389 | let(:puppetfile) { Tempfile.new('Puppetfile') } 390 | let(:args) do 391 | { 392 | from: from, 393 | image_name: image_name, 394 | puppetfile: puppetfile.path 395 | } 396 | end 397 | it 'should not raise an error' do 398 | expect { context }.not_to raise_error 399 | end 400 | it 'should produce a context which enables the puppetfile options' do 401 | expect(context).to include(use_puppetfile: true) 402 | end 403 | end 404 | 405 | context 'with a Puppetfile provided' do 406 | let(:puppetfile) { Tempfile.new('Puppetfile') } 407 | let(:args) do 408 | { 409 | from: from, 410 | image_name: image_name, 411 | puppetfile: puppetfile.path 412 | } 413 | end 414 | it 'should not raise an error' do 415 | expect { context }.not_to raise_error 416 | end 417 | it 'should produce a context which enables the puppetfile options' do 418 | expect(context).to include(use_puppetfile: true) 419 | end 420 | end 421 | 422 | context 'with a hiera configuration provided' do 423 | let(:hieraconfig) { Tempfile.new('hiera.yaml') } 424 | let(:hieradata) { Dir.mktmpdir('hieradata') } 425 | let(:args) do 426 | { 427 | from: from, 428 | image_name: image_name, 429 | hiera_config: hieraconfig.path, 430 | hiera_data: hieradata 431 | } 432 | end 433 | it 'should not raise an error' do 434 | expect { context }.not_to raise_error 435 | end 436 | it 'should produce a context which enables the hiera options' do 437 | expect(context).to include(use_hiera: true) 438 | end 439 | end 440 | 441 | context 'with a module path provided' do 442 | let(:module_path) { '/example/directory' } 443 | let(:args) do 444 | { 445 | from: 'alpine:3.4', 446 | image_name: image_name, 447 | module_path: module_path 448 | } 449 | end 450 | it 'should not raise an error' do 451 | expect { context }.not_to raise_error 452 | end 453 | it 'should produce a context with a module path' do 454 | expect(context).to include(module_path: module_path) 455 | end 456 | end 457 | 458 | context 'with an alternative operating system' do 459 | let(:args) do 460 | { 461 | from: 'alpine:3.4', 462 | image_name: image_name 463 | } 464 | end 465 | context 'should produce a context with' do 466 | it 'an operating sytem inferred' do 467 | expect(context).to include(os: 'alpine', os_version: '3.4') 468 | end 469 | it 'paths for Puppet binaries calculated' do 470 | expect(context).to include(:puppet_path, :gem_path, :r10k_path) 471 | end 472 | it 'the facter and puppet version in an environment variable' do 473 | expect(context[:environment]).to include(:facter_version, :puppet_version) 474 | end 475 | end 476 | end 477 | 478 | context 'with a config file used for providing input' do 479 | let(:configfile) do 480 | file = Tempfile.new('metadata.yaml') 481 | file.write <<-EOF 482 | --- 483 | from: #{from} 484 | image_name: #{image_name} 485 | EOF 486 | file.close 487 | file 488 | end 489 | let(:args) { { config_file: configfile.path } } 490 | it 'should not raise an error' do 491 | expect { context }.not_to raise_error 492 | end 493 | it 'the from value from the config file' do 494 | expect(context).to include(from: from) 495 | end 496 | it 'the image_name value from the config file' do 497 | expect(context).to include(image_name: image_name) 498 | end 499 | context 'providing a value in a file and as an argument' do 500 | let(:new_image_name) { 'puppet/different' } 501 | let(:args) do 502 | { 503 | config_file: configfile.path, 504 | image_name: new_image_name 505 | } 506 | end 507 | it 'hould use the value from the file' do 508 | expect(context).not_to include(image_name: new_image_name) 509 | end 510 | end 511 | end 512 | 513 | context 'with a config file in a directory' do 514 | let(:configdir) do 515 | dir = Dir.mktmpdir('metadata') 516 | file = File.new("#{dir}/metadata.yaml", 'w') 517 | file.write <<-EOF 518 | --- 519 | from: #{from} 520 | image_name: #{image_name} 521 | EOF 522 | file.close 523 | dir 524 | end 525 | let(:args) do 526 | { 527 | config_directory: configdir, 528 | config_file: 'metadata.yaml' 529 | } 530 | end 531 | it 'should determine the correct from value from the config file' do 532 | expect(context).to include(from: from) 533 | end 534 | end 535 | 536 | context 'with a host override config file in a directory' do 537 | let(:configdir) do 538 | dir = Dir.mktmpdir('metadata') 539 | file = File.new("#{dir}/metadata.yaml", 'w') 540 | file.write <<-EOF 541 | --- 542 | from: #{from} 543 | expose: 80 544 | EOF 545 | file.close 546 | file = File.new("#{dir}/sample.yaml", 'w') 547 | file.write <<-EOF 548 | --- 549 | expose: 90 550 | EOF 551 | file.close 552 | dir 553 | end 554 | let(:args) do 555 | { 556 | config_directory: configdir, 557 | config_file: 'metadata.yaml', 558 | image_name: image_name 559 | } 560 | end 561 | it 'should determine the correct from value from the config file' do 562 | expect(context).to include(from: from) 563 | end 564 | it 'should determine the correct port value from the host config file' do 565 | expect(context).to include(expose: ['90']) 566 | end 567 | end 568 | 569 | context 'with an invalid config file used for providing input' do 570 | let(:configfile) do 571 | file = Tempfile.new('metadata.yaml') 572 | file.write <<-EOF 573 | - 574 | invalid 575 | EOF 576 | file.close 577 | file 578 | end 579 | let(:args) do 580 | { 581 | config_file: configfile.path 582 | } 583 | end 584 | it 'should raise a suitable error' do 585 | expect { context }.to raise_exception(PuppetX::Puppetlabs::InvalidContextError, %r{valid YAML}) 586 | end 587 | end 588 | 589 | os_codenames = { 590 | ubuntu: { 591 | '16.04' => 'xenial', 592 | '14.04' => 'trusty', 593 | '12.04' => 'precise' 594 | }, 595 | debian: { 596 | '9' => 'stretch', 597 | '8' => 'jessie', 598 | '7' => 'wheezy' 599 | } 600 | } 601 | os_codenames.each do |os, hash| 602 | hash.each do |version, codename| 603 | context "when inheriting from #{os}:#{version}" do 604 | let(:args) do 605 | { 606 | from: "#{os}:#{version}", 607 | image_name: image_name 608 | } 609 | end 610 | it "the codename should be #{codename}" do 611 | expect(context[:environment]).to include(codename: codename) 612 | end 613 | end 614 | end 615 | end 616 | 617 | context 'when using a centos image' do 618 | let(:args) do 619 | { 620 | from: 'centos:7', 621 | image_name: image_name 622 | } 623 | end 624 | it 'should not raise an error' do 625 | expect { context }.not_to raise_error 626 | end 627 | end 628 | end 629 | --------------------------------------------------------------------------------