foreman_maintain 0.3.6 → 0.4.1

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 (62) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +30 -16
  3. data/bin/passenger-recycler +22 -22
  4. data/config/foreman_maintain.yml.example +7 -0
  5. data/config/foreman_maintain.yml.packaging +4 -0
  6. data/definitions/checks/candlepin/db_up.rb +1 -1
  7. data/definitions/checks/check_hotfix_installed.rb +90 -0
  8. data/definitions/checks/check_tmout.rb +20 -0
  9. data/definitions/checks/disk/performance.rb +1 -11
  10. data/definitions/checks/maintenance_mode/check_consistency.rb +62 -0
  11. data/definitions/checks/puppet/verify_no_empty_cacert_requests.rb +24 -0
  12. data/definitions/checks/repositories/check_upstream_repository.rb +22 -0
  13. data/definitions/checks/repositories/validate.rb +2 -2
  14. data/definitions/features/cron.rb +29 -0
  15. data/definitions/features/downstream.rb +3 -3
  16. data/definitions/features/foreman_1_7_x.rb +0 -35
  17. data/definitions/features/foreman_database.rb +0 -6
  18. data/definitions/features/iptables.rb +58 -0
  19. data/definitions/features/mongo.rb +7 -9
  20. data/definitions/features/pulp.rb +2 -4
  21. data/definitions/features/puppet_server.rb +45 -4
  22. data/definitions/features/service.rb +33 -2
  23. data/definitions/features/sync_plans.rb +50 -25
  24. data/definitions/features/system_repos.rb +50 -0
  25. data/definitions/features/tar.rb +1 -0
  26. data/definitions/procedures/backup/config_files.rb +1 -4
  27. data/definitions/procedures/backup/offline/mongo.rb +1 -1
  28. data/definitions/procedures/backup/online/pg_global_objects.rb +5 -1
  29. data/definitions/procedures/backup/pulp.rb +6 -1
  30. data/definitions/procedures/backup/snapshot/logical_volume_confirmation.rb +2 -2
  31. data/definitions/procedures/backup/snapshot/mount_mongo.rb +14 -6
  32. data/definitions/procedures/backup/snapshot/mount_pulp.rb +6 -4
  33. data/definitions/procedures/crond/start.rb +19 -0
  34. data/definitions/procedures/crond/stop.rb +18 -0
  35. data/definitions/procedures/iptables/add_maintenance_mode_chain.rb +15 -0
  36. data/definitions/procedures/iptables/remove_maintenance_mode_chain.rb +15 -0
  37. data/definitions/procedures/maintenance_mode/is_enabled.rb +16 -0
  38. data/definitions/procedures/passenger_recycler.rb +3 -2
  39. data/definitions/procedures/puppet/delete_empty_ca_cert_request_files.rb +37 -0
  40. data/definitions/procedures/repositories/disable.rb +13 -0
  41. data/definitions/procedures/service/base.rb +3 -2
  42. data/definitions/procedures/sync_plans/disable.rb +2 -2
  43. data/definitions/procedures/sync_plans/enable.rb +1 -1
  44. data/definitions/scenarios/backup.rb +10 -10
  45. data/definitions/scenarios/maintenance_mode.rb +53 -0
  46. data/lib/foreman_maintain.rb +1 -0
  47. data/lib/foreman_maintain/cli.rb +3 -0
  48. data/lib/foreman_maintain/cli/base.rb +17 -0
  49. data/lib/foreman_maintain/cli/maintenance_mode_command.rb +50 -0
  50. data/lib/foreman_maintain/concerns/directory_marker.rb +35 -0
  51. data/lib/foreman_maintain/concerns/system_service.rb +5 -1
  52. data/lib/foreman_maintain/config.rb +12 -1
  53. data/lib/foreman_maintain/param.rb +2 -1
  54. data/lib/foreman_maintain/reporter/cli_reporter.rb +1 -1
  55. data/lib/foreman_maintain/utils/command_runner.rb +1 -1
  56. data/lib/foreman_maintain/utils/service.rb +4 -0
  57. data/lib/foreman_maintain/utils/service/systemd.rb +1 -1
  58. data/lib/foreman_maintain/version.rb +1 -1
  59. metadata +21 -6
  60. data/definitions/features/puppet.rb +0 -23
  61. data/definitions/procedures/maintenance_mode/disable.rb +0 -13
  62. data/definitions/procedures/maintenance_mode/enable.rb +0 -13
