moose-inventory 1.0.8 → 2.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.
Files changed (104) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +49 -0
  3. data/.github/workflows/release.yml +58 -0
  4. data/.gitignore +1 -1
  5. data/.gitleaks.toml +9 -0
  6. data/.rubocop.yml +19 -784
  7. data/BACKLOG.md +290 -0
  8. data/Gemfile.lock +95 -0
  9. data/README.md +38 -9
  10. data/Rakefile +1 -1
  11. data/bin/moose-inventory +1 -1
  12. data/docs/release/publishing.md +109 -0
  13. data/docs/release/release-readiness.md +55 -0
  14. data/docs/security-audit-2026-05-21.md +71 -0
  15. data/docs/security-audit-2026-05-26-rerun.md +75 -0
  16. data/docs/security-audit-2026-05-26.md +63 -0
  17. data/lib/moose_inventory/cli/formatter.rb +16 -17
  18. data/lib/moose_inventory/cli/group.rb +4 -1
  19. data/lib/moose_inventory/cli/group_add.rb +89 -75
  20. data/lib/moose_inventory/cli/group_addchild.rb +84 -71
  21. data/lib/moose_inventory/cli/group_addhost.rb +78 -69
  22. data/lib/moose_inventory/cli/group_addvar.rb +37 -37
  23. data/lib/moose_inventory/cli/group_get.rb +23 -26
  24. data/lib/moose_inventory/cli/group_list.rb +12 -15
  25. data/lib/moose_inventory/cli/group_listvars.rb +12 -14
  26. data/lib/moose_inventory/cli/group_rm.rb +104 -76
  27. data/lib/moose_inventory/cli/group_rmchild.rb +99 -54
  28. data/lib/moose_inventory/cli/group_rmhost.rb +64 -60
  29. data/lib/moose_inventory/cli/group_rmvar.rb +5 -5
  30. data/lib/moose_inventory/cli/helpers.rb +76 -0
  31. data/lib/moose_inventory/cli/host.rb +4 -1
  32. data/lib/moose_inventory/cli/host_add.rb +51 -66
  33. data/lib/moose_inventory/cli/host_addgroup.rb +77 -68
  34. data/lib/moose_inventory/cli/host_addvar.rb +6 -6
  35. data/lib/moose_inventory/cli/host_get.rb +15 -18
  36. data/lib/moose_inventory/cli/host_list.rb +3 -3
  37. data/lib/moose_inventory/cli/host_listvars.rb +21 -23
  38. data/lib/moose_inventory/cli/host_rm.rb +9 -9
  39. data/lib/moose_inventory/cli/host_rmgroup.rb +63 -60
  40. data/lib/moose_inventory/cli/host_rmvar.rb +3 -3
  41. data/lib/moose_inventory/config/config.rb +43 -40
  42. data/lib/moose_inventory/db/db.rb +92 -52
  43. data/lib/moose_inventory/db/models.rb +11 -12
  44. data/lib/moose_inventory/inventory_context.rb +50 -0
  45. data/lib/moose_inventory/operations/add_associations.rb +127 -0
  46. data/lib/moose_inventory/operations/add_groups.rb +115 -0
  47. data/lib/moose_inventory/operations/add_hosts.rb +110 -0
  48. data/lib/moose_inventory/operations/group_child_relations.rb +118 -0
  49. data/lib/moose_inventory/operations/group_cleanup.rb +55 -0
  50. data/lib/moose_inventory/operations/remove_associations.rb +101 -0
  51. data/lib/moose_inventory/operations/remove_groups.rb +79 -0
  52. data/lib/moose_inventory/version.rb +1 -1
  53. data/moose-inventory.gemspec +38 -20
  54. data/scripts/check.sh +10 -0
  55. data/scripts/ci/check_permissions.sh +35 -0
  56. data/scripts/ci/check_rubocop.sh +28 -0
  57. data/scripts/ci/check_secrets.sh +26 -0
  58. data/scripts/ci/check_security.sh +68 -0
  59. data/scripts/ci/install_security_tools.sh +47 -0
  60. data/scripts/ci/package_sanity.sh +46 -0
  61. data/scripts/files.rb +1 -4
  62. data/scripts/install_dependencies.sh +19 -0
  63. data/scripts/reports.sh +2 -2
  64. data/spec/lib/moose_inventory/cli/cli_spec.rb +13 -14
  65. data/spec/lib/moose_inventory/cli/group_add_spec.rb +118 -119
  66. data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +49 -51
  67. data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +80 -83
  68. data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +91 -91
  69. data/spec/lib/moose_inventory/cli/group_get_spec.rb +22 -23
  70. data/spec/lib/moose_inventory/cli/group_list_spec.rb +19 -20
  71. data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +35 -36
  72. data/spec/lib/moose_inventory/cli/group_rm_spec.rb +115 -78
  73. data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +86 -45
  74. data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +43 -46
  75. data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +131 -131
  76. data/spec/lib/moose_inventory/cli/group_spec.rb +9 -9
  77. data/spec/lib/moose_inventory/cli/host_add_spec.rb +103 -43
  78. data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +78 -80
  79. data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +122 -122
  80. data/spec/lib/moose_inventory/cli/host_get_spec.rb +16 -16
  81. data/spec/lib/moose_inventory/cli/host_list_spec.rb +8 -8
  82. data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +50 -52
  83. data/spec/lib/moose_inventory/cli/host_rm_spec.rb +12 -12
  84. data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +48 -51
  85. data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +136 -136
  86. data/spec/lib/moose_inventory/config/config_spec.rb +16 -3
  87. data/spec/lib/moose_inventory/db/db_spec.rb +386 -2
  88. data/spec/lib/moose_inventory/db/models_spec.rb +10 -11
  89. data/spec/lib/moose_inventory/operations/add_associations_spec.rb +77 -0
  90. data/spec/lib/moose_inventory/operations/add_groups_spec.rb +65 -0
  91. data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +69 -0
  92. data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +76 -0
  93. data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +78 -0
  94. data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +57 -0
  95. data/spec/shared/shared_config_setup.rb +2 -2
  96. data/spec/spec_helper.rb +7 -8
  97. metadata +157 -105
  98. data/.coveralls.yml +0 -0
  99. data/Guardfile +0 -38
  100. data/config/dotfiles/coveralls.yml +0 -0
  101. data/config/dotfiles/gitignore +0 -20
  102. data/config/dotfiles/rubocop.yml +0 -793
  103. data/scripts/guard_quality.sh +0 -3
  104. data/scripts/guard_test.sh +0 -2
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
- require_relative './formatter.rb'
4
+ require_relative 'formatter'
5
+ require_relative '../inventory_context'
6
+ require_relative '../operations/remove_groups'
3
7
 
