Browse Source

refactored auto update endpoints to process Bitbucket's native POST payload

Christoph 12 years ago
parent
commit
048f1b3164
1 changed files with 108 additions and 45 deletions
  1. 108 45
      src/Packagist/WebBundle/Controller/ApiController.php

+ 108 - 45
src/Packagist/WebBundle/Controller/ApiController.php

@@ -20,6 +20,7 @@ use Composer\Package\Loader\ValidatingArrayLoader;
 use Composer\Package\Loader\ArrayLoader;
 use Packagist\WebBundle\Package\Updater;
 use Packagist\WebBundle\Entity\Package;
+use Packagist\WebBundle\Entity\User;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
@@ -84,7 +85,17 @@ class ApiController extends Controller
      */
     public function githubPostReceive(Request $request)
     {
-        return $this->receivePost($request, '{^(?:https?://|git://|git@)?(?P<domain>github\.com)[:/](?P<repo>[\w.-]+/[\w.-]+?)(?:\.git)?$}');
+        // parse the GitHub payload
+        $payload = json_decode($request->request->get('payload'), true);
+
+        if (!$payload || !isset($payload['repository']['url'])) {
+            return new Response(json_encode(array('status' => 'error', 'message' => 'Missing or invalid payload',)), 406);
+        }
+
+        $urlRegex = '{^(?:https?://|git://|git@)?(?P<host>github\.com)[:/](?P<path>[\w.-]+/[\w.-]+?)(?:\.git)?$}';
+        $repoUrl = $payload['repository']['url'];
+
+        return $this->receivePost($request, $repoUrl, $urlRegex);
     }
 
     /**
@@ -93,7 +104,17 @@ class ApiController extends Controller
      */
     public function bitbucketPostReceive(Request $request)
     {
-        return $this->receivePost($request, '{^(?:https?://)?(?P<domain>bitbucket\.org)/(?P<repo>[\w.-]+/[\w.-]+?)/?$}');
+        // decode Bitbucket's POST payload
+        $payload = json_decode($request->request->get('payload'), true);
+
+        if (!$payload || !isset($payload['canon_url']) || !isset($payload['repository']['absolute_url'])) {
+            return new Response(json_encode(array('status' => 'error', 'message' => 'Missing or invalid payload',)), 406);
+        }
+
+        $urlRegex = '{^(?:https?://)?(?P<host>bitbucket\.org)/(?P<path>[\w.-]+/[\w.-]+?)/?$}';
+        $repoUrl = $payload['canon_url'].$payload['repository']['absolute_url'];
+
+        return $this->receivePost($request, $repoUrl, $urlRegex);
     }
 
     /**
@@ -182,18 +203,77 @@ class ApiController extends Controller
         }
     }
 
-    protected function receivePost(Request $request, $urlRegex)
+    /**
+     * Perform the package update
+     *
+     * @param Request $request the current request
+     * @param string $url the repository's URL (deducted from the request)
+     * @param string $urlRegex the regex used to split the user packages into domain and path
+     * @return Response
+     */
+    protected function receivePost(Request $request, $url, $urlRegex)
     {
-        $payload = json_decode($request->request->get('payload'), true);
-        if (!$payload || !isset($payload['repository']['url'])) {
-            return new Response(json_encode(array('status' => 'error', 'message' => 'Missing or invalid payload',)), 406);
+        // try to parse the URL first to avoid the DB lookup on malformed requests
+        if (!preg_match($urlRegex, $url)) {
+            return new Response(json_encode(array('status' => 'error', 'message' => 'Could not parse payload repository URL')), 406);
         }
 
-        // try to parse the URL first to avoid the DB lookup on malformed requests
-        if (!preg_match($urlRegex, $payload['repository']['url'], $requestedRepo)) {
-            return new Response(json_encode(array('status' => 'error', 'message' => 'Could not parse payload repository URL',)), 406);
+        // find the user
+        $user = $this->findUser($request);
+
+        if (!$user) {
+            return new Response(json_encode(array('status' => 'error', 'message' => 'Invalid credentials')), 403);
+        }
+
+        // try to find the user package
+        $package = $this->findPackageByUrl($user, $url, $urlRegex);
+
+        if (!$package) {
+            return new Response(json_encode(array('status' => 'error', 'message' => 'Could not find a package that matches this request (does user maintain the package?)')), 404);
+        }
+
+        // prepare updating the package
+        $config = Factory::createConfig();
+        $loader = new ValidatingArrayLoader(new ArrayLoader());
+        $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE);
+        $em = $this->get('doctrine.orm.entity_manager');
+
+        set_time_limit(3600);
+
+        // update the package entity
+        $repository = new VcsRepository(array('url' => $package->getRepository()), $io, $config);
+        $repository->setLoader($loader);
+        $package->setAutoUpdated(true);
+        $em->flush();
+
+        // perform the actual update (fetch and re-scan the repository's source)
+        $updater = $this->get('packagist.package_updater');
+
+        try {
+            $updater->update($package, $repository);
+        } catch (\Exception $e) {
+            if ($e instanceof InvalidRepositoryException) {
+                $this->get('packagist.package_manager')->notifyUpdateFailure($package, $e, $io->getOutput());
+            }
+
+            return new Response(json_encode(array(
+                'status' => 'error',
+                'message' => '['.get_class($e).'] '.$e->getMessage(),
+                'details' => '<pre>'.$io->getOutput().'</pre>'
+            )), 400);
         }
 
+        return new JsonResponse(array('status' => 'success'), 202);
+    }
+
+    /**
+     * Find a user by his username and API token
+     *
+     * @param Request $request
+     * @return User|null the found user or null otherwise
+     */
+    protected function findUser(Request $request)
+    {
         $username = $request->request->has('username') ?
             $request->request->get('username') :
             $request->query->get('username');
@@ -205,49 +285,32 @@ class ApiController extends Controller
         $user = $this->get('packagist.user_repository')
             ->findOneBy(array('username' => $username, 'apiToken' => $apiToken));
 
-        if (!$user) {
-            return new Response(json_encode(array('status' => 'error', 'message' => 'Invalid credentials',)), 403);
-        }
+        return $user;
+    }
 
-        $updated = false;
-        $config = Factory::createConfig();
-        $loader = new ValidatingArrayLoader(new ArrayLoader());
-        $updater = $this->get('packagist.package_updater');
-        $em = $this->get('doctrine.orm.entity_manager');
+    /**
+     * Find a user package given by its full URL
+     *
+     * @param User $user
+     * @param string $url
+     * @param string $urlRegex
+     * @return Package|null the found package or null otherwise
+     */
+    protected function findPackageByUrl(User $user, $url, $urlRegex)
+    {
+        if (!preg_match($urlRegex, $url, $matched)) {
+            return null;
+        }
 
         foreach ($user->getPackages() as $package) {
             if (preg_match($urlRegex, $package->getRepository(), $candidate)
-                && $candidate['domain'] === $requestedRepo['domain']
-                && $candidate['repo'] === $requestedRepo['repo']
+                && $candidate['host'] === $matched['host']
+                && $candidate['path'] === $matched['path']
             ) {
-                set_time_limit(3600);
-                $updated = true;
-
-                $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE);
-                $repository = new VcsRepository(array('url' => $package->getRepository()), $io, $config);
-                $repository->setLoader($loader);
-                $package->setAutoUpdated(true);
-                $em->flush();
-                try {
-                    $updater->update($package, $repository);
-                } catch (\Exception $e) {
-                    if ($e instanceof InvalidRepositoryException) {
-                        $this->get('packagist.package_manager')->notifyUpdateFailure($package, $e, $io->getOutput());
-                    }
-
-                    return new Response(json_encode(array(
-                        'status' => 'error',
-                        'message' => '['.get_class($e).'] '.$e->getMessage(),
-                        'details' => '<pre>'.$io->getOutput().'</pre>'
-                    )), 400);
-                }
+                return $package;
             }
         }
 
-        if ($updated) {
-            return new Response('{"status": "success"}', 202);
-        }
-
-        return new Response(json_encode(array('status' => 'error', 'message' => 'Could not find a package that matches this request (does user maintain the package?)',)), 404);
+        return null;
     }
 }