class DTK::Client::CommandBaseThor

Constants

ALT_IDENTIFIER_SEPARATOR

thor command specific constants

EXTENDED_TIMEOUT
HIDE_FROM_BASE_CONTEXT
TIME_DIFF

Public Class Methods

action_on_revalidation_response(validation_response, conn, method_name, context_params, thor_options, shell_execution) click to toggle source

TODO: Make sure that server responds with new format of ARGVS

# File lib/parser/adapters/thor.rb, line 80
def self.action_on_revalidation_response(validation_response, conn, method_name, context_params, thor_options, shell_execution)
  puts "[NOTICE] #{validation_response.validation_message}"
  actions = validation_response.validation_actions

  actions.each_with_index do |action, i|
    if Console.confirmation_prompt("Pre-action '#{action['action']}' needed, execute"+"?")
      # we have hash map with values { :assembly_id => 2123123123, :option_1 => true }
      # we translate to array of values, with action as first element

      # def self.execute_from_cli(conn,method_name,context_params,options_args,shell_execution=false)
      response = self.execute_from_cli(conn, action['action'],  create_context_arguments(action['params']),[],shell_execution)
      # we abort if error happens
      ResponseErrorHandler.check(response)

      if action['wait_for_complete']
        entity_id, entity_type = action['wait_for_complete']['id'].to_s, action['wait_for_complete']['type']
        puts "Waiting for task to complete ..."
        task_status_aux(entity_id,entity_type,:wait => true)
      end
    else
      # validation action are being skipped
      return ""
    end
  end

  puts "Executing original action: '#{method_name}' ..."
  # if all executed correctly we run original action
  return self.execute_from_cli(conn,method_name, context_params,thor_options,shell_execution)
end
basename() click to toggle source

This is fix where we wanna exclude basename print when using dtk-shell. Thor has banner method, representing row when help is invoked. As such banner will print out basename first. e.g.

dtk-shell assembly list [library|target] # List asssemblies in library or target

Basename is derived from name of file, as such can be overriden to serve some other logic.

Calls superclass method
# File lib/parser/adapters/thor.rb, line 517
def self.basename
  basename = super
  basename = '' if basename == 'dtk-shell'
  return basename
end
create_context_arguments(params) click to toggle source
# File lib/parser/adapters/thor.rb, line 177
def self.create_context_arguments(params)
  context_params = DTK::Shell::ContextParams.new
  params.each do |k,v|
    context_params.add_context_to_params(k,k,v)
  end
  return context_params
end
execute_from_cli(conn,method_name,context_params,thor_options,shell_execution=false) click to toggle source
# File lib/parser/adapters/thor.rb, line 60
def self.execute_from_cli(conn,method_name,context_params,thor_options,shell_execution=false)
  @@shell_execution = shell_execution

  if method_name == 'help'
    ret = start([method_name] + context_params.method_arguments,:conn => conn)
  else
    ret = start([method_name, context_params] + (thor_options||[]),:conn => conn)
  end

  # special case where we have validation reply
  if ret.kind_of?(Response)
    if ret.validation_response?
      ret = action_on_revalidation_response(ret, conn, method_name, context_params, thor_options, shell_execution)
    end
  end

  ret.kind_of?(Response) ? ret : Response::NoOp.new
end
generate_cached_id(subtype) click to toggle source
# File lib/parser/adapters/thor.rb, line 164
def self.generate_cached_id(subtype)
  values = ''
  # subtype.sort.map do |key,value|
  # removed sort since subtype is hash where keys are symbols,
  # sort method uses the <=> comparison operator to put things into order but
  # symbols don't have a <=> comparison operator in ruby 1.8.7
  subtype.map do |key,value|
    values += value.to_s
  end

  Digest::SHA1.hexdigest(values)
end
get_cached_response(entity_name, url, subtype={}) click to toggle source

we take current timestamp and compare it to timestamp stored in @@cached_response if difference is greater than TIME_DIFF we send request again, if not we use response from cache