4
8
  module Moose
5
9
  module Inventory
@@ -7,85 +11,109 @@ module Moose
7
11
  ##
8
12
  # Implementation of "group rm" methods of the CLI
9
13
  class Group
10
-
11
14
  #==========================
15
+ option :recursive,
16
+ type: :boolean,
17
+ default: false,
18
+ desc: 'Also delete child groups that become orphaned'
12
19
  desc 'rm NAME',
13
20
  'Remove a group NAME from the inventory'
14
- def rm(*argv) # rubocop:disable Metrics/AbcSize
15
- #
16
- # Sanity
17
- if argv.length < 1
18
- abort('ERROR: Wrong number of arguments, '\
19
- "#{argv.length} for 1 or more.")
20
- end
21
-
22
- # Convenience
23
- db = Moose::Inventory::DB
24
- fmt = Moose::Inventory::Cli::Formatter
25
-
26
- # Arguments
27
- names = argv.uniq.map(&:downcase)
28
-
29
- # sanity
30
- if names.include?('ungrouped')
31
- abort("Cannot manually manipulate the automatic group 'ungrouped'\n")
32
- end
33
-
34
- # Transaction
35
- warn_count = 0
36
- db.transaction do # Transaction start
37
- names.each do |name|
38
- puts "Remove group '#{name}':"
39
- fmt.puts 2, "- Retrieve group '#{name}'..."
40
- group = db.models[:group].find(name: name)
41
- if group.nil?
42
- warn_count += 1
43
- fmt.warn "Group '#{ name }' does not exist, skipping.\n"
44
- fmt.puts 4, "- No such group, skipping."
45
- end
46
- fmt.puts 4, "- OK"
47
- unless group.nil?
48
- # Dissociate from any parent groups
49
- pgroups_ds = group.parents_dataset
50
- pgroups_ds.each do |parent|
51
- fmt.puts 2, "- Remove association {group:#{name} <-> group:#{parent.name}}..."
52
- parent.remove_child(group)
53
- fmt.puts 4, '- OK'
54
- end
55
-
56
- # Dissociate from any child groups
57
- groups_ds = group.children_dataset
58
- groups_ds.each do |child|
59
- fmt.puts 2, "- Remove association {group:#{name} <-> group:#{child.name}}..."
60
- group.remove_child(child)
61
- # TODO: Should we propagate the delete to orphaned children?
62
- fmt.puts 4, '- OK'
63
- end
64
-
65
- # Handle automatic group for any associated hosts
66
- hosts_ds = group.hosts_dataset
67
- hosts_ds.each do |host|
68
- host_groups_ds = host.groups_dataset
69
- if host_groups_ds.count == 1 # We're the only group
70
- fmt.puts 2, "- Adding automatic association {group:ungrouped <-> host:#{host[:name]}}..."
71
- ungrouped = db.models[:group].find_or_create(name: 'ungrouped')
72
- host.add_group(ungrouped)
73
- fmt.puts 4, "- OK"
74
- end
75
- end
76
- # Remove the group
77
- fmt.puts 2, "- Destroy group '#{name}'..."
78
- group.remove_all_hosts
79
- group.destroy
80
- fmt.puts 4, "- OK"
81
- end
82
- fmt.puts 2, "- All OK"
83
- end
84
- end # Transaction end
85
- if warn_count == 0
86
- puts "Succeeded."
21
+ def rm(*argv)
22
+ abort_if_missing_args(argv, 1, '1 or more')
23
+
24
+ names = normalize_names(argv)
25
+
26
+ abort_if_automatic_group(
27
+ names,
28
+ "Cannot manually manipulate the automatic group 'ungrouped'\n"
29
+ )
30
+
31
+ result = remove_groups(names)
32
+
33
+ if result.warning_count.zero?
34
+ puts 'Succeeded.'
87
35
  else
