dtk-client 0.7.4.1 → 0.7.5

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/bin/dtk +10 -3
  3. data/bin/dtk-shell +1 -1
  4. data/lib/command_helpers/git_repo.rb +26 -20
  5. data/lib/command_helpers/jenkins_client.rb +4 -3
  6. data/lib/command_helpers/service_importer.rb +37 -25
  7. data/lib/commands.rb +2 -2
  8. data/lib/commands/common/thor/assembly_workspace.rb +185 -173
  9. data/lib/commands/common/thor/base_command_helper.rb +42 -0
  10. data/lib/commands/common/thor/clone.rb +1 -1
  11. data/lib/commands/common/thor/module.rb +37 -58
  12. data/lib/commands/common/thor/module/import.rb +1 -1
  13. data/lib/commands/common/thor/pull_from_remote.rb +7 -12
  14. data/lib/commands/common/thor/purge_clone.rb +1 -1
  15. data/lib/commands/common/thor/push_clone_changes.rb +3 -1
  16. data/lib/commands/common/thor/task_status.rb +52 -75
  17. data/lib/commands/common/thor/task_status/refresh_mode.rb +56 -0
  18. data/lib/commands/common/thor/task_status/snapshot_mode.rb +11 -0
  19. data/lib/commands/common/thor/task_status/stream_mode.rb +31 -0
  20. data/lib/commands/common/thor/task_status/stream_mode/element.rb +90 -0
  21. data/lib/commands/common/thor/task_status/stream_mode/element/no_results.rb +10 -0
  22. data/lib/commands/common/thor/task_status/stream_mode/element/render.rb +88 -0
  23. data/lib/commands/common/thor/task_status/stream_mode/element/stage.rb +13 -0
  24. data/lib/commands/common/thor/task_status/stream_mode/element/task_end.rb +10 -0
  25. data/lib/commands/common/thor/task_status/stream_mode/element/task_start.rb +10 -0
  26. data/lib/commands/thor/account.rb +10 -8
  27. data/lib/commands/thor/assembly.rb +9 -2
  28. data/lib/commands/thor/component_module.rb +0 -52
  29. data/lib/commands/thor/library.rb +1 -0
  30. data/lib/commands/thor/node.rb +1 -36
  31. data/lib/commands/thor/node_template.rb +4 -47
  32. data/lib/commands/thor/service.rb +57 -46
  33. data/lib/commands/thor/service_module.rb +2 -49
  34. data/lib/commands/thor/target.rb +7 -7
  35. data/lib/commands/thor/workspace.rb +44 -27
  36. data/lib/context_router.rb +4 -0
  37. data/lib/core.rb +71 -99
  38. data/lib/domain/response.rb +9 -0
  39. data/lib/domain/response/error_handler.rb +61 -0
  40. data/lib/dtk-client/version.rb +1 -1
  41. data/lib/dtk_client.rb +14 -0
  42. data/lib/dtk_error.rb +91 -0
  43. data/lib/error.rb +3 -9
  44. data/lib/execute/cli_pure/cli_rerouter.rb +82 -0
  45. data/lib/parser/adapters/thor.rb +3 -0
  46. data/lib/shell.rb +2 -1
  47. data/lib/shell/domain/context_params.rb +2 -0
  48. data/lib/util/console.rb +1 -1
  49. data/lib/util/os_util.rb +1 -0
  50. data/lib/util/remote_dependency_util.rb +20 -3
  51. data/lib/view_processor/table_print.rb +7 -25
  52. metadata +17 -5
  53. data/lib/commands/common/thor/test_action_agent.rb +0 -39
  54. data/lib/commands/thor/repo.rb +0 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a7875186712bb106f9fbbf11458c702e66041ae
4
- data.tar.gz: 23996874f5fb73b451fab137e6157252addb9224
3
+ metadata.gz: 90044fb0aeac2ab716a8bbad16eab81ba09b177f
4
+ data.tar.gz: af29727daadaa126bc8b3ea39e8515b49e8ace37
5
5
  SHA512:
