This dataobject represents a target environment that source code can be deployed to. Permissions are controlled by environment, see the various many-many relationships.
Inheritance: extends DataObject
Example #1
0
 public function __construct($controller, $name, DNEnvironment $environment, DNProject $project)
 {
     if ($environment->HasPipelineSupport()) {
         // Determine if commits are filtered
         $canBypass = Permission::check(DNRoot::DEPLOYNAUT_BYPASS_PIPELINE);
         $canDryrun = $environment->DryRunEnabled && Permission::check(DNRoot::DEPLOYNAUT_DRYRUN_PIPELINE);
         $commits = $environment->getDependentFilteredCommits();
         if (empty($commits)) {
             // There are no filtered commits, so show all commits
             $field = $this->buildCommitSelector($project);
             $validator = new DeployForm_CommitValidator();
         } elseif ($canBypass) {
             // Build hybrid selector that allows users to follow pipeline or use any commit
             $field = $this->buildCommitSelector($project, $commits);
             $validator = new DeployForm_CommitValidator();
         } else {
             // Restrict user to only select pipeline filtered commits
             $field = $this->buildPipelineField($commits);
             $validator = new DeployForm_PipelineValidator();
         }
         // Generate actions allowed for this user
         $actions = new FieldList(FormAction::create('startPipeline', "Begin the release process on " . $environment->Name)->addExtraClass('btn btn-primary')->setAttribute('onclick', "return confirm('This will begin a release pipeline. Continue?');"));
         if ($canDryrun) {
             $actions->push(FormAction::create('doDryRun', "Dry-run release process")->addExtraClass('btn btn-info')->setAttribute('onclick', "return confirm('This will begin a release pipeline, but with the following exclusions:\\n" . " - No messages will be sent\\n" . " - No capistrano actions will be invoked\\n" . " - No deployments or snapshots will be created.');"));
         }
         if ($canBypass) {
             $actions->push(FormAction::create('doDeploy', "Direct deployment (bypass pipeline)")->addExtraClass('btn btn-warning')->setAttribute('onclick', "return confirm('This will start a direct deployment, bypassing the pipeline " . "process in place.\\n\\nAre you sure this is necessary?');"));
         }
     } else {
         // without a pipeline simply allow any commit to be selected
         $field = $this->buildCommitSelector($project);
         $validator = new DeployForm_CommitValidator();
         $actions = new FieldList(FormAction::create('doDeploy', "Deploy to " . $environment->Name)->addExtraClass('btn btn-primary')->setAttribute('onclick', "return confirm('This will start a direct deployment.\\n\\nContinue?');"));
     }
     parent::__construct($controller, $name, new FieldList($field), $actions, $validator);
 }
 /**
  * Use snowcake to do the deployment
  */
 public function deploy(DNEnvironment $environment, $sha, DeploynautLogFile $log, DNProject $project, $leaveMaintenancePage = false)
 {
     $log->write(sprintf('Deploying "%s" to "%s"', $sha, $environment->getFullName()));
     if (!defined('SNOWCAKE_PATH')) {
         $log->write('SNOWCAKE_PATH is not defined');
         throw new RuntimeException('SNOWCAKE_PATH is not defined');
     }
     // Construct our snowcake command
     $name = $environment->SnowcakeName . '-' . substr($sha, 0, 8) . '-' . mt_rand();
     // Filter invalid characters out of $name (Value 'ssorg_uat-fdceda2e-1400725889-bake' at 'stackName' failed to satisfy constraint:
     // "Member must satisfy regular expression pattern: [a-zA-Z][-a-zA-Z0-9]*)"
     $name = str_replace('_', '-', $name);
     $command = sprintf('%s deploy %s %s %s', SNOWCAKE_PATH, $environment->SnowcakeName, $name, $sha);
     $log->write(sprintf('Running command: %s', $command));
     $process = new Process($command, dirname(dirname(SNOWCAKE_PATH)));
     $process->setTimeout(3600);
     $process->run(function ($type, $buffer) use($log) {
         $log->write($buffer);
     });
     if (!$process->isSuccessful()) {
         throw new RuntimeException($process->getErrorOutput());
     }
     $log->write(sprintf('Deploy of "%s" to "%s" finished', $sha, $environment->getFullName()));
 }
 /**
  * @param SS_HTTPRequest $request
  *
  * @return string
  */
 public function show(\SS_HTTPRequest $request)
 {
     $targetEnvironment = null;
     $targetEnvironmentId = $request->getVar('environmentId');
     if (!empty($targetEnvironmentId)) {
         $targetEnvironment = DNEnvironment::get()->byId((int) $targetEnvironmentId);
     }
     $refs = [];
     $prevDeploys = [];
     $uatEnvironment = $this->project->DNEnvironmentList()->filter('Usage', DNEnvironment::UAT)->first();
     $uatBuild = $uatEnvironment ? $uatEnvironment->CurrentBuild() : null;
     if ($uatBuild && $uatBuild->exists() && $targetEnvironment && $targetEnvironment->Usage === DNEnvironment::PRODUCTION) {
         $refs[self::REF_TYPE_FROM_UAT] = ['id' => self::REF_TYPE_FROM_UAT, 'label' => 'Promote the version currently on UAT', 'description' => 'Promote the version currently on UAT', 'promote_build' => $this->formatter->getDeploymentData($uatBuild)];
     }
     $refs[self::REF_TYPE_BRANCH] = ['id' => self::REF_TYPE_BRANCH, 'label' => 'Branch version', 'description' => 'Deploy the latest version of a branch', 'list' => $this->getGitBranches($this->project)];
     $refs[self::REF_TYPE_TAG] = ['id' => self::REF_TYPE_TAG, 'label' => 'Tag version', 'description' => 'Deploy a tagged release', 'list' => $this->getGitTags($this->project)];
     // @todo: the original was a tree that was keyed by environment, the
     // front-end dropdown needs to be changed to support that. brrrr.
     foreach ($this->getGitPrevDeploys($this->project) as $env) {
         foreach ($env as $deploy) {
             $prevDeploys[] = $deploy;
         }
     }
     $refs[self::REF_TYPE_PREVIOUS] = ['id' => self::REF_TYPE_PREVIOUS, 'label' => 'Redeploy a release that was previously deployed (to any environment)', 'description' => 'Deploy a previous release', 'list' => $prevDeploys];
     $refs[self::REF_TYPE_SHA] = ['id' => self::REF_TYPE_SHA, 'label' => 'Deploy a specific SHA', 'description' => 'Deploy a specific SHA'];
     $options = [];
     if ($targetEnvironment) {
         foreach ($targetEnvironment->getSupportedOptions() as $option) {
             $options[] = ['name' => $option->getName(), 'title' => $option->getTitle(), 'defaultValue' => $option->getDefaultValue()];
         }
     }
     // get the last time git fetch was run
     $lastFetchedDate = 'never';
     $lastFetchedAgo = null;
     $fetch = DNGitFetch::get()->filter(['ProjectID' => $this->project->ID, 'Status' => 'Finished'])->sort('LastEdited', 'DESC')->first();
     if ($fetch) {
         $lastFetchedDate = $fetch->obj('LastEdited')->Date();
         $lastFetchedAgo = $fetch->obj('LastEdited')->Ago();
     }
     return $this->getAPIResponse(['refs' => $refs, 'options' => $options, 'last_fetched_date' => $lastFetchedDate, 'last_fetched_ago' => $lastFetchedAgo], 200);
 }
 /**
  * @var array
  * @return \DeploymentStrategy
  */
 protected function createStrategy($options)
 {
     $strategy = $this->environment->Backend()->planDeploy($this->environment, $options);
     $data = $strategy->toArray();
     $interface = $this->project->getRepositoryInterface();
     if ($interface instanceof \ArrayData && $this->canCompareCodeVersions($interface, $data['changes'])) {
         $compareurl = sprintf('%s/compare/%s...%s', $interface->URL, $data['changes']['Code version']['from'], $data['changes']['Code version']['to']);
         $data['changes']['Code version']['compareUrl'] = $compareurl;
         // special case for .platform.yml field so we don't show a huge blob of changes,
         // but rather a link to where the .platform.yml changes were made in the code
         if (isset($data['changes']['.platform.yml other'])) {
             $data['changes']['.platform.yml other']['compareUrl'] = $compareurl;
             $data['changes']['.platform.yml other']['description'] = '';
         }
     }
     $this->extend('updateDeploySummary', $data);
     // Ensure changes that would have been updated are persisted in the object,
     // such as the comparison URL, so that they will be written to the Strategy
     // field on the DNDeployment object as part of {@link createDeployment()}
     $strategy->setChanges($data['changes']);
     return $strategy;
 }
