# frozen_string_literal: true require 'fileutils' require 'rake' require 'rspec/core/rake_task' require 'tmpdir' require 'pathname' require 'puppetlabs_spec_helper/version' require 'puppetlabs_spec_helper/tasks/fixtures' require 'puppetlabs_spec_helper/tasks/check_symlinks' require 'English' # optional gems begin require 'metadata-json-lint/rake_task' rescue LoadError # ignore end begin require 'puppet_blacksmith/rake_tasks' rescue LoadError # ignore end begin require 'github_changelog_generator/task' rescue LoadError # ignore end begin require 'puppet-strings/tasks' rescue LoadError # ignore end parallel_tests_loaded = false begin require 'parallel_tests' parallel_tests_loaded = true rescue LoadError # ignore end task default: [:help] pattern = 'spec/{aliases,classes,defines,functions,hosts,integration,plans,tasks,type_aliases,types,unit}/**/*_spec.rb' RSpec::Core::RakeTask.new(:spec_standalone) do |t, args| t.rspec_opts = [] t.rspec_opts << ENV['CI_SPEC_OPTIONS'] unless ENV['CI_SPEC_OPTIONS'].nil? if ENV['CI_NODE_TOTAL'] && ENV['CI_NODE_INDEX'] ci_total = ENV['CI_NODE_TOTAL'].to_i ci_index = ENV['CI_NODE_INDEX'].to_i raise "CI_NODE_INDEX must be between 1-#{ci_total}" unless ci_index >= 1 && ci_index <= ci_total files = Rake::FileList[pattern].to_a per_node = (files.size / ci_total.to_f).ceil t.pattern = if args.extras.nil? || args.extras.empty? files.each_slice(per_node).to_a[ci_index - 1] || files.first else args.extras.join(',') end else t.pattern = if args.extras.nil? || args.extras.empty? pattern else args.extras.join(',') end end end desc 'List spec tests in a JSON document' RSpec::Core::RakeTask.new(:spec_list_json) do |t| t.rspec_opts = ['--dry-run', '--format', 'json'] t.pattern = pattern end desc 'Run spec tests and clean the fixtures directory if successful' task :spec do |_t, args| Rake::Task[:spec_prep].invoke Rake::Task[:spec_standalone].invoke(*args.extras) Rake::Task[:spec_clean].invoke ensure Rake::Task[:spec_clean_symlinks].invoke end desc 'Run spec tests with ruby simplecov code coverage' namespace :spec do task :simplecov do ENV['SIMPLECOV'] = 'yes' Rake::Task['spec'].execute end end desc 'Run spec tests in parallel and clean the fixtures directory if successful' task :parallel_spec do |_t, args| Rake::Task[:spec_prep].invoke Rake::Task[:parallel_spec_standalone].invoke(*args.extras) Rake::Task[:spec_clean].invoke ensure Rake::Task[:spec_clean_symlinks].invoke end desc 'Parallel spec tests' task :parallel_spec_standalone do |_t, args| raise 'Add the parallel_tests gem to Gemfile to enable this task' unless parallel_tests_loaded if Rake::FileList[pattern].to_a.empty? warn 'No files for parallel_spec to run against' else args = ['-t', 'rspec'] args.push('--').concat(ENV['CI_SPEC_OPTIONS'].strip.split).push('--') unless ENV['CI_SPEC_OPTIONS'].nil? || ENV['CI_SPEC_OPTIONS'].strip.empty? args.concat(Rake::FileList[pattern].to_a) ParallelTests::CLI.new.run(args) end end desc 'Build puppet module package' task :build do Rake::Task['build:pdk'].invoke end namespace :build do desc 'Build Puppet module with PDK' task :pdk do require 'pdk/util' require 'pdk/module/build' path = PDK::Module::Build.invoke(force: true, 'target-dir': File.join(Dir.pwd, 'pkg')) puts "Module built: #{path}" rescue LoadError _ = `pdk --version` unless $CHILD_STATUS.success? warn 'Unable to build module. Please install PDK or add the `pdk` gem to your Gemfile.' abort end system('pdk build --force') end end desc 'Clean a built module package' task :clean do FileUtils.rm_rf('pkg/') end require 'puppet-lint/tasks/puppet-lint' # Must clear as it will not override the existing puppet-lint rake task since we require to import for # the PuppetLint::RakeTask Rake::Task[:lint].clear PuppetLint.configuration.fail_on_warnings = true # Utilize PuppetLint global configuration so that these settings can be tweaked by # spec_helper.rb in an individual module PuppetLint.configuration.relative = true PuppetLint.configuration.ignore_paths ||= [] PuppetLint.configuration.ignore_paths << '.vendor/**/*.pp' PuppetLint.configuration.ignore_paths << 'bundle/**/*.pp' PuppetLint.configuration.ignore_paths << 'pkg/**/*.pp' PuppetLint.configuration.ignore_paths << 'spec/**/*.pp' PuppetLint.configuration.ignore_paths << 'tests/**/*.pp' PuppetLint.configuration.ignore_paths << 'types/**/*.pp' PuppetLint.configuration.ignore_paths << 'vendor/**/*.pp' puppet_lint_disable_checks = %w[ 80chars 140chars class_inherits_from_params_class class_parameter_defaults disable_autoloader_layout documentation single_quote_string_with_variables ] puppet_lint_disable_checks.each do |check| PuppetLint.configuration.send(:"disable_#{check}") end PuppetLint::RakeTask.new(:lint) desc 'Run puppet-lint and fix issues automatically' PuppetLint::RakeTask.new(:lint_fix) do |config| config.fix = true end require 'puppet-syntax/tasks/puppet-syntax' PuppetSyntax.exclude_paths ||= [] PuppetSyntax.exclude_paths << 'spec/fixtures/**/*' PuppetSyntax.exclude_paths << 'pkg/**/*' PuppetSyntax.exclude_paths << 'vendor/**/*' PuppetSyntax.exclude_paths << '.vendor/**/*' PuppetSyntax.exclude_paths << 'plans/**/*' desc 'Check syntax of Ruby files and call :syntax and :metadata_lint' task :validate do Dir['lib/**/*.rb'].each do |lib_file| sh "ruby -c #{lib_file}" end Rake::Task[:syntax].invoke if File.exist?('metadata.json') if Rake::Task.task_defined?(:metadata_lint) Rake::Task[:metadata_lint].invoke else warn 'Skipping metadata validation; the metadata-json-lint gem was not found' end end if File.exist?('REFERENCE.md') if Rake::Task.task_defined?('strings:validate:reference') Rake::Task['strings:validate:reference'].invoke else warn 'Skipping reference documentation validation; the puppet-strings gem was not found' end end end desc 'Print development version of module' task :compute_dev_version do version = '' if File.exist?('metadata.json') require 'json' modinfo = JSON.parse(File.read('metadata.json')) version = modinfo['version'] elsif File.exist?('Modulefile') modfile = File.read('Modulefile') version = modfile.match(/\nversion +['"](.*)['"]/)[1] else raise 'Could not find a metadata.json or Modulefile! Cannot compute dev version without one or the other!' end sha = `git rev-parse HEAD`[0..7] branch = `git rev-parse --abbrev-ref HEAD` # If we're in a CI environment include our build number # If the branch is a release branch we append an 'r' into the new_version, # this is due to the release branch buildID conflicting with main branch when trying to push to the staging forge. # More info can be found at https://tickets.puppetlabs.com/browse/FM-6170 new_version = if (build = ENV.fetch('BUILD_NUMBER', nil)) if branch.eql? 'release' format('%s-%s%04d-%s', version, 'r', build, sha) # legacy support code else format('%s-%04d-%s', version, build, sha) # legacy support code end else "#{version}-#{sha}" end print new_version end desc 'Runs all necessary checks on a module in preparation for a release' task :release_checks do Rake::Task[:lint].invoke Rake::Task[:validate].invoke if parallel_tests_loaded Rake::Task[:parallel_spec].invoke else Rake::Task[:spec].invoke end Rake::Task[:check].invoke end namespace :check do desc 'Fails if symlinks are present in directory' task :symlinks do symlinks = PuppetlabsSpecHelper::Tasks::CheckSymlinks.new.check unless symlinks.empty? symlinks.each { |r| puts "Symlink found: #{r} => #{r.readlink}" } raise 'Symlink(s) exist within this directory' end end desc 'Fails if .pp files present in tests folder' task :test_file do ppfiles = Dir[File.join('tests', '**', '*.pp')] unless ppfiles.empty? puts ppfiles raise '.pp files present in tests folder; Move them to an examples folder following the new convention' end end desc 'Fails if any ._ files are present in directory' task :dot_underscore do dirs = Dir['._*'] unless dirs.empty? puts dirs raise '._ files are present in the directory' end end desc 'Fails if directories contain the files specified in .gitignore' task :git_ignore do matched = `git ls-files --ignored --exclude-standard --cached` raise 'git ls-files failed' unless $CHILD_STATUS.success? unless matched == '' puts matched raise 'File specified in .gitignore has been committed' end end end desc 'Run static pre release checks' task check: ['check:symlinks', 'check:test_file', 'check:dot_underscore', 'check:git_ignore'] desc 'Display the list of available rake tasks' task :help do system('rake -T') end begin require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) do |task| # These make the rubocop experience maybe slightly less terrible task.options = ['-D', '-S', '-E'] # Use Rubocop's Github Actions formatter if possible if ENV['GITHUB_ACTIONS'] == 'true' rubocop_spec = Gem::Specification.find_by_name('rubocop') task.formatters << 'github' if Gem::Version.new(rubocop_spec.version) >= Gem::Version.new('1.2') end end rescue LoadError desc 'rubocop is not available in this installation' task :rubocop do raise 'rubocop is not available in this installation' end end def create_gch_task(changelog_user = nil, changelog_project = nil, changelog_since_tag = nil, changelog_tag_pattern = 'v%s') if Bundler.rubygems.find_name('github_changelog_generator').any? # needed a place to hide these methods # rubocop:disable Lint/NestedMethodDefinition def changelog_user_from_metadata result = JSON.parse(File.read('metadata.json'))['author'] raise 'unable to find the changelog_user in .sync.yml, or the author in metadata.json' if result.nil? puts "GitHubChangelogGenerator user:#{result}" result end def changelog_project_from_metadata result = JSON.parse(File.read('metadata.json'))['name'] raise 'unable to find the changelog_project in .sync.yml or the name in metadata.json' if result.nil? puts "GitHubChangelogGenerator project:#{result}" result end def changelog_future_release return unless Rake.application.top_level_tasks.include? 'changelog' result = JSON.parse(File.read('metadata.json'))['version'] raise 'unable to find the future_release (version) in metadata.json' if result.nil? puts "GitHubChangelogGenerator future_release:#{result}" result end # rubocop:enable Lint/NestedMethodDefinition GitHubChangelogGenerator::RakeTask.new :changelog do |config| raise "Set CHANGELOG_GITHUB_TOKEN environment variable eg 'export CHANGELOG_GITHUB_TOKEN=valid_token_here'" if ENV['CHANGELOG_GITHUB_TOKEN'].nil? config.user = changelog_user || changelog_user_from_metadata config.project = changelog_project || changelog_project_from_metadata config.since_tag = changelog_since_tag if changelog_since_tag config.future_release = changelog_tag_pattern % changelog_future_release.to_s config.exclude_labels = ['maintenance'] config.header = "# Change log\n\nAll notable changes to this project will be documented in this file. " \ 'The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres ' \ 'to [Semantic Versioning](https://semver.org).' config.add_pr_wo_labels = true config.issues = false config.merge_prefix = '### UNCATEGORIZED PRS; GO LABEL THEM' config.configure_sections = { 'Changed' => { 'prefix' => '### Changed', 'labels' => ['backwards-incompatible'] }, 'Added' => { 'prefix' => '### Added', 'labels' => %w[feature enhancement] }, 'Fixed' => { 'prefix' => '### Fixed', 'labels' => ['bugfix'] } } end else desc 'Generate a Changelog from GitHub' task :changelog do raise <<~MESSAGE The changelog tasks depends on unreleased features of the github_changelog_generator gem. Please manually add it to your .sync.yml for now, and run `pdk update`: --- Gemfile: optional: ':development': - gem: 'github_changelog_generator' git: 'https://github.com/skywinder/github-changelog-generator' ref: '20ee04ba1234e9e83eb2ffb5056e23d641c7a018' condition: "Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.2.2')" MESSAGE end end end