# File lib/parser/adapters/thor.rb, line 122
def self.get_cached_response(entity_name, url, subtype={})
  subtype ||= {}
  current_ts = Time.now.to_i
  cache_id = (subtype.empty? ? :response : generate_cached_id(subtype))

  # if @@cache_response is empty return true if not than return time difference between
  # current_ts and ts stored in cache
  time_difference = @@cached_response[entity_name].nil? ? true : ((current_ts - @@cached_response[entity_name][:ts]) > TIME_DIFF)

  if (@@cached_response[entity_name])
    time_difference = true if @@cached_response[entity_name][cache_id].nil?
  end

  if (time_difference || @@invalidate_map.include?(entity_name))
    response = post rest_url(url), subtype

    # we do not want to catch is if it is not valid
    if response.nil? || response.empty?
      DtkLogger.instance.debug("Response was nil or empty for that reason we did not cache it.")
      return response
    end

    if response.ok?
      response_hash = {cache_id => response, :ts => current_ts}

      @@invalidate_map.delete(entity_name) if @@invalidate_map.include?(entity_name)

      if @@cached_response[entity_name]
        @@cached_response[entity_name].merge!(response_hash)
      else
        @@cached_response.store(entity_name, response_hash)
      end
    end
  end

  if @@cached_response[entity_name]
    return @@cached_response[entity_name][cache_id]
  else
    return nil
  end
end
get_identifiers(conn, context_params) click to toggle source
# File lib/parser/adapters/thor.rb, line 330
def self.get_identifiers(conn, context_params)
  @conn    = conn if @conn.nil?

  # we force raw output
  # options = Thor::CoreExt::HashWithIndifferentAccess.new({'list' => true})

  3.downto(1) do
    # get list data from one of the methods
    if respond_to?(:validation_list)
      response = validation_list(context_params)
    else
      clazz, endpoint, opts = whoami()
      response = get_cached_response(clazz, endpoint, opts)
    end

    unless (response.nil? || response.empty?)
      unless response['data'].nil?
        identifiers = []
        response['data'].each do |element|
          # special flag to filter out data not needed here
          next if element['dtk_context_hidden']

          identifiers << { :name => element['display_name'], :identifier => element['id'], :shadow_entity => element['dtk_client_type'] }
        end
        return identifiers
      end
    end
    unless response.nil?
      break if response["status"].eql?('ok')
    end
    sleep(1)
  end

  DtkLogger.instance.warn("[WARNING] We were not able to check cached context, possible errors may occur.")
  return []
end
get_usage_info(entity_name, method) click to toggle source
# File lib/parser/adapters/thor.rb, line 194
def self.get_usage_info(entity_name, method)
  # no need for nil checks since task will be found
  # [0] element contains desire usage description
  # [2] element contains method name with '_'
  result = printable_tasks().select { |help_item|  help_item[2].gsub('_','-') == method }.flatten[0]
  # we add entity name with dashes
  return result.gsub('dtk', "dtk #{entity_name.gsub('_','-')}")
end
invalidate_entities(*array_of_entites) click to toggle source
# File lib/parser/adapters/thor.rb, line 110
def self.invalidate_entities(*array_of_entites)
  #  we handle to cases here
  # n-context invalidation, whole structure
  @@invalidate_map << array_of_entites.join('_').to_sym

  # last entity
  @@invalidate_map << array_of_entites.last.to_sym
end
invisible_context_list(sufix = 'identifier') click to toggle source

Returns list of invisible contexts with sufix provided (if any)

# File lib/parser/adapters/thor.rb, line 527
def self.invisible_context_list(sufix = 'identifier')
  self.respond_to?(:invisible_context) ? self.invisible_context.collect { |i| "#{i}-#{sufix}" } : []
end
list_method_supported?() click to toggle source
# File lib/parser/adapters/thor.rb, line 185
def self.list_method_supported?
  return (respond_to?(:validation_list) || respond_to?(:whoami))
end
new(args, opts, config) click to toggle source
Calls superclass method
# File lib/parser/adapters/thor.rb, line 55
def initialize(args, opts, config)
  @conn = config[:conn]
  super
