diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..a51f56a --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +-f doc +--color diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..19ccb66 --- /dev/null +++ b/Gemfile @@ -0,0 +1,24 @@ +source 'https://rubygems.org' + +group :development, :test do + gem 'puppetlabs_spec_helper' + gem 'puppet-lint', '~> 0.3.2' + gem 'rspec-puppet', '~> 2.2.0' + gem 'rspec-puppet-utils', '~> 2.0.0' + gem 'openstack' + gem 'netaddr' + gem 'deep_merge' + gem 'pry' + gem 'simplecov' + gem 'puppet-spec' + gem 'colorize' + gem 'parallel' +end + +if ENV['PUPPET_GEM_VERSION'] + gem 'puppet', ENV['PUPPET_GEM_VERSION'] +else + gem 'puppet', '3.4.3' +end + +# vim:ft=ruby diff --git a/deployment/.gitkeep b/deployment/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/noop.rb b/lib/noop.rb new file mode 100644 index 0000000..a67d4a8 --- /dev/null +++ b/lib/noop.rb @@ -0,0 +1,48 @@ +require_relative 'noop/utils' +require_relative 'noop/config' +require_relative 'noop/manager' +require_relative 'noop/task' +require_relative 'noop/matchers' + +module Noop + def self.new_task(*args) + self.task = Noop::Task.new *args + end + + def self.task_spec=(value) + self.task.file_name_spec = value + end + + def self.task_hiera=(value) + self.task.file_name_hiera = value + end + + def self.task_facts=(value) + self.task.file_name_facts = value + end + + def self.task_spec + self.task.file_name_spec + end + + def self.task_hiera + self.task.file_name_hiera + end + + def self.task_facts + self.task.file_name_facts + end + + def self.task=(value) + @task = value + end + + def self.task + return @task if @task + @task = Noop::Task.new + end + + def self.method_missing(method, *args) + self.task.send method, *args + end +end diff --git a/lib/noop/config.rb b/lib/noop/config.rb new file mode 100644 index 0000000..53846df --- /dev/null +++ b/lib/noop/config.rb @@ -0,0 +1,6 @@ +require_relative 'utils' +require_relative 'config/base' +require_relative 'config/hiera' +require_relative 'config/facts' +require_relative 'config/globals' +require_relative 'config/log' diff --git a/lib/noop/config/base.rb b/lib/noop/config/base.rb new file mode 100644 index 0000000..c49fe6d --- /dev/null +++ b/lib/noop/config/base.rb @@ -0,0 +1,106 @@ +require 'pathname' + +module Noop + module Config + # @return [Pathname] + def self.dir_path_config + return @dirname if @dirname + @dirname = Pathname.new(__FILE__).dirname.realpath + end + + # @return [Pathname] + def self.dir_path_root + return @dir_path_root if @dir_path_root + @dir_path_root = Noop::Utils.path_from_env 'SPEC_ROOT_DIR' + @dir_path_root = dir_path_config.parent.parent.parent unless @dir_path_root + begin + @dir_path_root = @dir_path_root.realpath + rescue + @dir_path_root + end + end + + # @return [Pathname] + def self.dir_path_task_spec + return @dir_path_task_spec if @dir_path_task_spec + @dir_path_task_spec = Noop::Utils.path_from_env 'SPEC_SPEC_DIR' + @dir_path_task_spec = dir_path_root + 'spec' + 'hosts' unless @dir_path_task_spec + begin + @dir_path_task_spec = @dir_path_task_spec.realpath + rescue + @dir_path_task_spec + end + end + + # @return [Pathname] + def self.dir_path_modules_local + return @dir_path_modules_local if @dir_path_modules_local + @dir_path_modules_local = Noop::Utils.path_from_env 'SPEC_MODULEPATH', 'SPEC_MODULE_PATH' + @dir_path_modules_local = dir_path_root + 'modules' unless @dir_path_modules_local + begin + @dir_path_modules_local = @dir_path_modules_local.realpath + rescue + @dir_path_modules_local + end + end + + # @return [Pathname] + def self.dir_path_tasks_local + return @dir_path_tasks_local if @dir_path_tasks_local + @dir_path_tasks_local = Noop::Utils.path_from_env 'SPEC_TASK_DIR' + @dir_path_tasks_local = dir_path_root + 'tasks' unless @dir_path_tasks_local + begin + @dir_path_tasks_local = @dir_path_tasks_local.realpath + rescue + @dir_path_tasks_local + end + end + + # @return [Pathname] + def self.dir_path_modules_node + return @dir_path_modules_node if @dir_path_modules_node + @dir_path_modules_node = Pathname.new '/etc/puppet/modules' + end + + # @return [Pathname] + def self.dir_path_tasks_node + return @dir_path_tasks_node if @dir_path_tasks_node + @dir_path_tasks_node = dir_path_modules_node + 'osnailyfacter' + 'modular' + end + + # @return [Pathname] + def self.dir_path_deployment + return @dir_path_deployment if @dir_path_deployment + @dir_path_deployment = Noop::Utils.path_from_env 'SPEC_DEPLOYMENT_DIR' + @dir_path_deployment = dir_path_root + 'deployment' unless @dir_path_deployment + begin + @dir_path_deployment = @dir_path_deployment.realpath + rescue + @dir_path_deployment + end + end + + # Workspace directory where gem bundle will be created + # is passed from Jenkins or the default value is used + # @return [Pathname] + def self.dir_path_workspace + return @dir_path_workspace if @dir_path_workspace + @dir_path_workspace = Noop::Utils.path_from_env 'WORKSPACE' + @dir_path_workspace = Noop::Config.dir_path_root + Pathname.new('workspace') unless @dir_path_workspace + begin + @dir_path_workspace = @dir_path_workspace.realpath + rescue + nil + end + @dir_path_workspace.mkpath + raise "Workspace '#{@dir_path_workspace}' is not a directory!" unless @dir_path_workspace.directory? + @dir_path_workspace + end + + # @return [Pathname] + def self.dir_path_reports + return @dir_path_reports if @dir_path_reports + @dir_path_reports = dir_path_root + 'reports' + end + end +end diff --git a/lib/noop/config/facts.rb b/lib/noop/config/facts.rb new file mode 100644 index 0000000..7d75653 --- /dev/null +++ b/lib/noop/config/facts.rb @@ -0,0 +1,36 @@ +require 'pathname' + +module Noop + module Config + # @return [Pathname] + def self.dir_name_facts + Pathname.new 'facts' + end + + # @return [Pathname] + def self.dir_path_facts + return @dir_path_facts if @dir_path_facts + @dir_path_facts = Noop::Utils.path_from_env 'SPEC_FACTS_DIR' + @dir_path_facts = dir_path_root + dir_name_facts unless @dir_path_facts + begin + @dir_path_facts = @dir_path_facts.realpath + rescue + @dir_path_facts + end + end + + # @return [Pathname] + def self.dir_name_facts_override + Pathname.new 'override' + end + + # @return [Pathname] + def self.dir_path_facts_override + dir_path_facts + dir_name_facts_override + end + + def self.default_facts_file_name + Pathname.new 'ubuntu.yaml' + end + end +end diff --git a/lib/noop/config/globals.rb b/lib/noop/config/globals.rb new file mode 100644 index 0000000..e557738 --- /dev/null +++ b/lib/noop/config/globals.rb @@ -0,0 +1,33 @@ +require 'pathname' + +module Noop + module Config + # @return [Pathname] + def self.spec_name_globals + Pathname.new 'globals/globals_spec.rb' + end + + # @return [Pathname] + def self.spec_path_globals + dir_path_task_spec + spec_name_globals + end + + def self.manifest_name_globals + Noop::Utils.convert_to_manifest spec_name_globals + end + + def self.manifest_path_globals + dir_path_tasks_local + manifest_name_globals + end + + # @return [Pathname] + def self.dir_name_globals + Pathname.new 'globals' + end + + # @return [Pathname] + def self.dir_path_globals + dir_path_hiera + dir_name_globals + end + end +end diff --git a/lib/noop/config/hiera.rb b/lib/noop/config/hiera.rb new file mode 100644 index 0000000..ddc5ee3 --- /dev/null +++ b/lib/noop/config/hiera.rb @@ -0,0 +1,36 @@ +require 'pathname' + +module Noop + module Config + # @return [Pathname] + def self.dir_name_hiera + Pathname.new 'hiera' + end + + # @return [Pathname] + def self.dir_path_hiera + return @dir_path_hiera if @dir_path_hiera + @dir_path_hiera = Noop::Utils.path_from_env 'SPEC_HIERA_DIR', 'SPEC_YAML_DIR' + @dir_path_hiera = dir_path_root + dir_name_hiera unless @dir_path_hiera + begin + @dir_path_hiera = @dir_path_hiera.realpath + rescue + @dir_path_hiera + end + end + + # @return [Pathname] + def self.dir_name_hiera_override + Pathname.new 'override' + end + + # @return [Pathname] + def self.dir_path_hiera_override + dir_path_hiera + dir_name_hiera_override + end + + def self.default_hiera_file_name + Pathname.new 'novanet-primary-controller.yaml' + end + end +end diff --git a/lib/noop/config/log.rb b/lib/noop/config/log.rb new file mode 100644 index 0000000..63ca717 --- /dev/null +++ b/lib/noop/config/log.rb @@ -0,0 +1,26 @@ +require 'logger' + +module Noop + module Config + def self.log_destination + return ENV['SPEC_DEBUG_LOG'] if ENV['SPEC_DEBUG_LOG'] + STDOUT + end + + def self.log_level + if ENV['SPEC_TASK_DEBUG'] + Logger::DEBUG + else + Logger::WARN + end + end + + def self.log + return @log if @log + @log = Logger.new log_destination + @log.level = log_level + @log.progname = 'noop_manager' + @log + end + end +end diff --git a/lib/noop/manager.rb b/lib/noop/manager.rb new file mode 100644 index 0000000..1ed6e47 --- /dev/null +++ b/lib/noop/manager.rb @@ -0,0 +1,7 @@ +require_relative 'config' +require_relative 'manager/library' +require_relative 'manager/options' +require_relative 'manager/actions' +require_relative 'manager/report' +require_relative 'manager/setup' +require_relative 'manager/xunit' diff --git a/lib/noop/manager/actions.rb b/lib/noop/manager/actions.rb new file mode 100644 index 0000000..51721a1 --- /dev/null +++ b/lib/noop/manager/actions.rb @@ -0,0 +1,153 @@ +require 'parallel' + +module Noop + class Manager + def find_tasks_without_specs + task_file_names.reject do |manifest| + spec = Noop::Utils.convert_to_spec manifest + spec_file_names.include? spec + end + end + + def debug(message) + Noop::Config.log.debug message + end + + def output(message) + puts message + end + + def parallel_run? + options[:parallel_run] and options[:parallel_run] > 0 + end + + def list_hiera_files + hiera_file_names.sort.each do |file_name_hiera| + next unless hiera_included? file_name_hiera + output file_name_hiera + end + exit(0) + end + + def list_facts_files + facts_file_names.sort.each do |file_name_facts| + next unless facts_included? file_name_facts + output file_name_facts + end + exit(0) + end + + def list_spec_files + spec_file_names.sort.each do |file_name_spec| + next unless spec_included? file_name_spec + output file_name_spec + end + exit(0) + end + + def list_task_files + task_file_names.sort.each do |file_name_task| + output file_name_task + end + exit(0) + end + + def run_all_tasks + Parallel.map(task_list, :in_threads => options[:parallel_run]) do |task| + task.run unless options[:pretend] + task + end + end + + def run_failed_tasks + Parallel.map(task_list, :in_threads => options[:parallel_run]) do |task| + next if task.success? + task.status = :pending + task.run unless options[:pretend] + task + end + end + + def load_task_reports + Parallel.map(task_list, :in_threads => options[:parallel_run]) do |task| + task.file_load_report_json + task.determine_task_status + task + end + end + + def list_tasks_without_specs + tasks_without_specs = find_tasks_without_specs.to_a + if tasks_without_specs.any? + Noop::Utils.error "There are tasks without specs: #{tasks_without_specs.join ', '}" + end + end + + def have_failed_tasks? + task_list.any? do |task| + task.failed? + end + end + + def exit_with_error_code + exit 1 if have_failed_tasks? + exit 0 + end + + + + def main + options + + if ENV['SPEC_TASK_CONSOLE'] + require 'pry' + binding.pry + exit(0) + end + + if options[:list_missing] + list_tasks_without_specs + end + + if options[:bundle_setup] + setup_bundle + end + + if options[:update_librarian_puppet] + prepare_library + end + + if options[:self_check] + check_paths + show_filters + show_library + exit(0) + end + + list_hiera_files if options[:list_hiera] + list_facts_files if options[:list_facts] + list_spec_files if options[:list_specs] + list_task_files if options[:list_tasks] + + if options[:run_failed_tasks] + load_task_reports + run_failed_tasks + task_report + exit_with_error_code + end + + if options[:load_saved_reports] + load_task_reports + task_report + save_xunit_report if options[:xunit_report] + exit_with_error_code + end + + run_all_tasks + task_report + save_xunit_report if options[:xunit_report] + exit_with_error_code + end + + end +end diff --git a/lib/noop/manager/library.rb b/lib/noop/manager/library.rb new file mode 100644 index 0000000..591ed90 --- /dev/null +++ b/lib/noop/manager/library.rb @@ -0,0 +1,283 @@ +require 'yaml' +require 'set' + +module Noop + class Manager + + # @return [Array] + def spec_file_names + return @spec_file_names if @spec_file_names + @spec_file_names = [] + Noop::Utils.error "No #{Noop::Config.dir_path_task_spec} directory!" unless Noop::Config.dir_path_task_spec.directory? + Noop::Config.dir_path_task_spec.find do |spec_file| + next unless spec_file.file? + next unless spec_file.to_s.end_with? '_spec.rb' + @spec_file_names << spec_file.relative_path_from(Noop::Config.dir_path_task_spec) + end + @spec_file_names + end + + # @return [Array] + def hiera_file_names + return @hiera_file_names if @hiera_file_names + @hiera_file_names = [] + Noop::Utils.error "No #{Noop::Config.dir_path_hiera} directory!" unless Noop::Config.dir_path_hiera.directory? + Noop::Config.dir_path_hiera.find do |hiera_name| + next unless hiera_name.file? + next unless hiera_name.to_s.end_with? '.yaml' + base_dir = hiera_name.dirname.basename + next if base_dir == Noop::Config.dir_name_hiera_override + next if base_dir == Noop::Config.dir_name_globals + @hiera_file_names << hiera_name.relative_path_from(Noop::Config.dir_path_hiera) + end + @hiera_file_names + end + + # @return [Array] + def facts_file_names + return @facts_file_names if @facts_file_names + @facts_file_names = [] + Noop::Utils.error "No #{Noop::Config.dir_path_facts} directory!" unless Noop::Config.dir_path_facts.directory? + Noop::Config.dir_path_facts.find do |facts_name| + next unless facts_name.file? + next unless facts_name.to_s.end_with? '.yaml' + next if facts_name.dirname.basename == Noop::Config.dir_name_facts_override + @facts_file_names << facts_name.relative_path_from(Noop::Config.dir_path_facts) + end + @facts_file_names + end + + # @return [Array] + def task_file_names + return @task_file_names if @task_file_names + @task_file_names = [] + Noop::Utils.error "No #{Noop::Config.dir_path_tasks_local} directory!" unless Noop::Config.dir_path_tasks_local.directory? + Noop::Config.dir_path_tasks_local.find do |task_name| + next unless task_name.file? + next unless task_name.to_s.end_with? '.pp' + @task_file_names << task_name.relative_path_from(Noop::Config.dir_path_tasks_local) + end + @task_file_names + end + + # @return [Hash Hash>] + def task_graph_metadata + return @task_graph_metadata if @task_graph_metadata + @task_graph_metadata = {} + Noop::Utils.error "No #{Noop::Config.dir_path_modules_local} directory!" unless Noop::Config.dir_path_modules_local.directory? + Noop::Config.dir_path_modules_local.find do |task_file| + next unless task_file.file? + next unless task_file.to_s.end_with? 'tasks.yaml' + begin + tasks = YAML.load_file task_file + rescue + next + end + tasks.each do |task| + id = task['id'] + @task_graph_metadata[id] = task + end + end + + @task_graph_metadata.each do |id, group_task| + next unless group_task['type'] == 'group' and group_task['tasks'].is_a? Array + group_task['tasks'].each do |task| + next unless @task_graph_metadata[task] + @task_graph_metadata[task]['groups'] = [] unless @task_graph_metadata[task]['groups'].is_a? Array + @task_graph_metadata[task]['groups'] << id + end + end + + @task_graph_metadata + end + + # @return [Hash Set>] + def assign_spec_to_roles + return @assign_spec_to_roles if @assign_spec_to_roles + @assign_spec_to_roles = {} + task_graph_metadata.values.each do |task_data| + roles = task_data['groups'] or task_data['roles'] + next unless roles.is_a? Array + file_path_manifest = task_data.fetch('parameters', {}).fetch('puppet_manifest', nil) + next unless file_path_manifest + file_path_manifest = Pathname.new file_path_manifest + file_name_manifest = file_path_manifest.relative_path_from Noop::Config.dir_path_tasks_node + file_name_spec = Noop::Utils.convert_to_spec file_name_manifest + roles = Set.new roles + @assign_spec_to_roles[file_name_spec] = Set.new unless @assign_spec_to_roles[file_name_spec].is_a? Set + @assign_spec_to_roles[file_name_spec] += roles + end + @assign_spec_to_roles + end + + # @return [Hash Set>] + def assign_hiera_to_roles + return @assign_hiera_to_roles if @assign_hiera_to_roles + @assign_hiera_to_roles = {} + hiera_file_names.each do |hiera_file| + begin + data = YAML.load_file(Noop::Config.dir_path_hiera + hiera_file) + next unless data.is_a? Hash + fqdn = data['fqdn'] + next unless fqdn + nodes = data.fetch('network_metadata', {}).fetch('nodes', nil) + next unless nodes + this_node = nodes.find do |node| + node.last['fqdn'] == fqdn + end + node_roles = this_node.last['node_roles'] + roles = Set.new + roles.merge node_roles if node_roles.is_a? Array + role = data['role'] + roles.add role if role + @assign_hiera_to_roles[hiera_file] = roles + rescue + next + end + end + @assign_hiera_to_roles + end + + def assign_spec_to_hiera + return @assign_spec_to_hiera if @assign_spec_to_hiera + @assign_spec_to_hiera = {} + assign_spec_to_roles.each do |file_name_spec, spec_roles| + hiera_files = assign_hiera_to_roles.select do |file_name_hiera, hiera_roles| + hiera_roles.intersect? spec_roles + end.keys + @assign_spec_to_hiera[file_name_spec] = hiera_files if hiera_files.any? + end + @assign_spec_to_hiera + end + + # @return [Hash Array>] + def spec_run_metadata + return @spec_run_metadata if @spec_run_metadata + @spec_run_metadata = {} + Noop::Config.dir_path_task_spec.find do |spec_file| + next unless spec_file.file? + next unless spec_file.to_s.end_with? '_spec.rb' + spec_name = spec_file.relative_path_from(Noop::Config.dir_path_task_spec) + spec_data = parse_spec_file spec_file + @spec_run_metadata[spec_name] = spec_data if spec_data.any? + end + @spec_run_metadata + end + + # @param [Pathname] task_spec + def parse_spec_file(task_spec) + task_spec_metadata = {} + + begin + text = task_spec.read + text.split("\n").each do |line| + line = line.downcase + + if line =~ /^\s*#\s*(?:yamls|hiera):\s*(.*)/ + task_spec_metadata[:hiera] = get_list_of_yamls $1 + end + if line =~ /^\s*#\s*facts:\s*(.*)/ + task_spec_metadata[:facts] = get_list_of_yamls $1 + end + if line =~ /disable_spec/ + task_spec_metadata[:disable] = true + end + + if line =~ /^\s*#\s*run:\s*(.*)/ + run_record = get_list_of_yamls $1 + if run_record.length >= 2 + run_record = { + :hiera => run_record[0], + :facts => run_record[1], + } + task_spec_metadata[:runs] = [] unless task_spec_metadata[:runs].is_a? Array + task_spec_metadata[:runs] << run_record + end + end + end + rescue + return task_spec_metadata + end + task_spec_metadata + end + + # @return [Array] + def get_list_of_yamls(line) + line = line.split /\s*,\s*|\s+/ + line.map do |yaml| + yaml = Pathname.new yaml + yaml = yaml.sub /$/, '.yaml' unless yaml.extname =~ /\.yaml/i + yaml + end + end + + def get_spec_runs(file_name_spec) + file_name_spec = Noop::Utils.convert_to_path file_name_spec + metadata = spec_run_metadata.fetch file_name_spec, {} + metadata[:facts] = [Noop::Config.default_facts_file_name] unless metadata[:facts] + metadata[:hiera] = assign_spec_to_hiera.fetch file_name_spec, [] unless metadata[:hiera] + runs = [] + metadata[:facts].product metadata[:hiera] do |facts, hiera| + run_record = { + :hiera => hiera, + :facts => facts, + } + runs << run_record + end + runs += metadata[:runs] if metadata[:runs].is_a? Array + runs + end + + def spec_included?(spec) + filter = options[:filter_specs] + return true unless filter + filter = [filter] unless filter.is_a? Array + filter.include? spec + end + + def facts_included?(facts) + filter = options[:filter_facts] + return true unless filter + filter = [filter] unless filter.is_a? Array + filter.include? facts + end + + def hiera_included?(hiera) + filter = options[:filter_hiera] + return true unless filter + filter = [filter] unless filter.is_a? Array + filter.include? hiera + end + + def skip_globals?(file_name_spec) + return false unless file_name_spec == Noop::Config.spec_name_globals + return true unless options[:filter_specs] + not spec_included? file_name_spec + end + + def spec_is_disabled?(file_name_spec) + file_name_spec = Noop::Utils.convert_to_path file_name_spec + spec_run_metadata.fetch(file_name_spec, {}).fetch(:disable, false) + end + + def task_list + return @task_list if @task_list + @task_list = [] + spec_file_names.each do |file_name_spec| + next if spec_is_disabled? file_name_spec + next if skip_globals? file_name_spec + next unless spec_included? file_name_spec + get_spec_runs(file_name_spec).each do |run| + next unless run[:hiera] and run[:facts] + next unless facts_included? run[:facts] + next unless hiera_included? run[:hiera] + task = Noop::Task.new file_name_spec, run[:hiera], run[:facts] + task.parallel = true if parallel_run? + @task_list << task + end + end + @task_list + end + + end +end diff --git a/lib/noop/manager/options.rb b/lib/noop/manager/options.rb new file mode 100644 index 0000000..5be14c6 --- /dev/null +++ b/lib/noop/manager/options.rb @@ -0,0 +1,170 @@ +require 'optparse' + +module Noop + class Manager + + def options + return @options if @options + @options = {} + options_defaults @options + + optparse = OptionParser.new do |opts| + opts.separator 'Main options:' + opts.on('-j', '--jobs JOBS', 'Parallel run RSpec jobs') do |jobs| + @options[:parallel_run] = jobs.to_i + end + opts.on('-g', '--globals', 'Run all globals tasks and update saved globals YAML files') do |jobs| + ENV['SPEC_UPDATE_GLOBALS'] = 'YES' + options[:filter_specs] = [Noop::Config.spec_name_globals] + end + opts.on('-b', '--bundle_setup', 'Setup Ruby environment using Bundle') do + @options[:bundle_setup] = true + end + opts.on('-B', '--bundle_exec', 'Use "bundle exec" to run rspec') do + ENV['SPEC_BUNDLE_EXEC'] = 'YES' + end + opts.on('-l', '--update-librarian', 'Run librarian-puppet update in the deployment directory prior to testing') do + @options[:update_librarian_puppet] = true + end + opts.on('-L', '--reset-librarian', 'Reset puppet modules to librarian versions in the deployment directory prior to testing') do + @options[:reset_librarian_puppet] = true + end + opts.on('-o', '--report_only_failed', 'Show only failed tasks and examples in the report') do + @options[:report_only_failed] = true + end + opts.on('-r', '--load_saved_reports', 'Read saved report JSON files from the previous run and show tasks report') do + @options[:load_saved_reports] = true + end + opts.on('-R', '--run_failed_tasks', 'Run the task that have previously failed again') do + @options[:run_failed_tasks] = true + end + opts.on('-M', '--list_missing', 'List all task manifests without a spec file') do + @options[:list_missing] = true + end + opts.on('-x', '--xunit_report', 'Save report in xUnit format to a file') do + @options[:xunit_report] = true + end + + opts.separator 'List options:' + opts.on('-Y', '--list_hiera', 'List all hiera yaml files') do + @options[:list_hiera] = true + end + opts.on('-S', '--list_specs', 'List all task spec files') do + @options[:list_specs] = true + end + opts.on('-F', '--list_facts', 'List all facts yaml files') do + @options[:list_facts] = true + end + opts.on('-T', '--list_tasks', 'List all task manifest files') do + @options[:list_tasks] = true + end + + opts.separator 'Filter options:' + opts.on('-s', '--specs SPEC1,SPEC2', Array, 'Run only these spec files. Example: "hosts/hosts_spec.rb,apache/apache_spec.rb"') do |specs| + @options[:filter_specs] = import_specs_list specs + end + opts.on('-y', '--yamls YAML1,YAML2', Array, 'Run only these hiera yamls. Example: "controller.yaml,compute.yaml"') do |yamls| + @options[:filter_hiera] = import_yamls_list yamls + end + opts.on('-f', '--facts FACTS1,FACTS2', Array, 'Run only these facts yamls. Example: "ubuntu.yaml,centos.yaml"') do |yamls| + @options[:filter_facts] = import_yamls_list yamls + end + # opts.on('-e', '--examples STR1,STR2', Array, 'Run only these spec examples. Example: "should compile"') do |examples| + # @options[:filter_examples] = examples + # end + + opts.separator 'Debug options:' + opts.on('-c', '--task_console', 'Run PRY console') do + ENV['SPEC_TASK_CONSOLE'] = 'YES' + end + opts.on('-C', '--rspec_console', 'Run PRY console in the ') do + ENV['SPEC_RSPEC_CONSOLE'] = 'YES' + end + opts.on('-d', '--task_debug', 'Show framework debug messages') do + ENV['SPEC_TASK_DEBUG'] = 'YES' + end + opts.on('-D', '--puppet_debug', 'Show Puppet debug messages') do + ENV['SPEC_PUPPET_DEBUG'] = 'YES' + end + opts.on('--debug_log FILE', 'Write all debug messages to this files') do |file| + ENV['SPEC_DEBUG_LOG'] = file + end + opts.on('-t', '--self-check', 'Perform self-check and diagnostic procedures') do + @options[:self_check] = true + end + opts.on('-p', '--pretend', 'Show which tasks will be run without actually running them') do + @options[:pretend] = true + end + + opts.separator 'Path options:' + opts.on('--dir_root DIR', 'Path to the test root folder') do |dir| + ENV['SPEC_ROOT_DIR'] = dir + end + opts.on('--dir_deployment DIR', 'Path to the test deployment folder') do |dir| + ENV['SPEC_DEPLOYMENT_DIR'] = dir + end + opts.on('--dir_hiera_yamls DIR', 'Path to the folder with hiera files') do |dir| + ENV['SPEC_HIERA_DIR'] = dir + end + opts.on('--dir_facts_yamls DIR', 'Path to the folder with facts yaml files') do |dir| + ENV['SPEC_FACTS_DIR'] = dir + end + opts.on('--dir_spec_files DIR', 'Path to the folder with task spec files (changing this may break puppet-rspec)') do |dir| + ENV['SPEC_SPEC_DIR'] = dir + end + opts.on('--dir_task_files DIR', 'Path to the folder with task manifest files') do |dir| + ENV['SPEC_TASK_DIR'] = dir + end + opts.on('--dir_puppet_modules DIR', 'Path to the puppet modules') do |dir| + ENV['SPEC_MODULE_PATH'] = dir + end + + opts.separator 'Spec options:' + opts.on('-A', '--catalog_show', 'Show catalog content debug output') do + ENV['SPEC_CATALOG_SHOW'] = 'YES' + end + # opts.on('--catalog_save', 'Save catalog to the files instead of comparing them with the current catalogs') do + # ENV['SPEC_CATALOG_CHECK'] = 'save' + # end + # opts.on('--catalog_check', 'Check the saved catalog against the current one') do + # ENV['SPEC_CATALOG_CHECK'] = 'check' + # end + # opts.on('--spec_generate', 'Generate specs for catalogs') do + # ENV['SPEC_SPEC_GENERATE'] = 'YES' + # end + opts.on('-a', '--spec_status', 'Show spec status blocks') do + ENV['SPEC_SHOW_STATUS'] = 'YES' + end + # opts.on('--spec_coverage', 'Show spec coverage statistics') do + # ENV['SPEC_COVERAGE'] = 'YES' + # end + # opts.on('--puppet_binary_files', 'Check if Puppet installs binary files') do + # ENV['SPEC_PUPPET_BINARY_FILES'] = 'YES' + # end + # opts.on('--file_resources DIR', 'Save file resources to this dir') do |dir| + # ENV['SPEC_SAVE_FILE_RESOURCES'] = dir + # end + + end + optparse.parse! + @options + end + + def import_specs_list(specs) + specs.map do |spec| + Noop::Utils.convert_to_spec spec + end + end + + def import_yamls_list(yamls) + yamls.map do |yaml| + Noop::Utils.convert_to_yaml yaml + end + end + + def options_defaults(options) + options[:parallel_run] = 0 + end + + end +end diff --git a/lib/noop/manager/report.rb b/lib/noop/manager/report.rb new file mode 100644 index 0000000..7f5cfe9 --- /dev/null +++ b/lib/noop/manager/report.rb @@ -0,0 +1,196 @@ +require 'erb' +require 'colorize' +require 'rexml/document' + +module Noop + class Manager + STATUS_STRING_LENGTH = 8 + + def tasks_report_structure(tasks) + tasks_report = [] + + tasks.each do |task| + task_hash = {} + task_hash[:status] = task.status + task_hash[:name] = task.to_s + task_hash[:description] = task.description + task_hash[:spec] = task.file_name_spec.to_s + task_hash[:hiera] = task.file_name_hiera.to_s + task_hash[:facts] = task.file_name_facts.to_s + task_hash[:task] = task.file_name_manifest.to_s + task_hash[:examples] = [] + + if task.report.is_a? Hash + examples = task.report['examples'] + next unless examples.is_a? Array + examples.each do |example| + example_hash = {} + example_hash[:file_path] = example['file_path'] + example_hash[:line_number] = example['line_number'] + example_hash[:description] = example['description'] + example_hash[:status] = example['status'] + example_hash[:run_time] = example['run_time'] + example_hash[:pending_message] = example['pending_message'] + exception_class = example.fetch('exception', {}).fetch('class', nil) + exception_message = example.fetch('exception', {}).fetch('message', nil) + next unless example_hash[:description] and example_hash[:status] + if exception_class and exception_message + example_hash[:exception_class] = exception_class + example_hash[:exception_message] = exception_message + end + task_hash[:examples] << example_hash + end + + summary = task.report['summary'] + task_hash[:example_count] = summary['example_count'] + task_hash[:failure_count] = summary['failure_count'] + task_hash[:pending_count] = summary['pending_count'] + task_hash[:duration] = summary['duration'] + end + + tasks_report << task_hash + end + tasks_report + end + + def output_task_status(task) + return if options[:report_only_failed] and task.success? + line = task_status_string task + line += "#{task.file_base_spec.to_s.ljust max_length_spec + 1}" + line += "#{task.file_base_facts.to_s.ljust max_length_facts + 1}" + line += "#{task.file_base_hiera.to_s.ljust max_length_hiera + 1}" + output line + output_task_examples task + end + + def output_task_examples(task) + return unless task.report.is_a? Hash + examples = task.report['examples'] + return unless examples.is_a? Array + examples.each do |example| + description = example['description'] + status = example['status'] + next unless description and status + next if options[:report_only_failed] and status == 'passed' + line = " #{example_status_string status} #{description}" + exception_message = example.fetch('exception', {}).fetch('message', nil) + line += " (#{exception_message.colorize :cyan})" if exception_message + output line + end + end + + def task_status_string(task) + if task.pending? + 'PENDING'.ljust(STATUS_STRING_LENGTH).colorize :blue + elsif task.success? + 'SUCCESS'.ljust(STATUS_STRING_LENGTH).colorize :green + elsif task.failed? + 'FAILED'.ljust(STATUS_STRING_LENGTH).colorize :red + else + task.status + end + end + + def example_status_string(status) + if status == 'passed' + status.ljust(STATUS_STRING_LENGTH).colorize :green + elsif status == 'failed' + status.ljust(STATUS_STRING_LENGTH).colorize :red + else + status.ljust(STATUS_STRING_LENGTH).colorize :blue + end + end + + def directory_check_status_string(directory) + if directory.directory? + 'SUCCESS'.ljust(STATUS_STRING_LENGTH).colorize :green + else + 'FAILED'.ljust(STATUS_STRING_LENGTH).colorize :red + end + end + + def max_length_spec + return @max_length_spec if @max_length_spec + @max_length_spec = task_list.map do |task| + task.file_base_spec.to_s.length + end.max + end + + def max_length_hiera + return @max_length_hiera if @max_length_hiera + @max_length_hiera = task_list.map do |task| + task.file_base_hiera.to_s.length + end.max + end + + def max_length_facts + return @max_length_facts if @max_length_facts + @max_length_facts = task_list.map do |task| + task.file_base_facts.to_s.length + end.max + end + + def task_report + task_list.each do |task| + output_task_status task + end + end + + def show_filters + if options[:filter_specs] + options[:filter_specs] = [options[:filter_specs]] unless options[:filter_specs].is_a? Array + output "Spec filter: #{options[:filter_specs].join ', '}" + end + if options[:filter_facts] + options[:filter_facts] = [options[:filter_facts]] unless options[:filter_facts].is_a? Array + output "Facts filter: #{options[:filter_facts].join ', '}" + end + if options[:filter_hiera] + options[:filter_hiera] = [options[:filter_hiera]] unless options[:filter_hiera].is_a? Array + output "Hiera filter: #{options[:filter_hiera].join ', '}" + end + if options[:filter_examples] + options[:filter_examples] = [options[:filter_examples]] unless options[:filter_examples].is_a? Array + output "Examples filter: #{options[:filter_examples].join ', '}" + end + end + + def show_library + template = <<-'eof' +<%= '=' * 80 %> +Tasks discovered: <%= task_file_names.length %> +Specs discovered: <%= spec_file_names.length %> +Hiera discovered: <%= hiera_file_names.length %> +Facts discovered: <%= facts_file_names.length %> +Tasks in graph metadata: <%= task_graph_metadata.length %> +Tasks with spec metadata: <%= spec_run_metadata.length %> +Total tasks to run: <%= task_list.count %> + eof + output ERB.new(template, nil, '-').result(binding) + end + + def check_paths + paths = [ + :dir_path_config, + :dir_path_root, + :dir_path_task_spec, + :dir_path_modules_local, + :dir_path_tasks_local, + :dir_path_deployment, + :dir_path_workspace, + :dir_path_hiera, + :dir_path_hiera_override, + :dir_path_facts, + :dir_path_facts_override, + :dir_path_globals, + :dir_path_reports, + ] + max_length = paths.map { |p| p.to_s.length }.max + paths.each do |path| + directory = Noop::Config.send path + output "#{directory_check_status_string directory} #{path.to_s.ljust max_length} #{directory}" + end + end + + end +end diff --git a/lib/noop/manager/setup.rb b/lib/noop/manager/setup.rb new file mode 100644 index 0000000..bfebc9d --- /dev/null +++ b/lib/noop/manager/setup.rb @@ -0,0 +1,46 @@ +module Noop + class Manager + PUPPET_GEM_VERSION = '~> 3.8.0' + + def dir_path_gem_home + return Pathname.new ENV['GEM_HOME'] if ENV['GEM_HOME'] + dir_name_bundle = Pathname.new '.bundled_gems' + Noop::Utils.dir_path_workspace + dir_name_bundle + end + + def bundle_installed? + `bundle --version` + $?.exitstatus == 0 + end + + def setup_bundle + Noop::Utils.error 'Bundle is not installed!' unless bundle_installed? + ENV['GEM_HOME'] = dir_path_gem_home.to_s + ENV['PUPPET_GEM_VERSION'] = PUPPET_GEM_VERSION unless ENV['PUPPET_GEM_VERSION'] + Dir.chdir Noop::Config.dir_path_root + Noop::Utils.run 'bundle install' + Noop::Utils.run 'bundle update' + Noop::Utils.error 'Could not prepare bundle environment!' if $?.exitstatus != 0 + end + + # run librarian-puppet to fetch modules as necessary + def prepare_library + # these are needed to ensure we have the correctly bundle + ENV['PUPPET_GEM_VERSION'] = PUPPET_GEM_VERSION unless ENV['PUPPET_GEM_VERSION'] + ENV['BUNDLE_DIR'] = dir_path_gem_home.to_s + ENV['GEM_HOME'] = dir_path_gem_home.to_s + command = './update_modules.sh -v' + # pass the bundle parameter to update_modules if specified for this script + command = command + ' -b' if options[:bundle_exec] + # pass the reset parameter to update_modules if specified for this script + command = command + ' -r' if options[:reset_librarian_puppet] + + Noop::Utils.debug 'Starting update_modules script' + Dir.chdir Noop::Config.dir_path_deployment + Noop::Utils.run command + Noop::Utils.error 'Unable to update upstream puppet modules using librarian-puppet!' if $?.exitstatus != 0 + Noop::Utils.debug 'Finished update_modules script' + end + + end +end diff --git a/lib/noop/manager/xunit.rb b/lib/noop/manager/xunit.rb new file mode 100644 index 0000000..eac79a6 --- /dev/null +++ b/lib/noop/manager/xunit.rb @@ -0,0 +1,86 @@ +module Noop + class Manager + def xunit_report(tasks) + tasks_report = tasks_report_structure tasks + return unless tasks_report.is_a? Array + document = REXML::Document.new + declaration = REXML::XMLDecl.new + declaration.encoding = 'UTF-8' + declaration.version = '1.0' + document.add declaration + testsuites = document.add_element 'testsuites' + tests = 0 + failures = 0 + task_id = 0 + + tasks_report.each do |task| + testsuite = testsuites.add_element 'testsuite' + testsuite.add_attribute 'id', task_id + task_id += 1 + testsuite.add_attribute 'name', task[:description] + testsuite.add_attribute 'package', task[:name] + testsuite.add_attribute 'tests', task[:example_count] + testsuite.add_attribute 'failures', task[:failure_count] + testsuite.add_attribute 'skipped', task[:pending_count] + testsuite.add_attribute 'time', task[:duration] + testsuite.add_attribute 'status', task[:status] + + properties = testsuite.add_element 'properties' + property_task = properties.add_element 'property' + property_task.add_attribute 'name', 'task' + property_task.add_attribute 'value', task[:task] + property_spec = properties.add_element 'property' + property_spec.add_attribute 'name', 'spec' + property_spec.add_attribute 'value', task[:spec] + property_hiera = properties.add_element 'property' + property_hiera.add_attribute 'name', 'hiera' + property_hiera.add_attribute 'value', task[:hiera] + property_facts = properties.add_element 'property' + property_facts.add_attribute 'name', 'facts' + property_facts.add_attribute 'value', task[:facts] + + if task[:examples].is_a? Array + task[:examples].each do |example| + tests += 1 + testcase = testsuite.add_element 'testcase' + testcase.add_attribute 'name', example[:description] + testcase.add_attribute 'classname', "#{example[:file_path]}:#{example[:line_number]}" + testcase.add_attribute 'time', example[:run_time] + testcase.add_attribute 'status', example[:status] + if example[:status] == 'pending' + skipped = testcase.add_element 'skipped' + skipped.add_attribute 'message', example[:pending_message] if example[:pending_message] + end + if example[:status] == 'failed' + failures += 1 + end + if example[:exception_message] and example[:exception_class] + failure = testcase.add_element 'failure' + failure.add_attribute 'message', example[:exception_message] + failure.add_attribute 'type', example[:exception_class] + end + end + end + end + testsuites.add_attribute 'tests', tests + testsuites.add_attribute 'failures', failures + document.to_s + end + + def file_name_xunit_report + Pathname.new 'report.xml' + end + + def file_path_xunit_report + Noop::Config.dir_path_reports + file_name_xunit_report + end + + def save_xunit_report + File.open(file_path_xunit_report.to_s, 'w') do |file| + file.puts xunit_report task_list + end + Noop::Utils.debug "xUnit XML report was saved to: #{file_path_xunit_report.to_s}" + end + + end +end diff --git a/lib/noop/matchers.rb b/lib/noop/matchers.rb new file mode 100644 index 0000000..8b12779 --- /dev/null +++ b/lib/noop/matchers.rb @@ -0,0 +1,40 @@ +module FuelRelationshipGraphMatchers + class EnsureTransitiveDependency + def initialize(before, after) + @before = before + @after = after + end + + def matches?(actual_graph) + @actual_graph = actual_graph + + @dependents = actual_graph.dependents( + vertex_called(actual_graph, @before)) + !@dependents.find_all { |d| d.ref =~ /#{Regexp.escape(@after)}/i }.empty? + end + + def failure_message + msg = "expected deployment graph to contain a transitional dependency between\n" + msg << "#{@before} and #{@after} but it did not happen\n" + msg << "#{@before} dependents are: #{@dependents.map {|dep| dep.ref}}\n" + msg + end + + def failure_message_when_negated + msg = "expected deployment graph to NOT contain a transitional dependency between\n" + msg << "#{@before} and #{@after} but it did not happen\n" + msg << "#{@before} dependents are: #{@dependents.map {|dep| dep.ref}}\n" + msg + end + + private + + def vertex_called(graph, name) + graph.vertices.find { |v| v.ref =~ /#{Regexp.escape(name)}/i } + end + end + + def ensure_transitive_dependency(before, after) + EnsureTransitiveDependency.new(before, after) + end +end diff --git a/lib/noop/task.rb b/lib/noop/task.rb new file mode 100644 index 0000000..bd84a90 --- /dev/null +++ b/lib/noop/task.rb @@ -0,0 +1,10 @@ +require_relative 'config' +require_relative 'task/base' +require_relative 'task/facts' +require_relative 'task/globals' +require_relative 'task/hiera' +require_relative 'task/spec' +require_relative 'task/run' +require_relative 'task/overrides' +require_relative 'task/catalog' +require_relative 'task/helpers' diff --git a/lib/noop/task/base.rb b/lib/noop/task/base.rb new file mode 100644 index 0000000..2f98746 --- /dev/null +++ b/lib/noop/task/base.rb @@ -0,0 +1,85 @@ +module Noop + class Task + def initialize(spec=nil, hiera=nil, facts=nil) + self.status = :pending + self.file_name_spec = Noop::Utils.convert_to_spec spec if spec + self.file_name_hiera = hiera if hiera + self.file_name_facts = facts if facts + self.pid = Process.pid + self.thread = Thread.current.object_id + @parallel = false + Noop::Utils.warning "#{self}: Validation is failed!" unless valid? + end + + attr_accessor :parallel + attr_accessor :pid + attr_accessor :thread + attr_accessor :status + + def success? + status == :success + end + + def failed? + status == :failed + end + + def pending? + status == :pending + end + + # @return [true,false] + def parallel_run? + parallel + end + + # @return [true,false] + def valid? + unless file_path_spec.exist? + Noop::Utils.warning "No spec file: #{file_path_spec}!" + return false + end + unless file_path_manifest.exist? + Noop::Utils.warning "No task file: #{file_path_manifest}!" + return false + end + unless file_path_hiera.exist? + Noop::Utils.warning "No hiera file: #{file_path_hiera}!" + return false + end + unless file_path_facts.exist? + Noop::Utils.error "No facts file: #{file_path_hiera}!" + return false + end + true + end + + # @return [String] + def to_s + "Task[#{file_base_spec}]" + end + + def description + message = '' + message += "Task: #{file_name_manifest}" + message += " Spec: #{file_name_spec}" + message += " Hiera: #{file_name_hiera}" + message += " Facts: #{file_name_facts}" + message += " Status: #{status}" + message + end + + def process_info + message = '' + message + "Object: #{object_id}" + message += " Pid: #{pid}" if pid + message += " Thread: #{thread}" if thread + message + end + + # @return [Strong] + def inspect + "Task[#{description}]" + end + end +end diff --git a/lib/noop/task/catalog.rb b/lib/noop/task/catalog.rb new file mode 100644 index 0000000..bca4713 --- /dev/null +++ b/lib/noop/task/catalog.rb @@ -0,0 +1,145 @@ +require 'erb' + +module Noop + class Task + # @return [String] + def status_report(context) + task = context.task + template = <<-'eof' +Facts: <%= task.file_path_facts %> +Hiera: <%= task.file_path_hiera %> +Spec: <%= task.file_path_spec %> +Manifest: <%= task.file_path_manifest %> + +Node: <%= task.hiera_lookup 'fqdn' or '?' %> +Role: <%= task.hiera_lookup 'role' or '?' %> + +Puppet: <%= Puppet.version %> +Ruby: <%= RUBY_VERSION %> + +Hiera hierarchy: +<% task.hiera_hierarchy.each do |element| -%> +* <%= element %> +<% end -%> + +Facts hierarchy: +<% task.facts_hierarchy.reverse.each do |element| -%> +* <%= element %> +<% end -%> + eof + ERB.new(template, nil, '-').result(binding) + end + + # dumps the entire catalog structure to the text + # representation in the Puppet language + # @param context [Object] the context from the rspec test + # @param resources_filter [Array] the list of resources to dump. Dump all resources if not given + def catalog_dump(context, resources_filter = []) + catalog = context.subject + catalog = catalog.call if catalog.is_a? Proc + text = '' + resources_filter = [resources_filter] unless resources_filter.is_a? Array + catalog.resources.select do |catalog_resource| + if catalog_resource.type == 'Class' + next false if %w(main Settings).include? catalog_resource.title.to_s + end + next true unless resources_filter.any? + resources_filter.find do |filter_resource| + resources_are_same? catalog_resource, filter_resource + end + end.sort_by do |catalog_resource| + catalog_resource.to_s + end.each do |catalog_resource| + text += dump_resource(catalog_resource) + "\n" + text += "\n" + end + text + end + + # takes a parameter value and formats it to the literal value + # that could be placed in the Puppet manifest + # @param value [String, Array, Hash, true, false, nil] + # @return [String] + def parameter_value_format(value) + case value + when TrueClass then 'true' + when FalseClass then 'false' + when NilClass then 'undef' + when Array then begin + array = value.collect do |v| + parameter_value_format v + end.join(', ') + "[ #{array} ]" + end + when Hash then begin + hash = value.keys.sort do |a, b| + a.to_s <=> b.to_s + end.collect do |key| + "#{parameter_value_format key.to_s} => #{parameter_value_format value[key]}" + end.join(', ') + "{ #{hash} }" + end + when Numeric, Symbol then parameter_value_format value.to_s + when String then begin + # escapes single quote characters and wrap into them + "'#{value.gsub "'", '\\\\\''}'" + end + else value.to_s + end + end + + # take a resource object and generate a manifest representation of it + # in the Puppet language. Replaces "to_manifest" Puppet function which + # is not working correctly. + # @param resource [Puppet::Resource] + # @return [String] + def dump_resource(resource) + return '' unless resource.is_a? Puppet::Resource or resource.is_a? Puppet::Parser::Resource + attributes = resource.keys + if attributes.include?(:name) and resource[:name] == resource[:title] + attributes.delete(:name) + end + attribute_max_length = attributes.inject(0) do |max_length, attribute| + attribute.to_s.length > max_length ? attribute.to_s.length : max_length + end + attributes.sort! + if attributes.first != :ensure && attributes.include?(:ensure) + attributes.delete(:ensure) + attributes.unshift(:ensure) + end + attributes_text_block = attributes.map { |attribute| + value = resource[attribute] + " #{attribute.to_s.ljust attribute_max_length} => #{parameter_value_format value},\n" + }.join + "#{resource.type.to_s.downcase} { '#{resource.title.to_s}' :\n#{attributes_text_block}}" + end + + # This function preprocesses both saved and generated + # catalogs before they will be compared. It allows us to ignore + # irrelevant changes in the catalogs: + # * ignore trailing whitespaces + # * ignore empty lines + # @param data [String] + # @return [String] + def preprocess_catalog_data(data) + clear_data = [] + data.to_s.split("\n").each do |line| + line = line.rstrip + next if line == '' + clear_data << line + end + clear_data.join "\n" + end + + # check if two resources have same type and title + # @param res1 [Puppet::Resource] + # @param res2 [Puppet::Resource] + # @return [TrueClass, False,Class] + def resources_are_same?(res1, res2) + res1 = res1.to_s.downcase.gsub %r|'"|, '' + res2 = res2.to_s.downcase.gsub %r|'"|, '' + res1 == res2 + end + + end +end diff --git a/lib/noop/task/facts.rb b/lib/noop/task/facts.rb new file mode 100644 index 0000000..0ce3a08 --- /dev/null +++ b/lib/noop/task/facts.rb @@ -0,0 +1,98 @@ +require 'yaml' + +module Noop + class Task + # @return [Pathname] + def file_name_facts + return @file_name_facts if @file_name_facts + self.file_name_facts = Noop::Utils.path_from_env 'SPEC_FACTS_NAME' + return @file_name_facts if @file_name_facts + self.file_name_facts = Noop::Config.default_facts_file_name + @file_name_facts + end + alias :facts :file_name_facts + + # @return [Pathname] + def file_name_facts=(value) + return if value.nil? + @file_name_facts = Noop::Utils.convert_to_path value + @file_name_facts = @file_name_facts.sub_ext '.yaml' if @file_name_facts.extname == '' + end + alias :facts= :file_name_facts= + + # @return [Pathname] + def file_base_facts + file_name_facts.basename.sub_ext '' + end + + # @return [Pathname] + def file_path_facts + Noop::Config.dir_path_facts + file_name_facts + end + + # @return [true,false] + def file_present_facts? + return false unless file_path_facts + file_path_facts.readable? + end + + # @return [Pathname] + def file_name_facts_override + file_name_task_extension + end + + # @return [Pathname] + def file_path_facts_override + Noop::Config.dir_path_facts_override + file_name_facts_override + end + + # @return [true,false] + def file_present_facts_override? + return unless file_path_facts_override + file_path_facts_override.readable? + end + + # @return [Array] + def facts_hierarchy + file_paths = [] + file_paths << file_path_facts.to_s if file_present_facts? + file_paths << file_path_facts_override.to_s if file_present_facts_override? + file_paths + end + + def add_host_names(facts_data) + hostname = hiera_lookup 'node_name' + fqdn = hiera_lookup 'fqdn' + facts_data[:hostname] = hostname if hostname + facts_data[:l3_fqdn_hostname] = hostname if hostname + facts_data[:fqdn] = fqdn if fqdn + end + + # @return [Hash] + def facts_data + facts_data = {} + facts_hierarchy.each do |file_path| + begin + file_data = YAML.load_file file_path + next unless file_data.is_a? Hash + facts_data.merge! file_data + rescue + next + end + end + add_host_names facts_data + facts_data + end + alias :ubuntu_facts :facts_data + alias :centos_facts :facts_data + + def hostname + facts_data[:hostname] + end + + def fqdn + facts_data[:fqdn] + end + + end +end diff --git a/lib/noop/task/globals.rb b/lib/noop/task/globals.rb new file mode 100644 index 0000000..5611121 --- /dev/null +++ b/lib/noop/task/globals.rb @@ -0,0 +1,37 @@ +module Noop + class Task + + # @return [Pathname] + def file_path_globals + Noop::Config.dir_path_globals + file_name_hiera + end + + # @return [true,false] + def file_present_globals? + return false unless file_path_globals + file_path_globals.readable? + end + + def write_file_globals(content) + File.open(file_path_globals.to_s, 'w') do |file| + file.write content + end + Noop::Utils.debug "Globals YAML saved to: '#{file_path_globals.to_s}'" + end + + # @return [Pathname] + def file_name_globals + file_name_hiera + end + + # @return [Pathname] + def file_base_globals + file_base_hiera + end + + # @return [Pathname] + def element_globals + Noop::Config.dir_name_globals + file_base_globals + end + end +end diff --git a/lib/noop/task/helpers.rb b/lib/noop/task/helpers.rb new file mode 100644 index 0000000..f4ac3e1 --- /dev/null +++ b/lib/noop/task/helpers.rb @@ -0,0 +1,60 @@ +module Noop + class Task + + def resource_parameter_value(context, resource_type, resource_name, parameter) + catalog = context.subject + catalog = catalog.call if catalog.is_a? Proc + resource = catalog.resource resource_type, resource_name + fail "No resource type: '#{resource_type}' name: '#{resource_name}' in the catalog!" unless resource + resource[parameter.to_sym] + end + + # save the current puppet scope + def puppet_scope=(value) + @puppet_scope = value + end + + def puppet_scope + return @puppet_scope if @puppet_scope + PuppetlabsSpec::PuppetInternals.scope + end + + # load a puppet function if it's not already loaded + def puppet_function_load(name) + name = name.to_sym unless name.is_a? Symbol + Puppet::Parser::Functions.autoloader.load name + end + + # call a puppet function and return it's value + def puppet_function(name, *args) + name = name.to_sym unless name.is_a? Symbol + puppet_function_load name + fail "Could not load Puppet function '#{name}'!" unless puppet_scope.respond_to? "function_#{name}".to_sym + puppet_scope.send "function_#{name}".to_sym, args + end + + # take a variable value from the saved puppet scope + def lookupvar(name) + puppet_scope.lookupvar name + end + + # convert resource catalog to a RAL catalog + # and run "generate" hook for all resources + def create_ral_catalog(context) + catalog = context.subject + catalog = catalog.call if catalog.is_a? Proc + ral_catalog = catalog.to_ral + ral_catalog.resources.each do |resource| + next unless resource.respond_to? :generate + generated = resource.generate + next unless generated.is_a? Array + generated.each do |generated_resource| + next unless generated_resource.is_a? Puppet::Type + ral_catalog.add_resource generated_resource + end + end + lambda { ral_catalog } + end + + end +end diff --git a/lib/noop/task/hiera.rb b/lib/noop/task/hiera.rb new file mode 100644 index 0000000..bd84b64 --- /dev/null +++ b/lib/noop/task/hiera.rb @@ -0,0 +1,146 @@ +module Noop + class Task + # @return [Pathname] + def file_name_hiera + return @file_name_hiera if @file_name_hiera + self.file_name_hiera = Noop::Utils.path_from_env 'SPEC_ASTUTE_FILE_NAME', 'SPEC_HIERA_NAME' + return @file_name_hiera if @file_name_hiera + self.file_name_hiera = Noop::Config.default_hiera_file_name unless + @file_name_hiera + end + + def file_name_hiera=(value) + return if value.nil? + @file_name_hiera = Noop::Utils.convert_to_path value + @file_name_hiera = @file_name_hiera.sub_ext '.yaml' if @file_name_hiera.extname == '' + end + + # @return [Pathname] + def file_base_hiera + file_name_hiera.basename.sub_ext '' + end + + # @return [Pathname] + def file_path_hiera + Noop::Config.dir_path_hiera + file_name_hiera + end + + # @return [true,false] + def file_present_hiera? + return false unless file_path_hiera + file_path_hiera.readable? + end + + # @return [Pathname] + def element_hiera + file_base_hiera + end + + # @return [Pathname] + def file_name_hiera_override + file_name_task_extension + end + + # @return [Pathname] + def file_path_hiera_override + Noop::Config.dir_path_hiera_override + file_name_hiera_override + end + + # @return [true,false] + def file_present_hiera_override? + return unless file_path_hiera_override + file_path_hiera_override.readable? + end + + # @return [Pathname] + def element_hiera_override + override_file = file_name_hiera_override + return unless override_file + Noop::Config.dir_name_hiera_override + override_file.sub_ext('') + end + + # @return [String] + def hiera_logger + if ENV['SPEC_PUPPET_DEBUG'] + 'console' + else + 'noop' + end + end + + # @return [Array] + def hiera_hierarchy + elements = [] + elements << element_hiera_override.to_s if file_present_hiera_override? + elements << element_hiera.to_s if file_present_hiera? + elements << element_globals.to_s if file_present_globals? + elements + end + + # @return [Hash] + def hiera_config + { + :backends => [ + 'yaml', + ], + :yaml => { + :datadir => Noop::Config.dir_path_hiera.to_s, + }, + :hierarchy => hiera_hierarchy, + :logger => hiera_logger, + :merge_behavior => :deeper, + } + end + + # @return [Hiera] + def hiera_object + return @hiera_object if @hiera_object + @hiera_object = Hiera.new(:config => hiera_config) + Hiera.logger = hiera_config[:logger] + @hiera_object + end + + # @return [Object] + def hiera_lookup(key, default = nil, resolution_type = :priority) + key = key.to_s + # def lookup(key, default, scope, order_override=nil, resolution_type=:priority) + hiera_object.lookup key, default, {}, nil, resolution_type + end + alias :hiera :hiera_lookup + + # @return [Hash] + def hiera_hash(key, default = nil) + hiera_lookup key, default, :hash + end + + # @return [Array] + def hiera_array(key, default = nil) + hiera_lookup key, default, :array + end + + # @return [Object] + def hiera_structure(key, default = nil, separator = '/', resolution_type = :priority) + path_lookup = lambda do |data, path, default_value| + break default_value unless data + break data unless path.is_a? Array and path.any? + break default_value unless data.is_a? Hash or data.is_a? Array + + key = path.shift + if data.is_a? Array + begin + key = Integer key + rescue ArgumentError + break default_value + end + end + path_lookup.call data[key], path, default_value + end + + path = key.split separator + key = path.shift + data = hiera key, nil, resolution_type + path_lookup.call data, path, default + end + + end +end diff --git a/lib/noop/task/overrides.rb b/lib/noop/task/overrides.rb new file mode 100644 index 0000000..1bf4c0d --- /dev/null +++ b/lib/noop/task/overrides.rb @@ -0,0 +1,83 @@ +module Noop + class Task + def setup_overrides + # puppet_default_settings + hiera_config_override + puppet_debug_override if ENV['SPEC_PUPPET_DEBUG'] + setup_manifest + puppet_resource_scope_override + end + + def setup_manifest + RSpec.configuration.manifest = file_path_manifest.to_s + RSpec.configuration.module_path = Noop::Config.dir_path_modules_local.to_s + RSpec.configuration.manifest_dir = Noop::Config.dir_path_tasks_local.to_s + end + + def hiera_config_override + class << HieraPuppet + def hiera + @hiera ||= Hiera.new(:config => hiera_config) + Hiera.logger = 'noop' + @hiera + end + end + + class << Hiera::Config + attr_accessor :config + + def load(source) + @config ||= {} + end + + def yaml_load_file(source) + @config ||= {} + end + + def []=(key, value) + @config ||= {} + @config[key] = value + end + end + Hiera::Config.config = hiera_config + end + + def puppet_resource_scope_override + Puppet::Parser::Resource.module_eval do + def initialize(*args) + raise ArgumentError, "Resources require a hash as last argument" unless args.last.is_a? Hash + raise ArgumentError, "Resources require a scope" unless args.last[:scope] + super + Noop.task.puppet_scope = scope + @source ||= scope.source + end + end + end + + def puppet_debug_override + Puppet::Util::Log.level = :debug + Puppet::Util::Log.newdestination(:console) + end + + # These settings are pulled from the Puppet TestHelper + # (See Puppet::Test::TestHelper.initialize_settings_before_each) + # These items used to be setup in puppet 3.4 but were moved to before tests + # which breaks our testing framework because we attempt to call + # PuppetlabsSpec::PuppetInternals.scope and + # Puppet::Parser::Function.autoload.load prior to the testing being run. + # This results in an rspec failure so we need to initialize the basic + # settings up front to prevent issues with test framework. See PUP-5601 + def puppet_default_settings + Puppet.settings.initialize_app_defaults( + { + :logdir => '/dev/null', + :confdir => '/dev/null', + :vardir => '/dev/null', + :rundir => '/dev/null', + :hiera_config => '/dev/null', + } + ) + end + + end +end diff --git a/lib/noop/task/run.rb b/lib/noop/task/run.rb new file mode 100644 index 0000000..6233844 --- /dev/null +++ b/lib/noop/task/run.rb @@ -0,0 +1,98 @@ +require 'json' + +module Noop + class Task + def run + return unless pending? + self.pid = Process.pid + self.thread = Thread.current.object_id + Noop::Utils.debug "RUN: #{self.inspect}" + file_remove_report_json + rspec_command_run + file_load_report_json + determine_task_status + Noop::Utils.debug "FINISH: #{self.inspect}" + status + end + + def file_load_report_json + self.report = file_data_report_json + end + + def set_status_value(value) + if value.is_a? TrueClass + self.status = :success + elsif value.is_a? FalseClass + self.status = :failed + else + self.status = :pending + end + end + + def determine_task_status + if report.is_a? Hash + failures = report.fetch('summary', {}).fetch('failure_count', nil) + if failures.is_a? Numeric + set_status_value(failures == 0) + end + end + status + end + + # @return [Pathname] + def file_name_report_json + Noop::Utils.convert_to_path "#{file_name_task_extension.sub_ext ''}_#{file_base_hiera}_#{file_base_facts}.json" + end + + # @return [Pathname] + def file_path_report_json + Noop::Config.dir_path_reports + file_name_report_json + end + + # @return [Hash] + def file_data_report_json + return unless file_present_report_json? + file_data = nil + begin + file_content = File.read file_path_report_json.to_s + file_data = JSON.load file_content + return unless file_data.is_a? Hash + rescue + nil + end + file_data + end + + def file_remove_report_json + file_path_report_json.unlink if file_present_report_json? + end + + # @return [true,false] + def file_present_report_json? + file_path_report_json.exist? + end + + def rspec_options + options = '--color --tty' + options += ' --format documentation' unless parallel_run? + options + end + + # @return [true,false] + def rspec_command_run + environment = { + 'SPEC_HIERA_NAME' => file_name_hiera.to_s, + 'SPEC_FACTS_NAME' => file_name_facts.to_s, + 'SPEC_FILE_NAME' => file_name_spec.to_s, + } + command = "rspec #{file_path_spec.to_s} #{rspec_options} --format json --out #{file_path_report_json.to_s}" + command = "bundle exec #{command}" if ENV['SPEC_BUNDLE_EXEC'] + Dir.chdir Noop::Config.dir_path_root + success = Noop::Utils.run environment, command + set_status_value success + success + end + + attr_accessor :report + end +end diff --git a/lib/noop/task/spec.rb b/lib/noop/task/spec.rb new file mode 100644 index 0000000..64463ed --- /dev/null +++ b/lib/noop/task/spec.rb @@ -0,0 +1,48 @@ +module Noop + class Task + # @return [Pathname] + def file_name_spec + return @file_name_spec if @file_name_spec + self.file_name_spec = Noop::Utils.path_from_env 'SPEC_FILE_NAME' + @file_name_spec + end + + # @return [Pathname] + def file_base_spec + Noop::Utils.convert_to_path(file_name_spec.to_s.gsub /_spec\.rb$/, '') + end + + # @return [Pathname] + def file_name_spec=(value) + return if value.nil? + @file_name_spec = Noop::Utils.convert_to_spec value + @file_name_spec + end + + # @return [Pathname] + def file_name_manifest + Noop::Utils.convert_to_manifest file_name_spec + end + + # @return [Pathname] + def file_path_manifest + Noop::Config.dir_path_tasks_local + file_name_manifest + end + + # @return [Pathname] + def file_path_spec + Noop::Config.dir_path_task_spec + file_name_spec + end + + # @return [true,false] + def file_present_spec + file_path_spec.readable? + end + + # @return [Pathname] + def file_name_task_extension + Noop::Utils.convert_to_path(file_base_spec.to_s.gsub('/', '-') + '.yaml') + end + + end +end diff --git a/lib/noop/utils.rb b/lib/noop/utils.rb new file mode 100644 index 0000000..5c66391 --- /dev/null +++ b/lib/noop/utils.rb @@ -0,0 +1,81 @@ +module Noop + module Utils + # @param [Array, String] names + # @return [Pathname, nil] + def self.path_from_env(*names) + names.each do |name| + name = name.to_s + return convert_to_path ENV[name] if ENV[name] + end + nil + end + + # @param [Object] value + # @return [Pathname] + def self.convert_to_path(value) + value = Pathname.new value.to_s unless value.is_a? Pathname + value + end + + def self.convert_to_manifest(spec) + manifest = spec.to_s.gsub /_spec\.rb$/, '.pp' + convert_to_path manifest + end + + def self.convert_to_spec(value) + value = value.to_s.chomp.strip + value = value[0...-3] if value.end_with? '.pp' + value += '_spec.rb' unless value.end_with? '_spec.rb' + convert_to_path value + end + + def self.convert_to_yaml(value) + value = value.to_s.chomp.strip + value = convert_to_path value + value = value.sub /$/, '.yaml' unless value.extname =~ /\.yaml/i + value + end + + # Run the code block inside the tests directory + # and then return back + def self.inside_task_root_dir + current_directory = Dir.pwd + Dir.chdir Noop::Config.dir_path_root + result = yield + Dir.chdir current_directory if current_directory + result + end + + # Run the code block inside the deployment driectory + # and then return back + def self.inside_deployment_directory + current_directory = Dir.pwd + Dir.chdir Noop::Config.dir_path_deployment + result = yield + Dir.chdir current_directory if current_directory + result + end + + def self.run(*args) + debug "CMD: #{args.inspect} PWD: #{Dir.pwd}" + system *args + end + + def self.debug(message) + Noop::Config.log.debug message + end + + def self.info(message) + Noop::Config.log.info message + end + + def self.warning(message) + Noop::Config.log.warn message + end + + def self.error(message) + Noop::Config.log.fatal message + exit(1) + end + end +end diff --git a/modules/.gitkeep b/modules/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/noop_tests.rb b/noop_tests.rb new file mode 100755 index 0000000..5e5a2ba --- /dev/null +++ b/noop_tests.rb @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby + +require_relative 'lib/noop/config' +require_relative 'lib/noop/task' +require_relative 'lib/noop/manager' +require_relative 'lib/noop/utils' + +manager = Noop::Manager.new +manager.main diff --git a/reports/.gitignore b/reports/.gitignore new file mode 100644 index 0000000..67c51fe --- /dev/null +++ b/reports/.gitignore @@ -0,0 +1,2 @@ +*.xml +*.json diff --git a/spec/.gitignore b/spec/.gitignore new file mode 100644 index 0000000..6ff331c --- /dev/null +++ b/spec/.gitignore @@ -0,0 +1 @@ +hosts diff --git a/spec/lib/config_spec.rb b/spec/lib/config_spec.rb new file mode 100644 index 0000000..77a8d16 --- /dev/null +++ b/spec/lib/config_spec.rb @@ -0,0 +1,137 @@ +require 'spec_helper' +require 'noop/config' + +describe Noop::Config do + let (:root) do + File.absolute_path File.join File.dirname(__FILE__), '..', '..' + end + + context 'base' do + it 'dir_path_config' do + expect(subject.dir_path_config).to be_a Pathname + expect(subject.dir_path_config.to_s).to eq "#{root}/lib/noop/config" + end + + it 'dir_path_root' do + expect(subject.dir_path_root).to be_a Pathname + expect(subject.dir_path_root.to_s).to eq root + end + + it 'dir_path_task_spec' do + expect(subject.dir_path_task_spec).to be_a Pathname + expect(subject.dir_path_task_spec.to_s).to eq "#{root}/spec/hosts" + end + + it 'dir_path_modules_local' do + expect(subject.dir_path_modules_local).to be_a Pathname + expect(subject.dir_path_modules_local.to_s).to eq "#{root}/modules" + end + + it 'dir_path_tasks_local' do + expect(subject.dir_path_tasks_local).to be_a Pathname + expect(subject.dir_path_tasks_local.to_s).to eq "#{root}/tasks" + end + + it 'dir_path_modules_node' do + expect(subject.dir_path_modules_node).to be_a Pathname + expect(subject.dir_path_modules_node.to_s).to eq '/etc/puppet/modules' + end + + it 'dir_path_tasks_node' do + expect(subject.dir_path_tasks_node).to be_a Pathname + expect(subject.dir_path_tasks_node.to_s).to eq '/etc/puppet/modules/osnailyfacter/modular' + end + + it 'dir_path_deployment' do + expect(subject.dir_path_deployment).to be_a Pathname + expect(subject.dir_path_deployment.to_s).to eq "#{root}/deployment" + end + + it 'dir_path_workspace' do + expect(subject.dir_path_workspace).to be_a Pathname + expect(subject.dir_path_workspace.to_s).to eq "#{root}/workspace" + end + + it 'dir_path_reports' do + expect(subject.dir_path_reports).to be_a Pathname + expect(subject.dir_path_reports.to_s).to eq "#{root}/reports" + end + end + + context 'hiera' do + it 'dir_name_hiera' do + expect(subject.dir_name_hiera).to be_a Pathname + expect(subject.dir_name_hiera.to_s).to eq 'hiera' + end + + it 'dir_path_hiera' do + expect(subject.dir_path_hiera).to be_a Pathname + expect(subject.dir_path_hiera.to_s).to eq "#{root}/hiera" + end + + it 'dir_name_hiera_override' do + expect(subject.dir_name_hiera_override).to be_a Pathname + expect(subject.dir_name_hiera_override.to_s).to eq 'override' + end + + it 'dir_path_hiera_override' do + expect(subject.dir_path_hiera_override).to be_a Pathname + expect(subject.dir_path_hiera_override.to_s).to eq "#{root}/hiera/override" + end + end + + context 'facts' do + it 'dir_name_facts' do + expect(subject.dir_name_facts).to be_a Pathname + expect(subject.dir_name_facts.to_s).to eq 'facts' + end + + it 'dir_path_facts' do + expect(subject.dir_path_facts).to be_a Pathname + expect(subject.dir_path_facts.to_s).to eq "#{root}/facts" + end + + it 'dir_name_facts_override' do + expect(subject.dir_name_facts_override).to be_a Pathname + expect(subject.dir_name_facts_override.to_s).to eq 'override' + end + + it 'dir_path_facts_override' do + expect(subject.dir_path_facts_override).to be_a Pathname + expect(subject.dir_path_facts_override.to_s).to eq "#{root}/facts/override" + end + end + + context 'globals' do + it 'spec_name_globals' do + expect(subject.spec_name_globals).to be_a Pathname + expect(subject.spec_name_globals.to_s).to eq 'globals/globals_spec.rb' + end + + it 'spec_path_globals' do + expect(subject.spec_path_globals).to be_a Pathname + expect(subject.spec_path_globals.to_s).to eq "#{root}/spec/hosts/globals/globals_spec.rb" + end + + it 'manifest_name_globals' do + expect(subject.manifest_name_globals).to be_a Pathname + expect(subject.manifest_name_globals.to_s).to eq 'globals/globals.pp' + end + + it 'manifest_path_globals' do + expect(subject.manifest_path_globals).to be_a Pathname + expect(subject.manifest_path_globals.to_s).to eq "#{root}/tasks/globals/globals.pp" + end + + it 'dir_name_globals' do + expect(subject.dir_name_globals).to be_a Pathname + expect(subject.dir_name_globals.to_s).to eq 'globals' + end + + it 'dir_path_globals' do + expect(subject.dir_path_globals).to be_a Pathname + expect(subject.dir_path_globals.to_s).to eq "#{root}/hiera/globals" + end + end + +end diff --git a/spec/lib/task_spec.rb b/spec/lib/task_spec.rb new file mode 100644 index 0000000..751c03f --- /dev/null +++ b/spec/lib/task_spec.rb @@ -0,0 +1,202 @@ +require 'spec_helper' +require 'noop/task' + +describe Noop::Task do + before(:each) do + allow(Noop::Utils).to receive(:warning) + end + + subject do + Noop::Task.new 'my/test_spec.rb' + end + + let (:root) do + File.absolute_path File.join File.dirname(__FILE__), '..', '..' + end + + context 'base' do + it 'should have status' do + is_expected.to respond_to :status + end + + it 'should have success?' do + subject.status = :pending + is_expected.not_to be_success + subject.status = :success + is_expected.to be_success + end + + it 'should have pending?' do + subject.status = :failed + is_expected.not_to be_pending + subject.status = :pending + is_expected.to be_pending + end + + it 'should have failed?' do + subject.status = :success + is_expected.not_to be_failed + subject.status = :failed + is_expected.to be_failed + end + + it 'should have parallel_run?' do + is_expected.not_to be_parallel_run + subject.parallel = true + is_expected.to be_parallel_run + end + + it 'should have valid?' do + is_expected.not_to be_valid + end + + it 'should have to_s' do + expect(subject.to_s).to eq 'Task[my/test]' + end + + it 'should have inspect' do + expect(subject.inspect).to eq 'Task[Task: my/test.pp Spec: my/test_spec.rb Hiera: novanet-primary-controller.yaml Facts: ubuntu.yaml Status: pending]' + end + end + + context 'spec' do + it 'has file_name_spec' do + expect(subject.file_name_spec).to be_a Pathname + expect(subject.file_name_spec.to_s).to eq 'my/test_spec.rb' + end + + it 'can set file_name_spec' do + subject.file_name_spec = 'my/test2_spec.rb' + expect(subject.file_name_spec).to be_a Pathname + expect(subject.file_name_spec.to_s).to eq 'my/test2_spec.rb' + end + + it 'will get spec name from the manifest name' do + subject.file_name_spec = 'my/test3.pp' + expect(subject.file_name_spec).to be_a Pathname + expect(subject.file_name_spec.to_s).to eq 'my/test3_spec.rb' + end + + it 'has file_name_manifest' do + expect(subject.file_name_manifest).to be_a Pathname + expect(subject.file_name_manifest.to_s).to eq 'my/test.pp' + end + + it 'has file_path_manifest' do + expect(subject.file_path_manifest).to be_a Pathname + expect(subject.file_path_manifest.to_s).to eq "#{root}/tasks/my/test.pp" + end + + it 'has file_path_spec' do + expect(subject.file_path_spec).to be_a Pathname + expect(subject.file_path_spec.to_s).to eq "#{root}/spec/hosts/my/test_spec.rb" + end + end + + context 'facts' do + it 'has file_name_facts' do + expect(subject.file_name_facts).to be_a Pathname + expect(subject.file_name_facts.to_s).to eq 'ubuntu.yaml' + end + + it 'can set file_name_facts' do + subject.file_name_facts = 'master.yaml' + expect(subject.file_name_facts).to be_a Pathname + expect(subject.file_name_facts.to_s).to eq 'master.yaml' + end + + it 'will add yaml extension to the facts name' do + subject.file_name_facts = 'centos' + expect(subject.file_name_facts).to be_a Pathname + expect(subject.file_name_facts.to_s).to eq 'centos.yaml' + end + + it 'has file_path_facts' do + expect(subject.file_path_facts).to be_a Pathname + expect(subject.file_path_facts.to_s).to eq "#{root}/facts/ubuntu.yaml" + end + + it 'has file_name_facts_override' do + expect(subject.file_name_facts_override).to be_a Pathname + expect(subject.file_name_facts_override.to_s).to eq 'my-test.yaml' + end + + it 'has file_path_facts_override' do + expect(subject.file_path_facts_override).to be_a Pathname + expect(subject.file_path_facts_override.to_s).to eq "#{root}/facts/override/my-test.yaml" + end + end + + context 'hiera' do + it 'has file_name_hiera' do + expect(subject.file_name_hiera).to be_a Pathname + expect(subject.file_name_hiera.to_s).to eq 'novanet-primary-controller.yaml' + end + + it 'has file_base_hiera' do + expect(subject.file_base_hiera).to be_a Pathname + expect(subject.file_base_hiera.to_s).to eq 'novanet-primary-controller' + end + + it 'has element_hiera' do + expect(subject.element_hiera).to be_a Pathname + expect(subject.element_hiera.to_s).to eq 'novanet-primary-controller' + end + + it 'can set file_name_hiera' do + subject.file_name_hiera = 'compute.yaml' + expect(subject.file_name_hiera).to be_a Pathname + expect(subject.file_name_hiera.to_s).to eq 'compute.yaml' + end + + it 'will add yaml extension to the hiera name' do + subject.file_name_hiera = 'controller' + expect(subject.file_name_hiera).to be_a Pathname + expect(subject.file_name_hiera.to_s).to eq 'controller.yaml' + end + + it 'has file_path_hiera' do + expect(subject.file_path_hiera).to be_a Pathname + expect(subject.file_path_hiera.to_s).to eq "#{root}/hiera/novanet-primary-controller.yaml" + end + + it 'has file_name_hiera_override' do + expect(subject.file_name_hiera_override).to be_a Pathname + expect(subject.file_name_hiera_override.to_s).to eq 'my-test.yaml' + end + + it 'has file_path_hiera_override' do + expect(subject.file_path_hiera_override).to be_a Pathname + expect(subject.file_path_hiera_override.to_s).to eq "#{root}/hiera/override/my-test.yaml" + end + + it 'has element_hiera_override' do + expect(subject.element_hiera_override).to be_a Pathname + expect(subject.element_hiera_override.to_s).to eq 'override/my-test' + end + + end + + context 'globals' do + it 'has file_path_globals' do + expect(subject.file_path_globals).to be_a Pathname + expect(subject.file_path_globals.to_s).to eq "#{root}/hiera/globals/novanet-primary-controller.yaml" + end + + it 'has file_name_globals' do + expect(subject.file_name_globals).to be_a Pathname + expect(subject.file_name_globals.to_s).to eq 'novanet-primary-controller.yaml' + end + + it 'has file_base_globals' do + expect(subject.file_base_globals).to be_a Pathname + expect(subject.file_base_globals.to_s).to eq 'novanet-primary-controller' + end + + it 'has element_globals' do + expect(subject.element_globals).to be_a Pathname + expect(subject.element_globals.to_s).to eq 'globals/novanet-primary-controller' + end + end + +end diff --git a/spec/shared-examples.rb b/spec/shared-examples.rb new file mode 100644 index 0000000..697d1e0 --- /dev/null +++ b/spec/shared-examples.rb @@ -0,0 +1,84 @@ +shared_examples 'compile' do + it { is_expected.to compile } +end + +shared_examples 'show_catalog' do + it 'shows catalog contents' do + puts '=' * 80 + puts Noop.task.catalog_dump self + puts '=' * 80 + end +end + +shared_examples 'status' do + it 'shows status' do + puts '=' * 80 + puts Noop.task.status_report self + puts '=' * 80 + end +end + +shared_examples 'console' do + it 'runs pry console' do + require 'pry' + binding.pry + end +end + +############################################################################### + +def run_test(manifest_file, *args) + Noop.task_spec = manifest_file unless Noop.task_spec + + Noop::Config.log.progname = 'noop_spec' + Noop::Utils.debug "RSPEC: #{Noop.task.inspect}" + + include FuelRelationshipGraphMatchers + + let(:task) do + Noop.task + end + + before(:all) do + Noop.setup_overrides + end + + let(:facts) do + Noop.facts_data + end + + let (:catalog) do + catalog = subject + catalog = catalog.call if catalog.is_a? Proc + end + + let (:ral) do + ral = catalog.to_ral + ral.finalize + ral + end + + let (:graph) do + graph = Puppet::Graph::RelationshipGraph.new(Puppet::Graph::TitleHashPrioritizer.new) + graph.populate_from(ral) + graph + end + + include_examples 'compile' + include_examples 'status' if ENV['SPEC_SHOW_STATUS'] + include_examples 'show_catalog' if ENV['SPEC_CATALOG_SHOW'] + include_examples 'console' if ENV['SPEC_RSPEC_CONSOLE'] + + begin + include_examples 'catalog' + rescue ArgumentError + true + end + + yield self if block_given? + +end + +alias :test_ubuntu_and_centos :run_test +alias :test_ubuntu :run_test +alias :test_centos :run_test diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..74dd788 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,30 @@ +require 'rubygems' +require 'puppet' +require 'hiera_puppet' +require 'rspec-puppet' +require 'rspec-puppet-utils' +require 'puppetlabs_spec_helper/module_spec_helper' + +require_relative '../lib/noop' + +# Add fixture lib dirs to LOAD_PATH. Work-around for PUP-3336 +if Puppet.version < '4.0.0' + Dir["#{Noop::Config.dir_path_modules_local}/*/lib"].entries.each do |lib_dir| + $LOAD_PATH << lib_dir + end +end + +RSpec.configure do |c| + c.mock_with :rspec + c.expose_current_running_example_as :example + + c.before :each do + # avoid "Only root can execute commands as other users" + Puppet.features.stubs(:root? => true) + # clear cached facts + Facter::Util::Loader.any_instance.stubs(:load_all) + Facter.clear + Facter.clear_messages + end + +end diff --git a/workspace/.gitkeep b/workspace/.gitkeep new file mode 100644 index 0000000..e69de29