deploy_pin 1.5.1 → 1.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c21646b048b09408bf131ab463208e76dd54b18596dcdfce4c16fae3fa1669f1
4
- data.tar.gz: 5ac45e42ba5a3af95dadbf44031e683997f8fe42a8e422f1d7114f5007437e8e
3
+ metadata.gz: ced30166491b43cb26d26c837ca1276e9825d3b48631cea70f3095e0a8590d01
4
+ data.tar.gz: '0538bf5ea753ba70a2daa8e2104939d38d8605f8ae72d284417bcbe249659a4f'
5
5
  SHA512:
6
- metadata.gz: 6137f6270259681a0a74188e1a0145894d25eb77bde43d8ed47b9dea38c1ab7733ca618b44ac04d15e6ebbaf3f565200cb09af3ee91e8f9b2fca756ee9c5c250
7
- data.tar.gz: 8551c4fadd20f97cd19584566408a115d42a9402b45b46fba4c4fdf6e94b3f1b9b074136287b814ca3ff1603bfe77b5fa51bbc9814fcdf1ef59f421dd6052025
6
+ metadata.gz: f16f07130b5544236e0a17d0658e2c1b5ec2eb2103a5321c4f11ef391492f87c056b69e8b8498553f686e1523989f6723bec205147760fc2c12062f266c3a27e
7
+ data.tar.gz: 64424aa72c121603c5ddd23f9e15051fca979b4e3393b1ed942b0319aadd3d441515d24203201148c8c125aed4c6dcb0e43b4c2079466e430f7681a56c5700e8
data/README.md CHANGED
@@ -6,8 +6,6 @@
6
6
 
7
7
  # DeployPin
8
8
 
