Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
teak-dev / lib / teak / dev / tasks.rb
Size: Mime:
# frozen_string_literal: true

module Teak
  module Dev
    # Class for setting up our Rake tasks.
    class Tasks # rubocop:disable Metrics/ClassLength
      include Rake::DSL if defined? Rake::DSL

      attr_reader :service

      def self.install(opts)
        new(opts[:service]).install
      end

      def initialize(service)
        @service = service
        @rubocop_todo_before = nil
      end

      def install
        install_lint
        install_report
        install_rubocop
        install_terraform
      end

    private

      def install_lint # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
        lint_tasks = %w[lint:rubocop lint:bundler_audit]
        namespace :lint do
          desc 'Run Rubocop.'
          task :rubocop do
            # N.B. Not using `RuboCop::RakeTask.new` because it's not available during image builds, which
            # was causing issues since we _do_ need rake tasks available.
            sh 'bundle exec rubocop'
          end

          has_reek = true
          begin
            require 'reek/version'
          rescue LoadError
            has_reek = false
          end

          if has_reek
            lint_tasks << 'lint:reek'

            desc 'Run Reek.'
            task :reek do
              sh 'bundle exec reek'
            end
          end

          desc 'Run bundler-audit.'
          task :bundler_audit do
            sh 'bundle exec bundle-audit check --update'
          end
        end

        desc 'Run all lint tasks.'
        task lint: lint_tasks

        namespace :fix do
          desc 'Run Rubocop with the autocorrect option.  This will only apply fixes deemed safe.'
          task :rubocop do
            # N.B. Not using `RuboCop::RakeTask.new` because it's not available during image builds, which
            # was causing issues since we _do_ need rake tasks available.
            sh 'bundle exec rubocop --autocorrect'
          end
        end

        desc 'Run linters in fix mode, where appropriate.'
        task fix: %w[
          fix:rubocop
        ]
      end

      def install_report # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
        has_librato = true
        begin
          require 'librato/metrics'
        rescue LoadError
          has_librato = false
        end
        return unless has_librato

        namespace :report do
          desc 'Report count of unresolved Rubocop offenses (ignoring TODO file) to Librato.'
          task :rubocop do
            with_no_rubocop_todos do
              raw_output = `bundle exec rubocop --format=offenses 2>/dev/null | grep -v =`
              puts raw_output
              total_line = raw_output.split("\n").find { |line| line.include?('Total in') }
              total = total_line.split(/\s+/).first.to_i

              report_for_branch("rubocop.#{service}.offenses", total)
            end
          end

          desc 'Report Ruby code coverage to Librato'
          task :coverage do
            require 'simplecov'

            SimpleCov.collate(Dir['/tmp/coverage/**/.resultset.json'])
            total_covered_percent = SimpleCov.result.coverage_statistics[:line].percent

            puts "Total coverage: #{total_covered_percent}%"

            report_for_branch("coverage.#{service}.rspec", total_covered_percent)
          end
        end
      end

      def with_no_rubocop_todos
        if File.exist?('.rubocop_todo.yml')
          @rubocop_todo_before = File.read('.rubocop_todo.yml')
          File.write('.rubocop_todo.yml', '')
        end

        begin
          yield
        ensure
          restore_rubocop_todos
        end
      end

      def restore_rubocop_todos
        File.write('.rubocop_todo.yml', @rubocop_todo_before) if @rubocop_todo_before
      end

      def report_for_branch(metric_name, value)
        Librato::Metrics.authenticate(ENV.fetch('LIBRATO_USER'), ENV.fetch('LIBRATO_TOKEN'))
        Librato::Metrics.submit(metric_name => {
          type:     :gauge,
          value:    value,
          source:   ENV.fetch('CIRCLE_BRANCH', 'unknown').gsub(/[^\.a-zA-Z0-9_-]/, '--')[0..255],
          sporadic: true,
        })
      end

      def install_rubocop # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength
        namespace :rubocop do # rubocop:disable Metrics/BlockLength
          desc 'Regenerate the .rubocop_todo.yml file.'
          task :regenerate_todo do
            FileUtils.rm_f('.rubocop_todo.yml')
            sh 'bundle exec rubocop --auto-gen-config --no-exclude-limit --auto-gen-only-exclude ' \
               '--no-auto-gen-enforced-style'
          end

          desc 'Find candidates for incremental TODO paydown.'
          task :paydown_candidates do # rubocop:disable Metrics/BlockLength
            todo_contents = File.read('.rubocop_todo.yml')
            File.write('.rubocop_todo.yml', '')
            raw_offense_info =
              `bundle exec rubocop --format=offenses`
                .split("\n")
                .grep(/^\d+/)
                .map { |line| line.split(/\s+/) }
                .map { |pieces| [pieces[0].to_i, pieces[1], pieces[2]&.sub('[', '') || 'N/A'] }
                .reject { |offense| offense[1] == 'Style/FrozenStringLiteralComment' }

            # Prioritize offenses that are safe to correct...
            most_violated_cop = raw_offense_info.find { |offense| offense[2] == 'Safe' }
            # Then prioritize ones we're just going to mark with ignore comments...
            most_violated_cop ||= raw_offense_info.find { |offense| offense[2] == 'N/A' }
            # Then just pick the most violated cop if we have to...
            most_violated_cop ||= raw_offense_info.first

            is_uncorrectable = most_violated_cop[2] == 'N/A'
            is_unsafe        = most_violated_cop[2] == 'Unsafe'
            is_rspec         = most_violated_cop[1] =~ %r{^RSpec/}

            puts "Most violated cop: #{most_violated_cop[1]} (Correctability: #{most_violated_cop[2]}; " \
                 "#{most_violated_cop[0]} offenses total)"
            puts

            raw_offender_info = `bundle exec rubocop --format=worst --only #{most_violated_cop[1]}`
                                  .split("\n")
                                  .grep(/^\d+/)
                                  .grep_v(/Total/)
            offense_count     = 0
            worst_offenders   = []
            unless is_uncorrectable
              # N.B. If it's unsafe, but it's RSpec, we can plow ahead because it'll surface quickly if something
              # breaks.
              #
              # Also: This is madness!!
              offense_threshold = is_unsafe && !is_rspec ? 30 : 300
              file_threshold    = is_unsafe && !is_rspec ? 10 : 50
              raw_offender_info.each do |line|
                pieces = line.split(/\s+/)
                offense_count += pieces[0].to_i
                worst_offenders << pieces[1]
                break if offense_count >= offense_threshold
                break if worst_offenders.length >= file_threshold
              end
              puts "Worst offenders (#{offense_count} offenses total):"
              puts(worst_offenders.map { |offender| "  #{offender}" })
              puts
            end

            common_prefix = "echo > .rubocop_todo.yml; rubocop --only #{most_violated_cop[1]}"
            common_suffix = '; rake rubocop:regenerate_todo'
            puts 'Next steps:'
            if is_uncorrectable
              puts "#{common_prefix} --autocorrect --disable-uncorrectable#{common_suffix}"
            elsif is_unsafe
              puts "#{common_prefix} --autocorrect-all #{worst_offenders.join(' ')}#{common_suffix}"
              puts
              puts 'WARNING: This is an unsafe correction!  You MUST validate ALL changes!  ' \
                   'You MUST include the fact that this is an unsafe correction when filing your PR!'
            else
              puts "#{common_prefix} --autocorrect #{worst_offenders.join(' ')}#{common_suffix}"
            end
          ensure
            File.write('.rubocop_todo.yml', todo_contents)
          end
        end
      end

      # :reek:TooManyStatements
      def install_terraform
        return unless Dir.exist?('./terraform')

        namespace :terraform do
          desc 'Updates all terraform providers and modules'
          task :update do
            sh 'terraform -chdir=terraform init -upgrade'
            sh 'terraform -chdir=terraform providers lock -platform=darwin_arm64 -platform=darwin_amd64 ' \
               '-platform=linux_arm64 -platform=linux_amd64'
          end
        end
      end
    end
  end
end