dtk-shell 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +5 -0
- data/Gemfile_dev +13 -0
- data/README.md +121 -0
- data/bin/dtk-execute +32 -0
- data/bin/dtk-run +92 -0
- data/bin/dtk-shell +31 -0
- data/dtk-shell.gemspec +50 -0
- data/lib/auxiliary.rb +61 -0
- data/lib/bundler_monkey_patch.rb +26 -0
- data/lib/client.rb +58 -0
- data/lib/command_helper.rb +33 -0
- data/lib/command_helpers/git_repo.rb +589 -0
- data/lib/command_helpers/git_repo/merge.rb +153 -0
- data/lib/command_helpers/jenkins_client.rb +106 -0
- data/lib/command_helpers/jenkins_client/config_xml.rb +288 -0
- data/lib/command_helpers/service_importer.rb +251 -0
- data/lib/command_helpers/service_link.rb +33 -0
- data/lib/command_helpers/test_module_creator.rb +69 -0
- data/lib/command_helpers/test_module_templates/dtk.model.yaml.eruby +10 -0
- data/lib/command_helpers/test_module_templates/spec_helper.rb.eruby +10 -0
- data/lib/command_helpers/test_module_templates/temp_component_spec.rb.eruby +5 -0
- data/lib/commands.rb +57 -0
- data/lib/commands/common/thor/access_control.rb +133 -0
- data/lib/commands/common/thor/action_result_handler.rb +74 -0
- data/lib/commands/common/thor/assembly_template.rb +92 -0
- data/lib/commands/common/thor/assembly_workspace.rb +1801 -0
- data/lib/commands/common/thor/base_command_helper.rb +59 -0
- data/lib/commands/common/thor/clone.rb +82 -0
- data/lib/commands/common/thor/common.rb +88 -0
- data/lib/commands/common/thor/common_base.rb +49 -0
- data/lib/commands/common/thor/create_target.rb +70 -0
- data/lib/commands/common/thor/edit.rb +255 -0
- data/lib/commands/common/thor/inventory_parser.rb +98 -0
- data/lib/commands/common/thor/list_diffs.rb +128 -0
- data/lib/commands/common/thor/module.rb +1011 -0
- data/lib/commands/common/thor/module/import.rb +210 -0
- data/lib/commands/common/thor/node.rb +53 -0
- data/lib/commands/common/thor/poller.rb +65 -0
- data/lib/commands/common/thor/pull_clone_changes.rb +28 -0
- data/lib/commands/common/thor/pull_from_remote.rb +152 -0
- data/lib/commands/common/thor/puppet_forge.rb +72 -0
- data/lib/commands/common/thor/purge_clone.rb +101 -0
- data/lib/commands/common/thor/push_clone_changes.rb +162 -0
- data/lib/commands/common/thor/push_to_remote.rb +94 -0
- data/lib/commands/common/thor/remotes.rb +71 -0
- data/lib/commands/common/thor/reparse.rb +40 -0
- data/lib/commands/common/thor/set_required_attributes.rb +46 -0
- data/lib/commands/thor/account.rb +239 -0
- data/lib/commands/thor/assembly.rb +356 -0
- data/lib/commands/thor/attribute.rb +79 -0
- data/lib/commands/thor/component.rb +70 -0
- data/lib/commands/thor/component_module.rb +501 -0
- data/lib/commands/thor/component_template.rb +174 -0
- data/lib/commands/thor/dependency.rb +34 -0
- data/lib/commands/thor/developer.rb +144 -0
- data/lib/commands/thor/dtk.rb +152 -0
- data/lib/commands/thor/library.rb +125 -0
- data/lib/commands/thor/node.rb +504 -0
- data/lib/commands/thor/node_template.rb +94 -0
- data/lib/commands/thor/project.rb +34 -0
- data/lib/commands/thor/provider.rb +233 -0
- data/lib/commands/thor/remotes.rb +49 -0
- data/lib/commands/thor/service.rb +941 -0
- data/lib/commands/thor/service_module.rb +914 -0
- data/lib/commands/thor/state_change.rb +25 -0
- data/lib/commands/thor/target.rb +250 -0
- data/lib/commands/thor/task.rb +116 -0
- data/lib/commands/thor/test_module.rb +310 -0
- data/lib/commands/thor/utils.rb +21 -0
- data/lib/commands/thor/workspace.rb +685 -0
- data/lib/config/cacert.pem +3785 -0
- data/lib/config/client.conf.header +20 -0
- data/lib/config/configuration.rb +99 -0
- data/lib/config/default.conf +16 -0
- data/lib/config/disk_cacher.rb +80 -0
- data/lib/configurator.rb +176 -0
- data/lib/context_router.rb +44 -0
- data/lib/core.rb +497 -0
- data/lib/domain/git_adapter.rb +412 -0
- data/lib/domain/git_error_handler.rb +64 -0
- data/lib/domain/response.rb +285 -0
- data/lib/domain/response/error_handler.rb +86 -0
- data/lib/dtk-shell/version.rb +20 -0
- data/lib/dtk_constants.rb +40 -0
- data/lib/dtk_error.rb +114 -0
- data/lib/dtk_logger.rb +126 -0
- data/lib/dtk_shell.rb +31 -0
- data/lib/error.rb +85 -0
- data/lib/execute.rb +29 -0
- data/lib/execute/cli_pure/cli_rerouter.rb +102 -0
- data/lib/execute/command.rb +40 -0
- data/lib/execute/command/api_call.rb +60 -0
- data/lib/execute/command/api_call/map.rb +60 -0
- data/lib/execute/command/api_call/service.rb +91 -0
- data/lib/execute/command/api_call/translation_term.rb +119 -0
- data/lib/execute/command/rest_call.rb +37 -0
- data/lib/execute/command_processor.rb +30 -0
- data/lib/execute/command_processor/rest_call.rb +59 -0
- data/lib/execute/error_usage.rb +21 -0
- data/lib/execute/execute_context.rb +86 -0
- data/lib/execute/execute_context/result_store.rb +37 -0
- data/lib/execute/script.rb +64 -0
- data/lib/execute/script/add_tenant.rb +121 -0
- data/lib/git-logs/git.log +0 -0
- data/lib/parser/adapters/option_parser.rb +70 -0
- data/lib/parser/adapters/thor.rb +555 -0
- data/lib/parser/adapters/thor/common_option_defs.rb +40 -0
- data/lib/require_first.rb +104 -0
- data/lib/search_hash.rb +44 -0
- data/lib/shell.rb +261 -0
- data/lib/shell/context.rb +1065 -0
- data/lib/shell/context_aux.rb +46 -0
- data/lib/shell/domain/active_context.rb +186 -0
- data/lib/shell/domain/context_entity.rb +89 -0
- data/lib/shell/domain/context_params.rb +223 -0
- data/lib/shell/domain/override_tasks.rb +88 -0
- data/lib/shell/domain/shadow_entity.rb +76 -0
- data/lib/shell/header_shell.rb +44 -0
- data/lib/shell/help_monkey_patch.rb +283 -0
- data/lib/shell/interactive_wizard.rb +225 -0
- data/lib/shell/message_queue.rb +63 -0
- data/lib/shell/parse_monkey_patch.rb +39 -0
- data/lib/shell/status_monitor.rb +124 -0
- data/lib/task_status.rb +83 -0
- data/lib/task_status/refresh_mode.rb +77 -0
- data/lib/task_status/snapshot_mode.rb +28 -0
- data/lib/task_status/stream_mode.rb +48 -0
- data/lib/task_status/stream_mode/element.rb +101 -0
- data/lib/task_status/stream_mode/element/format.rb +101 -0
- data/lib/task_status/stream_mode/element/hierarchical_task.rb +100 -0
- data/lib/task_status/stream_mode/element/hierarchical_task/result.rb +72 -0
- data/lib/task_status/stream_mode/element/hierarchical_task/result/action.rb +93 -0
- data/lib/task_status/stream_mode/element/hierarchical_task/result/components.rb +26 -0
- data/lib/task_status/stream_mode/element/hierarchical_task/result/node_level.rb +26 -0
- data/lib/task_status/stream_mode/element/hierarchical_task/steps.rb +34 -0
- data/lib/task_status/stream_mode/element/hierarchical_task/steps/action.rb +53 -0
- data/lib/task_status/stream_mode/element/hierarchical_task/steps/components.rb +53 -0
- data/lib/task_status/stream_mode/element/hierarchical_task/steps/node_level.rb +42 -0
- data/lib/task_status/stream_mode/element/no_results.rb +26 -0
- data/lib/task_status/stream_mode/element/render.rb +59 -0
- data/lib/task_status/stream_mode/element/stage.rb +84 -0
- data/lib/task_status/stream_mode/element/stage/render.rb +76 -0
- data/lib/task_status/stream_mode/element/task_end.rb +35 -0
- data/lib/task_status/stream_mode/element/task_start.rb +37 -0
- data/lib/util/common_util.rb +37 -0
- data/lib/util/console.rb +235 -0
- data/lib/util/dtk_puppet.rb +65 -0
- data/lib/util/module_util.rb +66 -0
- data/lib/util/os_util.rb +385 -0
- data/lib/util/permission_util.rb +31 -0
- data/lib/util/remote_dependency_util.rb +84 -0
- data/lib/util/ssh_util.rb +94 -0
- data/lib/view_processor.rb +129 -0
- data/lib/view_processor/augmented_simple_list.rb +44 -0
- data/lib/view_processor/hash_pretty_print.rb +123 -0
- data/lib/view_processor/simple_list.rb +156 -0
- data/lib/view_processor/table_print.rb +309 -0
- data/lib/violation.rb +86 -0
- data/lib/violation/attribute.rb +76 -0
- data/lib/violation/fix.rb +26 -0
- data/lib/violation/fix/result.rb +73 -0
- data/lib/violation/fix/result/error.rb +34 -0
- data/lib/violation/fix/set_attribute.rb +41 -0
- data/lib/violation/sub_classes.rb +60 -0
- data/puppet/manifests/init.pp +72 -0
- data/puppet/manifests/params.pp +16 -0
- data/puppet/r8meta.puppet.yml +35 -0
- data/puppet/templates/bash_profile.erb +2 -0
- data/puppet/templates/client.conf.erb +1 -0
- data/puppet/templates/dtkclient.erb +2 -0
- data/spec/component_module_spec.rb +34 -0
- data/spec/dependency_spec.rb +6 -0
- data/spec/dtk_shell_spec.rb +13 -0
- data/spec/dtk_spec.rb +33 -0
- data/spec/lib/spec_helper.rb +10 -0
- data/spec/lib/spec_thor.rb +108 -0
- data/spec/node_template_spec.rb +24 -0
- data/spec/project_spec.rb +6 -0
- data/spec/repo_spec.rb +7 -0
- data/spec/response_spec.rb +52 -0
- data/spec/service_module_spec.rb +38 -0
- data/spec/service_spec.rb +50 -0
- data/spec/state_change_spec.rb +7 -0
- data/spec/table_print_spec.rb +48 -0
- data/spec/target_spec.rb +57 -0
- data/spec/task_spec.rb +28 -0
- data/views/assembly/augmented_simple_list.rb +12 -0
- data/views/assembly_template/augmented_simple_list.rb +12 -0
- data/views/list_task/augmented_simple_list.rb +12 -0
- metadata +421 -0
|
@@ -0,0 +1,1065 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2010-2016 dtk contributors
|
|
3
|
+
#
|
|
4
|
+
# This file is part of the dtk project.
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
require File.expand_path('../commands/thor/dtk', File.dirname(__FILE__))
|
|
19
|
+
require File.expand_path('../auxiliary', File.dirname(__FILE__))
|
|
20
|
+
require 'json'
|
|
21
|
+
|
|
22
|
+
module DTK
|
|
23
|
+
module Shell
|
|
24
|
+
|
|
25
|
+
class Context
|
|
26
|
+
include DTK::Client::Auxiliary
|
|
27
|
+
include DTK::Client::CommandBase
|
|
28
|
+
|
|
29
|
+
# client commands
|
|
30
|
+
CLIENT_COMMANDS = ['cc','exit','clear','pushc','popc','dirs','help']
|
|
31
|
+
# CLIENT_COMMANDS = ['cc','exit','clear','help']
|
|
32
|
+
DEV_COMMANDS = ['restart']
|
|
33
|
+
DTK_ROOT_PROMPT = "dtk:/>"
|
|
34
|
+
COMMAND_HISTORY_LIMIT = 200
|
|
35
|
+
HISTORY_LOCATION = DTK::Client::OsUtil.dtk_local_folder + "shell_history"
|
|
36
|
+
ROOT_TASKS = DTK::Client::Dtk.task_names
|
|
37
|
+
ALL_COMMANDS = ROOT_TASKS + DTK::Client::Dtk.additional_entities
|
|
38
|
+
IDENTIFIERS_ONLY = ['cc','cd','pushc']
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
SYM_LINKS = [
|
|
42
|
+
{ :alias => :workspace, :path => 'workspace/workspace' }
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# current holds context (list of commands) for active context e.g. dtk:\library>
|
|
46
|
+
attr_accessor :current
|
|
47
|
+
attr_accessor :active_context
|
|
48
|
+
attr_accessor :cached_tasks
|
|
49
|
+
attr_accessor :dirs
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def initialize(skip_caching=false)
|
|
53
|
+
@cached_tasks, @dirs = DTK::Shell::CachedTasks.new, []
|
|
54
|
+
@active_context = ActiveContext.new
|
|
55
|
+
@previous_context = nil
|
|
56
|
+
|
|
57
|
+
# member used to hold current commands loaded for current command
|
|
58
|
+
@context_commands = []
|
|
59
|
+
@conn = DTK::Client::Session.get_connection()
|
|
60
|
+
|
|
61
|
+
# if connection parameters are not set up properly, print warning and exit dtk_shell
|
|
62
|
+
exit if validate_connection(@conn)
|
|
63
|
+
|
|
64
|
+
unless skip_caching
|
|
65
|
+
@cached_tasks.store('dtk', ROOT_TASKS.sort)
|
|
66
|
+
|
|
67
|
+
ALL_COMMANDS.each do |task_name|
|
|
68
|
+
# we exclude help since there is no command class for it
|
|
69
|
+
next if task_name.eql? "help"
|
|
70
|
+
Context.require_command_class(task_name)
|
|
71
|
+
|
|
72
|
+
get_latest_tasks(task_name)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.get_command_class(command_name)
|
|
78
|
+
::DTK::Client::OsUtil.get_dtk_class(command_name)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.require_command_class(command_name)
|
|
82
|
+
# normalize to file_names
|
|
83
|
+
file_name = command_name.gsub('-','_')
|
|
84
|
+
require File.expand_path("../commands/thor/#{file_name}", File.dirname(__FILE__))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# SYM_LINKS methods is used to calculate aliases that will be used for certain entities
|
|
88
|
+
# one of those approaches will be as such
|
|
89
|
+
def self.check_for_sym_link(entries)
|
|
90
|
+
# remove empty strings from array
|
|
91
|
+
entries.reject! { |e| e.empty? }
|
|
92
|
+
|
|
93
|
+
if (entries.size > 0)
|
|
94
|
+
SYM_LINKS.each do |sym_link|
|
|
95
|
+
if entries.first.downcase.to_sym.eql?(sym_link[:alias])
|
|
96
|
+
entries[0] = sym_link[:path].split('/')
|
|
97
|
+
entries.flatten!
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
entries
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# take current path and see if it is aliased path
|
|
106
|
+
def self.enchance_path_with_alias(path, context_list)
|
|
107
|
+
SYM_LINKS.each do |sym_link|
|
|
108
|
+
if path.downcase.include?(sym_link[:path])
|
|
109
|
+
path = path.gsub(sym_link[:path], sym_link[:alias].to_s)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
unless context_list.empty?
|
|
114
|
+
init_context = context_list.first.name
|
|
115
|
+
command_clazz = Context.get_command_class(init_context)
|
|
116
|
+
invisible_context = command_clazz.respond_to?(:invisible_context) ? command_clazz.invisible_context() : {}
|
|
117
|
+
|
|
118
|
+
invisible_context.each do |ic|
|
|
119
|
+
path = path.gsub(/\/#{ic}\//,'/')
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
path
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Validates and changes context
|
|
127
|
+
def change_context(args, cmd=[])
|
|
128
|
+
begin
|
|
129
|
+
# check if we are doing switch context
|
|
130
|
+
if args.join("").match(/\A\-\Z/)
|
|
131
|
+
revert_context()
|
|
132
|
+
return
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# remember current context
|
|
136
|
+
@previous_context = @active_context.clone_me()
|
|
137
|
+
|
|
138
|
+
# jump to root
|
|
139
|
+
reset if args.join('').match(/^\//)
|
|
140
|
+
|
|
141
|
+
# begin
|
|
142
|
+
# hack: used just to avoid entering assembly/id/node or workspace/node context (remove when include this contexts again)
|
|
143
|
+
first_c, warning_message = nil, nil
|
|
144
|
+
first_c = @active_context.context_list().first.name unless @active_context.context_list().empty?
|
|
145
|
+
tmp_active_context = @active_context.clone_me
|
|
146
|
+
restricted = is_restricted_context(first_c, args, tmp_active_context)
|
|
147
|
+
|
|
148
|
+
args = restricted[:args]
|
|
149
|
+
warning_message = restricted[:message]
|
|
150
|
+
node_specific = restricted[:node_specific]
|
|
151
|
+
|
|
152
|
+
DTK::Client::OsUtil.print_warning(warning_message) if warning_message
|
|
153
|
+
# end
|
|
154
|
+
|
|
155
|
+
# Validate and change context
|
|
156
|
+
@active_context, error_message = prepare_context_change(args, @active_context, node_specific, cmd, true)
|
|
157
|
+
|
|
158
|
+
load_context(active_context.last_context_name)
|
|
159
|
+
|
|
160
|
+
if error_message
|
|
161
|
+
revert_context()
|
|
162
|
+
raise DTK::Client::DtkValidationError, error_message
|
|
163
|
+
end
|
|
164
|
+
rescue DTK::Client::DtkValidationError => e
|
|
165
|
+
DTK::Client::OsUtil.print(e.message, :yellow)
|
|
166
|
+
rescue DTK::Shell::Error, Exception => e
|
|
167
|
+
DtkLogger.instance.error_pp(e.message, e.backtrace)
|
|
168
|
+
ensure
|
|
169
|
+
return shell_prompt()
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# this method is used to scan and provide context to be available Readline.complete_proc
|
|
174
|
+
def dynamic_autocomplete_context(readline_input, line_buffer=[])
|
|
175
|
+
# special case indicator when user starts cc with '/' (go from root)
|
|
176
|
+
goes_from_root = readline_input.start_with?('/')
|
|
177
|
+
# Cloning existing active context, as all changes shouldn't be permanent, but temporary for autocomplete
|
|
178
|
+
active_context_copy = @active_context.clone_me
|
|
179
|
+
# Emptying context copy if it goes from root '/'
|
|
180
|
+
active_context_copy.clear if goes_from_root
|
|
181
|
+
# Invalid context is user leftover to be matched; i.e. 'cc /assembly/te' - 'te' is leftover
|
|
182
|
+
invalid_context = ""
|
|
183
|
+
|
|
184
|
+
# Validate and change context; skip step if user's input is empty or it is equal to '/'
|
|
185
|
+
active_context_copy, error_message, invalid_context = prepare_context_change([readline_input], active_context_copy, nil, line_buffer) unless (readline_input.empty? || readline_input == "/")
|
|
186
|
+
|
|
187
|
+
# using extended_context when we want to use autocomplete from other context
|
|
188
|
+
# e.g. we are in assembly/apache context and want to create-component we will use extended context to add
|
|
189
|
+
# component-templates to autocomplete
|
|
190
|
+
extended_candidates, new_context, line_buffer_first = {}, nil, nil
|
|
191
|
+
command_clazz = Context.get_command_class(active_context_copy.last_command_name)
|
|
192
|
+
|
|
193
|
+
unless (line_buffer.empty? || line_buffer.strip().empty?)
|
|
194
|
+
line_buffer = line_buffer.split(' ')
|
|
195
|
+
line_buffer_last = line_buffer.last
|
|
196
|
+
line_buffer_first = line_buffer.first
|
|
197
|
+
line_buffer_first.gsub!('-','_') unless (line_buffer_first.nil? || line_buffer_first.empty?)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
unless command_clazz.nil?
|
|
201
|
+
extended_context = command_clazz.respond_to?(:extended_context) ? command_clazz.extended_context() : {}
|
|
202
|
+
|
|
203
|
+
unless extended_context.empty?
|
|
204
|
+
extended_context = extended_context[:context] || {}
|
|
205
|
+
extended_context.select!{|k,v| line_buffer.include?(k.to_s)} if extended_context.respond_to?(:select!)
|
|
206
|
+
|
|
207
|
+
if (extended_context[line_buffer_last] && !line_buffer_first.eql?(line_buffer_last))
|
|
208
|
+
new_context = extended_context[line_buffer_last]
|
|
209
|
+
elsif (extended_context[line_buffer[line_buffer.size-2]] && !line_buffer_first.eql?(extended_context[line_buffer[line_buffer.size-2]]))
|
|
210
|
+
new_context = extended_context[line_buffer[line_buffer.size-2]]
|
|
211
|
+
else
|
|
212
|
+
new_context = extended_context[line_buffer_first.to_sym] unless line_buffer_first.nil? || line_buffer_first.empty?
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
unless new_context.nil?
|
|
216
|
+
if new_context.is_a?(String)
|
|
217
|
+
active_context_copy.push_new_context(new_context, new_context)
|
|
218
|
+
elsif new_context.is_a?(Hash)
|
|
219
|
+
context_candidates = load_extended_context_commands(new_context, active_context_copy)
|
|
220
|
+
results_filter = (readline_input.match(/\/$/) && invalid_context.empty?) ? "" : readline_input.split("/").last
|
|
221
|
+
results_filter ||= ""
|
|
222
|
+
context_candidates = context_candidates.grep( /^#{Regexp.escape(results_filter)}/ )
|
|
223
|
+
return context_candidates
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
return get_ac_candidates(active_context_copy, readline_input, invalid_context, goes_from_root, line_buffer_first||{})
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# TODO: this is hack used this to hide 'node' context and use just node_identifier
|
|
233
|
+
# we should rethink the design of shell context if we are about to use different behaviors like this
|
|
234
|
+
def self.check_invisible_context(acc, entries, is_root, line_buffer=[], args=[], current_context_clazz=nil)
|
|
235
|
+
check = nil
|
|
236
|
+
entries.reject! { |e| e.empty? }
|
|
237
|
+
goes_from_root = args.first.start_with?('/')
|
|
238
|
+
|
|
239
|
+
unless (line_buffer.empty? && args.empty?)
|
|
240
|
+
command = line_buffer.empty? ? args.first : line_buffer.split(' ').first
|
|
241
|
+
# command = line_buffer.split(' ').first
|
|
242
|
+
current_c_name = acc.last_command_name
|
|
243
|
+
current_context = acc.last_context
|
|
244
|
+
clazz = DTK::Shell::Context.get_command_class(current_c_name)
|
|
245
|
+
command_from_args = nil
|
|
246
|
+
|
|
247
|
+
if args.first.include?('/')
|
|
248
|
+
command_from_args = goes_from_root ? args.first.split('/')[1] : args.first.split('/').first
|
|
249
|
+
clazz_from_args = DTK::Shell::Context.get_command_class(command_from_args) if command_from_args
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
check = (command.eql?('cd') || command.eql?('cc') || command.eql?('popc') || command.eql?('pushc') || command.eql?('delete-node'))
|
|
253
|
+
# used when calling node commands from service or workspace context
|
|
254
|
+
# e.g. service/service_name>node1 info will display info about nodes instead of service instance
|
|
255
|
+
unless check
|
|
256
|
+
if current_c_name.eql?('service') || current_c_name.eql?('workspace')
|
|
257
|
+
context_hash_data, error_message, invalid_context = current_context_clazz.validate_value('node', command, acc)
|
|
258
|
+
check = true if context_hash_data && !error_message
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# this delete-node is a hack because we need autocomplete when there is node with name 'node'
|
|
263
|
+
# if (command.eql?('cd') || command.eql?('cc') || command.eql?('popc') || command.eql?('pushc') || command.eql?('delete-node'))
|
|
264
|
+
if check
|
|
265
|
+
if is_root
|
|
266
|
+
if entries.size >= 3
|
|
267
|
+
node = entries[2]
|
|
268
|
+
if (node && clazz_from_args.respond_to?(:valid_child?) && !clazz_from_args.invisible_context_list.empty?)
|
|
269
|
+
unless clazz_from_args.valid_children().first.to_s.include?(node)
|
|
270
|
+
entries[2] = ["node", node]
|
|
271
|
+
entries.flatten!
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
else
|
|
276
|
+
double_dots_count = DTK::Shell::ContextAux.count_double_dots(entries)
|
|
277
|
+
|
|
278
|
+
unless double_dots_count > 0
|
|
279
|
+
if clazz.respond_to?(:invisible_context)
|
|
280
|
+
if current_context.is_command?
|
|
281
|
+
node = entries[1]
|
|
282
|
+
if (node && clazz.respond_to?(:valid_child?))
|
|
283
|
+
unless clazz.valid_children().first.to_s.include?(node)
|
|
284
|
+
entries[1] = ["node", node]
|
|
285
|
+
entries.flatten!
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
elsif current_context.is_identifier?
|
|
289
|
+
node = entries[0]
|
|
290
|
+
if (node && clazz.respond_to?(:valid_child?))
|
|
291
|
+
unless clazz.valid_children().first.to_s.include?(node)
|
|
292
|
+
entries[0] = ["node", node]
|
|
293
|
+
entries.flatten!
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
entries
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def prepare_context_change(args, active_context_copy, node_specific=nil, line_buffer=[], on_complete=false)
|
|
310
|
+
# split original cc command
|
|
311
|
+
entries = args.first.split(/\//)
|
|
312
|
+
|
|
313
|
+
# transform alias to full path
|
|
314
|
+
entries = Context.check_for_sym_link(entries) if root?
|
|
315
|
+
entries = Context.check_invisible_context(active_context_copy, entries, root?, line_buffer, args, self)
|
|
316
|
+
|
|
317
|
+
# if only '/' or just cc skip validation
|
|
318
|
+
return active_context_copy if entries.empty?
|
|
319
|
+
|
|
320
|
+
current_context_clazz, error_message, current_index = nil, nil, 0
|
|
321
|
+
double_dots_count = DTK::Shell::ContextAux.count_double_dots(entries)
|
|
322
|
+
|
|
323
|
+
# we remove '..' from our entries
|
|
324
|
+
entries = entries.select { |e| !(e.empty? || DTK::Shell::ContextAux.is_double_dot?(e)) }
|
|
325
|
+
|
|
326
|
+
# need this for autocomplete from service/node to service/utils
|
|
327
|
+
is_utils = active_context_copy.last_command_name.eql?('utils')
|
|
328
|
+
|
|
329
|
+
# we go back in context based on '..'
|
|
330
|
+
active_context_copy.pop_context(double_dots_count)
|
|
331
|
+
|
|
332
|
+
# if cd ../utils from service/node
|
|
333
|
+
if active_context_copy.current_command? && active_context_copy.last_command_name.eql?('node')
|
|
334
|
+
active_context_copy.pop_context(1)
|
|
335
|
+
entries = Context.check_invisible_context(active_context_copy, entries, root?, line_buffer, args, self)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# need this for autocomplete from service/node to service/utils
|
|
339
|
+
if is_utils
|
|
340
|
+
entries = Context.check_invisible_context(active_context_copy, entries, root?, line_buffer, args, self)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# if cd .. back to node, skip node context and go to assembly/workspace context
|
|
344
|
+
if (active_context_copy.last_context && entries)
|
|
345
|
+
active_context_copy.pop_context(1) if (node_specific && active_context_copy.last_context.is_command? && active_context_copy.last_command_name.eql?("node") && on_complete)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# special case when using workspace context
|
|
349
|
+
# if do cd .. from workspace/workspace identifier go directly to root not to workspace
|
|
350
|
+
if active_context_copy.name_list.include?("workspace")
|
|
351
|
+
count_workspaces = active_context_copy.name_list.inject(Hash.new(0)) {|h,i| h[i] += 1; h }
|
|
352
|
+
# don't need this since we can have workspace service instance
|
|
353
|
+
# active_context_copy.pop_context(1) if count_workspaces['workspace']==1
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# we add active commands array to begining, using dup to avoid change by ref.
|
|
357
|
+
context_name_list = active_context_copy.name_list
|
|
358
|
+
entries = context_name_list + entries
|
|
359
|
+
|
|
360
|
+
# we check the size of active commands
|
|
361
|
+
ac_size = context_name_list.size
|
|
362
|
+
|
|
363
|
+
invalid_context = ""
|
|
364
|
+
# check each par for command / value
|
|
365
|
+
(0..(entries.size-1)).step(2) do |i|
|
|
366
|
+
command = entries[i]
|
|
367
|
+
value = entries[i+1]
|
|
368
|
+
|
|
369
|
+
clazz = DTK::Shell::Context.get_command_class(command)
|
|
370
|
+
error_message, invalid_context = validate_command(clazz, current_context_clazz, command, active_context_copy)
|
|
371
|
+
|
|
372
|
+
# restrict utils subcontext in service/service_name/node_group only
|
|
373
|
+
if active_context_copy.last_context_is_shadow_entity? && active_context_copy.last_context.shadow_entity().eql?('node_group')
|
|
374
|
+
if current_context_clazz.respond_to?(:multi_context_children)
|
|
375
|
+
multi_context_children = current_context_clazz.multi_context_children()
|
|
376
|
+
if command.eql?('utils') && multi_context_children.include?(command.to_sym)
|
|
377
|
+
return active_context_copy, "'#{command}' context is not valid.", invalid_context
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
break if error_message
|
|
383
|
+
# if we are dealing with new entries add them to active_context
|
|
384
|
+
active_context_copy.push_new_context(command, command) if (i >= ac_size)
|
|
385
|
+
current_context_clazz = clazz
|
|
386
|
+
|
|
387
|
+
if value
|
|
388
|
+
# context_hash_data is hash with :name, :identifier values
|
|
389
|
+
context_hash_data, error_message, invalid_context = validate_value(command, value, active_context_copy)
|
|
390
|
+
if error_message
|
|
391
|
+
# hack: used just to avoid entering assembly/id/node or workspace/node context (remove when include this contexts again)
|
|
392
|
+
if ((@active_context.last_context_name.eql?("node") || node_specific) && !@active_context.first_context_name().eql?("node") )
|
|
393
|
+
active_context_copy.pop_context(1)
|
|
394
|
+
end
|
|
395
|
+
break
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
active_context_copy.push_new_context(context_hash_data[:name], command, context_hash_data[:identifier], context_hash_data[:shadow_entity]) if ((i+1) >= ac_size)
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
return active_context_copy, error_message, invalid_context
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def validate_command(clazz, current_context_clazz, command, active_context_copy=nil)
|
|
406
|
+
error_message = nil
|
|
407
|
+
invalid_context = ""
|
|
408
|
+
|
|
409
|
+
# if command class did not found or if command ends with '-'
|
|
410
|
+
if (clazz.nil? || command.match(/-$/))
|
|
411
|
+
error_message = "Context for '#{command}' could not be loaded.";
|
|
412
|
+
invalid_context = command
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# check if previous context support this one as a child
|
|
416
|
+
unless current_context_clazz.nil?
|
|
417
|
+
# valid child method is necessery to define parent-child relet.
|
|
418
|
+
if current_context_clazz.respond_to?(:valid_child?)
|
|
419
|
+
root_clazz = DTK::Shell::Context.get_command_class(active_context_copy.first_command_name)
|
|
420
|
+
all_children = root_clazz.all_children() + root_clazz.valid_children()
|
|
421
|
+
|
|
422
|
+
valid_all_children = (root_clazz != current_context_clazz) ? all_children.include?(command.to_sym) : true
|
|
423
|
+
unless (current_context_clazz.valid_child?(command) && valid_all_children)
|
|
424
|
+
|
|
425
|
+
error_message = "'#{command}' context is not valid."
|
|
426
|
+
invalid_context = command
|
|
427
|
+
|
|
428
|
+
if current_context_clazz.respond_to?(:invisible_context)
|
|
429
|
+
ic = current_context_clazz.invisible_context()
|
|
430
|
+
ic.each do |c|
|
|
431
|
+
if c.to_s.include?(command)
|
|
432
|
+
return nil, ""
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
else
|
|
438
|
+
error_message = "'#{command}' context is not valid."
|
|
439
|
+
invalid_context = command
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
return error_message, invalid_context
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
# hack: used just to avoid entering assembly/id/node or workspace/node context (remove when include this contexts again)
|
|
448
|
+
def is_restricted_context(first_c, args = [], tmp_active_context=nil)
|
|
449
|
+
entries = args.first.split(/\//)
|
|
450
|
+
invalid_context = ["workspace/node", "service/node"]
|
|
451
|
+
double_dots_count = DTK::Shell::ContextAux.count_double_dots(entries)
|
|
452
|
+
only_double_dots = entries.select{|e| !e.to_s.include?('..')}||[]
|
|
453
|
+
back_flag = false
|
|
454
|
+
|
|
455
|
+
last_from_current, message = nil, nil
|
|
456
|
+
unless (root? || double_dots_count==0 || !only_double_dots.empty?)
|
|
457
|
+
test_c = @previous_context
|
|
458
|
+
test_c.pop_context(double_dots_count)
|
|
459
|
+
last_from_current = test_c.last_context_name
|
|
460
|
+
back_flag = true
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
unless args.empty?
|
|
464
|
+
first_c ||= entries.first
|
|
465
|
+
last_c = last_from_current||entries.last
|
|
466
|
+
|
|
467
|
+
invalid_context.each do |ac|
|
|
468
|
+
if ac.eql?("#{first_c}/#{last_c}")
|
|
469
|
+
unless last_from_current
|
|
470
|
+
last_1, last_2 = entries.last(2)
|
|
471
|
+
if last_1.eql?(last_2)
|
|
472
|
+
args = entries.join('/')
|
|
473
|
+
return {:args => [args], :node_specific => true}
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
message = "'#{last_c}' context is not valid."
|
|
477
|
+
is_valid_id = check_for_id(first_c, last_c, tmp_active_context, args)
|
|
478
|
+
|
|
479
|
+
# if ../ to node context, add one more .. to go to previous context (assembly/id or workspace)
|
|
480
|
+
if back_flag
|
|
481
|
+
message = nil
|
|
482
|
+
entries << ".." if is_valid_id==false
|
|
483
|
+
else
|
|
484
|
+
if is_valid_id==false
|
|
485
|
+
entries.pop
|
|
486
|
+
else
|
|
487
|
+
message = nil
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
args = (entries.size<=1 ? entries : entries.join('/'))
|
|
492
|
+
args = args.is_a?(Array) ? args : [args]
|
|
493
|
+
if args.empty?
|
|
494
|
+
raise DTK::Client::DtkValidationError, message
|
|
495
|
+
else
|
|
496
|
+
return {:args => args, :message => message, :node_specific => true}
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
return {:args => args, :message => message}
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def check_for_id(context, command, tmp_active_context, args)
|
|
507
|
+
command_clazz = Context.get_command_class(context)
|
|
508
|
+
invisible_context = command_clazz.respond_to?(:invisible_context) ? command_clazz.invisible_context.map { |e| e.to_s } : []
|
|
509
|
+
entries = args.first.split(/\//)
|
|
510
|
+
|
|
511
|
+
entries = Context.check_for_sym_link(entries) if root?
|
|
512
|
+
unless invisible_context.empty?
|
|
513
|
+
if root?
|
|
514
|
+
tmp_active_context.push_new_context(entries[0], entries[0])
|
|
515
|
+
context_hash_data, error_message, invalid_context = validate_value(entries[0], entries[1], tmp_active_context)
|
|
516
|
+
|
|
517
|
+
return if error_message
|
|
518
|
+
tmp_active_context.push_new_context(context_hash_data[:name], entries[0], context_hash_data[:identifier])
|
|
519
|
+
context_hash_data, error_message, invalid_context = validate_value(command, command, tmp_active_context)
|
|
520
|
+
|
|
521
|
+
return if error_message
|
|
522
|
+
tmp_active_context.push_new_context(context_hash_data[:name], command, context_hash_data[:identifier])
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
node_ids = get_command_identifiers(invisible_context.first.to_s, tmp_active_context)
|
|
527
|
+
node_names = node_ids ? node_ids.collect { |e| e[:name] } : []
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
return node_names.include?(command)
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def validate_value(command, value, active_context_copy=nil)
|
|
536
|
+
context_hash_data = nil
|
|
537
|
+
invalid_context = ""
|
|
538
|
+
# check value
|
|
539
|
+
if value
|
|
540
|
+
context_hash_data = valid_id?(command, value, nil, active_context_copy)
|
|
541
|
+
unless context_hash_data
|
|
542
|
+
error_message = "Identifier '#{value}' is not valid."
|
|
543
|
+
# error_message = "Identifier '#{value}' for context '#{command}' is not valid";
|
|
544
|
+
invalid_context = value
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
return context_hash_data, error_message, invalid_context
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
# load context will load list of commands available for given command (passed)
|
|
552
|
+
# to method. Context is list of command available at current tier.
|
|
553
|
+
def load_context(command_name=nil)
|
|
554
|
+
# when switching to tier 2 we need to use command name from tier one
|
|
555
|
+
# e.g. cc library/public, we are caching context under library_1, library_2
|
|
556
|
+
# so getting context for 'public' will not work and we use than library
|
|
557
|
+
command_name = root? ? 'dtk' : @active_context.last_command_name
|
|
558
|
+
|
|
559
|
+
if @active_context.last_context_is_shadow_entity?
|
|
560
|
+
@current = ShadowEntity.resolve_tasks(@active_context.last_context)
|
|
561
|
+
else
|
|
562
|
+
# if there is no new context (current) we use old one
|
|
563
|
+
@current = current_context_task_names() || @current
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
client_commands = CLIENT_COMMANDS
|
|
567
|
+
client_commands.concat(DEV_COMMANDS) if DTK::Configuration.get(:development_mode)
|
|
568
|
+
|
|
569
|
+
# we add client commands
|
|
570
|
+
@current.concat(client_commands).sort!
|
|
571
|
+
|
|
572
|
+
# holder for commands to be used since we do not want to remember all of them
|
|
573
|
+
@context_commands = @current
|
|
574
|
+
|
|
575
|
+
# we load thor command class identifiers for autocomplete context list
|
|
576
|
+
command_context = get_command_identifiers(command_name)
|
|
577
|
+
|
|
578
|
+
command_name_list = command_context ? command_context.collect { |e| e[:name] } : []
|
|
579
|
+
@context_commands.concat(command_name_list) if current_command?
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
# logic behind context loading
|
|
583
|
+
#Readline.completer_word_break_characters=" "
|
|
584
|
+
Readline.completion_proc = proc { |input| dynamic_autocomplete_context(input, Readline.respond_to?("line_buffer") ? Readline.line_buffer : [])}
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
def push_context()
|
|
588
|
+
raise "DEV WE need to RE-IMPLEMENT this."
|
|
589
|
+
@current_path = @active_context.full_path()
|
|
590
|
+
@dirs.unshift(@current_path) unless @current_path.nil?
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# resets context
|
|
594
|
+
def reset
|
|
595
|
+
@active_context.clear
|
|
596
|
+
load_context()
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def revert_context()
|
|
600
|
+
if @previous_context
|
|
601
|
+
# swap 2 variables
|
|
602
|
+
@active_context, @previous_context = @previous_context, @active_context
|
|
603
|
+
end
|
|
604
|
+
load_context(active_context.last_context_name)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# when e.g assembly is deleted we want it to be removed from list without
|
|
608
|
+
# exiting dtk-shell
|
|
609
|
+
def reload_cached_tasks(command_name)
|
|
610
|
+
# we clear @current since this will be reloaded
|
|
611
|
+
@current = nil
|
|
612
|
+
|
|
613
|
+
load_context(command_name)
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
# gets current path for shell
|
|
617
|
+
def shell_prompt
|
|
618
|
+
return root? ? DTK_ROOT_PROMPT : "dtk:#{@active_context.full_path}>"
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def root_tasks
|
|
622
|
+
return @cached_tasks['dtk']
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
# returns true if context is on root at the moment
|
|
626
|
+
def root?
|
|
627
|
+
return @active_context.empty?
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
def current_command?
|
|
631
|
+
return @active_context.current_command?
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
def current_identifier?
|
|
635
|
+
return @active_context.current_identifier?
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
def current_alt_identifier?
|
|
639
|
+
return @active_context.current_alt_identifier?
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
# returns list of tasks for given command name
|
|
643
|
+
def current_context_task_names()
|
|
644
|
+
@cached_tasks.fetch(@active_context.get_task_cache_id(),[]).dup
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
# checks if method name is valid in current context
|
|
648
|
+
def method_valid?(method_name)
|
|
649
|
+
# validate method, see if we support given method in current tasks
|
|
650
|
+
(current_context_task_names() + ['help']).include?(method_name)
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
# adds command to current list of active commands
|
|
655
|
+
def push_to_active_context(context_name, entity_name, context_value = nil)
|
|
656
|
+
@active_context.push_new_context(context_name, entity_name, context_value)
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
# remove last active command, and returns it
|
|
660
|
+
def pop_from_active_context
|
|
661
|
+
return @active_context.pop_context
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
# calls 'valid_id?' method in Thor class to validate ID/NAME
|
|
665
|
+
def valid_id?(thor_command_name,value, override_context_params=nil, active_context_copy=nil)
|
|
666
|
+
|
|
667
|
+
command_clazz = Context.get_command_class(thor_command_name)
|
|
668
|
+
if command_clazz.list_method_supported?
|
|
669
|
+
if override_context_params
|
|
670
|
+
context_params = override_context_params
|
|
671
|
+
else
|
|
672
|
+
context_params = get_command_parameters(thor_command_name, [], active_context_copy)[2]
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
tmp = command_clazz.valid_id?(value, @conn, context_params)
|
|
676
|
+
return tmp
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
return nil
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
def get_ac_candidates(active_context_copy, readline_input, invalid_context, goes_from_root, line_buffer=[])
|
|
683
|
+
# helper indicator for case when there are more options in current context and cc command is not ended with '/'
|
|
684
|
+
cutoff_forcely = false
|
|
685
|
+
# input string segment used to filter results candidates
|
|
686
|
+
results_filter = (readline_input.match(/\/$/) && invalid_context.empty?) ? "" : readline_input.split("/").last
|
|
687
|
+
results_filter ||= ""
|
|
688
|
+
|
|
689
|
+
command_clazz = Context.get_command_class(active_context_copy.last_command_name)
|
|
690
|
+
extended_context_commands = nil
|
|
691
|
+
|
|
692
|
+
unless command_clazz.nil?
|
|
693
|
+
extended_context = command_clazz.respond_to?(:extended_context) ? command_clazz.extended_context() : {}
|
|
694
|
+
|
|
695
|
+
unless extended_context.empty?
|
|
696
|
+
extended_context = extended_context[:command]
|
|
697
|
+
extended_context.reject!{|k,v| k.to_s!=line_buffer} if extended_context
|
|
698
|
+
extended_context_commands = extended_context[line_buffer.to_sym] unless (line_buffer.empty? || extended_context.nil?)
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
if extended_context_commands
|
|
703
|
+
context_candidates = load_extended_context_commands(extended_context_commands, active_context_copy)
|
|
704
|
+
else
|
|
705
|
+
# If command does not end with '/' check if there are more than one result candidate for current context
|
|
706
|
+
if !readline_input.empty? && !readline_input.match(/\/$/) && invalid_context.empty? && !active_context_copy.empty?
|
|
707
|
+
context_list = active_context_copy.context_list
|
|
708
|
+
context_name = context_list.size == 1 ? nil : context_list[context_list.size-2] # if case when on 1st level, return root candidates
|
|
709
|
+
context_candidates = get_ac_candidates_for_context(context_name, active_context_copy)
|
|
710
|
+
cutoff_forcely = true
|
|
711
|
+
else
|
|
712
|
+
# If last context is command, load all identifiers, otherwise, load next possible context command; if no contexts, load root tasks
|
|
713
|
+
context_candidates = get_ac_candidates_for_context(active_context_copy.last_context(), active_context_copy)
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
# checking if results will contain context candidates based on input string segment
|
|
718
|
+
context_candidates = context_candidates.grep( /^#{Regexp.escape(results_filter)}/ )
|
|
719
|
+
|
|
720
|
+
# Show all context tasks if active context orignal and it's copy are on same context, and are not on root,
|
|
721
|
+
# and if readline has one split result indicating user is not going trough n-level, but possibly executing a task
|
|
722
|
+
task_candidates = []
|
|
723
|
+
|
|
724
|
+
#task_candidates = @context_commands if (active_context_copy.last_context_name() == @active_context.last_context_name() && !active_context_copy.empty?)
|
|
725
|
+
task_candidates = @context_commands if (active_context_copy.last_context_name() == @active_context.last_context_name() && !active_context_copy.empty? && readline_input.split("/").size <= 1)
|
|
726
|
+
|
|
727
|
+
# create results object filtered by user input segment (results_filter)
|
|
728
|
+
task_candidates = task_candidates.grep( /^#{Regexp.escape(results_filter)}/ )
|
|
729
|
+
|
|
730
|
+
# autocomplete candidates are both context and task candidates; remove duplicates in results
|
|
731
|
+
results = context_candidates
|
|
732
|
+
|
|
733
|
+
# if command is 'cc/cd/pushc' displat only context candidates
|
|
734
|
+
if line_buffer.empty?
|
|
735
|
+
results += task_candidates
|
|
736
|
+
else
|
|
737
|
+
is_cc = line_buffer.split(' ')
|
|
738
|
+
results += task_candidates unless (IDENTIFIERS_ONLY.include?(is_cc.first) || extended_context_commands)
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
# remove duplicate context or task candidates
|
|
742
|
+
results.uniq!
|
|
743
|
+
|
|
744
|
+
# Send system beep if there are no candidates
|
|
745
|
+
if results.empty?
|
|
746
|
+
print "\a"
|
|
747
|
+
return []
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
# default value of input user string
|
|
751
|
+
input_context_path = readline_input
|
|
752
|
+
|
|
753
|
+
# cut off last context if it is leftover (invalid_context),
|
|
754
|
+
# or if last context is not finished with '/' and it can have more than option for current context
|
|
755
|
+
# i.e. dtk> cc assembly - have 2 candidates: 'assembly' and 'assembly-template'
|
|
756
|
+
if !invalid_context.empty? || cutoff_forcely
|
|
757
|
+
start_index = goes_from_root ? 1 : 0 # if it starts with / don't take first element after split
|
|
758
|
+
input_context_path = readline_input.split("/")[start_index.. -2].join("/")
|
|
759
|
+
input_context_path = input_context_path + "/" unless input_context_path.empty?
|
|
760
|
+
input_context_path = "/" + input_context_path if goes_from_root
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
# Augment input string with candidates to satisfy thor
|
|
764
|
+
results = results.map { |element| (input_context_path + element) }
|
|
765
|
+
|
|
766
|
+
# If there is only one candidate, and candidate is not task operation
|
|
767
|
+
#return (results.size() == 1 && !context_candidates.empty?) ? (results.first + "/") : results
|
|
768
|
+
return results
|
|
769
|
+
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
def get_ac_candidates_for_context(context, active_context_copy)
|
|
773
|
+
# If last context is command, load all identifiers, otherwise, load next possible context command; if no contexts, load root tasks
|
|
774
|
+
if context
|
|
775
|
+
if context.is_command?
|
|
776
|
+
command_identifiers = get_command_identifiers(context.name, active_context_copy)
|
|
777
|
+
n_level_ac_candidates = command_identifiers ? command_identifiers.collect { |e| e[:name] } : []
|
|
778
|
+
else
|
|
779
|
+
command_clazz = Context.get_command_class(active_context_copy.last_command_name)
|
|
780
|
+
root_clazz = DTK::Shell::Context.get_command_class(active_context_copy.first_command_name)
|
|
781
|
+
valid_all_children = (root_clazz != command_clazz) ? (root_clazz.all_children() + root_clazz.valid_children()) : []
|
|
782
|
+
n_level_ac_candidates = command_clazz.respond_to?(:valid_children) ? command_clazz.valid_children.map { |e| e.to_s } : []
|
|
783
|
+
|
|
784
|
+
n_level_ac_candidates.select {|v| valid_all_children.include?(v.to_sym)} unless valid_all_children.empty?
|
|
785
|
+
invisible_context = command_clazz.respond_to?(:invisible_context) ? command_clazz.invisible_context.map { |e| e.to_s } : []
|
|
786
|
+
|
|
787
|
+
unless invisible_context.empty?
|
|
788
|
+
node_ids = get_command_identifiers(invisible_context.first.to_s, active_context_copy)
|
|
789
|
+
node_names = node_ids ? node_ids.collect { |e| e[:name] } : []
|
|
790
|
+
|
|
791
|
+
n_level_ac_candidates.concat(node_names)
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
# restrict autocomple to utils subcontext in service/service_name/node_group only
|
|
795
|
+
if active_context_copy.last_context_is_shadow_entity? && active_context_copy.last_context().shadow_entity().eql?('node_group')
|
|
796
|
+
n_level_ac_candidates.delete('utils')
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
n_level_ac_candidates
|
|
800
|
+
end
|
|
801
|
+
else
|
|
802
|
+
n_level_ac_candidates = ROOT_TASKS
|
|
803
|
+
end
|
|
804
|
+
end
|
|
805
|
+
|
|
806
|
+
# get class identifiers for given thor command, returns array of identifiers
|
|
807
|
+
def get_command_identifiers(thor_command_name, active_context_copy=nil)
|
|
808
|
+
begin
|
|
809
|
+
command_clazz = Context.get_command_class(thor_command_name)
|
|
810
|
+
|
|
811
|
+
if command_clazz && command_clazz.list_method_supported?
|
|
812
|
+
# take just hashed arguemnts from multi return method
|
|
813
|
+
hashed_args = get_command_parameters(thor_command_name, [], active_context_copy)[2]
|
|
814
|
+
return command_clazz.get_identifiers(@conn, hashed_args)
|
|
815
|
+
end
|
|
816
|
+
rescue DTK::Client::DtkValidationError => e
|
|
817
|
+
# TODO Check if handling needed. Error should happen only when autocomplete ID search illigal
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
return []
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
def load_extended_context_commands(extended_context_commands, active_context_copy)
|
|
824
|
+
candidates = []
|
|
825
|
+
entity_name = active_context_copy.last_context
|
|
826
|
+
parent_entity = active_context_copy.context_list[1]
|
|
827
|
+
response_ruby_obj = nil
|
|
828
|
+
|
|
829
|
+
field = extended_context_commands[:field]||'display_name'
|
|
830
|
+
endpoint = extended_context_commands[:endpoint]
|
|
831
|
+
url = extended_context_commands[:url]
|
|
832
|
+
opts = extended_context_commands[:opts]||{}
|
|
833
|
+
|
|
834
|
+
if entity_name.is_identifier?
|
|
835
|
+
if (parent_entity && parent_entity.is_identifier? && (parent_entity != entity_name))
|
|
836
|
+
parent_id_label = "#{endpoint}_id".to_sym
|
|
837
|
+
parent_id = parent_entity.identifier
|
|
838
|
+
opts[parent_id_label] = parent_id
|
|
839
|
+
id_label = "#{entity_name.entity}_id".to_sym
|
|
840
|
+
end
|
|
841
|
+
|
|
842
|
+
id_label ||= "#{endpoint}_id".to_sym
|
|
843
|
+
id = entity_name.identifier
|
|
844
|
+
opts[id_label] = id
|
|
845
|
+
|
|
846
|
+
response_ruby_obj = DTK::Client::CommandBaseThor.get_cached_response(endpoint, url, opts)
|
|
847
|
+
# response_ruby_obj = post rest_url(url), opts
|
|
848
|
+
else
|
|
849
|
+
response_ruby_obj = DTK::Client::CommandBaseThor.get_cached_response(endpoint, url, opts)
|
|
850
|
+
# response_ruby_obj = post rest_url(url), opts
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
if response_ruby_obj && response_ruby_obj.ok?
|
|
854
|
+
response_ruby_obj.data.each do |d|
|
|
855
|
+
candidates << d[field]
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
candidates
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
def get_dtk_command_parameters(entity_name, args)
|
|
863
|
+
method_name, entity_name_id = nil, nil
|
|
864
|
+
context_params = ContextParams.new
|
|
865
|
+
|
|
866
|
+
if (ROOT_TASKS + ['dtk']).include?(entity_name)
|
|
867
|
+
Context.require_command_class(entity_name)
|
|
868
|
+
available_tasks = Context.get_command_class(entity_name).task_names
|
|
869
|
+
if available_tasks.include?(args.first)
|
|
870
|
+
method_name = args.shift
|
|
871
|
+
else
|
|
872
|
+
entity_name_id = args.shift
|
|
873
|
+
method_name = args.shift
|
|
874
|
+
end
|
|
875
|
+
else
|
|
876
|
+
raise DTK::Client::DtkError, "Could not find context \"#{entity_name}\"."
|
|
877
|
+
end
|
|
878
|
+
|
|
879
|
+
# if no method specified use help
|
|
880
|
+
method_name ||= 'help'
|
|
881
|
+
|
|
882
|
+
context_params.add_context_to_params(entity_name, entity_name)
|
|
883
|
+
|
|
884
|
+
if entity_name_id
|
|
885
|
+
identifier_response = valid_id?(entity_name, entity_name_id, context_params)
|
|
886
|
+
if identifier_response
|
|
887
|
+
context_params.add_context_to_params(identifier_response[:name], entity_name, identifier_response[:identifier])
|
|
888
|
+
else
|
|
889
|
+
raise DTK::Client::DtkError, "Could not validate identifier \"#{entity_name_id}\"."
|
|
890
|
+
end
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
# extract thor options
|
|
894
|
+
clazz = Context.get_command_class(entity_name)
|
|
895
|
+
options = Context.get_thor_options(clazz, method_name) unless clazz.nil?
|
|
896
|
+
args, thor_options, invalid_options = Context.parse_thor_options(args, options)
|
|
897
|
+
context_params.method_arguments = args
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
unless available_tasks.include?(method_name)
|
|
901
|
+
raise DTK::Client::DtkError, "Could not find task \"#{method_name}\"."
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
raise DTK::Client::DtkValidationError, "Option '#{invalid_options.first}' is not valid for current command!" unless invalid_options.empty?
|
|
905
|
+
|
|
906
|
+
return entity_name, method_name, context_params, thor_options
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
#
|
|
910
|
+
# We use enrich data to help when using dynamic_context loading, Readline.completition_proc
|
|
911
|
+
# See bellow for more details
|
|
912
|
+
#
|
|
913
|
+
def get_command_parameters(cmd,args, active_context_copy=nil)
|
|
914
|
+
# To support autocomplete feature, temp active context may be forwarded into method
|
|
915
|
+
active_context_copy = @active_context unless active_context_copy
|
|
916
|
+
|
|
917
|
+
entity_name, method_name, option_types = nil, nil, nil
|
|
918
|
+
|
|
919
|
+
context_params = ContextParams.new
|
|
920
|
+
|
|
921
|
+
if root? && !args.empty?
|
|
922
|
+
# this means that somebody is calling command with assembly/.. method_name
|
|
923
|
+
entity_name = cmd
|
|
924
|
+
method_name = args.shift
|
|
925
|
+
else
|
|
926
|
+
context_params.current_context = active_context_copy.clone_me
|
|
927
|
+
entity_name = active_context_copy.name_list.first
|
|
928
|
+
entity_name ||= "dtk"
|
|
929
|
+
method_name = cmd
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
# extract thor options
|
|
933
|
+
clazz = Context.get_command_class(entity_name)
|
|
934
|
+
current_context_command = active_context_copy.last_command_name
|
|
935
|
+
|
|
936
|
+
if ((current_context_command != entity_name) && !current_context_command.eql?("utils"))
|
|
937
|
+
current_context_clazz = Context.get_command_class(current_context_command)
|
|
938
|
+
options = Context.get_thor_options(current_context_clazz, cmd) if current_context_clazz
|
|
939
|
+
else
|
|
940
|
+
options = Context.get_thor_options(clazz, cmd) if clazz
|
|
941
|
+
end
|
|
942
|
+
|
|
943
|
+
# set rest of arguments as method options
|
|
944
|
+
args, thor_options, invalid_options = Context.parse_thor_options(args, options)
|
|
945
|
+
context_params.method_arguments = args
|
|
946
|
+
|
|
947
|
+
return entity_name, method_name, context_params, thor_options, invalid_options
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
private
|
|
951
|
+
|
|
952
|
+
#
|
|
953
|
+
# method takes parameters that can hold specific thor options
|
|
954
|
+
#
|
|
955
|
+
def self.parse_thor_options(args, options=nil)
|
|
956
|
+
type, invalid_options = nil, []
|
|
957
|
+
|
|
958
|
+
# options to handle thor options -m MESSAGE and --list
|
|
959
|
+
options_param_args = []
|
|
960
|
+
args.each_with_index do |e,i|
|
|
961
|
+
if (e.match(/^\-[a-zA-Z]?/) || e.match(/^\-\-/))
|
|
962
|
+
type = Context.get_option_type(options, e) unless options.nil?
|
|
963
|
+
if type.nil?
|
|
964
|
+
options_param_args = nil
|
|
965
|
+
invalid_options << e
|
|
966
|
+
break
|
|
967
|
+
else
|
|
968
|
+
options_param_args << e
|
|
969
|
+
options_param_args << args[i+1] unless type == :boolean
|
|
970
|
+
end
|
|
971
|
+
end
|
|
972
|
+
end
|
|
973
|
+
|
|
974
|
+
# remove thor_options but only once
|
|
975
|
+
args = Client::CommonUtil.substract_array_once(args, options_param_args, true) unless options_param_args.nil?
|
|
976
|
+
|
|
977
|
+
return args, options_param_args, invalid_options
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
def self.get_thor_options(clazz, command)
|
|
981
|
+
command = command.gsub('-','_')
|
|
982
|
+
options = nil
|
|
983
|
+
options = clazz.all_tasks[command].options.collect{|k,v|{:alias=>v.aliases,:name=>v.name,:type=>v.type,:switch=>v.switch_name}} unless clazz.all_tasks[command].nil?
|
|
984
|
+
|
|
985
|
+
return options
|
|
986
|
+
end
|
|
987
|
+
|
|
988
|
+
def self.get_option_type(options, option)
|
|
989
|
+
@ret = nil
|
|
990
|
+
|
|
991
|
+
options.each do |opt|
|
|
992
|
+
@ret = opt[:type] if(opt[:alias].first == option || opt[:switch] == option)
|
|
993
|
+
end
|
|
994
|
+
|
|
995
|
+
return @ret
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
def self.command_to_id_sym(command_name)
|
|
999
|
+
"#{command_name}_id".gsub(/\-/,'_').to_sym
|
|
1000
|
+
end
|
|
1001
|
+
|
|
1002
|
+
def get_latest_tasks(command_name)
|
|
1003
|
+
file_name = command_name.gsub('-','_')
|
|
1004
|
+
cached_for_command = Context.get_command_class(file_name).tiered_task_names
|
|
1005
|
+
|
|
1006
|
+
# gets thor command class and then all the task names for that command
|
|
1007
|
+
@cached_tasks.merge!(cached_for_command)
|
|
1008
|
+
end
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
# PART OF THE CODE USED FOR WORKING WITH DTK::Shell HISTORY
|
|
1012
|
+
public
|
|
1013
|
+
|
|
1014
|
+
# this file loads sessions history
|
|
1015
|
+
def self.load_session_history()
|
|
1016
|
+
unless is_there_history_file()
|
|
1017
|
+
puts "[INFO] History file is missing, shell history will be disabled. To enable it create file: '#{HISTORY_LOCATION}'"
|
|
1018
|
+
return []
|
|
1019
|
+
end
|
|
1020
|
+
|
|
1021
|
+
content = File.open(HISTORY_LOCATION,'r').read
|
|
1022
|
+
return (content.empty? ? [] : JSON.parse(content))
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
def self.save_session_history(array_of_commands)
|
|
1026
|
+
return [] unless is_there_history_file()
|
|
1027
|
+
# we filter the list to remove neighbour duplicates
|
|
1028
|
+
filtered_commands = []
|
|
1029
|
+
array_of_commands.each_with_index do |a,i|
|
|
1030
|
+
filtered_commands << a if (a != array_of_commands[i+1] && is_allowed_command?(a))
|
|
1031
|
+
end
|
|
1032
|
+
|
|
1033
|
+
# make sure we only save up to 'COMMAND_HISTORY_LIMIT' commands
|
|
1034
|
+
if filtered_commands.size > COMMAND_HISTORY_LIMIT
|
|
1035
|
+
filtered_commands = filtered_commands[-COMMAND_HISTORY_LIMIT,COMMAND_HISTORY_LIMIT+1]
|
|
1036
|
+
end
|
|
1037
|
+
|
|
1038
|
+
File.open(HISTORY_LOCATION,'w') { |f| f.write(filtered_commands.to_json) }
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1041
|
+
private
|
|
1042
|
+
|
|
1043
|
+
# list of commands that should be excluded from history
|
|
1044
|
+
EXCLUDE_COMMAND_LIST = ['create-provider','create-ec2-provider','create-physical-provider']
|
|
1045
|
+
|
|
1046
|
+
def self.is_allowed_command?(full_command_entry)
|
|
1047
|
+
found = EXCLUDE_COMMAND_LIST.find { |cmd| full_command_entry.include?(cmd) }
|
|
1048
|
+
found.nil?
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
def self.is_there_history_file()
|
|
1052
|
+
unless File.exists? HISTORY_LOCATION
|
|
1053
|
+
begin
|
|
1054
|
+
File.open(HISTORY_LOCATION, 'w') {}
|
|
1055
|
+
return true
|
|
1056
|
+
rescue
|
|
1057
|
+
return false
|
|
1058
|
+
end
|
|
1059
|
+
#DtkLogger.instance.info "[INFO] Session shell history has been disabled, please create file '#{HISTORY_LOCATION}' to enable it."
|
|
1060
|
+
end
|
|
1061
|
+
return true
|
|
1062
|
+
end
|
|
1063
|
+
end
|
|
1064
|
+
end
|
|
1065
|
+
end
|