Example #5
0
 /**
  * Sync the in-db project list with a list of file paths
  * @param array $paths Array of pathnames
  * @param boolean $remove Should obsolete environments be removed?
  */
 public function syncWithPaths($paths, $dryRun = false)
 {
     // Normalise paths in DB
     foreach ($this as $item) {
         $real = realpath($item->Filename);
         if ($real && $real != $item->Filename) {
             $item->Filename = $real;
             $item->write();
         }
     }
     foreach ($paths as $path) {
         $path = basename($path);
         if ($this->filter('Filename', $path)->count()) {
             continue;
         }
         $this->message('Adding "' . basename($path) . '" to db');
         if (!$dryRun) {
             $environment = DNEnvironment::create_from_path($path);
             $environment->ProjectID = $this->projectID;
             $environment->write();
         }
     }
 }
Example #6
0
 /**
  * Check if this member can move archive into the environment.
  *
  * @param DNEnvironment $targetEnv Environment to check.
  * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember();
  *
  * @return boolean true if $member can upload archives linked to this environment, false if they can't.
  */
 public function canMoveTo($targetEnv, $member = null)
 {
     if ($this->Environment()->Project()->ID != $targetEnv->Project()->ID) {
         // We don't permit moving snapshots between projects at this stage.
         return false;
     }
     if (!$member) {
         $member = Member::currentUser();
     }
     // Must be logged in to check permissions
     if (!$member) {
         return false;
     }
     // Admin can always move.
     if (Permission::checkMember($member, 'ADMIN')) {
         return true;
     }
     // Checks if the user can actually access the archive.
     if (!$this->canDownload($member)) {
         return false;
     }
     // Hooks into ArchiveUploaders permission to prevent proliferation of permission checkboxes.
     // Bypasses the quota check - we don't need to check for it as long as we move the snapshot within the project.
     return $targetEnv->ArchiveUploaders()->byID($member->ID) || $member->inGroups($targetEnv->ArchiveUploaderGroups());
 }
 /**
  * Utility function for triggering the db rebuild and flush.
  * Also cleans up and generates new error pages.
  * @param DeploynautLogFile $log
  */
 public function rebuild(DNEnvironment $environment, $log)
 {
     $name = $environment->getFullName();
     $command = $this->getCommand('deploy:migrate', 'web', $environment, null, $log);
     $command->run(function ($type, $buffer) use($log) {
         $log->write($buffer);
     });
     if (!$command->isSuccessful()) {
         $log->write(sprintf('Rebuild of "%s" failed: %s', $name, $command->getErrorOutput()));
         throw new RuntimeException($command->getErrorOutput());
     }
     $log->write(sprintf('Rebuild of "%s" done', $name));
 }
 /**
  * This is mostly copy-pasted from Anthill/Smoketest.
  *
  * @param \DNEnvironment $environment
  * @param \DeploynautLogFile $log
  * @return bool
  */
 protected function smokeTest(\DNEnvironment $environment, \DeploynautLogFile $log)
 {
     $url = $environment->getBareURL();
     $timeout = 600;
     $tick = 60;
     if (!$url) {
         $log->write('Skipping site accessible check: no URL found.');
         return true;
     }
     $start = time();
     $infoTick = time() + $tick;
     $log->write(sprintf('Waiting for "%s" to become accessible... (timeout: %smin)', $url, $timeout / 60));
     // configure curl so that curl_exec doesn't wait a long time for a response
     $ch = curl_init();
     curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
     curl_setopt($ch, CURLOPT_TIMEOUT, 5);
     curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
     curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
     curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
     // set a high number of max redirects (but not infinite amount) to avoid a potential infinite loop
     curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
     curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
     curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
     curl_setopt($ch, CURLOPT_URL, $url);
     curl_setopt($ch, CURLOPT_USERAGENT, 'Rainforest');
     $success = false;
     // query the site every second. Note that if the URL doesn't respond,
     // curl_exec will take 5 seconds to timeout (see CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT above)
     do {
         if (time() > $start + $timeout) {
             $log->write(sprintf(' * Failed: check for %s timed out after %smin', $url, $timeout / 60));
             return false;
         }
         $response = curl_exec($ch);
         // check the HTTP response code for HTTP protocols
         $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
         if ($status && !in_array($status, [500, 501, 502, 503, 504])) {
             $success = true;
         }
         // check for any curl errors, mostly for checking the response state of non-HTTP protocols,
         // but applies to checks of any protocol
         if ($response && !curl_errno($ch)) {
             $success = true;
         }
         // Produce an informational ticker roughly every $tick
         if (time() > $infoTick) {
             $message = [];
             // Collect status information from different sources.
             if ($status) {
                 $message[] = sprintf('HTTP status code is %s', $status);
             }
             if (!$response) {
                 $message[] = 'response is empty';
             }
             if ($error = curl_error($ch)) {
                 $message[] = sprintf('request error: %s', $error);
             }
             $log->write(sprintf(' * Still waiting: %s...', implode(', ', $message)));
             $infoTick = time() + $tick;
         }
         sleep(1);
     } while (!$success);
     curl_close($ch);
     $log->write(' * Success: site is accessible!');
     return true;
 }
