foreman-tasks 0.9.5 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2fbc8738b9259442215b12e07307cb605dbdc299
4
- data.tar.gz: e172529a5fd0c0950a163e403a8667dab0f8538c
3
+ metadata.gz: e1e9246d6fdeb8fc99d6e42f7ed41e87e398341a
4
+ data.tar.gz: a67dd1ec1cbad7e37a168b8495ed48242a862bf2
5
5
  SHA512:
6
- metadata.gz: 16557fbad3ca19db7cef81f6ee905ebcde440c5c397a85e2e5c77bdf1ed97a7984d8e9eb09610d257bb95202cd872241394cd1fcb45365e5bd2204648f8b434c
7
- data.tar.gz: 898dba76fa6a6cb556555ea5fbc8403df8070ebe156b37410ada39a4bd58a853d1716f6eb1f4cfde4424e1ab6285086e75d519857db873b850cc4283ddd41586
6
+ metadata.gz: d3f758c1445380c7b28ee3e132fdfc2f467c1c7b907286e18a56b6fdfeb62abcb0f81753e9c3809f7d8c8342ff7e35860dbdb34d491acc71b7107aac10fbde79
7
+ data.tar.gz: c814a2d7dd11fadd76cce7a11a7f38e469529301ab6debd6e3fdc7763a5fc8793acc20291bf11c69ba65ad837670f8a3cc027d320641f83e7b5633e9384df03b
@@ -9,18 +9,43 @@
9
9
  # :action:
10
10
  # :enabled: true
11
11
 
12
+ # Task backup configuration can be changed by altering the values in
13
+ # the backup section
14
+ #
15
+ :backup:
16
+ #
17
+ # Whether to back up tasks when they are removed
18
+ #
19
+ :backup_deleted_tasks: true
20
+ #
21
+ # Where to put the tasks which were backed up
22
+ #
23
+ :backup_dir: /var/lib/foreman/tasks-backup
12
24
 
13
25
  # Cleaning configuration: how long should the actions be kept before deleted
14
26
  # by `rake foreman_tasks:clean` task
15
27
  #
16
- # :cleanup:
28
+ :cleanup:
17
29
  #
18
30
  # the period after which to delete all the tasks (by default all tasks are not being deleted after some period)
31
+ # will be deprecated in Foreman 1.18 and the use of rules is recommended.
19
32
  #
20
- # :after: 365d
33
+ # :after: 30d
21
34
  #
22
35
  # per action settings to override the default defined in the actions (self.cleanup_after method)
23
36
  #
24
37
  # :actions:
25
38
  # - :name: Actions::Foreman::Host::ImportFacts
26
39
  # :after: 10d
40
+ #
41
+ # Rules defined in this section by default don't operate
42
+ # on tasks specified in the actions section. This behavior
43
+ # can be overriden by setting the override_actions to true
44
+ :rules:
45
+ # Delete successful tasks after a month
46
+ - :filter: result = success
47
+ :after: 30d
48
+ # Delete everything (any action, any state) after one year
49
+ - :states: all # Either list of state names or all
50
+ :after: 1y
51
+ :override_actions: true
@@ -29,7 +29,7 @@ same resource. It also optionally provides Dynflow infrastructure for using it f
29
29
  s.extra_rdoc_files = Dir['README*', 'LICENSE']
30
30
 
31
31
  s.add_dependency "foreman-tasks-core"
32
- s.add_dependency "dynflow", '~> 0.8.26'
32
+ s.add_dependency "dynflow", '~> 0.8.29'
33
33
  s.add_dependency "sequel" # for Dynflow process persistence
34
34
  s.add_dependency "sinatra" # for Dynflow web console
35
35
  s.add_dependency "daemons" # for running remote executor
@@ -1,3 +1,5 @@
1
+ require 'csv'
2
+
1
3
  module ForemanTasks
2
4
  # Represents the cleanup mechanism for tasks
3
5
  class Cleaner
@@ -11,11 +13,16 @@ module ForemanTasks
11
13
  end
12
14
  end
13
15
  if cleanup_settings[:after]
16
+ Foreman::Deprecation.deprecation_warning('1.18', _(':after setting in tasks cleanup section is deprecated, use :after in :rules section to set the value. to cleanup rules'))
14
17
  new(options.merge(:filter => '', :after => cleanup_settings[:after])).delete
15
18
  end
16
- actions_with_default_cleanup.each do |action_class, period|
19
+ with_periods = actions_with_default_cleanup
20
+ with_periods.each do |action_class, period|
17
21
  new(options.merge(:filter => "label = #{action_class.name}", :after => period)).delete
18
22
  end
