orange-cloudfoundry/cf-ops-automation

View on GitHub
concourse/tasks/repackage_boshreleases_fallback/repackage_releases_fallback.rb

Summary

Maintainability
A
3 hrs
Test Coverage
A
97%

Class has too many lines. [137/100]
Open

class RepackageReleasesFallback
  BOSH_IO_PREFIX = "https://bosh.io/d/github.com".freeze
  GITHUB_PREFIX = "https://github.com".freeze

  def initialize(repackaged_error_filepath = "")

This cop checks if the length a class exceeds some maximum value. Comment lines can optionally be ignored. The maximum allowed length is configurable.

Method has too many lines. [15/10]
Open

  def fallback_to_github(errors, fallback_fixes, repackaged_releases_fallback_path)
    successfully_processed = []
    remaining_errors = errors.dup
    remaining_errors.each do |name, details|
      puts "Failed to repackage #{name} boshrelease, trying direct download from github release"

This cop checks if the length of a method exceeds some maximum value. Comment lines can optionally be ignored. The maximum allowed length is configurable.

Method has too many lines. [14/10]
Open

  def download_boshrelease(url, boshrelease_filename, download_path, max_retry = 3)
    retries ||= 0
    puts "Start downloading #{boshrelease_filename} from #{url}"
    begin
      download(url, download_path)

This cop checks if the length of a method exceeds some maximum value. Comment lines can optionally be ignored. The maximum allowed length is configurable.

Method has too many lines. [14/10]
Open

  def fallback_to_bosh_io(errors, fallback_fixes, repackaged_releases_fallback_path)
    successfully_processed = []
    @repackaged_errors.each do |name, details|
      puts "Failed to repackage #{name} boshrelease, trying direct download from bosh.io"
      begin

This cop checks if the length of a method exceeds some maximum value. Comment lines can optionally be ignored. The maximum allowed length is configurable.

Method has too many lines. [13/10]
Open

  def generate_boshrelease_namespaces(repackaged_releases_path, successfully_processed)
    File.open(File.join(repackaged_releases_path, 'boshreleases-namespaces.csv'), 'a') do |file|
      successfully_processed.each do |name|
        version = @repackaged_errors&.dig(name, 'version')
        release_details = @repackaged_errors&.dig(name, 'repository')

This cop checks if the length of a method exceeds some maximum value. Comment lines can optionally be ignored. The maximum allowed length is configurable.

Method has too many lines. [12/10]
Open

  def create_github_download_info(name, details)
    repo = details['repository'] || ""
    version = details['version']
    tag_prefix = details['tag_prefix']
    github_release_name = if details['github-release-name'].to_s.empty?

This cop checks if the length of a method exceeds some maximum value. Comment lines can optionally be ignored. The maximum allowed length is configurable.

Assignment Branch Condition size for process is too high. [16.25/15]
Open

  def process(repackaged_releases_fallback_path, repackaged_releases_path)
    errors = @repackaged_errors.dup
    fallback_fixes = {}
    init_fallback_dir_with_repackaged_dir(repackaged_releases_fallback_path, repackaged_releases_path)
    fallback_to_bosh_io(errors, fallback_fixes, repackaged_releases_fallback_path)

This cop checks that the ABC size of methods is not higher than the configured maximum. The ABC size is based on assignments, branches (method calls), and conditions. See http://c2.com/cgi/wiki?AbcMetric

Method download_boshrelease has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.
Open

  def download_boshrelease(url, boshrelease_filename, download_path, max_retry = 3)
    retries ||= 0
    puts "Start downloading #{boshrelease_filename} from #{url}"
    begin
      download(url, download_path)

Cognitive Complexity

Cognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.

A method's cognitive complexity is based on a few simple rules:

  • Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one
  • Code is considered more complex for each "break in the linear flow of the code"
  • Code is considered more complex when "flow breaking structures are nested"

Further reading

RepackageReleasesFallback#create_github_download_info refers to 'details' more than self (maybe move it to another class?)
Open

    repo = details['repository'] || ""
    version = details['version']
    tag_prefix = details['tag_prefix']
    github_release_name = if details['github-release-name'].to_s.empty?
                            "#{name}-#{version}.tgz"

Feature Envy occurs when a code fragment references another object more often than it references itself, or when several clients do the same series of manipulations on a particular type of object.

Feature Envy reduces the code's ability to communicate intent: code that "belongs" on one class but which is located in another can be hard to find, and may upset the "System of Names" in the host class.

Feature Envy also affects the design's flexibility: A code fragment that is in the wrong class creates couplings that may not be natural within the application's domain, and creates a loss of cohesion in the unwilling host class.