6
- metadata.gz: 6b938efca3539c8ba284580cfc4f747c179e45939abe5025f479c696fa227618c4c98731e8b493f77834ed396c31b63f81230b3e324b8618be907da363efd3ea
7
- data.tar.gz: f428e0857dbea981c780d12bc8e4fb7cf31d2a249282302d29d358917364a52230426c9dcf6802e85289b9859c7cf15f223b194a7a7a6dbb2a21b2aacf0292f8
6
+ metadata.gz: 4d40f5739a3921835fb2c38cad3428134f807091327c50eb40c7066cc1fa8168dc1e8c60da2e26fd9c7ceb6c6940f6d81e74001717a6bd0c0f5335bb3abbf7ae
7
+ data.tar.gz: e2ab0c0121f8034da2262595f5f7caabe81d6cd85a37a72b7fe6983d480ea89503779e8bb2aef0e5efc4d8d512d8088929db0ec4f634a33a920444e0f2c77326
data/bin/dtk CHANGED
@@ -15,9 +15,11 @@ require File.expand_path('../lib/shell/domain/shadow_entity', File.dirname(__FIL
15
15
  require File.expand_path('../lib/commands/thor/account', File.dirname(__FILE__))
16
16
  require File.expand_path('../lib/shell/parse_monkey_patch', File.dirname(__FILE__))
17
17
  require File.expand_path('../lib/shell/help_monkey_patch', File.dirname(__FILE__))
18
+ require File.expand_path('../lib/execute/cli_pure/cli_rerouter', File.dirname(__FILE__))
18
19
 
19
20
 
20
21
  require 'shellwords'
22
+ require 'json'
21
23
 
22
24
  $: << "/usr/lib/ruby/1.8/" #TODO: put in to get around path problem in rvm 1.9.2 environment
23
25
 
@@ -47,9 +49,14 @@ end
47
49
  context = DTK::Shell::Context.new(true)
48
50
 
49
51
  begin
50
- entity_name, method_name, context_params, thor_options = context.get_dtk_command_parameters(entity_name, args)
51
-
52
- top_level_execute(entity_name, method_name, context_params, thor_options, false)
52
+ if ::DTK::CLIRerouter.is_candidate?(entity_name, args)
53
+ response_obj = ::DTK::CLIRerouter.new(entity_name, args).run()
54
+ puts response_obj.to_json
55
+ else
56
+ # default execution
57
+ entity_name, method_name, context_params, thor_options = context.get_dtk_command_parameters(entity_name, args)
58
+ top_level_execute(entity_name, method_name, context_params, thor_options, false)
59
+ end
53
60
  rescue DTK::Client::DtkError => e
54
61
  DtkLogger.instance.error(e.message, true)
55
62
  rescue Exception => e
data/bin/dtk-shell CHANGED
@@ -11,4 +11,4 @@ config_exists = ::DTK::Client::Configurator.check_config_exists
11
11
  # check if .add_direct_access file exists, if not then add direct access and create .add_direct_access file
12
12
  resolve_direct_access(::DTK::Client::Configurator.check_direct_access, config_exists)
13
13
 
14
- run_shell_command()
14
+ run_shell_command()
@@ -118,6 +118,7 @@ module DTK; module Client; class CommandHelper
118
118
  full_module_name = full_module_name(module_name,opts)
119
119
  repo_dir = local_repo_dir(type,full_module_name,opts[:version],opts)
120
120
  repo = create(repo_dir,opts[:local_branch])
121
+ opts.merge!(:full_module_name => full_module_name)
121
122
  response = pull_repo_changes_aux(repo,opts)
122
123
  response
123
124
  end
@@ -446,46 +447,51 @@ module DTK; module Client; class CommandHelper
446
447
  { "diffs" => (diffs[:diffs]||"").to_s, "status" => repo.local_summary() }
447
448
  end
448
449
 
449
- def pull_repo_changes_aux(repo,opts={})
450
+ def pull_repo_changes_aux(repo, opts = {})
450
451
  diffs = DiffSummary.new()
451
452
  hard_reset = opts[:hard_reset] || opts[:force]
452
453
 
453
454
  # default commit in case it is needed
454
- repo.stage_and_commit("Commit prior to pull from remote") if repo.changed?
455
+ repo.stage_and_commit('Commit prior to pull from remote') if repo.changed?
455
456
 
456
457
  if commit_sha = opts[:commit_sha]
457
- #no op if at commit_sha
458
+ # no op if at commit_sha
458
459
  return diffs if (commit_sha == repo.head_commit_sha()) && !hard_reset
459
460
  end
460
461
 
461
- if opts[:remote_repo] and opts[:remote_repo_url]
462
- repo.add_remote(opts[:remote_repo],opts[:remote_repo_url])
462
+ if opts[:remote_repo] && opts[:remote_repo_url]
463
+ repo.add_remote(opts[:remote_repo], opts[:remote_repo_url])
463
464
  end
464
465
 
465
466
  repo.fetch(remote(opts[:remote_repo]))
466
467
  local_branch = repo.local_branch_name
467
- remote_branch_ref = remote_branch_ref(local_branch,opts)
468
+ remote_branch_ref = remote_branch_ref(local_branch, opts)
468
469
 
469
470
  if hard_reset
470
- diffs = DiffSummary.diff(repo,local_branch, remote_branch_ref)
471
+ diffs = DiffSummary.diff(repo, local_branch, remote_branch_ref)
471
472
  repo.merge_theirs(remote_branch_ref)
472
- return({:diffs => diffs, :commit_sha => repo.head_commit_sha()})
473
+ return({ :diffs => diffs, :commit_sha => repo.head_commit_sha() })
473
474
  end
474
475
 
475
- #check if merge needed
476
- merge_rel = repo.merge_relationship(:remote_branch,remote_branch_ref)
476
+ # check if merge needed
477
+ merge_rel = repo.merge_relationship(:remote_branch, remote_branch_ref)
477
478
  if merge_rel == :equal
478
479
  { :diffs => diffs, :commit_sha => repo.head_commit_sha() }
479
- elsif [:branchpoint,:local_ahead].include?(merge_rel)
480
- raise ErrorUsage.new("Unable to do fast-forward merge. You can use --force but all changes in the service instance will be lost") unless opts[:force]
481
- # TODO: right now just wiping out what is in repo
482
- diffs = DiffSummary.diff(repo,local_branch, remote_branch_ref)
483
- repo.merge_theirs(remote_branch_ref)
484
- { :diffs => diffs, :commit_sha => repo.head_commit_sha() }
480
+ elsif [:branchpoint, :local_ahead].include?(merge_rel)
481
+ if opts[:force]
482
+ # TODO: right now just wiping out what is in repo
483
+ diffs = DiffSummary.diff(repo, local_branch, remote_branch_ref)
484
+ repo.merge_theirs(remote_branch_ref)
485
+ { :diffs => diffs, :commit_sha => repo.head_commit_sha() }
486
+ elsif opts[:ignore_dependency_merge_conflict]
487
+ { :diffs => diffs, :commit_sha => repo.head_commit_sha(), :custom_message => "Unable to do fast-forward merge. You can go to '#{opts[:full_module_name]}' and pull with --force option but all changes will be lost." }
488
+ else
489
+ raise Error.new('Unable to do fast-forward merge. You can use --force but all changes will be lost.')
490
+ end
485
491
  elsif merge_rel == :local_behind
486
- #see if any diffs between fetched remote and local branch
487
- #this has be done after commit
488
- diffs = DiffSummary.diff(repo,local_branch, remote_branch_ref)
492
+ # see if any diffs between fetched remote and local branch
493
+ # this has be done after commit
494
+ diffs = DiffSummary.diff(repo, local_branch, remote_branch_ref)
489
495
  return diffs unless diffs.any_diffs?()
490
496
 
491
497
  begin
@@ -494,7 +500,7 @@ module DTK; module Client; class CommandHelper
494
500
  puts e
495
501
  end
496
502
 
497
- if commit_sha and commit_sha != repo.head_commit_sha()
503
+ if commit_sha && commit_sha != repo.head_commit_sha()
498
504
  raise Error.new("Git synchronization problem: expected local head to have sha (#{commit_sha})")
499
505
  end
500
506
 
@@ -1,4 +1,5 @@
1
- require 'jenkins-client'
1
+ # TODO: Marked for removal [Haris] - Do we need this?
2
+ require 'jenkins-client'
2
3
  module DTK; module Client
3
4
  class JenkinsClient
4
5
  require File.expand_path('jenkins_client/config_xml', File.dirname(__FILE__))
@@ -58,7 +59,7 @@ module DTK; module Client
58
59
  def create_job(job_name,config_xml_contents)
59
60
  ::Jenkins::Client::Job.create(job_name, config_xml_contents)
60
61
  end
61
-
62
+
62
63
  def get_info()
63
64
  get('api/json')
64
65
  end
@@ -86,6 +87,6 @@ module DTK; module Client
86
87
  end
87
88
  end
88
89
  end; end
89
-
90
+
90
91
 
91
92
 
@@ -18,10 +18,10 @@ module DTK::Client
18
18
  ##
19
19
  # Method will trigger pull from dtkn for each existing module
20
20
  #
21
- def trigger_module_auto_pull(required_modules, force = false)
21
+ def trigger_module_auto_pull(required_modules, opts = {})
22
22
  return if required_modules.empty?
23
23
 
24
- if force || Console.confirmation_prompt("Do you want to update in addition to this module its dependent modules from the catalog?")
24
+ if opts[:force] || Console.confirmation_prompt("Do you want to update in addition to this module its dependent modules from the catalog?")
25
25
  required_modules.each do |r_module|
26
26
  module_name = full_module_name(r_module)
27
27
  module_type = r_module['type']
@@ -31,46 +31,56 @@ module DTK::Client
31
31
  new_context_params = DTK::Shell::ContextParams.new
32
32
  new_context_params.add_context_to_params(module_type, module_type)
33
33
  new_context_params.add_context_name_to_params(module_type, module_type, module_name)
34
- new_context_params.forward_options( { :skip_recursive_pull => true })
34
+
35
+ forwarded_opts = { skip_recursive_pull: true, ignore_dependency_merge_conflict: true }
36
+ forwarded_opts.merge!(:do_not_raise => true) if opts[:do_not_raise]
37
+ new_context_params.forward_options(forwarded_opts)
35
38
 
36
39
  response = ContextRouter.routeTask(module_type, "pull_dtkn", new_context_params, @conn)
37
40
 
38
- raise DTK::Client::DtkError, response.error_message unless response.ok?
41
+ unless response.ok?
42
+ if opts[:do_not_raise]
43
+ OsUtil.print("#{response.error_message}", :red)
44
+ else
45
+ raise DTK::Client::DtkError, response.error_message
46
+ end
47
+ end
39
48
  end
40
49
 
41
- print "Resuming pull ... " unless force
50
+ print "Resuming pull ... " unless opts[:force]
42
51
  end
43
52
  end
44
53
 
45
-
46
54
  ##
47
55
  # Method will trigger import for each missing module component
48
56
  #
49
- def trigger_module_auto_import(modules_to_import, required_modules, opts={})
50
- puts "Auto-importing missing module(s)"
51
- update_all, update_none = false, false
57
+ def trigger_module_auto_import(modules_to_import, required_modules, opts = {})
58
+ puts 'Auto-installing missing module(s)'
59
+ update_all = false
60
+ update_none = false
52
61
 
53
62
  # Print out or update installed modules from catalog
54
63
  required_modules.each do |r_module|
55
64
  module_name = full_module_name(r_module)
56
65
  module_type = r_module['type']
57
66
 
58
- print "Using #{module_type.gsub('_',' ')} '#{module_name}'\n"
67
+ print "Using #{module_type.gsub('_', ' ')} '#{module_name}'\n"
59
68
  next if update_none || opts[:update_none]
60
69
 
61
70
  if update_all
62
- trigger_module_auto_pull([r_module], true)
71
+ trigger_module_auto_pull([r_module], :force => true, :do_not_raise => true)
63
72
  else
64
- update = Console.confirmation_prompt_additional_options("Do you want to update dependent #{module_type.gsub('_',' ')} '#{module_name}' from the catalog?", ['all', 'none'])
73
+ options = required_modules.size > 1 ? %w(all none) : []
74
+ update = Console.confirmation_prompt_additional_options("Do you want to update dependent #{module_type.gsub('_', ' ')} '#{module_name}' from the catalog?", options)
65
75
  next unless update
66
76
 
67
77
  if update.to_s.eql?('all')
68
78
  update_all = true
69
- trigger_module_auto_pull([r_module], true)
79
+ trigger_module_auto_pull([r_module], :force => true, :do_not_raise => true)
70
80
  elsif update.to_s.eql?('none')
71
81
  update_none = true
72
82
  else
73
- trigger_module_auto_pull([r_module], true)
83
+ trigger_module_auto_pull([r_module], :force => true, :do_not_raise => true)
74
84
  end
75
85
  end
76
86
  end
@@ -84,25 +94,27 @@ module DTK::Client
84
94
  module_url = m_module['module_url']
85
95
 
86
96
  # descriptive message
87
- import_msg = "Importing #{module_type.gsub('_',' ')} '#{module_name}'"
97
+ importing = module_url ? "Importing" : "Installing"
98
+ import_msg = "#{importing} #{module_type.gsub('_', ' ')} '#{module_name}'"
88
99
  import_msg += " from git source #{module_url}" if module_url
89
100
  print "#{import_msg} ... "
90
101
 
91
- unless module_url
102
+ if module_url
103
+ # import from Git source
104
+ new_context_params = ::DTK::Shell::ContextParams.new([module_url, module_name])
105
+ new_context_params.forward_options(:internal_trigger => true)
106
+ response = ContextRouter.routeTask(module_type, 'import_git', new_context_params, @conn)
107
+ else
92
108
  # import from Repo Manager
93
109
  new_context_params = ::DTK::Shell::ContextParams.new([module_name])
94
110
  new_context_params.override_method_argument!('option_2', m_module['version'])
95
- new_context_params.forward_options( { :skip_cloning => false, :skip_auto_install => true, :module_type => module_type}).merge!(opts)
96
- response = ContextRouter.routeTask(module_type, "install", new_context_params, @conn)
97
- else
98
- # import from Git source
99
- new_context_params = ::DTK::Shell::ContextParams.new([module_url, module_name])
100
- new_context_params.forward_options( { :internal_trigger => true })
101
- response = ContextRouter.routeTask(module_type, "import_git", new_context_params, @conn)
111
+ new_context_params.forward_options(:skip_cloning => false, :skip_auto_install => true, :module_type => module_type).merge!(opts)
112
+ response = ContextRouter.routeTask(module_type, 'install', new_context_params, @conn)
102
113
  end
103
114
 
104
- puts(response.data(:does_not_exist) ? response.data(:does_not_exist) : "Done.")
105
- raise DTK::Client::DtkError, response.error_message unless response.ok?
115
+ ignore_component_error = (new_context_params.get_forwarded_options() || {})[:ignore_component_error] && module_type.eql?('component_module')
116
+ puts(response.data(:does_not_exist) ? response.data(:does_not_exist) : 'Done.')
117
+ raise DTK::Client::DtkError, response.error_message if !response.ok? && !ignore_component_error
106
118
  end
107
119
 
108
120
  Response::Ok.new()
data/lib/commands.rb CHANGED
@@ -25,7 +25,7 @@ module DTK
25
25
  DTK::Client::Session.get_connection()
26
26
  end
27
27
 
28
- def self.handle_argument_error(task, error)
28
+ def self.handle_argument_error(task, error)
29
29
  super
30
30
  end
31
31
 
@@ -35,6 +35,6 @@ module DTK
35
35
  self.class.pretty_print_cols()
36
36
  end
37
37
  end
38
-
38
+
39
39
  end
40
40
  end
@@ -56,8 +56,10 @@ module DTK::Client
56
56
 
57
57
  # mode will be :create or :update
58
58
  # service_module_name_x can be name or fullname (NS:MOduleName)
59
- def promote_assembly_aux(mode,assembly_or_workspace_id,service_module_name_x=nil,assembly_template_name=nil,opts={})
60
- namespace,local_clone_dir_exists = nil, nil
59
+ def promote_assembly_aux(mode, assembly_or_workspace_id, service_module_name_x = nil, assembly_template_name = nil, opts = {})
60
+ namespace = nil
61
+ local_clone_dir_exists = nil
62
+
61
63
  post_body = {
62
64
  :assembly_id => assembly_or_workspace_id,
63
65
  :mode => mode.to_s
@@ -66,7 +68,7 @@ module DTK::Client
66
68
  if service_module_name_x
67
69
  service_module_name = service_module_name_x
68
70
  if service_module_name_x =~ /(^[^:]+):([^:]+$)/
69
- namespace,service_module_name = [$1,$2]
71
+ namespace, service_module_name = [$1,$2]
70
72
  end
71
73
  post_body.merge!(:service_module_name => service_module_name)
72
74
  end
@@ -81,16 +83,16 @@ module DTK::Client
81
83
  post_body.merge!(:assembly_template_name => assembly_template_name) if assembly_template_name
82
84
  post_body.merge!(:use_module_namespace => true) if opts[:use_module_namespace]
83
85
  post_body.merge!(:description => opts[:description]) if opts[:description]
84
- response = post rest_url("assembly/promote_to_template"), post_body
86
+ response = post rest_url('assembly/promote_to_template'), post_body
85
87
  return response unless response.ok?()
86
88
 
87
- #synchronize_clone will load new assembly template into service clone on workspace (if it exists)
88
- commit_sha,workspace_branch,namespace,full_module_name,repo_url,version = response.data(:commit_sha,:workspace_branch,:module_namespace,:full_module_name,:repo_url,:version)
89
+ # synchronize_clone will load new assembly template into service clone on workspace (if it exists)
90
+ commit_sha, workspace_branch, namespace, full_module_name, repo_url, version = response.data(:commit_sha, :workspace_branch, :module_namespace, :full_module_name, :repo_url, :version)
89
91
  service_module_name ||= response.data(:module_name)
90
- opts = {:local_branch=>workspace_branch, :namespace => namespace}
92
+ opts = { :local_branch => workspace_branch, :namespace => namespace }
91
93
 
92
94
  if (mode == :update) || local_clone_dir_exists
93
- response = Helper(:git_repo).synchronize_clone(:service_module,service_module_name,commit_sha,opts)
95
+ response = Helper(:git_repo).synchronize_clone(:service_module, service_module_name, commit_sha, opts)
94
96
  else
95
97
  response = Helper(:git_repo).create_clone_with_branch(:service_module, service_module_name, repo_url, workspace_branch, version, namespace)
96
98
  end
@@ -111,44 +113,63 @@ module DTK::Client
111
113
  response = post rest_url("assembly/print_includes"),:assembly_id => assembly_or_workspace_id
112
114
  end
113
115
 
114
- def converge_aux(context_params)
115
- assembly_or_workspace_id,task_action,task_params_string = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID,:option_1,:option_2],method_argument_names)
116
-
117
- task_params = nil
118
- if task_params_string
119
- task_params = task_params_string.split(',').inject(Hash.new) do |h,av|
120
- av_split = av.split('=')
121
- unless av_split.size == 2
122
- raise DTK::Client::DtkValidationError, "The task parameters (#{task_params_string}) is ill-formed"
123
- end
124
- h.merge(av_split[0] => av_split[1])
125
- end
126
- end
116
+ def list_ad_hoc_actions_aux(context_params)
117
+ assembly_or_workspace_id = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID],method_argument_names)
127
118
 