88
- puts "Succeeded, with warnings."
36
+ puts 'Succeeded, with warnings.'
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def remove_groups(names)
43
+ context = Moose::Inventory::InventoryContext.new(db: db)
44
+ operation = Moose::Inventory::Operations::RemoveGroups.new(context: context)
45
+
46
+ db.transaction do
47
+ result = operation.call(names: names, recursive: options[:recursive])
48
+ render_group_rm_events(result.events)
49
+ return result
50
+ end
51
+ end
52
+
53
+ def render_group_rm_events(events)
54
+ events.each { |event| render_group_rm_event(event) }
55
+ end
56
+
57
+ def render_group_rm_event(event)
58
+ payload = event.payload
59
+
60
+ render_group_rm_warning(payload) if event.type == :group_missing
61
+ return render_group_rm_progress(event.type, payload) if group_rm_progress?(event.type)
62
+
63
+ render_group_rm_status(event.type, payload)
64
+ end
65
+
66
+ def group_rm_progress?(type)
67
+ %i[group_started retrieving_group removing_parent_association removing_child_association].include?(type)
68
+ end
69
+
70
+ def render_group_rm_warning(payload)
71
+ fmt.warn "Group '#{payload[:name]}' does not exist, skipping.\n"
72
+ end
73
+
74
+ def render_group_rm_progress(type, payload)
75
+ case type
76
+ when :group_started
77
+ puts "Remove group '#{payload[:name]}':"
78
+ when :retrieving_group
79
+ fmt.puts 2, "- Retrieve group '#{payload[:name]}'..."
80
+ when :removing_parent_association, :removing_child_association
81
+ fmt.puts 2, "- Remove association {group:#{payload[:group]} <-> group:#{payload[:related_group]}}..."
82
+ end
83
+ end
84
+
85
+ def render_group_rm_status(type, payload)
86
+ return render_group_rm_secondary_status(type, payload) if group_rm_secondary_status?(type)
87
+
88
+ case type
89
+ when :group_missing
90
+ fmt.puts 4, '- No such group, skipping.'
91
+ when :destroying_group
92
+ fmt.puts payload[:indent], "- Destroy group '#{payload[:name]}'..."
93
+ when :group_complete
94
+ fmt.puts 2, '- All OK'
95
+ end
96
+ end
97
+
98
+ def group_rm_secondary_status?(type)
99
+ %i[
100
+ recursively_delete_orphaned_group
101
+ removing_recursive_child_association
102
+ adding_automatic_group_to_host
103
+ ok
104
+ ].include?(type)
105
+ end
106
+
107
+ def render_group_rm_secondary_status(type, payload)
108
+ case type
109
+ when :recursively_delete_orphaned_group
110
+ fmt.puts 2, "- Recursively delete orphaned group '#{payload[:name]}'..."
111
+ when :removing_recursive_child_association
112
+ fmt.puts 4, "- Remove association {group:#{payload[:parent]} <-> group:#{payload[:child]}}..."
113
+ when :adding_automatic_group_to_host
114
+ fmt.puts payload[:indent], "- Adding automatic association {group:ungrouped <-> host:#{payload[:host]}}..."
115
+ when :ok
116
+ fmt.puts payload[:indent], '- OK'
89
117
  end