23
+ actions_by_rules(with_periods).each do |hash|
24
+ new(options.merge(hash)).delete
25
+ end
19
26
  end
20
27
  end
21
28
 
@@ -44,6 +51,19 @@ module ForemanTasks
44
51
  @cleanup_settings = SETTINGS[:'foreman-tasks'] && SETTINGS[:'foreman-tasks'][:cleanup] || {}
45
52
  end
46
53
 
54
+ def self.actions_by_rules(actions_with_periods)
55
+ disable_actions_with_periods = "label !^ (#{actions_with_periods.keys.join(', ')})"
56
+ cleanup_settings.fetch(:rules, []).map do |hash|
57
+ next if hash[:after].nil?
58
+ conditions = []
59
+ conditions << disable_actions_with_periods unless hash[:override_actions]
60
+ conditions << hash[:filter] if hash[:filter]
61
+ hash[:states] = [] if hash[:states] == 'all'
62
+ hash[:filter] = conditions.map { |condition| "(#{condition})" }.join(' AND ')
63
+ hash
64
+ end.compact
65
+ end
66
+
47
67
  attr_reader :filter, :after, :states, :verbose, :batch_size, :noop, :full_filter
48
68
 
49
69
  # @param filter [String] scoped search matching the tasks to be deleted
@@ -56,7 +76,8 @@ module ForemanTasks
56
76
  :verbose => false,
57
77
  :batch_size => 1000,
58
78
  :noop => false,
59
- :states => ['stopped'] }
79
+ :states => ['stopped'],
80
+ :backup_dir => ForemanTasks.dynflow.world.persistence.current_backup_dir }
60
81
  options = default_options.merge(options)
61
82
 
62
83
  @filter = options[:filter]
@@ -65,6 +86,7 @@ module ForemanTasks
65
86
  @verbose = options[:verbose]
66
87
  @batch_size = options[:batch_size]
67
88
  @noop = options[:noop]
89
+ @backup_dir = options[:backup_dir]
68
90
 
69
91
  raise ArgumentError, 'filter not speficied' if @filter.nil?
70
92
 
@@ -91,12 +113,33 @@ module ForemanTasks
91
113
  end
92
114
 
93
115
  def delete_tasks(chunk)
94
- ForemanTasks::Task.where(:id => chunk.map(&:id)).delete_all
116
+ tasks = ForemanTasks::Task.where(:id => chunk.map(&:id))
117
+ tasks_to_csv(tasks, @backup_dir, 'foreman_tasks.csv') if @backup_dir
118
+ tasks.delete_all
119
+ end
120
+
121
+ def tasks_to_csv(dataset, backup_dir, file_name)
122
+ with_backup_file(backup_dir, file_name) do |csv, appending|
123
+ csv << ForemanTasks::Task.attribute_names.to_csv unless appending
124
+ dataset.each do |row|
125
+ csv << row.attributes.values.to_csv
126
+ end
127
+ end
128
+ dataset
129
+ end
130
+
131
+ def with_backup_file(backup_dir, file_name)
132
+ FileUtils.mkdir_p(backup_dir) unless File.directory?(backup_dir)
133
+ csv_file = File.join(backup_dir, file_name)
134
+ appending = File.exist?(csv_file)
135
+ File.open(csv_file, 'a') do |f|
136
+ yield f, appending
137
+ end
95
138
  end
96
139
 
97
140
  def delete_dynflow_plans(chunk)
98
141
  dynflow_ids = chunk.find_all { |task| task.is_a? Task::DynflowTask }.map(&:external_id)
99
- ForemanTasks.dynflow.world.persistence.delete_execution_plans({ 'uuid' => dynflow_ids }, batch_size)
142
+ ForemanTasks.dynflow.world.persistence.delete_execution_plans({ 'uuid' => dynflow_ids }, batch_size, @backup_dir)
100
143
  end
101
144
 
102
145
  def prepare_filter
@@ -103,6 +103,8 @@ module ForemanTasks
103
103
  config.transaction_adapter = transaction_adapter
104
104
  config.executor = ->(world, _) { initialize_executor(world) }
105
105
  config.connector = ->(world, _) { initialize_connector(world) }
106
+ config.backup_deleted_plans = backup_settings[:backup_deleted_plans]
107
+ config.backup_dir = backup_settings[:backup_dir]
106
108
 
107
109
  # we can't do any operation until the ForemanTasks.dynflow.world is set
108
110
  config.auto_execute = false
@@ -144,5 +146,29 @@ module ForemanTasks
144
146
  def initialize_persistence
145
147
  ForemanTasks::Dynflow::Persistence.new(default_sequel_adapter_options)
146
148
  end
