class Artifactory::Cleaner::CLI

Command Line Interface class, powers the artifactory-cleaner terminal command


A single Artifactory::Cleaner::CLI instance is created by the bin/artifactory_cleaner command for parsing options and executing the command specified by the user. The Artifactory::Cleaner::CLI uses Thor to provide git command/subcommand style ARGV parsing

@see exe/artifactory-cleaner

@see github.com/erikhuda/thor

Constants

RepoTableCol

Public Class Methods

default_repo_table_cols() click to toggle source
# File lib/artifactory/cleaner/cli.rb, line 48
def self.default_repo_table_cols
  [:key, :package_type, :rclass, :url, :description]
end
new(*args) click to toggle source

Constructor for a new CLI interface

Calls superclass method
# File lib/artifactory/cleaner/cli.rb, line 54
def initialize(*args)
  super
  @config = {}
  begin
    load_conf_file options[:conf_file] if options[:conf_file]
  rescue => ex
    STDERR.puts "Unable to load config from #{options[:conf_file]}: #{ex}"
    exit Sysexits::EX_DATAERR
  end
  @artifactory_config = {
     endpoint: options[:endpoint] || @config['endpoint'],
     api_key: options[:api_key] || @config['api-key'],
  }
  @repo_table_cols = Artifactory::Cleaner::CLI.repo_table_cols
  #invoke :create_controller
  create_controller
end
repo_table_cols() click to toggle source
# File lib/artifactory/cleaner/cli.rb, line 26
def self.repo_table_cols
  [
    RepoTableCol.new(:key, 'ID', nil),
    RepoTableCol.new(:package_type, 'Type', nil),
    RepoTableCol.new(:rclass, 'Class', :local),
    RepoTableCol.new(:url, 'URL', :remote),
    RepoTableCol.new(:description, 'Description', nil),
    RepoTableCol.new(:notes, 'Notes', nil),
    RepoTableCol.new(:blacked_out?, 'Blacked Out', nil),
    RepoTableCol.new(:yum_root_depth, 'YUM Root Depth', nil),
    RepoTableCol.new(:checksum_policy_type, 'Checksum Policy', nil),
    RepoTableCol.new(:includes_pattern, 'Includes Pattern', nil),
    RepoTableCol.new(:excludes_pattern, 'Excludes Pattern', nil),
    RepoTableCol.new(:handle_releases, 'Releases', nil),
    RepoTableCol.new(:handle_snapshots, 'Snapshots', nil),
    RepoTableCol.new(:property_sets, 'Property Sets', nil),
    RepoTableCol.new(:repo_layout_ref, 'Layout', nil),
    RepoTableCol.new(:repositories, 'Included Repos', nil),
    RepoTableCol.new(:inspect, 'Inspection'),
  ]
end

Public Instance Methods

archive() click to toggle source

Download artifacts meeting specific criteria

WARNING: This method will cause the `last_downloaded` property of all the matching artifacts to be updated; therefore, using this method with a `last_used_before` switch may not be idempotent as it will cause the set of artifacts matching the search to change

Consider using `clean –archive` instead

# File lib/artifactory/cleaner/cli.rb, line 175
def archive
  dates = parse_date_options
  filter = load_artifact_filter
  archive_to = parse_archive_option
  if archive_to.nil?
    STDERR.puts "Missing required `--archive-to` option specifying a a valid, existing directory under which to store archived artifacts"
    exit Sysexits::EX_USAGE
  end

  report = {
      archived: {
          artifact_count: 0,
          bytes: 0
      },
      skipped: {
          artifact_count: 0,
          bytes: 0
      }
  }

  @controller.with_discovered_artifacts(from: dates[:from], to: dates[:to], repos: options[:repos], threads: options[:threads]) do |artifact|
    if artifact_meets_criteria(artifact, dates, filter)
      if options.dry_run?
        STDERR.puts "Would archive #{artifact} to #{archive_to}"
      else
        @controller.archive_artifact artifact, archive_to
      end

      report[:archived][:artifact_count] += 1
      report[:archived][:bytes] += artifact.size
    else
      STDERR.puts "[DEBUG] Skipped #{artifact.inspect} because it did not meet the criteria" if options.verbose?
      report[:skipped][:artifact_count] += 1
      report[:skipped][:bytes] += artifact.size
    end
  end
  report.each do |key,values|
    puts "#{key} #{values[:artifact_count]} artifacts totaling #{Util.filesize values[:bytes]}"
  end
end
clean() click to toggle source

Delete artifacts meeting specific criteria

Clean up an Artifactory instance by deleting old, unused artifacts which meet given criteria