Example #9
0
 /**
  * Provides a DNEnvironmentList of environments found in this project.
  */
 public function DNEnvironmentList()
 {
     return DNEnvironment::get()->filter('ProjectID', $this->ID)->setProjectID($this->ID);
 }
 /**
  * Validate a specific alert configuration from configuration YAML is correct.
  *
  * @param string $name
  * @param array $config
  * @param DNProject $project
  * @param DeploynautLogFile $log
  * @return boolean
  */
 public function validateAlert($name, $config, $project, $log)
 {
     // validate we have an environment set for the alert
     if (!isset($config['environment'])) {
         $log->write(sprintf('WARNING: Failed to configure alert "%s". Missing "environment" key in .alerts.yml. Skipped.', $name));
         return false;
     }
     // validate we have an environmentcheck suite name to check
     if (!isset($config['check_url'])) {
         $log->write(sprintf('WARNING: Failed to configure alert "%s". Missing "check_url" key in .alerts.yml. Skipped.', $name));
         return false;
     }
     // validate we have contacts for the alert
     if (!isset($config['contacts'])) {
         $log->write(sprintf('WARNING: Failed to configure alert "%s". Missing "contacts" key in .alerts.yml. Skipped.', $name));
         return false;
     }
     // validate that each value in the config is valid, build up a list of contacts we'll use later
     foreach ($config['contacts'] as $contactEmail) {
         // special case for ops
         if ($contactEmail == 'ops') {
             continue;
         }
         $contact = $project->AlertContacts()->filter('Email', $contactEmail)->first();
         if (!($contact && $contact->exists())) {
             $log->write(sprintf('WARNING: Failed to configure alert "%s". No such contact "%s". Skipped.', $name, $contactEmail));
             return false;
         }
     }
     // validate the environment specified in the alert actually exists
     if (!DNEnvironment::get()->filter('Name', $config['environment'])->first()) {
         $log->write(sprintf('WARNING: Failed to configure alert "%s". Invalid environment "%s" in .alerts.yml. Skipped.', $name, $config['environment']));
         return false;
     }
     return true;
 }
 /**
  * @param string $action
  * @return string
  */
 public function Link($action = '')
 {
     return \Controller::join_links($this->environment->Link(), self::ACTION_APPROVALS, $action);
 }
