octopusci 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README.markdown +132 -0
  2. data/bin/octopusci-reset-redis +26 -0
  3. data/bin/octopusci-skel +2 -7
  4. data/bin/octopusci-tentacles +2 -2
  5. data/config.ru +1 -1
  6. data/lib/octopusci.rb +3 -7
  7. data/lib/octopusci/config.rb +63 -49
  8. data/lib/octopusci/errors.rb +2 -0
  9. data/lib/octopusci/helpers.rb +16 -15
  10. data/lib/octopusci/io.rb +70 -0
  11. data/lib/octopusci/job.rb +145 -34
  12. data/lib/octopusci/job_store.rb +67 -0
  13. data/lib/octopusci/notifier.rb +7 -17
  14. data/lib/octopusci/notifier/job_complete.html.erb +76 -3
  15. data/lib/octopusci/queue.rb +14 -10
  16. data/lib/octopusci/server.rb +17 -20
  17. data/lib/octopusci/server/views/index.erb +3 -4
  18. data/lib/octopusci/server/views/job.erb +3 -3
  19. data/lib/octopusci/server/views/job_summary.erb +18 -18
  20. data/lib/octopusci/server/views/layout.erb +6 -5
  21. data/lib/octopusci/stage_locker.rb +11 -7
  22. data/lib/octopusci/version.rb +1 -1
  23. data/lib/octopusci/worker_launcher.rb +1 -1
  24. data/spec/lib/octopusci/config_spec.rb +195 -0
  25. data/spec/lib/octopusci/io_spec.rb +64 -0
  26. data/spec/lib/octopusci/job_spec.rb +122 -0
  27. data/spec/lib/octopusci/job_store_spec.rb +155 -0
  28. data/spec/lib/octopusci/notifier_spec.rb +0 -15
  29. data/spec/lib/octopusci/queue_spec.rb +122 -0
  30. data/spec/lib/octopusci/server_spec.rb +92 -1
  31. data/spec/lib/octopusci/stage_locker_spec.rb +94 -0
  32. data/spec/spec_helper.rb +8 -0
  33. metadata +39 -58
  34. data/README +0 -63
  35. data/bin/octopusci-db-migrate +0 -10
  36. data/db/migrate/0001_init.rb +0 -29
  37. data/db/migrate/0002_add_status_job.rb +0 -19
  38. data/lib/octopusci/notifier/job_complete.text.erb +0 -5
  39. data/lib/octopusci/schema.rb +0 -140
@@ -1,8 +1,7 @@
1
1
  <script type="text/javascript" charset="utf-8">
2
2
  $(document).ready(function() {
3
3
  $('#jobs').delegate("a.show_hide_job_output", 'click', function() {
4
- $('.job_output').not(this).hide();
5
- var job_cont = $(this).closest('.job_summary').next('.job_content')
4
+ var job_cont = $(this).closest('.job_summary').next('.job_content');
6
5
  var job_out = job_cont.find('.job_output').first();
7
6
  job_out.toggle();
8
7
  return false;
@@ -20,12 +19,12 @@
20
19
  </script>
21
20
  <div id="jobs">
22
21
  <% @jobs.each do |j| %>
23
- <div id="job_<%= j.id%>" class="job status_<%= j.status %>" data-status-url="/jobs/<%= j.id %>/status" data-output-url="/jobs/<%= j.id %>/output" data-summary-url="/jobs/<%= j.id %>/ajax_summary">
22
+ <div id="job_<%= j['id']%>" class="job status_<%= j['status'] %>" data-status-url="/jobs/<%= j['id'] %>/status" data-output-url="/jobs/<%= j['id'] %>/output" data-summary-url="/jobs/<%= j['id'] %>/ajax_summary">
24
23
  <div class="job_summary">
25
24
  <%= erb :job_summary, :locals => { :j => j } %>
26
25
  </div>
27
26
  <div class="job_content">
28
- <div class="job_output"><pre><code><%= j.output %></code></pre></div>
27
+ <div class="job_output"><pre><code><%= Octopusci::IO.new(j).read_all_out %></code></pre></div>
29
28
  </div>
30
29
  </div>
31
30
  <% end %>
@@ -11,16 +11,16 @@
11
11
  }, 5000);
12
12
  });