90
118
  end
91
119
  end
@@ -1,81 +1,126 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
 
3
- require_relative './formatter.rb'
5
+ require_relative 'formatter'
6
+ require_relative '../inventory_context'
7
+ require_relative '../operations/group_child_relations'
4
8
 
5
9
  module Moose
6
10
  module Inventory
7
11
  module Cli
8
12
  ##
9
13
  # Implemention of the "group rmchild" methods of the CLI
10
- class Group < Thor # rubocop:disable ClassLength
14
+ class Group
11
15
  #==========================
16
+ option :delete_orphans,
17
+ type: :boolean,
18
+ default: false,
19
+ desc: 'Delete child groups that become orphaned'
12
20
  desc 'rmchild PARENTGROUP CHILDGROUP_1 [CHILDGROUP_2 ... ]',
13
- 'Dissociate one or more child-groups CHILDGROUP_n from PARENTGROUP'
21
+ 'Dissociate one or more child-groups CHILDGROUP_n from PARENTGROUP'
14
22
  def rmchild(*argv)
23
+ abort_if_missing_args(argv, 2, '2 or more')
15
24
 
16
- # Sanity check
17
- if args.length < 2
18
- abort("ERROR: Wrong number of arguments, #{args.length} "\
19
- "for 2 or more.")
20
- end
25
+ pname = argv[0].downcase
26
+ cnames = normalize_names(argv.slice(1, argv.length - 1))
21
27
 
22
- # Arguments
23
- pname = args[0].downcase
24
- cnames = args.slice(1, args.length - 1).uniq.map(&:downcase)
28
+ abort_if_automatic_group([pname] + cnames)
25
29
 
26
- # Sanity
27
- if pname == 'ungrouped' || cnames.include?('ungrouped')
28
- abort("ERROR: Cannot manually manipulate the automatic group 'ungrouped'.")
30
+ result = remove_children_from_group(pname, cnames)
31
+
32
+ if result.warning_count.zero?
33
+ puts 'Succeeded.'
34
+ else
35
+ puts 'Succeeded, with warnings.'
29
36
  end
37
+ end
38
+
39
+ private
30
40
 
