MDL-38493 Make mdeploy.php able to install new plugin
authorDavid Mudrák <david@moodle.com>
Thu, 14 Mar 2013 19:44:35 +0000 (20:44 +0100)
committerDavid Mudrák <david@moodle.com>
Thu, 28 Mar 2013 10:54:05 +0000 (11:54 +0100)
The patch implements the second mode of the mdeploy.php script. If
--install option is passed, the given package is downloaded, verified
and deployed to the requested location.

mdeploy.php
mdeploytest.php

index 303d3d1..ca9a4ce 100644 (file)
@@ -775,7 +775,53 @@ class worker extends singleton_pattern {
             $this->done();
 
         } else if ($this->input->get_option('install')) {
-            // Installing a new plugin not implemented yet.
+            $this->log('Plugin installation requested');
+
+            $plugintyperoot = $this->input->get_option('typeroot');
+            $pluginname     = $this->input->get_option('name');
+            $source         = $this->input->get_option('package');
+            $md5remote      = $this->input->get_option('md5');
+
+            // Check if the plugin location if available for us.
+            $pluginlocation = $plugintyperoot.'/'.$pluginname;
+
+            $this->log('New plugin code location: '.$pluginlocation);
+
+            if (file_exists($pluginlocation)) {
+                throw new filesystem_exception('Unable to prepare the plugin location (directory already exists)');
+            }
+
+            if (!$this->create_directory_precheck($pluginlocation)) {
+                throw new filesystem_exception('Unable to prepare the plugin location (cannot create new directory)');
+            }
+
+            // Fetch the ZIP file into a temporary location.
+            $target = $this->target_location($source);
+            $this->log('Downloading package '.$source);
+
+            if ($this->download_file($source, $target)) {
+                $this->log('Package downloaded into '.$target);
+            } else {
+                $this->log('cURL error ' . $this->curlerrno . ' ' . $this->curlerror);
+                $this->log('Unable to download the file');
+                throw new download_file_exception('Unable to download the package');
+            }
+
+            // Compare MD5 checksum of the ZIP file
+            $md5local = md5_file($target);
+
+            if ($md5local !== $md5remote) {
+                $this->log('MD5 checksum failed. Expected: '.$md5remote.' Got: '.$md5local);
+                throw new checksum_exception('MD5 checksum failed');
+            }
+            $this->log('MD5 checksum ok');
+
+            // Unzip the plugin package file into the plugin location.
+            $this->unzip_plugin($target, $plugintyperoot, $pluginlocation, false);
+            $this->log('Package successfully extracted');
+
+            // Redirect to the given URL (in HTTP) or exit (in CLI).
+            $this->done();
         }
 
         // Print help in CLI by default.
@@ -1126,18 +1172,36 @@ class worker extends singleton_pattern {
     }
 
     /**
-     * Checks to see if a source foldr could be safely moved into the given new location
+     * Checks to see if a source folder could be safely moved into the given new location
      *
      * @param string $destination full path to the new expected location of a folder
      * @return bool
      */
     protected function move_directory_target_precheck($target) {
 
-        if (file_exists($target)) {
+        // Check if the target folder does not exist yet, can be created
+        // and removed again.
+        $result = $this->create_directory_precheck($target);
+
+        // At the moment, it seems to be enough to check. We may want to add
+        // more steps in the future.
+
+        return $result;
+    }
+
+    /**
+     * Make sure the given directory can be created (and removed)
+     *
+     * @param string $path full path to the folder
+     * @return bool
+     */
+    protected function create_directory_precheck($path) {
+
+        if (file_exists($path)) {
             return false;
         }
 
-        $result = mkdir($target, 02777) && rmdir($target);
+        $result = mkdir($path, 02777) && rmdir($path);
 
         return $result;
     }
@@ -1273,7 +1337,9 @@ class worker extends singleton_pattern {
         $result = $zip->open($ziplocation);
 
         if ($result !== true) {
-            $this->move_directory($backuplocation, $expectedlocation);
+            if ($backuplocation !== false) {
+                $this->move_directory($backuplocation, $expectedlocation);
+            }
             throw new zip_exception('Unable to open the zip package');
         }
 
@@ -1292,7 +1358,9 @@ class worker extends singleton_pattern {
         if (!$zip->extractTo($plugintyperoot)) {
             $zip->close();
             $this->remove_directory($expectedlocation, true); // just in case something was created
-            $this->move_directory_into($backuplocation, $expectedlocation);
+            if ($backuplocation !== false) {
+                $this->move_directory_into($backuplocation, $expectedlocation);
+            }
             throw new zip_exception('Unable to extract the zip package');
         }
 
index 76800eb..7fb81c5 100644 (file)
@@ -106,6 +106,13 @@ class testable_worker extends worker {
     public function remove_directory($path, $keeppathroot = false) {
         return parent::remove_directory($path, $keeppathroot);
     }
+
+    /**
+     * Provides access to the protected method.
+     */
+    public function create_directory_precheck($path) {
+        return parent::create_directory_precheck($path);
+    }
 }
 
 
@@ -284,4 +291,13 @@ class mdeploytest extends PHPUnit_Framework_TestCase {
         $this->assertTrue($worker->remove_directory($root.'/c'));
         $this->assertFalse(is_dir($root.'/c'));
     }
+
+    public function test_create_directory_precheck() {
+        $worker = testable_worker::instance();
+
+        $root = sys_get_temp_dir().'/'.uniqid('mdeploytest', true);
+        $this->assertFalse(file_exists($root));
+        $this->assertTrue($worker->create_directory_precheck($root));
+        $this->assertFalse(file_exists($root)); // The precheck is supposed to remove it again.
+    }
 }