Top Level Namespace

Defined Under Namespace

Modules: Bundler, DTK, DtkShell Classes: DiskCacher, DtkLogger, DtkOpenStruct, MainContext, PPColumns, String, Thor

Constant Summary collapse

ALIAS_COMMANDS =
{
  'ls' => 'list',
  'cd' => 'cc',
  'rm' => 'delete'
}
POSSIBLE_COMMON_CORE_FOLDERS =

we leave possibilites that folders user multiple names when somebody takes fresh projects from git it is expected that person will use dtk-common name

['dtk-common-repo','dtk-common-core']
DEFAULT_COMMIT_MSG =
"Initial commit."
PULL_CATALOGS =
["dtkn"]
LOG_SLEEP_TIME_W =
DTK::Configuration.get(:tail_log_frequency)

Instance Method Summary collapse

Instance Method Details

#dtk_nested_require(dir, *files_x) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/require_first.rb', line 41

def dtk_nested_require(dir,*files_x)
  files = (files_x.first.kind_of?(Array) ? files_x.first : files_x) 
  caller_dir = caller.first.gsub(/\/[^\/]+$/,"")

  # invalid command will be send here as such needs to be handled.
  # we will throw DtkClient error as invalid command
  files.each do |f|
    begin
      require File.expand_path("#{dir}/#{f}",caller_dir)
    rescue LoadError => e
      if e.message.include? "#{dir}/#{f}"
        raise DTK::Client::DtkError,"Command '#{f}' not found."
      else
        raise e
      end
    end
  end
end

#dtk_require(*files_x) ⇒ Object



26
27
28
29
30
# File 'lib/require_first.rb', line 26

def dtk_require(*files_x)
  files = (files_x.first.kind_of?(Array) ? files_x.first : files_x) 
  caller_dir = caller.first.gsub(/\/[^\/]+$/,"")
  files.each{|f|require File.expand_path(f,caller_dir)}
end

#dtk_require_common_commands(*files_x) ⇒ Object



37
38
39
# File 'lib/require_first.rb', line 37

def dtk_require_common_commands(*files_x)
  dtk_require_from_base(*files_x.map{|f|"commands/common/#{f}"})
end

#dtk_require_dtk_common_core(common_library) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/require_first.rb', line 66

def dtk_require_dtk_common_core(common_library)
  # use common folder else common gem
  common_folder = determine_common_folder()

  if common_folder
    dtk_require("../../" + common_folder + "/lib/#{common_library}")
  elsif is_dtk_common_core_gem_installed?       
    # already loaded so do not do anything
  else
    raise DTK::Client::DtkError,"Common directory/gem not found, please make sure that you have cloned dtk-common folder or installed dtk common gem!"
  end
end

#dtk_require_from_base(*files_x) ⇒ Object



32
33
34
35
# File 'lib/require_first.rb', line 32

def dtk_require_from_base(*files_x)
  #different than just calling dtk_require because of change to context give by caller
  dtk_require(*files_x)
end

#execute_shell_command(line, prompt) ⇒ Object



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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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
# File 'lib/shell.rb', line 126