31
- # Convenience
32
- db = Moose::Inventory::DB
33
- fmt = Moose::Inventory::Cli::Formatter
41
+ def remove_children_from_group(parent_name, child_names)
42
+ context = Moose::Inventory::InventoryContext.new(db: db)
43
+ operation = Moose::Inventory::Operations::GroupChildRelations.new(context: context)
34
44
 
35
- # Transaction
36
- warn_count = 0
37
45
  begin
38
- db.transaction do # Transaction start
39
- puts "Dissociate parent group '#{pname}' from child group(s) '#{cnames.join(',')}':"
40
- # Get the target group
41
- fmt.puts 2, "- retrieve group '#{pname}'..."
42
- pgroup = db.models[:group].find(name: pname)
43
- if pgroup.nil?
44
- abort("ERROR: The group '#{pname}' does not exist.")
45
- end
46
- fmt.puts 4, "- OK"
47
-
48
- # Dissociate parent group from the child groups
49
- groups_ds = pgroup.children_dataset
50
- cnames.each do |cname|
51
- fmt.puts 2, "- remove association {group:#{pname} <-> group:#{cname}}..."
52
-
53
- # Check against existing associations
54
- if groups_ds[name: cname].nil?
55
- warn_count += 1
56
- fmt.warn "Association {group:#{pname} <-> group:#{cname}}"\
57
- " does not exist, skipping.\n"
58
- fmt.puts 4, "- doesn't exist, skipping."
59
- fmt.puts 4, '- OK'
60
- next
61
- end
62
-
63
- # remove association
64
- cgroup = db.models[:group].find(name: cname)
65
- pgroup.remove_child(cgroup)
66
- fmt.puts 4, '- OK'
67
- end
46
+ db.transaction do
47
+ puts "Dissociate parent group '#{parent_name}' from child group(s) '#{child_names.join(',')}':"
48
+ parent_group = fetch_existing_group_for_rmchild(context, parent_name)
49
+ result = operation.remove_children(
50
+ parent_group: parent_group,
51
+ parent_name: parent_name,
52
+ child_names: child_names,
53
+ delete_orphans: options[:delete_orphans]
54
+ )
55
+ render_rmchild_events(result.events)
68
56
  fmt.puts 2, '- all OK'
69
- end # Transaction end
57
+ return result
58
+ end
70
59
  rescue db.exceptions[:moose] => e
71
60
  abort("ERROR: #{e}")
72
61
  end
73
- if warn_count == 0
74
- puts 'Succeeded.'
75
- else
76
- puts 'Succeeded, with warnings.'
62
+ end
63
+
64
+ def fetch_existing_group_for_rmchild(context, name)
65
+ fmt.puts 2, "- retrieve group '#{name}'..."
66
+ group = context.find_group(name)
67
+ abort("ERROR: The group '#{name}' does not exist.") if group.nil?
68
+
69
+ fmt.puts 4, '- OK'
70
+ group
71
+ end
72
+
73
+ def render_rmchild_events(events)
74
+ events.each { |event| render_rmchild_event(event) }
75
+ end
76
+
77
+ def render_rmchild_event(event)
78
+ payload = event.payload
79
+
80
+ return render_rmchild_warning(payload) if event.type == :child_association_missing
81
+ return render_rmchild_missing(payload) if event.type == :missing_skipping
82
+ return render_rmchild_progress(event.type, payload) if rmchild_progress_event?(event.type)
83
+
84
+ render_rmchild_status(event.type, payload)
85
+ end
86
+
87
+ def rmchild_progress_event?(type)
88
+ %i[
89
+ removing_child_association
90
+ recursively_delete_orphaned_group
91
+ removing_recursive_child_association
92
+ ].include?(type)
93
+ end
94
+
95
+ def render_rmchild_progress(type, payload)
96
+ case type
97
+ when :removing_child_association
98
+ fmt.puts 2, "- remove association {group:#{payload[:parent]} <-> group:#{payload[:child]}}..."
99
+ when :recursively_delete_orphaned_group
100
+ fmt.puts 2, "- Recursively delete orphaned group '#{payload[:name]}'..."
101
+ when :removing_recursive_child_association
102
+ fmt.puts 4, "- Remove association {group:#{payload[:parent]} <-> group:#{payload[:child]}}..."
77
103
  end