128
119
  post_body = {
129
- :assembly_id => assembly_or_workspace_id
120
+ :assembly_id => assembly_or_workspace_id,
121
+ :type => options.summary? ? :component_type : :component_instance
122
+ }
123
+
124
+ response = post rest_url("assembly/ad_hoc_action_list"), post_body
125
+ response.render_table()
126
+ end
127
+
128
+ # desc "SERVICE-NAME/ID execute-action COMPONENT-INSTANCE [ACTION-NAME [ACTION-PARAMS]]"
129
+ def execute_ad_hoc_action_aux(context_params)
130
+ assembly_or_workspace_id,component_id,method_name,action_params_string = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID,:option_1!,:option_2,:option_3],method_argument_names)
131
+
132
+ action_params = parse_params?(action_params_string)
133
+
134
+ post_body = {
135
+ :assembly_id => assembly_or_workspace_id,
136
+ :component_id => component_id
130
137
  }
138
+ post_body.merge!(:method_name => method_name) if method_name
139
+ post_body.merge!(:action_params => action_params) if action_params
140
+
141
+ response = post rest_url("assembly/ad_hoc_action_execute"), post_body
142
+ return response unless response.ok?
143
+
144
+ task_status_stream(assembly_or_workspace_id)
145
+ Response::Ok.new()
146
+ end
147
+
148
+ def converge_aux(context_params,opts={})
149
+ assembly_or_workspace_id,task_action,task_params_string = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID,:option_1,:option_2],method_argument_names)
131
150
 