149
+
150
+ def backup_settings
151
+ return @backup_settings if @backup_settings
152
+ backup_options = {
153
+ :backup_deleted_plans => true,
154
+ :backup_dir => default_backup_dir
155
+ }
156
+ settings = SETTINGS[:'foreman-tasks'] && SETTINGS[:'foreman-tasks'][:backup]
157
+ backup_options.merge!(settings) if settings
158
+ @backup_settings = with_environment_override backup_options
159
+ end
160
+
161
+ def default_backup_dir
162
+ File.join(Rails.root, 'tmp', 'task-backup')
163
+ end
164
+
165
+ def with_environment_override(options)
166
+ env_var = ENV['TASK_BACKUP']
167
+ unless env_var.nil?
168
+ # Everything except 0, n, no, false is considered to be a truthy value
169
+ options[:backup_deleted_plans] = !%w[0 n no false].include?(env_var.downcase)
170
+ end
171
+ options
172
+ end
147
173
  end
148
174
  end
@@ -5,10 +5,11 @@ namespace :foreman_tasks do
5
5
 
6
6
  * TASK_SEARCH : scoped search filter (example: 'label = "Actions::Foreman::Host::ImportFacts"')
7
7
  * AFTER : delete tasks created after *AFTER* period. Expected format is a number followed by the time unit (s,h,m,y), such as '10d' for 10 days
8
- * STATES : comma separated list of task states to touch with the cleanup, by default only stopped tasks are covered
9
- * NOOP : set to "true" if the task should not actuall perform the deletion
8
+ * STATES : comma separated list of task states to touch with the cleanup, by default only stopped tasks are covered, special value all can be used to clean the tasks, disregarding their states
9
+ * NOOP : set to "true" if the task should not actually perform the deletion
10
10
  * VERBOSE : set to "true" for more verbose output
11
11
  * BATCH_SIZE : the size of batches the tasks get processed in (1000 by default)
12
+ * TASK_BACKUP : set to "true" or "false" to enable/disable task backup
12
13
 
13
14
  If none of TASK_SEARCH, BEFORE, STATES is specified, the tasks will be cleaned based
14
15
  configuration in settings
@@ -21,6 +22,7 @@ namespace :foreman_tasks do
21
22
  options[:after] = ENV['AFTER'] if ENV['AFTER']
22
23
 
23
24
  options[:states] = ENV['STATES'].to_s.split(',') if ENV['STATES']
25
+ options[:states] = [] if options[:states] == ['all']
24
26
 
25
27
  options[:noop] = true if ENV['NOOP']
26
28
 
@@ -52,6 +54,24 @@ namespace :foreman_tasks do
52
54
  printf("%-50s %s\n", action.name, after)
53
55
  end
54
56
  end
57
+ puts
58
+ by_rules = ForemanTasks::Cleaner.actions_by_rules(ForemanTasks::Cleaner.actions_with_default_cleanup)
59
+ if by_rules.empty?
60
+ puts _('No cleanup rules are configured')
61
+ else
62
+ printf("%-50s %-15s %s\n", _('states'), _('delete after'), _('filter'))
63
+ by_rules.each do |hash|
64
+ state = case hash[:states]
65
+ when []
66
+ _('ANY')
67
+ when nil
68
+ 'stopped'
69
+ else
70
+ hash[:states]
71
+ end
72
+ printf("%-50s %-15s %s\n", state, hash[:after], hash[:filter])
73
+ end
74
+ end
55
75
  end
56
76
  end
57
77
 
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '0.9.5'.freeze
2
+ VERSION = '0.9.6'.freeze
3
3
  end
@@ -1,6 +1,11 @@
1
1
  require 'foreman_tasks_test_helper'
2
2
 
3
3
  class TasksTest < ActiveSupport::TestCase
4
+ before do
5
+ # To stop dynflow from backing up actions, execution_plans and steps
6
+ ForemanTasks.dynflow.world.persistence.adapter.stubs(:backup_to_csv)
7
+ end
8
+
4
9
  describe ForemanTasks::Cleaner do
5
10
  it 'is able to delete tasks (including the dynflow plans) based on filter' do
6
11
  cleaner = ForemanTasks::Cleaner.new(:filter => 'label = "Actions::User::Create"', :after => '10d')
@@ -12,6 +17,7 @@ class TasksTest < ActiveSupport::TestCase
12
17
  task.save
13
18
  end,
14
19
  FactoryGirl.create(:dynflow_task, :product_create_task)]
20
+ cleaner.expects(:tasks_to_csv)
15
21
  cleaner.delete
16
22
  ForemanTasks::Task.where(id: tasks_to_delete).must_be_empty