13
13
  </script>
14
- <div id="job_<%= @job.id%>" class="job status_<%= @job.status %>" data-status-url="/jobs/<%= @job.id %>/status" data-output-url="/jobs/<%= @job.id %>/output" data-silent-output-url="/jobs/<%= @job.id %>/silent_output" data-summary-url="/jobs/<%= @job.id %>/ajax_summary">
14
+ <div id="job_<%= @job['id']%>" class="job status_<%= @job['status'] %>" data-status-url="/jobs/<%= @job['id'] %>/status" data-output-url="/jobs/<%= @job['id'] %>/output" data-silent-output-url="/jobs/<%= @job['id'] %>/silent_output" data-summary-url="/jobs/<%= @job['id'] %>/ajax_summary">
15
15
  <div class="job_summary">
16
16
  <%= erb :job_summary, :locals => { :j => @job } %>
17
17
  </div>
18
18
  <div class="job_content">
19
19
  Output:
20
- <div class="output job_output" style="display: block;"><pre><code><%= @job.output %></code></pre></div>
20
+ <div class="output job_output" style="display: block;"><pre><code><%= Octopusci::IO.new(@job).read_all_out %></code></pre></div>
21
21
  </div>
22
22
  <div class="job_content">
23
23
  Log:
24
- <div class="silent_output job_output" style="display: block;"><pre><code><%= @job.silent_output %></code></pre></div>
24
+ <div class="silent_output job_output" style="display: block;"><pre><code><%= Octopusci::IO.new(@job).read_all_log %></code></pre></div>
25
25
  </div>
26
26
  </div>
@@ -1,13 +1,13 @@
1
1
  <div class="job_header">
2
2
  <div class="job_title">
3
- <span class="status <%= j.status %>"></span>
3
+ <span class="status <%= j['status'] %>"></span>
4
4
  <div style="float: left;">
5
- <a href="<%= j.payload['repository']['url'] %>"><%= j.repo_name %></a> / <a href="/<%= j.repo_name %>/<%= j.branch_name %>/jobs"><%= j.branch_name %></a>
5
+ <a href="<%= j['payload']['repository']['url'] %>"><%= j['repo_name'] %></a> / <a href="/<%= j['repo_name'] %>/<%= j['branch_name'] %>/jobs"><%= j['branch_name'] %></a>
6
6
  </div>
7
7
  <div style="clear: both;"></div>
8
8
  </div>
9
9
  <div class="created_on">
10
- <%= j.created_at.strftime('%A, %B %d, %Y AT %I:%M%p') %>
10
+ <%= j['created_at'].strftime('%A, %B %d, %Y AT %I:%M%p') unless j['created_at'].nil? %>
11
11
  </div>
12
12
  <div style="clear: both;"></div>
13
13
  </div>
@@ -15,11 +15,11 @@
15
15
  <table>
16
16
  <tr>
17
17
  <td style="text-align: right;">Job:</td>
18
- <td style="width: 200px;"><a href="/jobs/<%= j.id %>"><%= j.id %></a></td>
18
+ <td style="width: 200px;"><a href="/jobs/<%= j['id'] %>"><%= j['id'] %></a></td>
19
19
  <td style="text-align: right;">Before Commit:</td>
20
20
  <td>
21
- <% if !j.payload['created'] %>
22
- <%= j.payload['before'] %>
21
+ <% if !j['payload']['created'] %>
22
+ <%= j['payload']['before'] %>
23
23
  <% else %>
24
24
  No before commit, remote branch was created with this push.
25
25
  <% end %>
@@ -28,49 +28,49 @@
28
28
  <tr>
29
29
  <td style="text-align: right;">Finished:</td>
30
30
  <td>