132
- response = post rest_url("assembly/find_violations"), post_body
151
+ task_params = parse_params?(task_params_string)
152
+
153
+ # check for violations
154
+ response = post rest_url("assembly/find_violations"), :assembly_id => assembly_or_workspace_id
133
155
  return response unless response.ok?
134
156
  if response.data and response.data.size > 0
135
- #TODO: may not directly print here; isntead use a lower level fn
136
157
  error_message = "The following violations were found; they must be corrected before workspace can be converged"
137
158
  DTK::Client::OsUtil.print(error_message, :red)
138
159
  return response.render_table(:violation)
139
160
  end
140
161
 
141
- post_body.merge!(:commit_msg => options.commit_msg) if options.commit_msg
142
- post_body.merge!(:task_action => task_action) if task_action
143
- post_body.merge!(:task_params => task_params) if task_params
144
-
162
+ post_body = PostBody.new(
163
+ :assembly_id => assembly_or_workspace_id,
164
+ :commit_msg? => options.commit_msg,
165
+ :task_action? => task_action,
166
+ :task_params? => task_params
167
+ )
145
168
  response = post rest_url("assembly/create_task"), post_body
146
169
  return response unless response.ok?
147
170
 
148
171
  if response.data
149
- confirmation_message = response.data["confirmation_message"]
150
-
151
- if confirmation_message
172
+ if confirmation_message = response.data["confirmation_message"]
152
173
  return unless Console.confirmation_prompt("Workspace service is stopped, do you want to start it"+'?')