Example #12
0
 /**
  * Return a dependent {@link DNEnvironment} based on this pipeline's dependent environment configuration.
  * @return DNEnvironment
  */
 public function getDependentEnvironment()
 {
     // dependent environment not available
     $projectName = $this->getConfigSetting('PipelineConfig', 'DependsOnProject');
     $environmentName = $this->getConfigSetting('PipelineConfig', 'DependsOnEnvironment');
     if (empty($projectName) || empty($environmentName)) {
         return null;
     }
     $project = DNProject::get()->filter('Name', $projectName)->first();
     if (!($project && $project->exists())) {
         throw new Exception(sprintf('Could not find dependent project "%s"', $projectName));
     }
     $environment = DNEnvironment::get()->filter(array('ProjectID' => $project->ID, 'Name' => $environmentName))->first();
     if (!($environment && $environment->exists())) {
         throw new Exception(sprintf('Could not find dependent environment "%s" in project "%s"', $environmentName, $projectName));
     }
     return $environment;
 }
Example #13
0
 /**
  * Used by the sync task
  *
  * @param string $path
  * @return \DNEnvironment
  */
 public static function create_from_path($path)
 {
     $e = new DNEnvironment();
     $e->Filename = $path;
     $e->Name = basename($e->Filename, '.rb');
     // add each administrator member as a deployer of the new environment
     $adminGroup = Group::get()->filter('Code', 'administrators')->first();
     if ($adminGroup && $adminGroup->exists()) {
         foreach ($adminGroup->Members() as $member) {
             $e->Deployers()->add($member);
         }
     }
     return $e;
 }
Example #14
0
 public function onAfterWrite()
 {
     parent::onAfterWrite();
     if ($this->Usage == 'Production' || $this->Usage == 'UAT') {
         $conflicting = DNEnvironment::get()->filter('ProjectID', $this->ProjectID)->filter('Usage', $this->Usage)->exclude('ID', $this->ID);
         foreach ($conflicting as $otherEnvironment) {
             $otherEnvironment->Usage = 'Unspecified';
             $otherEnvironment->write();
         }
     }
 }