31
- <% if j.ended_at %>
32
- <%= j.ended_at.ago_in_words %>
31
+ <% if j['ended_at'] %>
32
+ <%= j['ended_at'].ago_in_words %>
33
33
  <% else %>
34
34
  -
35
35
  <% end %>
36
36
  </td>
37
37
  <td style="text-align: right;">After Commit:</td>
38
38
  <td>
39
- <%= j.payload['after'] %>
39
+ <%= j['payload']['after'] %>
40
40
  </td>
41
41
  </tr>
42
42
  <tr>
43
43
  <td style="text-align: right;">Duration:</td>
44
44
  <td>
45
- <% if j.ended_at && j.started_at %>
46
- <%= ((j.ended_at - j.started_at)/60).to_i %> minutes
45
+ <% if j['ended_at'] && j['started_at'] %>
46
+ <%= ((j['ended_at'] - j['started_at'])/60).to_i %> minutes
47
47
  <% else %>
48
48
  -
49
49
  <% end %>
50
50
  </td>
51
51
  <td style="text-align: right;">Compare:</td>
52
- <td><a href="<%= j.compare %>"><%= j.compare %></a></td>
52
+ <td><a href="<%= j['compare'] %>"><%= j['compare'] %></a></td>
53
53
  </tr>
54
54
  <tr>
55
55
  <td style="text-align: right;">Stage:</td>
56
- <td><%= j.stage %></td>
56
+ <td><%= j['stage'] %></td>
57
57
  <td style="text-align: right;">Pusher:</td>
58
58
  <td>
59
- <% if j.payload['pusher']['email'] %>
60
- <a href="mailto:<%= j.payload['pusher']['name'] %>"><%= j.payload['pusher']['name'] %></a>
59
+ <% if j['payload']['pusher']['email'] %>
60
+ <a href="mailto:<%= j['payload']['pusher']['name'] %>"><%= j['payload']['pusher']['name'] %></a>
61
61
  <% else %>
62
- <%= j.payload['pusher']['name'] %>
62
+ <%= j['payload']['pusher']['name'] %>
63
63
  <% end %>
64
64
  </td>
65
65
  </tr>
66
66
  <tr>
67
67
  <td style="text-align: right;">Output:</td>
68
68
  <td>
69
- <a class="show_hide_job_output" style="display: block; float: left;" href="#">toggle</a><div class="output_busy" style="float: left; margin-left: 5px; margin-top: 2px; <% if j.status == 'running' %>display: block;<% else %>display: none;<% end %>"><img src="/images/ajax-loader-1.gif" style="height: 12px;"/></div><div style="clear: both;"></div>
69
+ <a class="show_hide_job_output" style="display: block; float: left;" href="#">toggle</a><div class="output_busy" style="float: left; margin-left: 5px; margin-top: 2px; <% if j['status'] == 'running' %>display: block;<% else %>display: none;<% end %>"><img src="/images/ajax-loader-1.gif" style="height: 12px;"/></div><div style="clear: both;"></div>
70
70
  </td>
71
71
  <td style="text-align: right;">Log:</td>
72
72
  <td>
73
- <a target="_blank" href="/jobs/<%= j.id %>/silent_output">click here</a>
73
+ <a target="_blank" href="/jobs/<%= j['id'] %>/silent_output">click here</a>
74
74
  </td>
75
75
  </tr>
76
76
  </table>
@@ -103,25 +103,26 @@
103
103
  height: 26px;
104
104
  display: block;
105
105
  margin: 0 0 0 0;
106
+ background-position: 0px -74px; /* pending... in the case that no status specific class is provided but this is, the default case */
106
107
  }
107
108
 
