migrake 0.1.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.
- data/.gitignore +3 -0
- data/.travis.yml +11 -0
- data/Gemfile +2 -0
- data/README.md +115 -0
- data/Rakefile +15 -0
- data/examples/Rakefile +10 -0
- data/lib/migrake/dsl.rb +45 -0
- data/lib/migrake/version.rb +3 -0
- data/lib/migrake.rb +82 -0
- data/migrake.gemspec +19 -0
- data/test/runner_test.rb +54 -0
- data/test/store_test.rb +45 -0
- data/test/test_helper.rb +9 -0
- metadata +80 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# Migrake
|
2
|
+
|
3
|
+
[](http://travis-ci.org/foca/migrake)
|
4
|
+
|
5
|
+
A simple [Rake][rake] extension that lets you define tasks that are divided into
|
6
|
+
multiple smaller "versions". When you run the task in a given host, it will only
|
7
|
+
run the "versions" that haven't yet been run in that host. Think something
|
8
|
+
similar to [ActiveRecord::Migration][migrations] for Rake tasks.
|
9
|
+
|
10
|
+
This is specially useful to define tasks that should be run on deploy, in order
|
11
|
+
to do some sort of house cleaning that should be run once. For example, some
|
12
|
+
complex data migrations, or re-processing your user avatars because now the UI
|
13
|
+
shows them in a different size.
|
14
|
+
|
15
|
+
The important thing is these tasks should only be run once when you deploy to a
|
16
|
+
given environment, and they should be run immediately.
|
17
|
+
|
18
|
+
[rake]: http://rake.rubyforge.org
|
19
|
+
[migrations]: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html
|
20
|
+
|
21
|
+
## An example
|
22
|
+
|
23
|
+
In `lib/tasks/migrake.rake`:
|
24
|
+
|
25
|
+
``` ruby
|
26
|
+
require "migrake"
|
27
|
+
|
28
|
+
migrake Set.new([
|
29
|
+
"data:set_default_user_state",
|
30
|
+
"twitter:purge_cache",
|
31
|
+
# etc, etc
|
32
|
+
])
|
33
|
+
```
|
34
|
+
|
35
|
+
This would define a task called `:migrake` that, when called, will invoke the
|
36
|
+
tasks in that set _unless they have been invoked before_. This means that after
|
37
|
+
each deploy you can run this task (with a capistrano hook, for example) and any
|
38
|
+
tasks that need to be run in this environment will be run.
|
39
|
+
|
40
|
+
## How it works
|
41
|
+
|
42
|
+
This will keep a file named `MIGRAKE_STATUS`, that will contain the name of the
|
43
|
+
tasks that were ran in the past. Whenever you run `rake migrake` migrake will
|
44
|
+
check that file, see which tasks in the set aren't in it, and will run those
|
45
|
+
tasks (no order is guaranteed, if you need tasks to run in order, define the
|
46
|
+
dependencies in the task themselves.)
|
47
|
+
|
48
|
+
## Overriding where to keep the MIGRAKE_STATUS file
|
49
|
+
|
50
|
+
The file will be located, by default, at whichever directory `rake` is invoked
|
51
|
+
from. In order to change that (for example, to put it in capistrano's `shared`
|
52
|
+
directory), do either of the following:
|
53
|
+
|
54
|
+
Define `Migrake.status_file_directory` before running the tasks (so, for
|
55
|
+
example, at the top of your `migrake.rake`):
|
56
|
+
|
57
|
+
``` ruby
|
58
|
+
Migrake.status_file_directory = File.expand_path("../tmp", __FILE__)
|
59
|
+
```
|
60
|
+
|
61
|
+
Or as an alternative, pass it as an environment variable to Rake:
|
62
|
+
|
63
|
+
``` shell
|
64
|
+
MIGRAKE_STATUS_DIR=./some/path bundle exec rake migrake
|
65
|
+
```
|
66
|
+
|
67
|
+
The latter lets you define it within the capistrano definition file, like this:
|
68
|
+
|
69
|
+
``` ruby
|
70
|
+
namespace :deploy do
|
71
|
+
task :migrake do
|
72
|
+
run "MIGRAKE_STATUS_DIR=#{shared_path} bundle exec rake migrake"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
after "deploy:migrations", "deploy:migrake"
|
77
|
+
```
|
78
|
+
|
79
|
+
## Bootstrapping a new environment
|
80
|
+
|
81
|
+
When you bootstrap a new environment you don't need to run migrake tasks that
|
82
|
+
have been already run. For this, when the `migrake` method is invoked we also
|
83
|
+
define a `migrake:ready` task, that forces all tasks defined in the migrake set
|
84
|
+
into the `MIGRAKE_STATUS` file.
|
85
|
+
|
86
|
+
This way you can just run that when you bootstrap the environment and then keep
|
87
|
+
running `rake migrake` when you deploy.
|
88
|
+
|
89
|
+
## License
|
90
|
+
|
91
|
+
(The MIT License)
|
92
|
+
|
93
|
+
Copyright (c) 2012 [Nicolas Sanguinetti][me], with the support of [Cubox][cubox]
|
94
|
+
|
95
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
96
|
+
a copy of this software and associated documentation files (the
|
97
|
+
'Software'), to deal in the Software without restriction, including
|
98
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
99
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
100
|
+
permit persons to whom the Software is furnished to do so, subject to
|
101
|
+
the following conditions:
|
102
|
+
|
103
|
+
The above copyright notice and this permission notice shall be
|
104
|
+
included in all copies or substantial portions of the Software.
|
105
|
+
|
106
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
107
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
108
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
109
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
110
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
111
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
112
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
113
|
+
|
114
|
+
[me]: http://nicolassanguinetti.info
|
115
|
+
[cubox]: http://cuboxlabs.com
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "rake/testtask"
|
2
|
+
require "rubygems/package_task"
|
3
|
+
|
4
|
+
gem_spec = eval(File.read("./migrake.gemspec"))
|
5
|
+
Gem::PackageTask.new(gem_spec) do |pkg|
|
6
|
+
pkg.need_zip = false
|
7
|
+
pkg.need_tar = false
|
8
|
+
end
|
9
|
+
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.pattern = "test/*_test.rb"
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
task default: :test
|
data/examples/Rakefile
ADDED
data/lib/migrake/dsl.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require "migrake"
|
2
|
+
|
3
|
+
module Migrake
|
4
|
+
module DSL
|
5
|
+
# Public: Define the rake tasks required to run migrake. This defines the
|
6
|
+
# following tasks:
|
7
|
+
#
|
8
|
+
# - A task to create the `Migrake.status_file_directory` unless it exists.
|
9
|
+
# - A task to create the MIGRAKE_STATUS file in the aforementioned
|
10
|
+
# directory, unless it exists.
|
11
|
+
# - A `migrake:ready` task to bootstrap new environments by writing all
|
12
|
+
# the tasks to the store.
|
13
|
+
# - The `migrake` task, that will run any tasks not run before.
|
14
|
+
#
|
15
|
+
# tasks - A Set of tasks to be run.
|
16
|
+
#
|
17
|
+
# Returns the `migrake` Rake::Task.
|
18
|
+
def migrake(tasks)
|
19
|
+
dir = Migrake.status_file_directory
|
20
|
+
status_file = dir.join("MIGRAKE_STATUS")
|
21
|
+
|
22
|
+
namespace :migrake do
|
23
|
+
directory dir.to_s
|
24
|
+
|
25
|
+
file status_file.to_s => dir.to_s do
|
26
|
+
touch status_file
|
27
|
+
end
|
28
|
+
|
29
|
+
task :run_tasks => status_file.to_s do
|
30
|
+
Migrake::Runner.run(tasks, Migrake::Store.new(status_file))
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Tell migrake that all defined tasks have already been run"
|
34
|
+
task :ready => status_file.to_s do
|
35
|
+
Migrake::Store.new(status_file).write(tasks)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "Run the tasks defined by migrake"
|
40
|
+
task migrake: "migrake:run_tasks"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
extend Migrake::DSL
|
data/lib/migrake.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require "rake/task"
|
2
|
+
require "migrake/dsl"
|
3
|
+
require "migrake/version"
|
4
|
+
require "yaml"
|
5
|
+
require "set"
|
6
|
+
require "pathname"
|
7
|
+
|
8
|
+
module Migrake
|
9
|
+
# Public: Change the directory where we keep the MIGRAKE_STATUS file.
|
10
|
+
#
|
11
|
+
# dir - A filesystem path.
|
12
|
+
#
|
13
|
+
# Returns the directory path.
|
14
|
+
def self.status_file_directory=(dir)
|
15
|
+
@status_dir = Pathname(dir)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: Get the directory where we keep the MIGRAKE_STATUS file. If no
|
19
|
+
# directory is set, we will try to fetch it from the ENV, using the
|
20
|
+
# MIGRAKE_STATUS_DIR environment variable. If that isn't set either, we
|
21
|
+
# default to the working directory.
|
22
|
+
#
|
23
|
+
# Returns a Pathname.
|
24
|
+
def self.status_file_directory
|
25
|
+
@status_dir ||= Pathname(ENV.fetch("MIGRAKE_STATUS_DIR", Dir.pwd))
|
26
|
+
end
|
27
|
+
|
28
|
+
# The Runner is responsible for selecting the rake tasks to be run and run
|
29
|
+
# them, unless they have already been run in this host.
|
30
|
+
module Runner
|
31
|
+
# Public: Run all the rake tasks in `set` that haven't been stored in
|
32
|
+
# `store`.
|
33
|
+
#
|
34
|
+
# set - A Set with tasks to be run.
|
35
|
+
# store - A Migrake::Store with the tasks that have already been run.
|
36
|
+
#
|
37
|
+
# Returns the Set of tasks that were run.
|
38
|
+
def self.run(set, store)
|
39
|
+
(set - store.all).each do |task|
|
40
|
+
Rake::Task[task].invoke
|
41
|
+
store.put(task)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# The Store handles the file where tasks that have already been run are stored
|
47
|
+
# so they aren't run again.
|
48
|
+
class Store
|
49
|
+
# Public: Initialize the store.
|
50
|
+
#
|
51
|
+
# path - The path to the file where we store the information.
|
52
|
+
def initialize(path)
|
53
|
+
@path = path
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: Add one task to the store. This immediately writes the file to
|
57
|
+
# disk, to preserve consistency.
|
58
|
+
#
|
59
|
+
# task - A string with a task's name.
|
60
|
+
#
|
61
|
+
# Returns nil.
|
62
|
+
def put(task)
|
63
|
+
write(all << task)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Public: Load all the tasks from the store.
|
67
|
+
#
|
68
|
+
# Returns a Set.
|
69
|
+
def all
|
70
|
+
@all ||= YAML.load(@path.read) || Set.new
|
71
|
+
end
|
72
|
+
|
73
|
+
# Public: Write a whole set of tasks to the store, replacing what is in it.
|
74
|
+
#
|
75
|
+
# set - A Set of tasks.
|
76
|
+
#
|
77
|
+
# Returns nil.
|
78
|
+
def write(set)
|
79
|
+
@path.open("w+") { |f| f.puts YAML.dump(set) }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/migrake.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# For some reason, 'require "migrake/version"' doesn't work in jruby (1.6.6, on
|
2
|
+
# 1.9 mode), even with RUBYOPT=-Ilib, so resorting to this hackery.
|
3
|
+
require File.expand_path("../lib/migrake/version", __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "migrake"
|
7
|
+
s.version = Migrake::VERSION
|
8
|
+
s.description = "Migrake allows you to run rake tasks that only need to be run once in a given environment each time you deploy"
|
9
|
+
s.summary = "Migrake is like ActiveRecord::Migration for Rake"
|
10
|
+
s.authors = ["Nicolas Sanguinetti"]
|
11
|
+
s.email = "hi@nicolassanguinetti.info"
|
12
|
+
s.homepage = "http://github.com/foca/migrake"
|
13
|
+
s.has_rdoc = false
|
14
|
+
s.files = `git ls-files`.split "\n"
|
15
|
+
s.platform = Gem::Platform::RUBY
|
16
|
+
|
17
|
+
s.add_dependency("rake", ">= 0.9.2")
|
18
|
+
s.add_development_dependency("minitest")
|
19
|
+
end
|
data/test/runner_test.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
require "rake"
|
3
|
+
|
4
|
+
describe Migrake::Runner do
|
5
|
+
it "runs tasks that aren't in the store" do
|
6
|
+
define_tasks "a", "b", "c"
|
7
|
+
define_store "a"
|
8
|
+
|
9
|
+
expect_tasks_ran "b", "c"
|
10
|
+
|
11
|
+
Migrake::Runner.run(Set.new(["a", "b", "c"]), store)
|
12
|
+
|
13
|
+
store.verify
|
14
|
+
end
|
15
|
+
|
16
|
+
it "runs no tasks if all the tasks are in the store" do
|
17
|
+
define_tasks "a", "b", "c"
|
18
|
+
define_store "a", "b", "c"
|
19
|
+
# setting no expectation for tasks run passes only if no tasks are run
|
20
|
+
|
21
|
+
Migrake::Runner.run(Set.new(["a", "b", "c"]), store)
|
22
|
+
|
23
|
+
store.verify
|
24
|
+
end
|
25
|
+
|
26
|
+
it "doesn't mind stored tasks that don't exist anymore" do
|
27
|
+
define_tasks "a"
|
28
|
+
define_store "b", "c"
|
29
|
+
|
30
|
+
expect_tasks_ran "a"
|
31
|
+
|
32
|
+
Migrake::Runner.run(Set.new(["a", "b", "c"]), store)
|
33
|
+
|
34
|
+
store.verify
|
35
|
+
end
|
36
|
+
|
37
|
+
def store
|
38
|
+
@store ||= MiniTest::Mock.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def define_store(*tasks)
|
42
|
+
store.expect :all, Set.new(tasks)
|
43
|
+
end
|
44
|
+
|
45
|
+
def define_tasks(*tasks)
|
46
|
+
tasks.each { |task| Rake::Task.define_task(task) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def expect_tasks_ran(*tasks)
|
50
|
+
Set.new(tasks).each do |task|
|
51
|
+
store.expect :put, nil, [task]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/test/store_test.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
require "tempfile"
|
3
|
+
|
4
|
+
describe Migrake::Store do
|
5
|
+
it "can read a stored set" do
|
6
|
+
set = Set.new(["a", "b", "c"])
|
7
|
+
save_in_source set
|
8
|
+
|
9
|
+
store = Migrake::Store.new(source)
|
10
|
+
assert_equal set, store.all
|
11
|
+
end
|
12
|
+
|
13
|
+
it "an empty file means an empty set" do
|
14
|
+
store = Migrake::Store.new(source)
|
15
|
+
assert_equal Set.new, store.all
|
16
|
+
end
|
17
|
+
|
18
|
+
it "can add an entry to the store" do
|
19
|
+
store = Migrake::Store.new(source)
|
20
|
+
store.put("test")
|
21
|
+
|
22
|
+
assert_equal Set.new(["test"]), store.all
|
23
|
+
end
|
24
|
+
|
25
|
+
it "can write a set replacing whatever is there on the file" do
|
26
|
+
save_in_source Set.new(["d", "e", "f", "g"])
|
27
|
+
|
28
|
+
store = Migrake::Store.new(source)
|
29
|
+
store.write Set.new(["a", "b", "c"])
|
30
|
+
|
31
|
+
assert_equal Set.new(["a", "b", "c"]), store.all
|
32
|
+
end
|
33
|
+
|
34
|
+
after do
|
35
|
+
source.unlink # just to be polite
|
36
|
+
end
|
37
|
+
|
38
|
+
def save_in_source(set)
|
39
|
+
source.open("w+") { |f| f.puts YAML.dump(set) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def source
|
43
|
+
@source ||= Pathname(Tempfile.new("store").path)
|
44
|
+
end
|
45
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: migrake
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nicolas Sanguinetti
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70142092483920 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.9.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70142092483920
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: minitest
|
27
|
+
requirement: &70142092483500 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70142092483500
|
36
|
+
description: Migrake allows you to run rake tasks that only need to be run once in
|
37
|
+
a given environment each time you deploy
|
38
|
+
email: hi@nicolassanguinetti.info
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- .travis.yml
|
45
|
+
- Gemfile
|
46
|
+
- README.md
|
47
|
+
- Rakefile
|
48
|
+
- examples/Rakefile
|
49
|
+
- lib/migrake.rb
|
50
|
+
- lib/migrake/dsl.rb
|
51
|
+
- lib/migrake/version.rb
|
52
|
+
- migrake.gemspec
|
53
|
+
- test/runner_test.rb
|
54
|
+
- test/store_test.rb
|
55
|
+
- test/test_helper.rb
|
56
|
+
homepage: http://github.com/foca/migrake
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.8.11
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Migrake is like ActiveRecord::Migration for Rake
|
80
|
+
test_files: []
|