moose-inventory 1.0.7 → 1.0.9

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 (81) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +35 -0
  3. data/.gitignore +1 -1
  4. data/BACKLOG.md +184 -0
  5. data/Gemfile.lock +60 -0
  6. data/README.md +23 -5
  7. data/bin/moose-inventory +1 -1
  8. data/docs/release/publishing.md +113 -0
  9. data/docs/release/release-readiness.md +41 -0
  10. data/docs/security-audit-2026-05-21.md +71 -0
  11. data/lib/moose_inventory/cli/formatter.rb +16 -17
  12. data/lib/moose_inventory/cli/group.rb +1 -1
  13. data/lib/moose_inventory/cli/group_add.rb +19 -21
  14. data/lib/moose_inventory/cli/group_addchild.rb +36 -40
  15. data/lib/moose_inventory/cli/group_addhost.rb +14 -18
  16. data/lib/moose_inventory/cli/group_addvar.rb +37 -37
  17. data/lib/moose_inventory/cli/group_get.rb +23 -26
  18. data/lib/moose_inventory/cli/group_list.rb +12 -15
  19. data/lib/moose_inventory/cli/group_listvars.rb +12 -14
  20. data/lib/moose_inventory/cli/group_rm.rb +36 -21
  21. data/lib/moose_inventory/cli/group_rmchild.rb +5 -6
  22. data/lib/moose_inventory/cli/group_rmhost.rb +12 -16
  23. data/lib/moose_inventory/cli/group_rmvar.rb +5 -5
  24. data/lib/moose_inventory/cli/host.rb +1 -1
  25. data/lib/moose_inventory/cli/host_add.rb +18 -18
  26. data/lib/moose_inventory/cli/host_addgroup.rb +9 -9
  27. data/lib/moose_inventory/cli/host_addvar.rb +6 -6
  28. data/lib/moose_inventory/cli/host_get.rb +15 -18
  29. data/lib/moose_inventory/cli/host_list.rb +3 -3
  30. data/lib/moose_inventory/cli/host_listvars.rb +21 -23
  31. data/lib/moose_inventory/cli/host_rm.rb +9 -9
  32. data/lib/moose_inventory/cli/host_rmgroup.rb +5 -5
  33. data/lib/moose_inventory/cli/host_rmvar.rb +3 -3
  34. data/lib/moose_inventory/config/config.rb +43 -40
  35. data/lib/moose_inventory/db/db.rb +70 -50
  36. data/lib/moose_inventory/db/models.rb +11 -12
  37. data/lib/moose_inventory/version.rb +1 -1
  38. data/moose-inventory.gemspec +35 -20
  39. data/scripts/check.sh +8 -0
  40. data/scripts/ci/check_permissions.sh +32 -0
  41. data/scripts/ci/check_security.sh +50 -0
  42. data/scripts/ci/package_sanity.sh +46 -0
  43. data/scripts/files.rb +1 -4
  44. data/scripts/install_dependencies.sh +17 -0
  45. data/scripts/reports.sh +2 -2
  46. data/spec/lib/moose_inventory/cli/cli_spec.rb +13 -14
  47. data/spec/lib/moose_inventory/cli/group_add_spec.rb +118 -119
  48. data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +49 -51
  49. data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +80 -83
  50. data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +91 -91
  51. data/spec/lib/moose_inventory/cli/group_get_spec.rb +22 -23
  52. data/spec/lib/moose_inventory/cli/group_list_spec.rb +19 -20
  53. data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +35 -36
  54. data/spec/lib/moose_inventory/cli/group_rm_spec.rb +103 -49
  55. data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +41 -45
  56. data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +43 -46
  57. data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +131 -131
  58. data/spec/lib/moose_inventory/cli/group_spec.rb +9 -9
  59. data/spec/lib/moose_inventory/cli/host_add_spec.rb +103 -43
  60. data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +78 -80
  61. data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +122 -122
  62. data/spec/lib/moose_inventory/cli/host_get_spec.rb +16 -16
  63. data/spec/lib/moose_inventory/cli/host_list_spec.rb +8 -8
  64. data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +50 -52
  65. data/spec/lib/moose_inventory/cli/host_rm_spec.rb +12 -12
  66. data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +48 -51
  67. data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +136 -136
  68. data/spec/lib/moose_inventory/config/config_spec.rb +16 -3
  69. data/spec/lib/moose_inventory/db/db_spec.rb +224 -2
  70. data/spec/lib/moose_inventory/db/models_spec.rb +10 -11
  71. data/spec/shared/shared_config_setup.rb +2 -2
  72. data/spec/spec_helper.rb +7 -8
  73. metadata +99 -136
  74. data/.coveralls.yml +0 -0
  75. data/.rubocop.yml +0 -793
  76. data/Guardfile +0 -38
  77. data/config/dotfiles/coveralls.yml +0 -0
  78. data/config/dotfiles/gitignore +0 -20
  79. data/config/dotfiles/rubocop.yml +0 -793
  80. data/scripts/guard_quality.sh +0 -3
  81. data/scripts/guard_test.sh +0 -2
