/** * @test */ public function getUnsresolvedParameter() { $blueprint = $this->getMockedBlueprint(['parameters' => ['Foo' => '{env:DONTRESOVLE}']]); $parameters = $blueprint->getParameters(false); $parameters = Div::flatten($parameters, 'ParameterKey', 'ParameterValue'); $this->assertEquals('{env:DONTRESOVLE}', $parameters['Foo']); }
/** * @param \StackFormation\Template[] $templates * @param string|null $description * @param array $additionalData * * @return string * @throws \Exception */ public function merge(array $templates, $description = null, array $additionalData = []) { if (count($templates) == 0) { throw new \InvalidArgumentException('No templates given'); } $mergedTemplate = ['AWSTemplateFormatVersion' => '2010-09-09']; $mergeKeys = ['Parameters', 'Mappings', 'Conditions', 'Resources', 'Outputs', 'Metadata']; foreach ($templates as $template) { if (!$template instanceof \StackFormation\Template) { throw new \InvalidArgumentException('Expecting an array of \\StackFormation\\Template objects'); } try { $array = $template->getDecodedJson(); // Copy the current description into the final template if (!empty($array['Description'])) { $mergedTemplate['Description'] = $array['Description']; } // Merge keys from current template with final template foreach ($mergeKeys as $mergeKey) { if (isset($array[$mergeKey]) && is_array($array[$mergeKey])) { foreach ($array[$mergeKey] as $key => $value) { if (isset($mergedTemplate[$mergeKey][$key])) { // it's ok if the parameter has the same name and type... if ($mergeKey != 'Parameters' || $value['Type'] != $mergedTemplate[$mergeKey][$key]['Type']) { throw new \Exception("Duplicate key '{$key}' found in '{$mergeKey}'"); } } $mergedTemplate[$mergeKey][$key] = $value; } } } } catch (TemplateDecodeException $e) { if (Div::isProgramInstalled('jq')) { $tmpfile = tempnam(sys_get_temp_dir(), 'json_validate_'); file_put_contents($tmpfile, $template->getProcessedTemplate()); passthru('jq . ' . $tmpfile); unlink($tmpfile); } throw $e; } } // If a description override is specified use it if (!empty($description)) { $mergedTemplate['Description'] = trim($description); } if (empty($mergedTemplate['Description'])) { $mergedTemplate['Description'] = 'Merged Template'; } $mergedTemplate = array_merge_recursive($mergedTemplate, $additionalData); $json = json_encode($mergedTemplate, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); // Check for max template size if (strlen($json) > self::MAX_CF_TEMPLATE_SIZE) { $json = json_encode($mergedTemplate, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); // Re-check for max template size if (strlen($json) > self::MAX_CF_TEMPLATE_SIZE) { throw new \Exception(sprintf('Template too big (%s bytes). Maximum template size is %s bytes.', strlen($json), self::MAX_CF_TEMPLATE_SIZE)); } } return $json; }
public function compare() { if (empty($this->stack)) { throw new \InvalidArgumentException('Stack not set'); } if (empty($this->blueprint)) { throw new \InvalidArgumentException('Blueprint not set'); } $tmp = []; try { // parameters if ($this->output->isVerbose()) { $this->output->writeln($this->stack->getName() . ': Comparing parameters'); } $parametersStack = $this->stack->getParameters(); $parametersBlueprint = $this->blueprint->getParameters(true); $parametersBlueprint = Div::flatten($parametersBlueprint, 'ParameterKey', 'ParameterValue'); if ($this->parametersAreEqual($parametersStack, $parametersBlueprint)) { $tmp['parameters'] = "<fg=green>equal</>"; } else { $tmp['parameters'] = "<fg=red>different</>"; $tmp['error'] = true; } // template if ($this->output->isVerbose()) { $this->output->writeln($this->stack->getName() . ': Comparing template'); } $templateStack = trim($this->stack->getTemplate()); $templateBlueprint = trim($this->blueprint->getPreprocessedTemplate()); $templateStack = $this->normalizeJson($templateStack); $templateBlueprint = $this->normalizeJson($templateBlueprint); if ($templateStack === $templateBlueprint) { $tmp['template'] = "<fg=green>equal</>"; } else { $tmp['template'] = "<fg=red>different</>"; $tmp['error'] = true; } } catch (CloudFormationException $e) { $tmp['parameters'] = 'Stack not found'; $tmp['template'] = 'Stack not found'; $tmp['error'] = true; } catch (\Exception $e) { $tmp['parameters'] = '<fg=red>EXCEPTION: ' . $e->getMessage() . '</>'; $tmp['template'] = 'EXCEPTION'; $tmp['error'] = true; } return $tmp; }
protected function interactAskForBlueprint(InputInterface $input, OutputInterface $output) { $blueprint = $input->getArgument('blueprint'); if (empty($blueprint) || strpos($blueprint, '*') !== false || strpos($blueprint, '?') !== false) { $choices = $this->blueprintFactory->getBlueprintLabels($blueprint ? $blueprint : null); if (count($choices) == 0) { throw new \Exception('No matching blueprints found'); } elseif (count($choices) == 1) { $blueprint = end($choices); } else { $helper = $this->getHelper('question'); $question = new ChoiceQuestion('Please select a blueprint', $choices); $question->setErrorMessage('Blueprint %s is invalid.'); $blueprint = $helper->ask($input, $output, $question); } $output->writeln('Selected blueprint: ' . $blueprint); list($blueprint) = explode(' ', $blueprint); } elseif (!empty($blueprint) && !$this->blueprintFactory->blueprintExists($blueprint)) { if ($result = $this->blueprintFactory->findByStackname($blueprint)) { $output->writeln('Blueprint reverse match found: <fg=green>' . $result['blueprint'] . '</>'); $output->writeln('With ENV vars: <fg=green>' . Helper\Div::assocArrayToString($result['envvars']) . '</>'); $helper = $this->getHelper('question'); $question = new ConfirmationQuestion("Use this blueprint and set env vars? [y/N] ", false); if (!$helper->ask($input, $output, $question)) { throw new \Exception('Operation aborted'); } $blueprint = $result['blueprint']; foreach ($result['envvars'] as $var => $value) { $output->writeln("Setting env var: {$var}={$value}"); putenv("{$var}={$value}"); } } } $input->setArgument('blueprint', $blueprint); return $blueprint; }
/** * Get outputs * * @return array */ public function getOutputs() { if (!isset($this->data['Outputs'])) { return []; } return Div::flatten($this->data['Outputs'], 'OutputKey', 'OutputValue'); }
protected function execute(InputInterface $input, OutputInterface $output) { $nameFilter = $input->getOption('nameFilter'); $stacks = $this->getStackFactory()->getStacksFromApi(false, $nameFilter); $error = false; $data = []; foreach ($stacks as $stackName => $stack) { /* @var $stack Stack */ $this->dependencyTracker->reset(); $tmp = []; $tmp['stackName'] = $stackName; $tmp['status'] = Helper\Decorator::decorateStatus($stack->getStatus()); $tmp['blueprintName'] = ''; $tmp['parameters'] = ''; $tmp['template'] = ''; $diff = new Diff($output); try { $blueprint = $this->blueprintFactory->getBlueprintByStack($stack); $env = $stack->getUsedEnvVars(); $diff->setStack($stack); $diff->setBlueprint($blueprint); $tmp['blueprintName'] = '<fg=green>' . $blueprint->getName() . '</>'; if (count($env)) { $tmp['blueprintName'] .= "\n" . wordwrap(Div::assocArrayToString($stack->getUsedEnvVars()), 80, "\n"); } $tmp = array_merge($tmp, $diff->compare()); if (isset($tmp['error']) && $tmp['error'] === true) { $error = true; } } catch (BlueprintReferenceNotFoundException $e) { $tmp['blueprintName'] = '-'; $tmp['parameters'] = '<fg=red>not found</>'; $tmp['template'] = '<fg=red>not found</>'; $error = true; } catch (BlueprintNotFoundException $e) { $tmp['blueprintName'] = '<fg=red>Not found: ' . $e->getBlueprintName() . '</>'; $tmp['parameters'] = '<fg=red>not found</>'; $tmp['template'] = '<fg=red>not found</>'; $error = true; } catch (\Exception $e) { $tmp['blueprintName'] = '<fg=red>Exception: ' . $e->getMessage() . '</>'; $tmp['parameters'] = '<fg=red>exception</>'; $tmp['template'] = '<fg=red>exception</>'; $error = true; } if (isset($tmp['error'])) { // so the column doesn't show on in the output table unset($tmp['error']); } $data[] = $tmp; } $output->writeln(''); $table = new Table($output); $table->setHeaders(['Stack', 'Status', 'Blueprint', 'Parameters', 'Template']); $table->setRows($data); $table->render(); $output->writeln(''); $output->writeln("-> Run this to show a diff for a specific stack:"); $output->writeln("{$GLOBALS['argv'][0]} stack:diff <stackName>"); $output->writeln(''); $output->writeln("-> Run this to update a live stack:"); $output->writeln("{$GLOBALS['argv'][0]} blueprint:deploy -o <blueprintName>"); $output->writeln(''); return $error === true ? 1 : 0; }
/** * @test */ public function testSwitchProfileComplex() { putenv('ACCOUNT=t'); putenv('BASE_TYPE_VERSION=42'); $profileManagerMock = $this->getMock('\\StackFormation\\Profile\\Manager', [], [], '', false); $profileManagerMock->method('getStackFactory')->willReturnCallback(function () { $stackFactoryMock = $this->getMock('\\StackFormation\\StackFactory', ['getStackOutput'], [], '', false); $stackFactoryMock->method('getStackOutput')->willReturnCallback(function ($stackName, $key) { return "DummyValue|{$stackName}|{$key}"; }); return $stackFactoryMock; }); $config = new \StackFormation\Config([FIXTURE_ROOT . 'Config/blueprint.switch_profile.yml']); $valueResolver = new \StackFormation\ValueResolver\ValueResolver(null, $profileManagerMock, $config); $blueprintFactory = new \StackFormation\BlueprintFactory($config, $valueResolver); $blueprint = $blueprintFactory->getBlueprint('switch_profile_complex'); $parameters = $blueprint->getParameters(true); $parameters = Div::flatten($parameters, 'ParameterKey', 'ParameterValue'); $this->assertEquals('DummyValue|ecom-t-all-ami-types-42-stack|VarnishAmi', $parameters['VarnishAmi']); }