etsy-deployinator 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +368 -0
- data/Rakefile +16 -0
- data/bin/deployinator-tailer.rb +78 -0
- data/deployinator.gemspec +31 -0
- data/lib/deployinator.rb +114 -0
- data/lib/deployinator/app.rb +204 -0
- data/lib/deployinator/base.rb +58 -0
- data/lib/deployinator/config.rb +7 -0
- data/lib/deployinator/controller.rb +147 -0
- data/lib/deployinator/helpers.rb +610 -0
- data/lib/deployinator/helpers/deploy.rb +68 -0
- data/lib/deployinator/helpers/dsh.rb +42 -0
- data/lib/deployinator/helpers/git.rb +348 -0
- data/lib/deployinator/helpers/plugin.rb +50 -0
- data/lib/deployinator/helpers/stack-tail.rb +32 -0
- data/lib/deployinator/helpers/version.rb +62 -0
- data/lib/deployinator/helpers/view.rb +67 -0
- data/lib/deployinator/logging.rb +16 -0
- data/lib/deployinator/plugin.rb +7 -0
- data/lib/deployinator/stack-tail.rb +34 -0
- data/lib/deployinator/static/css/diff_style.css +283 -0
- data/lib/deployinator/static/css/highlight.css +235 -0
- data/lib/deployinator/static/css/style.css +1223 -0
- data/lib/deployinator/static/js/flot/jquery.flot.min.js +1 -0
- data/lib/deployinator/static/js/flot/jquery.flot.selection.js +299 -0
- data/lib/deployinator/static/js/jquery-1.8.3.min.js +2 -0
- data/lib/deployinator/static/js/jquery-ui-1.8.24.min.js +5 -0
- data/lib/deployinator/static/js/jquery.timed_bar.js +36 -0
- data/lib/deployinator/tasks/initialize.rake +84 -0
- data/lib/deployinator/tasks/tests.rake +22 -0
- data/lib/deployinator/templates/deploys_status.mustache +42 -0
- data/lib/deployinator/templates/generic_single_push.mustache +64 -0
- data/lib/deployinator/templates/index.mustache +12 -0
- data/lib/deployinator/templates/layout.mustache +604 -0
- data/lib/deployinator/templates/log.mustache +24 -0
- data/lib/deployinator/templates/log_table.mustache +90 -0
- data/lib/deployinator/templates/messageboxes.mustache +29 -0
- data/lib/deployinator/templates/run_logs.mustache +15 -0
- data/lib/deployinator/templates/scroll_control.mustache +8 -0
- data/lib/deployinator/templates/stream.mustache +13 -0
- data/lib/deployinator/version.rb +3 -0
- data/lib/deployinator/views/deploys_status.rb +22 -0
- data/lib/deployinator/views/index.rb +14 -0
- data/lib/deployinator/views/layout.rb +48 -0
- data/lib/deployinator/views/log.rb +12 -0
- data/lib/deployinator/views/log_table.rb +35 -0
- data/lib/deployinator/views/run_logs.rb +32 -0
- data/templates/app.rb.erb +7 -0
- data/templates/config.ru.erb +10 -0
- data/templates/helper.rb.erb +15 -0
- data/templates/stack.rb.erb +17 -0
- data/templates/template.mustache +1 -0
- data/templates/view.rb.erb +7 -0
- data/test/unit/helpers_dsh_test.rb +40 -0
- data/test/unit/helpers_test.rb +77 -0
- data/test/unit/version_test.rb +104 -0
- metadata +245 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
<!-- Depoly Status Page <3 -->
|
2
|
+
|
3
|
+
<script>
|
4
|
+
|
5
|
+
function load_deploys(){
|
6
|
+
$.get( "/deploys", function( data ) {
|
7
|
+
data = JSON.parse(data)
|
8
|
+
var items = [];
|
9
|
+
var html = ''
|
10
|
+
if (data.length===0){
|
11
|
+
data.push({"stack":"alcohol","stage":"consuming"})
|
12
|
+
}
|
13
|
+
for (var i = 0; i < data.length; ++i){
|
14
|
+
var stack = data[i]['stack']
|
15
|
+
var stage = data[i]['stage']
|
16
|
+
html = html +
|
17
|
+
"<div class='stack-box'> \
|
18
|
+
<div class=\"deploys-listing-heading\"><h2>" + stack + ":" + stage + "</h2></div> \
|
19
|
+
<a href=\"javascript:stream_log_websocket_deploys('" + stack + "', '" + stage + "')\">Watch</a> \
|
20
|
+
</div>"
|
21
|
+
}
|
22
|
+
$( "#running-deploys" ).html(html);
|
23
|
+
});
|
24
|
+
}
|
25
|
+
|
26
|
+
setInterval( load_deploys, 2000 );
|
27
|
+
|
28
|
+
</script>
|
29
|
+
|
30
|
+
<aside id="running-deploys" class="pushers">
|
31
|
+
{{#current_deploys}}
|
32
|
+
<div class="stack-box">
|
33
|
+
<div class="deploys-listing-heading"><h2>{{stack}}:{{stage}}</h2></div>
|
34
|
+
<a href="javascript:stream_log_websocket_deploys('{{stack}}', '{{stage}}')">Watch</a>
|
35
|
+
</div>
|
36
|
+
{{/current_deploys}}
|
37
|
+
</aside>
|
38
|
+
<section id="main" class="info">
|
39
|
+
<div id = "shell_deploys"class="log-main">
|
40
|
+
</div>
|
41
|
+
</section>
|
42
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
<!-- Stacks info -->
|
2
|
+
<section id="info-bar" class="clearfix">
|
3
|
+
<ul>
|
4
|
+
<li class="heading">Version: </li>
|
5
|
+
{{#environments}}
|
6
|
+
<li>{{name}} – <span class="{{stack}}_{{name}}_version">{{current_version}}</span> </li>
|
7
|
+
{{/environments}}
|
8
|
+
</ul>
|
9
|
+
</section>
|
10
|
+
|
11
|
+
{{> messageboxes }}
|
12
|
+
|
13
|
+
<aside class="pushers">
|
14
|
+
|
15
|
+
{{#environments}}
|
16
|
+
<div class="stack-box">
|
17
|
+
<h2><span>{{number}}</span>{{title}}</h2>
|
18
|
+
<form action="/deploys" method="post" data-method="{{stack}}_{{method}}">
|
19
|
+
<input type="hidden" name="method" value="{{method}}">
|
20
|
+
<input type="hidden" name="stack" value="{{stack}}">
|
21
|
+
<input type="hidden" name="env" value="{{name}}">
|
22
|
+
|
23
|
+
<p class="stack-status">
|
24
|
+
<a href="/diff/{{stack}}/{{current_build}}/{{next_build}}{{#use_github_diff}}/github{{/use_github_diff}}" target="_blank">
|
25
|
+
<span class="{{stack}}_{{name}}_current_build">{{current_build}}</span>
|
26
|
+
→
|
27
|
+
<span class="{{stack}}_{{name}}_next_build">{{next_build}}</span>
|
28
|
+
</a>
|
29
|
+
</p>
|
30
|
+
<p class="stack-push"><button class="button {{#disabled}} button-disabled" disabled="disabled{{/disabled}}" type="submit">Deploy {{name}}</button></p>
|
31
|
+
</form>
|
32
|
+
{{#not_last}}
|
33
|
+
<span class="arrow"></span>
|
34
|
+
{{/not_last}}
|
35
|
+
</div>
|
36
|
+
{{/environments}}
|
37
|
+
|
38
|
+
</aside>
|
39
|
+
|
40
|
+
<section id="main" class="info">
|
41
|
+
<!-- heading -->
|
42
|
+
<div class="heading clearfix">
|
43
|
+
<h2>Info</h2>
|
44
|
+
<span class="option">
|
45
|
+
{{< scroll_control }}
|
46
|
+
</span>
|
47
|
+
</div>
|
48
|
+
|
49
|
+
<!-- topic -->
|
50
|
+
<div class="push-topic">
|
51
|
+
<div class="title"></div>
|
52
|
+
<span class="log-run">
|
53
|
+
<form action="/run_logs">
|
54
|
+
<button class="button small" type="submit">See run logs</button>
|
55
|
+
</form>
|
56
|
+
</span>
|
57
|
+
</div>
|
58
|
+
|
59
|
+
<!-- log -->
|
60
|
+
<div class="log-main">
|
61
|
+
{{< log }}
|
62
|
+
</div>
|
63
|
+
|
64
|
+
</section>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<!-- Stack Index Page <3 -->
|
2
|
+
|
3
|
+
<section id="info-bar" class="clearfix">
|
4
|
+
<ul><li class="heading">Stack Index</li></ul>
|
5
|
+
</section>
|
6
|
+
<div class="stack-list stack-box">
|
7
|
+
<ul>
|
8
|
+
{{#get_other_stack_list}}
|
9
|
+
<li><a href="/{{.}}">{{.}}</a></li>
|
10
|
+
{{/get_other_stack_list}}
|
11
|
+
</ul>
|
12
|
+
</div>
|
@@ -0,0 +1,604 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
5
|
+
<title>Deployinator</title>
|
6
|
+
<link rel="shortcut icon" href="/favicon.ico">
|
7
|
+
<link rel="stylesheet" href="/static/css/highlight.css" type="text/css" media="screen">
|
8
|
+
<link rel="stylesheet" href="/static/css/style.css?v=10" type="text/css" media="screen">
|
9
|
+
<link rel="stylesheet" href="/static/css/diff_style.css" type="text/css" media="screen">
|
10
|
+
<script src="/js/jquery-1.8.3.min.js"></script>
|
11
|
+
<script src="/js/jquery-ui-1.8.24.min.js"></script>
|
12
|
+
{{{ additional_header_html }}}
|
13
|
+
</head>
|
14
|
+
<body>
|
15
|
+
{{{ additional_top_body_html }}}
|
16
|
+
|
17
|
+
{{#dev_context?}}
|
18
|
+
<div id="dev">Deployinator is in Dev Context. Pushes will NOT go live.</div>
|
19
|
+
{{/dev_context?}}
|
20
|
+
<div class="hidden" id="app_context" data-context="{{ app_context }}"></div>
|
21
|
+
|
22
|
+
<div id="content" class="clearfix">
|
23
|
+
|
24
|
+
<!-- header -->
|
25
|
+
<header class="{{get_header_css_class}} {{stack}} search-{{search_live_cluster}}">
|
26
|
+
<div id="dna">
|
27
|
+
<a href="/">_________ ______ _____ _____
|
28
|
+
______ /_____ ________ ___ /______ _____ _____(_)_______ ______ ___ /_______ ________
|
29
|
+
_ __ / _ _ \___ __ \__ / _ __ \__ / / /__ / __ __ \_ __ `/_ __/_ __ \__ ___/
|
30
|
+
/ /_/ / / __/__ /_/ /_ / / /_/ /_ /_/ / _ / _ / / // /_/ / / /_ / /_/ /_ /
|
31
|
+
\__,_/ \___/ _ .___/ /_/ \____/ _\__, / /_/ /_/ /_/ \__,_/ \__/ \____/ /_/
|
32
|
+
/_/ /____/</a>
|
33
|
+
|
34
|
+
</div>
|
35
|
+
<div id="user">
|
36
|
+
<div class="line">
|
37
|
+
<span class="label">Account: </span>
|
38
|
+
<span>
|
39
|
+
{{ username }}
|
40
|
+
<a href="{{ logout_url }}">logout</a>
|
41
|
+
</span>
|
42
|
+
</div>
|
43
|
+
{{#deploy_host?}}
|
44
|
+
<div class="line">
|
45
|
+
<span class="label">Deploy Host: </span>
|
46
|
+
<span>{{{get_deploy_target_status}}}</span>
|
47
|
+
</div>
|
48
|
+
{{/deploy_host?}}
|
49
|
+
<div class="line">
|
50
|
+
{{#can_remove_stack_lock?}}
|
51
|
+
<button id="unlockstack"> Clear deploy lock </button>
|
52
|
+
{{/can_remove_stack_lock?}}
|
53
|
+
<span class="label">Stack: </span>
|
54
|
+
<span>
|
55
|
+
<select name='stacks' id='stacks'>
|
56
|
+
{{#get_stack_select}}
|
57
|
+
<option value="{{stack}}"{{#current}} selected{{/current}}>{{stack}}</option>
|
58
|
+
{{/get_stack_select}}
|
59
|
+
</select>
|
60
|
+
</span>
|
61
|
+
</div>
|
62
|
+
<div class="line">
|
63
|
+
<span class="label">Current Deploys: </span>
|
64
|
+
<span><a title="Wm5sb3JMYmhGdWJoeXFPYmJncG56Y1ZhRnJwaGV2Z2w=" href= "/deploys_status">Watch</a></span>
|
65
|
+
</div>
|
66
|
+
</div>
|
67
|
+
</header>
|
68
|
+
|
69
|
+
<!-- main content -->
|
70
|
+
{{{ yield }}}
|
71
|
+
|
72
|
+
<!-- footer -->
|
73
|
+
<footer>
|
74
|
+
<div class="etsy">Etsy</div>
|
75
|
+
<div class="source"><a href="http://github.com/etsy/deployinator/tree/master">Deployinator on Github</a></div>
|
76
|
+
</footer>
|
77
|
+
|
78
|
+
<script src="/js/flot/jquery.flot.min.js"></script>
|
79
|
+
<script src="/js/flot/jquery.flot.selection.js"></script>
|
80
|
+
<script src="/js/jquery.timed_bar.js"></script>
|
81
|
+
<script>
|
82
|
+
var autoScroll = true;
|
83
|
+
var deploy_enabled = true;
|
84
|
+
var is_deploying = false;
|
85
|
+
var clearing_deploy_lock = false;
|
86
|
+
var current_method; // is used by the error report to make the message more specific
|
87
|
+
var deploy_buttons;
|
88
|
+
var app_context;
|
89
|
+
var deployer = "{{ username }}";
|
90
|
+
var ircnick; // the irc nick of the logged in user; will be set later on
|
91
|
+
var queue_head = ''; // If this stack has a queue then we will be storing a string
|
92
|
+
// here to represent the current head of the queue. This will
|
93
|
+
// be set in push_topic.mustache and comes from pushbot.
|
94
|
+
seen_errors = 0; // GLOBAL
|
95
|
+
|
96
|
+
// makes the stacks dropdown work
|
97
|
+
$('#stacks').change(function () {
|
98
|
+
var stack = $(this).val();
|
99
|
+
window.location = '/' + stack;
|
100
|
+
});
|
101
|
+
|
102
|
+
// listen for changes on the watch button
|
103
|
+
$('.watch').click(function () {
|
104
|
+
$.ajax({
|
105
|
+
url : '/{{stack}}/can-deploy',
|
106
|
+
dataType: 'json',
|
107
|
+
timeout: 1001,
|
108
|
+
success: function (data) {
|
109
|
+
var form = $("form[data-method='" + data.method + "']");
|
110
|
+
console.log(form);
|
111
|
+
if (form.length == 1) {
|
112
|
+
stream_log_websocket(form[0]);
|
113
|
+
}
|
114
|
+
}
|
115
|
+
});
|
116
|
+
});
|
117
|
+
|
118
|
+
// return a more readable method name for showing to the operator
|
119
|
+
var human_readable_method_name = function (method_name) {
|
120
|
+
if (method_name === "rsync_to_webs") {
|
121
|
+
method_name = "production";
|
122
|
+
} else if (method_name === "princess_rsync") {
|
123
|
+
method_name = "princess";
|
124
|
+
}
|
125
|
+
return method_name;
|
126
|
+
}
|
127
|
+
|
128
|
+
function close_stream_log_websocket_deploys(id){
|
129
|
+
$( "#shell-frame-" + id ).remove();
|
130
|
+
}
|
131
|
+
|
132
|
+
function connect_web_socket(stack, rnd_id, bar, button) {
|
133
|
+
var bufferedMsg = '';
|
134
|
+
var append;
|
135
|
+
var ws = new WebSocket('ws://' + window.location.hostname + ':{{stack_tail_websocket_port}}');
|
136
|
+
var show = function(el) {
|
137
|
+
return function(msg) {
|
138
|
+
bufferedMsg = bufferedMsg + msg;
|
139
|
+
if (append) {
|
140
|
+
clearTimeout(append);
|
141
|
+
}
|
142
|
+
append = setTimeout(function() {
|
143
|
+
var dom = $(bufferedMsg);
|
144
|
+
dom.filter('script#deploy-done').each(function(){
|
145
|
+
ws.onclose = function () { };
|
146
|
+
ws.close();
|
147
|
+
if (bar) {
|
148
|
+
bar.replaceWith(button);
|
149
|
+
updateVersionsAndBuilds();
|
150
|
+
}
|
151
|
+
return false;
|
152
|
+
});
|
153
|
+
el.html(bufferedMsg);
|
154
|
+
}, 50);
|
155
|
+
}
|
156
|
+
}
|
157
|
+
($('#' + rnd_id).contents().find('body.code'));
|
158
|
+
ws.onopen = function() { ws.send(stack) };
|
159
|
+
ws.onmessage = function(m) {
|
160
|
+
if (m.data == '{{stack_tail_version}}') {
|
161
|
+
ws.onmessage = function(m) { show(m.data); }
|
162
|
+
ws.onclose = function() { show('<div class="command"><h4>Websocket died. Please refresh page to continue viewing log</h4></div>'); }
|
163
|
+
} else {
|
164
|
+
show('<div class="command"><h4>Version mismatch between front end and back end for tailer. Please refresh page for latest js to fix</h4></div>');
|
165
|
+
ws.close();
|
166
|
+
}
|
167
|
+
};
|
168
|
+
return ws;
|
169
|
+
}
|
170
|
+
|
171
|
+
function massage_iframe (rnd_id) {
|
172
|
+
$('#' + rnd_id).contents().find('html')[0].innerHTML = '\
|
173
|
+
<head>\
|
174
|
+
<link rel="stylesheet" href="/static/css/style.css?v=10" type="text/css" media="screen">\
|
175
|
+
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" />\
|
176
|
+
<style type="text/css" media="screen">\
|
177
|
+
body { background:#111 none repeat scroll 0 0; }\
|
178
|
+
</style>\
|
179
|
+
</head>\
|
180
|
+
<body>\
|
181
|
+
</body>';
|
182
|
+
$('#' + rnd_id).contents().find('body').addClass('code');
|
183
|
+
$('#' + rnd_id).contents().find('body').html('\
|
184
|
+
<div class="command">\
|
185
|
+
<h4>\
|
186
|
+
{{ tailer_loading_message }}\
|
187
|
+
</h4>\
|
188
|
+
</div>');
|
189
|
+
}
|
190
|
+
|
191
|
+
function create_iframe (rnd_id) {
|
192
|
+
var frame = $("<iframe src='about:blank'>").attr("name", rnd_id);
|
193
|
+
frame.attr('id', rnd_id);
|
194
|
+
frame.addClass("runlogpane");
|
195
|
+
// Pauses scrolling if you hover over the iFrame.
|
196
|
+
|
197
|
+
frame.hover( function() {
|
198
|
+
autoScroll = false; },
|
199
|
+
function() { autoScroll = true;
|
200
|
+
});
|
201
|
+
return frame;
|
202
|
+
}
|
203
|
+
|
204
|
+
function stream_log_websocket_deploys (stack, stage) {
|
205
|
+
|
206
|
+
var rnd_id = "r_" + (Math.random() + "").replace('.', '');
|
207
|
+
var name = "<div id=shell-head><div class=deploys-listing-heading>Stack: " + stack + " Stage: " + stage + "</div><a href=\"javascript:close_stream_log_websocket_deploys('" + rnd_id + "')\">Remove</a></div>";
|
208
|
+
var whole_shell_frame = $("<div id=shell-frame-" + rnd_id + "></div>");
|
209
|
+
|
210
|
+
whole_shell_frame.append(name);
|
211
|
+
var frame = create_iframe (rnd_id);
|
212
|
+
whole_shell_frame.append(frame)
|
213
|
+
$("#shell_deploys").append(whole_shell_frame);
|
214
|
+
massage_iframe (rnd_id)
|
215
|
+
connect_web_socket(stack, rnd_id, null, null);
|
216
|
+
}
|
217
|
+
|
218
|
+
|
219
|
+
var stream_log_websocket = function (form) {
|
220
|
+
is_deploying = true;
|
221
|
+
seen_errors = 0;
|
222
|
+
disableDeploys();
|
223
|
+
|
224
|
+
var button = $(form).find('button');
|
225
|
+
var method = $(form).find('input[name="method"]').val();
|
226
|
+
var rnd_id = "r_" + (Math.random() + "").replace('.', '');
|
227
|
+
var frame = create_iframe (rnd_id);
|
228
|
+
|
229
|
+
var bar = $('<div>').addClass('push-progress');
|
230
|
+
|
231
|
+
var $panel = getDeployPanel(method);
|
232
|
+
|
233
|
+
if ($panel.length > 0) {
|
234
|
+
$panel.removeClass("hide");
|
235
|
+
$(".progress-panel", $panel).show();
|
236
|
+
$(".complete-panel", $panel).hide();
|
237
|
+
$panel.find('.run-log-console').html(frame);
|
238
|
+
} else {
|
239
|
+
$("#main").prepend(frame);
|
240
|
+
}
|
241
|
+
|
242
|
+
var doc = frames[rnd_id].document;
|
243
|
+
if (doc) {
|
244
|
+
doc.open();
|
245
|
+
doc.close();
|
246
|
+
}
|
247
|
+
massage_iframe (rnd_id)
|
248
|
+
// stash the method name in a global we can use later on
|
249
|
+
current_method = human_readable_method_name(method);
|
250
|
+
|
251
|
+
button.replaceWith(bar);
|
252
|
+
var extra_class = $(form).attr("class");
|
253
|
+
var extra;
|
254
|
+
if (extra_class === undefined) {
|
255
|
+
extra = "";
|
256
|
+
} else {
|
257
|
+
extra = extra_class.toLowerCase();
|
258
|
+
}
|
259
|
+
if (!extra) {
|
260
|
+
extra = ".*"; // this gets ti to at least return
|
261
|
+
}
|
262
|
+
|
263
|
+
$.get("/ti/{{stack}}/"+ extra , function(data) {
|
264
|
+
bar.timed_bar(data);
|
265
|
+
});
|
266
|
+
|
267
|
+
connect_web_socket('{{stack}}', rnd_id, bar, button);
|
268
|
+
}
|
269
|
+
|
270
|
+
$('body').on("click", '.pushers form[action="/deploys"] button', function() {
|
271
|
+
// Check that the user doing the deploy is at the head of the queue
|
272
|
+
if (!checkDeployerIsAtHeadOfQueue()) {
|
273
|
+
return false;
|
274
|
+
}
|
275
|
+
|
276
|
+
is_deploying = true;
|
277
|
+
seen_errors = 0;
|
278
|
+
disableDeploys();
|
279
|
+
|
280
|
+
var button = $(this);
|
281
|
+
var form = $(this).closest('form')[0];
|
282
|
+
var error_deploying = false;
|
283
|
+
var method = $(form).find('input[name="method"]').val();
|
284
|
+
|
285
|
+
// Manual config editor magic
|
286
|
+
if ($(form).data('method') == 'web_config_diff') {
|
287
|
+
/* copy the text of the textarea to our form for reviewing the diff */
|
288
|
+
$('#production_field').val($("textarea#production").val());
|
289
|
+
$("#config-editor").hide();
|
290
|
+
}
|
291
|
+
|
292
|
+
$.ajax({
|
293
|
+
type: 'POST',
|
294
|
+
url: form.action + '?stack={{stack}}&stage=' + method,
|
295
|
+
timeout: 5000,
|
296
|
+
data: $(form).serialize(),
|
297
|
+
async: false, // makes the parent wait for the response
|
298
|
+
success: function (data) {
|
299
|
+
},
|
300
|
+
error: function (data) {
|
301
|
+
error_deploying = true;
|
302
|
+
}
|
303
|
+
});
|
304
|
+
|
305
|
+
if (error_deploying) {
|
306
|
+
alert('There was an error deploying. Please try again in a moment or there is already a deploy going');
|
307
|
+
is_deploying = false;
|
308
|
+
enableDeploys();
|
309
|
+
return false;
|
310
|
+
}
|
311
|
+
|
312
|
+
stream_log_websocket(form);
|
313
|
+
|
314
|
+
return false;
|
315
|
+
});
|
316
|
+
|
317
|
+
/*
|
318
|
+
* checkDeployerIsAtHeadOfQueue
|
319
|
+
* uses global variables: deployer, queue_head and ircnick
|
320
|
+
* Determine if the user who pushed the button is at the
|
321
|
+
* head of the push queue. If not then put up an alert
|
322
|
+
* giving them a chance to not start the deploy.
|
323
|
+
*/
|
324
|
+
var checkDeployerIsAtHeadOfQueue = function () {
|
325
|
+
// Only do this check if not in dev context
|
326
|
+
if (app_context != 'dev') {
|
327
|
+
// Only do this check if there is a push queue on the page
|
328
|
+
var waiters = $(".waiters");
|
329
|
+
if (waiters.length > 0) {
|
330
|
+
$.ajax({
|
331
|
+
url: "/auth2nic/" + deployer,
|
332
|
+
timeout: 5000,
|
333
|
+
async: false, // makes the parent wait for the response
|
334
|
+
success: function (data) {
|
335
|
+
ircnick = data['irc_nick'];
|
336
|
+
},
|
337
|
+
error: function (data) {
|
338
|
+
ircnick = deployer;
|
339
|
+
}
|
340
|
+
});
|
341
|
+
// Double check we have a nick or fall back to using the auth name
|
342
|
+
// both ircnick and deployer are global
|
343
|
+
if (typeof ircnick === 'undefined') {
|
344
|
+
ircnick = deployer;
|
345
|
+
}
|
346
|
+
|
347
|
+
var in_queue = (queue_head.indexOf(ircnick) >= 0);
|
348
|
+
if (!in_queue) {
|
349
|
+
if (!confirm("Hey " + deployer + "!\n\nYou do not seem to be in the push queue at the moment. Are you sure you want to deploy?\n\nClick 'OK' to start the deploy or 'Cancel' to not start the deploy.")) {
|
350
|
+
return false;
|
351
|
+
}
|
352
|
+
}
|
353
|
+
}
|
354
|
+
}
|
355
|
+
return true;
|
356
|
+
};
|
357
|
+
|
358
|
+
function getDeployPanel(method) {
|
359
|
+
return $("#{{stack}}-"+method+"-panel");
|
360
|
+
}
|
361
|
+
|
362
|
+
function updateVersionsAndBuilds() {
|
363
|
+
$.each(["version", "build"], function(i, type) {
|
364
|
+
// This finds only the spans at the top of the page that match for version and build if they exist
|
365
|
+
if ($("[class^='{{stack}}_'][class$='" + type + "']").not("[class$='current_" + type + "']").not("[class$='next_" + type + "']").length) {
|
366
|
+
$.getJSON('/{{stack}}/' + type + 's', function(data) {
|
367
|
+
$.each(data, function(index, tuple) {
|
368
|
+
$(".{{stack}}_"+tuple[0]+"_"+type).html(function(i, o) {
|
369
|
+
if (tuple[1] != o) {
|
370
|
+
$(this).effect("highlight", {}, 3000);
|
371
|
+
}
|
372
|
+
return tuple[1];
|
373
|
+
});
|
374
|
+
});
|
375
|
+
});
|
376
|
+
}
|
377
|
+
});
|
378
|
+
}
|
379
|
+
|
380
|
+
// Updates the appropriate button text when someone is deploying
|
381
|
+
// with the lock status info (who and when)
|
382
|
+
function addStackLockStatus(method, who, when) {
|
383
|
+
// Find the appropriate stack form
|
384
|
+
var stack_form = $('.stack-box form').has('input[value="' + method +'"]');
|
385
|
+
var deploy_button = $(stack_form).find('button:submit');
|
386
|
+
var current_status = $(deploy_button).find("span").text();
|
387
|
+
var deploy_text = "<span style='color: yellow'>" + who + " is deploying since "+
|
388
|
+
when + "</span>";
|
389
|
+
|
390
|
+
if (current_status != deploy_text) {
|
391
|
+
$(deploy_button).html(deploy_text);
|
392
|
+
}
|
393
|
+
}
|
394
|
+
|
395
|
+
// Puts the original text back in each button
|
396
|
+
function removeStackLockStatus() {
|
397
|
+
$(deploy_buttons).each(function(i,b) {
|
398
|
+
b.innerText = b.originalInnerText;
|
399
|
+
});
|
400
|
+
}
|
401
|
+
|
402
|
+
// Disable deploy buttons for that stack - store the active ones first!
|
403
|
+
function disableDeploys(){
|
404
|
+
if (deploy_enabled) {
|
405
|
+
deploy_enabled = false;
|
406
|
+
$(deploy_buttons).attr('disabled','disabled').addClass('button-disabled');
|
407
|
+
$("#unlockstack").show();
|
408
|
+
}
|
409
|
+
}
|
410
|
+
|
411
|
+
// Enable deploy buttons for that stack - store the active ones first!
|
412
|
+
function enableDeploys(){
|
413
|
+
if ((!deploy_enabled) && (!is_deploying)) {
|
414
|
+
$(deploy_buttons).removeAttr('disabled').removeClass('button-disabled');
|
415
|
+
deploy_enabled = true;
|
416
|
+
$("#unlockstack").hide();
|
417
|
+
}
|
418
|
+
}
|
419
|
+
|
420
|
+
// Poll our endpoint to see if this stack is locked. Update the UI accordingly
|
421
|
+
function updateDeployLocks(open_streamer) {
|
422
|
+
if ('{{stack}}' != '') {
|
423
|
+
if (!is_deploying) {
|
424
|
+
$.ajax({
|
425
|
+
url : '/{{stack}}/can-deploy',
|
426
|
+
dataType: 'json',
|
427
|
+
timeout: 1001,
|
428
|
+
success: function (data) {
|
429
|
+
if (data.can_deploy) {
|
430
|
+
enableDeploys();
|
431
|
+
removeStackLockStatus();
|
432
|
+
} else {
|
433
|
+
// This is only called once on page load to determine if log streamer
|
434
|
+
// should be opened
|
435
|
+
var form = $("form[data-method='" + data.method + "']");
|
436
|
+
if (open_streamer && data.who == '{{username}}' && form.length == 1) {
|
437
|
+
stream_log_websocket(form[0]);
|
438
|
+
} else {
|
439
|
+
disableDeploys();
|
440
|
+
addStackLockStatus(data.method, data.who, data.lock_time);
|
441
|
+
}
|
442
|
+
}
|
443
|
+
}
|
444
|
+
});
|
445
|
+
}
|
446
|
+
}
|
447
|
+
}
|
448
|
+
|
449
|
+
var getTime = function() {
|
450
|
+
var date = new Date();
|
451
|
+
return date.getFullYear() + "-"
|
452
|
+
+ date.getMonth() + "-"
|
453
|
+
+ date.getDay() + " "
|
454
|
+
+ date.getHours() + ":"
|
455
|
+
+ date.getMinutes() + ":"
|
456
|
+
+ date.getSeconds();
|
457
|
+
}
|
458
|
+
|
459
|
+
var log = function(_elem, txt) {
|
460
|
+
var txtHolder = $('<pre></pre>');
|
461
|
+
var logLi = $("<li></li>");
|
462
|
+
var elem = $(_elem);
|
463
|
+
var platform = elem.attr('className');
|
464
|
+
var time = getTime();
|
465
|
+
var who = elem.find('input[name=who]').val()
|
466
|
+
var msg = elem.find('input[name=msg]').val()
|
467
|
+
logLi.text([platform, time, who, msg].join(' | '));
|
468
|
+
logLi.append(txtHolder.text(txt));
|
469
|
+
logLi.prependTo('#log ul');
|
470
|
+
}
|
471
|
+
|
472
|
+
// This block is for general stuff that needs to be set up on each page
|
473
|
+
// pollers are set in here with setInterval
|
474
|
+
$(function () {
|
475
|
+
$("#unlockstack").hide();
|
476
|
+
|
477
|
+
// Sets the app context
|
478
|
+
app_context = $('#app_context').data('context');
|
479
|
+
|
480
|
+
// Save the currently active deploy buttons with their original text
|
481
|
+
deploy_buttons = $('.stack-box form .stack-push button:submit:not(.button-disabled)');
|
482
|
+
$(deploy_buttons).each(function (i, b) {
|
483
|
+
b.originalInnerText = b.innerText;
|
484
|
+
});
|
485
|
+
|
486
|
+
updateDeployLocks(true);
|
487
|
+
|
488
|
+
setInterval(function() {
|
489
|
+
if (!autoScroll) { return; }
|
490
|
+
if ($("iframe").length === 0) {
|
491
|
+
return;
|
492
|
+
}
|
493
|
+
$("iframe").each(function() {
|
494
|
+
this.contentWindow.scrollTo(0, this.contentWindow.document.body.offsetHeight);
|
495
|
+
});
|
496
|
+
}, 200);
|
497
|
+
|
498
|
+
// Keep checking our endpoint to see if a deploy is in progress
|
499
|
+
setInterval(updateDeployLocks, 2000);
|
500
|
+
|
501
|
+
// don't do this if there isn't an error_report element on the page
|
502
|
+
if ($("#error_report").length > 0) {
|
503
|
+
// watch for stderror streaming by and copy them to an error_report div
|
504
|
+
// similarly info streams will be copied to an info report div
|
505
|
+
// this can be stopped with clearInterval(ec)
|
506
|
+
ec = setInterval(function() {
|
507
|
+
var errors = $("iframe").contents().find(".stderr");
|
508
|
+
if (errors.length && (seen_errors === 0)) {
|
509
|
+
// we only really need to do this one time
|
510
|
+
$("#error_report").append("<h1>There were some errors during this " +
|
511
|
+
current_method + " deploy</h1>");
|
512
|
+
$("#error_report").show();
|
513
|
+
}
|
514
|
+
var iw = 0; // i watcher, cause i scopes out
|
515
|
+
for (var i=1; i <= errors.length; i++) {
|
516
|
+
// this helps not show the same error more than once
|
517
|
+
// note we start with 1 not 0 and compensate -1 below
|
518
|
+
if (i > seen_errors) {
|
519
|
+
// we clone so that the error is still in the iframe too
|
520
|
+
var e = $(errors[i-1]).clone();
|
521
|
+
var t = e.text(e.text().replace(/STDERR:/,'ERROR: '));
|
522
|
+
$("#error_report").append(e);
|
523
|
+
}
|
524
|
+
iw = i;
|
525
|
+
}
|
526
|
+
seen_errors = iw;
|
527
|
+
|
528
|
+
}, 333); // 1/3 of a second should be faster than the human eye
|
529
|
+
}
|
530
|
+
});
|
531
|
+
|
532
|
+
$("iframe").click(function() {
|
533
|
+
autoScroll = !autoScroll;
|
534
|
+
});
|
535
|
+
|
536
|
+
$("#auto_scroll").click(function() {
|
537
|
+
autoScroll = this.checked;
|
538
|
+
});
|
539
|
+
|
540
|
+
function pad (n) {
|
541
|
+
return n<10 ? '0'+n : n;
|
542
|
+
}
|
543
|
+
|
544
|
+
/** Public: show a notification message
|
545
|
+
*
|
546
|
+
* Parameters:
|
547
|
+
* info_message - String to display
|
548
|
+
*
|
549
|
+
* Returns nothing
|
550
|
+
*/
|
551
|
+
function ui_notify(info_message) {
|
552
|
+
var info_box = $("#info_msg_report");
|
553
|
+
if (info_box.is(':empty')) {
|
554
|
+
info_box.append("<h1>Deploy notifications</h1>");
|
555
|
+
}
|
556
|
+
info_box.append(info_message);
|
557
|
+
info_box.show();
|
558
|
+
}
|
559
|
+
|
560
|
+
/** Public: show a notification message and do some
|
561
|
+
* housekeeping for done deploys.
|
562
|
+
*
|
563
|
+
* Parameters:
|
564
|
+
* info_message - String to display at the end of a
|
565
|
+
* deploy
|
566
|
+
*
|
567
|
+
* Returns nothing
|
568
|
+
*/
|
569
|
+
function deploy_done(info_message, method) {
|
570
|
+
var $panel = getDeployPanel(method);
|
571
|
+
if ($panel.length > 0 ) {
|
572
|
+
$panel.find('.progress-panel').hide();
|
573
|
+
$panel.find('.complete-panel').show();
|
574
|
+
} else {
|
575
|
+
ui_notify(info_message);
|
576
|
+
}
|
577
|
+
|
578
|
+
window.onbeforeunload = null;
|
579
|
+
is_deploying = false;
|
580
|
+
enableDeploys();
|
581
|
+
deploy_enabled = true;
|
582
|
+
current_method = "";
|
583
|
+
}
|
584
|
+
|
585
|
+
// clear the deploy lock
|
586
|
+
$('.line').on('click', '#unlockstack', function(evt) {
|
587
|
+
var msg = 'Are you sure you want to clear the deploy lock?';
|
588
|
+
if (is_deploying) {
|
589
|
+
msg = '**A DEPLOY IS IN PROGRESS**\n\n' + msg;
|
590
|
+
}
|
591
|
+
|
592
|
+
var confirmed = confirm(msg);
|
593
|
+
if (confirmed) {
|
594
|
+
clearing_deploy_lock = true;
|
595
|
+
window.location='/{{stack}}/remove-lock';
|
596
|
+
} else {
|
597
|
+
evt.preventDefault();
|
598
|
+
}
|
599
|
+
});
|
600
|
+
</script>
|
601
|
+
</div>
|
602
|
+
{{{ additional_bottom_body_html }}}
|
603
|
+
</body>
|
604
|
+
</html>
|