Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 83 additions & 9 deletions app/Services/CloudFormer/Sparkle.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class Sparkle implements TemplateEngine {

const REMOTE_HIGHCORE = 'highcore';
const APP_SSH_PRIVKEY = 'app/id_rsa';
const TEMPLATE_LOCK_TIMEOUT = 10 * 1000000; //10 seconds
const KEYGEN_TIMEOUT = 5000;

protected $authProvider;

Expand All @@ -26,7 +28,7 @@ protected function ensureKeyExists($storage_path)
if(!file_exists($storage_path)){
$cmd = sprintf('ssh-keygen -b 2048 -t rsa -f %s -q -N ""', escapeshellarg($storage_path));
$process = new Process($cmd);
$process->setTimeout(5000);
$process->setTimeout(static::KEYGEN_TIMEOUT);
$process->run();
if ($process->getExitCode() !== 0) {
$text = 'Exit code: ' . $process->getExitCode();
Expand All @@ -48,13 +50,25 @@ public function updateTemplate(Template $template)

$repo = Repository::openAuthenticated($repoPath, $this->getAuthProvider());

$repo->isInitialized() or $repo->init();
$repo->setRemote(self::REMOTE_HIGHCORE, $template->repository);
$repo->fetch(self::REMOTE_HIGHCORE);
$repo->reset(sprintf('%s/%s', self::REMOTE_HIGHCORE, $template->refspec), ResetCommand::RESET_HARD, true);
$repo->updateSubmodule(true, true, true);
return $this->exclusiveFor($template, function() use($template, $repo){
$repo->isInitialized() or $repo->init();
$repo->setRemote(self::REMOTE_HIGHCORE, $template->repository);
$repo->fetch(self::REMOTE_HIGHCORE);

return $repo->getCommit();
try{
$currentRevision = $repo->getCommit()->getSha(); //git show fails when missing refs/heads/master
}catch(\RuntimeException $e){
$currentRevision = null;
}

$targetRefspec = sprintf('%s/%s', self::REMOTE_HIGHCORE, $template->refspec);

if($currentRevision != $repo->getCommit($targetRefspec)->getSha()) {
$repo->reset($targetRefspec, ResetCommand::RESET_HARD, true);
$repo->updateSubmodule(true, true, true);
}
return $repo;
});
}

/**
Expand All @@ -76,8 +90,10 @@ public function getTemplate(Template $template, $stack_definition, $update = tru
Log::debug(sprintf('Calling sparke for template %s in project %s: %s',
$template->name, $template->project->name, $command->getCommandLine()));

//$exitCode = 0; //$command->run();
$exitCode = $command->run();
$exitCode = $this->exclusiveFor($template, function() use ($command) {
return $command->run();
});

if ($exitCode) {
throw new Exception($command->getErrorOutput());
}
Expand Down Expand Up @@ -128,6 +144,9 @@ public function getComponents(Template $template) {
private function getTemplatePath(Template $template) {
return storage_path("app/projects/{$template->id}-{$template->name}");
}
private function getLockPath(Template $template){
return storage_path(sprintf("app/locks/%d.lock", $template->id));
}

/**
* @inheritdoc
Expand Down Expand Up @@ -168,4 +187,59 @@ private function getAuthProvider()
}
return $this->authProvider;
}

/**
* @param Template $template
* @param callable $f
* @return mixed
* @throws Exception
*/
protected function exclusiveFor(Template $template, $f){
$lock = $this->acquireLock($template);
try {
$retval = $f();
}catch(\Exception $e){
$this->releaseLock($lock);
throw $e;
}
$this->releaseLock($lock);
return $retval;
}

protected function acquireLock(Template $template){
$handle = fopen($this->getLockPath($template), 'c');
if(!static::flock_t($handle, LOCK_EX, static::TEMPLATE_LOCK_TIMEOUT, 500)){
throw new \RuntimeException('Could not acquire Template repository lock at '.$this->getLockPath($template));
}
return $handle;
}

protected function releaseLock($handle){
fclose($handle);
}

private function flock_t ($lockfile, $locktype, $timeout_micro, $sleep_by_micro = 10000) {
if (!is_resource($lockfile)) {
throw new \InvalidArgumentException ('The $lockfile was not a file resource or the resource was closed.');
}
if ($sleep_by_micro < 1) {
throw new \InvalidArgumentException ('The $sleep_by_micro cannot be less than 1, or else an infinite loop.');
}
if ($timeout_micro < 1) {
$locked = flock ($lockfile, $locktype | LOCK_NB);
} else {
$count_micro = 0;
$locked = true;
while (!flock($lockfile, $locktype | LOCK_NB, $blocking)) {
if ($blocking AND (($count_micro += $sleep_by_micro) <= $timeout_micro)) {
usleep($sleep_by_micro);
} else {
$locked = false;
break;
}
}
}
return $locked;
}

}