/**
  * {@inheritdoc}
  */
 public function run(JobInterface $job, $data)
 {
     // Data format:
     // i) array('patch_file' => '...', 'patch_dir' => '...')
     // or
     // iii) array(array(...), array(...))
     // Normalize data to the third format, if necessary
     $data = count($data) == count($data, COUNT_RECURSIVE) ? [$data] : $data;
     $job->getOutput()->writeln("<info>Entering setup_patch().</info>");
     foreach ($data as $key => $details) {
         if (empty($details['patch_file'])) {
             $job->errorOutput("Error", "No valid patch file provided for the patch command.");
             return;
         }
         $workingdir = realpath($job->getWorkingDir());
         $patchfile = $details['patch_file'];
         $patchdir = !empty($details['patch_dir']) ? $details['patch_dir'] : $workingdir;
         // Validate target directory.
         if (!($directory = $this->validate_directory($job, $patchdir))) {
             // Invalid checkout directory
             $job->errorOutput("Error", "The patch directory <info>{$directory}</info> is invalid.");
             return;
         }
         $cmd = "patch -p1 -i {$patchfile} -d {$directory}";
         exec($cmd, $cmdoutput, $result);
         if ($result !== 0) {
             // The command threw an error.
             $job->errorOutput("Patch failed", "The patch attempt returned an error.");
             $job->getOutput()->writeln($cmdoutput);
             // TODO: Pass on the actual return value for the patch attempt
             return;
         }
         $job->getOutput()->writeln("<comment>Patch <options=bold>{$patchfile}</options=bold> applied to directory <options=bold>{$directory}</options=bold></comment>");
     }
 }
 protected function setupCheckoutGit(JobInterface $job, $details)
 {
     $job->getOutput()->writeln("<info>Entering setup_checkout_git().</info>");
     $repo = isset($details['repo']) ? $details['repo'] : 'git://drupalcode.org/project/drupal.git';
     $gitbranch = isset($details['branch']) ? $details['branch'] : 'master';
     $gitdepth = isset($details['depth']) ? $details['depth'] : NULL;
     $workingdir = $job->getWorkingDir();
     $checkoutdir = isset($details['checkout_dir']) ? $details['checkout_dir'] : $workingdir;
     // TODO: Ensure we don't end up with double slashes
     // Validate target directory.  Must be within workingdir.
     if (!($directory = $this->validate_directory($job, $checkoutdir))) {
         // Invalid checkout directory
         $job->errorOutput("Error", "The checkout directory <info>{$directory}</info> is invalid.");
         return;
     }
     $job->getOutput()->writeln("<comment>Performing git checkout of {$repo} {$gitbranch} branch to {$directory}.</comment>");
     $cmd = "git clone -b {$gitbranch} {$repo} {$directory}";
     if (!is_null($gitdepth)) {
         $cmd .= " --depth={$gitdepth}";
     }
     exec($cmd, $cmdoutput, $result);
     if ($result !== 0) {
         // Git threw an error.
         $job->errorOutput("Checkout failed", "The git checkout returned an error.");
         // TODO: Pass on the actual return value for the git checkout
         return;
     }
     $job->getOutput()->writeln("<comment>Checkout complete.</comment>");
 }
 protected function validate_directory(JobInterface $job, $dir)
 {
     // Validate target directory.  Must be within workingdir.
     $working_dir = $job->getWorkingDir();
     $true_dir = realpath($dir);
     if (!empty($true_dir)) {
         if ($true_dir == realpath($working_dir)) {
             // Passed directory is the root working directory.
             return $true_dir;
         } elseif (strpos($true_dir, realpath($working_dir)) === 0) {
             // Passed directory is an existing subdirectory within the working path.
             return $true_dir;
         }
     }
     // Assume the Passed directory is a subdirectory of the working, without the working prefix.  Construct the full path.
     if (!(strpos($dir, realpath($working_dir)) === 0)) {
         $dir = $working_dir . "/" . $dir;
     }
     $directory = realpath($dir);
     // TODO: Ensure we don't have double slashes
     // Check whether this is a pre-existing directory
     if ($directory === FALSE) {
         // Directory doesn't exist. Create and then validate.
         mkdir($dir, 0777, TRUE);
         $directory = realpath($dir);
     }
     // Validate that resulting directory is still within the working directory path.
     if (!strpos(realpath($directory), realpath($working_dir)) === 0) {
         // Invalid checkout directory
         $job->errorOutput("Error", "The checkout directory <info>{$directory}</info> is invalid.");
         return FALSE;
     }
     // Return the updated directory value.
     return $directory;
 }
 protected function buildImageNames($data, JobInterface $job)
 {
     $images = [];
     foreach ($data as $key => $php_version) {
         $images["web-{$php_version}"]['image'] = "drupalci/web-{$php_version}";
         $job->getOutput()->writeln("<info>Adding image: <options=bold>drupalci/web-{$php_version}</options=bold></info>");
     }
     return $images;
 }
 public function validateImageNames($containers, JobInterface $job)
 {
     // Verify that the appropriate container images exist
     $job->getOutput()->writeln("<comment>Validating container images exist</comment>");
     $docker = $job->getDocker();
     $manager = $docker->getImageManager();
     foreach ($containers as $key => $image_name) {
         $name = $image_name['image'];
         try {
             $image = $manager->find($name);
         } catch (ImageNotFoundException $e) {
             $job->errorOutput("Failed", "Required container image <options=bold>'{$name}'</options=bold> not found.");
             // TODO: Robust error handling.
             return FALSE;
         }
         $id = substr($image->getID(), 0, 8);
         $job->getOutput()->writeln("<comment>Found image <options=bold>{$name}</options=bold> with ID <options=bold>{$id}</options=bold></comment>");
     }
     return TRUE;
 }
 /**
  * {@inheritdoc}
  */
 public function run(JobInterface $job, $data = NULL)
 {
     // TODO: Ensure that all 'required' arguments are defined
     $definition = $job->getDefinition();
     $failflag = FALSE;
     foreach ($job->getRequiredArguments() as $env_var => $yaml_loc) {
         if (!empty($job->getBuildVars()[$env_var])) {
             continue;
         } else {
             // Look for the appropriate array structure in the job definition file
             // eg: environment:db
             $keys = explode(":", $yaml_loc);
             $eval = $definition;
             foreach ($keys as $key) {
                 if (!empty($eval[$key])) {
                     // Check if the next level contains a numeric [0] key, indicating a
                     // nested array of parameters.  If found, skip this level of the
                     // array.
                     if (isset($eval[$key][0])) {
                         $eval = $eval[$key][0];
                     } else {
                         $eval = $eval[$key];
                     }
                 } else {
                     // Missing a required key in the array key chain
                     $failflag = TRUE;
                     break;
                 }
             }
             if (!$failflag) {
                 continue;
             }
         }
         // If processing gets to here, we're missing a required variable
         $job->errorOutput("Failed", "Required test parameter <options=bold>'{$env_var}'</options=bold> not found in environment variables, and <options=bold>'{$yaml_loc}'</options=bold> not found in job definition file.");
         // TODO: Graceful handling of failed exit states
         return FALSE;
     }
     // TODO: Strip out arguments which are not defined in the 'Available' arguments array
     return TRUE;
 }
 /**
  * {@inheritdoc}
  */
 public function run(JobInterface $job, $data)
 {
     // Data format:
     // i) array('url' => '...', 'fetch_dir' => '...')
     // or
     // iii) array(array(...), array(...))
     // Normalize data to the third format, if necessary
     $data = count($data) == count($data, COUNT_RECURSIVE) ? [$data] : $data;
     $job->getOutput()->writeln("<info>Entering setup_fetch().</info>");
     foreach ($data as $key => $details) {
         // URL and target directory
         // TODO: Ensure $details contains all required parameters
         if (empty($details['url'])) {
             $job->errorOutput("Error", "No valid target file provided for fetch command.");
             return;
         }
         $url = $details['url'];
         $workingdir = realpath($job->getWorkingDir());
         $fetchdir = !empty($details['fetch_dir']) ? $details['fetch_dir'] : $workingdir;
         if (!($directory = $this->validate_directory($job, $fetchdir))) {
             // Invalid checkout directory
             $job->errorOutput("Error", "The fetch directory <info>{$directory}</info> is invalid.");
             return;
         }
         $info = pathinfo($url);
         $destfile = $directory . "/" . $info['basename'];
         $contents = file_get_contents($url);
         if ($contents === FALSE) {
             $job->errorOutput("Error", "An error was encountered while attempting to fetch <info>{$url}</info>.");
             return;
         }
         if (file_put_contents($destfile, $contents) === FALSE) {
             $job->errorOutput("Error", "An error was encountered while attempting to write <info>{$url}</info> to <info>{$directory}</info>");
             return;
         }
         $job->getOutput()->writeln("<comment>Fetch of <options=bold>{$url}</options=bold> to <options=bold>{$destfile}</options=bold> complete.</comment>");
     }
 }
 /**
  * {@inheritdoc}
  */
 public function run(JobInterface $job, $data)
 {
     // Data format: 'command [arguments]' or array('command [arguments]', 'command [arguments]')
     // $data May be a string if one version required, or array if multiple
     // Normalize data to the array format, if necessary
     $data = is_array($data) ? $data : [$data];
     $docker = $job->getDocker();
     $manager = $docker->getContainerManager();
     if (!empty($data)) {
         // Check that we have a container to execute on
         $configs = $job->getExecContainers();
         foreach ($configs as $type => $containers) {
             foreach ($containers as $container) {
                 $id = $container['id'];
                 $instance = $manager->find($id);
                 $short_id = substr($id, 0, 8);
                 $job->getOutput()->writeln("<info>Executing on container instance {$short_id}:</info>");
                 foreach ($data as $cmd) {
                     $job->getOutput()->writeln("<fg=magenta>{$cmd}</fg=magenta>");
                     $exec = explode(" ", $cmd);
                     $exec_id = $manager->exec($instance, $exec, TRUE, TRUE, TRUE, TRUE);
                     $job->getOutput()->writeln("<info>Command created as exec id " . substr($exec_id, 0, 8) . "</info>");
                     $result = $manager->execstart($exec_id, function ($output, $type) use($job) {
                         if ($type === 1) {
                             $job->getOutput()->writeln("<info>{$output}</info>");
                         } else {
                             $job->errorOutput('Error', $output);
                         }
                     });
                     //Response stream is never read you need to simulate a wait in order to get output
                     $result->getBody()->getContents();
                     $job->getOutput()->writeln((string) $result);
                 }
             }
         }
     }
 }
 protected function buildvarsToDefinition(JobInterface $job)
 {
     $buildvars = $job->getBuildVars();
     $job_definition = $job->jobDefinition;
     // Process dependencies
     if (!empty($buildvars['DCI_DEPENDENCIES'])) {
         // Format: module1,module2,module3
         $dependencies = explode(',', trim($buildvars['DCI_DEPENDENCIES'], '"'));
         foreach ($dependencies as $dependency) {
             // TODO: Remove the hardcoded git.drupal.org!!!
             // Perhaps we extend this with a DrupalConfigurator class?
             $directory = 'sites/all/modules';
             // TODO: We can't assume a branch here. Need to determine the Drupal version earlier!
             $job_definition['setup']['checkout'][] = array('protocol' => 'git', 'repo' => "git://git.drupal.org/project/{$dependency}.git", 'branch' => 'master', 'checkout_dir' => $directory);
         }
     }
     // Process GIT dependencies
     if (!empty($buildvars['DCI_DEPENDENCIES_GIT'])) {
         // Format: gitrepo1,branch;gitrepo2,branch;
         $dependencies = explode(';', trim($buildvars['DCI_DEPENDENCIES_GIT'], '"'));
         foreach ($dependencies as $dependency) {
             if (!empty($dependency)) {
                 list($repo, $branch) = explode(',', $dependency);
                 // TODO: Remove this hardcoded drupalism!!!
                 $directory = 'sites/all/modules/' . basename(parse_url($repo, PHP_URL_PATH), ".git");
                 $job_definition['setup']['checkout'][] = array('protocol' => 'git', 'repo' => $repo, 'branch' => $branch, 'checkout_dir' => $directory);
             }
         }
     }
     $job->job_definition = $job_definition;
     /*
     ### ./run.sh Options
     # Any valid Drupal branch or tag, like 8.0.x, 7.x or 7.30:
     DCI_DrupalBRANCH="8.0.x"
     # The identifier used by jenkins to name the Drupal docroot where all is stored:
     DCI_IDENTIFIER="build_$(date +%Y_%m_%d_%H%M%S)" # Only [a-z0-9-_.] allowed
     # The place where Drupal repos and DrupalDocRoot identifiers are kept:
     DCI_REPODIR="$HOME/testbotdata"
     # Request the runner to update the Drupal local repo before local cloning:
     DCI_UPDATEREPO="false"  # true to force repos update
     # By default we put the Drupal repo and docroots on the same place, but you can have BUILDSDIR elsewhere:
     DCI_BUILDSDIR="$DCI_REPODIR"
     # Same for the workspace:
     DCI_WORKSPACE="$DCI_BUILDSDIR/$DCI_IDENTIFIER/"
     # Install modules:
     DCI_DEPENDENCIES=""     # module1,module2,module2...
     # Git clone sandboxes:
     DCI_DEPENDENCIES_GIT="" # gitrepo1,branch;gitrepo2,branch;...
     # Download tgz modules:
     DCI_DEPENDENCIES_TGZ="" # module1_url.tgz,module1_url.tgz,...
     # Download and patch one or several patches:
     DCI_PATCH=""            # patch_url,apply_dir;patch_url,apply_dir;...
     */
 }
 public function validate_checkout_dir(JobInterface $job)
 {
     $arguments = $job->getBuildVars();
     $path = realpath($arguments['DCI_CheckoutDir']);
     $tmpdir = sys_get_temp_dir();
     if (strpos($path, $tmpdir) === 0) {
         return TRUE;
     }
     return FALSE;
 }