Better exit codes and error messages for auto command

This commit is contained in:
Niklas Keller
2016-06-04 19:25:46 +02:00
parent 0722e104d4
commit c4d15e2e26

View File

@@ -12,6 +12,15 @@ use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
class Auto implements Command { class Auto implements Command {
const EXIT_CONFIG_ERROR = 1;
const EXIT_SETUP_ERROR = 2;
const EXIT_ISSUANCE_ERROR = 3;
const EXIT_ISSUANCE_PARTIAL = 4;
const EXIT_ISSUANCE_OK = 5;
const STATUS_NO_CHANGE = 0;
const STATUS_RENEWED = 1;
private $climate; private $climate;
public function __construct(CLImate $climate) { public function __construct(CLImate $climate) {
@@ -37,23 +46,23 @@ class Auto implements Command {
); );
} catch (FilesystemException $e) { } catch (FilesystemException $e) {
$this->climate->error("Config file ({$configPath}) not found."); $this->climate->error("Config file ({$configPath}) not found.");
yield new CoroutineResult(1); yield new CoroutineResult(self::EXIT_CONFIG_ERROR);
return; return;
} catch (ParseException $e) { } catch (ParseException $e) {
$this->climate->error("Config file ({$configPath}) had an invalid format and couldn't be parsed."); $this->climate->error("Config file ({$configPath}) had an invalid format and couldn't be parsed.");
yield new CoroutineResult(1); yield new CoroutineResult(self::EXIT_CONFIG_ERROR);
return; return;
} }
if (!isset($config["email"])) { if (!isset($config["email"])) {
$this->climate->error("Config file ({$configPath}) didn't have a 'email' set."); $this->climate->error("Config file ({$configPath}) didn't have a 'email' set.");
yield new CoroutineResult(2); yield new CoroutineResult(self::EXIT_CONFIG_ERROR);
return; return;
} }
if (!isset($config["certificates"]) || !is_array($config["certificates"])) { if (!isset($config["certificates"]) || !is_array($config["certificates"])) {
$this->climate->error("Config file ({$configPath}) didn't have a 'certificates' section that's an array."); $this->climate->error("Config file ({$configPath}) didn't have a 'certificates' section that's an array.");
yield new CoroutineResult(2); yield new CoroutineResult(self::EXIT_CONFIG_ERROR);
return; return;
} }
@@ -77,13 +86,14 @@ class Auto implements Command {
$this->climate->error($command); $this->climate->error($command);
$this->climate->br()->out($result->out); $this->climate->br()->out($result->out);
$this->climate->br()->error($result->err); $this->climate->br()->error($result->err);
yield new CoroutineResult(3); yield new CoroutineResult(self::EXIT_SETUP_ERROR);
return; return;
} }
$certificateChunks = array_chunk($config["certificates"], 10); $certificateChunks = array_chunk($config["certificates"], 10, true);
$errors = []; $errors = [];
$values = [];
foreach ($certificateChunks as $certificateChunk) { foreach ($certificateChunks as $certificateChunk) {
$promises = []; $promises = [];
@@ -92,18 +102,30 @@ class Auto implements Command {
$promises[] = \Amp\resolve($this->checkAndIssue($certificate, $server, $storage)); $promises[] = \Amp\resolve($this->checkAndIssue($certificate, $server, $storage));
} }
list($errors) = (yield \Amp\any($promises)); list($chunkErrors, $chunkValues) = (yield \Amp\any($promises));
$errors = array_merge($errors, $errors);
$errors += $chunkErrors;
$values += $chunkValues;
} }
if (!empty($errors)) { $status = [
"no_change" => count(array_filter($values, function($value) { return $value === self::STATUS_NO_CHANGE; })),
"renewed" => count(array_filter($values, function($value) { return $value === self::STATUS_RENEWED; })),
"failure" => count($errors),
];
if ($status["failure"] > 0) {
foreach ($errors as $i => $error) { foreach ($errors as $i => $error) {
$certificate = $config["certificates"][$i]; $certificate = $config["certificates"][$i];
$this->climate->error("Issuance for the following domains failed: " . implode(", ", array_keys($this->toDomainPathMap($certificate["paths"])))); $this->climate->error("Issuance for the following domains failed: " . implode(", ", array_keys($this->toDomainPathMap($certificate["paths"]))));
$this->climate->error("Reason: {$error}"); $this->climate->error("Reason: {$error}");
} }
yield new CoroutineResult(3); $exitCode = $status["renewed"] > 0
? self::EXIT_ISSUANCE_PARTIAL
: self::EXIT_ISSUANCE_ERROR;
yield new CoroutineResult($exitCode);
return; return;
} }
} }
@@ -134,10 +156,11 @@ class Auto implements Command {
$command = implode(" ", array_map("escapeshellarg", $args)); $command = implode(" ", array_map("escapeshellarg", $args));
$process = new Process($command); $process = new Process($command);
$result = (yield $process->exec()); $result = (yield $process->exec(Process::BUFFER_ALL));
if ($result->exit === 0) { if ($result->exit === 0) {
// No need for renewal // No need for renewal
yield new CoroutineResult(self::STATUS_NO_CHANGE);
return; return;
} }
@@ -170,16 +193,17 @@ class Auto implements Command {
$command = implode(" ", array_map("escapeshellarg", $args)); $command = implode(" ", array_map("escapeshellarg", $args));
$process = new Process($command); $process = new Process($command);
$result = (yield $process->exec()); $result = (yield $process->exec(Process::BUFFER_ALL));
if ($result->exit !== 0) { if ($result->exit !== 0) {
throw new AcmeException("Unexpected exit code ({$result->exit}) for '{$command}'."); throw new AcmeException("Unexpected exit code ({$result->exit}) for '{$command}'." . PHP_EOL . $result->out . PHP_EOL . PHP_EOL . $result->err);
} }
yield new CoroutineResult(self::STATUS_RENEWED);
return; return;
} }
throw new AcmeException("Unexpected exit code ({$result->exit}) for '{$command}'."); throw new AcmeException("Unexpected exit code ({$result->exit}) for '{$command}'." . PHP_EOL . $result->out . PHP_EOL . PHP_EOL . $result->err);
} }
private function toDomainPathMap(array $paths) { private function toDomainPathMap(array $paths) {