Feature Envy often arises because it must manipulate other objects (usually its arguments) to get them into a useful form, and one force preventing them (the arguments) doing this themselves is that the common knowledge lives outside the arguments, or the arguments are of too basic a type to justify extending that type. Therefore there must be something which 'knows' about the contents or purposes of the arguments. That thing would have to be more than just a basic type, because the basic types are either containers which don't know about their contents, or they are single objects which can't capture their relationship with their fellows of the same type. So, this thing with the extra knowledge should be reified into a class, and the utility method will most likely belong there.

Example

Running Reek on:

class Warehouse
  def sale_price(item)
    (item.price - item.rebate) * @vat
  end
end

would report:

Warehouse#total_price refers to item more than self (FeatureEnvy)

since this:

(item.price - item.rebate)

belongs to the Item class, not the Warehouse.

RepackageReleasesFallback#create_bosh_io_download_info refers to 'details' more than self (maybe move it to another class?)
Open

    repo = details['repository'] || ""
    version = details['version']

Feature Envy occurs when a code fragment references another object more often than it references itself, or when several clients do the same series of manipulations on a particular type of object.

Feature Envy reduces the code's ability to communicate intent: code that "belongs" on one class but which is located in another can be hard to find, and may upset the "System of Names" in the host class.

Feature Envy also affects the design's flexibility: A code fragment that is in the wrong class creates couplings that may not be natural within the application's domain, and creates a loss of cohesion in the unwilling host class.

Feature Envy often arises because it must manipulate other objects (usually its arguments) to get them into a useful form, and one force preventing them (the arguments) doing this themselves is that the common knowledge lives outside the arguments, or the arguments are of too basic a type to justify extending that type. Therefore there must be something which 'knows' about the contents or purposes of the arguments. That thing would have to be more than just a basic type, because the basic types are either containers which don't know about their contents, or they are single objects which can't capture their relationship with their fellows of the same type. So, this thing with the extra knowledge should be reified into a class, and the utility method will most likely belong there.

Example

Running Reek on:

class Warehouse
  def sale_price(item)
    (item.price - item.rebate) * @vat
  end
end

would report:

Warehouse#total_price refers to item more than self (FeatureEnvy)

since this:

(item.price - item.rebate)

belongs to the Item class, not the Warehouse.

RepackageReleasesFallback#generate_boshrelease_namespaces has approx 9 statements
Open

  def generate_boshrelease_namespaces(repackaged_releases_path, successfully_processed)

A method with Too Many Statements is any method that has a large number of lines.

Too Many Statements warns about any method that has more than 5 statements. Reek's smell detector for Too Many Statements counts +1 for every simple statement in a method and +1 for every statement within a control structure (if, else, case, when, for, while, until, begin, rescue) but it doesn't count the control structure itself.

So the following method would score +6 in Reek's statement-counting algorithm:

def parse(arg, argv, &error)
  if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0]))
    return nil, block, nil                                         # +1
  end
  opt = (val = parse_arg(val, &error))[1]                          # +2
  val = conv_arg(*val)                                             # +3
  if opt and !arg
    argv.shift                                                     # +4
  else
    val[0] = nil                                                   # +5
  end
  val                                                              # +6
end

(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)

RepackageReleasesFallback#fallback_to_github has approx 12 statements
Open

  def fallback_to_github(errors, fallback_fixes, repackaged_releases_fallback_path)

A method with Too Many Statements is any method that has a large number of lines.

Too Many Statements warns about any method that has more than 5 statements. Reek's smell detector for Too Many Statements counts +1 for every simple statement in a method and +1 for every statement within a control structure (if, else, case, when, for, while, until, begin, rescue) but it doesn't count the control structure itself.

So the following method would score +6 in Reek's statement-counting algorithm:

def parse(arg, argv, &error)
  if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0]))
    return nil, block, nil                                         # +1
  end
  opt = (val = parse_arg(val, &error))[1]                          # +2
  val = conv_arg(*val)                                             # +3
  if opt and !arg
    argv.shift                                                     # +4
  else
    val[0] = nil                                                   # +5
  end
  val                                                              # +6
end

(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)

RepackageReleasesFallback#create_github_download_info has approx 9 statements
Open

  def create_github_download_info(name, details)

A method with Too Many Statements is any method that has a large number of lines.

Too Many Statements warns about any method that has more than 5 statements. Reek's smell detector for Too Many Statements counts +1 for every simple statement in a method and +1 for every statement within a control structure (if, else, case, when, for, while, until, begin, rescue) but it doesn't count the control structure itself.

So the following method would score +6 in Reek's statement-counting algorithm:

