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.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +49 -0
- data/.github/workflows/release.yml +58 -0
- data/.gitignore +1 -1
- data/.gitleaks.toml +9 -0
- data/.rubocop.yml +19 -784
- data/BACKLOG.md +290 -0
- data/Gemfile.lock +95 -0
- data/README.md +38 -9
- data/Rakefile +1 -1
- data/bin/moose-inventory +1 -1
- data/docs/release/publishing.md +109 -0
- data/docs/release/release-readiness.md +55 -0
- data/docs/security-audit-2026-05-21.md +71 -0
- data/docs/security-audit-2026-05-26-rerun.md +75 -0
- data/docs/security-audit-2026-05-26.md +63 -0
- data/lib/moose_inventory/cli/formatter.rb +16 -17
- data/lib/moose_inventory/cli/group.rb +4 -1
- data/lib/moose_inventory/cli/group_add.rb +89 -75
- data/lib/moose_inventory/cli/group_addchild.rb +84 -71
- data/lib/moose_inventory/cli/group_addhost.rb +78 -69
- data/lib/moose_inventory/cli/group_addvar.rb +37 -37
- data/lib/moose_inventory/cli/group_get.rb +23 -26
- data/lib/moose_inventory/cli/group_list.rb +12 -15
- data/lib/moose_inventory/cli/group_listvars.rb +12 -14
- data/lib/moose_inventory/cli/group_rm.rb +104 -76
- data/lib/moose_inventory/cli/group_rmchild.rb +99 -54
- data/lib/moose_inventory/cli/group_rmhost.rb +64 -60
- data/lib/moose_inventory/cli/group_rmvar.rb +5 -5
- data/lib/moose_inventory/cli/helpers.rb +76 -0
- data/lib/moose_inventory/cli/host.rb +4 -1
- data/lib/moose_inventory/cli/host_add.rb +51 -66
- data/lib/moose_inventory/cli/host_addgroup.rb +77 -68
- data/lib/moose_inventory/cli/host_addvar.rb +6 -6
- data/lib/moose_inventory/cli/host_get.rb +15 -18
- data/lib/moose_inventory/cli/host_list.rb +3 -3
- data/lib/moose_inventory/cli/host_listvars.rb +21 -23
- data/lib/moose_inventory/cli/host_rm.rb +9 -9
- data/lib/moose_inventory/cli/host_rmgroup.rb +63 -60
- data/lib/moose_inventory/cli/host_rmvar.rb +3 -3
- data/lib/moose_inventory/config/config.rb +43 -40
- data/lib/moose_inventory/db/db.rb +92 -52
- data/lib/moose_inventory/db/models.rb +11 -12
- data/lib/moose_inventory/inventory_context.rb +50 -0
- data/lib/moose_inventory/operations/add_associations.rb +127 -0
- data/lib/moose_inventory/operations/add_groups.rb +115 -0
- data/lib/moose_inventory/operations/add_hosts.rb +110 -0
- data/lib/moose_inventory/operations/group_child_relations.rb +118 -0
- data/lib/moose_inventory/operations/group_cleanup.rb +55 -0
- data/lib/moose_inventory/operations/remove_associations.rb +101 -0
- data/lib/moose_inventory/operations/remove_groups.rb +79 -0
- data/lib/moose_inventory/version.rb +1 -1
- data/moose-inventory.gemspec +38 -20
- data/scripts/check.sh +10 -0
- data/scripts/ci/check_permissions.sh +35 -0
- data/scripts/ci/check_rubocop.sh +28 -0
- data/scripts/ci/check_secrets.sh +26 -0
- data/scripts/ci/check_security.sh +68 -0
- data/scripts/ci/install_security_tools.sh +47 -0
- data/scripts/ci/package_sanity.sh +46 -0
- data/scripts/files.rb +1 -4
- data/scripts/install_dependencies.sh +19 -0
- data/scripts/reports.sh +2 -2
- data/spec/lib/moose_inventory/cli/cli_spec.rb +13 -14
- data/spec/lib/moose_inventory/cli/group_add_spec.rb +118 -119
- data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +49 -51
- data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +80 -83
- data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +91 -91
- data/spec/lib/moose_inventory/cli/group_get_spec.rb +22 -23
- data/spec/lib/moose_inventory/cli/group_list_spec.rb +19 -20
- data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +35 -36
- data/spec/lib/moose_inventory/cli/group_rm_spec.rb +115 -78
- data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +86 -45
- data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +43 -46
- data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +131 -131
- data/spec/lib/moose_inventory/cli/group_spec.rb +9 -9
- data/spec/lib/moose_inventory/cli/host_add_spec.rb +103 -43
- data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +78 -80
- data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +122 -122
- data/spec/lib/moose_inventory/cli/host_get_spec.rb +16 -16
- data/spec/lib/moose_inventory/cli/host_list_spec.rb +8 -8
- data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +50 -52
- data/spec/lib/moose_inventory/cli/host_rm_spec.rb +12 -12
- data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +48 -51
- data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +136 -136
- data/spec/lib/moose_inventory/config/config_spec.rb +16 -3
- data/spec/lib/moose_inventory/db/db_spec.rb +386 -2
- data/spec/lib/moose_inventory/db/models_spec.rb +10 -11
- data/spec/lib/moose_inventory/operations/add_associations_spec.rb +77 -0
- data/spec/lib/moose_inventory/operations/add_groups_spec.rb +65 -0
- data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +69 -0
- data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +76 -0
- data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +57 -0
- data/spec/shared/shared_config_setup.rb +2 -2
- data/spec/spec_helper.rb +7 -8
- metadata +157 -105
- data/.coveralls.yml +0 -0
- data/Guardfile +0 -38
- data/config/dotfiles/coveralls.yml +0 -0
- data/config/dotfiles/gitignore +0 -20
- data/config/dotfiles/rubocop.yml +0 -793
- data/scripts/guard_quality.sh +0 -3
- data/scripts/guard_test.sh +0 -2
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'thor'
|
|
2
4
|
require 'json'
|
|
3
5
|
|
|
4
|
-
require_relative '
|
|
5
|
-
require_relative '../db/exceptions
|
|
6
|
+
require_relative 'formatter'
|
|
7
|
+
require_relative '../db/exceptions'
|
|
8
|
+
require_relative '../inventory_context'
|
|
9
|
+
require_relative '../operations/remove_associations'
|
|
6
10
|
|
|
7
11
|
module Moose
|
|
8
12
|
module Inventory
|
|
@@ -13,67 +17,66 @@ module Moose
|
|
|
13
17
|
#==========================
|
|
14
18
|
desc 'rmgroup HOSTNAME GROUPNAME [GROUPNAME ...]',
|
|
15
19
|
'dissociation the host from a group'
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if groups.include?('ungrouped')
|
|
34
|
-
abort 'ERROR: Cannot manually manipulate the automatic '\
|
|
35
|
-
'group \'ungrouped\'.'
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Transaction
|
|
39
|
-
db.transaction do # Transaction start
|
|
40
|
-
puts "Dissociate host '#{name}' from groups '#{groups.join(',')}':"
|
|
41
|
-
fmt.puts 2, "- Retrieve host '#{name}'..."
|
|
42
|
-
host = db.models[:host].find(name: name)
|
|
43
|
-
if host.nil?
|
|
44
|
-
fail db.exceptions[:moose],
|
|
45
|
-
"The host '#{name}' was not found in the database."
|
|
46
|
-
end
|
|
47
|
-
fmt.puts 4, '- OK'
|
|
48
|
-
|
|
49
|
-
# dissociate host from the groups
|
|
50
|
-
groups_ds = host.groups_dataset
|
|
51
|
-
groups.each do |g|
|
|
52
|
-
fmt.puts 2, "- Remove association {host:#{name} <-> group:#{g}}..."
|
|
53
|
-
|
|
54
|
-
# Check against existing associations
|
|
55
|
-
if groups_ds[name: g].nil?
|
|
56
|
-
fmt.warn "Association {host:#{name} <-> group:#{g}} doesn't exist, skipping.\n"
|
|
57
|
-
fmt.puts 4, "- Doesn't exist, skipping."
|
|
58
|
-
else
|
|
59
|
-
group = db.models[:group].find(name: g)
|
|
60
|
-
host.remove_group(group) unless group.nil?
|
|
61
|
-
end
|
|
62
|
-
fmt.puts 4, '- OK'
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Handle 'ungrouped' group automation
|
|
66
|
-
if host.groups_dataset.count == 0
|
|
67
|
-
fmt.puts 2, '- Add automatic association '\
|
|
68
|
-
"{host:#{name} <-> group:ungrouped}..."
|
|
69
|
-
ungrouped = db.models[:group].find_or_create(name: 'ungrouped')
|
|
70
|
-
host.add_group(ungrouped) unless ungrouped.nil?
|
|
71
|
-
fmt.puts 4, '- OK'
|
|
72
|
-
end
|
|
20
|
+
def rmgroup(*args)
|
|
21
|
+
abort_if_missing_args(args, 2, '2 or more')
|
|
22
|
+
|
|
23
|
+
name = args[0].downcase
|
|
24
|
+
groups = normalize_names(args.slice(1, args.length - 1))
|
|
25
|
+
|
|
26
|
+
abort_if_automatic_group(groups)
|
|
27
|
+
|
|
28
|
+
context = Moose::Inventory::InventoryContext.new(db: db)
|
|
29
|
+
operation = Moose::Inventory::Operations::RemoveAssociations.new(context: context)
|
|
30
|
+
|
|
31
|
+
db.transaction do
|
|
32
|
+
puts "Dissociate host '#{name}' from groups '#{groups.join(',')}':"
|
|
33
|
+
host = fetch_existing_host_for_rmgroup(context, name)
|
|
34
|
+
render_host_rmgroup_events(
|
|
35
|
+
operation.host_from_groups(host: host, host_name: name, group_names: groups).events
|
|
36
|
+
)
|
|
73
37
|
fmt.puts 2, '- All OK'
|
|
74
|
-
end
|
|
38
|
+
end
|
|
75
39
|
puts 'Succeeded'
|
|
76
40
|
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def fetch_existing_host_for_rmgroup(context, name)
|
|
45
|
+
fmt.puts 2, "- Retrieve host '#{name}'..."
|
|
46
|
+
host = context.find_host(name)
|
|
47
|
+
raise db.exceptions[:moose], "The host '#{name}' was not found in the database." if host.nil?
|
|
48
|
+
|
|
49
|
+
fmt.puts 4, '- OK'
|
|
50
|
+
host
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def render_host_rmgroup_events(events)
|
|
54
|
+
events.each { |event| render_host_rmgroup_event(event) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def render_host_rmgroup_event(event)
|
|
58
|
+
payload = event.payload
|
|
59
|
+
|
|
60
|
+
return render_host_rmgroup_warning(payload) if event.type == :host_group_association_missing
|
|
61
|
+
return render_host_rmgroup_missing(payload) if event.type == :missing_skipping
|
|
62
|
+
|
|
63
|
+
case event.type
|
|
64
|
+
when :removing_host_group_association
|
|
65
|
+
fmt.puts 2, "- Remove association {host:#{payload[:host]} <-> group:#{payload[:group]}}..."
|
|
66
|
+
when :adding_automatic_group
|
|
67
|
+
fmt.puts 2, "- Add automatic association {host:#{payload[:host]} <-> group:ungrouped}..."
|
|
68
|
+
when :ok
|
|
69
|
+
fmt.puts payload[:indent], '- OK'
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def render_host_rmgroup_warning(payload)
|
|
74
|
+
fmt.warn "Association {host:#{payload[:host]} <-> group:#{payload[:group]}} doesn't exist, skipping.\n"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def render_host_rmgroup_missing(payload)
|
|
78
|
+
fmt.puts payload[:indent], "- Doesn't exist, skipping."
|
|
79
|
+
end
|
|
77
80
|
end
|
|
78
81
|
end
|
|
79
82
|
end
|
|
@@ -13,7 +13,7 @@ module Moose
|
|
|
13
13
|
#==========================
|
|
14
14
|
desc 'rmvar', 'Remove a variable from the host'
|
|
15
15
|
# rubocop:disable Metrics/LineLength
|
|
16
|
-
def rmvar(*args) # rubocop:disable Metrics/
|
|
16
|
+
def rmvar(*args) # rubocop:disable Metrics/AbcSize
|
|
17
17
|
# rubocop:enableMetrics/LineLength
|
|
18
18
|
if args.length < 2
|
|
19
19
|
abort('ERROR: Wrong number of arguments, ' \
|
|
@@ -30,8 +30,8 @@ module Moose
|
|
|
30
30
|
|
|
31
31
|
# Transaction
|
|
32
32
|
db.transaction do # Transaction start
|
|
33
|
-
puts "Remove variable(s) '#{vars.join(
|
|
34
|
-
|
|
33
|
+
puts "Remove variable(s) '#{vars.join(',')}' from host '#{name}':"
|
|
34
|
+
|
|
35
35
|
fmt.puts 2, "- retrieve host '#{name}'..."
|
|
36
36
|
host = db.models[:host].find(name: name)
|
|
37
37
|
if host.nil?
|
|
@@ -41,19 +41,19 @@ module Moose
|
|
|
41
41
|
# Default is to search standard locations.
|
|
42
42
|
#
|
|
43
43
|
# --env ENV => sets the section to be used as the configuration.
|
|
44
|
-
# Defaults to "", which forces the use of the
|
|
45
|
-
# defaultenv parameter from the general section of
|
|
46
|
-
# the config file.
|
|
44
|
+
# Defaults to "", which forces the use of the
|
|
45
|
+
# defaultenv parameter from the general section of
|
|
46
|
+
# the config file.
|
|
47
47
|
#
|
|
48
48
|
# --format FORMAT=> See formatter for supported types.
|
|
49
49
|
# Defaults to json.
|
|
50
50
|
#
|
|
51
51
|
# -- trace => Enable more complete exceptions for db transactions
|
|
52
|
-
# Default is not to trace.
|
|
52
|
+
# Default is not to trace.
|
|
53
53
|
|
|
54
54
|
@_confopts = { env: '', format: 'json', ansible: false, trace: false }
|
|
55
55
|
|
|
56
|
-
# Check for two-part flags
|
|
56
|
+
# Check for two-part flags
|
|
57
57
|
%w(config env format).each do |var|
|
|
58
58
|
@_argv.each_with_index do |val, index|
|
|
59
59
|
next if val != "--#{var}"
|
|
@@ -62,7 +62,7 @@ module Moose
|
|
|
62
62
|
break
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
# Check for one-part flags
|
|
67
67
|
%w(ansible trace).each do |var|
|
|
68
68
|
@_argv.each_with_index do |val, index|
|
|
@@ -72,59 +72,58 @@ module Moose
|
|
|
72
72
|
break
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
# Sanity
|
|
77
|
-
# - Ansible output format must be json - pjson is permitted, but yaml is not.
|
|
77
|
+
# - Ansible output format must be json - pjson is permitted, but yaml is not.
|
|
78
78
|
if @_confopts[:ansible] == true
|
|
79
|
-
unless @_confopts[:format] =~ /p|pjson|j|json/
|
|
79
|
+
unless @_confopts[:format] =~ /p|pjson|j|json/
|
|
80
80
|
@_confopts[:format] = 'json'
|
|
81
|
-
end
|
|
81
|
+
end
|
|
82
82
|
end
|
|
83
|
-
|
|
84
83
|
end
|
|
85
84
|
|
|
86
85
|
#----------------------
|
|
87
86
|
def self.top_level_help
|
|
88
87
|
if @_argv[0] == 'help'
|
|
89
|
-
puts
|
|
90
|
-
printf
|
|
91
|
-
printf
|
|
92
|
-
printf
|
|
93
|
-
printf
|
|
94
|
-
printf
|
|
88
|
+
puts 'Global flags:'
|
|
89
|
+
printf ' %-31s %-10s', '--ansible', "# Force Ansible mode (automatically set when using ansible flags)\n"
|
|
90
|
+
printf ' %-31s %-10s', '--config FILE', "# Specifies a configuration file to use\n"
|
|
91
|
+
printf ' %-31s %-10s', '--env ENV', "# Specifies the environment section of the config to use\n"
|
|
92
|
+
printf ' %-31s %-10s', '--format yaml|json|pjson', "# Format for the output of 'get', 'list', and 'listvars' subcommands\n"
|
|
93
|
+
printf ' %-31s %-10s', '--trace', "# Enable more complete exception dumps for database transactions\n"
|
|
95
94
|
puts "\nAnsible flags:"
|
|
96
|
-
printf
|
|
97
|
-
printf
|
|
95
|
+
printf ' %-31s %-10s', '--host HOSTNAME', "# Retrieves host variables for the specified host (alias for 'host listvars HOSTNAME')\n"
|
|
96
|
+
printf ' %-31s %-10s', '--list', "# Retrieves the list of groups (alias for 'group list')\n\n"
|
|
98
97
|
end
|
|
99
98
|
end
|
|
100
|
-
|
|
99
|
+
|
|
101
100
|
#----------------------
|
|
102
|
-
def self.ansible_args
|
|
101
|
+
def self.ansible_args
|
|
103
102
|
#
|
|
104
103
|
# See http://docs.ansible.com/developing_inventory.html for Ansible specs
|
|
105
104
|
# for dynamic inventory sources
|
|
106
|
-
|
|
105
|
+
|
|
107
106
|
# --list => group list
|
|
108
107
|
# --host HOSTNAME => host getvars HOSTNAME
|
|
109
108
|
|
|
110
109
|
case @_argv[0]
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
end
|
|
110
|
+
when '--list'
|
|
111
|
+
@_confopts[:ansible] = true
|
|
112
|
+
@_confopts[:format] = 'json' unless @_confopts[:format] =~ /p|pjson|j|json/
|
|
113
|
+
@_argv.clear
|
|
114
|
+
@_argv.concat(%w(group list)).flatten
|
|
115
|
+
when '--host'
|
|
116
|
+
@_confopts[:ansible] = true
|
|
117
|
+
@_confopts[:format] = 'json' unless @_confopts[:format] =~ /p|pjson|j|json/
|
|
118
|
+
host = @_argv[1]
|
|
119
|
+
@_argv.clear
|
|
120
|
+
@_argv.concat(['host', 'listvars', host.to_s]).flatten
|
|
121
|
+
end
|
|
123
122
|
end
|
|
124
123
|
|
|
125
124
|
#----------------------
|
|
126
|
-
def self.resolve_config_file
|
|
127
|
-
if
|
|
125
|
+
def self.resolve_config_file
|
|
126
|
+
if !@_confopts[:config].nil?
|
|
128
127
|
path = File.expand_path(@_confopts[:config])
|
|
129
128
|
if File.exist?(path)
|
|
130
129
|
@_confopts[:config] = path
|
|
@@ -135,8 +134,7 @@ module Moose
|
|
|
135
134
|
possibles = ['./.moose-tools/inventory/config',
|
|
136
135
|
'~/.moose-tools/inventory/config',
|
|
137
136
|
'~/local/etc/moose-tools/inventory/config',
|
|
138
|
-
'/etc/moose-tools/inventory/config'
|
|
139
|
-
]
|
|
137
|
+
'/etc/moose-tools/inventory/config']
|
|
140
138
|
possibles.each do |f|
|
|
141
139
|
file = File.expand_path(f)
|
|
142
140
|
@_confopts[:config] = file if File.exist?(file)
|
|
@@ -170,7 +168,12 @@ module Moose
|
|
|
170
168
|
# rubocop:disable PerceivedComplexity
|
|
171
169
|
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
|
|
172
170
|
def self.load
|
|
173
|
-
newsets = symbolize_keys(YAML.
|
|
171
|
+
newsets = symbolize_keys(YAML.safe_load_file(
|
|
172
|
+
@_confopts[:config],
|
|
173
|
+
aliases: false,
|
|
174
|
+
permitted_classes: [],
|
|
175
|
+
permitted_symbols: []
|
|
176
|
+
))
|
|
174
177
|
|
|
175
178
|
path = @_confopts[:config]
|
|
176
179
|
|
|
@@ -184,7 +187,7 @@ module Moose
|
|
|
184
187
|
env = @_confopts[:env]
|
|
185
188
|
@_settings[:config] = newsets[@_confopts[:env].to_sym]
|
|
186
189
|
else
|
|
187
|
-
env
|
|
190
|
+
env = @_settings[:general][:defaultenv]
|
|
188
191
|
(env.nil? || env.empty?) && fail("No defaultenv set in #{path}")
|
|
189
192
|
@_settings[:config] = newsets[env.to_sym]
|
|
190
193
|
end
|
|
@@ -22,6 +22,8 @@ module Moose
|
|
|
22
22
|
|
|
23
23
|
#----------------------
|
|
24
24
|
def self.init
|
|
25
|
+
init_exceptions
|
|
26
|
+
|
|
25
27
|
# If we allow init more than once, then the db connection is remade,
|
|
26
28
|
# which changes Sequel:DATABASES[0], thereby invalidating the sequel
|
|
27
29
|
# models. This causes unexpected behavour. That is to say, because
|
|
@@ -31,8 +33,8 @@ module Moose
|
|
|
31
33
|
# So, we allow init only once, gated by whether @db is nil. In effect,
|
|
32
34
|
# this means we pool the DB connection for the life of the application.
|
|
33
35
|
# Again, not a problem for our one-shot app, but it may be an issue in
|
|
34
|
-
# long-running code. Personally, I don't like this pooling regime -
|
|
35
|
-
# perhaps I'm not understanding how it's supposed to be used?
|
|
36
|
+
# long-running code. Personally, I don't like this pooling regime -
|
|
37
|
+
# perhaps I'm not understanding how it's supposed to be used?
|
|
36
38
|
#
|
|
37
39
|
# QUESTION: can the models be refreshed, to make then again valid? What if
|
|
38
40
|
# we "load" instead of "require" the models?
|
|
@@ -57,55 +59,58 @@ module Moose
|
|
|
57
59
|
@models[:group] = Moose::Inventory::DB::Group
|
|
58
60
|
@models[:groupvar] = Moose::Inventory::DB::Groupvar
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
@exceptions[:moose] = Moose::Inventory::DB::MooseDBException
|
|
62
|
+
end
|
|
62
63
|
|
|
64
|
+
#--------------------
|
|
65
|
+
def self.init_exceptions
|
|
66
|
+
@exceptions ||= {}
|
|
67
|
+
@exceptions[:moose] ||= Moose::Inventory::DB::MooseDBException
|
|
63
68
|
end
|
|
64
69
|
|
|
65
70
|
#--------------------
|
|
66
71
|
def self.transaction
|
|
67
72
|
fail('Database connection has not been established') if @db.nil?
|
|
68
|
-
|
|
73
|
+
|
|
69
74
|
tries = 0
|
|
70
|
-
|
|
75
|
+
|
|
71
76
|
begin
|
|
72
77
|
@db.transaction(savepoint: true) do
|
|
73
78
|
yield
|
|
74
79
|
end
|
|
75
80
|
|
|
76
81
|
rescue Sequel::DatabaseError => e
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
# We want to rescue Sqlite3::BusyException. But, sequel catches that
|
|
83
|
+
# and re-raises it as Sequel::DatabaseError, with a message referencing
|
|
84
|
+
# the original exception class
|
|
85
|
+
|
|
86
|
+
# We look into e, to see whether it is a BusyException. If not,
|
|
87
|
+
# we re-raise immediately.
|
|
88
|
+
raise unless e.message.include?('BusyException')
|
|
89
|
+
|
|
90
|
+
# Looks like a BusyException, so we retry, after a random delay.
|
|
91
|
+
tries += 1
|
|
92
|
+
case tries
|
|
93
|
+
when 1..10
|
|
94
|
+
if Moose::Inventory::Config._confopts[:trace] == true
|
|
95
|
+
$stderr.puts e.message
|
|
96
|
+
end
|
|
97
|
+
sleep rand
|
|
98
|
+
retry
|
|
99
|
+
else
|
|
100
|
+
warn('The database appears to be locked by another process, and '\
|
|
101
|
+
" did not become free after #{tries} tries. Giving up. ")
|
|
102
|
+
|
|
103
|
+
# TODO: Some useful advice to the user, as to what to do about this error.
|
|
104
|
+
raise
|
|
91
105
|
end
|
|
92
|
-
sleep rand()
|
|
93
|
-
retry
|
|
94
|
-
else
|
|
95
|
-
warn('The database appears to be locked by another process, and '\
|
|
96
|
-
" did not become free after #{tries} tries. Giving up. ")
|
|
97
106
|
|
|
98
|
-
# TODO: Some useful advice to the user, as to what to do about this error.
|
|
99
|
-
raise
|
|
100
|
-
end
|
|
101
|
-
|
|
102
107
|
rescue @exceptions[:moose] => e
|
|
103
108
|
warn 'An error occurred during a transaction, any changes have been rolled back.'
|
|
104
109
|
|
|
105
110
|
if Moose::Inventory::Config._confopts[:trace] == true
|
|
106
|
-
|
|
107
|
-
abort("ERROR: #{e}")
|
|
108
|
-
else
|
|
111
|
+
$stderr.puts e.full_message(highlight: false, order: :top)
|
|
112
|
+
abort("ERROR: #{e.message}")
|
|
113
|
+
else
|
|
109
114
|
abort("ERROR: #{e.message}")
|
|
110
115
|
end
|
|
111
116
|
|
|
@@ -158,7 +163,7 @@ module Moose
|
|
|
158
163
|
|
|
159
164
|
else
|
|
160
165
|
@db.drop_table(:hosts, :hostvars,
|
|
161
|
-
:groups,
|
|
166
|
+
:groups, :groupvars, :group_hosts,
|
|
162
167
|
if_exists: true, cascade: true)
|
|
163
168
|
end
|
|
164
169
|
end
|
|
@@ -195,7 +200,7 @@ module Moose
|
|
|
195
200
|
foreign_key :child_id, :groups
|
|
196
201
|
end
|
|
197
202
|
end
|
|
198
|
-
|
|
203
|
+
|
|
199
204
|
unless @db.table_exists? :groupvars
|
|
200
205
|
@db.create_table(:groupvars) do
|
|
201
206
|
primary_key :id
|
|
@@ -225,28 +230,29 @@ module Moose
|
|
|
225
230
|
when 'sqlite3'
|
|
226
231
|
init_sqlite3
|
|
227
232
|
|
|
228
|
-
when '
|
|
233
|
+
when 'mysql'
|
|
229
234
|
init_mysql
|
|
230
235
|
|
|
231
236
|
when 'postgresql'
|
|
232
237
|
init_postgresql
|
|
233
238
|
|
|
234
239
|
else
|
|
235
|
-
fail @exceptions[:moose
|
|
236
|
-
|
|
240
|
+
fail @exceptions[:moose],
|
|
241
|
+
"database adapter #{adapter} is not yet supported."
|
|
237
242
|
end
|
|
238
243
|
end
|
|
239
244
|
|
|
240
245
|
#--------------------
|
|
241
246
|
def self.init_sqlite3 # rubocop:disable Metrics/AbcSize
|
|
242
247
|
require 'sqlite3'
|
|
248
|
+
require 'fileutils'
|
|
243
249
|
|
|
244
250
|
# Quick check that expected keys are at least present & sensible
|
|
245
251
|
config = Moose::Inventory::Config._settings[:config][:db]
|
|
246
252
|
[:file].each do |key|
|
|
247
253
|
if config[key].nil?
|
|
248
|
-
fail @exceptions[:moose
|
|
249
|
-
|
|
254
|
+
fail @exceptions[:moose],
|
|
255
|
+
"Expected key #{key} missing in sqlite3 configuration"
|
|
250
256
|
end
|
|
251
257
|
end
|
|
252
258
|
config[:file].empty? && fail("SQLite3 DB 'file' cannot be empty")
|
|
@@ -254,7 +260,7 @@ module Moose
|
|
|
254
260
|
# Make sure the directory exists
|
|
255
261
|
dbfile = File.expand_path(config[:file])
|
|
256
262
|
dbdir = File.dirname(dbfile)
|
|
257
|
-
|
|
263
|
+
FileUtils.mkdir_p(dbdir) unless Dir.exist?(dbdir)
|
|
258
264
|
|
|
259
265
|
# Create and/or open the database file
|
|
260
266
|
@db = Sequel.sqlite(dbfile)
|
|
@@ -262,26 +268,60 @@ module Moose
|
|
|
262
268
|
|
|
263
269
|
#--------------------
|
|
264
270
|
def self.init_mysql
|
|
265
|
-
require '
|
|
271
|
+
require 'mysql2'
|
|
266
272
|
|
|
267
|
-
#
|
|
268
|
-
|
|
269
|
-
|
|
273
|
+
# Quick check that expected keys are at least present
|
|
274
|
+
config = Moose::Inventory::Config._settings[:config][:db]
|
|
275
|
+
[:host, :database, :user].each do |key|
|
|
276
|
+
if config[key].nil?
|
|
277
|
+
fail @exceptions[:moose],
|
|
278
|
+
"Expected key #{key} missing in mysql configuration"
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
password = db_password(config, 'mysql')
|
|
282
|
+
|
|
283
|
+
@db = Sequel.mysql2(user: config[:user],
|
|
284
|
+
password: password,
|
|
285
|
+
host: config[:host],
|
|
286
|
+
database: config[:database])
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
#--------------------
|
|
290
|
+
def self.init_postgresql
|
|
291
|
+
require 'pg'
|
|
270
292
|
|
|
271
293
|
# Quick check that expected keys are at least present
|
|
272
294
|
config = Moose::Inventory::Config._settings[:config][:db]
|
|
273
|
-
[:host, :database, :user
|
|
295
|
+
[:host, :database, :user].each do |key|
|
|
274
296
|
if config[key].nil?
|
|
275
|
-
fail @exceptions[:moose
|
|
276
|
-
|
|
297
|
+
fail @exceptions[:moose],
|
|
298
|
+
"Expected key #{key} missing in postgresql configuration"
|
|
277
299
|
end
|
|
278
300
|
end
|
|
301
|
+
password = db_password(config, 'postgresql')
|
|
302
|
+
|
|
303
|
+
@db = Sequel.postgres(user: config[:user],
|
|
304
|
+
password: password,
|
|
305
|
+
host: config[:host],
|
|
306
|
+
database: config[:database])
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
#--------------------
|
|
310
|
+
def self.db_password(config, adapter)
|
|
311
|
+
return config[:password] unless config[:password].nil?
|
|
312
|
+
|
|
313
|
+
if config[:password_env].nil?
|
|
314
|
+
fail @exceptions[:moose],
|
|
315
|
+
"Expected key password or password_env missing in #{adapter} configuration"
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
password = ENV.fetch(config[:password_env].to_s, nil)
|
|
319
|
+
if password.nil? || password.empty?
|
|
320
|
+
fail @exceptions[:moose],
|
|
321
|
+
"Environment variable #{config[:password_env]} is not set for #{adapter} password"
|
|
322
|
+
end
|
|
279
323
|
|
|
280
|
-
|
|
281
|
-
password: config[:password],
|
|
282
|
-
host: config[:host],
|
|
283
|
-
database: config[:database]
|
|
284
|
-
)
|
|
324
|
+
password
|
|
285
325
|
end
|
|
286
326
|
end
|
|
287
327
|
end
|
|
@@ -11,20 +11,19 @@ module Moose
|
|
|
11
11
|
##
|
|
12
12
|
# Model for the groups table
|
|
13
13
|
class Group < Sequel::Model
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
:
|
|
17
|
-
:
|
|
18
|
-
:class=>self
|
|
19
|
-
|
|
20
|
-
many_to_many :children,
|
|
21
|
-
:left_key=>:child_id,
|
|
22
|
-
:right_key=>:parent_id,
|
|
23
|
-
:class=>self
|
|
14
|
+
many_to_many :parents,
|
|
15
|
+
left_key: :parent_id,
|
|
16
|
+
right_key: :child_id,
|
|
17
|
+
class: self
|
|
24
18
|
|
|
25
|
-
many_to_many :
|
|
19
|
+
many_to_many :children,
|
|
20
|
+
left_key: :child_id,
|
|
21
|
+
right_key: :parent_id,
|
|
22
|
+
class: self
|
|
23
|
+
|
|
24
|
+
many_to_many :hosts
|
|
26
25
|
one_to_many :groupvars
|
|
27
|
-
end
|
|
26
|
+
end
|
|
28
27
|
|
|
29
28
|
##
|
|
30
29
|
# Model for the hostvars table
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moose
|
|
4
|
+
module Inventory
|
|
5
|
+
##
|
|
6
|
+
# Thin facade over the current DB singleton.
|
|
7
|
+
#
|
|
8
|
+
# This gives new operation/service objects a small inventory-facing seam
|
|
9
|
+
# without forcing the legacy CLI to stop using the DB singleton all at once.
|
|
10
|
+
class InventoryContext
|
|
11
|
+
AUTOMATIC_GROUP = 'ungrouped'
|
|
12
|
+
|
|
13
|
+
def initialize(db: Moose::Inventory::DB)
|
|
14
|
+
@db = db
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def transaction(&)
|
|
18
|
+
db.transaction(&)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def find_host(name)
|
|
22
|
+
db.models[:host].find(name: name)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create_host(name)
|
|
26
|
+
db.models[:host].create(name: name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def find_group(name)
|
|
30
|
+
db.models[:group].find(name: name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def create_group(name)
|
|
34
|
+
db.models[:group].create(name: name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def find_or_create_group(name)
|
|
38
|
+
db.models[:group].find_or_create(name: name)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def automatic_group
|
|
42
|
+
find_or_create_group(AUTOMATIC_GROUP)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
attr_reader :db
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|