diff --git a/lib/facter/docker.rb b/lib/facter/docker.rb index b7cb379..bdbe15c 100644 --- a/lib/facter/docker.rb +++ b/lib/facter/docker.rb @@ -1,107 +1,119 @@ # frozen_string_literal: true require 'facter' require 'json' +require 'etc' Facter.add(:docker_systemroot) do confine osfamily: :windows setcode do Puppet::Util.get_env('SystemRoot') end end Facter.add(:docker_program_files_path) do confine osfamily: :windows setcode do Puppet::Util.get_env('ProgramFiles') end end Facter.add(:docker_program_data_path) do confine osfamily: :windows setcode do Puppet::Util.get_env('ProgramData') end end Facter.add(:docker_user_temp_path) do confine osfamily: :windows setcode do Puppet::Util.get_env('TEMP') end end +Facter.add(:docker_home_dirs) do + confine kernel: 'Linux' + setcode do + home_dirs = {} + Etc.passwd do |user| + home_dirs[user.name] = user.dir + end + home_dirs + end +end + docker_command = if Facter.value(:kernel) == 'windows' 'powershell -NoProfile -NonInteractive -NoLogo -ExecutionPolicy Bypass -c docker' else 'docker' end def interfaces Facter.value(:interfaces).split(',') end Facter.add(:docker_client_version) do setcode do docker_version = Facter.value(:docker_version) docker_version['Client']['Version'] if docker_version end end Facter.add(:docker_server_version) do setcode do docker_version = Facter.value(:docker_version) if docker_version && !docker_version['Server'].nil? && docker_version['Server'].is_a?(Hash) docker_version['Server']['Version'] else nil end end end Facter.add(:docker_version) do setcode do if Facter::Util::Resolution.which('docker') value = Facter::Core::Execution.execute( "#{docker_command} version --format '{{json .}}'", ) val = JSON.parse(value) end val end end Facter.add(:docker) do setcode do docker_version = Facter.value(:docker_client_version) if docker_version !~ %r{1[.][0-9][0-2]?[.]\w+} if Facter::Util::Resolution.which('docker') docker_json_str = Facter::Util::Resolution.exec( "#{docker_command} info --format '{{json .}}'", ) begin docker = JSON.parse(docker_json_str) docker['network'] = {} docker['network']['managed_interfaces'] = {} network_list = Facter::Util::Resolution.exec("#{docker_command} network ls | tail -n +2") docker_network_names = [] network_list.each_line { |line| docker_network_names.push line.split[1] } docker_network_ids = [] network_list.each_line { |line| docker_network_ids.push line.split[0] } docker_network_names.each do |network| inspect = JSON.parse(Facter::Util::Resolution.exec("#{docker_command} network inspect #{network}")) docker['network'][network] = inspect[0] network_id = docker['network'][network]['Id'][0..11] interfaces.each do |iface| docker['network']['managed_interfaces'][iface] = network if iface =~ %r{#{network_id}} end end docker rescue JSON::ParserError nil end end end end end diff --git a/manifests/registry.pp b/manifests/registry.pp index b51052d..21c40b9 100644 --- a/manifests/registry.pp +++ b/manifests/registry.pp @@ -1,151 +1,154 @@ # == Class: docker # # Module to configure private docker registries from which to pull Docker images # If the registry does not require authentication, this module is not required. # # === Parameters # [*server*] # The hostname and port of the private Docker registry. Ex: dockerreg:5000 # # [*ensure*] # Whether or not you want to login or logout of a repository # # [*username*] # Username for authentication to private Docker registry. # auth is not required. # # [*password*] # Password for authentication to private Docker registry. Leave undef if # auth is not required. # # [*pass_hash*] # The hash to be used for receipt. If left as undef, a hash will be generated # # [*email*] # Email for registration to private Docker registry. Leave undef if # auth is not required. # # [*local_user*] # The local user to log in as. Docker will store credentials in this # users home directory # # [*receipt*] # Required to be true for idempotency # define docker::registry( Optional[String] $server = $title, Optional[Pattern[/^present$|^absent$/]] $ensure = 'present', Optional[String] $username = undef, Optional[String] $password = undef, Optional[String] $pass_hash = undef, Optional[String] $email = undef, Optional[String] $local_user = 'root', Optional[String] $version = $docker::version, Optional[Boolean] $receipt = true, ) { include docker::params $docker_command = $docker::params::docker_command if $::osfamily == 'windows' { $exec_environment = ["PATH=${::docker_program_files_path}/Docker/"] $exec_timeout = 3000 $exec_path = ["${::docker_program_files_path}/Docker/"] $exec_provider = 'powershell' $password_env = '$env:password' $exec_user = undef } else { $exec_environment = [] $exec_path = ['/bin', '/usr/bin'] $exec_timeout = 0 $exec_provider = undef $password_env = "\${password}" $exec_user = $local_user + $local_user_home = $facts['docker_home_dirs'][$local_user] } if $ensure == 'present' { if $username != undef and $password != undef and $email != undef and $version != undef and $version =~ /1[.][1-9]0?/ { $auth_cmd = "${docker_command} login -u '${username}' -p \"${password_env}\" -e '${email}' ${server}" $auth_environment = "password=${password}" } elsif $username != undef and $password != undef { $auth_cmd = "${docker_command} login -u '${username}' -p \"${password_env}\" ${server}" $auth_environment = "password=${password}" } else { $auth_cmd = "${docker_command} login ${server}" $auth_environment = '' } } else { $auth_cmd = "${docker_command} logout ${server}" $auth_environment = '' } $docker_auth = "${title}${auth_environment}${auth_cmd}${local_user}" if $auth_environment != '' { $exec_env = concat($exec_environment, $auth_environment, "docker_auth=${docker_auth}") } else { $exec_env = concat($exec_environment, "docker_auth=${docker_auth}") } if $receipt { if $::osfamily != 'windows' { # server may be an URI, which can contain / $server_strip = regsubst($server, '/', '_', 'G') # no - with pw_hash $local_user_strip = regsubst($local_user, '[-_]', '', 'G') $_pass_hash = $pass_hash ? { Undef => pw_hash($docker_auth, 'SHA-512', $local_user_strip), default => $pass_hash } - $_auth_command = "${auth_cmd} || rm -f \"/root/registry-auth-puppet_receipt_${server_strip}_${local_user}\"" + $_auth_command = "${auth_cmd} || rm -f \"/${local_user_home}/registry-auth-puppet_receipt_${server_strip}_${local_user}\"" - file { "/root/registry-auth-puppet_receipt_${server_strip}_${local_user}": + file { "/${local_user_home}/registry-auth-puppet_receipt_${server_strip}_${local_user}": ensure => $ensure, content => $_pass_hash, + owner => $local_user, + group => $local_user, notify => Exec["${title} auth"], } } else { # server may be an URI, which can contain / $server_strip = regsubst($server, '[/:]', '_', 'G') $passfile = "${::docker_user_temp_path}/registry-auth-puppet_receipt_${server_strip}_${local_user}" # lint:ignore:140chars $_auth_command = "if (-not (${auth_cmd})) { Remove-Item -Path ${passfile} -Force -Recurse -EA SilentlyContinue; exit 0 } else { exit 0 }" # lint:endignore if $ensure == 'absent' { file { $passfile: ensure => $ensure, notify => Exec["${title} auth"], } } elsif $ensure == 'present' { exec { 'compute-hash': command => template('docker/windows/compute_hash.ps1.erb'), environment => $exec_env, provider => $exec_provider, logoutput => true, unless => template('docker/windows/check_hash.ps1.erb'), notify => Exec["${title} auth"], } } } } else { $_auth_command = $auth_cmd } exec { "${title} auth": environment => $exec_env, command => $_auth_command, user => $exec_user, path => $exec_path, timeout => $exec_timeout, provider => $exec_provider, refreshonly => $receipt, } } diff --git a/spec/defines/registry_spec.rb b/spec/defines/registry_spec.rb index 420702d..ffb7170 100644 --- a/spec/defines/registry_spec.rb +++ b/spec/defines/registry_spec.rb @@ -1,82 +1,85 @@ require 'spec_helper' describe 'docker::registry', type: :define do let(:title) { 'localhost:5000' } let(:facts) do { osfamily: 'Debian', operatingsystem: 'Debian', lsbdistid: 'Debian', lsbdistcodename: 'jessie', kernelrelease: '3.2.0-4-amd64', operatingsystemmajrelease: '8', + docker_home_dirs: { + root: '/root', + }, } end let(:params) { { 'version' => '17.06', 'pass_hash' => 'test1234', 'receipt' => false } } it { is_expected.to contain_exec('localhost:5000 auth') } context 'with ensure => present' do let(:params) { { 'ensure' => 'absent', 'version' => '17.06', 'pass_hash' => 'test1234', 'receipt' => false } } it { is_expected.to contain_exec('localhost:5000 auth').with_command('docker logout localhost:5000') } end context 'with ensure => present' do let(:params) { { 'ensure' => 'present', 'version' => '17.06', 'pass_hash' => 'test1234', 'receipt' => false } } it { is_expected.to contain_exec('localhost:5000 auth').with_command('docker login localhost:5000') } end context 'with ensure => present and username => user1' do let(:params) { { 'ensure' => 'present', 'username' => 'user1', 'version' => '17.06', 'pass_hash' => 'test1234', 'receipt' => false } } it { is_expected.to contain_exec('localhost:5000 auth').with_command('docker login localhost:5000') } end context 'with ensure => present and password => secret' do let(:params) { { 'ensure' => 'present', 'password' => 'secret', 'version' => '17.06', 'pass_hash' => 'test1234', 'receipt' => false } } it { is_expected.to contain_exec('localhost:5000 auth').with_command('docker login localhost:5000') } end context 'with ensure => present and email => user1@example.io' do let(:params) { { 'ensure' => 'present', 'email' => 'user1@example.io', 'version' => '17.06', 'pass_hash' => 'test1234', 'receipt' => false } } it { is_expected.to contain_exec('localhost:5000 auth').with_command('docker login localhost:5000') } end context 'with ensure => present and username => user1, and password => secret and email => user1@example.io' do let(:params) { { 'ensure' => 'present', 'username' => 'user1', 'password' => 'secret', 'email' => 'user1@example.io', 'version' => '17.06', 'pass_hash' => 'test1234', 'receipt' => false } } it { is_expected.to contain_exec('localhost:5000 auth').with_command("docker login -u 'user1' -p \"${password}\" localhost:5000").with_environment(%r{password=secret}) } end context 'with ensure => present and username => user1, and password => secret and email => user1@example.io and version < 1.11.0' do let(:params) { { 'ensure' => 'present', 'username' => 'user1', 'password' => 'secret', 'email' => 'user1@example.io', 'version' => '1.9.0', 'pass_hash' => 'test1234', 'receipt' => false } } it { is_expected.to contain_exec('localhost:5000 auth').with_command("docker login -u 'user1' -p \"${password}\" -e 'user1@example.io' localhost:5000").with_environment(%r{password=secret}) } end context 'with username => user1, and password => secret' do let(:params) { { 'username' => 'user1', 'password' => 'secret', 'version' => '17.06', 'pass_hash' => 'test1234', 'receipt' => false } } it { is_expected.to contain_exec('localhost:5000 auth').with_command("docker login -u 'user1' -p \"${password}\" localhost:5000").with_environment(%r{password=secret}) } end context 'with username => user1, and password => secret and local_user => testuser' do let(:params) { { 'username' => 'user1', 'password' => 'secret', 'local_user' => 'testuser', 'version' => '17.06', 'pass_hash' => 'test1234', 'receipt' => false } } it { is_expected.to contain_exec('localhost:5000 auth').with_command("docker login -u 'user1' -p \"${password}\" localhost:5000").with_user('testuser').with_environment(%r{password=secret}) } end context 'with an invalid ensure value' do let(:params) { { 'ensure' => 'not present or absent' } } it do expect { is_expected.to contain_exec('docker logout localhost:5000') }.to raise_error(Puppet::Error) end end end