better-patches 2.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.
- checksums.yaml +7 -0
- data/.buildkite/pipeline.yml +24 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +18 -0
- data/Dockerfile +9 -0
- data/Gemfile +5 -0
- data/LICENSE.md +21 -0
- data/README.md +52 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/db/migrate/201506011700_create_patch.rb +10 -0
- data/docker-compose.yml +11 -0
- data/docs/patches.jpg +0 -0
- data/docs/usage.md +127 -0
- data/lib/generators/patches/patch_generator.rb +26 -0
- data/lib/generators/patches/templates/patch.rb.erb +5 -0
- data/lib/generators/patches/templates/patch_spec.rb.erb +8 -0
- data/lib/generators/patches.rb +22 -0
- data/lib/patches/base.rb +20 -0
- data/lib/patches/capistrano/tasks.rake +12 -0
- data/lib/patches/capistrano.rb +1 -0
- data/lib/patches/config.rb +58 -0
- data/lib/patches/engine.rb +7 -0
- data/lib/patches/notifier.rb +62 -0
- data/lib/patches/patch.rb +9 -0
- data/lib/patches/pending.rb +35 -0
- data/lib/patches/runner.rb +53 -0
- data/lib/patches/tenant_run_concern.rb +8 -0
- data/lib/patches/tenant_runner.rb +30 -0
- data/lib/patches/tenant_worker.rb +12 -0
- data/lib/patches/version.rb +3 -0
- data/lib/patches/worker.rb +11 -0
- data/lib/patches.rb +32 -0
- data/lib/tasks/patches.rake +26 -0
- data/patches.gemspec +40 -0
- data/script/buildkite.sh +21 -0
- metadata +319 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d93b9734dfe9442243fed8b29e0c0acb932402e6
|
4
|
+
data.tar.gz: be680642338863cf24576b23667bd0f568f72a01
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 96c7841d79b84d680cf53a80f67820089717dd30719fbab71922dd238afbaf82ac2ff7afe19c0df257b66ba6b51e1970b2f6e1cd7370d39af4a2c890b2116b67
|
7
|
+
data.tar.gz: 5af89632f9c2251a311b2702f9eec6d5ee2381cd5536d37205c613fa52cfa6400ecb7e40b802de6734aaa6b2a6ed4d88105d3a1d8fe7f50c4469bec5e648c68a
|
@@ -0,0 +1,24 @@
|
|
1
|
+
steps:
|
2
|
+
- label: ':hammer: Tests'
|
3
|
+
plugins:
|
4
|
+
docker-compose:
|
5
|
+
run: app
|
6
|
+
command: 'bundle exec rspec'
|
7
|
+
agents:
|
8
|
+
queue: docker-heavy
|
9
|
+
artifact_paths: 'coverage/.resultset.json'
|
10
|
+
|
11
|
+
- wait
|
12
|
+
|
13
|
+
- command: "Report CodeClimate Coverage"
|
14
|
+
label: ":codeclimate: Report coverage"
|
15
|
+
plugins:
|
16
|
+
jobready/codeclimate-test-reporter#v2.0:
|
17
|
+
prefix: /app
|
18
|
+
artifact: "coverage/.resultset*.json"
|
19
|
+
input_type: simplecov
|
20
|
+
parts: 1
|
21
|
+
env:
|
22
|
+
CC_TEST_REPORTER_ID:
|
23
|
+
agents:
|
24
|
+
queue: docker-heavy
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
5
|
+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [Unreleased]
|
8
|
+
|
9
|
+
## [2.4.1] - 2018-09-19
|
10
|
+
### Changed
|
11
|
+
- Corrected gem ownership and authors.
|
12
|
+
### Added
|
13
|
+
- Changelog
|
14
|
+
- Dockerfile and BuildKite pipeline config
|
15
|
+
|
16
|
+
## [2.4.0] - 2018-09-17
|
17
|
+
### Added
|
18
|
+
- Added slack notification configurability
|
data/Dockerfile
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2015 JobReady
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
21
|
+
https://opensource.org/licenses/MIT
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Patches
|
2
|
+
[](https://buildkite.com/jobready/patches)
|
3
|
+
[](https://codeclimate.com/repos/557f93b76956807f81000001/maintainability)
|
4
|
+
[](https://codeclimate.com/repos/557f93b76956807f81000001/test_coverage)
|
5
|
+
[](https://badge.fury.io/rb/patches)
|
6
|
+
|
7
|
+

|
8
|
+
|
9
|
+
|
10
|
+
A simple gem for one off tasks
|
11
|
+
|
12
|
+
## Version 2.0
|
13
|
+
|
14
|
+
Please note the breaking change release around deployment. See docs/usage.md for the full change.
|
15
|
+
|
16
|
+
TL;DR You need to manually declare the patches task to run in your deploy.rb
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Add this line to your application's Gemfile:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem 'patches'
|
24
|
+
```
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install patches
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
see `docs/usage.md`
|
36
|
+
|
37
|
+
## Development
|
38
|
+
|
39
|
+
```
|
40
|
+
docker-compose build
|
41
|
+
docker-compose run app bundle exec rspec
|
42
|
+
```
|
43
|
+
|
44
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org/gems/patches).
|
45
|
+
|
46
|
+
## Contributing
|
47
|
+
|
48
|
+
1. Fork it ( https://github.com/jobready/patches/fork )
|
49
|
+
2. Create your feature branch (`git checkout -b feature/my-feature-name`)
|
50
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
51
|
+
4. Push to the branch (`git push origin feature/my-new-feature`)
|
52
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "patches"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/docker-compose.yml
ADDED
data/docs/patches.jpg
ADDED
Binary file
|
data/docs/usage.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# Usage
|
2
|
+
|
3
|
+
## Initial Setup
|
4
|
+
|
5
|
+
Add patches to the project Gemfile
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'patches', '~> 2.4.0'
|
9
|
+
```
|
10
|
+
|
11
|
+
Install the database migration
|
12
|
+
|
13
|
+
```bash
|
14
|
+
bundle exec rake patches:install:migrations
|
15
|
+
```
|
16
|
+
|
17
|
+
Migrate database
|
18
|
+
|
19
|
+
```bash
|
20
|
+
bundle exec rake db:migrate
|
21
|
+
```
|
22
|
+
|
23
|
+
## Configuration
|
24
|
+
|
25
|
+
If you would like to run the patches asynchronously, or would like them to notify
|
26
|
+
your hipchat room or Slack channel when they fail or succeed, you need to set up
|
27
|
+
an initializer to set those options.
|
28
|
+
|
29
|
+
```Ruby
|
30
|
+
Patches::Config.configure do |config|
|
31
|
+
config.use_sidekiq = true
|
32
|
+
|
33
|
+
config.use_hipchat = true
|
34
|
+
config.hipchat_options = {
|
35
|
+
api_token: ENV['HIPCHAT_TOKEN'],
|
36
|
+
room: ENV['HIPCHAT_ROOM'],
|
37
|
+
user: ENV['HIPCHAT_USERNAME'], # maximum of 15 characters
|
38
|
+
api_version: 'v1', # optional
|
39
|
+
}
|
40
|
+
|
41
|
+
config.use_slack = true
|
42
|
+
config.slack_options = {
|
43
|
+
webhook_url: ENV['SLACK_WEBHOOK_URL'],
|
44
|
+
channel: ENV['SLACK_CHANNEL'],
|
45
|
+
username: ENV['SLACK_USER']
|
46
|
+
}
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
### Running patches in parallel for tenants
|
51
|
+
|
52
|
+
If you are using the Apartment gem, you can run the patches for each tenant in parallel.
|
53
|
+
Just set the config ```sidekiq_parallel``` to ```true``` and you're good to go.
|
54
|
+
|
55
|
+
```
|
56
|
+
Patches::Config.configure do |config|
|
57
|
+
config.use_sidekiq = true
|
58
|
+
config.sidekiq_parallel = true
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
*Note:* Make sure your sidekiq queue is able to process concurrent jobs.
|
63
|
+
You can use ```config.sidekiq_options``` to customise it.
|
64
|
+
|
65
|
+
## Creating Patches
|
66
|
+
|
67
|
+
Generate a patch
|
68
|
+
|
69
|
+
```
|
70
|
+
bundle exec rails g patches:patch PreferenceUpdate
|
71
|
+
```
|
72
|
+
|
73
|
+
Which will result in a file like below
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class PreferenceUpdate < Patches::Base
|
77
|
+
def run
|
78
|
+
# Code goes here
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
update the run method and then execute
|
84
|
+
|
85
|
+
Generate patch with specs
|
86
|
+
|
87
|
+
```
|
88
|
+
bundle exec rails g patches:patch PreferenceUpdate --specs=true
|
89
|
+
```
|
90
|
+
|
91
|
+
|
92
|
+
```bash
|
93
|
+
bundle exec rake patches:run
|
94
|
+
```
|
95
|
+
|
96
|
+
Patches will only ever run once, patches will run in order of creation date.
|
97
|
+
|
98
|
+
To run patches on deployment using Capistrano, edit your Capfile and add
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
require 'patches/capistrano'
|
102
|
+
```
|
103
|
+
|
104
|
+
And then in your deploy.rb
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
after 'last_task_you_want_to_run' 'patches:run'
|
108
|
+
```
|
109
|
+
|
110
|
+
If you are using sidekiq and restarting the sidekiq process on the box
|
111
|
+
as a part of the deploy process, please make sure that the patches run task runs
|
112
|
+
after sidekiq restarts, otherwise there is no guarentee the tasks will run.
|
113
|
+
|
114
|
+
## File Download
|
115
|
+
|
116
|
+
If a patch requires data assets, you could use S3 to store the file.
|
117
|
+
If credentials are defined in env vars, as per https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#id1
|
118
|
+
|
119
|
+
```
|
120
|
+
require 'aws-sdk-s3'
|
121
|
+
Aws::S3::Client.new.get_object(bucket: @bucket_name, key: filename, response_target: destination)
|
122
|
+
```
|
123
|
+
|
124
|
+
## Multitenant
|
125
|
+
|
126
|
+
Patches will detect if `Apartment` gem is installed and if there are any tenants
|
127
|
+
and run the patches for each tenant
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Patches
|
2
|
+
module Generators
|
3
|
+
class PatchGenerator < Rails::Generators::NamedBase
|
4
|
+
desc 'Adds an empty patch'
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
6
|
+
|
7
|
+
class_option :specs, type: :boolean, default: false, description: 'Generates a rspec file for the patch'
|
8
|
+
|
9
|
+
def generate_patch
|
10
|
+
template "patch.rb.erb", "db/patches/#{file_name}.rb"
|
11
|
+
template "patch_spec.rb.erb", "spec/patches/#{file_name}_spec.rb" if options['specs']
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def class_name
|
17
|
+
name.camelize
|
18
|
+
end
|
19
|
+
|
20
|
+
def file_name
|
21
|
+
"#{Time.now.strftime('%Y%m%d%H%M')}_#{name.underscore}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Patches
|
2
|
+
module Generators
|
3
|
+
class PatchGenerator < Rails::Generators::NamedBase
|
4
|
+
desc 'Adds an empty patch'
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
6
|
+
|
7
|
+
def generate_patch
|
8
|
+
template "patch.erb", "app/db/#{file_name}.rb"
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def class_name
|
14
|
+
name.camelize
|
15
|
+
end
|
16
|
+
|
17
|
+
def file_name
|
18
|
+
name.underscore
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/patches/base.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
class Patches::Base
|
2
|
+
|
3
|
+
# TODO: Use this :/
|
4
|
+
def self.tenant(name)
|
5
|
+
@tenant = name
|
6
|
+
end
|
7
|
+
|
8
|
+
def run
|
9
|
+
logger.warn("Run method not implemented for #{self.class.name}")
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def execute(sql)
|
14
|
+
ActiveRecord::Base.connection.execute(sql)
|
15
|
+
end
|
16
|
+
|
17
|
+
def logger
|
18
|
+
Patches.logger
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
load File.expand_path("capistrano/tasks.rake", File.dirname(__FILE__))
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Patches
|
2
|
+
class Config
|
3
|
+
class << self
|
4
|
+
def configuration
|
5
|
+
@configuration ||= Configuration.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def configuration=(config)
|
9
|
+
@configuration = config
|
10
|
+
end
|
11
|
+
|
12
|
+
def configure
|
13
|
+
yield configuration
|
14
|
+
end
|
15
|
+
|
16
|
+
class Configuration
|
17
|
+
attr_accessor :use_sidekiq, :sidekiq_queue, :sidekiq_options, :use_hipchat,
|
18
|
+
:hipchat_options, :sidekiq_parallel, :use_slack, :slack_options
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@sidekiq_queue = 'default'
|
22
|
+
end
|
23
|
+
|
24
|
+
def sidekiq_options
|
25
|
+
@sidekiq_options ||= { retry: false, queue: sidekiq_queue }
|
26
|
+
end
|
27
|
+
|
28
|
+
def hipchat_api_token
|
29
|
+
hipchat_options[:api_token]
|
30
|
+
end
|
31
|
+
|
32
|
+
def hipchat_init_options
|
33
|
+
hipchat_options.except(:api_token, :room, :user)
|
34
|
+
end
|
35
|
+
|
36
|
+
def hipchat_room
|
37
|
+
hipchat_options[:room]
|
38
|
+
end
|
39
|
+
|
40
|
+
def hipchat_user
|
41
|
+
hipchat_options[:user]
|
42
|
+
end
|
43
|
+
|
44
|
+
def slack_channel
|
45
|
+
slack_options[:channel]
|
46
|
+
end
|
47
|
+
|
48
|
+
def slack_username
|
49
|
+
slack_options[:username]
|
50
|
+
end
|
51
|
+
|
52
|
+
def slack_webhook_url
|
53
|
+
slack_options[:webhook_url]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'slack-notifier'
|
2
|
+
|
3
|
+
class Patches::Notifier
|
4
|
+
class << self
|
5
|
+
def notify_success(patches)
|
6
|
+
send_hipchat_message(success_message(patches), color: 'green')
|
7
|
+
send_slack_message(success_message(patches), 'good')
|
8
|
+
end
|
9
|
+
|
10
|
+
def notify_failure(patch_path, error)
|
11
|
+
send_hipchat_message(failure_message(patch_path, error), color: 'red')
|
12
|
+
send_slack_message(failure_message(patch_path, error), 'danger')
|
13
|
+
end
|
14
|
+
|
15
|
+
def success_message(patches)
|
16
|
+
message = "#{environment_prefix}#{patches.count} patches succeeded"
|
17
|
+
append_tenant_message(message)
|
18
|
+
end
|
19
|
+
|
20
|
+
def failure_message(patch_path, error)
|
21
|
+
details = "#{Pathname.new(patch_path).basename} failed with error: #{error}"
|
22
|
+
message = "#{environment_prefix}Error applying patch: #{details}"
|
23
|
+
append_tenant_message(message)
|
24
|
+
end
|
25
|
+
|
26
|
+
def environment_prefix
|
27
|
+
"[#{Rails.env.upcase}] " if defined?(Rails)
|
28
|
+
end
|
29
|
+
|
30
|
+
def append_tenant_message(message)
|
31
|
+
message = message + " for tenant: #{Apartment::Tenant.current}" if defined?(Apartment)
|
32
|
+
message
|
33
|
+
end
|
34
|
+
|
35
|
+
def send_hipchat_message(message, options)
|
36
|
+
return unless defined?(HipChat) && config.use_hipchat
|
37
|
+
|
38
|
+
client = HipChat::Client.new(config.hipchat_api_token, config.hipchat_init_options)
|
39
|
+
room = client[config.hipchat_room]
|
40
|
+
room.send(config.hipchat_user, message, options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def send_slack_message(message, color)
|
44
|
+
return unless defined?(Slack) && config.use_slack
|
45
|
+
|
46
|
+
notifier = Slack::Notifier.new(
|
47
|
+
config.slack_webhook_url,
|
48
|
+
channel: config.slack_channel,
|
49
|
+
username: config.slack_username)
|
50
|
+
|
51
|
+
payload = { attachments: [{ color: color, text: message }] }
|
52
|
+
|
53
|
+
notifier.post payload
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def config
|
59
|
+
Patches::Config.configuration
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Patches::Pending
|
2
|
+
include Enumerable
|
3
|
+
|
4
|
+
attr_accessor :path
|
5
|
+
|
6
|
+
def initialize(path=nil)
|
7
|
+
@path = path || Patches.default_path
|
8
|
+
end
|
9
|
+
|
10
|
+
def each
|
11
|
+
return nil unless files
|
12
|
+
new_files = files.reject { |file| already_run?(file) }
|
13
|
+
Patches.logger.info("Patches found: #{new_files.join(',')}")
|
14
|
+
|
15
|
+
new_files.each do |file|
|
16
|
+
unless already_run?(file)
|
17
|
+
yield file
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def files
|
25
|
+
@files ||= Dir[File.join(path, "*.rb")].to_a.sort
|
26
|
+
end
|
27
|
+
|
28
|
+
def already_run?(path)
|
29
|
+
patches[File.basename(path)]
|
30
|
+
end
|
31
|
+
|
32
|
+
def patches
|
33
|
+
@patches ||= Patches::Patch.path_lookup
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Patches::Runner
|
2
|
+
attr_accessor :path
|
3
|
+
|
4
|
+
class UnknownPatch < StandardError; end
|
5
|
+
|
6
|
+
def initialize(path = nil)
|
7
|
+
@path = path || Patches.default_path
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform
|
11
|
+
completed_patches = pending.each do |file_path|
|
12
|
+
klass = load_class(file_path)
|
13
|
+
instance = klass.new
|
14
|
+
Patches.logger.info("Running #{klass} from #{file_path}")
|
15
|
+
begin
|
16
|
+
instance.run
|
17
|
+
rescue => e
|
18
|
+
Patches::Notifier.notify_failure(file_path, format_exception(e))
|
19
|
+
raise e
|
20
|
+
end
|
21
|
+
complete!(patch_path(file_path))
|
22
|
+
end
|
23
|
+
Patches::Notifier.notify_success(completed_patches)
|
24
|
+
completed_patches
|
25
|
+
end
|
26
|
+
|
27
|
+
def complete!(patch_path)
|
28
|
+
Patches::Patch.create!(path: patch_path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def patch_path(patch_path)
|
32
|
+
File.basename(patch_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def pending
|
36
|
+
@pending ||= Patches::Pending.new(path)
|
37
|
+
end
|
38
|
+
|
39
|
+
def format_exception(e)
|
40
|
+
"#{e.class.name}: #{e.message} (#{e.backtrace.first})"
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def load_class(path)
|
45
|
+
begin
|
46
|
+
name = Patches.class_name(path)
|
47
|
+
load path
|
48
|
+
name.constantize
|
49
|
+
rescue StandardError, LoadError
|
50
|
+
raise UnknownPatch, "#{path} should define #{name}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Patches::TenantRunner
|
2
|
+
include Patches::TenantRunConcern
|
3
|
+
attr_accessor :path
|
4
|
+
|
5
|
+
def initialize(path: nil, tenants: nil)
|
6
|
+
@path = path
|
7
|
+
@tenants = tenants
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform
|
11
|
+
Patches.logger.info("Patches tenant runner for: #{tenants.join(',')}")
|
12
|
+
tenants.each do |tenant|
|
13
|
+
if parallel?
|
14
|
+
Patches::TenantWorker.perform_async(tenant, path)
|
15
|
+
else
|
16
|
+
run(tenant, path)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def tenants
|
22
|
+
@tenants ||= (Apartment.tenant_names || [])
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def parallel?
|
28
|
+
Patches::Config.configuration.sidekiq_parallel
|
29
|
+
end
|
30
|
+
end
|
data/lib/patches.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "patches/version"
|
2
|
+
|
3
|
+
module Patches
|
4
|
+
def self.default_path
|
5
|
+
Rails.root.join('db/patches/') if defined?(:Rails)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.class_name(path)
|
9
|
+
match = path.match(/\d+_(.+?)\.rb/)
|
10
|
+
match[1].camelcase if match
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.logger
|
14
|
+
@logger ||= Logger.new(STDOUT)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.logger=(log)
|
18
|
+
@logger = log
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require "patches/base"
|
23
|
+
require "patches/config"
|
24
|
+
require "patches/tenant_run_concern"
|
25
|
+
require "patches/tenant_worker" if defined?(Sidekiq)
|
26
|
+
require "patches/engine" if defined?(Rails)
|
27
|
+
require "patches/patch"
|
28
|
+
require "patches/pending"
|
29
|
+
require "patches/runner"
|
30
|
+
require "patches/tenant_runner"
|
31
|
+
require "patches/notifier"
|
32
|
+
require "patches/worker" if defined?(Sidekiq)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
namespace :patches do
|
2
|
+
desc "Run Patches"
|
3
|
+
task :run => [:environment] do
|
4
|
+
if defined?(Apartment) && tenants.present?
|
5
|
+
runner = Patches::TenantRunner
|
6
|
+
else
|
7
|
+
runner = Patches::Runner
|
8
|
+
end
|
9
|
+
|
10
|
+
if defined?(Sidekiq) && Patches::Config.configuration.use_sidekiq
|
11
|
+
Patches::Worker.perform_async(runner)
|
12
|
+
else
|
13
|
+
runner.new.perform
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def tenants
|
18
|
+
ENV['DB'] ? ENV['DB'].split(',').map { |s| s.strip } : Apartment.tenant_names || []
|
19
|
+
end
|
20
|
+
|
21
|
+
task :pending => [:environment] do
|
22
|
+
Patches::Pending.each do |patch|
|
23
|
+
puts patch
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/patches.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'patches/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "better-patches"
|
8
|
+
spec.version = Patches::VERSION
|
9
|
+
spec.authors = ["John D'Agostino"]
|
10
|
+
spec.email = ["john.dagostino@gmail.com"]
|
11
|
+
|
12
|
+
spec.licenses = ['MIT']
|
13
|
+
spec.summary = %q{A simple gem for one off tasks}
|
14
|
+
spec.description = %q{A simple gem for one off tasks for example database patches}
|
15
|
+
spec.homepage = "http://github.com/johndagostino/patches"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "railties", ">= 3.2"
|
23
|
+
spec.add_dependency "slack-notifier"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.8"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
spec.add_development_dependency "sqlite3", "~> 1.3.5"
|
28
|
+
spec.add_development_dependency "rspec-rails", "~> 3.2.0"
|
29
|
+
spec.add_development_dependency "capybara", "~> 2.3.0"
|
30
|
+
spec.add_development_dependency "generator_spec", "~> 0.9.0"
|
31
|
+
spec.add_development_dependency "simplecov", "~> 0.10"
|
32
|
+
spec.add_development_dependency "factory_girl", "~> 4.5.0"
|
33
|
+
spec.add_development_dependency "timecop", "~> 0.7.0"
|
34
|
+
spec.add_development_dependency "database_cleaner", "~> 1.3.0"
|
35
|
+
spec.add_development_dependency "pry"
|
36
|
+
spec.add_development_dependency "sidekiq", "~> 3.4.1"
|
37
|
+
spec.add_development_dependency "hipchat"
|
38
|
+
spec.add_development_dependency "webmock"
|
39
|
+
spec.add_development_dependency "byebug"
|
40
|
+
end
|
data/script/buildkite.sh
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
set -e
|
3
|
+
|
4
|
+
echo '--- setting ruby version'
|
5
|
+
cd /var/lib/buildkite-agent/.rbenv/plugins/ruby-build && git pull && cd -
|
6
|
+
rbenv install 2.3.7 -s
|
7
|
+
rbenv local 2.3.7
|
8
|
+
|
9
|
+
echo '--- setting up env'
|
10
|
+
REVISION=https://github.com/$BUILDBOX_PROJECT_SLUG/commit/$BUILDBOX_COMMIT
|
11
|
+
|
12
|
+
echo '--- bundling'
|
13
|
+
bundle install -j $(nproc) --without production --quiet
|
14
|
+
|
15
|
+
echo '--- running specs'
|
16
|
+
if bundle exec rspec; then
|
17
|
+
echo "[Successful] $BUILDBOX_PROJECT_SLUG - Build - $BUILDBOX_BUILD_URL - Commit - $REVISION" | hipchat_room_message -t $HIPCHAT_TOKEN -r $HIPCHAT_ROOM -f "Buildbox" -c "green"
|
18
|
+
else
|
19
|
+
echo "[Failed] Build $BUILDBOX_PROJECT_SLUG - Build - $BUILDBOX_BUILD_URL - Commit - $REVISION" | hipchat_room_message -t $HIPCHAT_TOKEN -r $HIPCHAT_ROOM -f "Buildbox" -c "red"
|
20
|
+
exit 1;
|
21
|
+
fi
|
metadata
ADDED
@@ -0,0 +1,319 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: better-patches
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.4.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John D'Agostino
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-09-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: railties
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: slack-notifier
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.8'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.3.5
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.3.5
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec-rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 3.2.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 3.2.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: capybara
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.3.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.3.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: generator_spec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.9.0
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.9.0
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.10'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.10'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: factory_girl
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 4.5.0
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 4.5.0
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: timecop
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 0.7.0
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 0.7.0
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: database_cleaner
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 1.3.0
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 1.3.0
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: pry
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: sidekiq
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - "~>"
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: 3.4.1
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - "~>"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: 3.4.1
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: hipchat
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: webmock
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
237
|
+
- !ruby/object:Gem::Dependency
|
238
|
+
name: byebug
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
240
|
+
requirements:
|
241
|
+
- - ">="
|
242
|
+
- !ruby/object:Gem::Version
|
243
|
+
version: '0'
|
244
|
+
type: :development
|
245
|
+
prerelease: false
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
247
|
+
requirements:
|
248
|
+
- - ">="
|
249
|
+
- !ruby/object:Gem::Version
|
250
|
+
version: '0'
|
251
|
+
description: A simple gem for one off tasks for example database patches
|
252
|
+
email:
|
253
|
+
- john.dagostino@gmail.com
|
254
|
+
executables: []
|
255
|
+
extensions: []
|
256
|
+
extra_rdoc_files: []
|
257
|
+
files:
|
258
|
+
- ".buildkite/pipeline.yml"
|
259
|
+
- ".gitignore"
|
260
|
+
- ".rspec"
|
261
|
+
- CHANGELOG.md
|
262
|
+
- Dockerfile
|
263
|
+
- Gemfile
|
264
|
+
- LICENSE.md
|
265
|
+
- README.md
|
266
|
+
- Rakefile
|
267
|
+
- bin/console
|
268
|
+
- bin/setup
|
269
|
+
- db/migrate/201506011700_create_patch.rb
|
270
|
+
- docker-compose.yml
|
271
|
+
- docs/patches.jpg
|
272
|
+
- docs/usage.md
|
273
|
+
- lib/generators/patches.rb
|
274
|
+
- lib/generators/patches/patch_generator.rb
|
275
|
+
- lib/generators/patches/templates/patch.rb.erb
|
276
|
+
- lib/generators/patches/templates/patch_spec.rb.erb
|
277
|
+
- lib/patches.rb
|
278
|
+
- lib/patches/base.rb
|
279
|
+
- lib/patches/capistrano.rb
|
280
|
+
- lib/patches/capistrano/tasks.rake
|
281
|
+
- lib/patches/config.rb
|
282
|
+
- lib/patches/engine.rb
|
283
|
+
- lib/patches/notifier.rb
|
284
|
+
- lib/patches/patch.rb
|
285
|
+
- lib/patches/pending.rb
|
286
|
+
- lib/patches/runner.rb
|
287
|
+
- lib/patches/tenant_run_concern.rb
|
288
|
+
- lib/patches/tenant_runner.rb
|
289
|
+
- lib/patches/tenant_worker.rb
|
290
|
+
- lib/patches/version.rb
|
291
|
+
- lib/patches/worker.rb
|
292
|
+
- lib/tasks/patches.rake
|
293
|
+
- patches.gemspec
|
294
|
+
- script/buildkite.sh
|
295
|
+
homepage: http://github.com/johndagostino/patches
|
296
|
+
licenses:
|
297
|
+
- MIT
|
298
|
+
metadata: {}
|
299
|
+
post_install_message:
|
300
|
+
rdoc_options: []
|
301
|
+
require_paths:
|
302
|
+
- lib
|
303
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
304
|
+
requirements:
|
305
|
+
- - ">="
|
306
|
+
- !ruby/object:Gem::Version
|
307
|
+
version: '0'
|
308
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
309
|
+
requirements:
|
310
|
+
- - ">="
|
311
|
+
- !ruby/object:Gem::Version
|
312
|
+
version: '0'
|
313
|
+
requirements: []
|
314
|
+
rubyforge_project:
|
315
|
+
rubygems_version: 2.5.2.3
|
316
|
+
signing_key:
|
317
|
+
specification_version: 4
|
318
|
+
summary: A simple gem for one off tasks
|
319
|
+
test_files: []
|