From 583318fa0be8e55248b22fd24de52e435abf48d3 Mon Sep 17 00:00:00 2001 From: Niklas Keller Date: Fri, 3 Jun 2016 19:38:47 +0200 Subject: [PATCH] Scan for config in /etc and ~, improve automation command --- src/Commands/Auto.php | 74 ++++++++++++++++++++++++----------------- src/Commands/Setup.php | 2 -- src/ConfigException.php | 5 +++ src/functions.php | 66 +++++++++++++++++++++++------------- 4 files changed, 90 insertions(+), 57 deletions(-) create mode 100644 src/ConfigException.php diff --git a/src/Commands/Auto.php b/src/Commands/Auto.php index c69d82e..7bfd5b1 100644 --- a/src/Commands/Auto.php +++ b/src/Commands/Auto.php @@ -8,6 +8,7 @@ use Amp\Process; use Kelunik\Acme\AcmeException; use League\CLImate\Argument\Manager; use League\CLImate\CLImate; +use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; class Auto implements Command { @@ -38,6 +39,10 @@ class Auto implements Command { $this->climate->error("Config file ({$configPath}) not found."); yield new CoroutineResult(1); return; + } catch (ParseException $e) { + $this->climate->error("Config file ({$configPath}) had an invalid format and couldn't be parsed."); + yield new CoroutineResult(1); + return; } if (!isset($config["email"])) { @@ -46,20 +51,12 @@ class Auto implements Command { return; } - if (!isset($config["certificates"])) { - $this->climate->error("Config file ({$configPath}) didn't have a 'certificates' section."); + if (!isset($config["certificates"]) || !is_array($config["certificates"])) { + $this->climate->error("Config file ({$configPath}) didn't have a 'certificates' section that's an array."); yield new CoroutineResult(2); return; } - if (isset($config["storage"])) { - $storage = $config["storage"]; - } - - if (isset($config["server"])) { - $server = $config["server"]; - } - $command = implode(" ", array_map("escapeshellarg", [ PHP_BINARY, $GLOBALS["argv"][0], @@ -73,27 +70,36 @@ class Auto implements Command { ])); $process = new Process($command); - $result = (yield $process->exec()); + $result = (yield $process->exec(Process::BUFFER_ALL)); if ($result->exit !== 0) { $this->climate->error("Registration failed ({$result->exit})"); $this->climate->error($command); + $this->climate->br()->out($result->out); + $this->climate->br()->error($result->err); yield new CoroutineResult(3); return; } - $promises = []; + $certificateChunks = array_chunk($config["certificates"], 10); - foreach ($config["certificates"] as $certificate) { - $promises[] = \Amp\resolve($this->checkAndIssue($certificate, $server, $storage)); + $errors = []; + + foreach ($certificateChunks as $chunk) { + $promises = []; + + foreach ($chunk as $certificate) { + $promises[] = \Amp\resolve($this->checkAndIssue($certificate, $server, $storage)); + } + + list($errors) = (yield \Amp\any($promises)); + $errors = array_merge($errors, $errors); } - list($errors) = (yield \Amp\any($promises)); - if (!empty($errors)) { foreach ($errors as $i => $error) { $certificate = $config["certificates"][$i]; - $this->climate->error("Issuance for the following domains failed: " . implode(", ", array_keys($this->toDomainPathMap((array) $certificate->paths)))); + $this->climate->error("Issuance for the following domains failed: " . implode(", ", array_keys($this->toDomainPathMap($certificate["paths"])))); $this->climate->error("Reason: {$error}"); } @@ -110,7 +116,7 @@ class Auto implements Command { * @throws AcmeException if something does wrong */ private function checkAndIssue(array $certificate, $server, $storage) { - $domainPathMap = $this->toDomainPathMap((array) $certificate["paths"]); + $domainPathMap = $this->toDomainPathMap($certificate["paths"]); $commonName = reset(array_keys($domainPathMap)); $args = [ @@ -137,7 +143,6 @@ class Auto implements Command { if ($result->exit === 1) { // Renew certificate - $args = [ PHP_BINARY, $GLOBALS["argv"][0], @@ -146,9 +151,9 @@ class Auto implements Command { $server, "--storage", $storage, - "-d", + "--domains", implode(",", array_keys($domainPathMap)), - "-p", + "--path", implode(PATH_SEPARATOR, array_values($domainPathMap)), ]; @@ -180,17 +185,15 @@ class Auto implements Command { private function toDomainPathMap(array $paths) { $result = []; - foreach ($paths as $pathDomainMap) { - foreach ($pathDomainMap as $path => $domains) { - $domains = (array) $domains; + foreach ($paths as $path => $domains) { + $domains = (array) $domains; - foreach ($domains as $domain) { - if (isset($result[$domain])) { - throw new \LogicException("Duplicate domain: {$domain}"); - } - - $result[$domain] = $path; + foreach ($domains as $domain) { + if (isset($result[$domain])) { + throw new \LogicException("Duplicate domain: {$domain}"); } + + $result[$domain] = $path; } } @@ -198,7 +201,7 @@ class Auto implements Command { } public static function getDefinition() { - return [ + $args = [ "server" => \Kelunik\AcmeClient\getArgumentDescription("server"), "storage" => \Kelunik\AcmeClient\getArgumentDescription("storage"), "config" => [ @@ -208,5 +211,14 @@ class Auto implements Command { "required" => true, ], ]; + + $configPath = \Kelunik\AcmeClient\getConfigPath(); + + if ($configPath) { + $args["config"]["required"] = false; + $args["config"]["defaultValue"] = $configPath; + } + + return $args; } } \ No newline at end of file diff --git a/src/Commands/Setup.php b/src/Commands/Setup.php index 99c1d8e..082260e 100644 --- a/src/Commands/Setup.php +++ b/src/Commands/Setup.php @@ -85,8 +85,6 @@ class Setup implements Command { } public static function getDefinition() { - - return [ "server" => \Kelunik\AcmeClient\getArgumentDescription("server"), "storage" => \Kelunik\AcmeClient\getArgumentDescription("storage"), diff --git a/src/ConfigException.php b/src/ConfigException.php new file mode 100644 index 0000000..e7b9308 --- /dev/null +++ b/src/ConfigException.php @@ -0,0 +1,5 @@ +getMessage()); + if (isset($config["server"]) && !is_string($config["server"])) { + throw new ConfigException("'server' set, but not a string."); } + + if (isset($config["storage"]) && !is_string($config["storage"])) { + throw new ConfigException("'storage' set, but not a string."); + } + } catch (ParseException $e) { + throw new AcmeException("Unable to parse the configuration ({$configPath}): " . $e->getMessage()); } } @@ -168,6 +184,8 @@ function getArgumentDescription($argument) { return $argument; case "storage": + $isPhar = isPhar(); + $argument = [ "longPrefix" => "storage", "description" => "Storage directory for account keys and certificates.",