@@ -20,7 +20,7 @@ module Moose
20
20
  abort('ERROR: Wrong number of arguments, '\
21
21
  "#{args.length} for 2 or more.")
22
22
  end
23
-
23
+
24
24
  # Convenience
25
25
  db = Moose::Inventory::DB
26
26
  fmt = Moose::Inventory::Cli::Formatter
@@ -34,10 +34,10 @@ module Moose
34
34
  abort 'ERROR: Cannot manually manipulate the automatic '\
35
35
  'group \'ungrouped\'.'
36
36
  end
37
-
37
+
38
38
  # Transaction
39
39
  db.transaction do # Transaction start
40
- puts "Dissociate host '#{name}' from groups '#{groups.join(',')}':"
40
+ puts "Dissociate host '#{name}' from groups '#{groups.join(',')}':"
41
41
  fmt.puts 2, "- Retrieve host '#{name}'..."
42
42
  host = db.models[:host].find(name: name)
43
43
  if host.nil?
@@ -54,7 +54,7 @@ module Moose
54
54
  # Check against existing associations
55
55
  if groups_ds[name: g].nil?
56
56
  fmt.warn "Association {host:#{name} <-> group:#{g}} doesn't exist, skipping.\n"
57
- fmt.puts 4, "- Doesn't exist, skipping."
57
+ fmt.puts 4, "- Doesn't exist, skipping."
58
58
  else
59
59
  group = db.models[:group].find(name: g)
60
60
  host.remove_group(group) unless group.nil?
@@ -66,7 +66,7 @@ module Moose
66
66
  if host.groups_dataset.count == 0
67
67
  fmt.puts 2, '- Add automatic association '\
68
68
  "{host:#{name} <-> group:ungrouped}..."
69
- ungrouped = db.models[:group].find_or_create(name: 'ungrouped')
69
+ ungrouped = db.models[:group].find_or_create(name: 'ungrouped')
70
70
  host.add_group(ungrouped) unless ungrouped.nil?
71
71
  fmt.puts 4, '- OK'
