nickstenning-outback 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ module Outback
2
+
3
+ class Command
4
+
5
+ def initialize( script )
6
+ @manager = Manager.new
7
+ parse(script)
8
+ end
9
+
10
+ def parse( script )
11
+ script.each_line do |l|
12
+ out, back = l[/^([^\^]+)\s*\^\s*(.+)$/]
13
+ p "o:", out, "b:", back
14
+ if out and back
15
+ p out, back
16
+ t = Task.new("temp")
17
+ t.rollout { |t| t.sys out }
18
+ t.rollback { |t| t.sys back }
19
+ @manager.tasks << t
20
+ end
21
+ end
22
+ end
23
+
24
+ def run( command )
25
+ raise %{Command "#{command}" not understood.} unless [:rollout, :rollback].include? command
26
+
27
+ #@manager.tasks.each
28
+ #@manager.send(command)
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,100 @@
1
+ require 'yaml'
2
+ require 'tempfile'
3
+
4
+ module Outback
5
+
6
+ class Manager
7
+
8
+ attr_reader :tasks, :status, :errors, :position
9
+ attr_writer :logger
10
+ attr_accessor :workdir
11
+
12
+ def initialize
13
+ @tasks = []
14
+ @statuses = []
15
+ @errors = []
16
+ @position = 0
17
+ end
18
+
19
+ def rollout
20
+ @direction = 1
21
+ run
22
+ end
23
+
24
+ def rollback
25
+ @direction = -1
26
+ run
27
+ end
28
+
29
+ def rollout_from( task )
30
+ @position = @tasks.index(task)
31
+ rollout
32
+ end
33
+
34
+ def rollback_from( task )
35
+ @position = @tasks.index(task) - 1
36
+ rollback
37
+ end
38
+
39
+ def attempt
40
+ method = {1 => :rollout, -1 => :rollback}[@direction]
41
+ Dir.chdir(@workdir || Dir.pwd) do
42
+ @statuses << [method, *current_task.send(method)]
43
+ end
44
+ return latest_okay?
45
+ end
46
+
47
+ def latest_okay?
48
+ status[1] == 0
49
+ end
50
+
51
+ def errors
52
+ @statuses.select { |status| status[1] != 0 }
53
+ end
54
+
55
+ def status
56
+ @statuses.last
57
+ end
58
+
59
+ def current_task
60
+ @tasks[@position]
61
+ end
62
+
63
+ def state
64
+ { :position => @position,
65
+ :direction => @direction,
66
+ :statuses => @statuses }
67
+ end
68
+
69
+ private
70
+
71
+ def run
72
+ if @direction == 1
73
+ task_list = @tasks[@position..-1]
74
+ elsif @direction == -1
75
+ task_list = @tasks[0..@position + 1].reverse
76
+ end
77
+ task_list.each do |task|
78
+ @position = @tasks.index(task)
79
+ unless attempt
80
+ fail
81
+ break
82
+ end
83
+ end
84
+ end
85
+
86
+ def fail
87
+ case status[0]
88
+ when :rollout
89
+ rollback_from current_task
90
+ raise TransactionError, "Could not rollout task #{current_task}, attempting rollback."
91
+ when :rollback
92
+ raise TransactionError, "Could not rollback task #{current_task}, aborting."
93
+ else
94
+ raise Error, "Unknown direction!"
95
+ end
96
+ end
97
+
98
+ end
99
+
100
+ end
@@ -0,0 +1,61 @@
1
+ require 'stringio'
2
+
3
+ require 'rubygems'
4
+ require 'open4'
5
+
6
+ module Outback
7
+
8
+ class Task
9
+
10
+ attr_reader :name
11
+ attr_accessor :stdout, :stderr
12
+
13
+ def initialize
14
+ @rollout, @rollback = lambda {}
15
+ @stdout = StringIO.new
16
+ @stderr = StringIO.new
17
+ end
18
+
19
+ def rollout( &block )
20
+ if block_given?
21
+ @rollout = block
22
+ else
23
+ run @rollout
24
+ end
25
+ end
26
+
27
+ def rollback( &block )
28
+ if block_given?
29
+ @rollback = block
30
+ else
31
+ run @rollback
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def run( proc )
38
+ proc.call(TaskHelper.new(self))
39
+ [0, stdout.string, stderr.string]
40
+ rescue SystemCallError => err
41
+ [1, stdout.string, stderr.string]
42
+ end
43
+
44
+ end
45
+
46
+
47
+ class ShellTask < Task
48
+
49
+ attr_reader :rollout_command, :rollback_command
50
+
51
+ def initialize(rollout, rollback)
52
+ super()
53
+ @rollout = lambda { |t| t.sys rollout }
54
+ @rollout_command = rollout
55
+ @rollback = lambda { |t| t.sys rollback }
56
+ @rollback_command = rollback
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,21 @@
1
+ module Outback
2
+
3
+ class TaskHelper
4
+
5
+ def initialize( task )
6
+ @task = task
7
+ end
8
+
9
+ def sys( *args )
10
+ pid, i, o, e = Open4.popen4(*args)
11
+ o.each_line { |l| @task.stdout.print "[#{pid}] #{l}" }
12
+ e.each_line { |l| @task.stderr.print "E [#{pid}] #{l}" }
13
+ end
14
+
15
+ def puts( *args )
16
+ @task.stdout.puts *args
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,25 @@
1
+ module Outback
2
+
3
+ module YAML
4
+
5
+ def self.load( yaml_string )
6
+ manager = Manager.new
7
+ tasks = ::YAML.load(yaml_string)
8
+ tasks.each do |task|
9
+ manager.tasks << Outback::ShellTask.new(*task)
10
+ end
11
+ return manager
12
+ end
13
+
14
+ def self.load_file( yaml_file )
15
+ manager = Manager.new
16
+ tasks = ::YAML.load_file(yaml_file)
17
+ tasks.each do |task|
18
+ manager.tasks << Outback::ShellTask.new(*task)
19
+ end
20
+ return manager
21
+ end
22
+
23
+ end
24
+
25
+ end
data/lib/outback.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'outback/manager'
2
+ require 'outback/task_helper'
3
+ require 'outback/task'
4
+ require 'outback/command'
5
+ require 'outback/yaml'
6
+
7
+ module Outback
8
+
9
+ class Error < RuntimeError; end
10
+ class TransactionError < Error; end
11
+
12
+ end
data/outback.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{outback}
3
+ s.version = "0.0.1"
4
+
5
+ s.specification_version = 2 if s.respond_to? :specification_version=
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Nick Stenning"]
9
+ s.date = %q{2008-07-14}
10
+ s.email = ["nick@whiteink.com"]
11
+ s.files = ["lib/outback/command.rb", "lib/outback/manager.rb", "lib/outback/task.rb", "lib/outback/task_helper.rb", "lib/outback/yaml.rb", "lib/outback.rb", "outback.gemspec", "spec/outback/manager_spec.rb", "spec/outback/task_spec.rb", "spec/outback/yaml_spec.rb", "spec/spec_helper.rb"]
12
+ s.has_rdoc = true
13
+ s.homepage = %q{http://github.com/nickstenning/outback}
14
+ s.require_paths = ["lib"]
15
+ s.summary = %q{Like rake, but backwards and forwards.}
16
+ s.description = %{Run pairs of rollout/rollback tasks in a transactional manner.}
17
+
18
+ s.add_dependency("open4", [">= 0.9.0"])
19
+ end
@@ -0,0 +1,125 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'yaml'
3
+
4
+ describe Outback::Manager do
5
+
6
+ before do
7
+ @obm = Outback::Manager.new
8
+ @task1 = mock("task1", :name => "Task 1")
9
+ @task2 = mock("task2", :name => "Task 2")
10
+ end
11
+
12
+ describe "(empty)" do
13
+
14
+ it "should have a list of tasks" do
15
+ @obm.tasks.should be_a_kind_of(Enumerable)
16
+ @obm.should have(0).tasks
17
+ end
18
+
19
+ it "should have a rollout method" do
20
+ @obm.should respond_to(:rollout)
21
+ end
22
+
23
+ it "should have a rollback method" do
24
+ @obm.should respond_to(:rollback)
25
+ end
26
+
27
+ it "should add tasks to its list with standard array operations" do
28
+ @obm.should have(0).tasks
29
+ @obm.tasks << @task1
30
+ @obm.should have(1).tasks
31
+ @obm.tasks << @task2
32
+ @obm.should have(2).tasks
33
+ end
34
+
35
+ end
36
+
37
+ describe "(with a few tasks)" do
38
+
39
+ before do
40
+ @obm.tasks << @task1 << @task2
41
+ end
42
+
43
+ it "should call each task's rollout method on rollout (in order)" do
44
+ @task1.should_receive(:rollout).and_return(0)
45
+ @task2.should_receive(:rollout).and_return(0)
46
+ @obm.rollout
47
+ end
48
+
49
+ it "should call each task's rollback method on rollback (in reverse order)" do
50
+ @task2.should_receive(:rollback).and_return(0)
51
+ @task1.should_receive(:rollback).and_return(0)
52
+ @obm.rollback
53
+ end
54
+
55
+ it "should raise an Outback::Error if a rollout fails" do
56
+ @task1.stub!(:rollback)
57
+ @task1.should_receive(:rollout).and_return(1)
58
+ @task2.should_not_receive(:rollout)
59
+ @task2.should_not_receive(:rollback)
60
+ lambda { @obm.rollout }.should raise_error(Outback::Error)
61
+ end
62
+
63
+ it "should raise an Outback::Error if a rollback fails" do
64
+ @task2.should_receive(:rollback).and_return(1)
65
+ @task1.should_not_receive(:rollback)
66
+ lambda { @obm.rollback }.should raise_error(Outback::Error)
67
+ end
68
+
69
+ it "should store the latest task's run direction (rollout, rollback), return code, stdout and stderr in #status" do
70
+ @task1.should_receive(:rollout).and_return([0, "A message", ""])
71
+ @task2.should_receive(:rollout).and_return([0, "Another message", ""])
72
+ @obm.rollout
73
+ @obm.status.should == [:rollout, 0, "Another message", ""]
74
+ end
75
+
76
+ it "should store the latest task's status even if an earlier task raised an error." do
77
+ @task1.stub!(:rollback).and_return([0, "Getting rolled back", ""])
78
+ @task1.should_receive(:rollout).and_return([1, "", "Error message"])
79
+ @task2.should_not_receive(:rollout)
80
+ begin
81
+ @obm.rollout
82
+ rescue Outback::Error
83
+ @obm.status.should == [:rollback, 0, "Getting rolled back", ""]
84
+ end
85
+ end
86
+
87
+ it "should store any status messages with non-zero return codes in #errors" do
88
+ @task1.should_receive(:rollout).and_return([0, "All okay", ""])
89
+ @task2.should_receive(:rollout).and_return([1, "", "Error message"])
90
+ @task2.should_receive(:rollback).and_return([0, "Getting rolled back", ""])
91
+ @task1.should_receive(:rollback).and_return([3, "", "Error while rolling back"])
92
+ begin
93
+ @obm.rollout
94
+ rescue Outback::Error
95
+ @obm.should have(2).errors
96
+ @obm.errors[0].should == [:rollout, 1, "", "Error message"]
97
+ @obm.errors[1].should == [:rollback, 3, "", "Error while rolling back"]
98
+ end
99
+ end
100
+
101
+ it "should rollback previously rolled-out tasks if a rollout fails" do
102
+ @task1.should_receive(:rollout).ordered.and_return([1, "", "Error message"])
103
+ @task2.should_not_receive(:rollout)
104
+ @task1.should_receive(:rollback).ordered
105
+ @task2.should_not_receive(:rollback)
106
+ begin
107
+ @obm.rollout
108
+ rescue Outback::Error
109
+ end
110
+ end
111
+ end
112
+
113
+ describe "(with #workdir set)" do
114
+
115
+ before do
116
+ @obm.tasks << Outback::ShellTask.new('pwd', 'pwd')
117
+ @obm.workdir = '/tmp'
118
+ end
119
+
120
+ it "should execute all tasks in the workdir" do
121
+ @obm.rollout
122
+ @obm.status[2] == "/tmp"
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,63 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Outback::Task do
4
+
5
+ before do
6
+ @task = Outback::Task.new
7
+ end
8
+
9
+ it "should allow its rollout method to be defined with #rollout" do
10
+ rollout_proc = lambda { |t| t.puts "rollout" }
11
+
12
+ lambda do
13
+ @task.rollout &rollout_proc
14
+ end.should_not raise_error
15
+
16
+ @task.rollout.should == [0, "rollout\n", ""]
17
+ end
18
+
19
+ it "should allow its rollback method to be defined with #rollback" do
20
+ rollback_proc = lambda { |t| t.puts "rollback" }
21
+
22
+ lambda do
23
+ @task.rollback &rollback_proc
24
+ end.should_not raise_error
25
+
26
+ @task.rollback.should == [0, "rollback\n", ""]
27
+ end
28
+
29
+ it "should have a rollout method" do
30
+ @task.should respond_to(:rollout)
31
+ end
32
+
33
+ it "should have a rollback method" do
34
+ @task.should respond_to(:rollback)
35
+ end
36
+
37
+ it "should return an exit code and the command output when rollout or rollback is run" do
38
+ returns = @task.rollout
39
+ returns[0].should be_a_kind_of(Numeric)
40
+ returns[1].should be_a_kind_of(String)
41
+ returns[2].should be_a_kind_of(String)
42
+ end
43
+
44
+ it "should provide a 'sys' method to rollout and rollback blocks to allow system calls" do
45
+ @task.rollout do |t|
46
+ t.sys 'echo', 'system call'
47
+ end
48
+ ret = []
49
+ lambda { ret = @task.rollout }.should_not raise_error
50
+ ret[0].should == 0
51
+ ret[1].should match(/\[\d+\] system call\n/)
52
+ ret[2].should be_empty
53
+ end
54
+
55
+ it "should catch failed system commands and provide an appropriate exit code" do
56
+ @task.rollout do |t|
57
+ t.sys "nonexistent"
58
+ end
59
+ ret = []
60
+ lambda { ret = @task.rollout }.should_not raise_error
61
+ ret[0].should == 1
62
+ end
63
+ end
@@ -0,0 +1,47 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'yaml'
3
+
4
+ describe Outback::YAML do
5
+
6
+ before do
7
+ @yaml =<<-EOM.gsub(/^\s{4}/, '')
8
+ -
9
+ - touch y
10
+ - rm y
11
+ -
12
+ - touch x
13
+ - rm x
14
+ EOM
15
+ @file = '/tmp/outback_yaml_spec_tmp'
16
+ File.open(@file, 'w') { |f| f.puts @yaml }
17
+ end
18
+
19
+ after do
20
+ File.delete(@file)
21
+ end
22
+
23
+ describe do
24
+ it "should return an Outback::Manager object" do
25
+ Outback::YAML.load(@yaml).should be_a_kind_of(Outback::Manager)
26
+ Outback::YAML.load_file(@file).should be_a_kind_of(Outback::Manager)
27
+ end
28
+ it "should return an Outback::Manager object with as many tasks as the YAML passed into it" do
29
+ Outback::YAML.load(@yaml).should have(2).tasks
30
+ Outback::YAML.load_file(@file).should have(2).tasks
31
+ end
32
+ it "should add instances of Outback::ShellTask to the Outback::Manager's task array" do
33
+ manager = Outback::YAML.load(@yaml)
34
+ manager.tasks.first.should be_a_kind_of(Outback::ShellTask)
35
+ end
36
+ it "should set the rollout_command to the first element of the YAML array" do
37
+ manager = Outback::YAML.load(@yaml)
38
+ manager.tasks[0].rollout_command.should == "touch y"
39
+ manager.tasks[1].rollout_command.should == "touch x"
40
+ end
41
+ it "should set the rollback_command to the second element of the YAML array" do
42
+ manager = Outback::YAML.load(@yaml)
43
+ manager.tasks[0].rollback_command.should == "rm y"
44
+ manager.tasks[1].rollback_command.should == "rm x"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,4 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'outback'
4
+ require 'logger'
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nickstenning-outback
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nick Stenning
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-07-14 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: open4
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.9.0
23
+ version:
24
+ description: Run pairs of rollout/rollback tasks in a transactional manner.
25
+ email:
26
+ - nick@whiteink.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/outback/command.rb
35
+ - lib/outback/manager.rb
36
+ - lib/outback/task.rb
37
+ - lib/outback/task_helper.rb
38
+ - lib/outback/yaml.rb
39
+ - lib/outback.rb
40
+ - outback.gemspec
41
+ - spec/outback/manager_spec.rb
42
+ - spec/outback/task_spec.rb
43
+ - spec/outback/yaml_spec.rb
44
+ - spec/spec_helper.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/nickstenning/outback
47
+ post_install_message:
48
+ rdoc_options: []
49
+
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.2.0
68
+ signing_key:
69
+ specification_version: 2
70
+ summary: Like rake, but backwards and forwards.
71
+ test_files: []
72
+