17
23
  ForemanTasks::Task.where(id: tasks_to_keep).order(:id).map(&:id).must_equal tasks_to_keep.map(&:id).sort
@@ -32,6 +38,7 @@ class TasksTest < ActiveSupport::TestCase
32
38
  end]
33
39
 
34
40
  tasks_to_keep = [FactoryGirl.create(:dynflow_task, :product_create_task)]
41
+ cleaner.expects(:tasks_to_csv)
35
42
  cleaner.delete
36
43
  ForemanTasks::Task.where(id: tasks_to_delete).must_be_empty
37
44
  ForemanTasks::Task.where(id: tasks_to_keep).must_equal tasks_to_keep
@@ -46,11 +53,32 @@ class TasksTest < ActiveSupport::TestCase
46
53
  task.started_at = task.ended_at = Time.zone.now
47
54
  task.save
48
55
  end]
56
+ cleaner.expects(:tasks_to_csv)
49
57
  cleaner.delete
50
58
  ForemanTasks::Task.where(id: tasks_to_delete).must_be_empty
51
59
  ForemanTasks::Task.where(id: tasks_to_keep).must_equal tasks_to_keep
52
60
  end
53
61
 
62
+ it 'backs tasks up before deleting' do
63
+ dir = '/tmp'
64
+ cleaner = ForemanTasks::Cleaner.new(:filter => '', :after => '10d', :backup_dir => dir)
65
+ tasks_to_delete = [FactoryGirl.create(:dynflow_task, :user_create_task),
66
+ FactoryGirl.create(:dynflow_task, :product_create_task)]
67
+
68
+ r, w = IO.pipe
69
+ cleaner.expects(:with_backup_file)
70
+ .with(dir, 'foreman_tasks.csv')
71
+ .yields(w, false)
72
+ cleaner.delete
73
+ w.close
74
+ header, *data = r.readlines.map(&:chomp)
75
+ header.must_equal ForemanTasks::Task.attribute_names.join(',')
76
+ expected_lines = tasks_to_delete.map { |task| task.attributes.values.join(',') }
77
+ data.count.must_equal expected_lines.count
78
+ expected_lines.each { |line| data.must_include line }
79
+ ForemanTasks::Task.where(id: tasks_to_delete).must_be_empty
80
+ end
81
+
54
82
  class ActionWithCleanup < Actions::Base
55
83
  def self.cleanup_after
56
84
  '15d'
@@ -68,6 +96,29 @@ class TasksTest < ActiveSupport::TestCase
68
96
  { :actions => [{ :name => ActionWithCleanup.name, :after => '5d' }] })
69
97
  ForemanTasks::Cleaner.actions_with_default_cleanup[ActionWithCleanup].must_equal '5d'
70
98
  end
99
+
100
+ it 'deprecates the usage of :after' do
101
+ Foreman::Deprecation.expects(:deprecation_warning)
102
+ ForemanTasks::Cleaner.any_instance.expects(:delete)
103
+ ForemanTasks::Cleaner.stubs(:cleanup_settings =>
104
+ { :after => '1d' })
105
+ ForemanTasks::Cleaner.stubs(:actions_with_default_cleanup).returns({})
106
+ ForemanTasks::Cleaner.run({})
107
+ end
108
+
109
+ it 'generates filters from rules properly' do
110
+ actions_with_default = { 'action1' => nil, 'action2' => nil }
111
+ rules = [{ :after => nil },
112
+ { :after => '10d', :filter => 'label = something', :states => %w[stopped paused] },
113
+ { :after => '15d', :filter => 'label = something_else',
114
+ :override_actions => true, :states => 'all' }]
115
+ ForemanTasks::Cleaner.stubs(:cleanup_settings).returns(:rules => rules)
116
+ r1, r2 = ForemanTasks::Cleaner.actions_by_rules actions_with_default
117
+ r1[:filter].must_equal '(label !^ (action1, action2)) AND (label = something)'
118
+ r1[:states].must_equal %w[stopped paused]
119
+ r2[:filter].must_equal '(label = something_else)'
120
+ r2[:states].must_equal []
121
+ end
71
122
  end
72
123
  end
73
124
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-03 00:00:00.000000000 Z
11
+ date: 2017-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: foreman-tasks-core
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.8.26
33
+ version: 0.8.29
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.8.26
40
+ version: 0.8.29
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: sequel
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -286,7 +286,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
286
286
  version: '0'
287
287
  requirements: []
288
288
  rubyforge_project:
289
- rubygems_version: 2.4.5
289
+ rubygems_version: 2.6.12
290
290
  signing_key:
291
291
  specification_version: 4
292
292
  summary: Foreman plugin for showing tasks information for resoruces and users