Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds a secondary form to project:create for template, catalog, and initialize options #831

Open
wants to merge 17 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9381f8e
update handling flag for catalog passing and initialization within th…
shawnawsu Aug 5, 2019
65a5027
handling values passed in via the flag for a template location
shawnawsu Aug 5, 2019
9da78e2
switched out all the options for fields to allow consoleForm to work …
shawnawsu Aug 5, 2019
95817f8
possible solution using a secondary form
shawnawsu Aug 6, 2019
c783520
cleaning up
shawnawsu Aug 6, 2019
fca2eef
moving catalog to end of create subscription for backwards compatibility
shawnawsu Aug 30, 2019
6b3dca3
some of the updates requested by Patrick still working on cleaning up…
shawnawsu Sep 3, 2019
422d38d
error handling around the initialize flag if required information is …
shawnawsu Sep 3, 2019
16b8b3c
function name change per patricks feedback
shawnawsu Sep 3, 2019
4002301
some additional checking to make sure things dont error out for no r…
shawnawsu Sep 3, 2019
9a53654
cleaning up after a change per what Patrick noticed and making a smal…
shawnawsu Sep 4, 2019
11f3652
plans and regions first looking to setup options api for a listing
shawnawsu Sep 6, 2019
69bab98
removing a print
shawnawsu Sep 11, 2019
d25b5b8
removing the empty catalog option still not sure if we need a error m…
shawnawsu Sep 16, 2019
60f0d6b
Merge branch 'setup-options-plan-region-lists' into project-create-se…
shawnawsu Sep 16, 2019
22e4abb
merging in PR 852 and adding error checking on the yaml that is passe…
shawnawsu Sep 16, 2019
3b39002
cleaning the file up a little
shawnawsu Sep 16, 2019
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
2 changes: 2 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ service:
- standard
- medium
- large
catalog:
- https://github.com/platformsh/template-drupal7-vanilla/blob/master/.platform.template.yaml
shawnawsu marked this conversation as resolved.
Show resolved Hide resolved

# Configuration relating to API calls.
# This can be overridden in the user config file.
Expand Down
230 changes: 186 additions & 44 deletions src/Command/Project/ProjectCreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Platformsh\Cli\Console\Bot;
use Platformsh\ConsoleForm\Field\Field;
use Platformsh\ConsoleForm\Field\OptionsField;
use Platformsh\ConsoleForm\Field\BooleanField;
use Platformsh\ConsoleForm\Field\UrlField;
use Platformsh\ConsoleForm\Form;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
Expand All @@ -31,7 +33,8 @@ protected function configure()
$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_OPTIONAL, 'Choose a starting template or provide a url of one.', false);

$this->setHelp(<<<EOF
Use this command to create a new project.
Expand All @@ -57,12 +60,24 @@ protected function execute(InputInterface $input, OutputInterface $output)
{
/** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */
$questionHelper = $this->getService('question_helper');

$options = $this->form->resolveOptions($input, $output, $questionHelper);
$template = $input->getOption('template');

if ($template !== false) {
if (empty(parse_url($template, PHP_URL_PATH))) {
$temp_provided = true;
}
else {
$temp_provided = false;
}
$this->template_form = Form::fromArray($this->getTemplateFields($temp_provided));
shawnawsu marked this conversation as resolved.
Show resolved Hide resolved
shawnawsu marked this conversation as resolved.
Show resolved Hide resolved
$template_options = $this->template_form->resolveOptions($input, $output, $questionHelper);
}

$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 @@ -78,13 +93,22 @@ protected function execute(InputInterface $input, OutputInterface $output)
return 1;
}

// Grab the url of the yaml file.
if (!empty($template_options['template_url_from_catalog'])) {
$options['catalog'] = $template_options['template_url_from_catalog'];
}
else if (!empty($template_options['template_url'])) {
$options['catalog'] = $template_options['template_url'];
}
shawnawsu marked this conversation as resolved.
Show resolved Hide resolved

$subscription = $this->api()->getClient()
->createSubscription(
$options['region'],
$options['plan'],
$options['title'],
$options['storage'] * 1024,
$options['environments']
$options['environments'],
$options['catalog']
);

$this->api()->clearProjectsCache();
Expand Down Expand Up @@ -129,7 +153,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 +169,37 @@ protected function execute(InputInterface $input, OutputInterface $output)
return 1;
}

