public
Description: A source code readers guide to the cruisecontrol.rb project
Homepage: http://projects.gandrew.com/cruise_notes/
Clone URL: git://github.com/gingerhendrix/cruise_notes.git
gingerhendrix (author)
Sun Oct 26 16:22:43 -0700 2008
commit  903035794505eb3040aa8507b0d68cb9a9909f41
tree    f90c593e3d28a895d878761bf0f8e087b76e2bc5
parent  fd01509c9d2e857f213d5d8bf37d7d800f23eac0
cruise_notes / notes.html.erb
100644 295 lines (221 sloc) 14.964 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
<html>
<head>
  <link type="text/css" rel="stylesheet" href="js/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css"></link>
  <link type="text/css" rel="stylesheet" href="css/notes.css"></link>
  <link type="text/css" rel="stylesheet" href="css/class_diagram.css"></link>
  <link type="text/css" rel="stylesheet" href="css/toc.css"></link>
  <script src="js/notes.js"></script>
  
  <script src="http://gandrew.com/mint/?js"></script>
  
  <title>Cruise Notes - gandrew.com</title>
  
</head>
<body>
 
<div id="toc">
 
</div>
 
<h1>Cruise Notes - A readers guide to Cruise Control</h1>
<div class="subheader">
  <span class="date">26<sup>th</sup> October 2008</span>
  <span class="status">Final Draft</span>
  
</div>
<p>From <a href="http://cruisecontrolrb.thoughtworks.com/">http://cruisecontrolrb.thoughtworks.com</a>:</p>
<blockquote>CruiseControl.rb is a continuous integration tool. Its basic purpose in life is to alert members of a software project when one of them checks something into source control that breaks the build.
</blockquote>
 
<p class="content">Cruise control consists of a set of scripts for adding new projects and building existing ones, and a Dashboard application to display the results of builds</p>
<p class="content">This guide describes cruise control 1.3.0
<h2>Dashboard Rails App</h2>
 
<img src="images/dashboard-screenshot.png" ></img>
 
<p class="content">The Dashboard is a very simple Rails application, it has skinny controllers and well-factored views, and makes use of rails ajax capabilties,
rails caching, builder and erb.</p>
<p class="content">As well as controllers for Projects and Builds, cruise control also serves its own documentation from the webapp.</p>
 
<h3>DocumentationController</h3>
 
<p class="content">The DocumentationController is a simple static controller it performs basic routing and caching</p>
 
<%= code_snippet :file => 'documentation_routes', :caption => "Documentation Routes : routes.rb" %>
<%= code_snippet :file => 'documentation_controller', :caption => "Documentation Controller : documentation_controller.rb" %>
 
 
<h3>ProjectsController</h3>
 
<table class="controller_routes">
<thead>
<tr>
  <td>Action</td><td>Path</td><td>Model action</td><td>Views</td>
</tr>
</thead>
<tbody>
<tr>
  <td>index</td><td>/projects/</td><td>Projects.load_all</td><td>
                                                              <ul>
                                                                <li>html</li>
                                                                <li>js</li>
                                                                <li>rss</li>
                                                                <li>cctray</li>
                                                              </ul>
                                                             </td>
</tr>
<tr>
  <td>show</td><td>/projects/:id</td><td>Projects.find(:id)</td><td><ul>
                                                                       <li>html => redirect /builds/:project_id</li>
                                                                       <li>rss => projects/show_rss</li>
                                                                    </ul>
                                                                </td>
</tr>
<tr>
  <td>code</td><td>/projects/:id/code/:path</td><td>Projects.find(:id)</td><td><ul><li> html => redirect /builds/:project_id</li></ul></td>
</tr>
<tr>
  <td>build</td><td>/projects/:id/build</td><td>@project.request_build</td><td><ul><li>js => redirect /builds/:project_id</li></ul></td>
</tr>
</tbody>
</table>
 
 
<%= code_snippet :file => 'projects_routes', :caption => "Projects Routes : routes.rb" %>
 
<h3>BuildsController</h3>
 
<table class="controller_routes">
<thead>
<tr>
  <td>Action</td><td>Path</td><td>Model action</td><td>Views</td>
</tr>
</thead>
<tbody>
<tr>
  <td>show</td><td>/builds/:project</td><td>@project.last_build</td><td>
                                                              <ul>
                                                                <li>html</li>
                                                              </ul>
                                                             </td>
</tr>
<tr>
  <td>show</td><td>/builds/:project/:build</td><td>@project.find_build(:build)</td><td><ul>
                                                                                       <li>html</li>
                                                                                      </ul>
                                                                                  </td>
</tr>
<tr>
  <td>drop_down</td><td>/builds/older/:project</td><td>Projects.find(:id)</td><td><ul><li> html => drop_down.rhtml </li></ul></td>