153
174
  post_body.merge!(:start_assembly=>true)
154
175
  response = post rest_url("assembly/create_task"), post_body
@@ -158,8 +179,28 @@ module DTK::Client
158
179
 
159
180
  # execute task
160
181
  task_id = response.data(:task_id)
161
- post rest_url("task/execute"), "task_id" => task_id
182
+ response = post rest_url("task/execute"), "task_id" => task_id
183
+ return response unless response.ok?
184
+
185
+ if opts[:mode] == :stream
186
+ task_status_stream(assembly_or_workspace_id)
187
+ end
188
+
189
+ Response::Ok.new()
190
+ end
191
+
192
+ def parse_params?(params_string)
193
+ if params_string
194
+ params_string.split(',').inject(Hash.new) do |h,av|
195
+ av_split = av.split('=')
196
+ unless av_split.size == 2
197
+ raise DtkValidationError, "The parameter string (#{params_string}) is ill-formed"
198
+ end
199
+ h.merge(av_split[0] => av_split[1])
200
+ end
201
+ end
162
202
  end
203
+ private :parse_params?
163
204
 
164
205
  def edit_module_aux(context_params)
165
206
  assembly_or_workspace_id, component_module_name = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID,:option_1!],method_argument_names)
@@ -208,15 +249,24 @@ module DTK::Client
208
249
  response = post rest_url("assembly/prepare_for_edit_module"), post_body
209
250
  end
210
251
 
211
- def edit_workflow_aux(context_params)
212
- assembly_or_workspace_id = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID],method_argument_names)
252
+ def edit_or_create_workflow_aux(context_params,opts={})
253
+ option_1 = (opts[:create] ? :option_1! : :option_1)
254
+ assembly_or_workspace_id, workflow_name = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID,option_1],method_argument_names)
213
255
  post_body = {
214
- :assembly_id => assembly_or_workspace_id,
215
- :module_type => 'service_module',
256
+ :assembly_id => assembly_or_workspace_id,
257
+ :module_type => 'service_module',
216
258
  :modification_type => 'workflow'
217
259
  }
260
+ if workflow_name
261
+ post_body.merge!(:task_action => workflow_name)
262
+ end
263
+ if opts[:create]
264
+ post_body.merge!(:create => true)
265
+ post_body.merge!(:base_task_action => opts[:create_from]) if opts[:create_from]
266
+ end
218
267
  response = post rest_url("assembly/prepare_for_edit_module"), post_body
219
268
  return response unless response.ok?
269
+
220
270
  assembly_name,service_module_id,service_module_name,version,repo_url,branch,branch_head_sha,edit_file = response.data(:assembly_name,:module_id,:full_module_name,:version,:repo_url,:workspace_branch,:branch_head_sha,:edit_file)
221
271
  edit_opts = {
222
272
  :automatically_clone => true,
@@ -234,6 +284,7 @@ module DTK::Client
234
284
  :modification_type => :workflow,
235
285
  :edit_file => edit_file
236
286
  }
287
+ edit_opts.merge!(:task_action => workflow_name) if workflow_name
237
288
  version = nil #TODO: version associated with assembly is passed in edit_opts, which is a little confusing
238
289
  edit_aux(:service_module,service_module_id,service_module_name,version,edit_opts)
239
290
  end
@@ -257,14 +308,14 @@ module DTK::Client
257
308
  end
258
309
 
259
310
  def push_module_updates_aux(context_params)
260
- assembly_or_workspace_id, component_module_name = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID,:option_1!],method_argument_names)
311
+ assembly_or_workspace_id, component_module_name = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID, :option_1!], method_argument_names)
261
312
  post_body = {
262
313
  :assembly_id => assembly_or_workspace_id,
263
314
  :module_name => component_module_name,
264
315
  :module_type => 'component_module'
265
316
  }
266
317
  post_body.merge!(:force => true) if options.force?
267
- response = post(rest_url("assembly/promote_module_updates"),post_body)
318
+ response = post(rest_url('assembly/promote_module_updates'), post_body)
268
319
  return response unless response.ok?