9
- ![DeployPin](http://hereisfree.com/content1//pic/zip/2009109935062477801.jpg)
10
-
11
9
  Deploying applications often involves the need to execute a series of specific commands or tasks either before or after the deployment process. While you might typically turn to migrations for such operations, these tasks aren't always migration-related. Additionally, there are situations where you need to execute code before or after a migration without causing the main thread to block.
12
10
 
13
11
  Introducing deploy_pins – your go-to solution for streamlined task management during the deployment process. This Ruby library allows you to seamlessly orchestrate tasks before, after, or independently of migrations, offering the flexibility you need to maintain a smooth and efficient deployment workflow. With deploy_pins, you can take control of your deployment tasks and ensure that your application operates flawlessly in any environment.
@@ -178,6 +176,30 @@ end
178
176
 
179
177
  To use a different formatting value than the default, you need to specify it explicitly in the task, similar to the database timeout configuration.
180
178
 
179
+ ## Resumable Tasks
180
+
181
+ When working with long-running code that processes a large dataset, it makes sense to store progress in the database to allow resuming the task later. You can do this by using the `DeployPin::Task` instance methods: `#progress` and `#increment_progress(num)`.
182
+
183
+ Here is an example of how to use these methods:
184
+
185
+ ```ruby
186
+ # Some DeployPin task
187
+ ...
188
+ # === task code goes here ===
189
+
190
+ # The progress is 0 by default
191
+ Users.where(id: progress..).find_each do |user|
192
+ # Do some work
193
+ increment_progress(1) # Increment progress by 1 and store it in the database so you can resume the task from this point
194
+ end
195
+ ```
196
+
197
+ This allows your task to resume from where it left off, minimizing the risk of repeating work.
198
+
199
+
200
+
201
+ ```bash
202
+
181
203
  ## Recurring Tasks
182
204
  If you want to generate a recurring task, you can use the `--recurring` option. Make sure to set a correct `--identifier`, which should be a numeric value. Positive and negative numbers are possible here. The identifier affects the order of task execution, allowing you to customize the sequence as desired.
183
205
 
@@ -192,27 +214,29 @@ rails g deploy_pin:task some_task_title --parallel --recurring --identifier 5
192
214
  ## DeploymentStateTrack
193
215
  In the initializer
194
216
  ```ruby
195
- DeployPin.setup do
196
- groups %w[I II III post rollback]
197
- ...
198
- deployment_state_transition({
199
- ongoing: %w[I III],
200
- pending: "rollback", # enters to pending step before "rollback"
201
- ttl: 20.second, # memoize the state to avoid Redis spam
202
- redis_url: "redis://localhost:6379"
203
- })
204
- end
217
+ DeployPin.setup do
218
+ groups %w[I II III post rollback]
219
+ ...
220
+ deployment_state_transition({
221
+ ongoing: %w[I III],
222
+ pending: "rollback", # enters to pending step before "rollback"
223
+ ttl: 20.second, # memoize the state to avoid Redis spam
224
+ redis_url: "redis://localhost:6379"
225
+ })
226
+ end
205
227
 
206
- # enabled next methods
207
- DeployPin.ongoing_deployment?
208
- DeployPin.pending_deployment?
228
+ # enabled next methods
229
+ DeployPin.ongoing_deployment?
230
+ DeployPin.pending_deployment?
209
231
  ```
210
232
 
211
233
  Around the deployment
212
234
  ```bash
213
- bundle exec rake deploy_pin:run[I, II, III] - # enters to ongoing state before "I" and leaves it after "III" so all tasks in I, II, III have DeployPin.oingoing_deployment? == true
214
- bundle exec rake deploy_pin:run[rollback] - # enters "pending state"
235
+ bundle exec rake deploy_pin:run[I, II, III] - # enters to ongoing state before "I" and leaves it after "III" so all tasks in I, II, III have DeployPin.oingoing_deployment? == true
236
+ bundle exec rake deploy_pin:run[rollback] - # enters "pending state"
215
237
  ```
238
+ ## Similar Gems
239
+ - https://github.com/theSteveMitchell/after_party
216
240
 
217
241
  ## Contributing
218
242
  Contribution directions go here.
@@ -12,50 +12,51 @@ module DeployPin
12
12
  # :reek:TooManyStatements
13
13
  def run
14
14
  # cache tasks
15
- _tasks = tasks
16
- _tasks.each_with_index do |task, index|
17
- DeployPin.task_wrapper.call(task, -> { process(_tasks, task, index) })
15
+ tasks = init_tasks
16
+ tasks.each_with_index do |task, index|
17
+ DeployPin.task_wrapper.call(task, -> { process(tasks, task, index) })
18
18
  end
19
19
  end
20
20
  # :reek:TooManyStatements
21
21
 
22
22
  def list
23
- _tasks = tasks
24
- _tasks.each_with_index do |task, index|
23
+ tasks = init_tasks
24
+ tasks.each_with_index do |task, index|
25
25
  DeployPin.list_formatter.call(index, task)
26
26
  end
27
27
  end
28
28
 
29
29
  def executable
30
30
  # cache tasks
31
- _tasks = tasks
32
- _tasks.map.with_index do |task, index|
33
- task if _tasks[0..index].none? { |_task| task.eql?(_task) }
31
+ tasks = init_tasks
32
+ tasks.map.with_index do |task, index|
33
+ task if tasks[0..index].none? { |other_task| task.eql?(other_task) }
34
34
  end.compact
35
35
  end
36
36
 
37
37
  def tasks_count
38
- tasks.count
38
+ init_tasks.count
39
39
  end
40
40
 
41
41
  private
42
42
 
43
43
  # :reek:FeatureEnvy
44
44
  # :reek:TooManyStatements
45
- def process(cached_tasks, task, index)
45
+ def process(tasks, task, index)
46
46
  # run only uniq tasks
47
- executable = cached_tasks[0..index].none? { |_task| task.eql?(_task) }
47
+ executable = tasks[0..index].none? { |other_task| task.eql?(other_task) }
48
48
 
49
- DeployPin.run_formatter.call(index, cached_tasks.count, task, executable, true)
49
+ DeployPin.run_formatter.call(index, tasks.count, task, executable, true)
50
+
51
+ task.prepare
50
52
 
51
53
  # run if executable
52
54
  if executable
53
55
  duration = execution_duration { run_with_timeout(task) { task.run } }
54
- DeployPin.run_formatter.call(index, cached_tasks.count, task, executable, false, duration)
56
+ DeployPin.run_formatter.call(index, tasks.count, task, executable, false, duration)
55
57
  end
56
58
 
57
- # mark each task as done
58
- task.mark unless task.done?
59
+ task.mark # mark each task as done
59
60
  end
60
61
  # :reek:TooManyStatements
61
62
  # :reek:FeatureEnvy
@@ -65,7 +66,7 @@ module DeployPin
65
66
  Dir["#{DeployPin.tasks_path}/*.rb"]
66
67
  end
67
68
 
68
- def tasks
69
+ def init_tasks
69
70
  [*DeployPin.deployment_tasks_code, *files].map do |file|
70
71
  task = DeployPin::Task.new(file)
71
72
  task.parse
@@ -76,7 +77,7 @@ module DeployPin
76
77
  end
77
78
 
78
79
  def task_criteria
79
- @task_criteria ||= DeployPin::TaskCriteria.new(identifiers: identifiers)
80
+ @task_criteria ||= DeployPin::TaskCriteria.new(identifiers:)
80
81
  end
81
82
 
82
83
  # :reek:UtilityFunction and :reek:ControlParameter
@@ -2,13 +2,8 @@
2
2
 
3
3
  module DeployPin
4
4
  module Database
5
- PG_TIMEOUT_STATEMENT = 'SET statement_timeout TO %s'
6
- MYSQL_TIMEOUT_STATEMENT = 'SET max_execution_time = %s'
7
-
8
5
  extend self
9
6
 
10
- def dummy_method; end
11
-
12
7
  # Run a block under a sql maximum timeout.
13
8
  #
14
9
  # A default timeout will be get from DeployPin.setup
@@ -68,23 +63,11 @@ module DeployPin
68
63
 
69
64
  def set_max_timeout(timeout)
70
65
  timeout_in_milliseconds = timeout.to_f.in_milliseconds.ceil # Make sure is always at least 1. 0 turns this off
71
-
72
- timeout_statement =
73
- if postgresql?
74
- PG_TIMEOUT_STATEMENT
75
- elsif mysql?
76
- MYSQL_TIMEOUT_STATEMENT
77
- end
78
-
79
- connection.execute timeout_statement % connection.quote(timeout_in_milliseconds)
80
- end
81
-
82
- def postgresql?
83
- connection.adapter_name =~ /postg/i
66
+ connection.execute db_engine::TIMEOUT_STATEMENT % connection.quote(timeout_in_milliseconds)
84
67
  end
85
68
 
86
- def mysql?
87
- connection.adapter_name =~ /mysql/i
69
+ def db_engine
70
+ @db_engine ||= DeployPin::DatabaseEngine.new(connection).detect
88
71
  end
89
72
 
90
73
  def connection
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeployPin
4
+ # This class is used to detect used database engine and managing specific
5
+ # differences between them.
6
+ class DatabaseEngine
7
+ module PostgreSQL
8
+ TIMEOUT_STATEMENT = 'SET statement_timeout TO %s'
9
+ end
10
+
11
+ module MySQL
12
+ TIMEOUT_STATEMENT = 'SET max_execution_time = %s'
13
+ end
14
+
15
+ module MariaDB
16
+ TIMEOUT_STATEMENT = 'SET SESSION max_statement_time = %s'
17
+ end
18
+
19
+ DB_ENGINES_MAPPING = {
20
+ mariadb: MariaDB,
21
+ mysql: MySQL,
22
+ pg: PostgreSQL
23
+ }.freeze
24
+
25
+ def initialize(connection)
26
+ @connection = connection
27
+ end
28
+
29
+ def detect
30
+ db_engine_symbol =
31
+ case connection.adapter_name
32
+ when /postg/i then :pg
33
+ when /mysql/i, /trilogy/i then detect_mysql_based_engine
34
+ end
35
+
36
+ DB_ENGINES_MAPPING[db_engine_symbol]
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :connection
42
+
43
+ def detect_mysql_based_engine
44
+ mariadb? ? :mariadb : :mysql
45
+ end
46
+
47
+ def mariadb?
48
+ connection.select_value('SELECT VERSION()') =~ /MariaDB/i
49
+ end
50
+ end
51
+ end
@@ -4,17 +4,17 @@
4
4
  module DeployPin
5
5
  module Runner
6
6
  def self.run(identifiers:)
7
- DeployPin::Collector.new(identifiers: identifiers).run
7
+ DeployPin::Collector.new(identifiers:).run
8
8
  end
9
9
 
10
10
  def self.list(identifiers:)
11
- DeployPin::Collector.new(identifiers: identifiers).list
11
+ DeployPin::Collector.new(identifiers:).list
12
12
  end
13
13
 
14
14
  def self.summary(identifiers:)
15
15
  # print summary
16
16
  self.print('======= Summary ========')
17
- self.print("Tasks number: #{DeployPin::Collector.new(identifiers: identifiers).tasks_count}")
17
+ self.print("Tasks number: #{DeployPin::Collector.new(identifiers:).tasks_count}")
18
18
  end
19
19
 
20
20
  def self.print(msg)
@@ -14,6 +14,8 @@ module DeployPin
14
14
  :recurring,
15
15
  :explicit_timeout
16
16
 
17
+ delegate :progress, to: :record
18
+
17
19
  def initialize(file)
18
20
  @file = file
19
21
  @identifier = nil
@@ -29,17 +31,34 @@ module DeployPin
29
31
  eval(script)
30
32
  end
31
33
 
34
+ def record
35
+ DeployPin::Record.find_by(uuid: identifier)
36
+ end
37
+
38
+ def prepare
39
+ return if recurring
40
+
41
+ DeployPin::Record.create(uuid: identifier) unless record
42
+ end
43
+
32
44
  def mark
33
45
  return if recurring
34
46
 
35
47
  # store record in the DB
36
- DeployPin::Record.create(uuid: identifier)
48
+ record.update(completed_at: Time.current)
49
+ end
50
+
51
+ def increment_progress!(incrementor)
52
+ raise NotImplementedError, 'Recurring tasks do not support progress yet.' if recurring
53
+
54
+ record.increment!(:progress, incrementor)
37
55
  end
38
56
 
39
57
  def done?
40
58
  return if recurring
59
+ return unless record
41
60
 
42
- DeployPin::Record.where(uuid: identifier).exists?
61
+ record.completed_at.present?
43
62
  end
44
63
 
45
64
  def under_timeout?
@@ -74,9 +93,9 @@ module DeployPin
74
93
 
75
94
  def details
76
95
  {
77
- identifier: identifier,
78
- group: group,
79
- title: title
96
+ identifier:,
97
+ group:,
98
+ title:
80
99
  }
81
100
  end
82
101
 
@@ -104,7 +123,6 @@ module DeployPin
104
123
 
105
124
  protected
106
125
 
107
-
108
126
  def group_index
109
127
  DeployPin.groups.index(group)
110
128
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeployPin
4
- VERSION = '1.5.1'
4
+ VERSION = '1.7.0'
5
5
  end
data/lib/deploy_pin.rb CHANGED
@@ -8,20 +8,21 @@ require 'deploy_pin/task'
8
8
  require 'deploy_pin/task_criteria'
9
9
  require 'deploy_pin/engine'
10
10
  require 'deploy_pin/database'
11
+ require 'deploy_pin/database_engine'
11
12
  require 'parallel'
12
13
  require 'ruby-progressbar'
13
14
  require 'colorize'
14
15
 
15
16
  module DeployPin
16
17
  OPTIONS = %i[
17
- tasks_path
18
+ deployment_state_transition
18
19
  fallback_group
19
20
  groups
20
- statement_timeout
21
- run_formatter
22
21
  list_formatter
22
+ run_formatter
23
+ statement_timeout
23
24
  task_wrapper
24
- deployment_state_transition
25
+ tasks_path
25
26
  ].freeze
26
27
 
27
28
  DEFAULTS = {
@@ -4,6 +4,8 @@ class CreateDeployPins < ActiveRecord::Migration[7.0]
4
4
  def change
5
5
  create_table :deploy_pins do |t|
6
6
  t.string :uuid
7
+ t.integer :progress, default: 0
8
+ t.datetime :completed_at, default: nil
7
9
  t.timestamps
8
10
  end
9
11
  end
@@ -6,11 +6,11 @@ DeployPin.setup do
6
6
  fallback_group 'III'
7
7
  statement_timeout 10.minutes
8
8
  deployment_state_transition({
9
- ongoing: %w[I III],
10
- pending: 'rollback', # enters to pending step before "rollback"
11
- ttl: 20.second, # memoize the state to avoid the store spam
12
- redis_url: 'redis://localhost:6379'
13
- })
9
+ ongoing: %w[I III],
10
+ pending: 'rollback', # enters to pending step before "rollback"
11
+ ttl: 20.second, # memoize the state to avoid the store spam
12
+ redis_url: 'redis://localhost:6379'
13
+ })
14
14
  run_formatter(
15
15
  lambda do |index, task_count, task, executable, start, duration = nil|
16
16
  end_of_msg = if executable
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Upgrades the deploy_pins table in the DB with an extra columns for resumable tasks
3
+
4
+ Example:
5
+ rails generate deploy_pin:upgrade
6
+
7
+ This will create:
8
+ db/migrate/upgrade_deploy_pins.rb
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UpgradeDeployPins < ActiveRecord::Migration[7.0]
4
+ def change
5
+ add_column :deploy_pins, :progress, :integer, default: 0, if_not_exists: true
6
+ add_column :deploy_pins, :completed_at, :datetime, default: nil, if_not_exists: true
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeployPin
4
+ # This generator is used to generate the migration file for upgrading db schema for 1.7 version support.
5
+ class UpgradeGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ source_root File.expand_path('templates', __dir__)
9
+ desc 'Add the upgrade migration for DeployPin.'
10
+
11
+ def self.next_migration_number(path)
12
+ next_migration_number = current_migration_number(path) + 1
13
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
14
+ end
15
+
16
+ def copy_migrations
17
+ migration_template 'upgrade_deploy_pins.rb', 'db/migrate/upgrade_deploy_pins.rb'
18
+ end
19
+ end
20
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deploy_pin
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Viktor Sych
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-19 00:00:00.000000000 Z
11
+ date: 2024-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -235,6 +235,7 @@ files:
235
235
  - lib/deploy_pin.rb
236
236
  - lib/deploy_pin/collector.rb
237
237
  - lib/deploy_pin/database.rb
238
+ - lib/deploy_pin/database_engine.rb
238
239
  - lib/deploy_pin/deployment_state.rb
239
240
  - lib/deploy_pin/engine.rb
240
241
  - lib/deploy_pin/parallel_wrapper.rb
@@ -250,6 +251,9 @@ files:
250
251
  - lib/generators/deploy_pin/task/task_generator.rb
251
252
  - lib/generators/deploy_pin/task/templates/parallel_task.rb.erb
252
253
  - lib/generators/deploy_pin/task/templates/task.rb.erb
254
+ - lib/generators/deploy_pin/upgrade/USAGE
255
+ - lib/generators/deploy_pin/upgrade/templates/upgrade_deploy_pins.rb
256
+ - lib/generators/deploy_pin/upgrade/upgrade_generator.rb
253
257
  - lib/tasks/deploy_pin_tasks.rake
254
258
  homepage: https://github.com/skcc321/deploy_pin
255
259
  licenses:
@@ -264,14 +268,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
264
268
  requirements:
265
269
  - - ">="
266
270
  - !ruby/object:Gem::Version
267
- version: '3.0'
271
+ version: '3.1'
268
272
  required_rubygems_version: !ruby/object:Gem::Requirement
269
273
  requirements:
270
274
  - - ">="
271
275
  - !ruby/object:Gem::Version
272
276
  version: '0'
273
277
  requirements: []
274
- rubygems_version: 3.5.11
278
+ rubygems_version: 3.5.16
275
279
  signing_key:
276
280
  specification_version: 4
277
281
  summary: pin some task around deployment