</tr>
<tr>
  <td>artifact</td><td>builds/:project/:build/*path</td><td>@project.find_build(:build)</td><td><ul><li>file => [build artifact]</li></ul></td>
</tr>
</tbody>
</table>
 
<%= code_snippet :file => 'builds_routes', :caption => "Builds Routes : routes.rb" %>
 
<p class="content">
The artifact action breaks the skinny controller rule, it may well be better to refactor this into a method on the builds model eg. Build::get_artifact(path)
however this is complicated by the desire to handle directories differently.</p>
 
<%= code_snippet :file => 'builds_controller_artifact', :caption => "Fat artifact method : builds_controller.rb" %>
 
<h3>Views</h3>
 
<p class="content">The views are very straightforward idomatic rails views.
  <ul>
    <li>They use templates, and partials and helpers effectively to modularise the views and limit the amount of embedded ruby</li>
    <li>RJS is used to provide asynchronous actions, which creates a better user experience</li>
    <li>Builder is used to generate tidy and simple xml views</li>
  </ul>
</p>
 
<%= code_snippet :file => 'default_template', :caption => "Partials used for modularisation : templates/default.rhtml" %>
<%= code_snippet :file => 'project_view', :caption => "Helpers used for cleaner erb : projects/_project.rhtml" %>
<%= code_snippet :file => 'project_form_remote_tag', :caption => "Asynchronous requests via form remote tag : projects/_project.rhtml" %>
<%= code_snippet :file => 'project_index_js', :caption => "RJS Response : projects/index_js.rjs" %>
<%= code_snippet :file => 'project_index_cctray', :caption => "Builder used for xml output : projects/index_cctray.rxml" %>
 
<h2>Scripts and Daemon</h2>
 
 
<p class="content">The cruise command is a simple shell script that delegates to one of the <code>server</code>, <code>add_project</code> or <code>builder</code> scripts depending on the command line arguments.
</p>
 
<p>
  <ul>
    <li><code>server</code> is just a simple wrapper around the rails builtin server script. It starts the dashboard rails app with mongrel.</li>
    <li><code>add project</code> creates a new project with options passed on the command line</li>
    <li><code>builder</code> builder runs a projects scheduler loop. There can be many builders running at once.</li>
  </ul>
</p>
 
<%= code_snippet :file => 'cruise', :caption => "cruise executable script : cruise" %>
<%= code_snippet :file => 'add_project', :caption => "add_project script : scripts/add_project" %>
<%= code_snippet :file => 'builder_script', :caption => "builder script : scripts/builder" %>
 
 
<h2>The Model</h2>
 
<p class="content">The meat and gravy of the application is in the model (as it should be). There are three domain objects represented in the model -
<code>Project</code>, <code>Build</code> and <code>SCM</code> (source control manager). The Project has many responsibilities including configuration, persistence and the build system.
The project class delegates many of these reponsibilities to helper classes including <code>BuilderStarter</code>, <code>BuilderStatus</code>, <code>PollingScheduler</code>, <code>ProjectConfigTracker</code>,
and <code>BuildSerializer</code>. Even with this refactoring the Project class is still over 500+ lines, further refactoring using delegates or mixins would
definitely help the code readability.</p>
 
<img id="projects_class_diagram" src="images/projects_class_diagram.png" width="800" height="400"></img>
 
<h3>Associations</h3>
 
 
<p class="content">Cruise control doesn't use Activerecord for persistence, instead it uses the filesystem. A project is stored as a directory in the
<code>[cruise data]/projects</code> directory, the project has a configuration file, a working copy of the source code, and a directory for each of the builds.</p>
<p>There are finder methods for Projects and Builds- these query the filesystem in order to locate the desired resource.</p>
 
<%= class_diagram :class => 'projects', :tag => 'associations' %>
<%= class_diagram :class => 'project', :tag => 'builds' %>
<br style="clear: both"/>
<%= code_snippet :file => 'project_builds', :caption => "Filesystem used to store associations : project.rb" %>
 
<h3>Initialization</h3>
 
<%= class_diagram :class => 'projects', :tag => 'init' %>
<%= class_diagram :class => 'project', :tag => 'init' %>
<br style="clear: both"/>
 
<p class="content">On initialization the project loads the central configuration file and the local configuration file. The configuration files are written
 in ruby and are expected to use <code>Project.configure</code> to set configuration attributes on the project. There is a clever trick here in setting a
 class variable to the current project on initialisation, this provides a means of accessing the current project in the configuration file</p>
 
<%= code_snippet :file => 'project_init', :caption => "Initialisation method stores project reference : project.rb" %>
<%= code_snippet :file => 'project_configure', :caption => "Example project configuration file : cruise_config.rb" %>
 
 
<h3>The Build System</h3>
 
<%= class_diagram :class => 'project', :tag => 'builder' %>
<%= class_diagram :class => 'build', :tag => 'builder' %>
<%= class_diagram :class => 'builder_starter' %>
<%= class_diagram :class => 'polling_scheduler', :tag => 'builder' %>
<%= class_diagram :class => 'build_serializer' %>
<br style="clear: both"/>
 
<p class="content">
The controller can start a build by calling <code>request_build</code> on a project. This method delegates starting the
 build to the <code>BuilderStarter</code> class. The build is started asynchronously by creating a new process which executes
 the builder script. The build request is stored in a flag file so that the build process is aware of the request.
</p>
 
<%= code_snippet :file => 'builder_starter', :caption => "Asynchronous build request : build_starter.rb" %>
 
<p>The builder script runs the project's scheduler loop, which calls <code>build_if_necessary</code> or <code>build_if_requested</code> to start a build.
The build is started with the <code>build</code> method, which in turn delegates to <code>build_without_serialization</code> (serialization is performed by a
<code>BuildSerializer</code> if required). </p>
 
<%= code_snippet :file => 'polling_scheduler', :caption => "Scheduler Loop : polling_scheduler.rb" %>
 
<p>A <code>Build</code> object is created which controls the build and provides access to and persistence for results. The <code>run</code> method creates
the appropriate environment and runs the project build command or rake task.</p>
 
<%= code_snippet :file => 'build', :caption => "Build's run method : build.rb" %>
 
 
<h3>Triggers</h3>
 
<%= class_diagram :class => 'project', :tag => 'trigger' %>
<%= class_diagram :class => 'change_in_source_control_trigger', :tag => 'trigger' %>
<%= class_diagram :class => 'successful_build_trigger', :tag => 'trigger' %>
<br style="clear: both"/>
 
 
<p><code>build_necessary?</code> delegates to the projects triggers. Triggers are simple objects which respond to <code>build_necessary?</code>.
Cruise control ships with two triggers <code>ChangeInSourceControlTrigger</code> and <code>SuccessfulBuildTrigger</code>.</p>
 
 
<%= code_snippet :file => 'project_build_necessary', :caption => "build_necessary? method : project.rb" %>
<%= code_snippet :file => 'change_in_source_control_trigger', :caption => "ChangeInSourceControlTrigger : change_in_source_control_trigger.rb" %>
 
<h3>Plugins</h3>
 
<%= class_diagram :class => 'project', :tag => 'plugins' %>
<%= class_diagram :class => 'build_reaper', :tag => 'plugins' %>
<%= class_diagram :class => 'email_notifier', :tag => 'plugins' %>
<%= class_diagram :class => 'builder_status', :tag => 'plugins' %>
<%= class_diagram :class => 'project_logger', :tag => 'plugins' %>
<br style="clear: both"/>
 
<p class="content">Each <code>project</code> has a set of <code>plugins</code> which are notified of events in the build sequence. A plugin can respond to
these events by defining a method of the same name as the event. The set of possible events is dynamic and events are despatched from several places
 including the <code>project</code> build methods, and the <code>triggers</code>. Some of the events include
</p>
<ul>
  <li><code>configuration_modified</code></li>
  <li><code>build_initiated</code></li>
  <li><code>build_started</code></li>
  <li><code>build_finished</code></li>
  <li><code>build_broken</code></li>
  <li><code>build_fixed</code></li>
  <li><code>build_requested</code></li>
  <li><code>build_loop_failed</code></li>
  <li><code>sleeping</code></li>
</ul>
 
 
<%= code_snippet :file => 'project_notify', :caption => "notify method : project.rb" %>
<%= code_snippet :file => 'email_notifier', :caption => "E-mail notifier plugin : email_notifier.rb" %>
 
<h3>Source Control</h3>
 
<%= class_diagram :class => 'project', :tag => 'scm' %>
<%= class_diagram :class => 'subversion', :tag => 'scm' %>
<br style="clear: both"/>
<p class="content">The build system interacts with the source control using three methods
<code>latest_revision(project)</code>, <code>revisions_since(project, revision_number)</code> and <code>update(project, revision = nil)</code>.
The add project script also requires a <code>checkout(revision = nil)</code> method.</p>
<p>Cruise Control currently ships with only a Subversion source control manager, but git and mercurial support are in the works and are available
from the projects git repository.</p>
 
<%= code_snippet :file => 'project_update', :caption => "update_to_latest_revision method : project.rb" %>
<%= code_snippet :file => 'subversion', :caption => "Subversion client : subversion.rb" %>
 
 
 
<script class="javascript" src="js/dp.SyntaxHighlighter/Scripts/shCore.js"></script>
<script class="javascript" src="js/dp.SyntaxHighlighter/Scripts/shBrushRuby.js"></script>
<script class="javascript">
dp.SyntaxHighlighter.HighlightAll('code');
</script>
 
</body>
</html>