Class: DTK::Client::CommandBaseThor

Inherits:
Thor
  • Object
show all
Extended by:
CommandBase, CommonOptionDefs::ClassMixin, Console, TaskStatusMixin
Includes:
CommandBase, CommandHelperMixin, Poller
Defined in:
lib/parser/adapters/thor.rb,
lib/parser/adapters/thor/common_option_defs.rb

Defined Under Namespace

Modules: CommonOptionDefs

Constant Summary collapse

TIME_DIFF =

second(s)

60
EXTENDED_TIMEOUT =

second(s)

360
HIDE_FROM_BASE_CONTEXT =
"HIDE_FROM_BASE"
ALT_IDENTIFIER_SEPARATOR =

thor command specific constants

':::'
@@cached_response =
{}
@@invalidate_map =
[]

Constants included from CommandHelperMixin

DTK::Client::CommandHelperMixin::Loaded

Constants included from ReparseMixin

ReparseMixin::YamlDTKMetaFiles

Constants included from Poller

Poller::PERIOD_WAIT_TIME

Constants inherited from Thor

Thor::HIDE_FROM_BASE_CONTEXT_HELP

Class Method Summary collapse

Instance Method Summary collapse

Methods included from CommandBase

get, get_connection, handle_argument_error, post, post_file, rest_url, rotate_args

Methods included from TaskStatusMixin

list_task_info_aux, task_status_aux, task_status_stream

Methods included from Console

confirmation_prompt, confirmation_prompt_additional_options, confirmation_prompt_multiple_choice, confirmation_prompt_simple, unix_shell, wait_animation

Methods included from CommandHelperMixin

#Helper

Methods included from ReparseMixin

#reparse_aux

Methods included from PushCloneChangesMixin

#push_clone_changes_aux

Methods included from CommonOptionDefs::ClassMixin

version_method_option

Methods included from Poller

#poller_response, #print_response, #resolve_type

Methods inherited from Thor

get_alternative_identifiers, help, match_help_item_changes, overriden_help, printable_tasks, replace_if_matched!, set_context

Constructor Details

#initialize(args, opts, config) ⇒ CommandBaseThor

Returns a new instance of CommandBaseThor.



55
56
57
58
# File 'lib/parser/adapters/thor.rb', line 55

def initialize(args, opts, config)
  @conn = config[:conn]
  super
end

Class Method Details

.action_on_revalidation_response(validation_response, conn, method_name, context_params, thor_options, shell_execution) ⇒ Object

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



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# 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

.basenameObject

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.



517
518
519
520
521
# 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) ⇒ Object



177
178
179
180
181
182
183
# 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) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 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) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
# 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 = {}) ⇒ Object

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



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# 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) ⇒ Object



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# 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) ⇒ Object



194
195
196
197
198
199
200
201
# 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) ⇒ Object



110
111
112
113
114
115
116
117
# 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') ⇒ Object

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



527
528
529
# 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?Boolean

Returns:

  • (Boolean)


185
186
187
# File 'lib/parser/adapters/thor.rb', line 185

def self.list_method_supported?
  return (respond_to?(:validation_list) || respond_to?(:whoami))
end

.task_namesObject

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



190
191
192
# File 'lib/parser/adapters/thor.rb', line 190

def self.task_names
  all_tasks().map(&:first).collect { |item| item.gsub('_','-')}
end

.tiered_task_namesObject

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



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# 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) ⇒ Boolean

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

Returns:

  • (Boolean)


322
323
324
325
326
327
328
# 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

Instance Method Details

#help(*args) ⇒ Object



532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
# 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