This is a CLI interface to Artifactory::Cleaner's primary function: deleting artifacts which have not been used in a long time (or which meet other criteria, determined by the powerful regex-based filters)

# File lib/artifactory/cleaner/cli.rb, line 234
def clean
  dates = parse_date_options
  filter = load_artifact_filter
  archive_to = parse_archive_option

  # Ready to locate and delete artifacts
  report = {
      deleted: {
          artifact_count: 0,
          bytes: 0
      },
      archived: {
          artifact_count: 0,
          bytes: 0
      },
      skipped: {
          artifact_count: 0,
          bytes: 0
      }
  }

  STDERR.puts "[DEBUG] controller.bucketize_artifacts from #{dates[:from]} to #{dates[:to]} repos #{options[:repos]}" if options.verbose?
  @controller.with_discovered_artifacts(from: dates[:from], to: dates[:to], repos: options[:repos], threads: options[:threads]) do |artifact|
    if artifact_meets_criteria(artifact, dates, filter)
      if archive_to
        if options.dry_run?
          STDERR.puts "Would archive #{artifact} to #{archive_to}"
        else
          @controller.archive_artifact artifact, archive_to
        end

        report[:archived][:artifact_count] += 1
        report[:archived][:bytes] += artifact.size
      end

      if options.dry_run?
        STDERR.puts "Would delete #{artifact}"
      else
        @controller.delete_artifact artifact
      end

      report[:deleted][:artifact_count] += 1
      report[:deleted][:bytes] += artifact.size
    else
      STDERR.puts "[DEBUG] Skipped #{artifact.inspect} because it did not meet the criteria" if options.verbose?
      report[:skipped][:artifact_count] += 1
      report[:skipped][:bytes] += artifact.size
    end
  end
  report.each do |key,values|
    puts "#{key} #{values[:artifact_count]} artifacts totaling #{Util.filesize values[:bytes]}"
  end
end
list_repos() click to toggle source

List all available repos

# File lib/artifactory/cleaner/cli.rb, line 89
def list_repos()
  repo_info_table = []
  repos = @controller.discover_repos
  repo_kinds = []
  repo_kinds << :local if options.local?
  repo_kinds << :remote if options.remote?
  repo_kinds << :virtual if options.virtual?
  include_cols = get_repo_cols(repo_kinds)
  repos[:local].each {|k, r| repo_info_table << repo_cols(r, include_cols)} if options.local?
  repos[:remote].each {|k, r| repo_info_table << repo_cols(r, include_cols)} if options.remote?
  repos[:virtual].each {|k, r| repo_info_table << repo_cols(r, include_cols)} if options.virtual?
  print_repo_list repo_info_table, include_cols
end
usage_report() click to toggle source

Analyze usage and report where space is used

# File lib/artifactory/cleaner/cli.rb, line 112
def usage_report
  begin
    from = Time.parse(options[:from])
    to = Time.parse(options[:to])
  rescue => ex
    STDERR.puts "Unable to parse time format. Please use: YYYY-MM-DD HH:II:SS"
    STDERR.puts ex
    exit Sysexits::EX_USAGE
  end

  begin
    STDERR.puts "[DEBUG] controller.bucketize_artifacts from #{from} to #{to} repos #{options[:repos]}" if options.verbose?
    buckets = @controller.bucketize_artifacts(
        from: from,
        to: to,
        repos: options[:repos],
        threads: options[:threads],
    )

    @controller.bucketized_artifact_report(buckets).each { |l| STDERR.puts l }
    if options.details?
      puts "# Detailed Bucket Report:"
      puts "buckets:"
      buckets.each do |bucket|
        puts "#--  #{bucket.length} artifacts between #{bucket.min} and #{bucket.max} days old repo_info_table #{bucket.filesize} bytes --"
        puts "  - min: #{bucket.min} # days old"
        puts "    max: #{bucket.max} # days old"
        if bucket.empty?
          puts "    artifacts: []"
        else
          puts "    artifacts:"
          bucket.each { |pkg| puts "    - #{@controller.yaml_format(pkg,6)}" }
        end
      end
    end
  rescue => err
    STDERR.puts "An exception occured while generating the usage report: #{err}"
    STDERR.puts err.full_message
    STDERR.puts "Caused by: #{err.cause.full_message}" if err.cause
    Pry::rescued(err) if defined?(Pry::rescue)
    exit Sysexits::EX_UNAVAILABLE
  end
end
version() click to toggle source

Show version information

