diff --git a/README.md b/README.md index e2452a9309..f5ed164a3c 100644 --- a/README.md +++ b/README.md @@ -102,17 +102,21 @@ This module will install packages, create configuration and start services neces Plugin sync is required if the custom sensu types and providers are used. -This module has a soft dependency on the [puppetlabs/apt](https://forge.puppet.com/puppetlabs/apt) module (`>= 5.0.1 < 8.0.0`) for systems using `apt`. +#### Soft module dependencies -If using Puppet >= 6.0.0 there is a soft dependency on the [puppetlabs/yumrepo_core](https://forge.puppet.com/puppetlabs/yumrepo_core) module (`>= 1.0.1 < 2.0.0`) for systems using `yum`. +For systems using `apt`: + * [puppetlabs/apt](https://forge.puppet.com/puppetlabs/apt) module (`>= 5.0.1 < 8.0.0`) -If managing Windows there is a soft dependency on the [puppetlabs/chocolatey](https://forge.puppet.com/puppetlabs/chocolatey) module (`>= 3.0.0 < 5.0.0`). +For systems using `yum` and Puppet >= 6.0.0: + * [puppetlabs/yumrepo_core](https://forge.puppet.com/puppetlabs/yumrepo_core) module (`>= 1.0.1 < 2.0.0`) -If managing Windows and defining `package_source`, there is a soft dependency on the [puppet/archive](https://forge.puppet.com/puppet/archive) module (`>= 3.0.0 < 5.0.0`). +For Windows: + * [puppetlabs/chocolatey](https://forge.puppet.com/puppetlabs/chocolatey) module (`>= 3.0.0 < 5.0.0`) + * [puppet/windows_env](https://forge.puppet.com/puppet/windows_env) module (`>= 3.0.0 < 4.0.0`) + * [puppet/archive](https://forge.puppet.com/puppet/archive) module (`>= 3.0. 0 < 5.0.0`) -If managing Windows and defining `service_env_vars` there is a soft depedency on [puppet/windows_env](https://forge.puppet.com/puppet/windows_env) module (`>= 3.0.0 < 4.0.0`) - -For PostgreSQL datastore support there is a soft dependency on [puppetlabs/postgresql](https://forge.puppet.com/puppetlabs/postgresql) module (`>= 6.0.0 < 7.0.0`). +For PostgreSQL datastore support: +* [puppetlabs/postgresql](https://forge.puppet.com/puppetlabs/postgresql) module (`>= 6.0.0 < 7.0.0`) ### Beginning with sensu @@ -198,6 +202,16 @@ class { '::sensu::cli': **NOTE**: The `sensu::backend` class calls the `sensu::cli` class so it is only necessary to directly call the `sensu::cli` class on hosts not using the `sensu::backend` class. +For Windows the `install_source` parameter must be provided: + +```puppet +class { '::sensu::cli': + install_source => 'https://s3-us-west-2.amazonaws.com/sensu.io/sensu-go/5.14.1/sensu-go_5.14.1_windows_amd64.zip', + url_host => 'sensu-backend.example.com', + password => 'supersecret', +} +``` + ### Manage Windows Agent This module supports Windows Sensu Go agent via chocolatey beginning with version 5.12.0. diff --git a/Vagrantfile b/Vagrantfile index 050e956954..55c7add9aa 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -172,6 +172,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| agent.vm.network "forwarded_port", host: 3390, guest: 3389, auto_correct: true agent.vm.provision :shell, :path => "tests/provision_basic_win.ps1" agent.vm.provision :shell, :inline => '$env:PATH += ";C:\Program Files\Puppet Labs\Puppet\bin" ; iex "puppet apply -v C:/vagrant/tests/sensu-agent.pp"' + agent.vm.provision :shell, :inline => '$env:PATH += ";C:\Program Files\Puppet Labs\Puppet\bin" ; iex "puppet apply -v C:/vagrant/tests/sensu-cli.pp"' agent.vm.provision :shell, :inline => '$env:PATH += ";C:\Program Files\Puppet Labs\Puppet\bin" ; iex "facter --custom-dir=C:\vagrant\lib\facter sensu_agent"' end @@ -186,6 +187,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| agent.vm.network "forwarded_port", host: 3389, guest: 3389, auto_correct: true agent.vm.provision :shell, :path => "tests/provision_basic_win.ps1" agent.vm.provision :shell, :inline => 'iex "puppet apply -v C:/vagrant/tests/sensu-agent.pp"' + agent.vm.provision :shell, :inline => 'iex "puppet apply -v C:/vagrant/tests/sensu-cli.pp"' agent.vm.provision :shell, :inline => 'iex "facter --custom-dir=C:\vagrant\lib\facter sensu_agent"' end @@ -215,6 +217,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| agent.vm.network "forwarded_port", host: 3391, guest: 3389, auto_correct: true agent.vm.provision :shell, :path => "tests/provision_basic_win.ps1" agent.vm.provision :shell, :inline => 'iex "puppet apply -v C:/vagrant/tests/sensu-agent.pp"' + agent.vm.provision :shell, :inline => 'iex "puppet apply -v C:/vagrant/tests/sensu-cli.pp"' agent.vm.provision :shell, :inline => 'iex "facter --custom-dir=C:\vagrant\lib\facter sensu_agent"' end diff --git a/data/os/windows.yaml b/data/os/windows.yaml index 0b0697bb59..95dae3186c 100644 --- a/data/os/windows.yaml +++ b/data/os/windows.yaml @@ -6,3 +6,4 @@ sensu::agent::package_name: 'sensu-agent' sensu::agent::package_download_path: 'C:\' sensu::agent::log_file: 'C:\ProgramData\sensu\log\sensu-agent.log' sensu::agent::service_name: SensuAgent +sensu::cli::install_path: 'C:\Program Files\Sensu' diff --git a/lib/puppet/provider/sensu_configure/sensuctl.rb b/lib/puppet/provider/sensu_configure/sensuctl.rb index 935c6fdd34..ed3ddb1cf4 100644 --- a/lib/puppet/provider/sensu_configure/sensuctl.rb +++ b/lib/puppet/provider/sensu_configure/sensuctl.rb @@ -57,10 +57,10 @@ def configure_cmd(bootstrap) def create begin - configure_cmd(resource[:bootstrap]) + output = configure_cmd(resource[:bootstrap]) rescue Puppet::ExecutionFailure => e - File.delete(config_path) - raise Puppet::Error, "sensuctl configure failed\nError message: #{e.message}" + File.delete(config_path) if File.exist?(config_path) + raise Puppet::Error, "sensuctl configure failed\nOutput: #{output}\nError message: #{e.message}" rescue Exception => e raise Puppet::Error, "sensuctl configure failed\nError message: #{e.message}" end @@ -71,7 +71,7 @@ def flush begin if @property_flush[:trusted_ca_file] == 'absent' Puppet.info("Deleting #{config_path} to clear trusted-ca-file") - File.delete(config_path) + File.delete(config_path) if File.exist?(config_path) end configure_cmd(false) rescue Exception => e diff --git a/lib/puppet/provider/sensuctl.rb b/lib/puppet/provider/sensuctl.rb index 5994fef059..9776ca6c24 100644 --- a/lib/puppet/provider/sensuctl.rb +++ b/lib/puppet/provider/sensuctl.rb @@ -5,16 +5,21 @@ class Puppet::Provider::Sensuctl < Puppet::Provider initvars - commands :sensuctl => 'sensuctl' + commands :sensuctl_cmd => 'sensuctl' class << self attr_accessor :chunk_size + attr_accessor :path end def self.config_path - # https://github.com/sensu/sensu-puppet/issues/1072 - # since $HOME is not set in systemd service File.expand_path('~') won't work - home = Etc.getpwuid(Process.uid).dir + if Dir.respond_to?(:home) + home = Dir.home + else + # https://github.com/sensu/sensu-puppet/issues/1072 + # since $HOME is not set in systemd service File.expand_path('~') won't work + home = Etc.getpwuid(Process.uid).dir + end File.join(home, '.config/sensu/sensuctl/cluster') end def config_path @@ -47,6 +52,19 @@ def convert_boolean_property_value(value) end end + def self.sensuctl(args) + sensuctl_cmd = which('sensuctl') + if ! path.nil? + cmd = [path] + args + else + cmd = [sensuctl_cmd] + args + end + execute(cmd) + end + def sensuctl(*args) + self.class.sensuctl(*args) + end + def self.sensuctl_list(command, namespaces = true) args = [command] args << 'list' diff --git a/lib/puppet/type/sensuctl_config.rb b/lib/puppet/type/sensuctl_config.rb index 08873bf8c5..5667076a55 100644 --- a/lib/puppet/type/sensuctl_config.rb +++ b/lib/puppet/type/sensuctl_config.rb @@ -16,6 +16,10 @@ desc "sensuctl chunk-size" end + newparam(:path) do + desc "path to sensuctl" + end + # First collect all types with sensuctl provider that come from this module # For each sensuctl type, set the class variable 'chunk_size' used by # each provider to list resources @@ -29,6 +33,7 @@ def generate sensuctl_types.each do |type| provider_class = Puppet::Type.type(type).provider(:sensuctl) provider_class.chunk_size = self[:chunk_size] + provider_class.path = self[:path] end [] end diff --git a/manifests/cli.pp b/manifests/cli.pp index d6b348b40d..0c35ee569a 100644 --- a/manifests/cli.pp +++ b/manifests/cli.pp @@ -12,6 +12,12 @@ # Windows MSI packaging and to avoid surprising upgrades. # @param package_name # Name of Sensu CLI package. +# @param install_source +# Source of Sensu Go CLI download for installing on Windows. +# Paths with http:// or https:// will be downloaded +# Paths with puppet:// or file:// paths will also be installed. +# @param install_path +# Where to install sensuctl for Windows. Default to `C:\Program Files\Sensu`. # @param url_host # Sensu backend host used to configure sensuctl and verify API access. # @param url_port @@ -19,17 +25,22 @@ # @param password # Sensu backend admin password used to confiure sensuctl. # @param bootstrap -# Should sensuctl be bootstrapped +# Should sensuctl be bootstrapped. This is a private parameter used by sensu::backend class. +# @param configure +# Determines if sensuctl should be configured # @param sensuctl_chunk_size # Chunk size to use when listing sensuctl resources # class sensu::cli ( Optional[String] $version = undef, String $package_name = 'sensu-go-cli', + Optional[Variant[Stdlib::HTTPSUrl, Stdlib::HTTPUrl, Pattern[/^(file|puppet):/]]] $install_source = undef, + Optional[Stdlib::Absolutepath] $install_path = undef, String $url_host = $trusted['certname'], Stdlib::Port $url_port = 8080, String $password = 'P@ssw0rd!', Boolean $bootstrap = false, + Boolean $configure = true, Optional[Integer] $sensuctl_chunk_size = undef, ) { @@ -50,23 +61,53 @@ $url = "${url_protocol}://${url_host}:${url_port}" - package { 'sensu-go-cli': - ensure => $_version, - name => $package_name, - require => $::sensu::package_require, + if $facts['os']['family'] == 'windows' { + if ! $install_source { + fail('sensu::cli: install_source is required for Windows') + } + $sensuctl_path = "${install_path}\\sensuctl.exe" + file { $install_path: + ensure => 'directory', + } + archive { 'sensu-go-cli.zip': + path => "${install_path}\\sensu-go-cli.zip", + source => $install_source, + extract => true, + extract_path => $install_path, + creates => "${install_path}\\sensuctl.exe", + cleanup => false, + require => File[$install_path], + } + windows_env { 'sensuctl-path': + ensure => 'present', + variable => 'PATH', + value => $install_path, + mergemode => 'append', + require => Archive['sensu-go-cli.zip'], + before => Sensu_configure['puppet'], + } + } else { + $sensuctl_path = undef + package { 'sensu-go-cli': + ensure => $_version, + name => $package_name, + require => $::sensu::package_require, + } } - sensuctl_config { 'sensu': - chunk_size => $sensuctl_chunk_size, - } + if $configure { + sensuctl_config { 'sensu': + chunk_size => $sensuctl_chunk_size, + path => $sensuctl_path, + } - sensu_configure { 'puppet': - url => $url, - username => 'admin', - password => $password, - bootstrap => $bootstrap, - bootstrap_password => 'P@ssw0rd!', - trusted_ca_file => $trusted_ca_file, + sensu_configure { 'puppet': + url => $url, + username => 'admin', + password => $password, + bootstrap => $bootstrap, + bootstrap_password => 'P@ssw0rd!', + trusted_ca_file => $trusted_ca_file, + } } - } diff --git a/spec/acceptance/windows_spec.rb b/spec/acceptance/windows_spec.rb index 049f645939..7d4de8eec7 100644 --- a/spec/acceptance/windows_spec.rb +++ b/spec/acceptance/windows_spec.rb @@ -1,6 +1,37 @@ require 'spec_helper_acceptance_windows' if Gem.win_platform? require 'json' +describe 'sensu::cli class', if: Gem.win_platform? do + context 'default' do + pp = <<-EOS + class { '::sensu': } + class { '::sensu::cli': + install_source => 'https://s3-us-west-2.amazonaws.com/sensu.io/sensu-go/5.14.1/sensu-go_5.14.1_windows_amd64.zip', + url_host => 'localhost', + # Not yet able to run backend in appveyor so configure will not work + configure => false, + } + EOS + + unless RSpec.configuration.skip_apply + it 'creates manifest' do + File.open('C:\manifest-cli.pp', 'w') { |f| f.write(pp) } + puts "C:\manifest-cli.pp" + puts File.read('C:\manifest-cli.pp') + end + + describe command('puppet apply --debug --detailed-exitcodes C:\manifest-cli.pp') do + its(:exit_status) { is_expected.to eq 256 } + end + + it 'has installed sensuctl' do + output = `sensuctl version` + expect(output).to eq(/5\.14\.1/) + end + end + end +end + describe 'sensu::agent class', if: Gem.win_platform? do context 'default' do pp = <<-EOS diff --git a/spec/classes/cli_spec.rb b/spec/classes/cli_spec.rb index b643c3b10f..676839aec2 100644 --- a/spec/classes/cli_spec.rb +++ b/spec/classes/cli_spec.rb @@ -5,6 +5,18 @@ context "on #{os}" do let(:facts) { facts } let(:node) { 'test.example.com' } + let(:install_source_param) do + 'https://s3-us-west-2.amazonaws.com/sensu.io/sensu-go/5.14.1/sensu-go_5.14.1_windows_amd64.zip' + end + let(:default_params) do + if facts[:osfamily] == 'windows' + { :install_source => install_source_param } + else + {} + end + end + let(:params) { default_params } + context 'with default values for all parameters' do # Unknown bug in rspec-puppet fails to compile windows paths # when they are used for file source of sensu_ssl_ca, issue with windows mocking @@ -17,13 +29,41 @@ it { should create_class('sensu::cli') } it { should contain_class('sensu') } - it { - should contain_package('sensu-go-cli').with({ - 'ensure' => 'installed', - 'name' => 'sensu-go-cli', - 'require' => platforms[facts[:osfamily]][:package_require], - }) - } + if facts[:os]['family'] == 'windows' + it { should contain_file('C:\\Program Files\\Sensu').with_ensure('directory') } + it { + should contain_archive('sensu-go-cli.zip').with({ + 'path' => 'C:\\Program Files\\Sensu\\sensu-go-cli.zip', + 'source' => install_source_param, + 'extract' => 'true', + 'extract_path' => 'C:\\Program Files\\Sensu', + 'creates' => 'C:\\Program Files\\Sensu\\sensuctl.exe', + 'cleanup' => 'false', + 'require' => 'File[C:\\Program Files\\Sensu]', + }) + } + it { + should contain_windows_env('sensuctl-path').with({ + 'ensure' => 'present', + 'variable' => 'PATH', + 'value' => 'C:\\Program Files\\Sensu', + 'mergemode' => 'append', + 'require' => 'Archive[sensu-go-cli.zip]', + 'before' => 'Sensu_configure[puppet]', + }) + } + it { should_not contain_package('sensu-go-cli') } + else + it { should_not contain_archive('sensu-go-cli.zip') } + it { should_not contain_windows_env('sensuctl-path') } + it { + should contain_package('sensu-go-cli').with({ + 'ensure' => 'installed', + 'name' => 'sensu-go-cli', + 'require' => platforms[facts[:osfamily]][:package_require], + }) + } + end it { should contain_sensuctl_config('sensu').without_chunk_size } @@ -39,6 +79,25 @@ end + context 'when install_source is puppet URL', if: facts[:osfamily] == 'windows' do + let(:install_source_param) { 'puppet:///sensu-go-cli.zip' } + it { should contain_archive('sensu-go-cli.zip').with_source(install_source_param) } + end + + context 'when install_source is file', if: facts[:osfamily] == 'windows' do + let(:install_source_param) { 'file:\\C:\\sensu-go-cli.zip' } + it { should contain_archive('sensu-go-cli.zip').with_source(install_source_param) } + end + + context 'when install_source is not set' do + let(:params) { {} } + if facts[:osfamily] == 'windows' + it { should compile.and_raise_error(/install_source is required for Windows/) } + else + it { should compile.with_all_deps } + end + end + context 'with use_ssl => false' do let(:pre_condition) do "class { 'sensu': use_ssl => false }" @@ -69,8 +128,8 @@ # https://github.com/rodjek/rspec-puppet/issues/750 if facts[:os]['family'] != 'windows' it { should compile.with_all_deps } + it { should contain_package('sensu-go-cli').without_require } end - it { should contain_package('sensu-go-cli').without_require } end end end diff --git a/spec/unit/provider/sensu_configure/sensuctl_spec.rb b/spec/unit/provider/sensu_configure/sensuctl_spec.rb index 5491ac2294..177aa78292 100644 --- a/spec/unit/provider/sensu_configure/sensuctl_spec.rb +++ b/spec/unit/provider/sensu_configure/sensuctl_spec.rb @@ -33,6 +33,7 @@ end it 'should remove SSL trusted ca' do allow(resource.provider).to receive(:config_path).and_return('/root/.config/sensu/sensuctl/cluster') + allow(File).to receive(:exist?).with('/root/.config/sensu/sensuctl/cluster').and_return(true) expect(resource.provider).to receive(:sensuctl).with(['configure','--non-interactive','--url','http://localhost:8080','--username','admin','--password','foobar']) expect(File).to receive(:delete).with('/root/.config/sensu/sensuctl/cluster') resource.provider.trusted_ca_file = 'absent' diff --git a/spec/unit/provider/sensuctl_spec.rb b/spec/unit/provider/sensuctl_spec.rb index 8ac7fbeb53..0640f3046e 100644 --- a/spec/unit/provider/sensuctl_spec.rb +++ b/spec/unit/provider/sensuctl_spec.rb @@ -7,6 +7,13 @@ context 'config_path' do it 'should return path' do + allow(Dir).to receive(:respond_to?).with(:home).and_return(true) + allow(Dir).to receive(:home).and_return('/root') + expect(subject.config_path).to eq('/root/.config/sensu/sensuctl/cluster') + end + + it 'should return path without Dir.home' do + allow(Dir).to receive(:respond_to?).with(:home).and_return(false) allow(Process).to receive(:uid).and_return(0) user = OpenStruct.new user.dir = '/root' diff --git a/spec/unit/sensuctl_config_spec.rb b/spec/unit/sensuctl_config_spec.rb index 272047576d..394700f11a 100644 --- a/spec/unit/sensuctl_config_spec.rb +++ b/spec/unit/sensuctl_config_spec.rb @@ -29,6 +29,7 @@ # String properties [ + :path, ].each do |property| it "should accept valid #{property}" do config[property] = 'foo' diff --git a/tests/provision_basic_win.ps1 b/tests/provision_basic_win.ps1 index 15351bd77d..bea0240e9f 100644 --- a/tests/provision_basic_win.ps1 +++ b/tests/provision_basic_win.ps1 @@ -41,6 +41,8 @@ $hiera_content | Out-File -FilePath "$hiera_file" -Encoding ascii $env:PATH += ";C:\Program Files\Puppet Labs\Puppet\bin" iex "puppet module install puppetlabs-stdlib" iex "puppet module install puppetlabs-chocolatey" +iex "puppet module install puppet-archive" +iex "puppet module install puppet-windows_env" New-Item -Path "C:\ProgramData\PuppetLabs\puppet\etc\ssl" -ItemType directory -Force | Out-Null Copy-Item -Path "C:\vagrant\tests\ssl\*" -Destination "C:\ProgramData\PuppetLabs\puppet\etc\ssl\" -Recurse -Force diff --git a/tests/sensu-cli.pp b/tests/sensu-cli.pp new file mode 100644 index 0000000000..7343a88abe --- /dev/null +++ b/tests/sensu-cli.pp @@ -0,0 +1,10 @@ +if $facts['os']['family'] == 'windows' { + $install_source = 'https://s3-us-west-2.amazonaws.com/sensu.io/sensu-go/5.14.1/sensu-go_5.14.1_windows_amd64.zip' +} else { + $install_source = undef +} + +class { '::sensu::cli': + install_source => $install_source, + url_host => 'sensu-backend.example.com', +}