72
72
  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/CyclomaticComplexity, Metrics/AbcSize
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(",")}' from host '#{name}':"
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 "Global flags:"
90
- printf " %-31s %-10s", "--ansible", "# Force Ansible mode (automatically set when using ansible flags)\n"
91
- printf " %-31s %-10s", "--config FILE", "# Specifies a configuration file to use\n"
92
- printf " %-31s %-10s", "--env ENV", "# Specifies the environment section of the config to use\n"
93
- printf " %-31s %-10s", "--format yaml|json|pjson", "# Format for the output of 'get', 'list', and 'listvars' subcommands\n"
94
- printf " %-31s %-10s", "--trace", "# Enable more complete exception dumps for database transactions\n"
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 " %-31s %-10s", "--host HOSTNAME", "# Retrieves host variables for the specified host (alias for 'host listvars HOSTNAME')\n"
97
- printf " %-31s %-10s", "--list", "# Retrieves the list of groups (alias for 'group list')\n\n"
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 # rubocop:disable Metrics/AbcSize
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
- when '--list'
112
- @_confopts[:ansible] = true
113
- @_confopts[:format] = 'json' unless @_confopts[:format] =~ /p|pjson|j|json/
114
- @_argv.clear
115
- @_argv.concat(['group', 'list']).flatten
116
- when '--host'
117
- @_confopts[:ansible] = true
118
- @_confopts[:format] = 'json' unless @_confopts[:format] =~ /p|pjson|j|json/
119
- host = @_argv[1]
120
- @_argv.clear
121
- @_argv.concat(['host', 'listvars', "#{host}"]).flatten
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 # rubocop:disable Metrics/AbcSize
127
- if ! @_confopts[:config].nil?
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.load_file(@_confopts[:config]))
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 = @_settings[:general][:defaultenv]
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
- @exceptions = {}
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
- # We want to rescue Sqlite3::BusyException. But, sequel catches that
78
- # and re-raises it as Sequel::DatabaseError, with a message referencing
79
- # the original exception class
80
-
81
- # We look into e, to see whether it is a BusyException. If not,
82
- # we re-raise immediately.
83
- raise unless e.message.include?("BusyException")
84
-
85
- # Looks like a BusyException, so we retry, after a random delay.
86
- tries += 1
87
- case tries
88
- when 1..10
89
- if Moose::Inventory::Config._confopts[:trace] == true
90
- STDERR.puts e.message
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
- STDERR.puts $!.backtrace
111
+ STDERR.puts $ERROR_INFO.backtrace
107
112
  abort("ERROR: #{e}")
108
- else
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, :groupvars, :group_hosts,
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 'msqsql'
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
- "database adapter #{adapter} is not yet supported."
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
- "Expected key #{key} missing in sqlite3 configuration"
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
- Dir.mkdir(dbdir) unless Dir.exist?(dbdir)
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,40 @@ module Moose
262
268
 
263
269
  #--------------------
264
270
  def self.init_mysql
265
- require 'mysql'
271
+ require 'mysql2'
266
272
 
267
- # TODO: native MySQL driver vs the pure ruby one?
268
- # Sequel requires the native on.
269
- # gem('mysql')
273
+ # Quick check that expected keys are at least present
274
+ config = Moose::Inventory::Config._settings[:config][:db]
275
+ [:host, :database, :user, :password].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
+
282
+ @db = Sequel.mysql2(user: config[:user],
283
+ password: config[:password],
284
+ host: config[:host],
285
+ database: config[:database])
286
+ end
287
+
288
+ #--------------------
289
+ def self.init_postgresql
290
+ require 'pg'
270
291
 
271
292
  # Quick check that expected keys are at least present
272
293
  config = Moose::Inventory::Config._settings[:config][:db]
273
294
  [:host, :database, :user, :password].each do |key|
274
295
  if config[key].nil?
275
- fail @exceptions[:moose ],
276
- "Expected key #{key} missing in mysql configuration"
296
+ fail @exceptions[:moose],
297
+ "Expected key #{key} missing in postgresql configuration"
277
298
  end
278
299
  end
279
300
 
280
- @db = Sequel.mysql(user: config[:user],
281
- password: config[:password],
282
- host: config[:host],
283
- database: config[:database]
284
- )
301
+ @db = Sequel.postgres(user: config[:user],
302
+ password: config[:password],
303
+ host: config[:host],
304
+ database: config[:database])
285
305
  end
286
306
  end
287
307
  end
@@ -11,20 +11,19 @@ module Moose
11
11
  ##
12
12
  # Model for the groups table
13
13
  class Group < Sequel::Model
14
-
15
- many_to_many :parents,
16
- :left_key=>:parent_id,
17
- :right_key=>:child_id,
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 :hosts
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
@@ -2,6 +2,6 @@ module Moose
2
2
  ##
3
3
  # The Moose-Tools dynamic inventory management library
4
4
  module Inventory