78
104
  end
105
+
106
+ def render_rmchild_status(type, payload)
107
+ case type
108
+ when :adding_automatic_group_to_host
109
+ fmt.puts payload[:indent], "- Adding automatic association {group:ungrouped <-> host:#{payload[:host]}}..."
110
+ when :destroying_group
111
+ fmt.puts payload[:indent], "- Destroy group '#{payload[:name]}'..."
112
+ when :ok
113
+ fmt.puts payload[:indent], '- OK'
114
+ end
115
+ end
116
+
117
+ def render_rmchild_warning(payload)
118
+ fmt.warn "Association {group:#{payload[:parent]} <-> group:#{payload[:child]}} does not exist, skipping.\n"
119
+ end
120
+
121
+ def render_rmchild_missing(payload)
122
+ fmt.puts payload[:indent], "- doesn't exist, skipping."
123
+ end
79
124
  end
80
125
  end
81
126
  end
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
- require_relative './formatter.rb'
4
+ require_relative 'formatter'
5
+ require_relative '../inventory_context'
6
+ require_relative '../operations/remove_associations'
3
7
 
4
8
  module Moose
5
9
  module Inventory
@@ -7,82 +11,82 @@ module Moose
7
11
  ##
8
12
  # Implementation of the "group rmhost" method of the CLI
9
13
  class Group
10
-
11
14
  #==========================
12
15
  desc 'rmhost GROUPNAME HOSTNAME_1 [HOSTNAME_2 ...]',
13
16
  'Dissociate the hosts HOSTNAME_n from the group NAME'
14
- # rubocop:disable Metrics/LineLength
15
- def rmhost(*args) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/CyclomaticComplexity
16
- # rubocop:enable Metrics/LineLength
17
- # Sanity
18
- if args.length < 2
19
- abort("ERROR: Wrong number of arguments, #{args.length} for 2 or more.")
20
- end
17
+ def rmhost(*args)
18
+ abort_if_missing_args(args, 2, '2 or more')
21
19
 
22
- # Arguments
23
20
  name = args[0].downcase
24
- hosts = args.slice(1, args.length - 1).uniq.map(&:downcase)
21
+ hosts = normalize_names(args.slice(1, args.length - 1))
22
+
23
+ abort_if_automatic_group([name])
25
24
 
26
- # Sanity
27
- if name == 'ungrouped'
28
- abort("ERROR: Cannot manually manipulate the automatic group 'ungrouped'.")
25
+ result = remove_hosts_from_group(name, hosts)
26
+
27
+ if result.warning_count.zero?
28
+ puts 'Succeeded.'
29
+ else
30
+ puts 'Succeeded, with warnings.'
29
31
  end
32
+ end
33
+
34
+ private
30
35
 
31
- # Convenience
32
- db = Moose::Inventory::DB
33
- fmt = Moose::Inventory::Cli::Formatter
36
+ def remove_hosts_from_group(name, hosts)
37
+ context = Moose::Inventory::InventoryContext.new(db: db)
38
+ operation = Moose::Inventory::Operations::RemoveAssociations.new(context: context)
34
39
 
35
- # Transaction
36
- warn_count = 0
37
40
  begin
38
- db.transaction do # Transaction start
39
- # Get the target group
41
+ db.transaction do
40
42
  puts "Dissociate group '#{name}' from host(s) '#{hosts.join(',')}':"
