Indefero

Indefero Commit Details


Date:2010-05-10 03:11:27 (14 years 7 months ago)
Author:Loic d'Anterroches
Branch:develop, feature-issue_links, feature.better-home, feature.content-md5, feature.diff-whitespace, feature.download-md5, feature.issue-links, feature.issue-of-others, feature.issue-summary, feature.search-filter, feature.webrepos, feature.wiki-default-page, master, release-1.1, release-1.2, release-1.3
Commit:47acc734515d8950a362aba399d5f713458a3639
Parents: 2f22d48dd0a046956b20763722c3bb0ea3dcf85d
Message:Added the webhooks.

Changes:

File differences

scripts/queuecron.php
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
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* This script process the queue of items.
*
* At the moment the queue is only used for the webhooks, but it would
* be good in the future to use it for indexing and email
* notifications.
*
*/
require dirname(__FILE__).'/../src/IDF/conf/path.php';
require 'Pluf.php';
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
#;*/ ::
$lock_file = Pluf::f('idf_queuecron_lock',
Pluf::f('tmp_folder', '/tmp').'/queuecron.lock');
if (file_exists($lock_file)) {
Pluf_Log::event(array('queuecron.php', 'skip'));
return;
}
file_put_contents($lock_file, time(), LOCK_EX);
/**
* [signal]
*
* queuecron.php::run
*
* [sender]
*
* queuecron.php
*
* [description]
*
* This signal allows an application to perform a set of tasks when
* the queue cron job is run. This is done usually every 5 minutes.
*
* [parameters]
*
* array()
*
*/
$params = array();
Pluf_Signal::send('queuecron.php::run', 'queuecron.php', $params);
unlink($lock_file);
src/IDF/Commit.php
288288
289289
290290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
291317
292318
$email->addTextMessage($text_email);
$email->sendMail();
Pluf_Translation::loadSetLocale($current_locale);
// Now we add to the queue, soon we will push everything in
// the queue, including email notifications and indexing.
// Even if the url is empty, we add to the queue as some
// plugins may want to do something with this information in
// an asynchronous way.
$url = str_replace(array('%p', '%r'),
array($project->shortname, $this->scm_id),
$conf->getVal('webhook_url', ''));
$payload = array('to_send' => array(
'project' => $project->shortname,
'rev' => $this->scm_id,
'summary' => $this->summary,
'fullmessage' => $this->fullmessage,
'author' => $this->origauthor,
'creation_date' => $this->creation_dtime,
),
'project_id' => $project->id,
'authkey' => $project->getPostCommitHookKey(),
'url' => $url,
);
$item = new IDF_Queue();
$item->type = 'new_commit';
$item->payload = $payload;
$item->create();
}
}
src/IDF/Form/SourceConf.php
3434
3535
3636
37
37
3838
3939
4040
......
4949
5050
5151
52
53
54
55
56
57
58
59
60
61
5262
5363
5464
public function initFields($extra=array())
{
$this->conf = $extra['conf'];
if ($this->conf->getVal('scm', 'git') == 'svn') {
if ($extra['remote_svn']) {
$this->fields['svn_username'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Repository username'),
'widget' => 'Pluf_Form_Widget_PasswordInput',
));
}
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
$url = Pluf_HTTP_URL_urlForView('idf_faq').'#webhooks';
$this->fields['webhook_url'] = new Pluf_Form_Field_Url(
array('required' => false,
'label' => __('Webhook URL'),
'initial' => $this->conf->getVal('webhook_url', ''),
'help_text' => sprintf(__('Learn more about the <a href="%s">post-commit web hooks</a>.'), $url),
'widget_attrs' => array('size' => 35),
));
}
}
src/IDF/Project.php
425425
426426
427427
428
429
430
431
432
433
434
435
436
437
438
428439
429440
430441
}
/**
* Get the post commit hook key.
*
* The goal is to get something predictable but from which one
* cannot reverse find the secret key.
*/
public function getPostCommitHookKey()
{
return md5($this->id.sha1(Pluf::f('secret_key')).$this->shortname);
}
/**
* Get the root name of the project scm
*
* @return string SCM root
src/IDF/Queue.php
138138
139139
140140
141
142
141143
142144
143145
......
183185
184186
185187
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
186222
$this->creation_dtime = gmdate('Y-m-d H:i:s');
$this->lasttry_dtime = gmdate('Y-m-d H:i:s');
$this->results = array();
$this->trials = 0;
$this->status = 0;
}
}
$this->lasttry_dtime = gmdate('Y-m-d H:i:s');
$this->update();
}
/**
* Parse the queue.
*
* It is a signal handler to just hook itself at the right time in
* the cron job performing the maintainance work.
*
* The processing relies on the fact that no other processing jobs
* must run at the same time. That is, your cron job must use a
* lock file or something like to not run in parallel.
*
* The processing is simple, first get 500 queue items, mark them
* as being processed and for each of them call the processItem()
* method which will trigger another event for processing.
*
* If you are processing more than 500 items per batch, you need
* to switch to a different solution.
*
*/
public static function process($sender, &$params)
{
$where = 'status=0 OR status=2';
$items = Pluf::factory('IDF_Queue')->getList(array('filter'=>$where,
'nb'=> 500));
Pluf_Log::event(array('IDF_Queue::process', $items->count()));
foreach ($items as $item) {
$item->status = 1;
$item->update();
}
foreach ($items as $item) {
$item->status = 1;
$item->processItem();
}
}
}
src/IDF/Views/Project.php
475475
476476
477477
478
479
480
478481
479482
480483
481484
482485
483486
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
487
488
489
490
491
492
493
494
495
496
497
498
510499
511
512
500
501
502
503
504
505
506
507
508
509
510
513511
514
515512
513
514
515
516
516517
517518
518519
......
529530
530531
531532
533
532534
533535
534536
/**
* Administrate the source control.
*
* There, the login/password of the subversion remote repo can be
* change together with the webhook url.
*/
public $adminSource_precond = array('IDF_Precondition::projectOwner');
public function adminSource($request, $match)
{
$prj = $request->project;
$title = sprintf(__('%s Source'), (string) $prj);
$form = null;
$remote_svn = false;
if ($request->conf->getVal('scm') == 'svn' and
strlen($request->conf->getVal('svn_remote_url')) > 0) {
$remote_svn = true;
$extra = array(
'conf' => $request->conf,
);
if ($request->method == 'POST') {
$form = new IDF_Form_SourceConf($request->POST, $extra);
if ($form->isValid()) {
foreach ($form->cleaned_data as $key=>$val) {
$request->conf->setVal($key, $val);
}
$request->user->setMessage(__('The project source configuration has been saved.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminSource',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$params = array();
foreach (array('svn_username', 'svn_password') as $key) {
$_val = $request->conf->getVal($key, false);
if ($_val !== false) {
$params[$key] = $_val;
}
$remote_svn = ($request->conf->getVal('scm') == 'svn' and
strlen($request->conf->getVal('svn_remote_url')) > 0);
$extra = array(
'conf' => $request->conf,
'remote_svn' => $remote_svn,
);
if ($request->method == 'POST') {
$form = new IDF_Form_SourceConf($request->POST, $extra);
if ($form->isValid()) {
foreach ($form->cleaned_data as $key=>$val) {
$request->conf->setVal($key, $val);
}
if (count($params) == 0) {
$params = null; //Nothing in the db, so new form.
$request->user->setMessage(__('The project source configuration has been saved.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminSource',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$params = array();
foreach (array('svn_username', 'svn_password', 'webhook_url') as $key) {
$_val = $request->conf->getVal($key, false);
if ($_val !== false) {
$params[$key] = $_val;
}
$form = new IDF_Form_SourceConf($params, $extra);
}
if (count($params) == 0) {
$params = null; //Nothing in the db, so new form.
}
$form = new IDF_Form_SourceConf($params, $extra);
}
$scm = $request->conf->getVal('scm', 'git');
$options = array(
'repository_size' => $prj->getRepositorySize(),
'page_title' => $title,
'form' => $form,
'hookkey' => $prj->getPostCommitHookKey(),
),
$request);
}
src/IDF/Webhook.php
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
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008, 2009, 2010 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Management of the webhooks.
*
* The class provides the tools to perform the POST request with
* authentication for the webhooks.
*
*/
class IDF_Webhook
{
/**
* Perform the POST request given the webhook payload.
*
* @param array Payload
* @return bool Success or error
*/
public static function postNotification($payload)
{
$data = json_encode($payload['to_send']);
$sign = hash_hmac('md5', $data, $payload['authkey']);
$params = array('http' => array(
'method' => 'POST',
'content' => $data,
'user_agent' => 'Indefero Hook Sender (http://www.indefero.net)',
'max_redirects' => 0,
'timeout' => 15,
'header'=> 'Indefero-Hook-Hmac: '.$sign."\r\n"
.'Content-Type: application/json'."\r\n",
)
);
$url = $payload['url'];
$ctx = stream_context_create($params);
$fp = @fopen($url, 'rb', false, $ctx);
if (!$fp) {
return false;
}
$meta = stream_get_meta_data($fp);
@fclose($fp);
if (!isset($meta['wrapper_data'][0]) or $meta['timed_out']) {
return false;
}
if (0 === strpos($meta['wrapper_data'][0], 'HTTP/1.1 2') or
0 === strpos($meta['wrapper_data'][0], 'HTTP/1.1 3')) {
return true;
}
return false;
}
/**
* Process the webhook.
*
*/
public static function process($sender, &$params)
{
$item = $params['item'];
if ($item->type != 'new_commit') {
// We do nothing.
return;
}
if (isset($params['res']['IDF_Webhook::process']) and
$params['res']['IDF_Webhook::process'] == true) {
// Already processed.
return;
}
// We have either to retry or to push for the first time.
$res = self::postNotification($item->payload);
if ($res) {
$params['res']['IDF_Webhook::process'] = true;
} elseif ($item->trials >= 9) {
// We are at trial 10, give up
$params['res']['IDF_Webhook::process'] = true;
} else {
// Need to try again
$params['res']['IDF_Webhook::process'] = false;
}
}
}
src/IDF/conf/urls.php
6666
6767
6868
69
69
70
7071
7172
7273
$ctl[] = array('regex' => '#^/help/$#',
'base' => $base,
'model' => 'IDF_Views',
'method' => 'faq');
'method' => 'faq',
'name' => 'idf_faq');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/$#',
'base' => $base,
src/IDF/relations.php
8484
8585
8686
87
88
89
90
8791
92
93
94
95
96
97
98
8899
Pluf_Signal::connect('gitpostupdate.php::run',
array('IDF_Plugin_SyncGit', 'entry'));
#
# -- Processing of the webhook queue --
Pluf_Signal::connect('queuecron.php::run',
array('IDF_Queue', 'process'));
#
# Processing of a given webhook, the hook can be configured
# directly in the configuration file if a different solution
# is required.
Pluf_Signal::connect('IDF_Queue::processItem',
Pluf::f('idf_hook_process_item',
array('IDF_Webhook', 'process')));
return $m;
src/IDF/templates/idf/admin/source.html
11
2
2
33
4
4
55
66
77
......
3737
3838
3939
40
41
42
43
44
45
46
47
48
49
50
51
4052
4153
4254
4355
4456
4557
46
58
4759
4860
4961
......
5163
5264
5365
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
5493
{extends "idf/admin/base.html"}
{block docclass}yui-t1{assign $inSource = true}{/block}
{block docclass}yui-t3{assign $inSource = true}{/block}
{block body}
{if $remote_svn and $form.errors}
{if $form.errors}
<div class="px-message-error">
<p>{trans 'The form contains some errors. Please correct them to update the source configuration.'}</p>
{if $form.get_top_errors}
<td>{if $form.f.svn_password.errors}{$form.f.svn_password.fieldErrors}{/if}
{$form.f.svn_password|unsafe}
</td>
</tr>{/if}
<tr>
<th>{$form.f.webhook_url.labelTag}:</th>
<td>{if $form.f.webhook_url.errors}{$form.f.webhook_url.fieldErrors}{/if}
{$form.f.webhook_url|unsafe}<br>
</td>
</tr>
<tr>
<th>{trans 'Post-commit authentication key:'}</th>
<td>{$hookkey}
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="submit" value="{trans 'Save Changes'}" name="submit" />
</td>
</tr>{/if}
</tr>
</table>
</form>
{/block}
<div class="issue-submit-info">
<p>{blocktrans}You can find here the current repository configuration of your project.{/blocktrans}</p>
</div>
<br>
<div class="issue-submit-info">
{blocktrans}<p>The webhook URL setting specifies a URL to which a HTTP POST
request is sent after each repository commit. If this field is empty,
notifications are disabled.</p>
<p>Only properly-escaped <strong>HTTP</strong> URLs are supported, for example:</p>
<ul>
<li>http://domain.com/commit</li>
<li>http://domain.com/commit?my%20param</li>
</ul>
<p>In addition, the URL may contain the following "%" notation, which
will be replaced with specific project values for each commit:</p>
<ul>
<li>%p - project name</li>
<li>%r - revision number</li>
</ul>
<p>For example, committing revision 123 to project 'my-project' with
post-commit URL http://mydomain.com/%p/%r would send a request to
http://mydomain.com/my-project/123.</p>{/blocktrans}</div>
{/block}

Archive Download the corresponding diff file

Page rendered in 0.11236s using 13 queries.