5
- VERSION = '1.0.7'
5
+ VERSION = '1.0.9'.freeze
6
6
  end
7
7
  end
@@ -16,30 +16,45 @@ Gem::Specification.new do |spec|
16
16
  # rubocop:enable Metrics/LineLength
17
17
  spec.homepage = 'https://github.com/RusDavies/moose-inventory'
18
18
  spec.license = 'MIT'
19
+ spec.required_ruby_version = '>= 3.2'
19
20
 
20
21
  spec.files = `git ls-files -z`.split("\x0")
21
22
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
23
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
24
  spec.require_paths = ['lib']
24
25
 
25
- spec.add_runtime_dependency 'indentation', '~> 0.1'
26
- spec.add_runtime_dependency 'json', '~>1.8'
27
- spec.add_runtime_dependency 'mysql', '~>2.9'
28
- # spec.add_runtime_dependency 'mysql2', '~>0.3'
29
- spec.add_runtime_dependency 'pg', '~>0.17'
30
- spec.add_runtime_dependency 'sequel', '~>4.22'
31
- spec.add_runtime_dependency 'sqlite3', '~>1.3'
32
- spec.add_runtime_dependency 'thor', '~>0.19'
33
- # spec.add_runtime_dependency 'yaml', '~>1.0'
34
-
35
- spec.add_development_dependency 'bundler', '~> 1.10'
36
- spec.add_development_dependency 'coveralls', '~> 0.8'
37
- spec.add_development_dependency 'hitimes', '~> 1.2'
38
- spec.add_development_dependency 'guard', '~> 2.12'
39
- spec.add_development_dependency 'guard-rspec', '~> 4.5'
40
- spec.add_development_dependency 'guard-rubocop', '~> 1.2'
41
- spec.add_development_dependency 'rake', '~> 10.1'
42
- spec.add_development_dependency 'rspec', '~>3.2'
43
- spec.add_development_dependency 'rubocop', '>= 0.19'
44
- spec.add_development_dependency 'simplecov', '~> 0.10'
26
+ # spec.add_runtime_dependency 'indentation', '~> 0.1'
27
+ # spec.add_runtime_dependency 'json', '~>1.8'
28
+ # #spec.add_runtime_dependency 'mysql', '~>2.9' # This causes lots of problems. Need to migrate to the newer mysql2.
29
+ # #spec.add_runtime_dependency 'mysql2', '~>0.3'
30
+ # spec.add_runtime_dependency 'mysql2'
31
+ # spec.add_runtime_dependency 'pg', '~>0.17'
32
+ # spec.add_runtime_dependency 'sequel', '~>4.22'
33
+ # spec.add_runtime_dependency 'sqlite3', '~>1.3'
34
+ # spec.add_runtime_dependency 'thor', '~>0.19'
35
+ # # spec.add_runtime_dependency 'yaml', '~>1.0'
36
+
37
+ # spec.add_development_dependency 'bundler', '~> 1.7'
38
+ # spec.add_development_dependency 'coveralls', '~> 0.8'
39
+ # spec.add_development_dependency 'guard', '~> 2.12'
40
+ # spec.add_development_dependency 'guard-rspec', '~> 4.5'
41
+ # spec.add_development_dependency 'guard-rubocop', '~> 1.2'
42
+ # spec.add_development_dependency 'rake', '~> 10.1'
43
+ # spec.add_development_dependency 'rspec', '~>3.2'
44
+ # spec.add_development_dependency 'rubocop', '>= 0.19'
45
+ # spec.add_development_dependency 'simplecov', '~> 0.10'
46
+
47
+ spec.add_runtime_dependency 'indentation', '~> 0'
48
+ spec.add_runtime_dependency 'json', '>= 2.7', '< 3'
49
+ spec.add_runtime_dependency 'mysql2', '>= 0.5.7', '< 0.6'
50
+ spec.add_runtime_dependency 'pg', '>= 1.5', '< 2'
51
+ spec.add_runtime_dependency 'sequel', '>= 5.80', '< 6'
52
+ spec.add_runtime_dependency 'sqlite3', '>= 1.7', '< 3'
53
+ spec.add_runtime_dependency 'thor', '>= 1.3', '< 2'
54
+
55
+ spec.add_development_dependency 'bundler', '>= 2.2.33', '< 3'
56
+ spec.add_development_dependency 'rake', '>= 13.0', '< 14'
57
+ spec.add_development_dependency 'rspec', '~> 3'
58
+ spec.add_development_dependency 'simplecov', '~> 0'
59
+
45
60
  end