108
- .successful {
109
+ .status.successful {
109
110
  background-position: 0px 0px;
110
111
  }
111
112
 
112
- .failed {
113
+ .status.failed {
113
114
  background-position: 0px -26px;
114
115
  }
115
116
 
116
- .running {
117
+ .status.running {
117
118
  background-position: 0px -52px;
118
119
  }
119
120
 
120
- .pending {
121
+ .status.pending {
121
122
  background-position: 0px -74px;
122
123
  }
123
124
 
124
- .error {
125
+ .status.error {
125
126
  background-position: 0px -26px;
126
127
  }
127
128
  </style>
@@ -8,28 +8,32 @@ module Octopusci
8
8
  end
9
9
 
10
10
  def self.clear
11
- Resque.redis.del('octopusci:stagelocker')
11
+ self.redis.del('octopusci:stagelocker')
12
12
  end
13
13
 
14
14
  def self.pop
15
- Resque.redis.lpop('octopusci:stagelocker')
15
+ self.redis.lpop('octopusci:stagelocker')
16
16
  end
17
17
 
18
18
  def self.rem(v)
19
- Resque.redis.lrem('octopusci:stagelocker', 1, v)
19
+ self.redis.lrem('octopusci:stagelocker', 1, v)
20
20
  end
21
21
 
22
22
  def self.push(v)
23
- Resque.redis.rpush('octopusci:stagelocker', v)
23
+ self.redis.rpush('octopusci:stagelocker', v)
24
24
  end
25
25
 
26
26
  def self.stages
27
- Octopusci::CONFIG['stages']
27
+ Octopusci::Config['stages']
28
28
  end
29
29
 
30
30
  def self.pool
31
- len = Resque.size('octopusci:stagelocker')
32
- Resque.peek('octopusci:stagelocker', 0, len)
31
+ len = self.redis.size('octopusci:stagelocker')
32
+ self.redis.peek('octopusci:stagelocker', 0, len)
33
+ end
34
+
35
+ def self.redis
36
+ Resque.redis
33
37
  end
34
38
  end
35
39
  end
@@ -1,3 +1,3 @@
1
1
  module Octopusci
2
- Version = VERSION = '0.2.3'
2
+ Version = VERSION = '0.3.0'
3
3
  end
@@ -27,7 +27,7 @@ module Octopusci
27
27
 
28
28
  worker_pids = []
29
29
 
30
- Octopusci::CONFIG['stages'].size.times do
30
+ Octopusci::Config['stages'].size.times do
31
31
  cur_pid = Process.fork do
32
32
  queues = ['octopusci:commit']
33
33
  worker = Resque::Worker.new(*queues)
@@ -0,0 +1,195 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octopusci::ConfigStore do
4
+
5
+ before(:each) do
6
+ @config_store = Octopusci::ConfigStore.new
7
+ end
8
+
9
+ describe "#initialize" do
10
+ it "should create an instance of Octopusci::ConfigStore class with an empty options set" do
11
+ @config_store.options.should == {}
12
+ end
13
+ end
14
+
15
+ describe "#options" do
16
+ it "should return a hash of the options" do
17
+ @config_store.options.class.should == Hash
18
+ end
19
+ end
20
+
21
+ describe "#load" do
22
+ it "should load a yml file into the options set additively" do
23
+ tmp_file_path = "/tmp/octopusci_test_config.yml"
24
+ File.open(tmp_file_path, "w") do |f|
25
+ f << "jack:\n"
26
+ f << " jobs_path: \"/etc/octopusci/jobs\"\n"
27
+ f << " workspace_base_path: \"/Users/adeponte/.octopusci\"\n"
28
+ end
29
+
30
+ @config_store.load(tmp_file_path)
31
+
32
+ @config_store.options.should == { 'jack' => { 'jobs_path' => "/etc/octopusci/jobs", "workspace_base_path" => "/Users/adeponte/.octopusci" } }
33
+
34
+ tmp_file_path = "/tmp/octopusci_test_config.yml"
35
+ File.open(tmp_file_path, "w") do |f|
36
+ f << "joe:\n"
37
+ f << " hello: \"world\"\n"
38
+ end
39
+
40
+ @config_store.load(tmp_file_path)
41
+
42
+ @config_store.options.should == { 'jack' => { 'jobs_path' => "/etc/octopusci/jobs", "workspace_base_path" => "/Users/adeponte/.octopusci" }, 'joe' => { 'hello' => 'world' } }
43
+ end
44
+
45
+ it "should load a provided block into the options set additively" do
46
+ @config_store.load() do |c|
47
+ c['jackie'] = 'fool'
48
+ end
49
+
50
+ @config_store.options.should == { 'jackie' => 'fool' }
51
+
52
+ @config_store.load() do |c|
53
+ c['brown'] = 'ace'
54
+ end
55
+
56
+ @config_store.options.should == { 'jackie' => 'fool', 'brown' => 'ace' }
57
+ end
58
+
59
+ it "should load a yml file and a provided block into the options set additively" do
60
+ tmp_file_path = "/tmp/octopusci_test_config.yml"
61
+ File.open(tmp_file_path, "w") do |f|
62
+ f << "alice:\n"
63
+ f << " hi: \"there\"\n"
64
+ end
65
+
66
+ @config_store.load(tmp_file_path) do |c|
67
+ c['bob'] = 'hey alice'
68
+ end
69
+
70
+ @config_store.options.should == { 'alice' => { 'hi' => 'there' }, 'bob' => 'hey alice' }
71
+
72
+ tmp_file_path = "/tmp/octopusci_test_config.yml"
73
+ File.open(tmp_file_path, "w") do |f|
74
+ f << "cindy:\n"
75
+ f << " good: \"bye\"\n"
76
+ end
77
+
78
+ @config_store.load(tmp_file_path) do |c|
79
+ c['rachel'] = 'cya'
80
+ end
81
+
82
+ @config_store.options.should == { 'alice' => { 'hi' => 'there' }, 'bob' => 'hey alice', 'cindy' => { 'good' => 'bye' }, 'rachel' => 'cya' }
83
+ end
84
+ end
85
+
86
+ describe "#reset" do
87
+ it "should reset the options set back to an empty set" do
88
+ @config_store.load() do |c|
89
+ c['kitty'] = 'little'
90
+ end
91
+
92
+ @config_store.options.should == { 'kitty' => 'little' }
93
+
94
+ @config_store.reset()
95
+
96
+ @config_store.options.should == {}
97
+ end
98
+ end
99
+
100
+ describe "#reload" do
101
+ it "should reset the options set to an empty set and then perform a normal additive load" do
102
+ @config_store.load() do |c|
103
+ c['big'] = 'dog'
104
+ end
105
+
106
+ @config_store.options.should == { 'big' => 'dog' }
107
+
108
+ @config_store.reload() do |c|
109
+ c['albino'] = 'zebra'
110
+ end
111
+
112
+ @config_store.options.should == { 'albino' => 'zebra' }
113
+ end
114
+ end
115
+
116
+ describe "hash style getter" do
117
+ it "should return the value of the option with the given key" do
118
+ @config_store.load() do |c|
119
+ c['test_getter'] = 'winning'
120
+ end
121
+
122
+ @config_store['test_getter'].should == 'winning'
123
+ end
124
+
125
+ it "should raise Octopusci::ConfigStore::MissingConfigField if try to get a key that doesn't exist" do
126
+ lambda { @config_store['blowup'] }.should raise_error(Octopusci::ConfigStore::MissingConfigField)
127
+ end
128
+ end
129
+
130
+ describe "hash style setter" do
131
+ it "should set the value of the option with the given key to the given value" do
132
+ @config_store['test_setter'] = 'bi-winning'
133
+ @config_store.options.should == { 'test_setter' => 'bi-winning' }
134
+ end
135
+ end
136
+
137
+ describe "method style getter" do
138
+ it "should return the value of the option with the give key as the method name" do
139
+ @config_store.load() do |c|
140
+ c['pirate'] = 'hords'
141
+ end
142
+
143
+ @config_store.pirate.should == 'hords'
144
+ end
145
+
146
+ it "should raise Octopusci::ConfigStore::MissingConfigField if try to get a key that doesn't exist" do
147
+ lambda { @config_store.hippy }.should raise_error(Octopusci::ConfigStore::MissingConfigField)
148
+ end
149
+ end
150
+
151
+ describe "method style setter" do
152
+ it "should set the value of the option with the given key as the method name to the given value" do
153
+ @config_store.ship = 'it'
154
+
155
+ @config_store.options.should == { 'ship' => 'it' }
156
+ end
157
+ end
158
+
159
+ describe "#has_key?" do
160
+ it "should return a boolean value indicating whether or not there is an option in the option set with the given key" do
161
+ @config_store['foo'] = 1
162
+
163
+ @config_store.has_key?('foo').should be_true
164
+ @config_store.has_key?('bar').should be_false
165
+ end
166
+ end
167
+
168
+ describe "#after_load" do
169
+ it "should set the after load callback when given a block" do
170
+ @config_store.after_load do
171
+ @config_store['after_load_cb_test'] = 'wootwoot'
172
+ end
173
+
174
+ @config_store.options.should == {}
175
+
176
+ @config_store.load() do |c|
177
+ @config_store['feet'] = 'wet'
178
+ end
179
+
180
+ @config_store.options.should == { 'feet' => 'wet', 'after_load_cb_test' => 'wootwoot' }
181
+ end
182
+
183
+ it "should execute the after load callback when NOT given a block and the callback has been previously set" do
184
+ @config_store.after_load do
185
+ @config_store['after_load_cb_test'] = 'wootwoot'
186
+ end
187
+
188
+ @config_store.load() do |c|
189
+ @config_store['feet'] = 'wet'
190
+ end
191
+
192
+ @config_store.options.should == { 'feet' => 'wet', 'after_load_cb_test' => 'wootwoot' }
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octopusci::IO do
4
+ describe "#initialize" do
5
+ it "should construct an instance of Octopusci::IO" do
6
+ job = stub('job')
7
+ Octopusci::IO.new(job).should be_a(Octopusci::IO)
8
+ end
9
+ end
10
+
11
+ describe "#read_all_out" do
12
+ it "should open the output file if it exists" do
13
+ file = stub('file').as_null_object
14
+ File.stub(:exists?).and_return(true)
15
+ File.should_receive(:open).and_return(file)
16
+ Octopusci::IO.new({ 'id' => 23 }).read_all_out
17
+ end
18
+
19
+ it "should return all the file content if the output file exists" do
20
+ file = stub('file', :read => 'file content', :close => 0)
21
+ File.stub(:exists?).and_return(true)
22
+ File.stub(:open).and_return(file)
23
+ Octopusci::IO.new({ 'id' => 23 }).read_all_out.should == 'file content'
24
+ end
25
+
26
+ it "should return an empty string if the file does not exist" do
27
+ File.stub(:exists?).and_return(false)
28
+ Octopusci::IO.new({ 'id' => 23 }).read_all_out.should == ""
29
+ end
30
+ end
31
+
32
+ describe "#read_all_log" do
33
+ it "should open the log file if it exists" do
34
+ file = stub('file').as_null_object
35
+ File.stub(:exists?).and_return(true)
36
+ File.should_receive(:open).and_return(file)
37
+ Octopusci::IO.new({ 'id' => 23 }).read_all_log
38
+ end
39
+
40
+ it "should return all the file content if the log file exists" do
41
+ file = stub('file', :read => 'file content')
42
+ File.stub(:exists?).and_return(true)
43
+ File.stub(:open).and_return(file)
44
+ Octopusci::IO.new({ 'id' => 23 }).read_all_log.should == 'file content'
45
+ end
46
+
47
+ it "should return an empty string if the file does not exist" do
48
+ File.stub(:exists?).and_return(false)
49
+ Octopusci::IO.new({ 'id' => 23 }).read_all_log.should == ""
50
+ end
51
+ end
52
+
53
+ describe "#open_log_for_writing" do
54
+ end
55
+
56
+ describe "#open_out_for_writing" do
57
+ end
58
+
59
+ describe "#puts" do
60
+ end
61
+
62
+ describe "#log" do
63
+ end
64
+ end