diff --git a/.pdkignore b/.pdkignore new file mode 100644 index 00000000..7e3277ab --- /dev/null +++ b/.pdkignore @@ -0,0 +1,52 @@ +# common list used in puppetlabs repos +.git/ +.*.sw[op] +.metadata +.yardoc +.yardwarns +*.iml +/.bundle/ +/.idea/ +/.vagrant/ +/coverage/ +/bin/ +/doc/ +/Gemfile.local +/Gemfile.lock +/junit/ +/log/ +/pkg/ +/spec/fixtures/manifests/ +/spec/fixtures/modules/* +/tmp/ +/vendor/ +/convert_report.txt +/update_report.txt +.DS_Store +.project +.envrc +/inventory.yaml +/spec/fixtures/litmus_inventory.yaml +/.fixtures.yml +/Gemfile +/.gitattributes +/.gitignore +/.pdkignore +/.puppet-lint.rc +/Rakefile +/rakelib/ +/.rspec +/..yml +/.yardopts +/spec/ +/.vscode/ +/.sync.yml +/.devcontainer/ +# OpenStack-specific files +/bindep.txt +/.gitreview +/releasenotes/ +/setup.cfg +/setup.py +/tox.ini +/.zuul.yaml diff --git a/.zuul.yaml b/.zuul.yaml index c8158709..486108d2 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -3,6 +3,5 @@ - puppet-openstack-check-jobs - puppet-openstack-module-unit-jobs - puppet-openstack-integration-jobs-all - - puppet-openstack-beaker-jobs + - puppet-openstack-litmus-jobs - release-notes-jobs-python3 - - tripleo-puppet-standalone diff --git a/Gemfile b/Gemfile index 8c7a257b..3f465614 100644 --- a/Gemfile +++ b/Gemfile @@ -1,14 +1,23 @@ source ENV['GEM_SOURCE'] || "https://rubygems.org" group :development, :test, :system_tests do - if ENV['ZUUL_PROJECT'] == 'openstack/puppet-openstack_spec_helper' - gem 'puppet-openstack_spec_helper', - :path => '../..', + spec_helper_dir = '/home/zuul/src/opendev.org/openstack/puppet-openstack_spec_helper' + if File.directory?(spec_helper_dir) + if ENV['ZUUL_PROJECT'] == 'openstack/puppet-openstack_spec_helper' + gem 'puppet-openstack_spec_helper', + :path => '../..', + :require => 'false' + else + gem 'puppet-openstack_spec_helper', + :path => spec_helper_dir, :require => 'false' + end else + spec_helper_version = ENV['ZUUL_BRANCH'] || "master" gem 'puppet-openstack_spec_helper', - :git => 'https://opendev.org/openstack/puppet-openstack_spec_helper', - :require => 'false' + :git => 'https://opendev.org/openstack/puppet-openstack_spec_helper', + :ref => spec_helper_version, + :require => 'false' end end diff --git a/README.md b/README.md index 7dfafd8b..3ed9c14a 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,10 @@ openstacklib 4. [Usage - The usage of the openstacklib module](#usage) 5. [Implementation - An under-the-hood peek at what the module is doing](#implementation) 6. [Limitations - OS compatibility, etc.](#limitations) -7. [Beaker-Rspec - Beaker-rspec tests for the project](#beaker-rpsec) -8. [Development - Guide for contributing to the module](#development) -9. [Contributors - Those with commits](#contributors) -10. [Release Notes - Release notes for the project](#release-notes) -11. [Repository - The project source code repository](#repository) -12. [Versioning - Notes on the version numbering scheme](#versioning) +6. [Development - Guide for contributing to the module](#development) +7. [Contributors - Those with commits](#contributors) +8. [Release Notes - Release notes for the project](#release-notes) +9. [Repository - The project source code repository](#repository) Overview -------- @@ -297,18 +295,6 @@ handle database migrations, it is common to set up refresh relationships between openstacklib::db::postgresql resource and the database sync exec resource. Relying on this behavior may cause errors. -Beaker-Rspec ------------- - -This module has beaker-rspec tests - -To run: - -```shell -bundle install -bundle exec rspec spec/acceptance -``` - Development ----------- @@ -319,7 +305,7 @@ Developer documentation for the entire puppet-openstack project. Contributors ------------ -* https://github.com/stackforge/puppet-openstacklib/graphs/contributors +* https://github.com/openstack/puppet-openstacklib/graphs/contributors Release Notes ------------- @@ -330,19 +316,3 @@ Repository ---------- * https://opendev.org/openstack/puppet-openstacklib - -Versioning ----------- - -This module has been given version 5 to track the puppet-openstack modules. The -versioning for the puppet-openstack modules are as follows: - -``` -Puppet Module :: OpenStack Version :: OpenStack Codename -2.0.0 -> 2013.1.0 -> Grizzly -3.0.0 -> 2013.2.0 -> Havana -4.0.0 -> 2014.1.0 -> Icehouse -5.0.0 -> 2014.2.0 -> Juno -6.0.0 -> 2015.1.0 -> Kilo -7.0.0 -> 2015.2.0 -> Liberty -``` diff --git a/doc/requirements.txt b/doc/requirements.txt index 1d9bc5a6..592fa6b5 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,7 +1,6 @@ # This is required for the docs build jobs -sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD -sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2;python_version>='3.4' # BSD -openstackdocstheme>=1.20.0 # Apache-2.0 +sphinx>=3.5.1 # BSD +openstackdocstheme>=2.2.7 # Apache-2.0 # This is required for the releasenotes build jobs -reno # Apache-2.0 +reno>=3.2.0 # Apache-2.0 diff --git a/examples/virtual_packages.pp b/examples/virtual_packages.pp index 2686a562..e43af67f 100644 --- a/examples/virtual_packages.pp +++ b/examples/virtual_packages.pp @@ -1,8 +1,8 @@ Exec { logoutput => 'on_failure' } -include ::openstacklib::defaults +include openstacklib::defaults -if $::osfamily == 'RedHat' { +if $facts['os']['family'] == 'RedHat' { # Virtual package name, present in @base. package { 'perl(Net::HTTP)': ensure => present, diff --git a/lib/facter/os_package_type.rb b/lib/facter/os_package_type.rb deleted file mode 100644 index 134509ea..00000000 --- a/lib/facter/os_package_type.rb +++ /dev/null @@ -1,38 +0,0 @@ -# -# We need this to be able to make decision of what style of package we are -# working with: Debian style package (for example, it uses a nova-consoleproxy -# package and not nova-novncproxy, or it has a openstack-dashboard-apache, -# etc.), or just the Ubuntu style package. -# -# This is needed, because in some cases, we are using the Debian style packages -# but running under Ubuntu. For example, that's the case when running with MOS -# over Ubuntu. For this case, a manual override is provided, in the form of a -# /etc/facter/facts.d/os_package_type.txt containing: -# os_package_type=debian -# -# In all other cases, we can consider that we're using vanilia (ie: unmodified) -# distribution packages, and we can set $::os_package_type depending on the -# value of $::operatingsystem. -# -# Having the below snipets helps simplifying checks within individual project -# manifests, so that we can just reuse $::os_package_type directly without -# having to also check if it contains a value, then check for the content of -# $::operatingsystem (ie: what's below factors the check once and for all). -Facter.add('os_package_type') do - setcode do - case Facter.value(:osfamily) - when 'Debian' - if Facter.value(:operatingsystem) == 'Debian' then - 'debian' - else - 'ubuntu' - end - when 'RedHat' - 'rpm' - when 'Solaris' - 'solaris' - else - 'unknown' - end - end -end diff --git a/lib/facter/os_workers.rb b/lib/facter/os_workers.rb index b48b71be..d6878399 100644 --- a/lib/facter/os_workers.rb +++ b/lib/facter/os_workers.rb @@ -1,5 +1,5 @@ # -# We've found that using $::processorcount for workers/threads can lead to +# We've found that using $facts['processors']['count] for workers/threads can lead to # unexpected memory or process counts for people deploying on baremetal or # if they have large number of cpus. This fact allows us to tweak the formula # used to determine number of workers in a single place but use it across all @@ -11,17 +11,26 @@ # This fact can be overloaded by an external fact from /etc/factor/facts.d if # a user would like to provide their own default value. # +# +def get_proc_count + procs = Facter.value('processors') + if procs.key?('count') then + procs['count'].to_i + else + 1 + end +end + Facter.add(:os_workers_small) do has_weight 100 setcode do - processors = Facter.value('processorcount') - [ [ (processors.to_i / 4), 2 ].max, 8 ].min + [ [ (get_proc_count / 4), 2 ].max, 8 ].min end end # # The value above for os_workers performs 3x worse in many cases compared to -# the prevuous default of $::processorcount. +# the previous default of $facts['processors']['count']. # # Based on performance data [1], the following calculation is within 1-2%. # @@ -33,8 +42,7 @@ Facter.add(:os_workers) do has_weight 100 setcode do - processors = Facter.value('processorcount') - [ [ (processors.to_i / 2), 2 ].max, 12 ].min + [ [ (get_proc_count / 2), 2 ].max, 12 ].min end end @@ -44,8 +52,7 @@ Facter.add(:os_workers_large) do has_weight 100 setcode do - processors = Facter.value('processorcount') - [ (processors.to_i / 2), 1 ].max + [ (get_proc_count / 2), 1 ].max end end @@ -57,7 +64,18 @@ Facter.add(:os_workers_heat_engine) do has_weight 100 setcode do - processors = Facter.value('processorcount') - [ [ (processors.to_i / 2), 4 ].max, 24 ].min + [ [ (get_proc_count / 2), 4 ].max, 24 ].min + end +end + +# +# Since we have merged keystone admin and keystone public into a single +# keystone instance, we need doubled workers to have the same number +# of workers in total to avoid performance degradation. +# +Facter.add(:os_workers_keystone) do + has_weight 100 + setcode do + [ [ get_proc_count, 4 ].max, 24 ].min end end diff --git a/lib/puppet/functions/inet6_prefix.rb b/lib/puppet/functions/inet6_prefix.rb index 6af2ceeb..a6b9c525 100644 --- a/lib/puppet/functions/inet6_prefix.rb +++ b/lib/puppet/functions/inet6_prefix.rb @@ -20,7 +20,7 @@ def inet6_prefix(*args) ip = "inet6:[#{ip_parts.shift}]#{ip_parts.join}" end end - rescue IPAddr::AddressFamilyError, IPAddr::Error, IPAddr::InvalidAddressError, IPAddr::InvalidPrefixError, ArgumentError, NoMethodError => e + rescue ArgumentError, NoMethodError => e # ignore it end result << ip diff --git a/lib/puppet/functions/os_database_connection.rb b/lib/puppet/functions/os_database_connection.rb index f63c70b1..4a3fc6a9 100644 --- a/lib/puppet/functions/os_database_connection.rb +++ b/lib/puppet/functions/os_database_connection.rb @@ -1,6 +1,6 @@ Puppet::Functions.create_function(:os_database_connection) do def os_database_connection(*args) - require 'uri' + require 'erb' if (args.size != 1) then raise(Puppet::ParseError, "os_database_connection(): Wrong number of arguments " + @@ -44,23 +44,23 @@ def os_database_connection(*args) end if v.include?('username') and (v['username'] != :undef) and (v['username'].to_s != '') - parts[:userinfo] = URI.escape(v['username']) + parts[:userinfo] = ERB::Util.url_encode(v['username']) if v.include?('password') and (v['password'] != :undef) and (v['password'].to_s != '') - parts[:userinfo] += ":#{URI.escape(v['password'])}" + parts[:userinfo] += ":#{ERB::Util.url_encode(v['password'])}" end end # support previous charset option on the function. Setting charset will # override charset if passed in via the extra parameters + extra = {} + if v.include?('extra') + extra.merge!(v['extra']) + end if v.include?('charset') - if v.include?('extra') - v['extra'].merge!({ 'charset' => v['charset'] }) - else - v['extra'] = { 'charset' => v['charset'] } - end + extra.merge!({ 'charset' => v['charset'] }) end - parts[:query] = v['extra'].map{ |k,v| "#{k}=#{v}" }.join('&') if v.include?('extra') + parts[:query] = extra.map{ |k,v| "#{k}=#{v}" }.join('&') if ! extra.empty? parts[:scheme] = v['dialect'] diff --git a/lib/puppet/functions/os_transport_url.rb b/lib/puppet/functions/os_transport_url.rb index b8f90b03..f0f65462 100644 --- a/lib/puppet/functions/os_transport_url.rb +++ b/lib/puppet/functions/os_transport_url.rb @@ -51,51 +51,29 @@ # Generates: # rabbit://username:password@1.1.1.1:5672,username:password@2.2.2.2:5672/virtual_host?key=value Puppet::Functions.create_function(:os_transport_url) do - # TODO(tobias-urdin): Rework and remove this. - # Taken straight from stdlib v5.1.0 module. - def _str2bool(string) - if !!string == string - return string - end - unless string.is_a?(String) - raise(Puppet::ParseError, 'str2bool(): Requires string to work with') - end - result = case string - when %r{^$}, '' then false # Empty string will be false ... - when %r{^(1|t|y|true|yes)$}i then true - when %r{^(0|f|n|false|no)$}i then false - when %r{^(undef|undefined)$} then false # This is not likely to happen ... - else - raise(Puppet::ParseError, 'os_transport_url _str2bool(): Unknown type of boolean given') - end - return result - end - - # TODO(tobias-urdin): Rework and remove this. - # Taken straight from stdlib v5.1.0 module. - def _bool2num(val) - value = _str2bool(val) - result = value ? 1 : 0 - return result - end def os_transport_url(*args) - require 'uri' + require 'erb' unless args.size == 1 raise(ArgumentError, 'os_transport_url(): Wrong number of arguments') end - v = args[0] - klass = v.class + v_raw = args[0] + klass = v_raw.class unless klass == Hash raise(Puppet::ParseError, "os_transport_url(): Requires an hash, got #{klass}") end + v = {} # type checking for the parameter hash - v.keys.each do |key| - v[key] = v[key].to_s if key == 'port' + v_raw.keys.each do |key| + if key == 'port' + v[key] = v_raw[key].to_s + else + v[key] = v_raw[key] + end klass = (key == 'hosts') ? Array : String klass = (key == 'query') ? Hash : klass unless (v[key].class == klass) or (v[key] == :undef) @@ -125,9 +103,9 @@ def os_transport_url(*args) parts[:transport] = v['transport'] if v.include?('username') and (v['username'] != :undef) and (v['username'].to_s != '') - parts[:userinfo] = URI.escape(v['username']) + parts[:userinfo] = ERB::Util.url_encode(v['username']) if v.include?('password') and (v['password'] != :undef) and (v['password'].to_s != '') - parts[:userinfo] += ":#{URI.escape(v['password'])}" + parts[:userinfo] += ":#{ERB::Util.url_encode(v['password'])}" end end @@ -156,6 +134,11 @@ def os_transport_url(*args) parts[:path] = "/#{v['virtual_host']}" if v.include?('virtual_host') + query = {} + if v.include?('query') + query.merge!(v['query']) + end + # support previous ssl option on the function. Setting ssl will # override ssl if passed in via the query parameters if v.include?('ssl') @@ -164,19 +147,14 @@ def os_transport_url(*args) # so we rely on _str2bool and _bool2num to ensure it's in the # format # TODO(tobias-urdin): Rework this to using proper data types and not the - # legacy puppet functions that is copied into this function statement. - # We need to do this right now because it fails testing if we call the - # str2bool or bool2num legacy functions using call_function. - ssl_str = _str2bool(v['ssl']) - ssl_val = _bool2num(v['ssl']) - if v.include?('query') - v['query'].merge!({ 'ssl' => ssl_val }) - else - v['query'] = { 'ssl' => ssl_val } - end + # legacy puppet functions. + ssl_str = call_function('str2bool', v['ssl']) + ssl_val = call_function('bool2num', v['ssl']) + + query.merge!({ 'ssl' => ssl_val }) end - parts[:query] = v['query'].map{ |k,val| "#{k}=#{val}" }.join('&') if v.include?('query') + parts[:query] = query.map{ |k,val| "#{k}=#{val}" }.join('&') if ! query.empty? url_parts = [] url_parts << parts[:transport] diff --git a/lib/puppet/functions/os_url.rb b/lib/puppet/functions/os_url.rb new file mode 100644 index 00000000..040cb39c --- /dev/null +++ b/lib/puppet/functions/os_url.rb @@ -0,0 +1,66 @@ +Puppet::Functions.create_function(:os_url) do + def os_url(*args) + require 'erb' + + if (args.size != 1) then + raise(Puppet::ParseError, "os_url(): Wrong number of arguments " + + "given (#{args.size} for 1)") + end + + v = args[0] + klass = v.class + + unless klass == Hash + raise(Puppet::ParseError, "os_url(): Requires an hash, got #{klass}") + end + + v.keys.each do |key| + klass = (key == 'query') ? Hash : String + unless (v[key].class == klass) or (v[key] == :undef) + raise(Puppet::ParseError, "os_url(): #{key} should be a #{klass}") + end + end + + parts = {} + + if v.include?('scheme') + parts[:scheme] = v['scheme'] + else + parts[:scheme] = 'http' + end + + if v.include?('host') + parts[:host] = v['host'] + end + + if v.include?('port') + if v.include?('host') + parts[:port] = v['port'].to_i + else + raise(Puppet::ParseError, 'os_url(): host is required with port') + end + end + + if v.include?('path') + parts[:path] = v['path'] + end + + userinfo = '' + if v.include?('username') and (v['username'] != :undef) and (v['username'].to_s != '') + userinfo = ERB::Util.url_encode(v['username']) + end + if v.include?('password') and (v['password'] != :undef) and (v['password'].to_s != '') + userinfo += ":#{ERB::Util.url_encode(v['password'])}" + end + + if userinfo != '' + parts[:userinfo] = userinfo + end + + if v.include?('query') and ! v['query'].empty? + parts[:query] = v['query'].map{ |k,v| "#{k}=#{v}" }.join('&') + end + + URI::Generic.build(parts).to_s + end +end diff --git a/lib/puppet/provider/openstack.rb b/lib/puppet/provider/openstack.rb index 3d80d683..b67f54d3 100644 --- a/lib/puppet/provider/openstack.rb +++ b/lib/puppet/provider/openstack.rb @@ -14,11 +14,9 @@ class Puppet::Provider::Openstack < Puppet::Provider commands :openstack_command => 'openstack' @@no_retry_actions = %w(create remove delete) - @@command_timeout = 40 - # Fails on the 5th retry for a max of 212s (~3.5min) before total - # failure. - @@request_timeout = 170 - @@retry_sleep = 3 + @@command_timeout = 90 + @@request_timeout = 300 + @@retry_sleep = 10 class << self [:no_retry_actions, :request_timeout, :retry_sleep].each do |m| define_method m do @@ -40,6 +38,12 @@ def self.command_timeout(action=nil) self.class_variable_get("@@command_timeout") end + # redact sensitive information in exception and raise + def self.redact_and_raise(e) + new_message = e.message.gsub(/\-\-password\ [\w]+/, "--password [redacted secret]") + raise e.class, new_message + end + # with command_timeout def self.openstack(*args) begin @@ -48,7 +52,10 @@ def self.openstack(*args) execute([command(:openstack_command)] + args, override_locale: false, failonfail: true, combine: true) end rescue Timeout::Error - raise Puppet::ExecutionFailure, "Command: 'openstack #{args.inspect}' has been running for more than #{command_timeout(action)} seconds" + e = Puppet::ExecutionFailure.new "Command: 'openstack #{args.inspect}' has been running for more than #{command_timeout(action)} seconds" + redact_and_raise(e) + rescue Puppet::ExecutionFailure => e + redact_and_raise(e) end end @@ -69,6 +76,23 @@ def self.request_without_retry(&block) rc end + # Copy of Puppet::Util::withenv but that filters out + # env variables starting with OS_ from the existing + # environment. + # + # @param hash [Hash] Hash of environment variables + def self.os_withenv(hash) + saved = ENV.to_hash + begin + cleaned_env = ENV.to_hash.reject { |k, _| k.start_with?('OS_') } + ENV.replace(cleaned_env) + ENV.merge!(hash.transform_keys(&:to_s)) + yield + ensure + ENV.replace(saved) + end + end + # Returns an array of hashes, where the keys are the downcased CSV headers # with underscores instead of spaces # @@ -78,10 +102,10 @@ def self.request(service, action, properties, credentials=nil, options={}) env = credentials ? credentials.to_env : {} no_retry = options[:no_retry_exception_msgs] - Puppet::Util.withenv(env) do + os_withenv(env) do rv = nil - end_time = current_time + request_timeout start_time = current_time + end_time = start_time + request_timeout retry_count = 0 loop do begin @@ -120,9 +144,10 @@ def self.request(service, action, properties, credentials=nil, options={}) rescue Puppet::ExecutionFailure => exception raise Puppet::Error::OpenstackUnauthorizedError, 'Could not authenticate' if exception.message =~ /HTTP 40[13]/ raise Puppet::Error::OpenstackUnauthorizedError, 'Could not authenticate' if exception.message.match(/Missing value \S* required for auth plugin/) - if current_time > end_time + remaining_time = end_time - current_time + if remaining_time < 0 error_message = exception.message - error_message += " (tried #{retry_count}, for a total of #{end_time - start_time } seconds)" + error_message += " (tried #{retry_count}, for a total of #{end_time - start_time} seconds)" raise(Puppet::ExecutionFailure, error_message) end @@ -133,7 +158,7 @@ def self.request(service, action, properties, credentials=nil, options={}) raise exception if exception.message.match(nr) end end - debug "Non-fatal error: '#{exception.message}'. Retrying for #{end_time - current_time} more seconds" + debug "Non-fatal error: '#{exception.message}'. Retrying for #{remaining_time} more seconds" sleep retry_sleep retry_count += 1 retry @@ -151,4 +176,12 @@ def self.parse_csv(text) text = text.split("\n").drop_while { |line| line !~ /^\".*\"/ }.join("\n") return CSV.parse(text + "\n") end + + def self.parse_python_dict(text) + return JSON.parse(text.gsub(/'/, '"').gsub(/: False([,}])/,': false\1').gsub(/: True([,}])/,': true\1')) + end + + def self.parse_python_list(text) + return JSON.parse(text.gsub(/'/, '"')) + end end diff --git a/lib/puppet/provider/openstack/auth.rb b/lib/puppet/provider/openstack/auth.rb index 743071db..c5cb7717 100644 --- a/lib/puppet/provider/openstack/auth.rb +++ b/lib/puppet/provider/openstack/auth.rb @@ -3,7 +3,12 @@ module Puppet::Provider::Openstack::Auth - RCFILENAME = "#{ENV['HOME']}/openrc" + CLOUDSFILENAMES = [ + # This allows overrides by users + "/etc/openstack/puppet/clouds.yaml", + # This is created by puppet-keystone + "/etc/openstack/puppet/admin-clouds.yaml", + ] def get_os_vars_from_env env = {} @@ -11,35 +16,38 @@ def get_os_vars_from_env return env end - def get_os_vars_from_rcfile(filename) - env = {} - rcfile = [filename, '/root/openrc'].detect { |f| File.exists? f } - unless rcfile.nil? - File.open(rcfile).readlines.delete_if{|l| l=~ /^#|^$/ }.each do |line| - # we only care about the OS_ vars from the file LP#1699950 - if line =~ /OS_/ - key, value = line.split('=') - key = key.split(' ').last - value = value.chomp.gsub(/'/, '') - env.merge!(key => value) if key =~ /OS_/ - end - end + def get_os_vars_from_cloudsfile(scope) + cloudsfile = clouds_filenames.detect { |f| File.exist? f} + unless cloudsfile.nil? + { + 'OS_CLOUD' => scope, + 'OS_CLIENT_CONFIG_FILE' => cloudsfile + } + else + {} end - return env end - def rc_filename - RCFILENAME + def clouds_filenames + CLOUDSFILENAMES end - def request(service, action, properties=nil, options={}) + def request(service, action, properties=nil, options={}, scope='project') properties ||= [] + + # First, check environments set_credentials(@credentials, get_os_vars_from_env) - unless @credentials.set? + + unless @credentials.set? and (!@credentials.scope_set? or @credentials.scope == scope) + # Then look for clouds.yaml @credentials.unset - set_credentials(@credentials, get_os_vars_from_rcfile(rc_filename)) + clouds_env = get_os_vars_from_cloudsfile(scope) + if ! clouds_env.empty? + set_credentials(@credentials, clouds_env) + end end - unless @credentials.set? + + unless @credentials.set? and (!@credentials.scope_set? or @credentials.scope == scope) raise(Puppet::Error::OpenstackAuthInputError, 'Insufficient credentials to authenticate') end super(service, action, properties, @credentials, options) diff --git a/lib/puppet/provider/openstack/credentials.rb b/lib/puppet/provider/openstack/credentials.rb index afade9c2..0260af63 100644 --- a/lib/puppet/provider/openstack/credentials.rb +++ b/lib/puppet/provider/openstack/credentials.rb @@ -35,20 +35,37 @@ def to_env env = {} self.instance_variables.each do |var| name = var.to_s.sub(/^@/,'OS_').upcase - env.merge!(name => self.instance_variable_get(var)) + value = self.instance_variable_get(var) + unless value.nil? + env.merge!(name => value) + end end env end + def scope_set? + @project_name + end + + def scope + if @project_name + return 'project' + else + # When only service token is used, there is not way to determine + # the scope unless we inspect the token using keystone API call. + return nil + end + end + def user_password_set? return true if @username && @password && @project_name && @auth_url end def unset - KEYS.each do |key| - if key != :identity_api_version && - self.instance_variable_defined?("@#{key}") - set(key, '') + self.instance_variables.each do |var| + if var.to_s != '@identity_api_version' && + self.instance_variable_defined?(var.to_s) + set(var.to_s.sub(/^@/,''), nil) end end end @@ -70,10 +87,13 @@ class Puppet::Provider::Openstack::CredentialsV3 < Puppet::Provider::Openstack:: :project_domain_id, :project_domain_name, :project_id, + :system_scope, :trust_id, :user_domain_id, :user_domain_name, - :user_id + :user_id, + :cloud, + :client_config_file, ] KEYS.each { |var| attr_accessor var } @@ -82,8 +102,30 @@ def self.defined?(name) KEYS.include?(name.to_sym) || super end + def user_set? + @username || @user_id + end + + def scope_set? + @system_scope || @domain_name || @domain_id || @project_name || @project_id + end + + def scope + if @project_name || @project_id + return 'project' + elsif @domain_name || @domain_id + return 'domain' + elsif @system_scope + return 'system' + else + # When clouds.yaml is used, parameters are not directly passed to puppet + # so the scope can't be detected. + return nil + end + end + def user_password_set? - return true if (@username || @user_id) && @password && (@project_name || @project_id) && @auth_url + return true if (user_set? && @password && scope_set? && @auth_url) || @cloud end def initialize diff --git a/lib/puppet/provider/openstack_config/ini_setting.rb b/lib/puppet/provider/openstack_config/ini_setting.rb index da925a44..2d9f6e84 100644 --- a/lib/puppet/provider/openstack_config/ini_setting.rb +++ b/lib/puppet/provider/openstack_config/ini_setting.rb @@ -20,7 +20,10 @@ def exists? end def create - resource[:value] = transform(:to, resource[:value]) + new_value = transform(:to, resource[:value]) + if resource[:value] != new_value + resource[:value] = new_value + end super end @@ -60,7 +63,11 @@ def transform_to=(value) end def separator - '=' + if resource.class.validattr?(:key_val_separator) + resource[:key_val_separator] || '=' + else + '=' + end end def file_path diff --git a/lib/puppet/provider/policy_rcd/policy_rcd.rb b/lib/puppet/provider/policy_rcd/policy_rcd.rb index c753be6f..35f57617 100644 --- a/lib/puppet/provider/policy_rcd/policy_rcd.rb +++ b/lib/puppet/provider/policy_rcd/policy_rcd.rb @@ -5,7 +5,7 @@ mk_resource_methods def check_os - Facter.value(:osfamily) == 'Debian' + Facter.value(:os)['family'] == 'Debian' end def check_policy_rcd diff --git a/manifests/clouds.pp b/manifests/clouds.pp new file mode 100644 index 00000000..08adfbd5 --- /dev/null +++ b/manifests/clouds.pp @@ -0,0 +1,85 @@ +# == Class: openstacklib::clouds +# +# Generates clouds.yaml for openstack CLI +# +# == Parameters +# +# [*username*] +# (Required) The name of the keystone user. +# +# [*password*] +# (Required) Password of the keystone user. +# +# [*auth_url*] +# (Required) The URL to use for authentication. +# +# [*path*] +# (Optional) Path to the clouds.yaml file. +# Defaults to $name +# +# [*user_domain_name*] +# (Optional) Name of domain for $username. +# Defaults to 'Default' +# +# [*project_name*] +# (Optional) The name of the keystone project. +# Defaults to undef +# +# [*project_domain_name*] +# (Optional) Name of domain for $project_name. +# Defaults to 'Default' +# +# [*system_scope*] +# (Optional) Scope for system operations. +# Defaults to undef +# +# [*interface*] +# (Optional) Determine the endpoint to be used. +# Defaults to undef +# +# [*region_name*] +# (Optional) The region in which the service can be found. +# Defaults to undef +# +# [*api_versions*] +# (Optional) Hash of service type and version to determine API version +# for that service to use. +# Example: { 'identity' => '3', 'compute' => '2.latest' } +# Defaults to {} +# +# [*file_user*] +# (Optional) User that owns the clouds.yaml file. +# Defaults to 'root'. +# +# [*file_group*] +# (Optional) Group that owns the clouds.yaml file. +# Defaults to 'root'. +# +define openstacklib::clouds ( + String[1] $username, + String[1] $password, + Stdlib::HTTPUrl $auth_url, + Stdlib::Absolutepath $path = $name, + String[1] $user_domain_name = 'Default', + Optional[String[1]] $project_name = undef, + String[1] $project_domain_name = 'Default', + Optional[String[1]] $system_scope = undef, + Optional[Enum['public', 'internal', 'admin']] $interface = undef, + Optional[String[1]] $region_name = undef, + Hash $api_versions = {}, + String $file_user = 'root', + String $file_group = 'root', +) { + if !$project_name and !$system_scope { + fail('One of project_name and system_scope should be set') + } + + file { $path: + ensure => file, + mode => '0600', + owner => $file_user, + group => $file_group, + content => template('openstacklib/clouds.yaml.erb'), + show_diff => false, + } +} diff --git a/manifests/db/mysql.pp b/manifests/db/mysql.pp index 83f9e5c1..41ce20bd 100644 --- a/manifests/db/mysql.pp +++ b/manifests/db/mysql.pp @@ -4,10 +4,14 @@ # # == Parameters: # -# [*password_hash*] -# Password hash to use for the database user for this service; +# [*password*] +# Password to use for the database user for this service; # string; required # +# [*plugin*] +# Authentication plugin to use when connecting to the MySQL server; +# string; optional; default to 'undef' +# # [*dbname*] # The name of the database # string; optional; default to the $title of the resource, i.e. 'nova' @@ -50,22 +54,40 @@ # The TLS options that the user will have # Defaults to ['NONE'] # +# DEPRECATED PARAMETERS +# +# [*password_hash*] +# Password hash to use for the database user for this service; +# string; optional; default to undef +# define openstacklib::db::mysql ( - $password_hash, - $dbname = $title, - $user = $title, - $host = '127.0.0.1', - $charset = 'utf8', - $collate = 'utf8_general_ci', - $allowed_hosts = [], - $privileges = 'ALL', - $create_user = true, - $create_grant = true, - $tls_options = ['NONE'], + Optional[String[1]] $password = undef, + Optional[String[1]] $plugin = undef, + String[1] $dbname = $title, + String[1] $user = $title, + String[1] $host = '127.0.0.1', + String[1] $charset = 'utf8', + String[1] $collate = 'utf8_general_ci', + Variant[String[1], Array[String[1]]] $allowed_hosts = [], + Variant[String[1], Array[String[1]]] $privileges = 'ALL', + Boolean $create_user = true, + Boolean $create_grant = true, + Variant[String[1], Array[String[1]]] $tls_options = ['NONE'], + # DEPRECATED PARAMETER + Optional[String[1]] $password_hash = undef, ) { + include mysql::server + include mysql::client - include ::mysql::server - include ::mysql::client + if $password_hash != undef { + warning("The password_hash parameter was deprecated and will be removed \ +in a future release. Use password instead") + $password_hash_real = $password_hash + } elsif $password != undef { + $password_hash_real = mysql::password($password) + } else { + fail('password should be set') + } mysql_database { $dbname: ensure => present, @@ -82,7 +104,8 @@ openstacklib::db::mysql::host_access { $real_allowed_hosts: user => $user, - password_hash => $password_hash, + plugin => $plugin, + password_hash => $password_hash_real, database => $dbname, privileges => $privileges, create_user => $create_user, diff --git a/manifests/db/mysql/host_access.pp b/manifests/db/mysql/host_access.pp index af3b63ea..6d8cf3a6 100644 --- a/manifests/db/mysql/host_access.pp +++ b/manifests/db/mysql/host_access.pp @@ -17,6 +17,10 @@ # [*privileges*] # the privileges to grant to this user # +# [*plugin*] +# Authentication plugin to use when connecting to the MySQL server; +# Defaults to undef +# # [*create_user*] # Flag to allow for the skipping of the user as part of the database setup. # Set to false to skip the user creation. @@ -32,22 +36,24 @@ # Defaults to ['NONE'] # define openstacklib::db::mysql::host_access ( - $user, - $password_hash, - $database, - $privileges, - $create_user = true, - $create_grant = true, - $tls_options = ['NONE'], + String[1] $user, + String[1] $password_hash, + String[1] $database, + Variant[String[1], Array[String[1]]] $privileges, + Optional[String[1]] $plugin = undef, + Boolean $create_user = true, + Boolean $create_grant = true, + Variant[String[1], Array[String[1]]] $tls_options = ['NONE'], ) { - - validate_legacy(Pattern[/_/], 'validate_re', $title, - ['_', 'Title must be $dbname_$host']) + if ! ($title =~ /_/) { + fail('Title must be $dbname_$host') + } $host = inline_template('<%= @title.split("_").last.downcase %>') if $create_user { mysql_user { "${user}@${host}": + plugin => $plugin, password_hash => $password_hash, tls_options => $tls_options, } diff --git a/manifests/db/postgresql.pp b/manifests/db/postgresql.pp index 01358040..56d02929 100644 --- a/manifests/db/postgresql.pp +++ b/manifests/db/postgresql.pp @@ -1,11 +1,12 @@ # == Definition: openstacklib::db::postgresql # +# DPERECATED !! # This resource configures a postgresql database for an OpenStack service # # == Parameters: # -# [*password_hash*] -# Password hash to use for the database user for this service; +# [*password*] +# Password to use for the database user for this service; # string; required # # [*dbname*] @@ -23,24 +24,38 @@ # [*privileges*] # Privileges given to the database user; # string or array of strings; optional; default to 'ALL' - +# +# DEPRECATED PARAMETERS +# +# [*password_hash*] +# Password hash to use for the database user for this service; +# string; required +# define openstacklib::db::postgresql ( - $password_hash, - $dbname = $title, - $user = $title, - $encoding = undef, - $privileges = 'ALL', -){ + Optional[String[1]] $password = undef, + String[1] $dbname = $title, + String[1] $user = $title, + Optional[String[1]] $encoding = undef, + Variant[String[1], Array[String[1]]] $privileges = 'ALL', + # DEPRECATED PARAMETERS + Optional[String[1]] $password_hash = undef, +) { + warning("Support for PostgreSQL has been deprecated and will be removed in \ +a future release") - if ((($::operatingsystem == 'RedHat' or $::operatingsystem == 'CentOS') and (versioncmp($::operatingsystemmajrelease, '6') <= 0)) - or ($::operatingsystem == 'Fedora' and (versioncmp($::operatingsystemmajrelease, '14') <= 0))) { - warning("The system packages handling the postgresql infrastructure for OpenStack \ -are out of date and should not be relied on for database migrations.") + if $password_hash != undef { + warning('The password_hash parameter was deprecated and will be removed +in a future release. Use password instead') + $password_hash_real = $password_hash + } elsif $password != undef { + $password_hash_real = postgresql::postgresql_password($user, $password) + } else { + fail('password should be set') } postgresql::server::db { $dbname: user => $user, - password => $password_hash, + password => $password_hash_real, encoding => $encoding, grant => $privileges, } diff --git a/manifests/defaults.pp b/manifests/defaults.pp index f1e471ff..1b69968f 100644 --- a/manifests/defaults.pp +++ b/manifests/defaults.pp @@ -5,23 +5,18 @@ # This file is loaded in the params.pp of each class. # class openstacklib::defaults { - - # TODO(tobias-urdin): Remove this in the V release when - # we officially remove the support. - if versioncmp($::puppetversion, '6.0.0') < 0 { - warning('OpenStack modules support for Puppet 5 is deprecated \ -and will be officially unsupported in the V release') - } - - if ($::os['family'] == 'Debian') { - $pyvers = '3' - $pyver3 = '3' - } elsif ($::os['name'] == 'Fedora') or - ($::os['family'] == 'RedHat' and Integer.new($::os['release']['major']) > 7) { - $pyvers = '3' - $pyver3 = '3.6' - } else { - $pyvers = '' - $pyver3 = '2.7' + case $facts['os']['family'] { + 'RedHat': { + $pyver3 = $facts['os']['release']['major'] ? { + '10' => '3.12', + default => '3.9', + } + } + 'Debian': { + $pyver3 = '3' + } + default:{ + fail("Unsupported osfamily: ${facts['os']['family']}") + } } } diff --git a/manifests/iscsid.pp b/manifests/iscsid.pp new file mode 100644 index 00000000..5b31ab29 --- /dev/null +++ b/manifests/iscsid.pp @@ -0,0 +1,57 @@ +# == Class: openstacklib::iscsid +# +# Installs and configures the iscsid daemon +# +# == Parameters +# +# [*enabled*] +# (optional) Should the service be enabled. +# Defaults to true. +# +# [*manage_service*] +# (optional) Whether the service should be managed by Puppet. +# Defaults to true. +# +# [*package_ensure*] +# (optional) ensure state for package. +# Defaults to 'present' +# +class openstacklib::iscsid ( + Boolean $enabled = true, + Boolean $manage_service = true, + Stdlib::Ensure::Package $package_ensure = 'present' +) { + include openstacklib::params + + package { 'open-iscsi': + ensure => $package_ensure, + name => $openstacklib::params::open_iscsi_package_name, + tag => 'openstack', + } + + # In CentOS9/RHEL9 initiatorname.iscsi is not created automatically + # so should be created + exec { 'create-initiatorname-file': + command => 'echo "InitiatorName=`/usr/sbin/iscsi-iname`" > /etc/iscsi/initiatorname.iscsi', + path => ['/usr/bin','/usr/sbin','/bin','/usr/bin'], + creates => '/etc/iscsi/initiatorname.iscsi', + require => Package['open-iscsi'], + } + + if $manage_service { + if $enabled { + $service_ensure = 'running' + } else { + $service_ensure = 'stopped' + } + + # iscsid service is started automatically when iscsiadm command is + # executed but there is no harm even if the service is already started. + service { 'iscsid': + ensure => $service_ensure, + enable => $enabled, + } + Package['open-iscsi'] ~> Service['iscsid'] + Exec['create-initiatorname-file'] ~> Service['iscsid'] + } +} diff --git a/manifests/messaging/rabbitmq.pp b/manifests/messaging/rabbitmq.pp index 16f6c62a..0d3335f9 100644 --- a/manifests/messaging/rabbitmq.pp +++ b/manifests/messaging/rabbitmq.pp @@ -61,19 +61,18 @@ # (optional) Manage or not the vhost # Defaults to true # -define openstacklib::messaging::rabbitmq( - $userid = 'guest', - $password = 'guest', - $virtual_host = '/', - $is_admin = false, - $configure_permission = '.*', - $write_permission = '.*', - $read_permission = '.*', - $manage_user = true, - $manage_user_permissions = true, - $manage_vhost = true, +define openstacklib::messaging::rabbitmq ( + String[1] $userid = 'guest', + String[1] $password = 'guest', + String[1] $virtual_host = '/', + Boolean $is_admin = false, + String $configure_permission = '.*', + String $write_permission = '.*', + String $read_permission = '.*', + Boolean $manage_user = true, + Boolean $manage_user_permissions = true, + Boolean $manage_vhost = true, ) { - if $manage_user { if $userid == 'guest' { $is_admin_real = false @@ -101,5 +100,4 @@ 'provider' => 'rabbitmqctl', }) } - } diff --git a/manifests/openstackclient.pp b/manifests/openstackclient.pp index ade510d2..5d84d7b4 100644 --- a/manifests/openstackclient.pp +++ b/manifests/openstackclient.pp @@ -10,15 +10,14 @@ # # [*package_name*] # (Optional) The name of the package to install -# Defaults to $::openstacklib::params::openstackclient_package_name +# Defaults to $openstacklib::params::openstackclient_package_name # -class openstacklib::openstackclient( - $package_name = $::openstacklib::params::openstackclient_package_name, - $package_ensure = 'present', -) inherits ::openstacklib::params { - - ensure_packages($package_name, { +class openstacklib::openstackclient ( + String[1] $package_name = $openstacklib::params::openstackclient_package_name, + Stdlib::Ensure::Package $package_ensure = 'present' +) inherits openstacklib::params { + stdlib::ensure_packages($package_name, { 'ensure' => $package_ensure, - 'tag' => 'openstack' + 'tag' => ['openstack', 'openstackclient'] }) } diff --git a/manifests/params.pp b/manifests/params.pp index c35a2b17..fec660ae 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -4,9 +4,19 @@ # should be considered to be constant # class openstacklib::params { + include openstacklib::defaults - include ::openstacklib::defaults - $pyvers = $::openstacklib::defaults::pyvers + $openstackclient_package_name = 'python3-openstackclient' - $openstackclient_package_name = "python${pyvers}-openstackclient" + case $facts['os']['family'] { + 'RedHat': { + $open_iscsi_package_name = 'iscsi-initiator-utils' + } + 'Debian': { + $open_iscsi_package_name = 'open-iscsi' + } + default:{ + fail("Unsupported osfamily: ${facts['os']['family']}") + } + } } diff --git a/manifests/policy.pp b/manifests/policy.pp index 091694cd..ab0d8b78 100644 --- a/manifests/policy.pp +++ b/manifests/policy.pp @@ -1,18 +1,85 @@ -# == Class: openstacklib::policies +# == Define: openstacklib::policies # # This resource is an helper to call the policy definition # # == Parameters: # -# [*policies*] -# Hash of policies one would like to set to specific values -# hash; optional +# [*policy_path*] +# (Optional) Path to the policy file. This should be an asbolute path. +# Defaults to $name # -class openstacklib::policy ( - $policies = {}, +# [*policies*] +# (Optional) Set of policies to configure. This parameter accepts a hash +# value and is used to define the openstacklib::policies::base defined-type +# resouces. For example +# +# { +# 'my-context_is_admin' => { +# 'key' => 'context_is_admin', +# 'value' => 'true' +# }, +# 'default' => { +# 'value' => 'rule:admin_or_owner' +# } +# } +# +# adds the following rules to the policy file. +# +# context_is_admin: true +# default: rule:admin_or_owner +# +# Defaults to {} +# +# [*file_mode*] +# (Optional) Permission mode for the policy file +# Defaults to '0640' +# +# [*file_user*] +# (Optional) User for the policy file +# Defaults to undef +# +# [*file_group*] +# (Optional) Group for the policy file +# Defaults to undef +# +# [*file_format*] +# (Optional) Format for file contents. Valid value is 'yaml'. +# Defaults to 'yaml'. +# +# [*purge_config*] +# (Optional) Whether to set only the specified policy rules in the policy +# file. +# Defaults to false. +# +define openstacklib::policy ( + Stdlib::Absolutepath $policy_path = $name, + Openstacklib::Policies $policies = {}, + Stdlib::Filemode $file_mode = '0640', + $file_user = undef, + $file_group = undef, + Enum['yaml'] $file_format = 'yaml', + Boolean $purge_config = false, ) { + if empty($policies) { + create_resources('openstacklib::policy::default', { + $policy_path => { + file_mode => $file_mode, + file_user => $file_user, + file_group => $file_group, + file_format => $file_format, + purge_config => $purge_config, + }, + }) + } else { + $policy_defaults = { + file_path => $policy_path, + file_mode => $file_mode, + file_user => $file_user, + file_group => $file_group, + file_format => $file_format, + purge_config => $purge_config, + } - validate_legacy(Hash, 'validate_hash', $policies) - - create_resources('openstacklib::policy::base', $policies) + create_resources('openstacklib::policy::base', $policies, $policy_defaults) + } } diff --git a/manifests/policy/base.pp b/manifests/policy/base.pp index 5cf2b761..43d91e1f 100644 --- a/manifests/policy/base.pp +++ b/manifests/policy/base.pp @@ -1,20 +1,19 @@ # == Definition: openstacklib::policy::base # -# This resource configures the policy.json file for an OpenStack service +# This resource configures the policy file for an OpenStack service # # == Parameters: # # [*file_path*] -# Path to the policy.json file -# string; required +# (required) Path to the policy file # # [*key*] -# The key to replace the value for -# string; required; the key to replace the value for +# (optional) The key to replace the value for +# Defaults to $name # # [*value*] -# The value to set -# string; optional; the value to set +# (optional) The value to set +# Defaults to '' # # [*file_mode*] # (optional) Permission mode for the policy file @@ -28,44 +27,45 @@ # (optional) Group for the policy file # Defaults to undef # +# [*file_format*] +# (optional) Format for file contents. Valid value is 'yaml' +# Defaults to 'yaml'. +# +# [*purge_config*] +# (optional) Whether to set only the specified policy rules in the policy +# file. +# Defaults to false. +# define openstacklib::policy::base ( - $file_path, - $key, - $value = '', - $file_mode = '0640', - $file_user = undef, - $file_group = undef, + Stdlib::Absolutepath $file_path, + String[1] $key = $name, + String $value = '', + Stdlib::Filemode $file_mode = '0640', + $file_user = undef, + $file_group = undef, + Enum['yaml'] $file_format = 'yaml', + Boolean $purge_config = false, ) { - - ensure_resource('file', $file_path, { - mode => $file_mode, - owner => $file_user, - group => $file_group, - replace => false, # augeas will manage the content, we just need to make sure it exists - content => '{}' + ensure_resource('openstacklib::policy::default', $file_path, { + file_path => $file_path, + file_mode => $file_mode, + file_user => $file_user, + file_group => $file_group, + file_format => $file_format, + purge_config => $purge_config, }) - # Add entry if it doesn't exists - augeas { "${file_path}-${key}-${value}-add": - lens => 'Json.lns', - incl => $file_path, - changes => [ - "set dict/entry[last()+1] \"${key}\"", - "set dict/entry[last()]/string \"${value}\"", - ], - onlyif => "match dict/entry[*][.=\"${key}\"] size == 0", - } + # NOTE(tkajianm): Currently we use single quotes('') to quote the whole + # value, thus a single quote in value should be escaped + # by another single quote (which results in '') + # NOTE(tkajinam): Replace '' by ' first in case ' is already escaped + $value_real = regsubst(regsubst($value, '\'\'', '\'', 'G'), '\'', '\'\'', 'G') - # Requires that the entry is added before this call or it will fail. - augeas { "${file_path}-${key}-${value}" : - lens => 'Json.lns', - incl => $file_path, - changes => "set dict/entry[*][.=\"${key}\"]/string \"${value}\"", + file_line { "${file_path}-${key}" : + path => $file_path, + line => "'${key}': '${value_real}'", + match => "^['\"]?${key}(?!:)['\"]?\\s*:.+", } - - File<| title == $file_path |> - -> Augeas<| title == "${file_path}-${key}-${value}-add" |> - ~> Augeas<| title == "${file_path}-${key}-${value}" |> - + Openstacklib::Policy::Default<| title == $file_path |> + -> File_line<| title == "${file_path}-${key}" |> } - diff --git a/manifests/policy/default.pp b/manifests/policy/default.pp new file mode 100644 index 00000000..52615f19 --- /dev/null +++ b/manifests/policy/default.pp @@ -0,0 +1,47 @@ +# == Definition: openstacklib::policy::default +# +# Create a default (empty) policy fie for an OpenStack service +# +# == Parameters: +# +# [*file_path*] +# (Optional) Path to the policy file +# Defaults to $name +# +# [*file_mode*] +# (Optional) Permission mode for the policy file +# Defaults to '0640' +# +# [*file_user*] +# (Optional) User for the policy file +# Defaults to undef +# +# [*file_group*] +# (Optional) Group for the policy file +# Defaults to undef +# +# [*file_format*] +# (Optional) Format for file contents. Valid value is 'yaml'. +# Defaults to 'yaml'. +# +# [*purge_config*] +# (Optional) Whether to set only the specified policy rules in the policy +# file. +# Defaults to false. +# +define openstacklib::policy::default ( + Stdlib::Absolutepath $file_path = $name, + Stdlib::Filemode $file_mode = '0640', + $file_user = undef, + $file_group = undef, + Enum['yaml'] $file_format = 'yaml', + Boolean $purge_config = false, +) { + ensure_resource('file', $file_path, { + mode => $file_mode, + owner => $file_user, + group => $file_group, + replace => $purge_config, + content => '' + }) +} diff --git a/manifests/policyrcd.pp b/manifests/policyrcd.pp index 171830cf..7a5bcad3 100644 --- a/manifests/policyrcd.pp +++ b/manifests/policyrcd.pp @@ -21,17 +21,14 @@ # (required) The services that should be in the policy-rc.d shell script # that should not autostart on install. # -class openstacklib::policyrcd( - $services +class openstacklib::policyrcd ( + Array[String[1]] $services ) { - - validate_legacy(Array, 'validate_array', $services) - - if $::osfamily == 'Debian' { + if $facts['os']['family'] == 'Debian' { # We put this out there so openstack services wont auto start # when installed. file { '/usr/sbin/policy-rc.d': - ensure => present, + ensure => file, content => template('openstacklib/policy-rc.d.erb'), mode => '0755', owner => root, diff --git a/manifests/service_validation.pp b/manifests/service_validation.pp index 91f735d3..50442062 100644 --- a/manifests/service_validation.pp +++ b/manifests/service_validation.pp @@ -65,7 +65,7 @@ # Environment to use # string; optional; default to empty array # -define openstacklib::service_validation( +define openstacklib::service_validation ( $command, $service_name = $name, $path = '/usr/bin:/bin:/usr/sbin:/sbin', @@ -78,7 +78,6 @@ $unless = undef, $environment = [], ) { - if $onlyif and $unless { fail ('Only one parameter should be declared: onlyif or unless') } @@ -99,6 +98,4 @@ anchor { "create ${service_name} anchor": } -> Exec<| title == "execute ${service_name} validation" |> - } - diff --git a/manifests/wsgi/apache.pp b/manifests/wsgi/apache.pp index 7f8f666c..bcb264fe 100644 --- a/manifests/wsgi/apache.pp +++ b/manifests/wsgi/apache.pp @@ -22,6 +22,15 @@ # # == Parameters # +# [*wsgi_script_dir*] +# (Required) The directory path of the WSGI script. +# +# [*wsgi_script_file*] +# (Required) The file path of the WSGI script. +# +# [*wsgi_script_source*] +# (Required) The source of the WSGI script. +# # [*service_name*] # (Optional) Name of the service to run. # Example: nova-api @@ -29,7 +38,7 @@ # # [*servername*] # (Optional) The servername for the virtualhost -# Defaults to $::fqdn +# Defaults to $facts['networking']['fqdn'] # # [*bind_host*] # (Optional) The host/ip address Apache will listen on. @@ -49,7 +58,11 @@ # # [*priority*] # (Optional) The priority for the vhost. -# Defaults to '10' +# Defaults to 10 +# +# [*setenv*] +# (Optional) Set environment variables for the vhost. +# Defaults to [] # # [*ssl*] # (Optional) Use SSL. @@ -63,6 +76,11 @@ # (Optional) Path to SSL key. # Default to apache::vhost 'ssl_*' defaults # +# [*ssl_verify_client*] +# (Optional) Sets the SSLVerifyClient directive which sets the +# certificate verification level for client authentication. +# Default to apache::vhost 'ssl_*' defaults +# # [*ssl_chain*] # (Optional) SSL chain. # Default to apache::vhost 'ssl_*' defaults @@ -93,7 +111,7 @@ # # [*workers*] # (Optional) The number of workers for the vhost. -# Defaults to $::os_workers +# Defaults to $facts['os_workers'] # # [*wsgi_daemon_process*] # (Optional) Name of the WSGI daemon process. @@ -107,18 +125,6 @@ # (Optional) Name of the WSGI process group. # Defaults to $name # -# [*wsgi_script_dir*] -# (Optional) The directory path of the WSGI script. -# Defaults to undef -# -# [*wsgi_script_file*] -# (Optional) The file path of the WSGI script. -# Defaults to undef -# -# [*wsgi_script_source*] -# (Optional) The source of the WSGI script. -# Defaults to undef -# # [*wsgi_application_group*] # (Optional) The application group of the WSGI script. # Defaults to '%{GLOBAL}' @@ -134,27 +140,16 @@ # WSGIChunkedRequest option in the vhost. # Defaults to undef # -# [*set_wsgi_import_script*] -# (Optional) Enable WSGIImportScript. -# Defaults to false -# -# [*wsgi_import_script*] -# (Optional) WSGIImportScript path. +# [*headers*] +# (Optional) Headers for the vhost. # Defaults to undef -# If not set and set_wsgi_import_script is true, defaults to the WSGI -# application module path # -# [*wsgi_import_script_options*] -# (Optional) Sets WSGIImportScript options. +# [*request_headers*] +# (Optional) Modifies collected request headers in various ways. # Defaults to undef -# If not set and set_wsgi_import_script is true, push a dict as follow: -# { -# process-group => $wsgi_daemon_process, -# application-group => $wsgi_application_group, -# } # -# [*headers*] -# (Optional) Headers for the vhost. +# [*aliases*] +# (Optional) Aliases for the vhost. # Defaults to undef # # [*custom_wsgi_process_options*] @@ -186,20 +181,25 @@ # [*access_log_file*] # (Optional) The log file name for the virtualhost. # access_log_file and access_log_pipe is mutually exclusive. -# Defaults to false +# Defaults to undef # # [*access_log_pipe*] # (Optional) Specifies a pipe where Apache sends access logs for the virtualhost. # access_log_file and access_log_pipe is mutually exclusive. -# Defaults to false +# Defaults to undef # # [*access_log_syslog*] # (Optional) Sends the virtualhost access log messages to syslog. -# Defaults to false +# Defaults to undef # # [*access_log_format*] # (Optional) The log format for the virtualhost. -# Defaults to false +# Defaults to undef +# +# [*access_log_env_var*] +# (Optional) Specifies that only requests with particular environment +# variables be logged. +# Defaults to undef # # [*error_log_file*] # (Optional) The error log file name for the virtualhost. @@ -215,14 +215,43 @@ # (Optional) Sends the virtualhost error log messages to syslog. # Defaults to undef # +# [*log_level*] +# (Optional) Specifies LogLevel for Apache WSGI. +# Defaults to undef +# +# DEPRECATED PARAMMETERS +# +# [*set_wsgi_import_script*] +# (Optional) Enable WSGIImportScript. +# Defaults to false +# +# [*wsgi_import_script*] +# (Optional) WSGIImportScript path. +# Defaults to undef +# If not set and set_wsgi_import_script is true, defaults to the WSGI +# application module path +# +# [*wsgi_import_script_options*] +# (Optional) Sets WSGIImportScript options. +# Defaults to undef +# If not set and set_wsgi_import_script is true, push a dict as follow: +# { +# process-group => $wsgi_daemon_process, +# application-group => $wsgi_application_group, +# } +# define openstacklib::wsgi::apache ( + Stdlib::Absolutepath $wsgi_script_dir, + String[1] $wsgi_script_file, + Stdlib::Absolutepath $wsgi_script_source, $service_name = $name, + $servername = $facts['networking']['fqdn'], $bind_host = undef, $bind_port = undef, $group = undef, $path = '/', - $priority = '10', - $servername = $::fqdn, + $priority = 10, + $setenv = [], $ssl = false, $ssl_ca = undef, $ssl_cert = undef, @@ -231,40 +260,38 @@ $ssl_crl = undef, $ssl_crl_path = undef, $ssl_key = undef, + $ssl_verify_client = undef, $threads = 1, $user = undef, - $workers = $::os_workers, + $workers = $facts['os_workers'], $wsgi_daemon_process = $name, $wsgi_process_display_name = $name, $wsgi_process_group = $name, - $wsgi_script_dir = undef, - $wsgi_script_file = undef, - $wsgi_script_source = undef, $wsgi_application_group = '%{GLOBAL}', $wsgi_pass_authorization = undef, $wsgi_chunked_request = undef, - $set_wsgi_import_script = false, - $wsgi_import_script = undef, - $wsgi_import_script_options = undef, $headers = undef, + $request_headers = undef, + $aliases = undef, $custom_wsgi_process_options = {}, $custom_wsgi_script_aliases = undef, $vhost_custom_fragment = undef, $allow_encoded_slashes = undef, - $access_log_file = false, - $access_log_pipe = false, - $access_log_syslog = false, - $access_log_format = false, + $access_log_file = undef, + $access_log_pipe = undef, + $access_log_syslog = undef, + $access_log_format = undef, + $access_log_env_var = undef, $error_log_file = undef, $error_log_pipe = undef, $error_log_syslog = undef, + $log_level = undef, + # DEPRECATED PARAMETERS + $set_wsgi_import_script = false, + $wsgi_import_script = undef, + $wsgi_import_script_options = undef, ) { - - include ::apache - include ::apache::mod::wsgi - if $ssl { - include ::apache::mod::ssl - } + include apache # Ensure there's no trailing '/' except if this is also the only character $path_real = regsubst($path, '(^/.*)/$', '\1') @@ -288,7 +315,7 @@ mode => '0644', } - $wsgi_daemon_process_options = merge ( + $wsgi_daemon_process_options = stdlib::merge( { user => $user, group => $group, @@ -299,16 +326,18 @@ $custom_wsgi_process_options, ) - $wsgi_script_aliases_default = hash([$path_real,"${wsgi_script_dir}/${wsgi_script_file}"]) + $wsgi_script_aliases_default = Hash([$path_real,"${wsgi_script_dir}/${wsgi_script_file}"]) if $custom_wsgi_script_aliases { - $wsgi_script_aliases_real = merge($wsgi_script_aliases_default, $custom_wsgi_script_aliases) + $wsgi_script_aliases_real = stdlib::merge($wsgi_script_aliases_default, $custom_wsgi_script_aliases) } else { $wsgi_script_aliases_real = $wsgi_script_aliases_default } # Sets WSGIImportScript related options if $set_wsgi_import_script { + warning('The set_wsgi_import_script parameter is deprecated and will be removed in a future release') + if $wsgi_import_script { $wsgi_import_script_real = $wsgi_import_script } else { @@ -328,43 +357,49 @@ } # End of WSGIImportScript related options - ::apache::vhost { $service_name: - ensure => 'present', - servername => $servername, - ip => $bind_host, - port => $bind_port, - docroot => $wsgi_script_dir, - docroot_owner => $user, - docroot_group => $group, - priority => $priority, - setenvif => ['X-Forwarded-Proto https HTTPS=1'], - ssl => $ssl, - ssl_cert => $ssl_cert, - ssl_key => $ssl_key, - ssl_chain => $ssl_chain, - ssl_ca => $ssl_ca, - ssl_crl_path => $ssl_crl_path, - ssl_crl => $ssl_crl, - ssl_certs_dir => $ssl_certs_dir, - wsgi_daemon_process => $wsgi_daemon_process, - wsgi_daemon_process_options => $wsgi_daemon_process_options, - wsgi_process_group => $wsgi_process_group, - wsgi_script_aliases => $wsgi_script_aliases_real, - wsgi_application_group => $wsgi_application_group, - wsgi_pass_authorization => $wsgi_pass_authorization, - wsgi_chunked_request => $wsgi_chunked_request, - wsgi_import_script => $wsgi_import_script_real, - wsgi_import_script_options => $wsgi_import_script_options_real, - headers => $headers, - custom_fragment => $vhost_custom_fragment, - allow_encoded_slashes => $allow_encoded_slashes, - access_log_file => $access_log_file, - access_log_pipe => $access_log_pipe, - access_log_syslog => $access_log_syslog, - access_log_format => $access_log_format, - error_log_file => $error_log_file, - error_log_pipe => $error_log_pipe, - error_log_syslog => $error_log_syslog, + apache::vhost { $service_name: + ensure => 'present', + servername => $servername, + ip => $bind_host, + port => $bind_port, + docroot => $wsgi_script_dir, + docroot_owner => $user, + docroot_group => $group, + priority => $priority, + setenv => $setenv, + setenvif => ['X-Forwarded-Proto https HTTPS=1'], + ssl => $ssl, + ssl_cert => $ssl_cert, + ssl_key => $ssl_key, + ssl_verify_client => $ssl_verify_client, + ssl_chain => $ssl_chain, + ssl_ca => $ssl_ca, + ssl_crl_path => $ssl_crl_path, + ssl_crl => $ssl_crl, + ssl_certs_dir => $ssl_certs_dir, + wsgi_daemon_process => Hash([$wsgi_daemon_process, $wsgi_daemon_process_options]), + wsgi_process_group => $wsgi_process_group, + wsgi_script_aliases => $wsgi_script_aliases_real, + wsgi_application_group => $wsgi_application_group, + wsgi_pass_authorization => $wsgi_pass_authorization, + wsgi_chunked_request => $wsgi_chunked_request, + wsgi_import_script => $wsgi_import_script_real, + wsgi_import_script_options => $wsgi_import_script_options_real, + headers => $headers, + request_headers => $request_headers, + aliases => $aliases, + custom_fragment => $vhost_custom_fragment, + allow_encoded_slashes => $allow_encoded_slashes, + access_log_file => $access_log_file, + access_log_pipe => $access_log_pipe, + access_log_syslog => $access_log_syslog, + access_log_format => $access_log_format, + access_log_env_var => $access_log_env_var, + error_log_file => $error_log_file, + error_log_pipe => $error_log_pipe, + error_log_syslog => $error_log_syslog, + log_level => $log_level, + options => ['-Indexes', '+FollowSymLinks'], } Package<| title == 'httpd' |> diff --git a/metadata.json b/metadata.json index 4038228f..20c33a30 100644 --- a/metadata.json +++ b/metadata.json @@ -3,27 +3,27 @@ "dependencies": [ { "name": "puppetlabs/apache", - "version_requirement": ">=3.0.0" + "version_requirement": ">=5.0.0 <14.0.0" }, { "name": "puppetlabs/inifile", - "version_requirement": ">=2.0.0 <3.0.0" + "version_requirement": ">=2.0.0 <7.0.0" }, { "name": "puppetlabs/mysql", - "version_requirement": ">=6.0.0 <9.0.0" + "version_requirement": ">=6.0.0 <17.0.0" }, { "name": "puppetlabs/stdlib", - "version_requirement": ">=5.0.0 <6.0.0" + "version_requirement": ">=9.0.0 <10.0.0" }, { "name": "puppet/rabbitmq", - "version_requirement": ">=2.0.2 <6.0.0" + "version_requirement": ">=2.0.2 <15.0.0" }, { "name": "puppetlabs/postgresql", - "version_requirement": ">=5.10.0 <6.0.0" + "version_requirement": ">=6.4.0 <11.0.0" } ], "description": "Puppet module library to expose common functionality between OpenStack modules.", @@ -34,25 +34,25 @@ { "operatingsystem": "Debian", "operatingsystemrelease": [ - "9" + "13" ] }, { - "operatingsystem": "Fedora", + "operatingsystem": "RedHat", "operatingsystemrelease": [ - "24" + "9" ] }, { - "operatingsystem": "RedHat", + "operatingsystem": "CentOS", "operatingsystemrelease": [ - "7" + "9" ] }, { "operatingsystem": "Ubuntu", "operatingsystemrelease": [ - "18.04" + "24.04" ] } ], @@ -60,10 +60,10 @@ "requirements": [ { "name": "puppet", - "version_requirement": "5.x" + "version_requirement": ">= 8.0.0 < 9.0.0" } ], - "source": "git://github.com/openstack/puppet-openstacklib.git", + "source": "https://opendev.org/openstack/puppet-openstacklib.git", "summary": "Puppet OpenStack Libraries", - "version": "15.4.0" + "version": "27.0.0" } diff --git a/releasenotes/notes/access_log_env_var-87f4bfd3356c6cd2.yaml b/releasenotes/notes/access_log_env_var-87f4bfd3356c6cd2.yaml new file mode 100644 index 00000000..40f3de63 --- /dev/null +++ b/releasenotes/notes/access_log_env_var-87f4bfd3356c6cd2.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add access_log_env_var for apache vhost. + Allows that only requests with particular environment + variables set are logged. + Example: set a dontlog variable for healthchecks, + so healthchecks are not logged. diff --git a/releasenotes/notes/add-ssl_verify_client-87e52209cc80861d.yaml b/releasenotes/notes/add-ssl_verify_client-87e52209cc80861d.yaml new file mode 100644 index 00000000..46bda7f6 --- /dev/null +++ b/releasenotes/notes/add-ssl_verify_client-87e52209cc80861d.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Added ssl_verify_client parameter to openstacklib::wsgi::apache. diff --git a/releasenotes/notes/apache-request_headers-1db72ccfd1e76735.yaml b/releasenotes/notes/apache-request_headers-1db72ccfd1e76735.yaml new file mode 100644 index 00000000..883493ed --- /dev/null +++ b/releasenotes/notes/apache-request_headers-1db72ccfd1e76735.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The new ``openstacklib::wsgi::apache::request_headers`` parameter has been + added. diff --git a/releasenotes/notes/apache-vhost-wsgi_daemon_process-3e90ea82934cbaea.yaml b/releasenotes/notes/apache-vhost-wsgi_daemon_process-3e90ea82934cbaea.yaml new file mode 100644 index 00000000..ff0c3028 --- /dev/null +++ b/releasenotes/notes/apache-vhost-wsgi_daemon_process-3e90ea82934cbaea.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + This module now requires a puppetlabs-apache version >= 5.0.0 diff --git a/releasenotes/notes/bump-mysql-dep-10-217373db021b4c1f.yaml b/releasenotes/notes/bump-mysql-dep-10-217373db021b4c1f.yaml new file mode 100644 index 00000000..a377c5d0 --- /dev/null +++ b/releasenotes/notes/bump-mysql-dep-10-217373db021b4c1f.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Puppet OpenStack now supports the 10.x release of the PuppetLabs MySQL module. diff --git a/releasenotes/notes/centos-9-support-899c5dc223ded57c.yaml b/releasenotes/notes/centos-9-support-899c5dc223ded57c.yaml new file mode 100644 index 00000000..c3876a08 --- /dev/null +++ b/releasenotes/notes/centos-9-support-899c5dc223ded57c.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Now this module supports CentOS 9 and Red Hat Enterprise Linux 9. diff --git a/releasenotes/notes/clouds-yaml-e8a87dfceba4619d.yaml b/releasenotes/notes/clouds-yaml-e8a87dfceba4619d.yaml new file mode 100644 index 00000000..cda48ab4 --- /dev/null +++ b/releasenotes/notes/clouds-yaml-e8a87dfceba4619d.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + The new ``openstacklib::clouds`` resource type has been added, which + manages the ``clouds.yaml`` file to store credentials for the ``openstack`` + cli. + + - | + Now ``Puppet::Provider::Openstack::CredentialsV3`` supports loading + credentials from the ``clouds.yaml`` file. diff --git a/releasenotes/notes/clouds-yaml-owner-group-4e7b2d4e7028cfef.yaml b/releasenotes/notes/clouds-yaml-owner-group-4e7b2d4e7028cfef.yaml new file mode 100644 index 00000000..ccba9432 --- /dev/null +++ b/releasenotes/notes/clouds-yaml-owner-group-4e7b2d4e7028cfef.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``file_user`` and ``file_group`` parameters to ``openstacklib::clouds`` + resource to manage ``clouds.yaml`` file owner and group. \ No newline at end of file diff --git a/releasenotes/notes/db-password_hash-1045114a36b6f292.yaml b/releasenotes/notes/db-password_hash-1045114a36b6f292.yaml new file mode 100644 index 00000000..718265f5 --- /dev/null +++ b/releasenotes/notes/db-password_hash-1045114a36b6f292.yaml @@ -0,0 +1,10 @@ +--- +deprecations: + - | + The ``password_hash`` parameter in ``openstacklib::db::mysql`` and + ``openstacklib::db::postgresql`` were deprecated and will be removed in + a future release. Use the ``password`` parameter instead, so that password + hash is generated from given user and password in puppet-openstacklib. +upgrade: + - | + Now this module requires puppetlabs-postgresql >= 6.4.0 . diff --git a/releasenotes/notes/deprecate-postgresql-a626f49f46bd6799.yaml b/releasenotes/notes/deprecate-postgresql-a626f49f46bd6799.yaml new file mode 100644 index 00000000..435239f9 --- /dev/null +++ b/releasenotes/notes/deprecate-postgresql-a626f49f46bd6799.yaml @@ -0,0 +1,5 @@ +--- +deprecations: + - | + Support for PostgreSQL has been deprecated and will be removed in a future + release. diff --git a/releasenotes/notes/deprecate-wsgi-apache-set_wsgi_import_script-7dc473250b364100.yaml b/releasenotes/notes/deprecate-wsgi-apache-set_wsgi_import_script-7dc473250b364100.yaml new file mode 100644 index 00000000..63d3efe3 --- /dev/null +++ b/releasenotes/notes/deprecate-wsgi-apache-set_wsgi_import_script-7dc473250b364100.yaml @@ -0,0 +1,9 @@ +--- +deprecations: + - | + The following parameters of the ``openstacklib::wsgi::apache`` defined + resource type have been deprecated and will be removed in a future release. + + - ``set_wsgi_import_script`` + - ``wsgi_import_script`` + - ``wsgi_import_script_options`` diff --git a/releasenotes/notes/drop-fedora-66989e6f7049e5bd.yaml b/releasenotes/notes/drop-fedora-66989e6f7049e5bd.yaml new file mode 100644 index 00000000..71818df4 --- /dev/null +++ b/releasenotes/notes/drop-fedora-66989e6f7049e5bd.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + Fedora is no longer supported. diff --git a/releasenotes/notes/drop-rc-file-6c905f0be7a93ff3.yaml b/releasenotes/notes/drop-rc-file-6c905f0be7a93ff3.yaml new file mode 100644 index 00000000..afbccdce --- /dev/null +++ b/releasenotes/notes/drop-rc-file-6c905f0be7a93ff3.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + The base provider class to manage OpenStack resources no longer attempts to + load credentials from ``openrc`` file in the user's home directory. Use + the ``clouds.yaml`` file instead. diff --git a/releasenotes/notes/iscsid-0a9fe8a9dba4047b.yaml b/releasenotes/notes/iscsid-0a9fe8a9dba4047b.yaml new file mode 100644 index 00000000..70848d2d --- /dev/null +++ b/releasenotes/notes/iscsid-0a9fe8a9dba4047b.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The new ``openstacklib::iscsid`` class has been added. This class can be + used to set up basic configurations for the iscsid service. diff --git a/releasenotes/notes/migrate-policy-format-from-json-to-yaml-3389e1d5348cd69b.yaml b/releasenotes/notes/migrate-policy-format-from-json-to-yaml-3389e1d5348cd69b.yaml new file mode 100644 index 00000000..7076d448 --- /dev/null +++ b/releasenotes/notes/migrate-policy-format-from-json-to-yaml-3389e1d5348cd69b.yaml @@ -0,0 +1,5 @@ +--- +deprecations: + - | + Json format of policy files has been deprecated and will be removed in + a future release. Use yaml format instead. diff --git a/releasenotes/notes/no-directory-listing-8e6270ed0e1eb1d0.yaml b/releasenotes/notes/no-directory-listing-8e6270ed0e1eb1d0.yaml new file mode 100644 index 00000000..46a1a9c1 --- /dev/null +++ b/releasenotes/notes/no-directory-listing-8e6270ed0e1eb1d0.yaml @@ -0,0 +1,6 @@ +--- +security: + - Do not authorize directory listing +fixes: + - rhbz#1778052 + - LP#1854442 diff --git a/releasenotes/notes/openstacklib-wsgi-apache-log-level-f0f1567b27c64ac0.yaml b/releasenotes/notes/openstacklib-wsgi-apache-log-level-f0f1567b27c64ac0.yaml new file mode 100644 index 00000000..24af828e --- /dev/null +++ b/releasenotes/notes/openstacklib-wsgi-apache-log-level-f0f1567b27c64ac0.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add `log_level` parameter to control `LogLevel` for Apache vhost. + By default, Apache only logs warnings and errors in its errors logfile, + while request timeouts are logged by `mod_reqtimeout` at LogLevel info. + Default `log_level` value is not defined, which corresponds to + the default Apache `LogLevel warn`. diff --git a/releasenotes/notes/os-withenv-3ca466fde75f6441.yaml b/releasenotes/notes/os-withenv-3ca466fde75f6441.yaml new file mode 100644 index 00000000..e381ca63 --- /dev/null +++ b/releasenotes/notes/os-withenv-3ca466fde75f6441.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + The ``Puppet::Provider::Openstack.request`` now filters out all external + ``OS_*`` environment variables to prevent collisions with environment + variables in the shell where Puppet is running. diff --git a/releasenotes/notes/os_url-efc774dd8ab4571d.yaml b/releasenotes/notes/os_url-efc774dd8ab4571d.yaml new file mode 100644 index 00000000..a555e1e2 --- /dev/null +++ b/releasenotes/notes/os_url-efc774dd8ab4571d.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + The new ``os_url`` function has been added. diff --git a/releasenotes/notes/puppet-5-unsupported-ac35e83ee3d2b12c.yaml b/releasenotes/notes/puppet-5-unsupported-ac35e83ee3d2b12c.yaml new file mode 100644 index 00000000..8a21fb5a --- /dev/null +++ b/releasenotes/notes/puppet-5-unsupported-ac35e83ee3d2b12c.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + Puppet 5 is now officially unsupported and support may be broken in any of + the Puppet modules in this cycle. We recommend all deployments to start + using Puppet 6 in this release. diff --git a/releasenotes/notes/puppet-8-4b210a58a65b61da.yaml b/releasenotes/notes/puppet-8-4b210a58a65b61da.yaml new file mode 100644 index 00000000..8b232039 --- /dev/null +++ b/releasenotes/notes/puppet-8-4b210a58a65b61da.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + This module now officially supports Puppet 8. diff --git a/releasenotes/notes/purge-policy-file-1ad9f366345142e7.yaml b/releasenotes/notes/purge-policy-file-1ad9f366345142e7.yaml new file mode 100644 index 00000000..4557a8d3 --- /dev/null +++ b/releasenotes/notes/purge-policy-file-1ad9f366345142e7.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Now the ``openstacklib::policies`` resource type provides the basic set + of configurations for policy settings. It provides the purge_config + parameter which ensures a policy file is purged. + +upgrade: + - | + The ``openstacklib::policies`` class has been re-implemented as a defined + resource type. diff --git a/releasenotes/notes/remove-centos-8-8b9bc74045173169.yaml b/releasenotes/notes/remove-centos-8-8b9bc74045173169.yaml new file mode 100644 index 00000000..7451387e --- /dev/null +++ b/releasenotes/notes/remove-centos-8-8b9bc74045173169.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + CentOS 8 Stream is no longer supported by this module. diff --git a/releasenotes/notes/remove-os_package_type-fact-9de4c7060b735a62.yaml b/releasenotes/notes/remove-os_package_type-fact-9de4c7060b735a62.yaml new file mode 100644 index 00000000..2c749530 --- /dev/null +++ b/releasenotes/notes/remove-os_package_type-fact-9de4c7060b735a62.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + The ``os_package_type`` fact has been removed. diff --git a/releasenotes/notes/remove-policy-json-8902dc11c5576e73.yaml b/releasenotes/notes/remove-policy-json-8902dc11c5576e73.yaml new file mode 100644 index 00000000..d704c793 --- /dev/null +++ b/releasenotes/notes/remove-policy-json-8902dc11c5576e73.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Support for json format policy files has been removed. Now yaml is the only + supported format. diff --git a/releasenotes/notes/remove-puppet-6-14aa2ddd0e088922.yaml b/releasenotes/notes/remove-puppet-6-14aa2ddd0e088922.yaml new file mode 100644 index 00000000..78061d56 --- /dev/null +++ b/releasenotes/notes/remove-puppet-6-14aa2ddd0e088922.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + Puppet 6 is no longer supported. diff --git a/releasenotes/notes/remove-puppet-7-154f38cab0ed46b6.yaml b/releasenotes/notes/remove-puppet-7-154f38cab0ed46b6.yaml new file mode 100644 index 00000000..c9c6bfa7 --- /dev/null +++ b/releasenotes/notes/remove-puppet-7-154f38cab0ed46b6.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + Puppet 7 is no longer supported. Use Puppet 8 instead. diff --git a/releasenotes/notes/support-for-yaml-policy-da1ccf9fa692dca3.yaml b/releasenotes/notes/support-for-yaml-policy-da1ccf9fa692dca3.yaml new file mode 100644 index 00000000..7dfe28ba --- /dev/null +++ b/releasenotes/notes/support-for-yaml-policy-da1ccf9fa692dca3.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Oslo policy is moving towards yaml as a policy file format and is + deprecating json. Policy definitons now may contain a 'file_format' + field to specify 'yaml' or 'json' as a file format. diff --git a/releasenotes/notes/system-scope-credential-113608525e12a22d.yaml b/releasenotes/notes/system-scope-credential-113608525e12a22d.yaml new file mode 100644 index 00000000..3a7ae40b --- /dev/null +++ b/releasenotes/notes/system-scope-credential-113608525e12a22d.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Now ``Puppet::Provider::Openstack::CredentialsV3`` supports system scope + credential and domain scope credential in addition to project scope + credential. diff --git a/releasenotes/notes/ubuntu-jammy-da580c780aeafbef.yaml b/releasenotes/notes/ubuntu-jammy-da580c780aeafbef.yaml new file mode 100644 index 00000000..b6e0f8a7 --- /dev/null +++ b/releasenotes/notes/ubuntu-jammy-da580c780aeafbef.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + This module now supports Ubuntu 22.04 (Jammy Jellyfish). + +upgrade: + - | + This module no longer supports Ubuntu 20.04 (Focal Fossa). diff --git a/releasenotes/notes/ubuntu-noble-89f5f89bd0e4c83d.yaml b/releasenotes/notes/ubuntu-noble-89f5f89bd0e4c83d.yaml new file mode 100644 index 00000000..96a8ee0f --- /dev/null +++ b/releasenotes/notes/ubuntu-noble-89f5f89bd0e4c83d.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Ubuntu 24.04 is now formally supported. + +upgrade: + - | + Ubuntu 22.04 is no longer supported. diff --git a/releasenotes/notes/wsgi-aliases-2f74cbb84fcfd706.yaml b/releasenotes/notes/wsgi-aliases-2f74cbb84fcfd706.yaml new file mode 100644 index 00000000..1fdd49c6 --- /dev/null +++ b/releasenotes/notes/wsgi-aliases-2f74cbb84fcfd706.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The new ``openstacklib::wsgi::apache::aliases`` parameter has been added. + This parameter can be used to add alias definitions to the httpd vhost. diff --git a/releasenotes/notes/wsgi-setenv-8e8519fdb96d98b0.yaml b/releasenotes/notes/wsgi-setenv-8e8519fdb96d98b0.yaml new file mode 100644 index 00000000..87d321b1 --- /dev/null +++ b/releasenotes/notes/wsgi-setenv-8e8519fdb96d98b0.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The new ``openstacklib::wsgi::apache::setenv`` parameter has been added. + This paramaeter can be to define environment variables for vhost, passed + by httpd. diff --git a/releasenotes/source/2023.1.rst b/releasenotes/source/2023.1.rst new file mode 100644 index 00000000..2c9a36fa --- /dev/null +++ b/releasenotes/source/2023.1.rst @@ -0,0 +1,6 @@ +=========================== +2023.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: unmaintained/2023.1 diff --git a/releasenotes/source/2023.2.rst b/releasenotes/source/2023.2.rst new file mode 100644 index 00000000..a4838d7d --- /dev/null +++ b/releasenotes/source/2023.2.rst @@ -0,0 +1,6 @@ +=========================== +2023.2 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2023.2 diff --git a/releasenotes/source/2024.1.rst b/releasenotes/source/2024.1.rst new file mode 100644 index 00000000..4977a4f1 --- /dev/null +++ b/releasenotes/source/2024.1.rst @@ -0,0 +1,6 @@ +=========================== +2024.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2024.1 diff --git a/releasenotes/source/2024.2.rst b/releasenotes/source/2024.2.rst new file mode 100644 index 00000000..aaebcbc8 --- /dev/null +++ b/releasenotes/source/2024.2.rst @@ -0,0 +1,6 @@ +=========================== +2024.2 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2024.2 diff --git a/releasenotes/source/2025.1.rst b/releasenotes/source/2025.1.rst new file mode 100644 index 00000000..3add0e53 --- /dev/null +++ b/releasenotes/source/2025.1.rst @@ -0,0 +1,6 @@ +=========================== +2025.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2025.1 diff --git a/releasenotes/source/2025.2.rst b/releasenotes/source/2025.2.rst new file mode 100644 index 00000000..4dae18d8 --- /dev/null +++ b/releasenotes/source/2025.2.rst @@ -0,0 +1,6 @@ +=========================== +2025.2 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2025.2 diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index d89d4955..0f34b394 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -45,7 +44,7 @@ master_doc = 'index' # General information about the project. -copyright = u'2017, Puppet OpenStack Developers' +copyright = '2017, Puppet OpenStack Developers' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -86,7 +85,7 @@ #show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = 'native' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] @@ -185,8 +184,8 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'puppet-openstacklibReleaseNotes.tex', u'puppet-openstacklib Release Notes Documentation', - u'2017, Puppet OpenStack Developers', 'manual'), + ('index', 'puppet-openstacklibReleaseNotes.tex', 'puppet-openstacklib Release Notes Documentation', + '2017, Puppet OpenStack Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -215,8 +214,8 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'puppet-openstacklibreleasenotes', u'puppet-openstacklib Release Notes Documentation', - [u'2017, Puppet OpenStack Developers'], 1) + ('index', 'puppet-openstacklibreleasenotes', 'puppet-openstacklib Release Notes Documentation', + ['2017, Puppet OpenStack Developers'], 1) ] # If true, show URL addresses after external links. @@ -229,8 +228,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'puppet-openstacklibReleaseNotes', u'puppet-openstacklib Release Notes Documentation', - u'2017, Puppet OpenStack Developers', 'puppet-openstacklibReleaseNotes', 'One line description of project.', + ('index', 'puppet-openstacklibReleaseNotes', 'puppet-openstacklib Release Notes Documentation', + '2017, Puppet OpenStack Developers', 'puppet-openstacklibReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] @@ -250,6 +249,7 @@ locale_dirs = ['locale/'] # openstackdocstheme options -repository_name = 'openstack/puppet-openstacklib' -bug_project = 'puppet-openstacklib' -bug_tag = '' +openstackdocs_repo_name = 'openstack/puppet-openstacklib' +openstackdocs_bug_project = 'puppet-openstacklib' +openstackdocs_bug_tag = '' +openstackdocs_auto_name = False diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 22ffc5b9..6195efb6 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -9,6 +9,18 @@ Contents :maxdepth: 2 unreleased + 2025.2 + 2025.1 + 2024.2 + 2024.1 + 2023.2 + 2023.1 + zed + yoga + xena + wallaby + victoria + ussuri train stein rocky diff --git a/releasenotes/source/ussuri.rst b/releasenotes/source/ussuri.rst new file mode 100644 index 00000000..e21e50e0 --- /dev/null +++ b/releasenotes/source/ussuri.rst @@ -0,0 +1,6 @@ +=========================== +Ussuri Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/ussuri diff --git a/releasenotes/source/victoria.rst b/releasenotes/source/victoria.rst new file mode 100644 index 00000000..4efc7b6f --- /dev/null +++ b/releasenotes/source/victoria.rst @@ -0,0 +1,6 @@ +============================= +Victoria Series Release Notes +============================= + +.. release-notes:: + :branch: stable/victoria diff --git a/releasenotes/source/wallaby.rst b/releasenotes/source/wallaby.rst new file mode 100644 index 00000000..bcf35c5f --- /dev/null +++ b/releasenotes/source/wallaby.rst @@ -0,0 +1,6 @@ +============================ +Wallaby Series Release Notes +============================ + +.. release-notes:: + :branch: unmaintained/wallaby diff --git a/releasenotes/source/xena.rst b/releasenotes/source/xena.rst new file mode 100644 index 00000000..d19eda48 --- /dev/null +++ b/releasenotes/source/xena.rst @@ -0,0 +1,6 @@ +========================= +Xena Series Release Notes +========================= + +.. release-notes:: + :branch: unmaintained/xena diff --git a/releasenotes/source/yoga.rst b/releasenotes/source/yoga.rst new file mode 100644 index 00000000..43cafdea --- /dev/null +++ b/releasenotes/source/yoga.rst @@ -0,0 +1,6 @@ +========================= +Yoga Series Release Notes +========================= + +.. release-notes:: + :branch: unmaintained/yoga diff --git a/releasenotes/source/zed.rst b/releasenotes/source/zed.rst new file mode 100644 index 00000000..6cc2b155 --- /dev/null +++ b/releasenotes/source/zed.rst @@ -0,0 +1,6 @@ +======================== +Zed Series Release Notes +======================== + +.. release-notes:: + :branch: unmaintained/zed diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 38793888..00000000 --- a/setup.cfg +++ /dev/null @@ -1,13 +0,0 @@ -[metadata] -name = puppet-openstacklib -summary = Puppet module for OpenStack Openstacklib -description-file = - README.md -author = OpenStack -author-email = openstack-discuss@lists.openstack.org -home-page = https://docs.openstack.org/puppet-openstack-guide/latest -classifier = - Intended Audience :: Developers - Intended Audience :: System Administrators - License :: OSI Approved :: Apache Software License - Operating System :: POSIX :: Linux diff --git a/setup.py b/setup.py deleted file mode 100644 index 70c2b3f3..00000000 --- a/setup.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT -import setuptools - -setuptools.setup( - setup_requires=['pbr'], - pbr=True) diff --git a/spec/acceptance/defaults_spec.rb b/spec/acceptance/defaults_spec.rb deleted file mode 100644 index 5e083835..00000000 --- a/spec/acceptance/defaults_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'spec_helper_acceptance' - -describe 'Defaults manifest' do - context 'virtual_package' do - it_behaves_like 'puppet_apply_success_from_example', 'virtual_packages' - end -end diff --git a/spec/acceptance/mysql_spec.rb b/spec/acceptance/mysql_spec.rb index e07cb531..4b3b4ca4 100644 --- a/spec/acceptance/mysql_spec.rb +++ b/spec/acceptance/mysql_spec.rb @@ -8,9 +8,16 @@ pp= <<-EOS Exec { logoutput => 'on_failure' } - class { '::mysql::server': } + class { 'mysql::server': } - ::openstacklib::db::mysql { 'beaker': + $charset = $facts['os']['name'] ? { + 'Ubuntu' => 'utf8mb3', + default => 'utf8', + } + + openstacklib::db::mysql { 'ci': + charset => $charset, + collate => "${charset}_general_ci", password_hash => mysql::password('keystone'), allowed_hosts => '127.0.0.1', } @@ -25,9 +32,9 @@ class { '::mysql::server': } it { is_expected.to be_listening.with('tcp') } end - describe 'test database listing' do - it 'should list beaker database' do - expect(shell("mysql -e 'show databases;'|grep -q beaker").exit_code).to be_zero + it 'should have ci database' do + command("mysql -e 'show databases;' | grep -q ci") do |r| + expect(r.exit_code).to eq 0 end end diff --git a/spec/acceptance/nodesets/centos-70-x64.yml b/spec/acceptance/nodesets/centos-70-x64.yml deleted file mode 100644 index 5f097e9f..00000000 --- a/spec/acceptance/nodesets/centos-70-x64.yml +++ /dev/null @@ -1,11 +0,0 @@ -HOSTS: - centos-server-70-x64: - roles: - - master - platform: el-7-x86_64 - box: puppetlabs/centos-7.0-64-nocm - box_url: https://vagrantcloud.com/puppetlabs/centos-7.0-64-nocm - hypervisor: vagrant -CONFIG: - log_level: debug - type: foss diff --git a/spec/acceptance/nodesets/default.yml b/spec/acceptance/nodesets/default.yml deleted file mode 100644 index 486b6a34..00000000 --- a/spec/acceptance/nodesets/default.yml +++ /dev/null @@ -1,10 +0,0 @@ -HOSTS: - ubuntu-server-14.04-amd64: - roles: - - master - platform: ubuntu-14.04-amd64 - box: puppetlabs/ubuntu-14.04-64-nocm - box_url: https://vagrantcloud.com/puppetlabs/ubuntu-14.04-64-nocm - hypervisor: vagrant -CONFIG: - type: foss diff --git a/spec/acceptance/nodesets/nodepool-bionic.yml b/spec/acceptance/nodesets/nodepool-bionic.yml deleted file mode 100644 index ad73cc50..00000000 --- a/spec/acceptance/nodesets/nodepool-bionic.yml +++ /dev/null @@ -1,10 +0,0 @@ -HOSTS: - ubuntu-18.04-amd64: - roles: - - master - platform: ubuntu-18.04-amd64 - hypervisor: none - ip: 127.0.0.1 -CONFIG: - type: foss - set_env: false diff --git a/spec/acceptance/nodesets/nodepool-centos7.yml b/spec/acceptance/nodesets/nodepool-centos7.yml deleted file mode 100644 index c5528742..00000000 --- a/spec/acceptance/nodesets/nodepool-centos7.yml +++ /dev/null @@ -1,10 +0,0 @@ -HOSTS: - centos-70-x64: - roles: - - master - platform: el-7-x86_64 - hypervisor: none - ip: 127.0.0.1 -CONFIG: - type: foss - set_env: false diff --git a/spec/acceptance/nodesets/nodepool-trusty.yml b/spec/acceptance/nodesets/nodepool-trusty.yml deleted file mode 100644 index 9fc624e2..00000000 --- a/spec/acceptance/nodesets/nodepool-trusty.yml +++ /dev/null @@ -1,10 +0,0 @@ -HOSTS: - ubuntu-14.04-amd64: - roles: - - master - platform: ubuntu-14.04-amd64 - hypervisor: none - ip: 127.0.0.1 -CONFIG: - type: foss - set_env: false diff --git a/spec/acceptance/nodesets/nodepool-xenial.yml b/spec/acceptance/nodesets/nodepool-xenial.yml deleted file mode 100644 index 99dd3187..00000000 --- a/spec/acceptance/nodesets/nodepool-xenial.yml +++ /dev/null @@ -1,10 +0,0 @@ -HOSTS: - ubuntu-16.04-amd64: - roles: - - master - platform: ubuntu-16.04-amd64 - hypervisor: none - ip: 127.0.0.1 -CONFIG: - type: foss - set_env: false diff --git a/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml b/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml deleted file mode 100644 index 8001929b..00000000 --- a/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml +++ /dev/null @@ -1,11 +0,0 @@ -HOSTS: - ubuntu-server-14.04-amd64: - roles: - - master - platform: ubuntu-14.04-amd64 - box: puppetlabs/ubuntu-14.04-64-nocm - box_url: https://vagrantcloud.com/puppetlabs/ubuntu-14.04-64-nocm - hypervisor: vagrant -CONFIG: - log_level: debug - type: foss diff --git a/spec/acceptance/openstacklib_policy_base_spec.rb b/spec/acceptance/openstacklib_policy_base_spec.rb new file mode 100644 index 00000000..80e8df2a --- /dev/null +++ b/spec/acceptance/openstacklib_policy_base_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper_acceptance' + +describe 'policy file management' do + + context 'with policy.yaml' do + it 'should work with no errors' do + pp= <<-EOS + Exec { logoutput => 'on_failure' } + openstacklib::policy::base { 'is_admin': + file_path => '/tmp/policy.yaml', + key => 'is_admin', + value => 'role:admin', + file_format => 'yaml', + } + openstacklib::policy::base { 'is_member': + file_path => '/tmp/policy.yaml', + key => 'is_member', + value => 'role:member', + file_format => 'yaml', + } + openstacklib::policy::base { 'get_router': + file_path => '/tmp/policy.yaml', + key => 'get_router', + value => 'rule:admin_or_owner', + file_format => 'yaml', + } + openstacklib::policy::base { 'get_router:distributed': + file_path => '/tmp/policy.yaml', + key => 'get_router:distributed', + value => 'rule:admin_only', + file_format => 'yaml', + } + + EOS + + # Run it twice and test for idempotency + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + describe file('/tmp/policy.yaml') do + it { should exist } + it { should contain("'is_admin': 'role:admin'") } + it { should contain("'is_member': 'role:member'") } + it { should contain("'get_router': 'rule:admin_or_owner'") } + it { should contain("'get_router:distributed': 'rule:admin_only'") } + end + end + +end diff --git a/spec/acceptance/rabbitmq_spec.rb b/spec/acceptance/rabbitmq_spec.rb index 8bde503b..bc929050 100644 --- a/spec/acceptance/rabbitmq_spec.rb +++ b/spec/acceptance/rabbitmq_spec.rb @@ -6,15 +6,15 @@ it 'should work with no errors' do pp= <<-EOS - include ::openstack_integration - include ::openstack_integration::repos - include ::openstack_integration::rabbitmq + include openstack_integration + include openstack_integration::repos + include openstack_integration::rabbitmq # openstacklib resources - include ::openstacklib::openstackclient + include openstacklib::openstackclient - ::openstacklib::messaging::rabbitmq { 'beaker': - userid => 'beaker', + ::openstacklib::messaging::rabbitmq { 'ci': + userid => 'ci', is_admin => true, } EOS @@ -25,15 +25,15 @@ end describe 'test rabbitmq resources' do - it 'should list rabbitmq beaker resources' do - shell('rabbitmqctl list_users') do |r| - expect(r.stdout).to match(/^beaker/) + it 'should list rabbitmq ci resources' do + command('rabbitmqctl list_users') do |r| + expect(r.stdout).to match(/^ci/) expect(r.stdout).not_to match(/^guest/) expect(r.exit_code).to eq(0) end - shell('rabbitmqctl list_permissions') do |r| - expect(r.stdout).to match(/^beaker\t\.\*\t\.\*\t\.\*$/) + command('rabbitmqctl list_permissions') do |r| + expect(r.stdout).to match(/^ci\t\.\*\t\.\*\t\.\*$/) expect(r.exit_code).to eq(0) end end diff --git a/spec/classes/openstacklib_iscsid_spec.rb b/spec/classes/openstacklib_iscsid_spec.rb new file mode 100644 index 00000000..42fff324 --- /dev/null +++ b/spec/classes/openstacklib_iscsid_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe 'openstacklib::iscsid' do + shared_examples_for 'openstacklib::iscsid' do + context 'with default params' do + it { is_expected.to contain_package('open-iscsi').with( + :name => platform_params[:open_iscsi_package_name], + :ensure => 'present', + :tag => 'openstack', + )} + + it { is_expected.to contain_exec('create-initiatorname-file').with({ + :command => 'echo "InitiatorName=`/usr/sbin/iscsi-iname`" > /etc/iscsi/initiatorname.iscsi', + :path => ['/usr/bin','/usr/sbin','/bin','/usr/bin'], + :creates => '/etc/iscsi/initiatorname.iscsi', + }).that_requires('Package[open-iscsi]')} + + it { is_expected.to contain_service('iscsid').with( + :ensure => 'running', + :enable => true, + ) } + end + + end + + on_supported_os({ + :supported_os => OSDefaults.get_supported_os + }).each do |os,facts| + context "on #{os}" do + let (:facts) do + facts.merge!(OSDefaults.get_facts()) + end + + let(:platform_params) do + case facts[:os]['family'] + when 'Debian' + { :open_iscsi_package_name => 'open-iscsi' } + when 'RedHat' + { :open_iscsi_package_name => 'iscsi-initiator-utils' } + end + end + + it_behaves_like 'openstacklib::iscsid' + end + end + +end diff --git a/spec/classes/openstacklib_openstackclient_spec.rb b/spec/classes/openstacklib_openstackclient_spec.rb index 29b984a4..b9b576ab 100644 --- a/spec/classes/openstacklib_openstackclient_spec.rb +++ b/spec/classes/openstacklib_openstackclient_spec.rb @@ -4,8 +4,8 @@ shared_examples_for 'openstacklib::openstackclient' do context 'with default params' do it { should contain_package(platform_params[:openstackclient_package_name]).with( - :ensure => 'present', - :tag => 'openstack' + :ensure => 'installed', + :tag => ['openstack', 'openstackclient'] )} end @@ -17,8 +17,8 @@ end it { should contain_package('my-openstackclient').with( - :ensure => 'present', - :tag => 'openstack' + :ensure => 'installed', + :tag => ['openstack', 'openstackclient'] )} end end @@ -32,15 +32,12 @@ end let(:platform_params) do - if facts[:osfamily] == 'Debian' or (facts[:os_package_type] == 'rpm' \ - and facts[:operatingsystemrelease].to_i > 7) then - openstackclient_package_name = 'python3-openstackclient' - else - openstackclient_package_name = 'python-openstackclient' + case facts[:os]['family'] + when 'Debian' + { :openstackclient_package_name => 'python3-openstackclient' } + when 'RedHat' + { :openstackclient_package_name => 'python3-openstackclient' } end - { - :openstackclient_package_name => openstackclient_package_name - } end it_behaves_like 'openstacklib::openstackclient' diff --git a/spec/classes/openstacklib_policy_spec.rb b/spec/classes/openstacklib_policy_spec.rb deleted file mode 100644 index 0b755a69..00000000 --- a/spec/classes/openstacklib_policy_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'spec_helper' - -describe 'openstacklib::policy' do - shared_examples 'openstacklib::policy' do - context 'with basic configuration' do - let :params do - { - :policies => { - 'foo' => { - 'file_path' => '/etc/nova/policy.json', - 'key' => 'context_is_admin', - 'value' => 'foo:bar' - } - } - } - end - - it { should contain_openstacklib__policy__base('foo').with( - :file_path => '/etc/nova/policy.json', - :key => 'context_is_admin', - :value => 'foo:bar' - )} - end - end - - on_supported_os({ - :supported_os => OSDefaults.get_supported_os - }).each do |os,facts| - context "on #{os}" do - let (:facts) do - facts.merge!(OSDefaults.get_facts()) - end - - it_behaves_like 'openstacklib::policy' - end - end - -end diff --git a/spec/classes/openstacklib_policyrcd_spec.rb b/spec/classes/openstacklib_policyrcd_spec.rb index ff31b157..5fb3d442 100644 --- a/spec/classes/openstacklib_policyrcd_spec.rb +++ b/spec/classes/openstacklib_policyrcd_spec.rb @@ -37,7 +37,7 @@ exit 101 fi - +exit 0 eof } @@ -65,7 +65,7 @@ exit 101 fi - +exit 0 eof } @@ -93,7 +93,7 @@ facts.merge!(OSDefaults.get_facts()) end - it_behaves_like "openstacklib::policyrcd on #{facts[:osfamily]} platforms" + it_behaves_like "openstacklib::policyrcd on #{facts[:os]['family']} platforms" end end end diff --git a/spec/defines/openstacklib_clouds_spec.rb b/spec/defines/openstacklib_clouds_spec.rb new file mode 100644 index 00000000..21fe1ee1 --- /dev/null +++ b/spec/defines/openstacklib_clouds_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' + +describe 'openstacklib::clouds' do + shared_examples 'openstacklib::clouds' do + let :title do + '/etc/openstack/clouds.yaml' + end + + context 'with the required parameters' do + let :params do + { + :username => 'admin', + :password => 'secrete', + :auth_url => 'http://127.0.0.1:5000/', + :project_name => 'demo', + } + end + + it 'creates a clouds.yaml file' do + should contain_file('/etc/openstack/clouds.yaml').with( + :mode => '0600', + :owner => 'root', + :group => 'root', + ) + end + end + + context 'with file owner/group' do + let :params do + { + :username => 'admin', + :password => 'secrete', + :auth_url => 'http://127.0.0.1:5000/', + :project_name => 'demo', + :file_user => 'foo', + :file_group => 'bar', + } + end + + it 'creates a clouds.yaml file with correct ownership' do + should contain_file('/etc/openstack/clouds.yaml').with( + :mode => '0600', + :owner => 'foo', + :group => 'bar', + ) + end + end + end + + on_supported_os({ + :supported_os => OSDefaults.get_supported_os + }).each do |os,facts| + context "on #{os}" do + let (:facts) do + facts.merge!(OSDefaults.get_facts()) + end + + it_behaves_like 'openstacklib::clouds' + end + end + +end diff --git a/spec/defines/openstacklib_db_mysql_host_access_spec.rb b/spec/defines/openstacklib_db_mysql_host_access_spec.rb index e568ca25..ef47537e 100644 --- a/spec/defines/openstacklib_db_mysql_host_access_spec.rb +++ b/spec/defines/openstacklib_db_mysql_host_access_spec.rb @@ -21,6 +21,33 @@ end it { should contain_mysql_user("#{params[:user]}@10.0.0.1").with( + :plugin => nil, + :password_hash => params[:password_hash], + :tls_options => ['NONE'] + )} + + it { should contain_mysql_grant("#{params[:user]}@10.0.0.1/#{params[:database]}.*").with( + :user => "#{params[:user]}@10.0.0.1", + :privileges => 'ALL', + :table => "#{params[:database]}.*" + )} + end + + context 'with overriding authentication plugin' do + let (:title) { 'nova_10.0.0.1' } + + let :params do + { + :user => 'foobar', + :plugin => 'mysql_native_password', + :password_hash => 'AA1420F182E88B9E5F874F6FBE7459291E8F4601', + :database => 'nova', + :privileges => 'ALL' + } + end + + it { should contain_mysql_user("#{params[:user]}@10.0.0.1").with( + :plugin => params[:plugin], :password_hash => params[:password_hash], :tls_options => ['NONE'] )} @@ -68,6 +95,7 @@ end it { should contain_mysql_user("#{params[:user]}@10.0.0.1").with( + :plugin => nil, :password_hash => params[:password_hash] )} diff --git a/spec/defines/openstacklib_db_mysql_spec.rb b/spec/defines/openstacklib_db_mysql_spec.rb index 9ada7283..44533d70 100644 --- a/spec/defines/openstacklib_db_mysql_spec.rb +++ b/spec/defines/openstacklib_db_mysql_spec.rb @@ -9,7 +9,7 @@ let :required_params do { - :password_hash => 'AA1420F182E88B9E5F874F6FBE7459291E8F4601' + :password => 'fooboozoo_default_password', } end @@ -26,6 +26,7 @@ it { should contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( :user => title, + :plugin => nil, :database => title, :privileges => 'ALL', :tls_options => ['NONE'], @@ -44,6 +45,7 @@ it { should contain_openstacklib__db__mysql__host_access("#{params[:dbname]}_127.0.0.1").with( :user => title, + :plugin => nil, :database => params[:dbname], :privileges => 'ALL', :create_user => true, @@ -64,6 +66,7 @@ it { should contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( :user => params[:user], + :plugin => nil, :database => title, :privileges => 'ALL', :create_user => true, @@ -72,6 +75,30 @@ )} end + context 'with overriding authentication plugin' do + let :params do + required_params.merge!( + :plugin => 'mysql_native_password', + ) + end + + it { should contain_mysql_database(title).with( + :charset => 'utf8', + :collate => 'utf8_general_ci' + )} + + it { should contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( + :user => title, + :plugin => params[:plugin], + :password_hash => '*3DDF34A86854A312A8E2C65B506E21C91800D206', + :database => title, + :privileges => 'ALL', + :create_user => true, + :create_grant => true, + :tls_options => ['NONE'], + )} + end + context 'when overriding charset parameter' do let :params do required_params.merge!( :charset => 'latin1' ) @@ -80,7 +107,7 @@ it { should contain_mysql_database(title).with_charset(params[:charset]) } end - context 'when omitting the required parameter password_hash' do + context 'when omitting the required parameter password' do let :params do {} end @@ -88,6 +115,17 @@ it { should raise_error(Puppet::Error) } end + context 'when deprecated password_hash is used' do + let :params do + { :password_hash => '*3DDF34A86854A312A8E2C65B506E21C91800D206' } + end + + it { should contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( + :user => title, + :password_hash => '*3DDF34A86854A312A8E2C65B506E21C91800D206', + )} + end + context 'when notifying other resources' do let :pre_condition do 'exec {"nova-db-sync":}' @@ -123,13 +161,15 @@ it { should contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( :user => title, - :password_hash => params[:password_hash], + :plugin => nil, + :password_hash => '*3DDF34A86854A312A8E2C65B506E21C91800D206', :database => title )} it { should contain_openstacklib__db__mysql__host_access("#{title}_%").with( :user => title, - :password_hash => params[:password_hash], + :plugin => nil, + :password_hash => '*3DDF34A86854A312A8E2C65B506E21C91800D206', :database => title )} end @@ -141,7 +181,8 @@ it { should contain_openstacklib__db__mysql__host_access("#{title}_192.168.1.1").with( :user => title, - :password_hash => params[:password_hash], + :plugin => nil, + :password_hash => '*3DDF34A86854A312A8E2C65B506E21C91800D206', :database => title )} end @@ -153,7 +194,8 @@ it { should contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( :user => title, - :password_hash => params[:password_hash], + :plugin => nil, + :password_hash => '*3DDF34A86854A312A8E2C65B506E21C91800D206', :database => title )} end @@ -170,6 +212,7 @@ it { should contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( :user => title, + :plugin => nil, :database => title, :privileges => 'ALL', :create_user => false, @@ -189,6 +232,7 @@ it { should contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( :user => title, + :plugin => nil, :database => title, :privileges => 'ALL', :create_user => true, @@ -217,7 +261,8 @@ it { should contain_openstacklib__db__mysql__host_access("#{title}_127.0.0.1").with( :user => title, - :password_hash => params[:password_hash], + :plugin => nil, + :password_hash => '*3DDF34A86854A312A8E2C65B506E21C91800D206', :database => title, :tls_options => ['SSL'], )} diff --git a/spec/defines/openstacklib_db_postgresql_spec.rb b/spec/defines/openstacklib_db_postgresql_spec.rb index 3e407e2b..762f3aff 100644 --- a/spec/defines/openstacklib_db_postgresql_spec.rb +++ b/spec/defines/openstacklib_db_postgresql_spec.rb @@ -5,12 +5,12 @@ let :required_params do { - :password_hash => 'AA1420F182E88B9E5F874F6FBE7459291E8F4601' + :password => 'pw' } end let (:pre_condition) do - "include ::postgresql::server" + "include postgresql::server" end shared_examples 'openstacklib::db::postgresql examples' do @@ -19,9 +19,18 @@ required_params end + let :password_hash do + case platform_params[:password_encryption] + when 'scram-sha-256' + 'SCRAM-SHA-256$4096:bm92YQ==$LiUdLrky9dt8Js3NPwLr3TrmmuQBa0NG/xmahcp98UM=:dVY0oEQewk/17+9zFMDkBTek1NRyTAt3iyyfLKHIR8M=' + else + 'md557ae0608fad632bf0155cb9502a6b454' + end + end + it { should contain_postgresql__server__db(title).with( :user => title, - :password => params[:password_hash] + :password => password_hash, )} end @@ -43,7 +52,7 @@ context 'when notifying other resources' do let :pre_condition do - "include ::postgresql::server + "include postgresql::server exec { 'nova-db-sync': }" end @@ -56,7 +65,7 @@ context 'when required for other openstack services' do let :pre_condition do - "include ::postgresql::server + "include postgresql::server service {'keystone':}" end @@ -70,6 +79,17 @@ it { should contain_service('keystone').that_requires("Openstacklib::Db::Postgresql[keystone]") } end + + context 'when deprecated password_hash is used' do + let :params do + { :password_hash => 'md557ae0608fad632bf0155cb9502a6b454' } + end + + it { should contain_postgresql__server__db(title).with( + :user => title, + :password => 'md557ae0608fad632bf0155cb9502a6b454' + )} + end end on_supported_os({ @@ -77,7 +97,20 @@ }).each do |os,facts| context "on #{os}" do let (:facts) do - facts.merge!(OSDefaults.get_facts()) + facts.merge!(OSDefaults.get_facts({ + # puppet-postgresql requires the service_provider fact provided by + # puppetlabs-postgresql. + :service_provider => 'systemd' + })) + end + + let :platform_params do + case facts[:os]['family'] + when 'Debian' + { :password_encryption => 'scram-sha-256' } + when 'RedHat' + { :password_encryption => 'ms5' } + end end it_behaves_like 'openstacklib::db::postgresql examples' diff --git a/spec/defines/openstacklib_policy_base_spec.rb b/spec/defines/openstacklib_policy_base_spec.rb index e29f6906..e97bfba2 100644 --- a/spec/defines/openstacklib_policy_base_spec.rb +++ b/spec/defines/openstacklib_policy_base_spec.rb @@ -2,44 +2,104 @@ describe 'openstacklib::policy::base' do shared_examples 'openstacklib::policy::base' do - context 'with some basic parameters' do - let :title do - 'nova-contest_is_admin' - end + let :title do + 'context_is_admin or owner' + end + context 'with policy.yaml' do let :params do { - :file_path => '/etc/nova/policy.json', - :key => 'context_is_admin or owner', + :file_path => '/etc/nova/policy.yaml', :value => 'foo:bar', :file_mode => '0644', :file_user => 'foo', - :file_group => 'bar' + :file_group => 'bar', } end - it { should contain_file('/etc/nova/policy.json').with( - :mode => '0644', - :owner => 'foo', - :group => 'bar' + it { should contain_openstacklib__policy__default('/etc/nova/policy.yaml').with( + :file_mode => '0644', + :file_user => 'foo', + :file_group => 'bar', + :file_format => 'yaml', + :purge_config => false, )} - it { should contain_augeas('/etc/nova/policy.json-context_is_admin or owner-foo:bar').with( - :lens => 'Json.lns', - :incl => '/etc/nova/policy.json', - :changes => 'set dict/entry[*][.="context_is_admin or owner"]/string "foo:bar"', - )} + it { should contain_file_line('/etc/nova/policy.yaml-context_is_admin or owner').with( + :path => '/etc/nova/policy.yaml', + :line => '\'context_is_admin or owner\': \'foo:bar\'', + :match => '^[\'"]?context_is_admin or owner(?!:)[\'"]?\s*:.+' + ) } + + context 'with single-quotes in value' do + before do + params.merge!({ + :value => 'foo:\'bar\'' + }) + end + + it { should contain_file_line('/etc/nova/policy.yaml-context_is_admin or owner').with( + :path => '/etc/nova/policy.yaml', + :line => '\'context_is_admin or owner\': \'foo:\'\'bar\'\'\'', + :match => '^[\'"]?context_is_admin or owner(?!:)[\'"]?\s*:.+' + ) } + end + + context 'with pre-formatted single-quotes in value' do + before do + params.merge!({ + :value => 'foo:\'\'bar\'\'' + }) + end + + it { should contain_file_line('/etc/nova/policy.yaml-context_is_admin or owner').with( + :path => '/etc/nova/policy.yaml', + :line => '\'context_is_admin or owner\': \'foo:\'\'bar\'\'\'', + :match => '^[\'"]?context_is_admin or owner(?!:)[\'"]?\s*:.+' + ) } + end + end + + context 'with purge_config enabled' do + let :params do + { + :file_path => '/etc/nova/policy.yaml', + :value => 'foo:bar', + :file_mode => '0644', + :file_user => 'foo', + :file_group => 'bar', + :purge_config => true, + } + end - it { should contain_augeas('/etc/nova/policy.json-context_is_admin or owner-foo:bar-add').with( - :lens => 'Json.lns', - :incl => '/etc/nova/policy.json', - :changes => [ - 'set dict/entry[last()+1] "context_is_admin or owner"', - 'set dict/entry[last()]/string "foo:bar"' - ], - :onlyif => 'match dict/entry[*][.="context_is_admin or owner"] size == 0' + it { should contain_openstacklib__policy__default('/etc/nova/policy.yaml').with( + :file_mode => '0644', + :file_user => 'foo', + :file_group => 'bar', + :file_format => 'yaml', + :purge_config => true, )} end + + context 'with key overridden' do + let :params do + { + :file_path => '/etc/nova/policy.yaml', + :key => 'context_is_admin', + :value => 'foo:bar', + :file_mode => '0644', + :file_user => 'foo', + :file_group => 'bar', + :file_format => 'yaml', + } + end + + it { should contain_file_line('/etc/nova/policy.yaml-context_is_admin').with( + :path => '/etc/nova/policy.yaml', + :line => '\'context_is_admin\': \'foo:bar\'', + :match => '^[\'"]?context_is_admin(?!:)[\'"]?\s*:.+' + ) } + end end on_supported_os({ diff --git a/spec/defines/openstacklib_policy_default_spec.rb b/spec/defines/openstacklib_policy_default_spec.rb new file mode 100644 index 00000000..e042f576 --- /dev/null +++ b/spec/defines/openstacklib_policy_default_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' + +describe 'openstacklib::policy::default' do + shared_examples 'openstacklib::policy::default' do + context 'with policy.yaml' do + let :title do + '/etc/nova/policy.yaml' + end + + let :params do + { + :file_mode => '0644', + :file_user => 'foo', + :file_group => 'bar', + } + end + + it { should contain_file('/etc/nova/policy.yaml').with( + :mode => '0644', + :owner => 'foo', + :group => 'bar', + :content => '', + :replace => false + )} + end + + context 'with purge_config enabled' do + let :title do + '/etc/nova/policy.yaml' + end + + let :params do + { + :file_mode => '0644', + :file_user => 'foo', + :file_group => 'bar', + :purge_config => true, + } + end + + it { should contain_file('/etc/nova/policy.yaml').with( + :mode => '0644', + :owner => 'foo', + :group => 'bar', + :content => '', + :replace => true + )} + end + end + + on_supported_os({ + :supported_os => OSDefaults.get_supported_os + }).each do |os,facts| + context "on #{os}" do + let (:facts) do + facts.merge!(OSDefaults.get_facts()) + end + + it_behaves_like 'openstacklib::policy::default' + end + end +end diff --git a/spec/defines/openstacklib_policy_spec.rb b/spec/defines/openstacklib_policy_spec.rb new file mode 100644 index 00000000..45f3b090 --- /dev/null +++ b/spec/defines/openstacklib_policy_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +describe 'openstacklib::policy' do + shared_examples 'openstacklib::policy' do + context 'with basic configuration' do + let :title do + '/etc/nova/policy.yaml' + end + + let :params do + { + :policies => { + 'foo' => { + 'key' => 'context_is_admin', + 'value' => 'foo:bar' + } + }, + :file_mode => '0644', + :file_user => 'foo', + :file_group => 'baa', + } + end + + it { should contain_openstacklib__policy__base('foo').with( + :file_path => '/etc/nova/policy.yaml', + :key => 'context_is_admin', + :value => 'foo:bar' + )} + end + + context 'with empty policies and purge_config enabled' do + let :title do + '/etc/nova/policy.yaml' + end + + let :params do + { + :file_mode => '0644', + :file_user => 'foo', + :file_group => 'baa', + :purge_config => true, + } + end + + it { should contain_openstacklib__policy__default('/etc/nova/policy.yaml').with( + :file_mode => '0644', + :file_user => 'foo', + :file_group => 'baa', + :file_format => 'yaml', + :purge_config => true, + )} + end + end + + on_supported_os({ + :supported_os => OSDefaults.get_supported_os + }).each do |os,facts| + context "on #{os}" do + let (:facts) do + facts.merge!(OSDefaults.get_facts()) + end + + it_behaves_like 'openstacklib::policy' + end + end + +end diff --git a/spec/defines/openstacklib_wsgi_apache_spec.rb b/spec/defines/openstacklib_wsgi_apache_spec.rb index 81df3f5d..a14a1693 100644 --- a/spec/defines/openstacklib_wsgi_apache_spec.rb +++ b/spec/defines/openstacklib_wsgi_apache_spec.rb @@ -20,25 +20,16 @@ describe 'openstacklib::wsgi::apache' do let (:title) { 'keystone_wsgi' } - let :global_facts do - { - :os_workers => 8, - :concat_basedir => '/var/lib/puppet/concat', - :fqdn => 'some.host.tld' - } - end - let :params do { :bind_port => 5000, :group => 'keystone', :ssl => true, + :ssl_verify_client => 'optional', :user => 'keystone', :wsgi_script_dir => '/var/www/cgi-bin/keystone', :wsgi_script_file => 'main', :wsgi_script_source => '/usr/share/keystone/keystone.wsgi', - :access_log_file => false, - :access_log_format => false, } end @@ -46,7 +37,6 @@ it { should contain_service('httpd').with_name(platform_params[:httpd_service_name]) should contain_class('apache') - should contain_class('apache::mod::wsgi') } context 'with default parameters' do @@ -68,33 +58,28 @@ )} it { should contain_apache__vhost('keystone_wsgi').with( - :servername => 'some.host.tld', + :servername => 'foo.example.com', :ip => nil, :port => '5000', :docroot => '/var/www/cgi-bin/keystone', :docroot_owner => 'keystone', :docroot_group => 'keystone', - :ssl => 'true', - :wsgi_daemon_process => 'keystone_wsgi', + :setenv => [], + :ssl => true, + :ssl_verify_client => 'optional', + :wsgi_daemon_process => { + 'keystone_wsgi' => { + 'user' => 'keystone', + 'group' => 'keystone', + 'processes' => facts[:os_workers], + 'threads' => 1, + 'display-name' => 'keystone_wsgi', + }}, :wsgi_process_group => 'keystone_wsgi', :wsgi_script_aliases => { '/' => "/var/www/cgi-bin/keystone/main" }, - :wsgi_daemon_process_options => { - 'user' => 'keystone', - 'group' => 'keystone', - 'processes' => global_facts[:os_workers], - 'threads' => 1, - 'display-name' => 'keystone_wsgi', - }, :wsgi_application_group => '%{GLOBAL}', - :headers => nil, :setenvif => ['X-Forwarded-Proto https HTTPS=1'], - :access_log_file => false, - :access_log_pipe => false, - :access_log_syslog => false, - :access_log_format => false, - :error_log_file => nil, - :error_log_pipe => nil, - :error_log_syslog => nil + :options => ['-Indexes', '+FollowSymLinks'], )} it { should contain_concat("#{platform_params[:httpd_ports_file]}") } @@ -108,13 +93,20 @@ :wsgi_script_source => '/usr/share/keystone/keystone.wsgi', :wsgi_pass_authorization => 'On', :wsgi_chunked_request => 'On', - :custom_wsgi_script_aliases => { '/admin' => '/var/www/cgi-bin/keystone/admin' }, - :headers => 'set X-Frame-Options "DENY"', + :custom_wsgi_script_aliases => { + '/admin' => '/var/www/cgi-bin/keystone/admin' + }, + :headers => ['set X-Frame-Options "DENY"'], + :request_headers => ['set Content-Type "application/json"'], + :aliases => [ + { 'alias' => '/robots.txt', 'path' => '/etc/keystone/robots.txt', } + ], :servername => 'dummy.host', :bind_host => '10.42.51.1', :bind_port => 4142, :user => 'keystone', :group => 'keystone', + :setenv => ['MYENV foo'], :ssl => false, :workers => 37, :vhost_custom_fragment => 'LimitRequestFieldSize 81900', @@ -122,8 +114,10 @@ :access_log_file => '/var/log/httpd/access_log', :access_log_syslog => 'syslog:local0', :access_log_format => 'some format', + :access_log_env_var => 'dontlog=1', :error_log_file => '/var/log/httpd/error_log', - :error_log_syslog => 'syslog:local0' + :error_log_syslog => 'syslog:local0', + :log_level => 'reqtimeout:info', } end @@ -132,15 +126,16 @@ :ip => '10.42.51.1', :port => '4142', :docroot => "/var/www/cgi-bin/keystone", + :setenv => ['MYENV foo'], :ssl => 'false', - :wsgi_daemon_process => 'keystone_wsgi', - :wsgi_daemon_process_options => { - 'user' => 'keystone', - 'group' => 'keystone', - 'processes' => '37', - 'threads' => '1', - 'display-name' => 'keystone_wsgi', - }, + :wsgi_daemon_process => { + 'keystone_wsgi' => { + 'user' => 'keystone', + 'group' => 'keystone', + 'processes' => '37', + 'threads' => '1', + 'display-name' => 'keystone_wsgi', + }}, :wsgi_process_group => 'keystone_wsgi', :wsgi_script_aliases => { '/' => '/var/www/cgi-bin/keystone/main', @@ -149,14 +144,20 @@ :wsgi_application_group => '%{GLOBAL}', :wsgi_pass_authorization => 'On', :wsgi_chunked_request => 'On', - :headers => 'set X-Frame-Options "DENY"', + :headers => ['set X-Frame-Options "DENY"'], + :request_headers => ['set Content-Type "application/json"'], + :aliases => [ + { 'alias' => '/robots.txt', 'path' => '/etc/keystone/robots.txt', } + ], :custom_fragment => 'LimitRequestFieldSize 81900', :allow_encoded_slashes => 'on', :access_log_file => '/var/log/httpd/access_log', :access_log_syslog => 'syslog:local0', :access_log_format => 'some format', + :access_log_env_var => 'dontlog=1', :error_log_file => '/var/log/httpd/error_log', - :error_log_syslog => 'syslog:local0' + :error_log_syslog => 'syslog:local0', + :log_level => 'reqtimeout:info', )} end @@ -179,14 +180,15 @@ end it { should contain_apache__vhost('keystone_wsgi').with( - :wsgi_daemon_process_options => { - 'user' => 'someotheruser', - 'group' => 'someothergroup', - 'processes' => global_facts[:os_workers], - 'threads' => 1, - 'display-name' => 'keystone_wsgi', - 'python_path' => '/my/python/admin/path', - }, + :wsgi_daemon_process => { + 'keystone_wsgi' => { + 'user' => 'someotheruser', + 'group' => 'someothergroup', + 'processes' => facts[:os_workers], + 'threads' => 1, + 'display-name' => 'keystone_wsgi', + 'python_path' => '/my/python/admin/path', + }}, )} end @@ -251,11 +253,13 @@ }).each do |os,facts| context "on #{os}" do let (:facts) do - facts.merge!(OSDefaults.get_facts(global_facts)) + facts.merge!(OSDefaults.get_facts({ + :os_workers => 8, + })) end let(:platform_params) do - case facts[:osfamily] + case facts[:os]['family'] when 'Debian' { :httpd_service_name => 'apache2', diff --git a/spec/functions/os_url_spec.rb b/spec/functions/os_url_spec.rb new file mode 100644 index 00000000..825989a1 --- /dev/null +++ b/spec/functions/os_url_spec.rb @@ -0,0 +1,117 @@ +require 'spec_helper' + +describe 'os_url' do + + it 'refuses String' do + is_expected.to run.with_params('foo').\ + and_raise_error(Puppet::ParseError, /Requires an hash/) + end + + it 'refuses Array' do + is_expected.to run.with_params(['foo']).\ + and_raise_error(Puppet::ParseError, /Requires an hash/) + end + + it 'refuses without at least one argument' do + is_expected.to run.with_params().\ + and_raise_error(Puppet::ParseError, /Wrong number of arguments/) + end + + it 'refuses too many arguments' do + is_expected.to run.with_params('foo', 'bar').\ + and_raise_error(Puppet::ParseError, /Wrong number of arguments/) + end + + it 'refuses query params passed as String' do + is_expected.to run.with_params({ + 'query' => 'key=value' + }).and_raise_error(Puppet::ParseError, /query should be a Hash/) + end + + it 'fails if port is provided with missing host' do + is_expected.to run.with_params({ + 'port' => '8080', + }).and_raise_error(Puppet::ParseError, /host is required with port/) + end + + context 'creates the correct connection URI' do + + it 'with all parameters' do + is_expected.to run.with_params({ + 'scheme' => 'https', + 'host' => '127.0.0.1', + 'port' => '443', + 'path' => '/test', + 'username' => 'guest', + 'password' => 's3cr3t', + 'query' => { 'key1' => 'value1', 'key2' => 'value2' } + }).and_return('https://guest:s3cr3t@127.0.0.1:443/test?key1=value1&key2=value2') + end + + it 'without port' do + is_expected.to run.with_params({ + 'host' => '127.0.0.1', + 'path' => '/test', + 'username' => 'guest', + 'password' => 's3cr3t', + }).and_return('http://guest:s3cr3t@127.0.0.1/test') + end + + it 'without host and port' do + is_expected.to run.with_params({ + 'scheme' => 'file', + 'path' => '/test', + }).and_return('file:///test') + end + + it 'without username and password' do + is_expected.to run.with_params({ + 'host' => '127.0.0.1', + }).and_return('http://127.0.0.1') + end + + it 'with username set to undef' do + is_expected.to run.with_params({ + 'host' => '127.0.0.1', + 'username' => :undef, + }).and_return('http://127.0.0.1') + end + + it 'with username set to an empty string' do + is_expected.to run.with_params({ + 'host' => '127.0.0.1', + 'username' => '', + }).and_return('http://127.0.0.1') + end + + it 'without password' do + is_expected.to run.with_params({ + 'host' => '127.0.0.1', + 'username' => 'guest', + }).and_return('http://guest@127.0.0.1') + end + + it 'with password' do + is_expected.to run.with_params({ + 'host' => '127.0.0.1', + 'password' => 's3cr3t', + }).and_return('http://:s3cr3t@127.0.0.1') + end + + it 'with password set to undef' do + is_expected.to run.with_params({ + 'host' => '127.0.0.1', + 'username' => 'guest', + 'password' => :undef, + }).and_return('http://guest@127.0.0.1') + end + + it 'with password set to an empty string' do + is_expected.to run.with_params({ + 'host' => '127.0.0.1', + 'username' => 'guest', + 'password' => '', + }).and_return('http://guest@127.0.0.1') + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cad00b19..f0eccce8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,10 +1,17 @@ +# Load libraries here to simulate how they live together in a real puppet run (for provider unit tests) +$LOAD_PATH.push(File.join(File.dirname(__FILE__), 'fixtures', 'modules', 'inifile', 'lib')) + require 'puppetlabs_spec_helper/module_spec_helper' require 'shared_examples' require 'puppet-openstack_spec_helper/facts' +fixture_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures')) + RSpec.configure do |c| c.alias_it_should_behave_like_to :it_configures, 'configures' c.alias_it_should_behave_like_to :it_raises, 'raises' + + c.module_path = File.join(fixture_path, 'modules') end at_exit { RSpec::Puppet::Coverage.report! } diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb index 9196bc99..d51dfdbf 100644 --- a/spec/spec_helper_acceptance.rb +++ b/spec/spec_helper_acceptance.rb @@ -1 +1 @@ -require 'puppet-openstack_spec_helper/beaker_spec_helper' +require 'puppet-openstack_spec_helper/litmus_spec_helper' diff --git a/spec/type_aliases/policies_spec.rb b/spec/type_aliases/policies_spec.rb new file mode 100644 index 00000000..be79b1e9 --- /dev/null +++ b/spec/type_aliases/policies_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe 'Openstacklib::Policies' do + describe 'valid types' do + context 'with valid types' do + [ + {}, + {'name' => {'key' => 'mykey', 'value' => 'myvalue'}}, + {'name' => {'value' => 'myvalue'}}, + ].each do |value| + describe value.inspect do + it { is_expected.to allow_value(value) } + end + end + end + end + + describe 'invalid types' do + context 'with garbage inputs' do + [ + {'name' => {}}, + {'name' => {'key' => 'mykey'}}, + {'name' => {'key' => 1, 'value' => 'myvalue'}}, + {'name' => {'key' => 'mykey', 'value' => 1}}, + {'name' => {'key' => 'mykey', 'value' => 'myvalue', 'foo' => 'bar'}}, + {'name' => {'value' => 'myvalue', 'foo' => 'bar'}}, + {0 => {'key' => 1, 'value' => 'myvalue'}}, + ].each do |value| + describe value.inspect do + it { is_expected.not_to allow_value(value) } + end + end + end + end +end + diff --git a/spec/type_aliases/servicedefault_spec.rb b/spec/type_aliases/servicedefault_spec.rb new file mode 100644 index 00000000..f1bbbe6e --- /dev/null +++ b/spec/type_aliases/servicedefault_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe 'Openstacklib::ServiceDefault' do + describe 'valid types' do + context 'with valid types' do + [ + '', + ].each do |value| + describe value.inspect do + it { is_expected.to allow_value(value) } + end + end + end + end + + describe 'invalid types' do + context 'with garbage inputs' do + [ + 'somethink', + true, + nil, + {}, + '', + 55555, + ].each do |value| + describe value.inspect do + it { is_expected.not_to allow_value(value) } + end + end + end + end +end + diff --git a/spec/unit/facter/os_workers_heat_engine_spec.rb b/spec/unit/facter/os_workers_heat_engine_spec.rb index 6b9c09f9..b891b98d 100644 --- a/spec/unit/facter/os_workers_heat_engine_spec.rb +++ b/spec/unit/facter/os_workers_heat_engine_spec.rb @@ -2,11 +2,11 @@ describe 'os_workers_heat_engine' do - before { Facter.flush } + before { Facter.clear } context 'with processorcount=1' do before do - Facter.fact(:processorcount).stubs(:value).returns(1) + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 1}) end it 'returns a minimum of 2' do @@ -16,7 +16,7 @@ context 'with processorcount=8' do before do - Facter.fact(:processorcount).stubs(:value).returns(8) + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 8}) end it 'returns processorcount/2' do @@ -26,7 +26,7 @@ context 'with processorcount=64' do before do - Facter.fact(:processorcount).stubs(:value).returns(64) + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 64}) end it 'returns a maximum of 24' do diff --git a/spec/unit/facter/os_workers_keystone_spec.rb b/spec/unit/facter/os_workers_keystone_spec.rb new file mode 100644 index 00000000..75543ad7 --- /dev/null +++ b/spec/unit/facter/os_workers_keystone_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe 'os_workers_keystone' do + + before { Facter.clear } + + context 'with processorcount=1' do + before do + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 1}) + end + + it 'returns a minimum of 4' do + expect(Facter.fact(:os_workers_keystone).value).to eq(4) + end + end + + context 'with processorcount=8' do + before do + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 8}) + end + + it 'returns processorcount' do + expect(Facter.fact(:os_workers_keystone).value).to eq(8) + end + end + + context 'with processorcount=32' do + before do + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 32}) + end + + it 'returns a maximum of 24' do + expect(Facter.fact(:os_workers_keystone).value).to eq(24) + end + end +end diff --git a/spec/unit/facter/os_workers_large_spec.rb b/spec/unit/facter/os_workers_large_spec.rb index 5df5c08e..0fedc849 100644 --- a/spec/unit/facter/os_workers_large_spec.rb +++ b/spec/unit/facter/os_workers_large_spec.rb @@ -2,11 +2,11 @@ describe 'os_workers_large' do - before { Facter.flush } + before { Facter.clear } context 'with processorcount=1' do before do - Facter.fact(:processorcount).stubs(:value).returns(1) + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 1}) end it 'returns a minimum of 1' do @@ -16,7 +16,7 @@ context 'with processorcount=8' do before do - Facter.fact(:processorcount).stubs(:value).returns(8) + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 8}) end it 'returns processorcount/2' do diff --git a/spec/unit/facter/os_workers_small_spec.rb b/spec/unit/facter/os_workers_small_spec.rb index e5a1e9e3..a698edd5 100644 --- a/spec/unit/facter/os_workers_small_spec.rb +++ b/spec/unit/facter/os_workers_small_spec.rb @@ -2,11 +2,11 @@ describe 'os_workers_small' do - before { Facter.flush } + before { Facter.clear } context 'with processorcount=1' do before do - Facter.fact(:processorcount).stubs(:value).returns(1) + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 1}) end it 'returns a minimum of 2' do @@ -16,7 +16,7 @@ context 'with processorcount=16' do before do - Facter.fact(:processorcount).stubs(:value).returns(16) + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 16}) end it 'returns processorcount/4' do @@ -26,7 +26,7 @@ context 'with processorcount=32' do before do - Facter.fact(:processorcount).stubs(:value).returns(32) + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 32}) end it 'returns a maximum of 8' do diff --git a/spec/unit/facter/os_workers_spec.rb b/spec/unit/facter/os_workers_spec.rb index 78588924..e5d70f71 100644 --- a/spec/unit/facter/os_workers_spec.rb +++ b/spec/unit/facter/os_workers_spec.rb @@ -2,11 +2,11 @@ describe 'os_workers' do - before { Facter.flush } + before { Facter.clear } context 'with processorcount=1' do before do - Facter.fact(:processorcount).stubs(:value).returns(1) + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 1}) end it 'returns a minimum of 2' do @@ -16,7 +16,7 @@ context 'with processorcount=8' do before do - Facter.fact(:processorcount).stubs(:value).returns(8) + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 8}) end it 'returns processorcount/2' do @@ -26,7 +26,7 @@ context 'with processorcount=32' do before do - Facter.fact(:processorcount).stubs(:value).returns(32) + allow(Facter.fact(:processors)).to receive(:value).and_return({'count' => 32}) end it 'returns a maximum of 12' do diff --git a/spec/unit/provider/openstack/auth_spec.rb b/spec/unit/provider/openstack/auth_spec.rb index dca29130..a731de18 100644 --- a/spec/unit/provider/openstack/auth_spec.rb +++ b/spec/unit/provider/openstack/auth_spec.rb @@ -62,12 +62,6 @@ class Puppet::Provider::Openstack::AuthTester < Puppet::Provider::Openstack end end - describe '#rc_filename' do - it 'returns RCFILENAME' do - expect(klass.rc_filename).to eq("#{ENV['HOME']}/openrc") - end - end - describe '#get_os_from_env' do context 'with Openstack environment variables set' do it 'provides a hash' do @@ -85,68 +79,41 @@ class Puppet::Provider::Openstack::AuthTester < Puppet::Provider::Openstack end end - describe '#get_os_vars_from_rcfile' do - context 'with a valid RC file' do + describe '#get_os_vars_from_cloudsfile' do + context 'with a clouds.yaml present' do it 'provides a hash' do - mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_PROJECT_NAME='test'\nexport OS_AUTH_URL='http://127.0.0.1:5000'" - filename = 'file' - File.expects(:exists?).with('file').returns(true) - File.expects(:open).with('file').returns(StringIO.new(mock)) + expect(File).to receive(:exist?).with('/etc/openstack/puppet/clouds.yaml').and_return(true) - response = klass.get_os_vars_from_rcfile(filename) + response = klass.get_os_vars_from_cloudsfile('project') expect(response).to eq({ - "OS_AUTH_URL" => "http://127.0.0.1:5000", - "OS_PASSWORD" => "abc123", - "OS_PROJECT_NAME" => "test", - "OS_USERNAME" => "test"}) + 'OS_CLOUD' => 'project', + 'OS_CLIENT_CONFIG_FILE' => '/etc/openstack/puppet/clouds.yaml' + }) end end - context 'with a valid RC file with extra code in it' do + context 'with a admin-clouds.yaml present' do it 'provides a hash' do - mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_PROJECT_NAME='test'\nexport OS_AUTH_URL='http://127.0.0.1:5000'\n_openstack() {\n foo\n} " - filename = 'file' - File.expects(:exists?).with('file').returns(true) - File.expects(:open).with('file').returns(StringIO.new(mock)) + expect(File).to receive(:exist?).with('/etc/openstack/puppet/clouds.yaml').and_return(false) + expect(File).to receive(:exist?).with('/etc/openstack/puppet/admin-clouds.yaml').and_return(true) - response = klass.get_os_vars_from_rcfile(filename) + response = klass.get_os_vars_from_cloudsfile('project') expect(response).to eq({ - "OS_AUTH_URL" => "http://127.0.0.1:5000", - "OS_PASSWORD" => "abc123", - "OS_PROJECT_NAME" => "test", - "OS_USERNAME" => "test"}) + 'OS_CLOUD' => 'project', + 'OS_CLIENT_CONFIG_FILE' => '/etc/openstack/puppet/admin-clouds.yaml' + }) end end - context 'with an empty file' do + context 'with a clouds.yaml not present' do it 'provides an empty hash' do - filename = 'file' - File.expects(:exists?).with(filename).returns(true) - File.expects(:open).with(filename).returns(StringIO.new("")) + expect(File).to receive(:exist?).with('/etc/openstack/puppet/clouds.yaml').and_return(false) + expect(File).to receive(:exist?).with('/etc/openstack/puppet/admin-clouds.yaml').and_return(false) - response = klass.get_os_vars_from_rcfile(filename) + response = klass.get_os_vars_from_cloudsfile('project') expect(response).to eq({}) end end - - context 'with a nonexistent file' do - it 'should get default rcfile when no environment or openrc file' do - ENV.clear - mock = "export OS_USERNAME='user'\nexport OS_PASSWORD='secret'\nexport OS_PROJECT_NAME='project'\nexport OS_AUTH_URL='http://127.0.0.1:5000'" - filename = '/root/openrc' - - File.expects(:exists?).with("#{ENV['HOME']}/openrc").returns(false) - File.expects(:exists?).with(filename).returns(true) - File.expects(:open).with(filename).returns(StringIO.new(mock)) - - expect(klass.get_os_vars_from_rcfile("#{ENV['HOME']}/openrc")).to eq({ - 'OS_USERNAME' => 'user', - 'OS_PASSWORD' => 'secret', - 'OS_PROJECT_NAME' => 'project', - 'OS_AUTH_URL' => 'http://127.0.0.1:5000' - }) - end - end end before(:each) do @@ -165,15 +132,15 @@ class Puppet::Provider::Openstack::AuthTester context 'with user credentials in env' do it 'is successful' do - klass.expects(:get_os_vars_from_env) - .returns({ 'OS_USERNAME' => 'test', - 'OS_PASSWORD' => 'abc123', - 'OS_PROJECT_NAME' => 'test', - 'OS_AUTH_URL' => 'http://127.0.0.1:5000', - 'OS_NOT_VALID' => 'notvalid' }) - klass.expects(:openstack) + expect(klass).to receive(:get_os_vars_from_env) + .and_return({ 'OS_USERNAME' => 'test', + 'OS_PASSWORD' => 'abc123', + 'OS_PROJECT_NAME' => 'test', + 'OS_AUTH_URL' => 'http://127.0.0.1:5000', + 'OS_NOT_VALID' => 'notvalid' }) + expect(klass).to receive(:openstack) .with('project', 'list', '--quiet', '--format', 'csv', ['--long']) - .returns('"ID","Name","Description","Enabled" + .and_return('"ID","Name","Description","Enabled" "1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True ') response = klass.request('project', 'list', ['--long']) @@ -190,13 +157,13 @@ class Puppet::Provider::Openstack::AuthTester context 'with service token credentials in env' do it 'is successful' do - klass.expects(:get_os_vars_from_env) - .returns({ 'OS_TOKEN' => 'test', - 'OS_ENDPOINT' => 'http://127.0.0.1:5000', - 'OS_NOT_VALID' => 'notvalid' }) - klass.expects(:openstack) + expect(klass).to receive(:get_os_vars_from_env) + .and_return({ 'OS_TOKEN' => 'test', + 'OS_ENDPOINT' => 'http://127.0.0.1:5000', + 'OS_NOT_VALID' => 'notvalid' }) + expect(klass).to receive(:openstack) .with('project', 'list', '--quiet', '--format', 'csv', ['--long']) - .returns('"ID","Name","Description","Enabled" + .and_return('"ID","Name","Description","Enabled" "1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True ') response = klass.request('project', 'list', ['--long']) @@ -209,51 +176,24 @@ class Puppet::Provider::Openstack::AuthTester end end - context 'with a RC file containing user credentials' do + context 'with clouds.yaml file' do it 'is successful' do # return incomplete creds from env - klass.expects(:get_os_vars_from_env) - .returns({ 'OS_USERNAME' => 'incompleteusername', - 'OS_AUTH_URL' => 'incompleteauthurl' }) - mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_PROJECT_NAME='test'\nexport OS_AUTH_URL='http://127.0.0.1:5000'\nexport OS_NOT_VALID='notvalid'" - File.expects(:exists?).with("#{ENV['HOME']}/openrc").returns(true) - File.expects(:open).with("#{ENV['HOME']}/openrc").returns(StringIO.new(mock)) - klass.expects(:openstack) + expect(klass).to receive(:get_os_vars_from_env) + .and_return({ 'OS_USERNAME' => 'incompleteusername', + 'OS_AUTH_URL' => 'incompleteauthurl' }) + expect(File).to receive(:exist?).with('/etc/openstack/puppet/clouds.yaml').and_return(true) + expect(klass).to receive(:openstack) .with('project', 'list', '--quiet', '--format', 'csv', ['--long']) - .returns('"ID","Name","Description","Enabled" + .and_return('"ID","Name","Description","Enabled" "1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True ') response = provider.class.request('project', 'list', ['--long']) expect(response.first[:description]).to eq("Test tenant") - expect(klass.instance_variable_get(:@credentials).to_env).to eq({ - 'OS_USERNAME' => 'test', - 'OS_PASSWORD' => 'abc123', - 'OS_PROJECT_NAME' => 'test', - 'OS_AUTH_URL' => 'http://127.0.0.1:5000', - 'OS_IDENTITY_API_VERSION' => '3' - }) - end - end - - context 'with a RC file containing service token credentials' do - it 'is successful' do - # return incomplete creds from env - klass.expects(:get_os_vars_from_env) - .returns({ 'OS_TOKEN' => 'incomplete' }) - mock = "export OS_TOKEN='test'\nexport OS_ENDPOINT='abc123'\nexport OS_NOT_VALID='notvalid'\n" - File.expects(:exists?).with("#{ENV['HOME']}/openrc").returns(true) - File.expects(:open).with("#{ENV['HOME']}/openrc").returns(StringIO.new(mock)) - klass.expects(:openstack) - .with('project', 'list', '--quiet', '--format', 'csv', ['--long']) - .returns('"ID","Name","Description","Enabled" -"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True -') - response = klass.request('project', 'list', ['--long']) - expect(response.first[:description]).to eq("Test tenant") expect(klass.instance_variable_get(:@credentials).to_env).to eq({ 'OS_IDENTITY_API_VERSION' => '3', - 'OS_TOKEN' => 'test', - 'OS_ENDPOINT' => 'abc123', + 'OS_CLOUD' => 'project', + 'OS_CLIENT_CONFIG_FILE' => '/etc/openstack/puppet/clouds.yaml', }) end end diff --git a/spec/unit/provider/openstack/credentials_spec.rb b/spec/unit/provider/openstack/credentials_spec.rb index 1562454f..ace7a209 100644 --- a/spec/unit/provider/openstack/credentials_spec.rb +++ b/spec/unit/provider/openstack/credentials_spec.rb @@ -47,7 +47,7 @@ describe '#password_set?' do context "with user credentials" do - it 'is successful' do + it 'is successful with project scope credential' do creds.auth_url = 'auth_url' creds.password = 'password' creds.project_name = 'project_name' @@ -56,6 +56,30 @@ expect(creds.service_token_set?).to be_falsey end + it 'is successful with project scope credential' do + creds.auth_url = 'auth_url' + creds.password = 'password' + creds.domain_name = 'domain_name' + creds.username = 'username' + expect(creds.user_password_set?).to be_truthy + expect(creds.service_token_set?).to be_falsey + end + + it 'is successful with system scope credential' do + creds.auth_url = 'auth_url' + creds.password = 'password' + creds.system_scope = 'all' + creds.username = 'username' + expect(creds.user_password_set?).to be_truthy + expect(creds.service_token_set?).to be_falsey + end + + it 'is successful with cloud' do + creds.cloud = 'openstack' + expect(creds.user_password_set?).to be_truthy + expect(creds.service_token_set?).to be_falsey + end + it 'fails' do creds.auth_url = 'auth_url' creds.password = 'password' @@ -86,18 +110,28 @@ creds.auth_url = 'auth_url' creds.password = 'password' creds.project_name = 'project_name' + creds.domain_name = 'domain_name' + creds.system_scope = 'system_scope' creds.username = 'username' creds.token = 'token' creds.endpoint = 'endpoint' + creds.region_name = 'region_name' creds.identity_api_version = 'identity_api_version' + creds.cloud = 'openstack' + creds.client_config_file = '/etc/openstack/clouds.yaml' creds.unset - expect(creds.auth_url).to eq('') - expect(creds.password).to eq('') - expect(creds.project_name).to eq('') - expect(creds.username).to eq('') - expect(creds.token).to eq('') - expect(creds.endpoint).to eq('') + expect(creds.auth_url).to eq(nil) + expect(creds.password).to eq(nil) + expect(creds.project_name).to eq(nil) + expect(creds.domain_name).to eq(nil) + expect(creds.system_scope).to eq(nil) + expect(creds.username).to eq(nil) + expect(creds.token).to eq(nil) + expect(creds.endpoint).to eq(nil) + expect(creds.region_name).to eq(nil) expect(creds.identity_api_version).to eq('identity_api_version') + expect(creds.cloud).to eq(nil) + expect(creds.client_config_file).to eq(nil) newcreds = Puppet::Provider::Openstack::CredentialsV3.new expect(newcreds.identity_api_version).to eq('3') end @@ -110,20 +144,28 @@ creds.auth_url = 'auth_url' creds.password = 'password' creds.project_name = 'project_name' + creds.domain_name = 'domain_name' + creds.system_scope = 'all' creds.username = 'username' creds.token = 'token' creds.endpoint = 'endpoint' - creds.identity_api_version = 'identity_api_version' creds.region_name = 'Region1' + creds.identity_api_version = 'identity_api_version' + creds.cloud = 'openstack' + creds.client_config_file = '/etc/openstack/clouds.yaml' expect(creds.to_env).to eq({ 'OS_USERNAME' => 'username', 'OS_PASSWORD' => 'password', 'OS_PROJECT_NAME' => 'project_name', + 'OS_DOMAIN_NAME' => 'domain_name', + 'OS_SYSTEM_SCOPE' => 'all', 'OS_AUTH_URL' => 'auth_url', 'OS_TOKEN' => 'token', 'OS_ENDPOINT' => 'endpoint', - 'OS_IDENTITY_API_VERSION' => 'identity_api_version', 'OS_REGION_NAME' => 'Region1', + 'OS_IDENTITY_API_VERSION' => 'identity_api_version', + 'OS_CLOUD' => 'openstack', + 'OS_CLIENT_CONFIG_FILE' => '/etc/openstack/clouds.yaml', }) end end @@ -145,6 +187,34 @@ creds.project_name = 'project_name' creds.username = 'username' expect(creds.user_password_set?).to be_truthy + expect(creds.scope).to eq('project') + end + end + describe '#password_set? with username and domain_name' do + it 'is successful' do + creds.auth_url = 'auth_url' + creds.password = 'password' + creds.domain_name = 'domain_name' + creds.username = 'username' + expect(creds.user_password_set?).to be_truthy + expect(creds.scope).to eq('domain') + end + end + describe '#password_set? with username and system_scope' do + it 'is successful' do + creds.auth_url = 'auth_url' + creds.password = 'password' + creds.system_scope = 'all' + creds.username = 'username' + expect(creds.user_password_set?).to be_truthy + expect(creds.scope).to eq('system') + end + end + describe '#password_set? with cloud' do + it 'is successful' do + creds.cloud = 'openstack' + expect(creds.user_password_set?).to be_truthy + expect(creds.scope).to eq(nil) end end describe '#password_set? with user_id and project_id' do @@ -154,6 +224,17 @@ creds.project_id = 'projid' creds.user_id = 'userid' expect(creds.user_password_set?).to be_truthy + expect(creds.scope).to eq('project') + end + end + describe '#password_set? with user_id and domain_id' do + it 'is successful' do + creds.auth_url = 'auth_url' + creds.password = 'password' + creds.domain_id = 'domid' + creds.user_id = 'userid' + expect(creds.user_password_set?).to be_truthy + expect(creds.scope).to eq('domain') end end end diff --git a/spec/unit/provider/openstack_config/ini_setting_spec.rb b/spec/unit/provider/openstack_config/ini_setting_spec.rb index 8b3d4490..b0a52bca 100644 --- a/spec/unit/provider/openstack_config/ini_setting_spec.rb +++ b/spec/unit/provider/openstack_config/ini_setting_spec.rb @@ -1,19 +1,3 @@ -# -# these tests are a little concerning b/c they are hacking around the -# modulepath, so these tests will not catch issues that may eventually arise -# related to loading these plugins. -# I could not, for the life of me, figure out how to programatcally set the modulepath -$LOAD_PATH.push( - File.join( - File.dirname(__FILE__), - '..', - '..', - '..', - 'fixtures', - 'modules', - 'inifile', - 'lib') -) require 'spec_helper' provider_class = Puppet::Type.type(:openstack_config).provider(:ini_setting) describe provider_class do diff --git a/spec/unit/provider/openstack_spec.rb b/spec/unit/provider/openstack_spec.rb index 375df177..fa369444 100644 --- a/spec/unit/provider/openstack_spec.rb +++ b/spec/unit/provider/openstack_spec.rb @@ -18,13 +18,13 @@ end let(:credentials) do - credentials = mock('credentials') - credentials.stubs(:to_env).returns({ - 'OS_USERNAME' => 'user', - 'OS_PASSWORD' => 'password', - 'OS_PROJECT_NAME' => 'project', - 'OS_AUTH_URL' => 'http://url', - }) + credentials = double('credentials') + allow(credentials).to receive(:to_env).and_return({ + 'OS_USERNAME' => 'user', + 'OS_PASSWORD' => 'password', + 'OS_PROJECT_NAME' => 'project', + 'OS_AUTH_URL' => 'http://url', + }) credentials end @@ -56,66 +56,97 @@ end it 'makes a successful list request' do - provider.class.expects(:openstack) + expect(provider.class).to receive(:openstack) .with('project', 'list', '--quiet', '--format', 'csv', ['--long']) - .returns list_data + .and_return list_data response = Puppet::Provider::Openstack.request('project', 'list', ['--long']) expect(response.first[:description]).to eq 'Test tenant' end it 'makes a successful show request' do - provider.class.expects(:openstack) + expect(provider.class).to receive(:openstack) .with('project', 'show', '--format', 'shell', ['1cb05cfed7c24279be884ba4f6520262']) - .returns show_data + .and_return show_data response = Puppet::Provider::Openstack.request('project', 'show', ['1cb05cfed7c24279be884ba4f6520262']) expect(response[:description]).to eq 'Test tenant' end it 'makes a successful set request' do - provider.class.expects(:openstack) + expect(provider.class).to receive(:openstack) .with('project', 'set', ['--name', 'new name', '1cb05cfed7c24279be884ba4f6520262']) - .returns '' + .and_return '' response = Puppet::Provider::Openstack.request('project', 'set', ['--name', 'new name', '1cb05cfed7c24279be884ba4f6520262']) expect(response).to eq '' end it 'uses provided credentials' do - Puppet::Util.expects(:withenv).with(credentials.to_env) + expect(provider.class).to receive(:os_withenv).with(credentials.to_env) Puppet::Provider::Openstack.request('project', 'list', ['--long'], credentials) end + it 'redacts sensitive data from an exception message' do + e1 = Puppet::ExecutionFailure.new "Execution of 'openstack user create --format shell hello --password world --enable --email foo@example.com --domain Default' returned 1: command failed" + expect do + Puppet::Provider::Openstack.redact_and_raise(e1) + end.to raise_error(Puppet::ExecutionFailure, /Execution of \'openstack user create --format shell hello --password \[redacted secret\] --enable --email foo@example.com --domain Default/) + e2 = Puppet::ExecutionFailure.new "Execution of 'openstack user create --format shell hello --password world' returned 1: command failed" + expect do + Puppet::Provider::Openstack.redact_and_raise(e2) + end.to raise_error(Puppet::ExecutionFailure, /Execution of \'openstack user create --format shell hello --password \[redacted secret\]\' returned/) + end + + it 'redacts password in execution output on exception' do + allow(provider.class).to receive(:execute) + .and_raise(Puppet::ExecutionFailure, "Execution of '/usr/bin/openstack user create --format shell hello --password world --enable --email foo@example.com --domain Default' returned 1: command failed") + expect do + Puppet::Provider::Openstack.request('user', 'create', ['hello', '--password', 'world', '--enable', '--email', 'foo@example.com', '--domain', 'Default']) + end.to raise_error Puppet::ExecutionFailure, "Execution of '/usr/bin/openstack user create --format shell hello --password [redacted secret] --enable --email foo@example.com --domain Default' returned 1: command failed" + end + context 'on connection errors' do it 'retries the failed command' do - provider.class.stubs(:openstack) + allow(provider.class).to receive(:openstack) .with('project', 'list', '--quiet', '--format', 'csv', ['--long']) - .raises(Puppet::ExecutionFailure, 'Unable to establish connection') - .then - .returns list_data - provider.class.expects(:sleep).with(3).returns(nil) + .and_invoke( + lambda { |*args| raise Puppet::ExecutionFailure, 'Unable to establish connection' }, + lambda { |*args| return list_data } + ) + expect(provider.class).to receive(:sleep).with(10).and_return(nil) response = Puppet::Provider::Openstack.request('project', 'list', ['--long']) expect(response.first[:description]).to eq 'Test tenant' end + it 'fails after the timeout and redacts' do + expect(provider.class).to receive(:execute) + .and_raise(Puppet::ExecutionFailure, "Execution of 'openstack user create foo --password secret' returned 1: command failed") + .exactly(6).times + allow(provider.class).to receive(:sleep) + allow(provider.class).to receive(:current_time) + .and_return(0, 10, 20, 100, 200, 300, 400) + expect do + Puppet::Provider::Openstack.request('project', 'list', ['--long']) + end.to raise_error Puppet::ExecutionFailure, /Execution of \'openstack user create foo --password \[redacted secret\]\' returned 1/ + end + it 'fails after the timeout' do - provider.class.expects(:openstack) + expect(provider.class).to receive(:openstack) .with('project', 'list', '--quiet', '--format', 'csv', ['--long']) - .raises(Puppet::ExecutionFailure, 'Unable to establish connection') - .times(3) - provider.class.stubs(:sleep) - provider.class.stubs(:current_time) - .returns(0, 10, 10, 20, 20, 200, 200) + .and_raise(Puppet::ExecutionFailure, 'Unable to establish connection') + .exactly(6).times + allow(provider.class).to receive(:sleep) + allow(provider.class).to receive(:current_time) + .and_return(0, 10, 20, 100, 200, 300, 400) expect do Puppet::Provider::Openstack.request('project', 'list', ['--long']) end.to raise_error Puppet::ExecutionFailure, /Unable to establish connection/ end it 'does not retry non-idempotent commands' do - provider.class.expects(:openstack) + expect(provider.class).to receive(:openstack) .with('project', 'create', '--format', 'shell', ['--quiet']) - .raises(Puppet::ExecutionFailure, 'Unable to establish connection') - .then - .returns list_data - provider.class.expects(:sleep).never + .and_raise(Puppet::ExecutionFailure, 'Unable to establish connection') + .exactly(1).times + expect(provider.class).to receive(:sleep).never expect do Puppet::Provider::Openstack.request('project', 'create', ['--quiet']) end.to raise_error Puppet::ExecutionFailure, /Unable to establish connection/ @@ -129,18 +160,18 @@ ENV['OS_PASSWORD'] = 'abc123' ENV['OS_PROJECT_NAME'] = 'test' ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000' - provider.class.stubs(:openstack) + allow(provider.class).to receive(:openstack) .with('project', 'list', '--quiet', '--format', 'csv', ['--long']) - .raises(Puppet::ExecutionFailure, 'Could not find user: test (HTTP 401)') + .and_raise(Puppet::ExecutionFailure, 'Could not find user: test (HTTP 401)') expect do Puppet::Provider::Openstack.request('project', 'list', ['--long']) end.to raise_error(Puppet::Error::OpenstackUnauthorizedError, /Could not authenticate/) end it 'should raise an error with not authorized to perform' do - provider.class.stubs(:openstack) + allow(provider.class).to receive(:openstack) .with('role', 'list', '--quiet', '--format', 'csv', ['--long']) - .raises(Puppet::ExecutionFailure, 'You are not authorized to perform the requested action: identity:list_grants (HTTP 403)') + .and_raise(Puppet::ExecutionFailure, 'You are not authorized to perform the requested action: identity:list_grants (HTTP 403)') expect do Puppet::Provider::Openstack.request('role', 'list', ['--long']) end.to raise_error(Puppet::Error::OpenstackUnauthorizedError, /Could not authenticate/) @@ -179,4 +210,77 @@ end end end + + describe '#parse_python_dict' do + it 'should return a hash when provided with a python dict' do + s = "{'key': 'value', 'key2': 'value2'}" + expect(Puppet::Provider::Openstack.parse_python_dict(s)).to eq({'key'=>'value', 'key2'=>'value2'}) + + s = "{'key': True, 'key2': 'value2'}" + expect(Puppet::Provider::Openstack.parse_python_dict(s)).to eq({'key'=>true, 'key2'=>'value2'}) + + s = "{'key': 'value', 'key2': True}" + expect(Puppet::Provider::Openstack.parse_python_dict(s)).to eq({'key'=>'value', 'key2'=>true}) + + s = "{'key': False, 'key2': 'value2'}" + expect(Puppet::Provider::Openstack.parse_python_dict(s)).to eq({'key'=>false, 'key2'=>'value2'}) + + s = "{'key': 'value', 'key2': False}" + expect(Puppet::Provider::Openstack.parse_python_dict(s)).to eq({'key'=>'value', 'key2'=>false}) + end + end + + describe '#parse_python_list' do + it 'should return an array when provided with a python list' do + s = "['foo', 'bar', 'baz']" + expect(Puppet::Provider::Openstack.parse_python_list(s)).to eq(['foo', 'bar', 'baz']) + + s = '[]' + expect(Puppet::Provider::Openstack.parse_python_list(s)).to eq([]) + + s = '[1, 2, 3]' + expect(Puppet::Provider::Openstack.parse_python_list(s)).to eq([1, 2, 3]) + end + end + + describe '#os_withenv' do + around do |example| + @original_env = ENV.to_hash + example.run + ENV.replace(@original_env) + end + + it 'removes environment variables starting with OS_' do + ENV['OS_FOO'] = 'should be removed' + ENV['OTHER'] = 'should stay' + + Puppet::Provider::Openstack.os_withenv({}) do + expect(ENV.key?('OS_FOO')).to be false + expect(ENV['OTHER']).to eq('should stay') + end + end + + it 'merges the given environment variables' do + Puppet::Provider::Openstack.os_withenv({'MY_VAR' => '123'}) do + expect(ENV['MY_VAR']).to eq('123') + end + end + + it 'restores the original environment after the block' do + original = ENV.to_hash + Puppet::Provider::Openstack.os_withenv({'TEMP_VAR' => 'test'}) do + ENV['INSIDE_BLOCK'] = 'yep' + end + + expect(ENV.key?('TEMP_VAR')).to be false + expect(ENV.key?('INSIDE_BLOCK')).to be false + expect(ENV.to_hash).to eq(original) + end + + it 'handles string and symbol keys in the input hash' do + Puppet::Provider::Openstack.os_withenv({:FOO => 'bar'}) do + expect(ENV['FOO']).to eq('bar') + end + end + end end diff --git a/spec/unit/provider/policy_rcd/policy_rcd_spec.rb b/spec/unit/provider/policy_rcd/policy_rcd_spec.rb index 396f96ab..e94097d3 100644 --- a/spec/unit/provider/policy_rcd/policy_rcd_spec.rb +++ b/spec/unit/provider/policy_rcd/policy_rcd_spec.rb @@ -27,109 +27,109 @@ describe 'managing policy' do describe '#create' do it 'creates a policy when policy-rc.d doesnt exist' do - file = mock('file') - provider.stubs(:policy_rcd).returns(file) - File.expects(:exist?).with(file).returns(false) + file = double('file') + allow(provider).to receive(:policy_rcd).and_return(file) + expect(File).to receive(:exist?).with(file).and_return(false) content = "#{header}[[ \"$1\" == \"service\" ]] && exit 101\n" - provider.class.expects(:write_to_file).with(file, content) + expect(provider.class).to receive(:write_to_file).with(file, content) provider.create end it 'creates a policy when policy-rc.d exists' do - file = mock('file') - provider.stubs(:policy_rcd).returns(file) - File.expects(:exist?).with(file).returns(true) + file = double('file') + allow(provider).to receive(:policy_rcd).and_return(file) + expect(File).to receive(:exist?).with(file).and_return(true) content = "[[ \"$1\" == \"service\" ]] && exit 101\n" - provider.class.expects(:write_to_file).with(file, content) + expect(provider.class).to receive(:write_to_file).with(file, content) provider.create end end describe '#destroy' do it 'destroy a policy' do - file = mock('file') + file = double('file') file_content = "#{header}[[ \"$1\" == \"service\" ]] && exit 101\n" - provider.stubs(:policy_rcd).returns(file) - File.expects(:exist?).with(file).returns(true) - provider.stubs(:file_lines).returns(file_content.split("\n")) - provider.class.expects(:write_to_file).with(file, ['#!/bin/bash', '# THIS FILE MANAGED BY PUPPET'], true) + allow(provider).to receive(:policy_rcd).and_return(file) + expect(File).to receive(:exist?).with(file).and_return(true) + allow(provider).to receive(:file_lines).and_return(file_content.split("\n")) + expect(provider.class).to receive(:write_to_file).with(file, ['#!/bin/bash', '# THIS FILE MANAGED BY PUPPET'], true) provider.destroy end end describe '#flush' do it 'update a policy' do - file = mock('file') - provider.stubs(:policy_rcd).returns(file) + file = double('file') + allow(provider).to receive(:policy_rcd).and_return(file) file_content = "#{header}[[ \"$1\" == \"service\" ]] && exit 102\n" - provider.stubs(:file_lines).returns(file_content.split("\n")) - provider.class.expects(:write_to_file).with(file, ['#!/bin/bash', "# THIS FILE MANAGED BY PUPPET", "[[ \"$1\" == \"service\" ]] && exit 101\n"], true) + allow(provider).to receive(:file_lines).and_return(file_content.split("\n")) + expect(provider.class).to receive(:write_to_file).with(file, ['#!/bin/bash', "# THIS FILE MANAGED BY PUPPET", "[[ \"$1\" == \"service\" ]] && exit 101\n"], true) provider.flush end it 'dont update a policy' do - file = mock('file') + file = double('file') file_content = "#{header}[[ \"$1\" == \"service\" ]] && exit 101\n" - provider.stubs(:policy_rcd).returns(file) - provider.stubs(:file_lines).returns(file_content.split("\n")) + allow(provider).to receive(:policy_rcd).and_return(file) + allow(provider).to receive(:file_lines).and_return(file_content.split("\n")) provider.flush end end describe '#exists?' do it 'should exists on Debian family' do - provider.stubs(:check_os).returns(true) - file = mock('file') + allow(provider).to receive(:check_os).and_return(true) + file = double('file') file_content = "#{header}[[ \"$1\" == \"service\" ]] && exit 101\n" - provider.stubs(:policy_rcd).returns(file) - provider.stubs(:check_policy_rcd).returns(true) - provider.stubs(:file_lines).returns(file_content.split("\n")) + allow(provider).to receive(:policy_rcd).and_return(file) + allow(provider).to receive(:check_policy_rcd).and_return(true) + allow(provider).to receive(:file_lines).and_return(file_content.split("\n")) expect(provider.exists?).to be_truthy end it 'should not exists on Debian family when file is present' do - provider.stubs(:check_os).returns(true) - file = mock('file') + allow(provider).to receive(:check_os).and_return(true) + file = double('file') file_content = "#{header}[[ \"$1\" == \"new-service\" ]] && exit 101\n" - provider.stubs(:policy_rcd).returns(file) - provider.stubs(:check_policy_rcd).returns(true) - provider.stubs(:file_lines).returns(file_content.split("\n")) + allow(provider).to receive(:policy_rcd).and_return(file) + allow(provider).to receive(:check_policy_rcd).and_return(true) + allow(provider).to receive(:file_lines).and_return(file_content.split("\n")) expect(provider.exists?).to be_falsey end it 'should not exists on Debian family when file is not present' do - provider.stubs(:check_os).returns(true) - provider.stubs(:check_policy_rcd).returns(false) + allow(provider).to receive(:check_os).and_return(true) + allow(provider).to receive(:check_policy_rcd).and_return(false) expect(provider.exists?).to be_falsey end it 'should exists on non-Debian family' do - provider.stubs(:check_os).returns(false) + allow(provider).to receive(:check_os).and_return(false) expect(provider.exists?).to be_truthy end end describe 'write_to_file' do it 'should write to file' do - file = mock - policy = mock + file = double + policy = double content = 'some_content' - File.expects(:open).with(file, 'a+').returns(policy) - policy.expects(:puts).with(content) - policy.expects(:close) - File.expects(:chmod).with(0744, file) + expect(File).to receive(:open).with(file, 'a+').and_return(policy) + expect(policy).to receive(:puts).with(content) + expect(policy).to receive(:close) + expect(File).to receive(:chmod).with(0744, file) provider.class.write_to_file(file, content) end it 'should truncate file' do - file = mock - policy = mock + file = double + policy = double content = 'some_content' - File.expects(:truncate).with(file, 0) - File.expects(:open).with(file, 'a+').returns(policy) - policy.expects(:puts).with(content) - policy.expects(:close) - File.expects(:chmod).with(0744, file) + expect(File).to receive(:truncate).with(file, 0) + expect(File).to receive(:open).with(file, 'a+').and_return(policy) + expect(policy).to receive(:puts).with(content) + expect(policy).to receive(:close) + expect(File).to receive(:chmod).with(0744, file) provider.class.write_to_file(file, content, true) end end diff --git a/spec/unit/puppet/util/openstackconfig_spec.rb b/spec/unit/puppet/util/openstackconfig_spec.rb index 75794833..46e765a0 100644 --- a/spec/unit/puppet/util/openstackconfig_spec.rb +++ b/spec/unit/puppet/util/openstackconfig_spec.rb @@ -15,7 +15,7 @@ end before :each do - Puppet::Util::OpenStackConfig.stubs(:readlines).returns(sample_content) + allow(Puppet::Util::OpenStackConfig).to receive(:readlines).and_return(sample_content) end context "when parsing a file" do @@ -48,32 +48,32 @@ it "should parse the correct number of sections" do # there is always a "global" section, so our count should be 3. - subject.section_names.length.should == 4 + expect(subject.section_names.length).to eq(4) end it "should parse the correct section_names" do # there should always be a "global" section named "" at the beginning of the list - subject.section_names.should == ["", "section1", "section2", "section3"] + expect(subject.section_names).to eq(["", "section1", "section2", "section3"]) end it "should expose settings for sections" do - subject.get_settings("section1").should == { + expect(subject.get_settings("section1")).to eq({ "bar" => "barvalue", "baz" => "", "foo" => "foovalue" - } + }) - subject.get_settings("section2").should == { + expect(subject.get_settings("section2")).to eq({ "baz" => "bazvalue", "foo" => "foovalue2", "l" => "git log", "xyzzy['thing1']['thing2']" => "xyzzyvalue", "zot" => "multi word value" - } + }) - subject.get_settings("section3").should == { + expect(subject.get_settings("section3")).to eq({ "multi_setting" => ["value1", "value2"] - } + }) end end @@ -90,16 +90,16 @@ it "should parse the correct number of sections" do # there is always a "global" section, so our count should be 2. - subject.section_names.length.should == 2 + expect(subject.section_names.length).to eq(2) end it "should parse the correct section_names" do # there should always be a "global" section named "" at the beginning of the list - subject.section_names.should == ["", "section1"] + expect(subject.section_names).to eq(["", "section1"]) end it "should expose settings for sections" do - subject.get_value("section1", "foo").should == "foovalue" + expect(subject.get_value("section1", "foo")).to eq("foovalue") end end @@ -117,17 +117,17 @@ it "should parse the correct number of sections" do # there is always a "global" section, so our count should be 2. - subject.section_names.length.should == 2 + expect(subject.section_names.length).to eq(2) end it "should parse the correct section_names" do # there should always be a "global" section named "" at the beginning of the list - subject.section_names.should == ["", "section1"] + expect(subject.section_names).to eq(["", "section1"]) end it "should expose settings for sections" do - subject.get_value("", "foo").should == "bar" - subject.get_value("section1", "foo").should == "foovalue" + expect(subject.get_value("", "foo")).to eq("bar") + expect(subject.get_value("section1", "foo")).to eq("foovalue") end end @@ -143,22 +143,22 @@ } it "should properly update uncommented values" do - subject.get_value("section1", "far").should == nil + expect(subject.get_value("section1", "far")).to eq(nil) subject.set_value("section1", "foo", "foovalue") - subject.get_value("section1", "foo").should == "foovalue" + expect(subject.get_value("section1", "foo")).to eq("foovalue") end it "should properly update commented values" do - subject.get_value("section1", "bar").should == nil + expect(subject.get_value("section1", "bar")).to eq(nil) subject.set_value("section1", "bar", "barvalue") - subject.get_value("section1", "bar").should == "barvalue" - subject.get_value("section1", "xyzzy['thing1']['thing2']").should == nil + expect(subject.get_value("section1", "bar")).to eq("barvalue") + expect(subject.get_value("section1", "xyzzy['thing1']['thing2']")).to eq(nil) subject.set_value("section1", "xyzzy['thing1']['thing2']", "xyzzyvalue") - subject.get_value("section1", "xyzzy['thing1']['thing2']").should == "xyzzyvalue" + expect(subject.get_value("section1", "xyzzy['thing1']['thing2']")).to eq("xyzzyvalue") end it "should properly add new empty values" do - subject.get_value("section1", "baz").should == nil + expect(subject.get_value("section1", "baz")).to eq(nil) end end @@ -179,11 +179,12 @@ end it 'should parse the sections' do - subject.section_names.should match_array ['', - 'branch "master"', - 'alias', - 'branch "production"' - ] + expect(subject.section_names).to eq([ + '', + 'branch "master"', + 'alias', + 'branch "production"' + ]) end end @@ -213,13 +214,13 @@ end it "should parse the correct section_names" do - subject.section_names.should match_array [ + expect(subject.section_names).to eq([ '', 'global', 'printers', 'print$', 'Shares' - ] + ]) end end @@ -233,10 +234,10 @@ end it "should parse the correct section_names" do - subject.section_names.should match_array [ + expect(subject.section_names).to eq([ '', 'monitor:///var/log/*.log' - ] + ]) end end @@ -252,8 +253,8 @@ end it "should expose settings for sections" do - subject.get_value("khotkeys", "{5465e8c7-d608-4493-a48f-b99d99fdb508}").should == "Print,none,PrintScreen" - subject.get_value("khotkeys", "{d03619b6-9b3c-48cc-9d9c-a2aadb485550}").should == "Search,none,Search" + expect(subject.get_value("khotkeys", "{5465e8c7-d608-4493-a48f-b99d99fdb508}")).to eq("Print,none,PrintScreen") + expect(subject.get_value("khotkeys", "{d03619b6-9b3c-48cc-9d9c-a2aadb485550}")).to eq("Search,none,Search") end end @@ -269,9 +270,9 @@ end it "should expose settings for sections" do - subject.get_value("Drive names", "A:").should eq '5.25" Floppy' - subject.get_value("Drive names", "B:").should eq '3.5" Floppy' - subject.get_value("Drive names", "C:").should eq 'Winchester' + expect(subject.get_value("Drive names", "A:")).to eq('5.25" Floppy') + expect(subject.get_value("Drive names", "B:")).to eq('3.5" Floppy') + expect(subject.get_value("Drive names", "C:")).to eq('Winchester') end end @@ -290,10 +291,10 @@ end it "should expose settings for sections" do - subject.get_value("global", "log file").should eq '/var/log/samba/log.%m' - subject.get_value("global", "kerberos method").should eq 'system keytab' - subject.get_value("global", "passdb backend").should eq 'tdbsam' - subject.get_value("global", "security").should eq 'ads' + expect(subject.get_value("global", "log file")).to eq('/var/log/samba/log.%m') + expect(subject.get_value("global", "kerberos method")).to eq('system keytab') + expect(subject.get_value("global", "passdb backend")).to eq('tdbsam') + expect(subject.get_value("global", "security")).to eq('ads') end end @@ -311,12 +312,12 @@ end it "should expose setting with array value" do - subject.get_value("test", "test").should eq ['value1', 'value2', 'value3'] + expect(subject.get_value("test", "test")).to eq(['value1', 'value2', 'value3']) end it "should create setting with array value" do subject.set_value("test", "test2", ['valueA', 'valueB', 'valueC']) - subject.get_value("test", "test2").should eq ['valueA', 'valueB', 'valueC'] + expect(subject.get_value("test", "test2")).to eq(['valueA', 'valueB', 'valueC']) end end end diff --git a/templates/clouds.yaml.erb b/templates/clouds.yaml.erb new file mode 100644 index 00000000..f4382054 --- /dev/null +++ b/templates/clouds.yaml.erb @@ -0,0 +1,38 @@ +clouds: +<% if @project_name -%> + project: + auth: + auth_url: <%= @auth_url %> + password: <%= @password %> + username: <%= @username %> + user_domain_name: <%= @user_domain_name %> + project_name: <%= @project_name %> + project_domain_name: <%= @project_domain_name %> + <%- @api_versions.sort.each do |opt_name,opt_val| -%> + <%= opt_name %>_api_version: <%= opt_val %> + <%- end -%> + <%- if !(@interface.nil?) -%> + interface: <%= @interface %> + <%- end -%> + <%- if !(@region_name.nil?) -%> + region_name: <%= @region_name %> + <%- end -%> +<% end -%> +<% if @system_scope -%> + system: + auth: + auth_url: <%= @auth_url %> + password: <%= @password %> + username: <%= @username %> + user_domain_name: <%= @user_domain_name %> + system_scope: <%= @system_scope %> + <%- @api_versions.sort.each do |opt_name,opt_val| -%> + <%= opt_name %>_api_version: <%= opt_val %> + <%- end -%> + <%- if !(@interface.nil?) -%> + interface: <%= @interface %> + <%- end -%> + <%- if !(@region_name.nil?) -%> + region_name: <%= @region_name %> + <%- end -%> +<% end -%> diff --git a/templates/policy-rc.d.erb b/templates/policy-rc.d.erb index ca420a09..b3787ba8 100644 --- a/templates/policy-rc.d.erb +++ b/templates/policy-rc.d.erb @@ -5,4 +5,4 @@ then exit 101 fi <% end %> - +exit 0 diff --git a/tox.ini b/tox.ini index 0aea3cfc..7070f7d7 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,8 @@ [tox] -minversion = 2.0 -skipsdist = True +minversion = 3.1 envlist = releasenotes -[testenv] -install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt} {opts} {packages} - [testenv:releasenotes] -basepython = python3 -deps = -r{toxinidir}/doc/requirements.txt +deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html diff --git a/types/policies.pp b/types/policies.pp new file mode 100644 index 00000000..6960bf0e --- /dev/null +++ b/types/policies.pp @@ -0,0 +1,8 @@ +type Openstacklib::Policies = Hash[ + String[1], Struct[ + { + key => Optional[String[1]], + value => String[1], + } + ] +] diff --git a/types/servicedefault.pp b/types/servicedefault.pp new file mode 100644 index 00000000..5008fd5d --- /dev/null +++ b/types/servicedefault.pp @@ -0,0 +1 @@ +type Openstacklib::ServiceDefault = Enum['']