data/scripts/check.sh ADDED
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ bundle exec rspec --format progress
5
+ git diff --check
6
+ scripts/ci/check_permissions.sh
7
+ scripts/ci/check_security.sh
8
+ scripts/ci/package_sanity.sh
@@ -0,0 +1,32 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ allowed_executables=(
5
+ "bin/moose-inventory"
6
+ "scripts/check.sh"
7
+ "scripts/ci/check_permissions.sh"
8
+ "scripts/ci/check_security.sh"
9
+ "scripts/ci/package_sanity.sh"
10
+ "scripts/files.rb"
11
+ "scripts/install_dependencies.sh"
12
+ "scripts/reports.sh"
13
+ "scripts/work-through.sh"
14
+ )
15
+
16
+ allowed_file="$(mktemp)"
17
+ actual_file="$(mktemp)"
18
+ trap 'rm -f "$allowed_file" "$actual_file"' EXIT
19
+
20
+ printf '%s\n' "${allowed_executables[@]}" | sort > "$allowed_file"
21
+
22
+ git ls-files -z | while IFS= read -r -d '' path; do
23
+ if [[ -x "$path" ]]; then
24
+ printf '%s\n' "$path"
25
+ fi
26
+ done | sort > "$actual_file"
27
+
28
+ if ! diff -u "$allowed_file" "$actual_file"; then
29
+ echo "Unexpected executable file permissions detected." >&2
30
+ echo "Update scripts/ci/check_permissions.sh only when a new executable entrypoint is intentional." >&2
31
+ exit 1
32
+ fi
@@ -0,0 +1,50 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ python3 - <<'PY'
5
+ import json
6
+ import sys
7
+ import urllib.error
8
+ import urllib.request
9
+
10
+ specs = []
11
+ for line in open('Gemfile.lock', encoding='utf-8'):
12
+ item = line.strip()
13
+ if not item or ' (' not in item:
14
+ continue
15
+ if item.startswith('moose-inventory'):
16
+ continue
17
+ name = item.split(' (', 1)[0]
18
+ version = item.split(' (', 1)[1].split(')', 1)[0].split('-', 1)[0]
19
+ if name and name[0].isalpha():
20
+ specs.append((name, version))
21
+
22
+ queries = [
23
+ {'package': {'name': name, 'ecosystem': 'RubyGems'}, 'version': version}
24
+ for name, version in specs
25
+ ]
26
+
27
+ request = urllib.request.Request(
28
+ 'https://api.osv.dev/v1/querybatch',
29
+ data=json.dumps({'queries': queries}).encode('utf-8'),
30
+ headers={'Content-Type': 'application/json'},
31
+ )
32
+
33
+ try:
34
+ with urllib.request.urlopen(request, timeout=30) as response:
35
+ data = json.load(response)
36
+ except (urllib.error.URLError, TimeoutError) as exc:
37
+ print(f'OSV dependency check failed: {exc}', file=sys.stderr)
38
+ sys.exit(2)
39
+
40
+ findings = []
41
+ for (name, version), result in zip(specs, data.get('results', [])):
42
+ for vuln in result.get('vulns') or []:
43
+ findings.append((name, version, vuln.get('id', 'unknown'), vuln.get('summary') or ''))
44
+
45
+ print(f'OSV dependency check: queried={len(specs)} vulnerable={len(findings)}')
46
+ if findings:
47
+ for name, version, vuln_id, summary in findings:
48
+ print(f'- {name} {version}: {vuln_id} {summary}', file=sys.stderr)
49
+ sys.exit(1)
50
+ PY