Skip to content

Commit

Permalink
Add prompting for missing template parameters.
Browse files Browse the repository at this point in the history
  • Loading branch information
brendandburns committed Mar 14, 2017
1 parent a212357 commit 2c49eba
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 17 deletions.
74 changes: 58 additions & 16 deletions src/azure-cli-core/azure/cli/core/prompting.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from __future__ import print_function
import sys
import getpass
from six.moves import input # pylint: disable=redefined-builtin
Expand All @@ -22,28 +23,64 @@ def _verify_is_a_tty():
raise NoTTYException()


def prompt(msg):
def prompt(msg, help_string=None):
_verify_is_a_tty()
return input(msg)
while True:
val = input(msg)
if val == '?' and help_string is not None:
print(help_string)
continue
return val


def prompt_pass(msg='Password: ', confirm=False):
def prompt_int(msg, help_string=None):
_verify_is_a_tty()
password = getpass.getpass(msg)
if confirm:
password2 = getpass.getpass('Confirm ' + msg)
assert password == password2, 'Passwords do not match.'
return password

while True:
value = input(msg)
if value == '?' and help_string is not None:
print(help_string)
continue
try:
return int(value)
except ValueError:
logger.warning('%s is not a valid number', value)


def prompt_pass(msg='Password: ', confirm=False, help_string=None):
_verify_is_a_tty()
while True:
password = getpass.getpass(msg)
if password == '?' and help_string is not None:
print(help_string)
continue
if confirm:
password2 = getpass.getpass('Confirm ' + msg)
if password != password2:
logger.warning('Passwords do not match.')
continue
return password


def prompt_y_n(msg, default=None, help_string=None):
return _prompt_bool(msg, 'y', 'n', default=default, help_string=help_string)


def prompt_t_f(msg, default=None, help_string=None):
return _prompt_bool(msg, 't', 'f', default=default, help_string=help_string)


def prompt_y_n(msg, default=None):
def _prompt_bool(msg, true_str, false_str, default=None, help_string=None):
_verify_is_a_tty()
if default not in [None, 'y', 'n']:
raise ValueError("Valid values for default are 'y', 'n' or None")
y = 'Y' if default == 'y' else 'y'
n = 'N' if default == 'n' else 'n'
if default not in [None, true_str, false_str]:
raise ValueError("Valid values for default are {}, {} or None".format(true_str, false_str))
y = true_str.upper() if default == true_str else true_str
n = false_str.upper() if default == false_str else false_str
while True:
ans = input('{} ({}/{}): '.format(msg, y, n))
if ans == '?' and help_string is not None:
print(help_string)
continue
if ans.lower() == n.lower():
return False
if ans.lower() == y.lower():
Expand All @@ -52,7 +89,7 @@ def prompt_y_n(msg, default=None):
return default == y.lower()


def prompt_choice_list(msg, a_list, default=1):
def prompt_choice_list(msg, a_list, default=1, help_string=None):
'''Prompt user to select from a list of possible choices.
:param str msg:A message displayed to the user before the choice list
:param str a_list:The list of choices (list of strings or list of dicts with 'name' & 'desc')
Expand All @@ -67,9 +104,14 @@ def prompt_choice_list(msg, a_list, default=1):
for i, x in enumerate(a_list)])
allowed_vals = list(range(1, len(a_list) + 1))
while True:
val = input('{}\n{}\nPlease enter a choice [{}]: '.format(msg, options, default))
if val == '?' and help_string is not None:
print(help_string)
continue
if len(val) == 0:
val = '{}'.format(default)
try:
ans = int(input('{}\n{}\nPlease enter a choice [{}]: '.format(msg, options, default)) or
default)
ans = int(val)
if ans in allowed_vals:
# array index is 0-based, user input is 1-based
return ans - 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from azure.mgmt.resource.links.models import ResourceLinkProperties

from azure.cli.core.parser import IncorrectUsageError
from azure.cli.core.prompting import prompt, prompt_pass, prompt_t_f, prompt_choice_list, prompt_int
from azure.cli.core._util import CLIError, get_file_json
import azure.cli.core.azlogging as azlogging
from azure.cli.core.commands.client_factory import get_mgmt_service_client
Expand Down Expand Up @@ -102,6 +103,56 @@ def validate_arm_template(resource_group_name, template_file=None, template_uri=
return _deploy_arm_template_core(resource_group_name, template_file, template_uri,
'deployment_dry_run', parameters, mode, validate_only=True)

def _find_missing_parameters(parameters, template):
if template is None:
return {}
template_parameters = template.get('parameters', None)
if template_parameters is None:
return {}

missing = {}
for parameter_name in template_parameters:
parameter = template_parameters[parameter_name]
if parameter.get('defaultValue', None) is not None:
continue
if parameters is not None and parameters.get(parameter_name, None) is not None:
continue
missing[parameter_name] = parameter
return missing

def _prompt_for_parameters(missing_parameters):
result = {}
for param_name in missing_parameters:
prompt_str = 'Please provide a value for \'{}\' (? for help): '.format(param_name)
param = missing_parameters[param_name]
param_type = param.get('type', 'string')
description = 'Missing description'
metadata = param.get('metadata', None)
if metadata is not None:
description = metadata.get('description', description)
allowed_values = param.get('allowedValues', None)

while True:
if allowed_values is not None:
ix = prompt_choice_list(prompt_str, allowed_values, help_string=description)
result[param_name] = allowed_values[ix]
break
elif param_type == 'securestring':
value = prompt_pass(prompt_str, help_string=description)
elif param_type == 'int':
int_value = prompt_int(prompt_str, help_string=description)
result[param_name] = int_value
break
elif param_type == 'bool':
value = prompt_t_f(prompt_str, help_string=description)
result[param_name] = value
break
else:
value = prompt(prompt_str, help_string=description)
if len(value) > 0:
break
return {}

def _deploy_arm_template_core(resource_group_name, template_file=None, template_uri=None,
deployment_name=None, parameters=None, mode='incremental',
validate_only=False, no_wait=False):
Expand All @@ -122,6 +173,12 @@ def _deploy_arm_template_core(resource_group_name, template_file=None, template_
else:
template = get_file_json(template_file)

missing = _find_missing_parameters(parameters, template)
if len(missing) > 0:
prompt_parameters = _prompt_for_parameters(missing)
for param_name in prompt_parameters:
parameters[param_name] = prompt_parameters[param_name]

properties = DeploymentProperties(template=template, template_link=template_link,
parameters=parameters, mode=mode)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,45 @@
# --------------------------------------------------------------------------------------------

import unittest
from azure.cli.command_modules.resource.custom import _list_resources_odata_filter_builder
from azure.cli.command_modules.resource.custom import (_list_resources_odata_filter_builder,
_find_missing_parameters)
from azure.cli.core.parser import IncorrectUsageError

class TestDeployResource(unittest.TestCase):

def test_find_missing_parameters_none(self):
template = {
"parameters": {
"foo": {
"defaultValue": "blah"
},
"bar": {},
"baz": {},
}
}

missing = _find_missing_parameters(None, template)
self.assertEqual(2, len(missing))

def test_find_missing_parameters(self):
parameters = {
"foo": "value1",
"bar": "value2"
}

template = {
"parameters": {
"foo": {
"defaultValue": "blah"
},
"bar": {},
"baz": {},
}
}

missing = _find_missing_parameters(parameters, template)
self.assertEqual(1, len(missing))

class TestListResources(unittest.TestCase):

def test_tag_name(self):
Expand Down

0 comments on commit 2c49eba

Please sign in to comment.