protected function setupCheckoutLocal(JobInterface $job, $details) { $job->getOutput()->writeln("<info>Entering setupCheckoutLocal().</info>"); $srcdir = isset($details['srcdir']) ? $details['srcdir'] : './'; $workingdir = $job->getWorkingDir(); $checkoutdir = isset($details['checkout_dir']) ? $details['checkout_dir'] : $workingdir; // TODO: Ensure we don't end up with double slashes // Validate source directory $source = realpath($srcdir); if (empty($source)) { $job->errorOutput("Error", "The source directory <info>{$srcdir}</info> does not exist."); return; } // Validate target directory. Must be within workingdir. if (!($directory = $this->validate_directory($job, $checkoutdir))) { // Invalidate checkout directory $job->errorOutput("Error", "The checkout directory <info>{$directory}</info> is invalid."); return; } $job->getOutput()->write("<comment>Copying files from <options=bold>{$srcdir}</options=bold> to the local checkout directory <options=bold>{$directory}</options=bold> ... </comment>"); exec("cp -r {$srcdir}/* {$directory}", $cmdoutput, $result); if (is_null($result)) { $job->errorOutput("Failed", "Error encountered while attempting to copy code to the local checkout directory."); return; } $job->getOutput()->writeln("<comment>DONE</comment>"); }
/** * {@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 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 create_tempdir(JobInterface $job, $dir = NULL, $prefix = NULL) { // PHP seems to have trouble creating temporary unique directories with the appropriate permissions, // So we create a temp file to get the unique filename, then mkdir a directory in it's place. $prefix = empty($prefix) ? "drupalci-" : $prefix; $tmpdir = $dir && is_dir($dir) ? $dir : sys_get_temp_dir(); $tempname = tempnam($tmpdir, $prefix); if (empty($tempname)) { // Unable to create temp filename $job->errorOutput("Error", "Unable to create temporary directory inside of {$tmpdir}."); return; } $tempdir = $tempname; unlink($tempname); if (mkdir($tempdir)) { return $tempdir; } else { // Unable to create temp directory $job->errorOutput("Error", "Error encountered while attempting to create temporary directory {$tempdir}."); return; } }
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); } } } } }
/** * {@inheritdoc} */ public function run(JobInterface $job, $data = NULL) { // Get and parse test definitions // DrupalCI jobs are controlled via a hierarchy of configuration settings, which define the behaviour of the platform while running DrupalCI jobs. This hierarchy is defined as follows, which each level overriding the previous: // 1. Out-of-the-box DrupalCI defaults // 2. Local overrides defined in ~/.drupalci/config // 3. 'DCI_' namespaced environment variable overrides // 4. Test-specific overrides passed inside a DrupalCI test definition (e.g. .drupalci.yml) // 5. Custom overrides located inside a test definition defined via the $source variable when calling this function. $confighelper = new ConfigHelper(); // Load job defaults $platform_args = $job->getPlatformDefaults(); $default_args = $job->getDefaultArguments(); if (!empty($default_args)) { $job->getOutput()->writeln("<comment>Loading build variables for this job type.</comment>"); } // Load DrupalCI local config overrides $local_args = $confighelper->getCurrentConfigSetParsed(); if (!empty($local_args)) { $job->getOutput()->writeln("<comment>Loading build variables from DrupalCI local config overrides.</comment>"); } // Load "DCI_ namespaced" environment variable overrides $environment_args = $confighelper->getCurrentEnvVars(); if (!empty($environment_args)) { $job->getOutput()->writeln("<comment>Loading build variables from namespaced environment variable overrides.</comment>"); } // Load command line arguments // TODO: Routine for loading command line arguments. // TODO: How do we pull arguments off the drupalci command, when in a job class? // $cli_args = $somehelper->loadCLIargs(); $cli_args = array(); if (!empty($cli_args)) { $job->getOutput()->writeln("<comment>Loading test parameters from command line arguments.</comment>"); } // Create temporary config array to use in determining the definition file source $config = $cli_args + $environment_args + $local_args + $default_args + $platform_args; // Load any build vars defined in the job definition file // Retrieve test definition file if (isset($source)) { $config['explicit_source'] = $source; } $definition_file = $this->getDefinitionFile($config); $definition_args = array(); // Load test definition file if (!empty($definition_file)) { $job->getOutput()->writeln("<comment>Loading test parameters from build file: </comment><info>{$definition_file}</info>"); $jobdef = new JobDefinition(); $result = $jobdef->load($definition_file); if ($result == -1) { // Error loading definition file. $job->errorOutput("Failed", "Unable to parse build file."); // TODO: Robust error handling return; } $job_definition = $jobdef->getParameters(); if (empty($job_definition)) { $job_definition = array(); $definition_args = array(); } else { $definition_args = !empty($job_definition['build_vars']) ? $job_definition['build_vars'] : array(); } $job->setDefinition($job_definition); } $config = $cli_args + $definition_args + $environment_args + $local_args + $default_args + $platform_args; // Set initial build variables $buildvars = $job->getBuildVars(); $job->setBuildVars($buildvars + $config); // Map relevant build variables into the job definition array // $this->buildvarsToDefinition($job); return; }