migrake 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ /MIGRAKE_STATUS
2
+ /Gemfile.lock
3
+ /pkg
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - rbx-19mode
6
+ - jruby-19mode
7
+ env:
8
+ - RUBYOPT="$RUBYOPT -Itest -Ilib"
9
+ notifications:
10
+ email:
11
+ - contacto@nicolassanguinetti.info
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
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
@@ -0,0 +1,10 @@
1
+ require "migrake"
2
+
3
+ migrake Set.new(%w(a b c d))
4
+
5
+ %w(a b c d).each do |t|
6
+ desc "Print #{t}"
7
+ task t do
8
+ puts t
9
+ end
10
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module Migrake
2
+ VERSION = "0.1.0"
3
+ end
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
@@ -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
@@ -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
@@ -0,0 +1,9 @@
1
+ require "minitest/autorun"
2
+ require "migrake"
3
+
4
+ begin
5
+ require "purdytest"
6
+ rescue LoadError
7
+ # Oh well, no colorized tests for you. You can always use minitest/pride if
8
+ # you want :P
9
+ end
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: []