https://github.com/wikimedia/operations-puppet
Raw File
Tip revision: 6b1ad4bfca6c5decb573c1da2df4a2c2f270ada1 authored by Giuseppe Lavagetto on 04 September 2017, 08:58:02 UTC
jobrunner: add nginx service
Tip revision: 6b1ad4b
Rakefile
# This rakefile is meant to run linters and tests
# tailored to a specific changeset.
# You will need 'bundler' to install dependencies:
#
#  $ apt-get install bundler
#  $ bundle install
#
# Then run all the tests, in parallel, that are pertinent to the current changeset
#
#   $ bundle exec rake test
#
# If you just want to check which tests would be ran, run
#
#   $ bundle exec rake debug
#
# Based on the contents of the change, this rakefile will define and run
# all or just some of the following tests:
#
# * puppet_lint - runs puppet lint on the changed puppet files
# * typos - checks the changed files against a predefined list of typos defined
#            in ./typos
# * syntax - run syntax checks for puppet files, hiera files, and templates
#            changed in the current changeset
# * rubocop - run rubocop style checks on ruby files changed in this changeset
# * spec - run the spec tests on the modules where files are changed, or whose
#           tests depend on modules that have been modified.
# * tox - run the tox tests if needed.
#
require 'git'
require 'set'
require 'rake'
require 'rake/tasklib'
require 'shellwords'

require 'puppet-lint/tasks/puppet-lint'
require 'puppet-syntax'
require 'puppet-syntax/tasks/puppet-syntax'
require 'rubocop/rake_task'
# Needed by docs
require 'puppet-strings/tasks/generate'

# Monkey-patch PuppetSyntax and its rake task
module PuppetSyntax
  @manifests_paths = ["**/*.pp"]
  @templates_paths = ["**/*.erb"]
  class << self
    attr_accessor :manifests_paths, :templates_paths
  end
end

class PuppetSyntax::RakeTask
  def filelist_manifests
    filelist(PuppetSyntax.manifests_paths)
  end

  def filelist_templates
    filelist(PuppetSyntax.templates_paths)
  end
end

class SpecDependencies
  # Finds all specs to run based on the changed modules.

  def initialize
    @deps = {}
    FileList["modules/*/.fixtures.yml"].each do |file|
      module_name = module_from_filename(file)
      symlinks = YAML.safe_load(File.open(file))['fixtures']['symlinks'].keys.select{ |x| x != module_name }
      symlinks.each do |dependency|
        @deps[dependency] ||= []
        @deps[dependency] << module_name
      end
    end
  end

  def specs_to_run(filelist)
    specs = Set.new
    modules = modules_modified(filelist)
    return [] if !modules
    modules.each do |mod|
      next unless Dir.exists?("modules/#{mod}/spec")
      specs.add(mod)
      if @deps.include?mod
        @deps[mod].each{ |m| specs.add(m) }
      end
    end
    specs.to_a
  end

  private

  def modules_modified(filelist)
    modules = Set.new
    filelist.each do |file|
      module_name = module_from_filename(file)

      modules.add(module_name) if module_name
    end
    modules.to_a
  end

  def module_from_filename(name)
    if %r{modules/([^/]+)} =~ name
      Regexp.last_match(1)
    else
      nil
    end
  end
end