$this->stdErr->writeln("The project is now ready!");
if ($template_options['initialize']) {
shawnawsu marked this conversation as resolved.
Show resolved Hide resolved
// Make sure nothing happens if the all important subscription info
// is not available for some reason.
$project = $this->api()->getProject($subscription->project_id);
if (!empty($project)) {
$environment = $this->api()->getEnvironment('master', $project);
if (isset($subscription->project_options['initialize']['profile']) && isset( $subscription->project_options['initialize']['repository'])) {
$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 could not be initialized at this time due to missing profile and repository information.");
}
}
}
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,6 +262,40 @@ 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 getDefaultCatalog($runtime = false)
{
if ($runtime) {
$catalog = [];
$catalog_items = $this->api()->getClient()->getCatalog()->getData();
if (!empty($catalog_items)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the if isn't needed - if it's always an array, the foreach won't mind if it's empty

foreach ($catalog_items as $item) {
if (isset($item['info']) && isset($item['template'])) {
$catalog[$item['template']] = $item['info']['name'];
}
}
}
$catalog['empty'] = 'Empty Project';
shawnawsu marked this conversation as resolved.
Show resolved Hide resolved
} else {
$catalog = (array) $this->config()->get('service.catalog');
}

if (empty($catalog)) {
// Should we throw an error here? Do we want to kill the process?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the getTemplateFields() receives an empty catalog array? If nothing significant -- I'd remove this condition. If something unexpected -- I'd throw an Exception and bail out.

I don't have a clue what am I writing about, right here, so please be free to ignore me if I make no sense at all.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If $catalog comes back as empty it will cause a logic exception since it's an asChoice field. So, do we want it to fail and skip to the next step and just now allow a template choice or stop the process? I'm not sure what the preference is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'd personally fail it. What do you think @pjcdawkins?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd return [] here, and let Symfony throw the LogicException

}

return $catalog;
}

/**
* Returns a list of ConsoleForm form fields for this command.
*
Expand All @@ -224,46 +304,108 @@ protected function getAvailableRegions($runtime = false)
protected function getFields()
{
return [
'title' => new Field('Project title', [
'optionName' => 'title',
'description' => 'The initial project title',
'questionLine' => '',
'default' => 'Untitled Project',
]),
'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', [
'optionName' => 'plan',
'description' => 'The subscription plan',
'options' => $this->getAvailablePlans(),
'title' => new Field('Project title', [
'optionName' => 'title',
'description' => 'The initial project title',
'questionLine' => '',
'default' => 'Untitled Project',
]),
'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', [
'optionName' => 'plan',
'description' => 'The subscription plan',
'options' => $this->getAvailablePlans(),
'optionsCallback' => function () {
return $this->getAvailablePlans(true);
},
'default' => in_array('development', $this->getAvailablePlans()) ? 'development' : null,
'allowOther' => true,
]),
'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', [
'description' => 'The amount of storage per environment, in GiB',
'default' => 5,
'validator' => function ($value) {
return is_numeric($value) && $value > 0 && $value < 1024;
},
]),
];
}

/**
* Returns a list of ConsoleForm form fields for this command.
*
* @return Field[]
*/
protected function getTemplateFields($url_provided)
{
$fields = [];

if (!$url_provided) {
$fields['template_option'] = new OptionsField('Template Options', [
'optionName' => 'template_option',
'options' => [
'Provide your own template url.',
'Choose a template from the catalog.',
'No template at this time.',
],
'description' => 'Choose a template, provide a url or choose not to use one.',
'includeAsOption' => false,
]);

$fields['template_url_from_catalog'] = new OptionsField('Template (from a catalog)', [
'optionName' => 'template_url_from_catalog',
'conditions' => [
'template_option' => [
'Choose a template from the catalog.'
],
],
'description' => 'The template from which to create your project or your own blank project.',
'options' => $this->getDefaultCatalog(),
'asChoice' => FALSE,
'optionsCallback' => function () {
return $this->getAvailablePlans(true);
},
'default' => in_array('development', $this->getAvailablePlans()) ? 'development' : null,
'allowOther' => true,
]),
'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', [
'description' => 'The amount of storage per environment, in GiB',
'default' => 5,
'validator' => function ($value) {
return is_numeric($value) && $value > 0 && $value < 1024;
},
]),
];
return $this->getDefaultCatalog(true);
},
]);

$fields['template_url'] = new UrlField('Template URL', [
shawnawsu marked this conversation as resolved.
Show resolved Hide resolved
'optionName' => 'template_url',
'conditions' => [
'template_option' => [
'Provide your own template url.'
],
],
'description' => 'The template url',
'questionLine' => 'What is the URL of the template?',
]);
}
$fields['initialize'] = new BooleanField('Initialize', [
'optionName' => 'initialized',
'conditions' => [
'template_option' => [
'Provide your own template url.',
'Choose a template from the catalog.',
],
],
'description' => 'Initialize this project from a template?',
'questionLine' => 'Initialize this project from a template?',
]);

return $fields;
}

/**
Expand Down