Skip to content
Closed
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ service:
- standard
- medium
- large
catalog:
- https://github.com/platformsh/template-builder/blob/master/templates/akeneo/.platform.template.yaml
- https://github.com/platformsh/template-builder/blob/master/templates/beego/.platform.template.yaml
- https://github.com/platformsh/template-builder/blob/master/templates/django1/.platform.template.yaml

# Configuration relating to API calls.
# This can be overridden in the user config file.
Expand Down
67 changes: 67 additions & 0 deletions src/Command/Project/CreateConsoleForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace Platformsh\Cli\Command\Project;

use Platformsh\ConsoleForm\Form;
use Platformsh\ConsoleForm\Exception\MissingValueException;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;

class CreateConsoleForm extends Form
{
/**
* {@inheritdoc}
*/
public function resolveOptions(InputInterface $input, OutputInterface $output, QuestionHelper $helper)
{
$values = [];
$stdErr = $output instanceof ConsoleOutput ? $output->getErrorOutput() : $output;
print_r('catalog: ' . $input->getOption('catalog'));
print_r('template: ' . $input->getOption('template'));
foreach ($this->fields as $key => $field) {
$field->onChange($values);

// Check for the catalog flag.
if ($field->getOptionName() == 'catalog_url' && $input->getOption('catalog')!==true) {
continue;
}
// Check if the initialize field should be shown.
if ($field->getOptionName() == 'initialized') {
// Do not show if the neither catalog or template flags are present.
if ($input->getOption('catalog')==false && $input->getOption('template')==false) {
continue;
}
// Do not show is the initialize flag is present.
if ($input->getOption('initialize')==true) {
continue;
}
}
if (!$this->includeField($field, $values)) {
continue;
}

// Get the value from the command-line options.
$value = $field->getValueFromInput($input, false);
if ($value !== null) {
$field->validate($value);
} elseif ($input->isInteractive()) {
// Get the value interactively.
$value = $helper->ask($input, $stdErr, $field->getAsQuestion());
$stdErr->writeln('');
} elseif ($field->isRequired()) {
throw new MissingValueException('--' . $field->getOptionName() . ' is required');
}

self::setNestedArrayValue(
$values,
$field->getValueKeys() ?: [$key],
$field->getFinalValue($value),
true
);
}

return $values;
}
}
119 changes: 102 additions & 17 deletions src/Command/Project/ProjectCreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
use Platformsh\Cli\Console\Bot;
use Platformsh\ConsoleForm\Field\Field;
use Platformsh\ConsoleForm\Field\OptionsField;
use Platformsh\ConsoleForm\Form;
use Platformsh\ConsoleForm\Field\BooleanField;
use Platformsh\Cli\Command\Project\CreateConsoleForm;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
Expand All @@ -27,11 +28,15 @@ protected function configure()
->setAliases(['create'])
->setDescription('Create a new project');

$this->form = Form::fromArray($this->getFields());
$this->form = CreateConsoleForm::fromArray($this->getFields());
$this->form->configureInputDefinition($this->getDefinition());

$this->addOption('check-timeout', null, InputOption::VALUE_REQUIRED, 'The API timeout while checking the project status', 30)
->addOption('timeout', null, InputOption::VALUE_REQUIRED, 'The total timeout for all API checks (0 to disable the timeout)', 900);
->addOption('timeout', null, InputOption::VALUE_REQUIRED, 'The total timeout for all API checks (0 to disable the timeout)', 900)
->addOption('template', null, InputOption::VALUE_REQUIRED, 'Choose a starting template or provide a url of one.')
->addOption('catalog', null, InputOption::VALUE_NONE, 'Choose a template from the catalog')
->addOption('initialize', null, InputOption::VALUE_NONE, 'Initialize the project after it has been created.');