class TaskGen < ::Rake::TaskLib
  attr_accessor :tasks, :failed_specs

  def initialize(path)
    @tasks_categories = [
      :puppet_lint,
      :typos,
      :syntax,
      :rubocop,
      :spec,
      :tox
    ]
    @changed_files = git_changed_in_head(path)
    @tasks = setup_tasks
    @failed_specs = []
  end

  private

  def git_changed_in_head(path)
    g = Git.open(path)
    diff = g.diff('HEAD^')
    diff.name_status.select { |_, status| 'ACM'.include? status}.keys
  end

  def setup_tasks
    tasks = []
    @tasks_categories.each do |cat|
      method_name = "setup_#{cat}"
      tasks.concat send(method_name)
    end
    tasks
  end

  def puppet_changed_files
    @changed_files.select{ |x| File.fnmatch("*.pp", x) }
  end

  def filter_files_by(*globs)
    changed = FileList[@changed_files]
    changed.exclude(*PuppetSyntax.exclude_paths).select do |file|
      # If at least one glob pattern matches, the file is included.
      !globs.select{ |glob| File.fnmatch(glob, file)}.empty?
    end
  end

  def setup_puppet_lint
    changed = puppet_changed_files
    return [] if changed.empty?
    # Reset puppet-lint tasks, define a new one
    Rake::Task[:lint].clear
    PuppetLint::RakeTask.new :puppet_lint do |config|
      config.fail_on_warnings = true  # be strict
      config.log_format = '%{path}:%{line} %{KIND} %{message} (%{check})'
      config.pattern = changed
    end
    [:puppet_lint]
  end

  def setup_typos
    return [] if @changed_files.empty?
    # Exclude the typos file itself
    shell_files = Shellwords.join(@changed_files - ['typos'])
    # If only typos was modified, bail out immediately
    return [] if shell_files.empty?
    desc "Check common typos from /typos"
    task :typos do
      system("git grep -q -I -P -f typos -- #{shell_files}")
      case $CHILD_STATUS.exitstatus
      when 0
        fail "Typo found!"
      when 1
        puts "No typo found."
      else
        fail "Some error occured"
      end
    end
    [:typos]
  end

  def setup_syntax
    # Reset puppet-syntax tasks, define a new one
    Rake::Task[:syntax].clear

    # site.pp still uses an import statement for realm.pp (T154915)
    # We can think of activating this once we've moved to the future parser
    PuppetSyntax.fail_on_deprecation_notices = false
    if Puppet.version.to_f < 4.0
      PuppetSyntax.exclude_paths = [
        'modules/stdlib/types/*.pp',
        'modules/stdlib/types/compat/*.pp',
        'modules/stdlib/spec/fixtures/test/manifests/*.pp',
      ]
    end
    # Set up filelists
    PuppetSyntax.manifests_paths = puppet_changed_files
    PuppetSyntax.templates_paths = filter_files_by("**/templates/**/*.erb", "**/templates/**/*.epp")
    PuppetSyntax.hieradata_paths = filter_files_by("hieradata/**/*.yaml", "conftool-data/**/*.yaml")
    tasks = []
    unless PuppetSyntax.manifests_paths.empty?
      tasks << 'syntax:manifests'
    end
    unless PuppetSyntax.templates_paths.empty?
      tasks << 'syntax:templates'
    end
    unless PuppetSyntax.hieradata_paths.empty?
      tasks << 'syntax:hiera'
    end
    return tasks if tasks.empty?
    # Now re-set up the jobs by instantiating the class
    PuppetSyntax::RakeTask.new
    # The jobs we select here need to be run in sequence for some thread-safety reasons
    task :syntax_all => tasks
    [:syntax_all]
  end

  def setup_rubocop
    # Files that require a full tree compilation.
    # If the gemfile changed, we might have updated rubocop.
    # Err on the side of caution and scan all files in that case.
    # Also, if the rubocop exceptions changed, check the whole tree
    global_files = ['Gemfile', '.rubocop.todo.yml']
    ruby_files = filter_files_by("**/*.rb", "**/Rakefile", 'Rakefile', 'Gemfile', '**/.rubocop.todo.yml')
    return [] if ruby_files.empty?
    rubocop_task = RuboCop::RakeTask.new(:rubocop)
    if @changed_files.select{ |f| global_files.include?f }.empty?
      rubocop_task.patterns = ruby_files
    end

    [:rubocop]
  end

  def setup_spec
    # Modules known not to pass tests
    ignored_modules = ['mysql', 'osm', 'puppetdbquery', 'stdlib', 'wdqs', 'tilerator', 'wmflib']
    deps = SpecDependencies.new
    spec_modules = deps.specs_to_run(@changed_files).select do |m|
      !ignored_modules.include?(m)
    end
    return [] if spec_modules.empty?

    namespace :spec do
      spec_modules.each do |module_name|
        desc "Run spec for module #{module_name}"
        task module_name do
          puts "---> spec:#{module_name}"
          spec_result = system("cd 'modules/#{module_name}' && rake spec")
          if !spec_result
            @failed_specs << module_name
          end
          puts "---> spec:#{module_name}"
        end
      end
    end
    desc "Run spec tests found in modules"
    multitask :spec => spec_modules.map{ |m| "spec:#{m}" } do
      raise "Modules that failed to pass the spec tests: #{@failed_specs.join ', '}" if !@failed_specs.empty?
    end
    [:spec]
  end

  def setup_tox
    tasks = []
    namespace :tox do
      if @changed_files.include?('tox.ini')
        desc 'Refresh the tox environment'
        task :update do
          raise "Running tox failed" unless system('tox -r')
        end
        tasks << 'tox:update'
      else
        if @changed_files.include?('admin/modules/data/data.yaml')
          desc 'Run tox for the admin data file'
          task :admin do
            res = system('tox -e testenv')
            raise "Tox tests for admin/data/data.yaml failed!" if !res
          end
          tasks << 'tox:admin'
        end
        tox_files = filter_files_by("*.py")
        unless tox_files.empty?
          desc 'Run flake8 on python files via tox'
          task :flake8 do
            shell_tox_files = Shellwords.join(tox_files)
            raise "Flake8 failed" unless system("tox -e pep8 #{shell_tox_files}")
          end
          tasks << 'tox:flake8'
        end
        desc 'Check commit message'
        task :commit_message do
          raise 'Invalid commit message' unless system("tox -e commit-message")
        end
        tasks << 'tox:commit_message'
      end
    end

    desc 'Run all the tox-related tasks'
    task :tox => tasks
    [:tox]
  end
end

t = TaskGen.new('.')

desc 'Run all actual tests in parallel for changes in HEAD'
multitask :test => t.tasks
# Show what we would run
task :debug do
  puts "Tasks that would be run: "
  puts t.tasks
end

# Global tasks. Only the ones deemed useful are added here.
namespace :global do
  desc "Build documentation"
  task :doc do
    Rake::Task['strings:generate'].invoke(
      '**/*.pp **/*.rb',  # patterns
      'false', # debug
      'false', # backtrace
      'rdoc',  # markup format
    )
  end

  spec_failed = []
  spec_tasks = []
  namespace :spec do
    FileList['modules/*/spec'].each do |path|
      next unless path.match('modules/(.+)/')
      module_name = Regexp.last_match(1)
      desc "Run spec for module #{module_name}"
      task module_name do
        spec_result = system("cd 'modules/#{module_name}' && rake spec")
        spec_failed << module_name if !spec_result
      end
      spec_tasks << "spec:#{module_name}"
    end
  end
  desc "Run all spec tests found in modules"
  multitask :spec => spec_tasks do
    raise "Modules that failed to pass the spec tests: #{spec_failed.join ', '}" unless spec_failed.empty?
  end
end

desc 'Show the help'
task :help do
  puts "Puppet helper for operations/puppet.git

Welcome #{ENV['USER']} to WMFs wonderful rake helper to play with puppet.

---[Command line options]----------------------------------------------
`rake -T` : list available tasks
`rake -P` : shows tasks dependencies

---[Available rake tasks]----------------------------------------------"

  # Show our tasks list.
  system "rake -T"

  puts "-----------------------------------------------------------------------"
end
back to top