269
320
  return Response::Ok.new() unless response.data(:any_updates)
270
321
  if dsl_parsing_errors = response.data(:dsl_parsing_errors)
@@ -272,11 +323,12 @@ module DTK::Client
272
323
  OsUtil.print(error_message, :red)
273
324
  return Response::NoOp.new()
274
325
  end
275
- module_name,namespace,branch,ff_change = response.data(:module_name,:module_namespace,:workspace_branch,:fast_forward_change)
326
+ module_name, namespace, branch, ff_change = response.data(:module_name, :module_namespace, :workspace_branch, :fast_forward_change)
276
327
  ff_change ||= true
277
- opts = {:local_branch => branch,:namespace => namespace}
278
- opts.merge!(:hard_reset => true) if !ff_change
279
- response = Helper(:git_repo).pull_changes?(:component_module,module_name,opts)
328
+ opts = { :local_branch => branch, :namespace => namespace }
329
+ opts.merge!(:hard_reset => true) unless ff_change
330
+ opts.merge!(:force => true) if options.force?
331
+ response = Helper(:git_repo).pull_changes?(:component_module, module_name, opts)
280
332
  return response unless response.ok?()
281
333
  Response::Ok.new()
282
334
  end
@@ -323,9 +375,9 @@ module DTK::Client
323
375
  response = Helper(:git_repo).pull_changes?(:component_module, module_name, edit_opts.merge!(opts))
324
376
  return response unless response.ok?()
325
377
 
326
- edit_opts.merge!(:force_parse => true, :update_from_includes => true, :print_dependencies => true, :remote_branch => local_branch)
378
+ edit_opts.merge!(:force_parse => true, :update_from_includes => true, :print_dependencies => true, :remote_branch => local_branch, :force_clone => true)
327
379
  response = push_clone_changes_aux(:component_module, module_id, nil, "Pull base module updates", true, edit_opts)
328
-
380
+
329
381
  unless response.ok?()
330
382
  # if parsing error on assembly module (components/attributes/link_defs integrity violations) do git reset --hard
331
383
  Helper(:git_repo).hard_reset_branch_to_sha(:component_module, module_name, edit_opts)
@@ -336,24 +388,46 @@ module DTK::Client
336
388
  end
337
389
 
338
390
  def workflow_info_aux(context_params)
339
- assembly_or_workspace_id = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID],method_argument_names)
391
+ assembly_or_workspace_id,workflow_name = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID,:option_1],method_argument_names)
340
392
  post_body = {
341
393
  :assembly_id => assembly_or_workspace_id,
342
394
  :subtype => 'instance'
343
395
  }
396
+ post_body.merge!(:task_action => workflow_name) if workflow_name
344
397
  post(rest_url("assembly/info_about_task"),post_body)
345
398
  end
346
399
 
400
+ def workflow_list_aux(context_params)
401
+ assembly_or_workspace_id = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID],method_argument_names)
402
+ post_body = {
403
+ :assembly_id => assembly_or_workspace_id
404
+ }
405
+ response = post(rest_url("assembly/task_action_list"),post_body)
406
+ data_type = 'task_action'
407
+ response.render_table(data_type)
408
+ end
409
+
347
410
  def task_status_aw_aux(context_params)
348
411
  assembly_or_workspace_id = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID],method_argument_names)
349
- response = task_status_aux(assembly_or_workspace_id,:assembly,:wait => options.wait?,:summarize => options.summarize?)
412
+ mode =
413
+ if options.wait?
414
+ :refresh
415
+ else
416
+ if options.has_key?('mode') and options.mode.nil?
417
+ raise DtkError::Usage.new("option --mode needs an argument")
418
+ end
419
+ (options.mode || :snapshot).to_sym
420
+ end
421
+ response = task_status_aux(mode,assembly_or_workspace_id,:assembly,:summarize => options.summarize?)
350
422
 
351
423
  # TODO: Hack which is necessery for the specific problem (DTK-725), we don't get proper error message when there is a timeout doing converge
352
- unless response == true
353
- return response.merge("data" => [{ "errors" => {"message" => "Task does not exist for workspace."}}]) unless response["data"]
354
- response["data"].each do |data|
355
- if data["errors"]
356
- data["errors"]["message"] = "[TIMEOUT ERROR] Server is taking too long to respond." if data["errors"]["message"] == "error"
424
+ unless mode == :stream
425
+ unless response == true
426
+ return response.merge("data" => [{ "errors" => {"message" => "Task does not exist for workspace."}}]) unless response["data"]
427
+ response["data"].each do |data|
428
+ if data["errors"]
429
+ data["errors"]["message"] = "[TIMEOUT ERROR] Server is taking too long to respond." if data["errors"]["message"] == "error"
430
+ end
357
431
  end
358
432
  end
359
433
  end
@@ -407,103 +481,6 @@ module DTK::Client
407
481
  list_aux(context_params)
408
482
  end
409
483
 