def execute_shell_command(line, prompt)
   begin
    # remove single/double quotes from string because shellwords module is not able to parse it
    if matched = line.scan(/['"]/)
      line.gsub!(/['"]/, '') if matched.size.odd?
    end

    # some special cases
    raise DTK::Shell::ExitSignal if line == 'exit'
    return prompt if line.empty?
    if line == 'clear'
      DTK::Client::OsUtil::clear_screen
      return prompt
    end
    # when using help on root this is needed
    line = 'dtk help' if (line == 'help' && MainContext.get_context.root?)

    args = Shellwords.split(line)
    cmd = args.shift

    # support command alias (ls for list etc.)
    cmd = preprocess_commands(cmd)

    # DEV only reload shell
    if ::DTK::Configuration.get(:development_mode)
      if ('restart' == cmd)
        puts "DEV Reloading shell ..."
        ::DTK::Client::OsUtil.dev_reload_shell()
        return prompt
      end
    end


    if ('cc' == cmd)
      # in case there is no params we just reload command
      args << "/" if args.empty?
      prompt = MainContext.get_context.change_context(args, cmd)
    elsif ('popc' == cmd)
        MainContext.get_context.dirs.shift()
        args << (MainContext.get_context.dirs.first.nil? ? '/' : MainContext.get_context.dirs.first)
        prompt = MainContext.get_context.change_context(args, cmd)
    elsif ('pushc' == cmd)
      if args.empty?
        args << (MainContext.get_context.dirs[1].nil? ? '/' : MainContext.get_context.dirs[1])
        MainContext.get_context.dirs.unshift(args.first)
        MainContext.get_context.dirs.uniq!
        prompt = MainContext.get_context.change_context(args, cmd)
      else
        prompt = MainContext.get_context.change_context(args)
        # using regex to remove dtk: and > from path returned by change_context
        # e.g transform dtk:/assembly/node> to /assembly/node
        full_path = prompt.match(/[dtk:](\/.*)[>]/)[1]
        MainContext.get_context.dirs.unshift(full_path)
      end
    elsif ('dirs' == cmd)
      puts MainContext.get_context.dirs.inspect
    else

      # get all next-context-candidates (e.g. for assembly get all assembly_names)
      context_candidates = MainContext.get_context.get_ac_candidates_for_context(MainContext.get_context.active_context.last_context(), MainContext.get_context.active_context())

      # this part of the code is used for calling of nested commands from base context (dtk:/>assembly/assembly_id converge)
      # base_command is used to check if first command from n-level is valid e.g.
      # (dtk:/>assembly/assembly_id converge - chech if 'assembly' exists in context_candidates)
      # revert_context is used to return to context which command is called from after command is executed
      base_command = cmd.split('/').first
      revert_context = false

      if context_candidates.include?(base_command)
        MainContext.get_context.change_context([cmd])
        cmd = args.shift
        revert_context = true
      end

      if cmd.nil?
        prompt = MainContext.get_context.change_context(["-"]) if revert_context
        raise DTK::Client::DtkValidationError, "You have to provide command after context name. Usage: CONTEXT-TYPE/CONTEXT-NAME COMMAND [ARG1] .. [ARG2]."
      end

      # send monkey patch class information about context
      Thor.set_context(MainContext.get_context)

      # we get command and hash params, will return Validation error if command is not valid
      entity_name, method_name, context_params, thor_options, invalid_options = MainContext.get_context.get_command_parameters(cmd,args)

      # check if command is executed from parent context (e.g assembly_name list-nodes)
      if context_candidates.include?(method_name)
        context_params.add_context_to_params(method_name, entity_name, method_name)
        method_name = context_params.method_arguments.shift if context_params.method_arguments.size > 0
      else
        unless MainContext.get_context.method_valid?(method_name)
          prompt = MainContext.get_context.change_context(["-"]) if revert_context
          raise DTK::Client::DtkValidationError, "Method '#{method_name}' is not valid in current context."
        end
      end

      # raise validation error if option is not valid
      raise DTK::Client::DtkValidationError.new("Option '#{invalid_options.first||method_name}' is not valid for current command!", true) unless invalid_options.empty?

      # execute command via Thor
      current_contex_path = MainContext.get_context.active_context.full_path
      top_level_execute(entity_name, method_name, context_params, thor_options, true)

      # when 'delete' or 'delete-and-destroy' command is executed reload cached tasks with latest commands
      unless (args.nil? || args.empty?)
        MainContext.get_context.reload_cached_tasks(entity_name) if (method_name.include?('delete') || method_name.include?('import'))
      end

      # check execution status, prints status to sttout
      DTK::Shell::StatusMonitor.check_status()

      # if we change context while executing command, change prompt as well
      unless current_contex_path.eql?(MainContext.get_context.active_context.full_path)
        prompt = "dtk:#{MainContext.get_context.active_context.full_path}>"
      end

      # after nested command called from base context is executed successfully, return to context which command is executed from
      # this is the same as 'cd -' command is executed
      prompt = MainContext.get_context.change_context(["-"]) if revert_context
    end
  rescue DTK::Client::DSLParsing => e
    DTK::Client::OsUtil.print(e.message, :red)
  rescue DTK::Client::DtkValidationError => e
    DTK::Client::OsUtil.print(e.message, :yellow)
  rescue DTK::Shell::Error => e
    DtkLogger.instance.error(e.message, true)
  end

  return prompt
end

#execute_shell_command_internal(line) ⇒ Object



259
260
261
# File 'lib/shell.rb', line 259

def execute_shell_command_internal(line)
  execute_shell_command(line, DTK::Shell::Context::DTK_ROOT_PROMPT)
end

#gem_only_available?Boolean

this returns true if there is no common folder e.g. dtk-common in parent folder, and gem is installed

Returns:

  • (Boolean)


62
63
64
# File 'lib/require_first.rb', line 62

def gem_only_available?()
  return !determine_common_folder() && is_dtk_common_core_gem_installed?
end

#init_shell_contextObject



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/shell.rb', line 105

def init_shell_context()
  begin
    # @context      = DTK::Shell::Context.new
    @shell_header = DTK::Shell::HeaderShell.new

    # loads root context
    MainContext.get_context.load_context()

    @t1   = nil
    Readline.completion_append_character=''
    DTK::Shell::Context.load_session_history().each do |c|
      Readline::HISTORY.push(c)
    end

  rescue DTK::Client::DtkError => e
    DtkLogger.instance.error(e.message, true)
    puts "Exiting ..."
    raise DTK::Shell::ExitSignal
  end
end

#load_command(command_name) ⇒ Object



137
138
139
140
141
142
# File 'lib/core.rb', line 137

def load_command(command_name)
  parser_adapter = DTK::Client::Config[:cli_parser] || "thor"

  dtk_nested_require("parser/adapters",parser_adapter)
  dtk_nested_require("commands/#{parser_adapter}",command_name)
end

#preprocess_commands(original_command) ⇒ Object

support for alias commands (ls for list, cd for cc etc.)



59
60
61
62
63
64
# File 'lib/shell.rb', line 59

def preprocess_commands(original_command)
  command = ALIAS_COMMANDS[original_command]
  # return command if alias for specific command exist in predefined ALIAS_COMMANDS
  # else return entered command because there is no alias for it
  return (command.nil? ? original_command : command)
end


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/core.rb', line 120

def print_method_response!(response_ruby_obj)
  # this will raise error if found
  DTK::Client::ResponseErrorHandler.check(response_ruby_obj)

  # this will find appropriate render adapter and give output, returns boolean
  if print = response_ruby_obj.render_data
    print = [print] unless print.kind_of?(Array)
    print.each do |el|
      if el.kind_of?(String)
        el.each_line{|l| STDOUT << l}
      else
        PP.pp(el,STDOUT)
      end
    end
  end
end

#resolve_direct_access(params, config_exists = nil) ⇒ Object

check if .add_direct_access file exists, if not then add direct access and create .add_direct_access file



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/core.rb', line 156

def resolve_direct_access(params, config_exists=nil)
  return if params[:username_exists]

  puts "Processing ..." if config_exists
  # check to see if catalog credentials are set
  conn = DTK::Client::Session.get_connection
  response = conn.post DTK::Client::CommandBase.class, conn.rest_url("account/check_catalog_credentials"), {}

  # set catalog credentails
  if response.ok? && !response.data['catalog_credentials_set']
    # setting up catalog credentials
    catalog_creds = DTK::Client::Configurator.ask_catalog_credentials
    unless catalog_creds.empty?
      response = conn.post DTK::Client::CommandBase.class, conn.rest_url("account/set_catalog_credentials"), { :username => catalog_creds[:username], :password => catalog_creds[:password], :validate => true}
      if errors = response['errors']
        DTK::Client::OsUtil.print("#{errors.first['message']} You will have to set catalog credentials manually ('dtk account set-catalog-credentials').", :yellow)
      end
    end
  end

  # response = DTK::Client::Account.add_access(params[:ssh_key_path])
  response, matched_pub_key, matched_username = DTK::Client::Account.add_key(params[:ssh_key_path], true, "#{DTK::Client::Session.connection_username}-client")

  if !response.ok?
    DTK::Client::OsUtil.print("We were not able to add access for current user. #{response.error_message}. In order to properly use dtk-shell you will have to add access manually ('dtk account add-ssh-key').\n", :yellow)
  elsif matched_pub_key
    # message will be displayed by add key # TODO: Refactor this flow
    DTK::Client::OsUtil.print("Provided SSH PUB key has already been added.", :yellow)
    DTK::Client::Configurator.add_current_user_to_direct_access
  elsif matched_username
    DTK::Client::OsUtil.print("User with provided name already exists.", :yellow)
  else
    # commented out because 'add_key' method called above will also print the same message
    # DTK::Client::OsUtil.print("Your SSH PUB key has been successfully added.", :yellow)
    DTK::Client::Configurator.add_current_user_to_direct_access
  end

  response
end

#run_shell_commandObject

RUNTIME PART - STARTS HERE



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/shell.rb', line 67

def run_shell_command()
  # init shell client
  init_shell_context()

  # prompt init
  prompt = DTK::Shell::Context::DTK_ROOT_PROMPT

  # trap CTRL-C and remove current text without leaving the dtk-shell
  trap("INT"){
    puts "\n"
    raise Interrupt
  }

  # runtime part
  begin
    while line = Readline.readline(prompt, true)
      prompt = execute_shell_command(line, prompt) unless line.strip.empty?
    end
  rescue DTK::Shell::ExitSignal => e
    # do nothing
  rescue ArgumentError => e
    puts e.backtrace if ::DTK::Configuration.get(:development_mode)
    retry
  rescue Interrupt => e
    retry
  rescue Exception => e
    client_internal_error = DTK::Client::DtkError::Client.label()
    DtkLogger.instance.error_pp("[#{client_internal_error}] #{e.message}", e.backtrace)
  ensure
    puts "\n" unless e.is_a? DTK::Shell::ExitSignal
    # logout
    DTK::Client::Session.logout()
    # save users history
    DTK::Shell::Context.save_session_history(Readline::HISTORY.to_a)
    exit!
  end
end

#top_level_execute(entity_name, method_name, context_params = nil, options_args = nil, shell_execute = false) ⇒ Object



47
48
49
50
51
52
53
54
55
56
# File 'lib/core.rb', line 47

def top_level_execute(entity_name, method_name, context_params=nil, options_args=nil, shell_execute=false)
  begin
    top_level_execute_core(entity_name, method_name, context_params, options_args, shell_execute)
  rescue DTK::Client::DtkLoginRequiredError
    # re-logging user and repeating request
    DTK::Client::OsUtil.print("Session expired: re-establishing session & repeating given task", :yellow)
    DTK::Client::Session.re_initialize
    top_level_execute_core(entity_name, method_name, context_params, options_args, shell_execute)
  end
end

#top_level_execute_core(entity_name, method_name, context_params = nil, options_args = nil, shell_execute = false) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
109
110
111
112
113
114
115
116
117
118
# File 'lib/core.rb', line 58

def top_level_execute_core(entity_name, method_name, context_params=nil, options_args=nil, shell_execute=false)
  extend DTK::Client::OsUtil

  entity_class = nil

  begin
    include DTK::Client::Auxiliary

    entity_name = entity_name.gsub("-","_")
    load_command(entity_name)
    conn = DTK::Client::Session.get_connection

    # if connection parameters are not set up properly then don't execute any command
    return if validate_connection(conn)

    # call proper thor class and task
    entity_class = DTK::Client.const_get "#{cap_form(entity_name)}"

    # call forwarding, in case there is no task for given entity we switch to last (n-context) and try than
    unless (entity_class.task_names.include?(method_name))
      entity_class = DTK::Client.const_get "#{cap_form(context_params.last_entity_name.to_s)}"
    end

    response_ruby_obj = entity_class.execute_from_cli(conn,method_name,context_params,options_args,shell_execute)

    # it will raise DTK::Client::Error in case of error response
    print_method_response!(response_ruby_obj)

    # process/print queued message from server
    DTK::Shell::MessageQueue.print_messages

  rescue DTK::Client::DtkLoginRequiredError => e
    # this error is handled in method above
    raise e
  rescue DTK::Client::DSLParsing => e
    DTK::Client::OsUtil.print(e.message, :red)
  rescue DTK::Client::DtkValidationError => e
    validation_message = e.message

    # if !e.skip_usage_info && entity_class && method_name
    #   usage_info = entity_class.get_usage_info(entity_name, method_name)
    #   validation_message += ", usage: #{usage_info}"
    # end

    if e.display_usage_info && entity_class && method_name
      usage_info = entity_class.get_usage_info(entity_name, method_name)
      validation_message += ", usage: #{usage_info}"

      validation_message.gsub!("^^", '') if validation_message.include?("^^")
      validation_message.gsub!("HIDE_FROM_BASE ", '') if validation_message.include?("HIDE_FROM_BASE")
    end

    DTK::Client::OsUtil.print(validation_message, :yellow)
  rescue DTK::Client::DtkError => e
    # this are expected application errors
    DtkLogger.instance.error_pp(e.message, e.backtrace)
  rescue Exception => e
    client_internal_error = DTK::Client::DtkError::Client.label()
    DtkLogger.instance.fatal_pp("[#{client_internal_error}] DTK has encountered an error #{e.class}: #{e.message}", e.backtrace)
  end
end

#validate_connection(connection) ⇒ Object

check if connection is set up properly



145
146
147
148
149
150
151
152
153
# File 'lib/core.rb', line 145

def validate_connection(connection)
  if connection.connection_error?
    connection.print_warning
    puts "\nDTK will now exit. Please set up your connection properly and try again."
    return true
  end

  false
end