@@ -18,8 +18,8 @@ module Checks::Repositories
18
18
  end
19
19
 
20
20
  def run
21
- if feature(:downstream).subscribed_using_activationkey?
22
- skip 'Your system is subscribed using custom activationkey'
21
+ if feature(:downstream).subscribed_using_activation_key?
22
+ skip 'Your system is subscribed using custom activation key'
23
23
  else
24
24
  with_spinner("Validating availability of repositories for #{@version}") do |spinner|
25
25
  find_absent_repos(spinner)
@@ -0,0 +1,29 @@
1
+ class Features::Cron < ForemanMaintain::Feature
2
+ metadata do
3
+ label :cron
4
+ confine do
5
+ ForemanMaintain.config.manage_crond && !(
6
+ feature(:downstream) && feature(:downstream).less_than_version?('6.3')
7
+ )
8
+ end
9
+ end
10
+
11
+ def status_for_maintenance_mode(mode_on)
12
+ cron_service = system_service(service_name)
13
+ if cron_service.running?
14
+ [
15
+ 'cron jobs: running',
16
+ mode_on ? [Procedures::Service::Stop.new(:only => [cron_service])] : []
17
+ ]
18
+ else
19
+ [
20
+ 'cron jobs: not running',
21
+ mode_on ? [] : [Procedures::Service::Start.new(:only => [cron_service])]
22
+ ]
23
+ end
24
+ end
25
+
26
+ def service_name
27
+ 'crond'
28
+ end
29
+ end
@@ -50,7 +50,7 @@ class Features::Downstream < ForemanMaintain::Feature
50
50
  execute!(%(subscription-manager refresh))
51
51
  end
52
52
 
53
- def subscribed_using_activationkey?
53
+ def subscribed_using_activation_key?
54
54
  ENV['EXTERNAL_SAT_ACTIVATION_KEY'] && ENV['EXTERNAL_SAT_ORG']
55
55
  end
56
56
 
@@ -64,7 +64,7 @@ class Features::Downstream < ForemanMaintain::Feature
64
64
 
65
65
  rh_repos.concat(sat_and_tools_repos(rh_version_major, sat_version))
66
66
 
67
- rh_repos << 'rhel-7-server-ansible-2.6-rpms' if sat_version >= version('6.4')
67
+ rh_repos << 'rhel-7-server-ansible-2.6-rpms' if sat_version.to_s == '6.4'
68
68
 
69
69
  if current_minor_version == '6.3' && sat_version.to_s != '6.4' && (
70
70
  feature(:puppet_server) && feature(:puppet_server).puppet_version.major == 4)
@@ -81,7 +81,7 @@ class Features::Downstream < ForemanMaintain::Feature
81
81
  sat_maintenance_repo_id = "rhel-#{rh_version_major}-server-satellite-maintenance-6-rpms"
82
82
 
83
83
  # Override to use Beta repositories for sat version until GA
84
- if ENV['FOREMAN_MAINTAIN_USE_BETA'] == '1'
84
+ if sat_version.to_s == '6.5'
85
85
  sat_repo_id = "rhel-server-#{rh_version_major}-satellite-6-beta-rpms"
86
86
  sat_tools_repo_id = "rhel-#{rh_version_major}-server-satellite-tools-6-beta-rpms"
87
87
  sat_maintenance_repo_id = "rhel-#{rh_version_major}-server-satellite-maintenance-6-beta-rpms"
@@ -6,39 +6,4 @@ class Features::Foreman_1_7_x < ForemanMaintain::Feature
6
6
  check_min_version('foreman', '1.7')
7
7
  end
8
8
  end
