migrake 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://secure.travis-ci.org/foca/migrake.png?branch=master)](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: []
|