# File lib/artifactory/cleaner/cli.rb, line 75
def version
  STDERR.puts "Artifactory::Cleaner version #{Artifactory::Cleaner::VERSION}"
  STDERR.puts "Copyright (C) 2020 Pinnacle 21, inc. All Rights Reserved"
end

Private Instance Methods

artifact_meets_criteria(artifact, dates, filter) click to toggle source

Check if a given artifact meets our CLI search criteria and filters

# File lib/artifactory/cleaner/cli.rb, line 359
def artifact_meets_criteria(artifact, dates, filter)
  (dates.has_key?(:created_before) ? artifact.created < dates[:created_before] : true) and
  (dates.has_key?(:modified_before) ? artifact.last_modified < dates[:modified_before] : true) and
  (dates.has_key?(:last_used_before) ? artifact.latest_date < dates[:last_used_before] : true) and
  (filter.action_for(artifact) == :include)
end
create_controller() click to toggle source

Initialize our Artifactory::Cleaner::Controller

# File lib/artifactory/cleaner/cli.rb, line 301
def create_controller
  @controller = Artifactory::Cleaner::Controller.new(@artifactory_config)
  @controller.verbose = true if options.verbose?
end
get_repo_cols(repo_kinds) click to toggle source

Helper method for generating CLI terminal-friendly tables of output

# File lib/artifactory/cleaner/cli.rb, line 376
def get_repo_cols(repo_kinds)
  if options.details?
    selected_cols =
        if options[:output].nil?
          Artifactory::Cleaner::CLI.default_repo_table_cols
        else
          options[:output].split(',').map &:to_sym
        end
    @repo_table_cols.select do |col|
      (col.only.nil? or repo_kinds.include? col.only) and (selected_cols.include? col.method)
    end
  else
    @repo_table_cols.select {|col| col.method == :key}
  end
end
load_artifact_filter() click to toggle source

Load Artifactory::Cleaner::ArtifactFilter objects from a YAML file

# File lib/artifactory/cleaner/cli.rb, line 344
def load_artifact_filter
  filter = ArtifactFilter.new
  if options[:filter]
    unless File.exist? options[:filter] and File.readable? options[:filter]
      STDERR.puts "Unable to read specified filter file #{options[:filter]}"
      exit Sysexits::EX_USAGE
    end
    rules = YAML.load_file options[:filter]
    rules.each {|rule| filter << rule}
  end
  filter
end
load_conf_file(path) click to toggle source

Loads the Artifactory configuration from a YAML file

# File lib/artifactory/cleaner/cli.rb, line 292
def load_conf_file(path)
  config = YAML.load_file path
  config.each do |key, val|
    @config[key] = val
  end
end
parse_archive_option() click to toggle source

Parse and validate value for the `–archive-to` CLI switch, ensuring it points to a valid, writable directory

# File lib/artifactory/cleaner/cli.rb, line 326
def parse_archive_option
  archive_to = options[:archive_to]
  if archive_to
    unless File.directory? archive_to
      STDERR.puts "#{archive_to} is not a directory. `--archive-to` expects a valid, existing directory under which to store archived artifacts"
      exit Sysexits::EX_USAGE
    end
    archive_to = File.realpath(archive_to)
    unless File.directory? archive_to and File.writable? archive_to
      STDERR.puts "Unable to write to directory #{archive_to} -- check permissions"
      exit Sysexits::EX_CANTCREAT
    end
  end
  archive_to
end
parse_date_options() click to toggle source

return Ruby Time objects formed from CLI switches `–to`, `–from`, `–ctreated-before` etc

# File lib/artifactory/cleaner/cli.rb, line 308
def parse_date_options
  dates = {}
  dates[:from] = Time.parse(options[:from]) if options[:from]
  dates[:created_before] = Time.parse(options[:created_before]) if options[:created_before]
  dates[:modified_before] = Time.parse(options[:modified_before]) if options[:modified_before]
  dates[:last_used_before] = Time.parse(options[:last_used_before]) if options[:last_used_before]
  dates[:to] = [dates[:created_before], dates[:modified_before], dates[:downloaded_before], dates[:last_used_before]].compact.sort.first

  if dates[:to].nil?
    STDERR.puts "At least one end date for search must be provided (--created-before, --modified-before, --downloaded-before or --last-used-before)"
    exit Sysexits::EX_USAGE
  end

  dates
end
print_repo_list(repo_info_table, include_cols) click to toggle source

CLI helper method for printing details of discovered repositories to a terminal

repo_cols(repo, include_cols) click to toggle source
# File lib/artifactory/cleaner/cli.rb, line 368
def repo_cols(repo, include_cols)
  include_cols.map do |col|
    repo.send(col.method).to_s
  end
end