9
-
10
- def maintenance_mode(enable_disable)
11
- case enable_disable
12
- when :enable
13
- custom_iptables_chain('FOREMAN_MAINTAIN',
14
- ['-i lo -j ACCEPT',
15
- '-p tcp --dport 443 -j REJECT'])
16
- when :disable
17
- del_custom_iptables_chain('FOREMAN_MAINTAIN')
18
- else
19
- raise "Unexpected argument #{enable_disable}"
20
- end
21
- end
22
-
23
- private
24
-
25
- def custom_iptables_chain(name, rules)
26
- # if the chain already exists, we assume it was set before: we're not touching
27
- # it again
28
- return if execute?("iptables -L #{name}")
29
- execute!("iptables -N #{name}")
30
- rules.each do |rule|
31
- execute!("iptables -A #{name} #{rule}")
32
- end
33
- execute!("iptables -I INPUT -j #{name}")
34
- end
35
-
36
- def del_custom_iptables_chain(name)
37
- return unless execute?("iptables -L #{name}") # the chain is already gone
38
- if execute?("iptables -L INPUT | tail -n +3 | grep '^#{name} '")
39
- execute!("iptables -D INPUT -j #{name}")
40
- end
41
- execute!("iptables -F #{name}")
42
- execute!("iptables -X #{name}")
43
- end
44
9
  end
@@ -15,12 +15,6 @@ class Features::ForemanDatabase < ForemanMaintain::Feature
15
15
  @configuration || load_configuration
16
16
  end
17
17
 
18
- def config_files
19
- [
20
- '/var/lib/pgsql/data/postgresql.conf'
21
- ]
22
- end
23
-
24
18
  def services
25
19
  [
26
20
  system_service('postgresql', 10, :component => 'foreman',
@@ -0,0 +1,58 @@
1
+ class Features::Iptables < ForemanMaintain::Feature
2
+ metadata do
3
+ label :iptables
4
+ end
5
+
6
+ def add_chain(chain_name, rules, rule_chain = 'INPUT')
7
+ # if the chain already exists, we assume it was set before: we're not touching
8
+ # it again
9
+ return if chain_exist?(chain_name)
10
+ execute!("iptables -N #{chain_name}")
11
+ rules.each do |rule|
12
+ execute!("iptables -A #{chain_name} #{rule}")
13
+ end
14
+ execute!("iptables -I #{rule_chain} -j #{chain_name}")
15
+ end
16
+
17
+ def remove_chain(chain_name, rule_chain = 'INPUT')
18
+ return unless chain_exist?(chain_name) # the chain is already gone
19
+ execute!("iptables -D #{rule_chain} -j #{chain_name}") if rule_exist?(chain_name, rule_chain)
20
+ execute!("iptables -F #{chain_name}")
21
+ execute!("iptables -X #{chain_name}")
22
+ end
23
+
24
+ def chain_exist?(chain_name)
25
+ execute?("iptables -L #{chain_name}")
26
+ end
27
+
28
+ def rule_exist?(target_name, rule_chain = 'INPUT')
29
+ execute?("iptables -L #{rule_chain} | tail -n +3 | grep '^#{target_name} '")
30
+ end
31
+
32
+ def add_maintenance_mode_chain
33
+ add_chain(custom_chain_name,
34
+ ['-i lo -j ACCEPT', '-p tcp --dport 443 -j REJECT'])
35
+ end
36
+
37
+ def remove_maintenance_mode_chain
38
+ remove_chain(custom_chain_name)
39
+ end
40
+
41
+ def maintenance_mode_chain_exist?
42
+ chain_exist?(custom_chain_name)
43
+ end
44
+
45
+ def status_for_maintenance_mode
46
+ if maintenance_mode_chain_exist?
47
+ ['Iptables chain: present', []]
48
+ else
49
+ ['Iptables chain: absent', []]
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def custom_chain_name
56
+ 'FOREMAN_MAINTAIN'
57
+ end
58
+ end
@@ -1,4 +1,6 @@
1
1
  class Features::Mongo < ForemanMaintain::Feature
2
+ include ForemanMaintain::Concerns::DirectoryMarker
3
+
2
4
  # assume mongo is installed when there is Pulp
3
5
  PULP_DB_CONFIG = '/etc/pulp/server.conf'.freeze
4
6
 
@@ -95,30 +97,30 @@ class Features::Mongo < ForemanMaintain::Feature
95
97
 
96
98
  def dump(target, config = configuration)
97
99
  execute!(base_command(core.dump_command, config, "-d #{config['name']} --out #{target}"),
98
- :hidden_patterns => [config['password']].compact)
100
+ :hidden_patterns => [config['password']])
99
101
  end
100
102
 
101
103
  def restore(dir, config = configuration)
102
104
  cmd = base_command(core.restore_command, config,
103
105
  "-d #{config['name']} #{File.join(dir, config['name'])}")
104
- execute!(cmd, :hidden_patterns => [config['password']].compact)
106
+ execute!(cmd, :hidden_patterns => [config['password']])
105
107
  end
106
108
 
107
109
  def dropdb(config = configuration)
108
110
  execute!(mongo_command("--eval 'db.dropDatabase()'", config),
109
- :hidden_patterns => [config['password']].compact)
111
+ :hidden_patterns => [config['password']])
110
112
  end