def parse(arg, argv, &error)
  if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0]))
    return nil, block, nil                                         # +1
  end
  opt = (val = parse_arg(val, &error))[1]                          # +2
  val = conv_arg(*val)                                             # +3
  if opt and !arg
    argv.shift                                                     # +4
  else
    val[0] = nil                                                   # +5
  end
  val                                                              # +6
end

(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)

RepackageReleasesFallback#fallback_to_bosh_io has approx 11 statements
Open

  def fallback_to_bosh_io(errors, fallback_fixes, repackaged_releases_fallback_path)

A method with Too Many Statements is any method that has a large number of lines.

Too Many Statements warns about any method that has more than 5 statements. Reek's smell detector for Too Many Statements counts +1 for every simple statement in a method and +1 for every statement within a control structure (if, else, case, when, for, while, until, begin, rescue) but it doesn't count the control structure itself.

So the following method would score +6 in Reek's statement-counting algorithm:

def parse(arg, argv, &error)
  if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0]))
    return nil, block, nil                                         # +1
  end
  opt = (val = parse_arg(val, &error))[1]                          # +2
  val = conv_arg(*val)                                             # +3
  if opt and !arg
    argv.shift                                                     # +4
  else
    val[0] = nil                                                   # +5
  end
  val                                                              # +6
end

(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)

RepackageReleasesFallback#download_boshrelease has approx 12 statements
Open

  def download_boshrelease(url, boshrelease_filename, download_path, max_retry = 3)

A method with Too Many Statements is any method that has a large number of lines.

Too Many Statements warns about any method that has more than 5 statements. Reek's smell detector for Too Many Statements counts +1 for every simple statement in a method and +1 for every statement within a control structure (if, else, case, when, for, while, until, begin, rescue) but it doesn't count the control structure itself.

So the following method would score +6 in Reek's statement-counting algorithm:

def parse(arg, argv, &error)
  if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0]))
    return nil, block, nil                                         # +1
  end
  opt = (val = parse_arg(val, &error))[1]                          # +2
  val = conv_arg(*val)                                             # +3
  if opt and !arg
    argv.shift                                                     # +4
  else
    val[0] = nil                                                   # +5
  end
  val                                                              # +6
end

(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)

RepackageReleasesFallback#process has approx 10 statements
Open

  def process(repackaged_releases_fallback_path, repackaged_releases_path)

A method with Too Many Statements is any method that has a large number of lines.

Too Many Statements warns about any method that has more than 5 statements. Reek's smell detector for Too Many Statements counts +1 for every simple statement in a method and +1 for every statement within a control structure (if, else, case, when, for, while, until, begin, rescue) but it doesn't count the control structure itself.

So the following method would score +6 in Reek's statement-counting algorithm:

def parse(arg, argv, &error)
  if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0]))
    return nil, block, nil                                         # +1
  end
  opt = (val = parse_arg(val, &error))[1]                          # +2
  val = conv_arg(*val)                                             # +3
  if opt and !arg
    argv.shift                                                     # +4
  else
    val[0] = nil                                                   # +5
  end
  val                                                              # +6
end

(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)

RepackageFallbackError has no descriptive comment
Open

class RepackageFallbackError < RuntimeError; end

Classes and modules are the units of reuse and release. It is therefore considered good practice to annotate every class and module with a brief comment outlining its responsibilities.

Example

Given

class Dummy
  # Do things...
end

Reek would emit the following warning:

test.rb -- 1 warning:
  [1]:Dummy has no descriptive comment (IrresponsibleModule)

Fixing this is simple - just an explaining comment:

# The Dummy class is responsible for ...
class Dummy
  # Do things...
end

Method generate_boshrelease_namespaces has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.
Open

  def generate_boshrelease_namespaces(repackaged_releases_path, successfully_processed)
    File.open(File.join(repackaged_releases_path, 'boshreleases-namespaces.csv'), 'a') do |file|
      successfully_processed.each do |name|
        version = @repackaged_errors&.dig(name, 'version')
        release_details = @repackaged_errors&.dig(name, 'repository')

Cognitive Complexity

Cognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.

A method's cognitive complexity is based on a few simple rules:

  • Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one
  • Code is considered more complex for each "break in the linear flow of the code"
  • Code is considered more complex when "flow breaking structures are nested"

Further reading

RepackageReleasesFallback#download doesn't depend on instance state (maybe move it to another class?)
Open

  def download(url, download_path)

A Utility Function is any instance method that has no dependency on the state of the instance.

RepackageReleasesFallback#init_fallback_dir_with_repackaged_dir doesn't depend on instance state (maybe move it to another class?)
Open

  def init_fallback_dir_with_repackaged_dir(target, origin)

