nickstenning-outback 0.0.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.
@@ -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
+