Page MenuHomeDevCentral

No OneTemporary

diff --git a/app/Actions/NotifyNewCommitsAction.php b/app/Actions/NotifyNewCommitsAction.php
new file mode 100644
index 0000000..de35171
--- /dev/null
+++ b/app/Actions/NotifyNewCommitsAction.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Nasqueron\Notifications\Actions;
+
+class NotifyNewCommitsAction extends Action {
+ /**
+ * The Phabricator repository call sign
+ *
+ * @var string
+ */
+ public $callSign;
+
+ /**
+ * Initializes a new instance of a AMQP action to report
+ */
+ function __construct ($callSign) {
+ parent::__construct();
+
+ $this->callSign = $callSign;
+ }
+}
diff --git a/app/Listeners/PhabricatorListener.php b/app/Listeners/PhabricatorListener.php
new file mode 100644
index 0000000..4a3d6f4
--- /dev/null
+++ b/app/Listeners/PhabricatorListener.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Nasqueron\Notifications\Listeners;
+
+use Nasqueron\Notifications\Actions\NotifyNewCommitsAction;
+use Nasqueron\Notifications\Events\GitHubPayloadEvent;
+use Nasqueron\Notifications\Events\ReportEvent;
+use Nasqueron\Notifications\Phabricator\PhabricatorAPI;
+use Nasqueron\Notifications\Phabricator\PhabricatorAPIException;
+
+use Event;
+
+class PhabricatorListener {
+ ///
+ /// GitHub → Phabricator
+ ///
+
+ /**
+ * Handles payload events
+ *
+ * @param GitHubPayloadEvent $event The GitHub payload event
+ */
+ public function onGitHubPayload (GitHubPayloadEvent $event) {
+ if ($event->event === 'push') {
+ $this->notifyNewCommits($event);
+ }
+ }
+
+ /**
+ * @return string the repository call sign "OPS", or "" if not in Phabricator
+ */
+ private static function getCallSign (PhabricatorAPI $api, $remoteURI) {
+ $reply = $api->call(
+ 'repository.query',
+ [ 'remoteURIs[0]' => $remoteURI ]
+ );
+
+ if (!count($reply)) {
+ return "";
+ }
+
+ return PhabricatorAPI::getFirstResult($reply)->callsign;
+ }
+
+ /**
+ * Notifies Phabricator there are new commits to pull
+ */
+ public function notifyNewCommits (GitHubPayloadEvent $event) {
+ $api = PhabricatorAPI::forProject($event->door);
+ if (!$api) {
+ // We don't have a Phabricator instance for this project.
+ return;
+ }
+
+ $callSign = static::getCallSign(
+ $api,
+ $event->payload->repository->clone_url
+ );
+
+ if ($callSign === "") {
+ return;
+ }
+
+ $actionToReport = new NotifyNewCommitsAction($callSign);
+ try {
+ $api->call(
+ 'diffusion.looksoon',
+ [ 'callsigns[0]' => $callSign ]
+ );
+ } catch (PhabricatorAPIException $ex) {
+ $actionToReport->attachException($ex);
+ }
+
+ Event::fire(new ReportEvent($actionToReport));
+ }
+
+ ///
+ /// Events listening
+ ///
+
+ /**
+ * Register the listeners for the subscriber.
+ *
+ * @param Illuminate\Events\Dispatcher $events
+ */
+ public function subscribe ($events) {
+ $class = 'Nasqueron\Notifications\Listeners\PhabricatorListener';
+ $events->listen(
+ 'Nasqueron\Notifications\Events\GitHubPayloadEvent',
+ "$class@onGitHubPayload"
+ );
+ }
+}
diff --git a/app/Phabricator/PhabricatorAPI.php b/app/Phabricator/PhabricatorAPI.php
index 5209aca..99d49cd 100644
--- a/app/Phabricator/PhabricatorAPI.php
+++ b/app/Phabricator/PhabricatorAPI.php
@@ -1,110 +1,159 @@
<?php
namespace Nasqueron\Notifications\Phabricator;
class PhabricatorAPI {
///
/// Private members
///
/**
* The Phabricator main URL
*
* @var string
*/
private $instance;
/**
* The token generated at /settings/panel/apitokens/ to query the API
*
* @var string
*/
private $apiToken;
///
/// Constructors
///
/**
* Initializes a new instance of the Phabricator API class
*
* @param string $instance The Phabricator main URL, without trailing slash
* @param string $apiToken The token generated at /settings/panel/apitokens/
*/
public function __construct ($instance, $apiToken) {
$this->instance = $instance;
$this->apiToken = $apiToken;
}
- private static function getServiceForInstance ($instance) {
+ public static function forInstance ($instance) {
+ $service = self::getServiceForInstance($instance);
+ if ($service === null) {
+ throw new \RuntimeException("No credentials for Phabricator instance $instance.");
+ }
+
+ return new self($service->instance, $service->secret);
+ }
+
+ /**
+ * Gets an API instance for the specify project
+ *
+ * @param string $project The name of the project (this matches the door parameter in credentials.json)
+ * @return PhabricatorAPI|null A PhabricatorAPI instance for the project if found; otherwise, null.
+ */
+ public static function forProject ($project) {
+ $service = self::getServiceForProject($project);
+ if ($service === null) {
+ return null;
+ }
+ return new self($service->instance, $service->secret);
+ }
+
+
+ ///
+ /// Helper methods for static constructors
+ ///
+
+ private static function getServices () {
$path = config('services.gate.credentials');
$data = json_decode(file_get_contents($path));
- foreach ($data->services as $service) {
- if ($service->gate === "Phabricator" && $service->instance = $instance) {
+ return $data->services;
+ }
+
+ private static function getServiceForInstance ($instance) {
+ foreach (self::getServices() as $service) {
+ if ($service->gate === "Phabricator" && $service->instance === $instance) {
return $service;
}
}
return null;
}
- public static function forInstance ($instance) {
- $service = self::getServiceForInstance($instance);
- if ($service === null) {
- throw new \RuntimeException("No credentials for Phabricator instance $instance.");
+ private static function getServiceForProject ($project) {
+ foreach (self::getServices() as $service) {
+ if ($service->gate === "Phabricator" && $service->door === $project) {
+ return $service;
+ }
}
- return new self($service->instance, $service->secret);
+ return null;
}
+ ///
+ /// Public methods
+ ///
+
/**
* Calls a Conduit API method
*
* @param $method The method to call (e.g. repository.create)
* @param $arguments The arguments to use
*/
public function call ($method, $arguments = []) {
$url = $this->instance . '/api/' . $method;
$arguments['api.token'] = $this->apiToken;
$reply = json_decode(static::post($url, $arguments));
if ($reply->error_code !== NULL) {
throw new PhabricatorAPIException(
$reply->error_code,
$reply->error_info
);
}
return $reply->result;
}
+ /**
+ * Gets the first result of an API reply
+ *
+ * @param Traversable|array $reply
+ * @return mixed
+ */
+ public static function getFirstResult ($reply) {
+ foreach ($reply as $value) {
+ return $value;
+ }
+ }
+
///
/// CURL session
///
public static function getPostFields ($arguments) {
$items = [];
foreach ($arguments as $key => $value) {
$items[] = urlencode($key) . '=' . urlencode($value);
}
return implode('&', $items);
}
public static function post ($url, $arguments) {
$options = [
CURLOPT_URL => $url,
CURLOPT_HEADER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => 1,
CURLOPT_POSTFIELDS => static::getPostFields($arguments),
];
$ch = curl_init();
curl_setopt_array($ch, $options);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Sep 18, 02:08 (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2989823
Default Alt Text
(8 KB)

Event Timeline