diff --git a/resources/sql/autopatches/20150715.harbor.1.buildparam.sql b/resources/sql/autopatches/20150715.harbor.1.buildparam.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150715.harbor.1.buildparam.sql @@ -0,0 +1,6 @@ +ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_build + ADD buildParameters LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL; + +ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_build + ADD buildParametersHash VARCHAR(32) COLLATE {$COLLATE_TEXT} + NOT NULL DEFAULT '2c08d653e1ee60d55cd0da551026ea56'; diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -84,7 +84,7 @@ $targets = array(); foreach ($build_targets as $build_target) { $header = id(new PHUIHeaderView()) - ->setHeader($build_target->getName()) + ->setHeader($build_target->getNameWithMergedParameters($build)) ->setUser($viewer); $target_box = id(new PHUIObjectBoxView()) @@ -224,6 +224,9 @@ $properties = new PHUIPropertyListView(); $properties->addProperty( + pht('Original Name'), + $build_target->getName()); + $properties->addProperty( pht('Build Target ID'), $build_target->getID()); $properties->addProperty( @@ -519,12 +522,20 @@ $handles[$build->getBuildPlanPHID()]->renderLink()); $properties->addProperty( + pht('Build Name'), + $build->getNameWithMergedParameters()); + + $properties->addProperty( pht('Restarts'), $build->getBuildGeneration()); $properties->addProperty( pht('Status'), $this->getStatus($build)); + + $properties->addProperty( + pht('Parameters'), + $this->buildProperties($build->getBuildParameters())); } private function getStatus(HarbormasterBuild $build) { diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -182,7 +182,7 @@ $view_uri = $this->getApplicationURI('/build/'.$build->getID().'/'); $item = id(new PHUIObjectItemView()) ->setObjectName(pht('Build %d', $build->getID())) - ->setHeader($build->getName()) + ->setHeader($build->getNameWithMergedParameters()) ->setHref($view_uri); $status = $build->getBuildStatus(); @@ -191,6 +191,10 @@ HarbormasterBuild::getBuildStatusName($status)); $item->addAttribute(HarbormasterBuild::getBuildStatusName($status)); + $parameters = $build->getBuildParametersAsString(); + if ($parameters !== '') { + $item->addAttribute($parameters); + } if ($build->isRestarting()) { $item->addIcon('fa-repeat', pht('Restarting')); @@ -243,7 +247,7 @@ $status_name = HarbormasterBuildTarget::getBuildTargetStatusName($status); - $name = $target->getName(); + $name = $target->getNameWithMergedParameters($build); $target_list->addItem( id(new PHUIStatusItemView()) diff --git a/src/applications/harbormaster/step/HarbormasterRunBuildPlanBuildImplementation.php b/src/applications/harbormaster/step/HarbormasterRunBuildPlanBuildImplementation.php --- a/src/applications/harbormaster/step/HarbormasterRunBuildPlanBuildImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterRunBuildPlanBuildImplementation.php @@ -33,12 +33,49 @@ $name); } + public function parseParameters( + HarbormasterBuildTarget $build_target, + $text) { + + if (trim($text) === '') { + return array(); + } + + $variables = $build_target->getVariables(); + $text = $this->mergeVariables( + 'vsprintf', + $text, + $variables); + + $pairs = phutil_split_lines($text); + $attributes = array(); + + foreach ($pairs as $line) { + $kv = explode('=', $line, 2); + if (count($kv) === 0) { + continue; + } else if (count($kv) === 1) { + $attributes[$kv[0]] = true; + } else { + $attributes[$kv[0]] = trim($kv[1]); + } + } + + return $attributes; + } + public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { $target_plan_id = $this->getSetting('id'); + $parameters = self::parseParameters( + $build_target, + $this->getSetting('parameters')); + $parameters_hash = + HarbormasterBuild::getBuildParametersHashForArray($parameters); + // Get the target build plan type. $target_plan = id(new HarbormasterBuildPlanQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) @@ -64,7 +101,8 @@ // Find the build for that build plan (if it exists). $target_build = null; foreach ($other_builds as $other_build) { - if ($other_build->getBuildPlanPHID() === $target_plan->getPHID()) { + if ($other_build->getBuildPlanPHID() === $target_plan->getPHID() && + $other_build->getBuildParametersHash() === $parameters_hash) { $target_build = $other_build; break; } @@ -73,7 +111,9 @@ if ($target_build === null) { // There is no current build with this build plan on the buildable, // so now we're going to start one. - $target_build = $build->getBuildable()->applyPlan($target_plan); + $target_build = $build->getBuildable()->applyPlan( + $target_plan, + $parameters); } else { // If the build plan has failed on a previous run, restart it. switch ($target_build->getBuildStatus()) { @@ -163,6 +203,14 @@ 'type' => 'text', 'required' => true, ), + 'parameters' => array( + 'name' => pht('Build Plan Parameters'), + 'type' => 'textarea', + 'caption' => pht( + 'A newline separated list of parameters to pass into the build. '. + 'Each attribute should be specified in a key=value format.'), + 'monospace' => true, + ), ); } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -136,11 +136,19 @@ } } - public function applyPlan(HarbormasterBuildPlan $plan) { + public function applyPlan( + HarbormasterBuildPlan $plan, + array $parameters = null) { + + if ($parameters === null) { + $parameters = array(); + } + $viewer = PhabricatorUser::getOmnipotentUser(); $build = HarbormasterBuild::initializeNewBuild($viewer) ->setBuildablePHID($this->getPHID()) ->setBuildPlanPHID($plan->getPHID()) + ->setBuildParameters($parameters) ->setBuildStatus(HarbormasterBuild::STATUS_PENDING); $auto_key = $plan->getPlanAutoKey(); diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -9,6 +9,8 @@ protected $buildPlanPHID; protected $buildStatus; protected $buildGeneration; + protected $buildParameters; + protected $buildParametersHash; protected $planAutoKey; private $buildable = self::ATTACHABLE; @@ -138,10 +140,43 @@ } } - public static function initializeNewBuild(PhabricatorUser $actor) { + public static function initializeNewBuild( + PhabricatorUser $actor) { return id(new HarbormasterBuild()) ->setBuildStatus(self::STATUS_INACTIVE) - ->setBuildGeneration(0); + ->setBuildGeneration(0) + ->setBuildParameters(array()); + } + + public function setBuildParameters(array $parameters) { + $this->buildParameters = $parameters; + $this->buildParametersHash = + self::getBuildParametersHashForArray($parameters); + return $this; + } + + public function getBuildParameters() { + if ($this->buildParameters === null) { + return array(); + } + + return $this->buildParameters; + } + + public function getBuildParametersAsString() { + $strs = array(); + $parameters = $this->buildParameters; + if ($parameters === null) { + $parameters = array(); + } + foreach ($parameters as $key => $value) { + $strs[] = $key.'='.$value; + } + return implode(', ', $strs); + } + + public static function getBuildParametersHashForArray(array $parameters) { + return md5(print_r($parameters, true)); } public function delete() { @@ -156,9 +191,14 @@ protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, + self::CONFIG_SERIALIZATION => array( + 'buildParameters' => self::SERIALIZATION_JSON, + ), self::CONFIG_COLUMN_SCHEMA => array( 'buildStatus' => 'text32', 'buildGeneration' => 'uint32', + 'buildParameters' => 'text', + 'buildParametersHash' => 'text32', 'planAutoKey' => 'text32?', ), self::CONFIG_KEY_SCHEMA => array( @@ -200,6 +240,31 @@ return pht('Build'); } + public static function mergeParameters($pattern, $parameters) { + $regexp = '/\\$\\{(?P[a-zA-Z\\.]+)\\}/'; + + $matches = null; + preg_match_all($regexp, $pattern, $matches); + + $argv = array(); + foreach ($matches['name'] as $name) { + if (array_key_exists($name, $parameters)) { + $argv[] = '['.$parameters[$name].']'; + } + } + + $pattern = str_replace('%', '%%', $pattern); + $pattern = preg_replace($regexp, '%s', $pattern); + + return vsprintf($pattern, $argv); + } + + public function getNameWithMergedParameters() { + $name = $this->getName(); + $parameters = $this->getBuildParameters(); + return self::mergeParameters($name, $parameters); + } + public function attachBuildPlan( HarbormasterBuildPlan $build_plan = null) { $this->buildPlan = $build_plan; @@ -246,6 +311,11 @@ } public function retrieveVariablesFromBuild() { + $parameters = $this->getBuildParameters(); + if ($parameters === null) { + $parameters = array(); + } + $results = array( 'buildable.diff' => null, 'buildable.revision' => null, @@ -255,7 +325,7 @@ 'repository.uri' => null, 'step.timestamp' => null, 'build.id' => null, - ); + ) + $parameters; $buildable = $this->getBuildable(); $object = $buildable->getBuildableObject(); diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -195,6 +195,12 @@ } } + public function getNameWithMergedParameters(HarbormasterBuild $build) { + $name = $this->getName(); + $parameters = $build->getBuildParameters(); + return HarbormasterBuild::mergeParameters($name, $parameters); + } + private function getBuildTargetVariables() { return array( 'target.phid' => $this->getPHID(),