$this->setHelp(<<<EOF
Use this command to create a new project.
Expand All @@ -55,6 +60,15 @@ protected function configure()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($input->getOption('initialize')==true &&
($input->getOption('catalog')==false &&
$input->getOption('template')==false)) {

$this->stdErr->writeln("Projects cannot be initialized without a template file.
If you would like to use the --initialize option please provide a template file by utilizing
the --template or --catalog options. For more information on this command please type project:create --help.");
return 0;
}
/** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */
$questionHelper = $this->getService('question_helper');

Expand All @@ -63,6 +77,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$estimate = $this->api()
->getClient()
->getSubscriptionEstimate($options['plan'], $options['storage'], $options['environments'], 1);

$costConfirm = sprintf(
'The estimated monthly cost of this project is: <comment>%s</comment>',
$estimate['total']
Expand All @@ -77,9 +92,17 @@ protected function execute(InputInterface $input, OutputInterface $output)
if (!$questionHelper->confirm($costConfirm)) {
return 1;
}
if (!empty($options['catalog_url'])) {
$options['catalog'] = $options['catalog_url'];
}
else if (!empty($input->getOption('template'))) {
$options['catalog'] = $input->getOption('template');
}


$subscription = $this->api()->getClient()
->createSubscription(
$options['catalog'],
$options['region'],
$options['plan'],
$options['title'],
Expand Down Expand Up @@ -129,7 +152,6 @@ protected function execute(InputInterface $input, OutputInterface $output)
$timedOut = $totalTimeout ? time() - $start > $totalTimeout : false;
}
$this->stdErr->writeln('');

if (!$subscription->isActive()) {
if ($timedOut) {
$this->stdErr->writeln('<error>The project failed to activate on time</error>');
Expand All @@ -146,14 +168,34 @@ protected function execute(InputInterface $input, OutputInterface $output)
return 1;
}

$this->stdErr->writeln("The project is now ready!");
if ($options['initialize']) {
// Check that the profile and repository are present and initializable.
if (empty($subscription->project_options['initialize']['repository'])) {
$this->stdErr->writeln("The project has been created but cannot be initialized because the project repository is empty.");
}
// Use the existing initialize command.
$project = $this->selectProject($subscription->project_id);
$environment = $this->api()->getEnvironment('master', $project, null, true);
$environment->initialize($subscription->project_options['initialize']['profile'], $subscription->project_options['initialize']['repository']);
$this->api()->clearEnvironmentsCache($environment->project);
$this->stdErr->writeln("The project has been initialized and is ready!");
}
else {
$this->stdErr->writeln("The project is now ready!");
}

$output->writeln($subscription->project_id);
$this->stdErr->writeln('');

if (!empty($subscription->project_options['initialize'])) {
$this->stdErr->writeln(" Template: <info>{$subscription->project_options[initialize][repository] }</info>");
}
$this->stdErr->writeln(" Region: <info>{$subscription->project_region}</info>");
$this->stdErr->writeln(" Project ID: <info>{$subscription->project_id}</info>");
$this->stdErr->writeln(" Project title: <info>{$subscription->project_title}</info>");
$this->stdErr->writeln(" URL: <info>{$subscription->project_ui}</info>");


return 0;
}

Expand Down Expand Up @@ -216,29 +258,66 @@ protected function getAvailableRegions($runtime = false)
return $regions;
}

/**
* Return the catalog.
*
* The default list is in the config `service.catalog`. This is
* replaced at runtime by an API call.
*
* @param bool $runtime
*
* @return array
*/
protected function getAvailableCatalog($runtime = false)
{
if ($runtime) {
$catalog = [];
foreach ($this->api()->getClient()->getCatalog()->getData() as $item) {
if ($item['info'] && $item['template']) {
$catalog[$item['template']] = $item['info']['name'];
}
}
$catalog['empty'] = 'Empty Project';
} else {
$catalog = (array) $this->config()->get('service.catalog');
}

return $catalog;
}

/**
* Returns a list of ConsoleForm form fields for this command.
*
* @return Field[]
*/
protected function getFields()
{
return [
'title' => new Field('Project title', [
$fields = [];

$fields['title'] = new Field('Project title', [
'optionName' => 'title',
'description' => 'The initial project title',
'questionLine' => '',
'default' => 'Untitled Project',
]),
'region' => new OptionsField('Region', [
]);
$fields['catalog_url'] = new OptionsField('Catalog', [
'optionName' => 'catalog_url',
'description' => 'The template from which to create your project or your own blank project.',
'options' => $this->getAvailableCatalog(),
'asChoice' => FALSE,
'optionsCallback' => function () {
return $this->getAvailableCatalog(true);
},
]);
$fields['region'] = new OptionsField('Region', [
'optionName' => 'region',
'description' => 'The region where the project will be hosted',
'options' => $this->getAvailableRegions(),
'optionsCallback' => function () {
return $this->getAvailableRegions(true);
},
]),
'plan' => new OptionsField('Plan', [
]);
$fields['plan'] = new OptionsField('Plan', [
'optionName' => 'plan',
'description' => 'The subscription plan',
'options' => $this->getAvailablePlans(),
Expand All @@ -247,23 +326,29 @@ protected function getFields()
},
'default' => in_array('development', $this->getAvailablePlans()) ? 'development' : null,
'allowOther' => true,
]),
'environments' => new Field('Environments', [
]);
$fields['environments'] = new Field('Environments', [
'optionName' => 'environments',
'description' => 'The number of environments',
'default' => 3,
'validator' => function ($value) {
return is_numeric($value) && $value > 0 && $value < 50;
},
]),
'storage' => new Field('Storage', [
]);
$fields['storage'] = new Field('Storage', [
'description' => 'The amount of storage per environment, in GiB',
'default' => 5,
'validator' => function ($value) {
return is_numeric($value) && $value > 0 && $value < 1024;
},
]),
];
]);
$fields['initialize'] = new BooleanField('Initialize', [
'optionName' => 'initialized',
'description' => 'Initialize this environment?',
'questionLine' => 'Initialize this environment?',
]);

return $fields;
}

/**
Expand Down