111
113
 
112
114
  def ping(config = configuration)
113
115
  execute?(mongo_command("--eval 'ping:1'", config),
114
- :hidden_patterns => [config['password']].compact)
116
+ :hidden_patterns => [config['password']])
115
117
  end
116
118
 
117
119
  def server_version(config = configuration)
118
120
  # do not use any core methods as we need this prior the core is created
119
121
  mongo_cmd = base_command(available_core.client_command, config,
120
122
  "--eval 'db.version()' #{config['name']}")
121
- version = execute!(mongo_cmd, :hidden_patterns => [config['password']].compact)
123
+ version = execute!(mongo_cmd, :hidden_patterns => [config['password']])
122
124
  version.split("\n").last
123
125
  end
124
126
 
@@ -138,10 +140,6 @@ class Features::Mongo < ForemanMaintain::Feature
138
140
  end
139
141
  end
140
142
 
141
- def find_base_directory(directory)
142
- find_dir_containing_file(directory, 'mongod.lock')
143
- end
144
-
145
143
  private
146
144
 
147
145
  def load_mongo_core_default(version)
@@ -1,4 +1,6 @@
1
1
  class Features::Pulp < ForemanMaintain::Feature
2
+ include ForemanMaintain::Concerns::DirectoryMarker
3
+
2
4
  metadata do
3
5
  label :pulp
4
6
 
@@ -36,8 +38,4 @@ class Features::Pulp < ForemanMaintain::Feature
36
38
  '/etc/qpid-dispatch'
37
39
  ]
38
40
  end
39
-
40
- def find_base_directory(directory)
41
- find_dir_containing_file(directory, '0005_puppet_module_name_change.txt')
42
- end
43
41
  end
@@ -2,19 +2,60 @@ class Features::PuppetServer < ForemanMaintain::Feature
2
2
  metadata do
3
3
  label :puppet_server
4
4
 
5
- # We only check puppetserver and not puppet-server, as puppet-server
6
- # is a part of httpd and relies on httpd service to restart, therefore
7
- # not requiring a separate service to restart
8
5
  confine do
9
- find_package('puppetserver') || find_package('puppet')
6
+ find_package('puppet-server') || find_package('puppetserver') || find_package('puppet')
10
7
  end
11
8
  end
12
9
 
10
+ def config_files
11
+ [
12
+ '/etc/puppet',
13
+ '/etc/puppetlabs',
14
+ '/opt/puppetlabs/puppet/cache/foreman_cache_data',
15
+ '/var/lib/puppet/foreman_cache_data',
16
+ '/opt/puppetlabs/puppet/ssl/',
17
+ '/var/lib/puppet/ssl',
18
+ '/var/lib/puppet',
19
+ '/usr/share/ruby/vendor_ruby/puppet/reports/foreman.rb',
20
+ '/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/reports/foreman.rb'
21
+ ]
22
+ end
23
+
13
24
  def services
25
+ # We only check puppetserver and not puppet-server, as puppet-server
26
+ # is a part of httpd and relies on httpd service to restart, therefore
27
+ # not requiring a separate service to restart
14
28
  find_package('puppetserver') ? [system_service('puppetserver', 30)] : []
15
29
  end
16
30
 
17
31
  def puppet_version
18
32
  version(execute!('puppet --version'))
19
33
  end
34
+
35
+ def find_empty_cacert_request_files
36
+ cmd_output = execute!("find #{cacert_requests_directory} -type f -size 0 | paste -d, -s")
37
+ cmd_output.split(',')
38
+ end
39
+
40
+ def delete_empty_cacert_files
41
+ execute!("find #{cacert_requests_directory} -type f -size 0 -delete")
42
+ end
43
+
44
+ def cacert_requests_directory
45
+ "#{ca_directory_path}/requests"
46
+ end
47
+
48
+ def cacert_requests_dir_exists?
49
+ File.directory?(cacert_requests_directory)
50
+ end
51
+
52
+ private
53
+
54
+ def ca_directory_path
55
+ "#{puppet_ssldir_path}/ca"
56
+ end
57
+
58
+ def puppet_ssldir_path
59
+ execute!('puppet master --configprint ssldir')
60
+ end
20
61
  end