41
- fmt.puts 2, "- retrieve group '#{name}'..."
42
- group = db.models[:group].find(name: name)
43
- if group.nil?
44
- abort("ERROR: The group '#{name}' does not exist.")
45
- end
46
- fmt.puts 4, '- OK'
47
-
48
- # dissociate group from the hosts
49
- ungrouped = db.models[:group].find_or_create(name: 'ungrouped')
50
- hosts_ds = group.hosts_dataset
51
- hosts.each do |h| # rubocop:disable Style/Next
52
- fmt.puts 2, "- remove association {group:#{name} <-> host:#{ h }}..."
53
-
54
- # Check against existing associations
55
- if hosts_ds[name: h].nil?
56
- warn_count += 1
57
- fmt.warn "Association {group:#{name} <-> host:#{ h }} doesn't"\
58
- " exist, skipping.\n"
59
- fmt.puts 4, '- doesn\'t exist, skipping.'
60
- fmt.puts 4, '- OK'
61
- next
62
- end
63
-
64
- host = db.models[:host].find(name: h)
65
- group.remove_host(host) unless host.nil?
66
- fmt.puts 4,'- OK'
67
-
68
- # Add the host to the ungrouped group if not in any other group
69
- if host.groups_dataset.count == 0
70
- fmt.puts 2, "- add automatic association {group:ungrouped <-> host:#{h}}..."
71
- host.add_group(ungrouped)
72
- fmt.puts 4, '- OK'
73
- end
74
- end
43
+ group = fetch_existing_group_for_rmhost(context, name)
44
+ result = operation.group_from_hosts(group: group, group_name: name, host_names: hosts)
45
+ render_group_rmhost_events(result.events)
75
46
  fmt.puts 2, '- all OK'
76
- end # Transaction end
47
+ return result
48
+ end
77
49
  rescue db.exceptions[:moose] => e
78
50
  abort("ERROR: #{e.message}")
79
51
  end
80
- if warn_count == 0
81
- puts 'Succeeded.'
82
- else
83
- puts 'Succeeded, with warnings.'
52
+ end
53
+
54
+ def fetch_existing_group_for_rmhost(context, name)
55
+ fmt.puts 2, "- retrieve group '#{name}'..."
56
+ group = context.find_group(name)
57
+ abort("ERROR: The group '#{name}' does not exist.") if group.nil?
58
+
59
+ fmt.puts 4, '- OK'
60
+ group
61
+ end
62
+
63
+ def render_group_rmhost_events(events)
64
+ events.each { |event| render_group_rmhost_event(event) }
65
+ end
66
+
67
+ def render_group_rmhost_event(event)
68
+ payload = event.payload
69
+
70
+ return render_group_rmhost_warning(payload) if event.type == :group_host_association_missing
71
+ return render_group_rmhost_missing(payload) if event.type == :missing_skipping
72
+
73
+ case event.type
74
+ when :removing_group_host_association
75
+ fmt.puts 2, "- remove association {group:#{payload[:group]} <-> host:#{payload[:host]}}..."
76
+ when :adding_automatic_group
77
+ fmt.puts 2, "- add automatic association {group:ungrouped <-> host:#{payload[:host]}}..."
78
+ when :ok
79
+ fmt.puts payload[:indent], '- OK'
84
80
  end
85
81
  end
82
+
83
+ def render_group_rmhost_warning(payload)
84
+ fmt.warn "Association {group:#{payload[:group]} <-> host:#{payload[:host]}} doesn't exist, skipping.\n"
85
+ end
86
+
87
+ def render_group_rmhost_missing(payload)
88
+ fmt.puts payload[:indent], "- doesn't exist, skipping."
89
+ end
86
90
  end
87
91
  end
88
92
  end
@@ -15,7 +15,7 @@ module Moose
15
15
  abort('ERROR: Wrong number of arguments, ' \
16
16
  "#{args.length} for 2 or more.")
17
17
  end
18
-
18
+
19
19
  # Convenience
20
20
  db = Moose::Inventory::DB
21
21
  fmt = Moose::Inventory::Cli::Formatter
@@ -26,8 +26,8 @@ module Moose
26
26
 
27
27
  # Transaction
28
28
  db.transaction do # Transaction start
29
- puts "Remove variable(s) '#{vars.join(",")}' from group '#{name}':"
30
-
29
+ puts "Remove variable(s) '#{vars.join(',')}' from group '#{name}':"
30
+
31
31
  fmt.puts 2, "- retrieve group '#{name}'..."
32
32
  group = db.models[:group].find(name: name)