Example #15
0
 /**
  * Used by the sync task
  *
  * @param string $path
  * @return \DNEnvironment
  */
 public static function create_from_path($path)
 {
     $e = DNEnvironment::create();
     $e->Filename = $path;
     $e->Name = basename($e->Filename, '.rb');
     // add each administrator member as a deployer of the new environment
     $adminGroup = Group::get()->filter('Code', 'administrators')->first();
     $e->DeployerGroups()->add($adminGroup);
     return $e;
 }
 public function ping(\DNEnvironment $environment, DeploynautLogFile $log, DNProject $project)
 {
     $log->write(sprintf('Ping "%s"', $environment->getFullName()));
 }
 public function run($request)
 {
     $args = $request->getVar('args');
     $dryRun = $args && in_array('--dry-run', $args);
     $log = function ($message) {
         $message = sprintf('[%s] ', date('Y-m-d H:i:s')) . $message;
         echo $message . PHP_EOL;
     };
     if (!Director::is_cli()) {
         $log('This task must be run under the command line');
         return;
     }
     if ($dryRun) {
         $log('Running in dry-run mode. No data will be deleted');
     }
     $count = 0;
     foreach (DNEnvironment::get() as $environment) {
         $project = $environment->Project();
         if (!$project || !$project->exists()) {
             $log(sprintf('Environment (ID %s, Name: %s, Created: %s) is linked to a non-existent project. Deleting', $environment->ID, $environment->Name, $environment->Created));
             if (!$dryRun) {
                 $environment->delete();
                 $environment->destroy();
             }
             $count++;
         }
     }
     foreach (DNDeployment::get() as $deployment) {
         $environment = $deployment->Environment();
         if (!$environment || !$environment->exists()) {
             $log(sprintf('Deployment (ID %s, Created: %s) is linked to a non-existent environment. Deleting', $deployment->ID, $deployment->Created));
             if (!$dryRun) {
                 $deployment->delete();
                 $deployment->destroy();
             }
             $count++;
         }
     }
     foreach (DNDataTransfer::get() as $transfer) {
         $environment = $transfer->Environment();
         if (!$environment || !$environment->exists()) {
             $log(sprintf('Data transfer (ID %s, Created: %s) is linked to a non-existent environment. Deleting', $transfer->ID, $transfer->Created));
             if (!$dryRun) {
                 $transfer->delete();
                 $transfer->destroy();
             }
             $count++;
         }
     }
     foreach (DNDataArchive::get() as $archive) {
         $environment = $archive->Environment();
         if (!$environment || !$environment->exists()) {
             $log(sprintf('Archive (ID %s, Created: %s) is linked to a non-existent environment. Deleting', $archive->ID, $archive->Created));
             if (!$dryRun) {
                 $archive->delete();
                 $archive->destroy();
             }
             $count++;
         }
     }
     foreach (DNGitFetch::get() as $fetch) {
         $project = $fetch->Project();
         if (!$project || !$project->exists()) {
             $log(sprintf('Git fetch (ID %s, Created: %s) is linked to a non-existent project. Deleting', $fetch->ID, $fetch->Created));
             if (!$dryRun) {
                 $fetch->delete();
                 $fetch->destroy();
             }
             $count++;
         }
     }
     foreach (DNPing::get() as $ping) {
         $environment = $ping->Environment();
         if (!$environment || !$environment->exists()) {
             $log(sprintf('Ping (ID %s, Created: %s) is linked to a non-existent environment. Deleting', $ping->ID, $ping->Created));
             if (!$dryRun) {
                 $ping->delete();
                 $ping->destroy();
             }
             $count++;
         }
     }
     $log(sprintf('Finished. Processed %s records', $count));
 }
Example #18
0
 /**
  * 
  */
 public function testGetConfigFilename()
 {
     $expected = $this->envPath . '/testproject/uat.rb';
     $this->assertEquals($expected, $this->env->getConfigFilename());
 }
Example #19
0
 public function onAfterWrite()
 {
     parent::onAfterWrite();
     if ($this->Usage === self::PRODUCTION || $this->Usage === self::UAT) {
         $conflicting = DNEnvironment::get()->filter('ProjectID', $this->ProjectID)->filter('Usage', $this->Usage)->exclude('ID', $this->ID);
         foreach ($conflicting as $otherEnvironment) {
             $otherEnvironment->Usage = self::UNSPECIFIED;
             $otherEnvironment->write();
         }
     }
 }