410
- # desc "WORKSPACE-NAME/ID list-assemblies","List assemblies for current workspace."
411
- # def list_assemblies(context_params)
412
- # data_type = :assembly
413
- # post_body = { :subtype => 'instance', :detail_level => 'nodes' }
414
- # rest_endpoint = "assembly/list"
415
- # response = post rest_url(rest_endpoint), post_body
416
-
417
- # response.render_table(data_type)
418
- # return response
419
- # end
420
-
421
- # desc "WORKSPACE-NAME/ID list-assemblies","List assemblies for current workspace."
422
- # def list_assemblies(context_params)
423
- # data_type = :assembly
424
- # post_body = { :subtype => 'instance', :detail_level => 'nodes' }
425
- # rest_endpoint = "assembly/list"
426
- # response = post rest_url(rest_endpoint), post_body
427
-
428
- # response.render_table(data_type)
429
- # return response
430
- # end
431
-
432
- # desc "WORKSPACE-NAME/ID list-assemblies","List assemblies for current workspace."
433
- # def list_assemblies(context_params)
434
- # workspace_id, node_id, component_id, attribute_id, about = context_params.retrieve_arguments([:workspace_id,:node_id,:component_id,:attribute_id,:option_1],method_argument_names)
435
- # detail_to_include = nil
436
-
437
- # if about
438
- # case about
439
- # when "nodes"
440
- # data_type = :node
441
- # when "components"
442
- # data_type = :component
443
- # detail_to_include = [:component_dependencies]
444
- # when "attributes"
445
- # data_type = :attribute
446
- # detail_to_include = [:attribute_links]
447
- # when "tasks"
448
- # data_type = :task
449
- # else
450
- # raise_validation_error_method_usage('list')
451
- # end
452
- # end
453
-
454
- # post_body = {
455
- # :assembly_id => workspace_id,
456
- # :node_id => node_id,
457
- # :component_id => component_id,
458
- # :subtype => 'instance'
459
- # }
460
- # post_body.merge!(:detail_to_include => detail_to_include) if detail_to_include
461
- # rest_endpoint = "assembly/info_about"
462
-
463
- # if context_params.is_last_command_eql_to?(:attribute)
464
- # raise DTK::Client::DtkError, "Not supported command for current context level." if attribute_id
465
- # about, data_type = get_type_and_raise_error_if_invalid(about, "attributes", ["attributes"])
466
- # elsif context_params.is_last_command_eql_to?(:component)
467
- # if component_id
468
- # about, data_type = get_type_and_raise_error_if_invalid(about, "attributes", ["attributes"])
469
- # else
470
- # about, data_type = get_type_and_raise_error_if_invalid(about, "components", ["attributes", "components"])
471
- # end
472
- # elsif context_params.is_last_command_eql_to?(:node)
473
- # if node_id
474
- # about, data_type = get_type_and_raise_error_if_invalid(about, "components", ["attributes", "components"])
475
- # data_type = :workspace_attribute
476
- # else
477
- # about, data_type = get_type_and_raise_error_if_invalid(about, "nodes", ["attributes", "components", "nodes"])
478
- # end
479
- # else
480
- # if workspace_id
481
- # about, data_type = get_type_and_raise_error_if_invalid(about, "nodes", ["attributes", "components", "nodes", "tasks"])
482
- # else
483
- # data_type = :assembly
484
- # post_body = { :subtype => 'instance', :detail_level => 'nodes' }
485
- # rest_endpoint = "assembly/list"
486
- # end
487
- # end
488
-
489
- # post_body[:about] = about
490
- # response = post rest_url(rest_endpoint), post_body
491
-
492
- # if (data_type.to_s.eql?("workspace_attribute") && response["data"])
493
- # response["data"].each do |data|
494
- # unless(data["linked_to_display_form"].to_s.empty?)
495
- # data_type = :workspace_attribute_w_link
496
- # break
497
- # end
498
- # end
499
- # end
500
-
501
- # # set render view to be used
502
- # response.render_table(data_type)
503
-
504
- # return response
505
- # end
506
-
507
484
  def link_attribute_aux(context_params)
508
485
  assembly_or_workspace_id, target_attr_term, source_attr_term = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID,:option_1!,:option_2!],method_argument_names)
509
486
  post_body = {
@@ -514,22 +491,20 @@ module DTK::Client
514
491
  post rest_url("assembly/add_ad_hoc_attribute_links"), post_body
515
492
  end
516
493
 
517
- def list_attribute_mappings_aux(context_params)
518
- post_body = Helper(:service_link).post_body_with_id_keys(context_params,method_argument_names)
519
- post rest_url("assembly/list_attribute_mappings"), post_body
520
- end
521
-
522
494
  def create_component_aux(context_params)
523
495
  # If method is invoked from 'assembly/node' level retrieve node_id argument
524
496
  # directly from active context
525
497
  if context_params.is_there_identifier?(:node)
526
- mapping = [REQ_ASSEMBLY_OR_WS_ID,:node_id!,:option_1!]
498
+ mapping = [REQ_ASSEMBLY_OR_WS_ID, :node_id!, :option_1!]
499
+ assembly_id, node_id, component_template_id = context_params.retrieve_arguments(mapping, method_argument_names)
527
500
  else
528
501
  # otherwise retrieve node_id from command options
529
- mapping = [REQ_ASSEMBLY_OR_WS_ID,:option_1!,:option_2!]
502
+ mapping = [REQ_ASSEMBLY_OR_WS_ID, :option_1!]
503
+ assembly_id, component_template_id = context_params.retrieve_arguments(mapping, method_argument_names)
504
+ node_id = nil
530
505
  end
531
506
 
532
- assembly_id,node_id,component_template_id = context_params.retrieve_arguments(mapping,method_argument_names)
507
+ # assembly_id,node_id,component_template_id = context_params.retrieve_arguments(mapping,method_argument_names)
533
508
  namespace, component_template_id = get_namespace_and_name_for_component(component_template_id)
534
509
 
535
510
  post_body = {
@@ -549,21 +524,24 @@ module DTK::Client
549
524
 
550
525
  def link_components_aux(context_params)
551
526
  post_body = link_unlink_components__ret_post_body(context_params)
552
- post rest_url("assembly/add_service_link"), post_body
527
+ post rest_url('assembly/add_service_link'), post_body
553
528
  end
554
529
 
555
530
  def link_unlink_components__ret_post_body(context_params)
556
531
  if context_params.is_last_command_eql_to?(:component)
557
- assembly_or_workspace_id,dep_cmp,antec_cmp,dependency_name = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID,:component_id!,:option_1!,:option_2],method_argument_names)
532
+ assembly_or_workspace_id, dep_cmp, antec_cmp, dependency_name = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID, :component_id!, :option_1!, :option_2], method_argument_names)
558
533
  else
559
- assembly_or_workspace_id,dep_cmp,antec_cmp,dependency_name = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID,:option_1!,:option_2!,:option_3],method_argument_names)
534
+ assembly_or_workspace_id, dep_cmp, antec_cmp, dependency_name = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID, :option_1!, :option_2!, :option_3], method_argument_names)
560
535
  end
536
+
537
+ antec_cmp = "assembly_wide/#{antec_cmp}" unless antec_cmp.include?('/')
561
538
  post_body = {
562
539
  :assembly_id => assembly_or_workspace_id,
563
540
  :input_component_id => dep_cmp,
564
541
  :output_component_id => antec_cmp
565
542
  }