@@ -4,9 +4,11 @@ class Features::Service < ForemanMaintain::Feature
4
4
  end
5
5
 
6
6
  def handle_services(spinner, action, options = {})
7
- # options is used to handle "exclude" and "only" i.e.
7
+ # options is used to handle "exclude" and "only" and "include" i.e.
8
8
  # { :only => ["httpd"] }
9
9
  # { :exclude => ["pulp-workers", "tomcat"] }
10
+ # { :include => ["crond"] }
11
+
10
12
  if feature(:downstream) && feature(:downstream).less_than_version?('6.3')
11
13
  use_katello_service(action, options)
12
14
  else
@@ -28,6 +30,7 @@ class Features::Service < ForemanMaintain::Feature
28
30
  service_list = existing_services
29
31
  service_list = filter_services(service_list, options)
30
32
  raise 'No services found matching your parameters' unless service_list.any?
33
+
31
34
  options[:reverse] ? service_list.reverse : service_list
32
35
  end
33
36
 
@@ -50,6 +53,7 @@ class Features::Service < ForemanMaintain::Feature
50
53
  spinner.update("All services #{action_past_tense(action)}")
51
54
  if action == 'status'
52
55
  raise "Some services are not running (#{failed_services.join(', ')})" if status > 0
56
+
53
57
  spinner.update('All services are running')
54
58
  end
55
59
  end
@@ -96,14 +100,39 @@ class Features::Service < ForemanMaintain::Feature
96
100
  end
97
101
 
98
102
  def filter_services(service_list, options)
103
+ service_list = include_unregistered_services(service_list, options[:include])
104
+
99
105
  if options[:only] && options[:only].any?
100
106
  service_list = service_list.select do |service|
101
107
  options[:only].any? { |opt| service.matches?(opt) }
102
108
  end
109
+ service_list = include_unregistered_services(service_list, options[:only])
103
110
  end
111
+
104
112
  if options[:exclude] && options[:exclude].any?
105
113
  service_list = service_list.reject { |service| options[:exclude].include?(service.name) }
106
114
  end
115
+ service_list.sort
116
+ end
117
+
118
+ def include_unregistered_services(service_list, services_filter)
119
+ return service_list unless services_filter
120
+ return service_list unless services_filter.any?
121
+
122
+ services_filter = services_filter.reject do |obj|
123
+ service_list.any? { |service| service.matches?(obj) }
124
+ end
125
+
126
+ unregistered_service_list = services_filter.map do |obj|
127
+ service = if obj.is_a? String
128
+ system_service(obj)
129
+ elsif valid_sys_service?(obj)
130
+ obj
131
+ end
132
+ service.exist? ? service : raise("No service found matching your parameter '#{service.name}'")
133
+ end
134
+
135
+ service_list.concat(unregistered_service_list)
107
136
  service_list
108
137
  end
109
138
 
@@ -140,7 +169,9 @@ class Features::Service < ForemanMaintain::Feature
140
169
 
141
170
  def run_katello_service(command)
142
171
  puts "Services are handled by katello-service in Satellite versions 6.2 and earlier. \n" \
143
- "Flags --brief or --failing will be ignored if present. Redirecting to: \n#{command}\n"
172
+ "Flags --brief or --failing will be ignored if present. \n"\
173
+ "Similarly, services that are not listed by katello-service will get ignored. \n"\
174
+ "Redirecting to: \n#{command}\n"
144
175
  puts execute(command)
145
176
  end
146
177
 
@@ -3,34 +3,33 @@ class Features::SyncPlans < ForemanMaintain::Feature
3
3
  label :sync_plans
4
4
  end
5
5
 
6
- def required_new_implementation
7
- @required_new_implementation ||=
8
- feature(:foreman_database).query(
9
- <<-SQL
10
- SELECT COUNT(1) FROM information_schema.table_constraints
11
- WHERE constraint_name='katello_sync_plan_foreman_tasks_recurring_logic_fk' AND table_name='katello_sync_plans'
12
- SQL
13
- ).first['count'].to_i > 0
6
+ def active_sync_plans_count
7
+ feature(:foreman_database).query(
8
+ <<-SQL
9
+ SELECT count(*) AS count FROM katello_sync_plans WHERE enabled ='t'
10
+ SQL
11
+ ).first['count'].to_i
14
12
  end