33
33
  if group.nil?
@@ -35,7 +35,7 @@ module Moose
35
35
  "The group '#{name}' does not exist."
36
36
  end
37
37
  fmt.puts 4, '- OK'
38
-
38
+
39
39
  groupvars_ds = group.groupvars_dataset
40
40
  vars.each do |v|
41
41
  fmt.puts 2, "- remove variable '#{v}'..."
@@ -45,7 +45,7 @@ module Moose
45
45
  "Incorrect format in {#{v}}. " \
46
46
  'Expected \'key\' or \'key=value\'.'
47
47
  end
48
-
48
+
49
49
  # Check against existing associations
50
50
  groupvar = groupvars_ds[name: vararray[0]]
51
51
  unless groupvar.nil?
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moose
4
+ module Inventory
5
+ module Cli
6
+ ##
7
+ # Shared helpers for Thor command classes.
8
+ module Helpers
9
+ AUTOMATIC_GROUP = 'ungrouped'
10
+
11
+ private
12
+
13
+ def db
14
+ Moose::Inventory::DB
15
+ end
16
+
17
+ def fmt
18
+ Moose::Inventory::Cli::Formatter
19
+ end
20
+
21
+ def normalize_names(values)
22
+ values.uniq.map(&:downcase)
23
+ end
24
+
25
+ def csv_option_names(value)
26
+ (value || '').downcase.split(',').uniq
27
+ end
28
+
29
+ def abort_if_missing_args(args, minimum, label)
30
+ return unless args.length < minimum
31
+
32
+ abort("ERROR: Wrong number of arguments, #{args.length} for #{label}.")
33
+ end
34
+
35
+ def abort_if_automatic_group(names, message = nil)
36
+ return unless names.include?(AUTOMATIC_GROUP)
37
+
38
+ abort(message || "ERROR: Cannot manually manipulate the automatic group '#{AUTOMATIC_GROUP}'.")
39
+ end
40
+
41
+ def association_exists?(dataset, name)
42
+ !dataset.nil? && !dataset[name: name].nil?
43
+ end
44
+
45
+ def automatic_group
46
+ db.models[:group].find_or_create(name: AUTOMATIC_GROUP)
47
+ end
48
+
49
+ def remove_automatic_group_from_host(host, indent:, message:)
50
+ ungrouped = host.groups_dataset[name: AUTOMATIC_GROUP]
51
+ return if ungrouped.nil?
52
+
53
+ fmt.puts indent, message
54
+ host.remove_group(ungrouped)
55
+ fmt.puts indent + 2, '- OK'
56
+ end
57
+
58
+ def add_automatic_group_to_host_if_last_group(host, indent:, message:)
59
+ add_automatic_group_to_host_if_group_count(host, 1, indent: indent, message: message)
60
+ end
61
+
62
+ def add_automatic_group_to_host_if_no_groups(host, indent:, message:)
63
+ add_automatic_group_to_host_if_group_count(host, 0, indent: indent, message: message)
64
+ end
65
+
66
+ def add_automatic_group_to_host_if_group_count(host, group_count, indent:, message:)
67
+ return unless host.groups_dataset.count == group_count
68
+
69
+ fmt.puts indent, message
70
+ host.add_group(automatic_group)
71
+ fmt.puts indent + 2, '- OK'
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -2,6 +2,7 @@ require 'thor'
2
2
  require 'json'
3
3
 
4
4
  require_relative './formatter.rb'
5
+ require_relative './helpers.rb'
5
6
  require_relative '../db/exceptions.rb'
6
7
 
7
8
  module Moose
@@ -9,7 +10,9 @@ module Moose
9
10
  module Cli
10
11
  ##
11
12
  # Class implementing the "host" methods of the CLI
12
- class Host < Thor # rubocop:disable Metrics/ClassLength
13
+ class Host < Thor
14
+ include Moose::Inventory::Cli::Helpers
15
+
13
16
  require_relative 'host_add'
14
17
  require_relative 'host_get'
15
18
  require_relative 'host_list'