end
task_names() click to toggle source

returns all task names for given thor class with use friendly names (with '-' instead '_')

# File lib/parser/adapters/thor.rb, line 190
def self.task_names
  all_tasks().map(&:first).collect { |item| item.gsub('_','-')}
end
tiered_task_names() click to toggle source

caches all the taks names for each possible tier and each thor class returnes it, executes only once and only on dtk-shell start

# File lib/parser/adapters/thor.rb, line 205
def self.tiered_task_names
  # cached data
  cached_tasks = {}

  # get command name from namespace (which is derived by thor from file name)
  command = namespace.split(':').last.gsub('_','-').upcase

  # first elvel identifier
  command_sym = command.downcase.to_sym
  command_id_sym = (command.downcase + '_wid').to_sym

  cached_tasks.store(command_sym, [])
  cached_tasks.store(command_id_sym, [])

  # n-context children
  all_children = []
  children = self.respond_to?(:all_children) ? self.all_children() : nil
  all_children << children unless children.nil?

  # some commands have multiple n-level contexts
  # e.g. workspace_node_component, workspace_utils and workspace_node_utils
  # we go through all of them and load them to 'all_children'
  multi_context_children = self.respond_to?(:multi_context_children) ? self.multi_context_children() : nil
  if multi_context_children
    multi_context_children.each do |mc|
      all_children << (mc.is_a?(Array) ? mc : multi_context_children)
    end
  end

  # n-context-override task, special case which
  override_task_obj = self.respond_to?(:override_allowed_methods) ? self.override_allowed_methods.dup : nil

  # we seperate tier 1 and tier 2 tasks
  all_tasks().each do |task|
    # noralize task name with '-' since it will be displayed to user that way
    task_name = task[0].gsub('_','-')
    usage     = task[1].usage
    # we will match those commands that require identifiers (NAME/ID)
    # e.g. ASSEMBLY-NAME/ID list ...   => MATCH
    # e.g. [ASSEMBLY-NAME/ID] list ... => MATCH
    matched_data = task[1].usage.match(/\[?#{command}.?(NAME\/ID|ID\/NAME)\]?/)
    matched_alt_identifiers_data = nil

    # we chance alternate providers
    if respond_to?(:alternate_identifiers)
      alternate_identifiers = self.alternate_identifiers()

      alternate_identifiers.each do |a_provider|
        if matched_alt_identifiers_data = task[1].usage.match(/\[?#{a_provider}.?(NAME\/ID|ID\/NAME)\]?/)
          command_alt_sym = "#{command}_#{a_provider}".downcase.to_sym
          cached_tasks[command_alt_sym] = cached_tasks.fetch(command_alt_sym,[])
          cached_tasks[command_alt_sym] << task_name
          # when found break
          break
        end
      end
    end

    # only if not matched alt data found continue with caching of task
    unless matched_alt_identifiers_data
      if matched_data.nil?
        # no match it means it is tier 1 task, tier 1 => dtk:\assembly>
        # using HIDE_FROM_BASE_CONTEXT to hide command from base context (e.g from dtk:/assembly>) ...
        # ... but to be able to use that command in other context
        # (e.g get-netstats removed from dtk:/assembly> but used in dtk:/assembly/assembly_id/utils)
        cached_tasks[command_sym] << task_name unless usage.include?(HIDE_FROM_BASE_CONTEXT)
      else
        # match means it is tier 2 taks, tier 2 => dtk:\assembly\231312345>
        cached_tasks[command_id_sym] << task_name
        # if there are '[' it means it is optinal identifiers so it is tier 1 and tier 2 task
        cached_tasks[command_sym] << task_name if matched_data[0].include?('[')
      end

      # n-level matching
      all_children.each do |child|
        current_children = []

        child.each do |c|
          current_children << c.to_s

          # create entry e.g. assembly_node_id
          child_id_sym = (command.downcase + '_' + current_children.join('_') + '_wid').to_sym

          # n-context matching
          matched_data = task[1].usage.match(/^\[?#{c.to_s.upcase}.?(NAME\/ID|ID\/NAME|ID|NAME)(\-?PATTERN)?\]?/)
          if matched_data
            cached_tasks[child_id_sym] = cached_tasks.fetch(child_id_sym,[]) << task_name
          end

          # override method list, we add these methods only once
          if override_task_obj && !override_task_obj.is_completed?(c)
            command_o_tasks, identifier_o_tasks = override_task_obj.get_all_tasks(c)
            child_sym    = (command.downcase + '_' + current_children.join('_')).to_sym

            command_o_tasks.each do |o_task|
              cached_tasks[child_sym] = cached_tasks.fetch(child_sym,[]) << o_task[0]
            end

            identifier_o_tasks.each do |o_task|
              cached_tasks[child_id_sym] = cached_tasks.fetch(child_id_sym,[]) << o_task[0]
            end

            override_task_obj.add_to_completed(c)
          end
        end
      end
    end
  end

  # there is always help, and in all cases this is exception to the rule
  cached_tasks[command_id_sym] << 'help'

  return cached_tasks
end
valid_id?(value, conn, context_params) click to toggle source

we make valid methods to make sure that when context changing we allow change only for valid ID/NAME

# File lib/parser/adapters/thor.rb, line 322
def self.valid_id?(value, conn, context_params)

  context_list = self.get_identifiers(conn, context_params)
  results = context_list.select { |e| e[:name].eql?(value) || e[:identifier].eql?(value.to_i)}

  return results.empty? ? nil : results.first
end

Public Instance Methods

current_method_info() click to toggle source

returns method name and usage

# File lib/parser/adapters/thor.rb, line 402
def current_method_info
  unless @_initializer[2][:current_task]
    raise DTK::Client::DtkError, "You are using development mode, and you have newer version of Thor gem than specified by dtk-client"
  end

  return @_initializer[2][:current_task].name, @_initializer[2][:current_task].usage
end
extended_timeout() { || ... } click to toggle source

Block that allows users to specify part of the code which is expected to run for longer duration

# File lib/parser/adapters/thor.rb, line 382
def extended_timeout
  puts "Please wait, this could take a few minutes ..."
  old_timeout = ::DTK::Client::Conn.get_timeout()
  ::DTK::Client::Conn.set_timeout(EXTENDED_TIMEOUT)
  result = yield
  ::DTK::Client::Conn.set_timeout(old_timeout)
  result
end
get_name_from_id_helper(entity_id, entity_type=nil,list_command_path=nil, subtype=nil) click to toggle source

TODO: can make more efficient by having rest call that returns name from id, rather than using 'list path' entity_id can be a name as well as an id

# File lib/parser/adapters/thor.rb, line 419
def get_name_from_id_helper(entity_id, entity_type=nil,list_command_path=nil, subtype=nil)
  return entity_id unless is_numeric_id?(entity_id)

  entity_id = entity_id.to_i
  if entity_type.nil?
    entity_type,list_command_path,subtype = self.class.whoami()
  end

  match = nil
  response = self.class.get_cached_response(entity_type,list_command_path,subtype)
  if response and response.ok? and response['data']
    match = response['data'].find{|entity|entity_id == entity['id']}
  end
  unless match
    raise DTK::Client::DtkError, "Not able to resolve entity name, please provide #{entity_type} name."
  end
  match['display_name']
end
get_namespace_and_name(input_remote_name, delimiter) click to toggle source

check for delimiter, if present returns namespace and name for module/service returns: namespace, name

# File lib/parser/adapters/thor.rb, line 476
def get_namespace_and_name(input_remote_name, delimiter)
  if (input_remote_name||'').include?(delimiter)
    input_remote_name.split(delimiter)
  # support ns/name as well as ns:name
  elsif (input_remote_name||'').include?('/')
    input_remote_name.split('/')
  else
    [nil, input_remote_name]
  end
end
get_namespace_and_name_for_component(component_full_name) click to toggle source
# File lib/parser/adapters/thor.rb, line 487
def get_namespace_and_name_for_component(component_full_name)
  namespace, name = nil, ''

  if (component_full_name||'').include?(':')
    match = component_full_name.match(/(^[^:]+):{1}(.*$)/)
    namespace, name = [$1,$2]

    return [nil, component_full_name] if (name.include?(':') && !name.include?('::'))

    # to be robust to user putting in ns::x::y which splits to ns=ns name=:x::y
    name.gsub!(/^:/,'')

    component_full_name = name
  end

  [namespace, component_full_name]
end
get_type_and_raise_error_if_invalid(about, default_about, type_options) click to toggle source
# File lib/parser/adapters/thor.rb, line 468
def get_type_and_raise_error_if_invalid(about, default_about, type_options)
  about ||= default_about
  raise DTK::Client::DtkError, "Not supported type '#{about}' for list for current context level. Possible type options: #{type_options.join(', ')}" unless type_options.include?(about)
  return about, about[0..-2].to_sym
end
help(*args) click to toggle source
Calls superclass method Thor::help
# File lib/parser/adapters/thor.rb, line 532
def help(*args)
  puts # pretty print
  not_dtk_clazz = true

  if defined?(DTK::Client::Dtk)
    not_dtk_clazz = !self.class.eql?(DTK::Client::Dtk)
  end

  # not_dtk_clazz - we don't use subcommand print in case of root DTK class
  # for other classes Assembly, Node, etc. we print subcommand
  # this gives us console output: dtk assembly converge ASSEMBLY-ID
  #
  # @@shell_execution - if we run from shell we don't want subcommand output
  #

  super(args.empty? ? nil : args.first, not_dtk_clazz && !@@shell_execution)

  # we will print error in case configuration has reported error
  @conn.print_warning if @conn.connection_error?
  puts # pretty print
end
is_numeric_id?(possible_id) click to toggle source
# File lib/parser/adapters/thor.rb, line 438
def is_numeric_id?(possible_id)
  !possible_id.to_s.match(/^[0-9]+$/).nil?
end
method_argument_names() click to toggle source

returns names of the arguments, after the method name

# File lib/parser/adapters/thor.rb, line 411
def method_argument_names
  name, usage = current_method_info
  results = usage.split(name.gsub(/_/,'-')).last || ""
  return results.split(' ')
end
not_implemented() click to toggle source

Method not implemented error

# File lib/parser/adapters/thor.rb, line 392
def not_implemented
  raise DTK::Client::DtkError, "Method NOT IMPLEMENTED!"
end
plural?(is_plural,term) click to toggle source

helper for error messages; prints singular or plural version tem will be of form singural/plural or simple term in which case plural formed by adding 's'

# File lib/parser/adapters/thor.rb, line 444
def plural?(is_plural,term)
  singular_plural = term.split('/')
  if singular_plural.size == 1
    singular_plural << "#{singular_plural[0]}s"
  end
  singular_plural[is_plural ? 1 : 0]
end
post_body(hash) click to toggle source

removes nil values

# File lib/parser/adapters/thor.rb, line 453
def post_body(hash)
  hash.inject(Hash.new){|h,(k,v)|v.nil? ? h : h.merge(k => v)}
end
raise_validation_error_method_usage(method_name) click to toggle source
# File lib/parser/adapters/thor.rb, line 396
def raise_validation_error_method_usage(method_name)
  usage_text = self.class.all_tasks[method_name][:usage]
  raise DTK::Client::DtkValidationError, "Invalid method usage, use: #{usage_text}"
end
run_shell_command(line) click to toggle source

Run shell command directly from main, use with CAUTION

# File lib/parser/adapters/thor.rb, line 375
def run_shell_command(line)
  TOPLEVEL_BINDING.eval('self').execute_shell_command_internal(line)
end
user_input(message) click to toggle source

User input prompt

# File lib/parser/adapters/thor.rb, line 458
def user_input(message)
  trap("INT", "SIG_IGN")
  while line = Readline.readline("#{message}: ",true)
    unless line.chomp.empty?
      trap("INT", false)
      return line
    end
  end
end