15
13
 
16
- def sync_plan_ids_by_status(enabled = true, filter_ids = nil)
17
- if filter_ids
18
- return [] if filter_ids.empty?
14
+ def ids_by_status(enabled = true)
15
+ enabled = enabled ? 't' : 'f'
16
+ feature(:foreman_database).query(
17
+ <<-SQL
18
+ SELECT id FROM katello_sync_plans WHERE enabled ='#{enabled}'
19
+ SQL
20
+ ).map { |r| r['id'].to_i }
21
+ end
19
22
 
20
- ids_condition = filter_ids.map { |id| "'#{id}'" }.join(',')
21
- end
23
+ def verify_existing_ids_by_status(ids, enabled = true)
24
+ return [] if ids.empty?
22
25
 
23
- if required_new_implementation
24
- query = <<-SQL
25
- select sp.id as id from katello_sync_plans sp inner join foreman_tasks_recurring_logics rl on sp.foreman_tasks_recurring_logic_id = rl.id
26
- where rl.state='#{enabled ? 'active' : 'disabled'}' #{ids_condition ? " AND sp.id IN (#{ids_condition})" : ''}
26
+ enabled = enabled ? 't' : 'f'
27
+ ids_condition = ids.map { |id| "'#{id}'" }.join(',')
28
+ feature(:foreman_database).query(
29
+ <<-SQL
30
+ SELECT id FROM katello_sync_plans WHERE enabled ='#{enabled}' AND id IN (#{ids_condition})
27
31
  SQL
28
- else
29
- query = <<-SQL
30
- SELECT id FROM katello_sync_plans WHERE enabled ='#{enabled ? 't' : 'f'}' #{ids_condition ? " AND id IN (#{ids_condition})" : ''}
31
- SQL
32
- end
33
- feature(:foreman_database).query(query).map { |r| r['id'].to_i }
32
+ ).map { |r| r['id'].to_i }
34
33
  end
35
34
 
36
35
  def make_disable(ids)
@@ -49,10 +48,28 @@ class Features::SyncPlans < ForemanMaintain::Feature
49
48
  storage[:sync_plans] = @data
50
49
  end
51
50
 
51
+ def status_for_maintenance_mode(mode_on)
52
+ default_storage = ForemanMaintain.storage(:default)
53
+ load_from_storage(default_storage)
54
+ return ['sync plans: empty data', []] if both_empty?
55
+
56
+ if @data[:enabled] && key_empty?(:disabled)
57
+ [
58
+ 'sync plans: enabled',
59
+ mode_on ? [Procedures::SyncPlans::Disable.new] : []
60
+ ]
61
+ else
62
+ [
63
+ 'sync plans: disabled',
64
+ mode_on ? [] : [Procedures::SyncPlans::Enable.new]
65
+ ]
66
+ end
67
+ end
68
+
52
69
  private
53
70
 
54
71
  def update_records(ids, enabled)
55
- ids_not_required_update = sync_plan_ids_by_status(enabled, ids)
72
+ ids_not_required_update = verify_existing_ids_by_status(ids, enabled)
56
73
  ids_required_update = ids - ids_not_required_update
57
74
  make_data_key_empty(enabled) if !ids_not_required_update.empty? && ids_required_update.empty?
58
75
  updated_record_ids = []
@@ -87,7 +104,15 @@ class Features::SyncPlans < ForemanMaintain::Feature
87
104
  else
88
105
  @data[:disabled] = [] unless @data[:disabled]
89
106
  @data[:enabled] = [] if @data[:disabled].empty?
90
- @data[:disabled].concat(new_ids).uniq!
107
+ @data[:disabled].concat(new_ids)
91
108
  end
92
109
  end
110
+
111
+ def both_empty?
112
+ key_empty?(:disabled) && key_empty?(:enabled)
113
+ end
114
+
115
+ def key_empty?(key_name)
116
+ (@data[key_name].nil? || @data[key_name] && @data[key_name].empty?)
117
+ end
93
118
  end