A Utility Function is any instance method that has no dependency on the state of the instance.

RepackageReleasesFallback#update_errors_and_warnings doesn't depend on instance state (maybe move it to another class?)
Open

  def update_errors_and_warnings(errors, fallback_fixes, name, successfully_processed)

A Utility Function is any instance method that has no dependency on the state of the instance.

RepackageReleasesFallback#fallback_to_bosh_io has the variable name 'e'
Open

      rescue RepackageFallbackError => e

An Uncommunicative Variable Name is a variable name that doesn't communicate its intent well enough.

Poor names make it hard for the reader to build a mental picture of what's going on in the code. They can also be mis-interpreted; and they hurt the flow of reading, because the reader must slow down to interpret the names.

RepackageReleasesFallback#fallback_to_github has the variable name 'e'
Open

      rescue RepackageFallbackError => e

An Uncommunicative Variable Name is a variable name that doesn't communicate its intent well enough.

Poor names make it hard for the reader to build a mental picture of what's going on in the code. They can also be mis-interpreted; and they hurt the flow of reading, because the reader must slow down to interpret the names.

RepackageReleasesFallback#download_boshrelease has the variable name 'e'
Open

    rescue Net::ReadTimeout => e
      puts "Retrying (#{retries += 1}/#{max_retry}: #{e.class}) downloading #{boshrelease_filename} from #{url}"
      retry if retries < max_retry
      File.delete(download_path) if File.exist?(download_path)
      raise RepackageFallbackError, e

An Uncommunicative Variable Name is a variable name that doesn't communicate its intent well enough.

Poor names make it hard for the reader to build a mental picture of what's going on in the code. They can also be mis-interpreted; and they hurt the flow of reading, because the reader must slow down to interpret the names.

Similar blocks of code found in 2 locations. Consider refactoring.
Open

      puts "Failed to repackage #{name} boshrelease, trying direct download from bosh.io"
      begin
        download_from_bosh_io(name, details, repackaged_releases_fallback_path)
        update_errors_and_warnings(errors, fallback_fixes, name, successfully_processed)
      rescue RepackageFallbackError => e
concourse/tasks/repackage_boshreleases_fallback/repackage_releases_fallback.rb on lines 74..83

Duplicated Code

Duplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

When you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).

Tuning

This issue has a mass of 42.

We set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.

The threshold configuration represents the minimum mass a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.

If the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.

See codeclimate-duplication's documentation for more information about tuning the mass threshold in your .codeclimate.yml.

Refactorings

Further Reading

Similar blocks of code found in 2 locations. Consider refactoring.
Open

      puts "Failed to repackage #{name} boshrelease, trying direct download from github release"
      begin
        download_from_github(name, details, repackaged_releases_fallback_path)
        update_errors_and_warnings(errors, fallback_fixes, name, successfully_processed)
      rescue RepackageFallbackError => e
concourse/tasks/repackage_boshreleases_fallback/repackage_releases_fallback.rb on lines 56..65

Duplicated Code

Duplicated code can lead to software that is hard to understand and difficult to change. The Don't Repeat Yourself (DRY) principle states:

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

When you violate DRY, bugs and maintenance problems are sure to follow. Duplicated code has a tendency to both continue to replicate and also to diverge (leaving bugs as two similar implementations differ in subtle ways).

Tuning

This issue has a mass of 42.

We set useful threshold defaults for the languages we support but you may want to adjust these settings based on your project guidelines.

The threshold configuration represents the minimum mass a code block must have to be analyzed for duplication. The lower the threshold, the more fine-grained the comparison.

If the engine is too easily reporting duplication, try raising the threshold. If you suspect that the engine isn't catching enough duplication, try lowering the threshold. The best setting tends to differ from language to language.

See codeclimate-duplication's documentation for more information about tuning the mass threshold in your .codeclimate.yml.

Refactorings

Further Reading

Rename has_errors? to errors?.
Open

  def has_errors?

This cop makes sure that predicates are named properly.

Example:

# bad
def is_even?(value)
end

# good
def even?(value)
end

# bad
def has_value?
end

# good
def value?
end

Place the end statement of a multi-line method on its own line.
Open

class RepackageFallbackError < RuntimeError; end

This cop checks for trailing code after the method definition.

Example:

# bad
def some_method
do_stuff; end

def do_this(x)
  baz.map { |b| b.this(x) } end

def foo
  block do
    bar
  end end

# good
def some_method
  do_stuff
end

def do_this(x)
  baz.map { |b| b.this(x) }
end

def foo
  block do
    bar
  end
end

There are no issues that match your filters.

Category
Status