566
543
  post_body.merge!(:dependency_name => dependency_name) if dependency_name
544
+
567
545
  post_body
568
546
  end
569
547
 
@@ -593,15 +571,6 @@ module DTK::Client
593
571
  response.render_table(:possible_service_connection)
594
572
  end
595
573
 
596
- def list_smoketests(context_params)
597
- assembly_or_workspace_id = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID],method_argument_names)
598
-
599
- post_body = {
600
- :assembly_id => assembly_or_workspace_id
601
- }
602
- post rest_url("assembly/list_smoketests"), post_body
603
- end
604
-
605
574
  def info_aux(context_params)
606
575
  assembly_or_workspace_id, node_id, component_id, attribute_id = context_params.retrieve_arguments([REQ_ASSEMBLY_OR_WS_ID, :node_id, :component_id, :attribute_id],method_argument_names)
607
576
  is_json_return = context_params.get_forwarded_options[:json_return] || false
@@ -726,18 +695,42 @@ module DTK::Client
726
695
 
727
696
  def set_attribute_aux(context_params)
728
697
  if context_params.is_there_identifier?(:attribute)
729
- mapping = (options.unset? ? [REQ_ASSEMBLY_OR_WS_ID,:attribute_id!] : [REQ_ASSEMBLY_OR_WS_ID,:attribute_id!,:option_1!])
698
+ mapping = (options.unset? ? [REQ_ASSEMBLY_OR_WS_ID, :attribute_id!] : [REQ_ASSEMBLY_OR_WS_ID, :attribute_id!, :option_1!])
730
699
  else
731
- mapping = (options.unset? ? [REQ_ASSEMBLY_OR_WS_ID,:option_1!] : [REQ_ASSEMBLY_OR_WS_ID,:option_1!,:option_2!])
700
+ mapping = (options.unset? ? [REQ_ASSEMBLY_OR_WS_ID, :option_1!] : [REQ_ASSEMBLY_OR_WS_ID, :option_1!, :option_2!])
732
701
  end
733
702
 
734
- assembly_or_workspace_id, pattern, value = context_params.retrieve_arguments(mapping,method_argument_names)
703
+ assembly_or_workspace_id, pattern, value = context_params.retrieve_arguments(mapping, method_argument_names)
735
704
  post_body = {
736
705
  :assembly_id => assembly_or_workspace_id,
737
706
  :pattern => pattern
738
707
  }
708
+
709
+ raise DTK::Client::DtkValidationError, 'Please use only component-attribute (-c) or node-attribute (-n) option' if options.component_attribute? && options.node_attribute?
710
+
711
+ # if try to set service instance attribute but using -n option to sepicify it is node attribute, say that node attribute does not exist
712
+ raise DTK::Client::DtkError, "[ERROR] Node attribute '#{pattern}' does not exist" if options.node_attribute? && !pattern.include?('/')
713
+
714
+ # make sure -c and -n are used only with node or cmp attributes directly on service instance
715
+ validate_service_instance_node_or_cmp_attrs(pattern, options) if options.component_attribute? || options.node_attribute?
716
+
717
+ post_body.merge!(:component_attribute => true) if options.component_attribute?
718
+ post_body.merge!(:node_attribute => true) if options.node_attribute? || context_params.is_there_identifier?(:node)
739
719
  post_body.merge!(:value => value) unless options.unset?
740
- post rest_url("assembly/set_attributes"), post_body
720
+
721
+ response = post rest_url('assembly/set_attributes'), post_body
722
+ return response unless response.ok?
723
+
724
+ if r_data = response.data
725
+ if r_data.is_a?(Hash) && (ambiguous = r_data['ambiguous'])
726
+ unless ambiguous.empty?
727
+ msg = "It is ambiguous whether '#{ambiguous.join(', ')}' #{ambiguous.size == 1 ? 'is' : 'are'} node or component attribute(s). Run set-attribute again with one of options -c [--component-attribute] or -n [--node-attribute]."
728
+ raise DTK::Client::DtkError, msg
729
+ end
730
+ end
731
+ end
732
+
733
+ Response::Ok.new()
741
734
  end
742
735
 
743
736
  def create_attribute_aux(context_params)
@@ -890,14 +883,24 @@ module DTK::Client
890
883
  return unless Console.confirmation_prompt("Are you sure you want to delete #{what} '#{component_id}'"+'?')
891
884
  end
892
885
 
886
+ if node_id.nil? && !(component_id.to_s =~ /^[0-9]+$/)
887
+ if component_id.to_s.include?('/')
888
+ node_id, component_id = component_id.split('/')
889
+ node_name = node_id
890
+ else
891
+ node_id = node_name = 'assembly_wide'
892
+ end
893
+ end
894
+
893
895
  post_body = {
894
896
  :assembly_id => assembly_or_workspace_id,
895
- :node_id => node_id,
896
897
  :component_id => component_id
897
898
  }
898
899
 
899
900
  # delete component by name (e.g. delete-component dtk_java)
900
901
  post_body.merge!(:cmp_full_name => "#{node_name}/#{component_id}") if (node_name && !(component_id.to_s =~ /^[0-9]+$/))
902
+ post_body.merge!(:node_id => node_id) if node_id
903
+
901
904
  response = post(rest_url("assembly/delete_component"),post_body)
902
905
  end
903
906
 
@@ -1413,5 +1416,14 @@ module DTK::Client
1413
1416
  post rest_url("assembly/clear_tasks"), post_body
1414
1417
  end
1415
1418
 
1419
+ def validate_service_instance_node_or_cmp_attrs(pattern, options)
1420
+ split_pattern = pattern.split('/')
1421
+ return if split_pattern.size == 2
1422
+ if options.node_attribute?
1423
+ raise DTK::Client::DtkError, 'Please use -n option only with service instance node attributes (node_name/attribute_name)'
1424
+ elsif options.component_attribute?
1425
+ raise DTK::Client::DtkError, 'Please use -c option only with service instance component attributes (cmp_name/attribute_name)'
1426
+ end
1427
+ end
1416
1428
  end
1417
1429
  end