def prompt(cls, console: io.IO, step_prompt: str, arguments: Dict[str, Any], credentials: Optional[credentials.Credentials] = None) -> str: """Prompt the user to a Google Cloud Platform project id. Args: console: Object to use for user I/O. step_prompt: A prefix showing the current step number e.g. "[1/3]". arguments: The arguments that have already been collected from the user e.g. {"project_id", "project-123"} credentials: The OAuth2 Credentials object to use for api calls during prompt. Returns: The value entered by the user. """ default_project_id = cls._generate_default_project_id( arguments.get('project_name', None)) while True: console.tell(('{} Enter a Google Cloud Platform Project ID, ' 'or leave blank to use').format(step_prompt)) project_id = console.ask('[{}]: '.format(default_project_id)) if not project_id.strip(): return default_project_id try: cls.validate(project_id) except ValueError as e: console.error(e) continue return project_id
def _is_valid_passed_arg(self, console: io.IO, step: str, value: Optional[str], validate: Callable[[str], None]) -> bool: """Checks if the passed in argument via the command line is valid. All prompts that collect a parameter should call this function first. It uses the validate function of the prompt. The code also will process a passed in paramater as a step. This is used to have a static amount of steps that is easier to manage. Returns: A boolean indicating if the passed in argument is valid. """ if value is None: return False try: validate(value) except ValueError as e: console.error(e) quit() msg = '{} {}: {}'.format(step, self.PARAMETER, value) console.tell(msg) return True
def _ask_prompt(question: str, console: io.IO, validate: Optional[Callable[[str], None]] = None, default: Optional[str] = None) -> str: """Used to ask for a single string value. Args: question: Question shown to the user on the console. console: Object to use for user I/O. validate: Function used to check if value provided is valid. It should raise a ValueError if the the value fails to validate. default: Default value if user provides no value. (Presses enter) If default is None, the user must provide an answer that is valid. Returns: The value entered by the user. """ validate = validate or (lambda x: None) while True: answer = console.ask(question) if default and not answer: answer = default try: validate(answer) break except ValueError as e: console.error(e) return answer
def _binary_prompt(question: str, console: io.IO, default: Optional[bool] = None) -> bool: """Used to prompt user to choose from a yes or no question. Args: question: Question shown to the user on the console. console: Object to use for user I/O. default: Default value if user provides no value. (Presses enter) If default is None the user is forced to choose a value (y/n). Returns: The bool representation of the choice of the user. Yes is True. """ while True: answer = console.ask(question).lower() if default is not None and not answer: return default try: _binary_validate(answer) break except ValueError as e: console.error(e) return answer == 'y'
def prompt(cls, console: io.IO, step_prompt: str, arguments: Dict[str, Any], credentials: Optional[credentials.Credentials] = None) -> str: """Prompt the user to enter a password. Args: console: Object to use for user I/O. step_prompt: A prefix showing the current step number e.g. "[1/3]". arguments: The arguments that have already been collected from the user e.g. {"project_id", "project-123"} credentials: The OAuth2 Credentials object to use for api calls during prompt. Returns: The value entered by the user. """ console_prompt = cls._get_prompt(arguments) console.tell(('{} {}').format(step_prompt, console_prompt)) while True: password = console.getpass('Postgres password: ') try: cls.validate(password) except ValueError as e: console.error(e) continue return password
def prompt(cls, console: io.IO, step_prompt: str, arguments: Dict[str, Any], credentials: Optional[credentials.Credentials] = None) -> str: """Prompt the user to a Google Cloud Platform project id. Args: console: Object to use for user I/O. step_prompt: A prefix showing the current step number e.g. "[1/3]". arguments: The arguments that have already been collected from the user e.g. {"project_id", "project-123"} credentials: The OAuth2 Credentials object to use for api calls during prompt. Returns: The value entered by the user. """ while True: console.tell( ('{} Enter the existing Google Cloud Platform Project ID ' 'to use.').format(step_prompt)) project_id = console.ask('Project ID: ') try: cls.validate(project_id) except ValueError as e: console.error(e) continue return project_id
def prompt(cls, console: io.IO, step_prompt: str, arguments: Dict[str, Any], credentials: Optional[credentials.Credentials] = None) -> str: """Prompt the user to enter some sort of name. Args: console: Object to use for user I/O. step_prompt: A prefix showing the current step number e.g. "[1/3]". arguments: The arguments that have already been collected from the user e.g. {"project_id", "project-123"} credentials: The OAuth2 Credentials object to use for api calls during prompt. Returns: The value entered by the user. """ default_name = cls._default_name(arguments) while True: console.tell(('{} {}').format(step_prompt, cls._PROMPT)) project_name = console.ask('[{}]: '.format(default_name)) if not project_name.strip(): project_name = default_name try: cls.validate(project_name) except ValueError as e: console.error(e) continue return project_name
def prompt(self, console: io.IO, step: str, args: Dict[str, Any]) -> Dict[str, Any]: """Extracts user arguments through the command-line. Args: console: Object to use for user I/O. step: Message to present to user regarding what step they are on. args: Dictionary holding prompts answered by user and set up command-line arguments. Returns: A Copy of args + the new parameter collected. """ new_args = copy.deepcopy(args) if self._is_valid_passed_arg(console, step, args.get(self.PARAMETER), self._validate): return new_args while True: directory = self._ask_for_directory(console, step, args) try: self._validate(directory) except ValueError as e: console.error(e) continue break new_args[self.PARAMETER] = directory return new_args
def main(args: argparse.Namespace, console: io.IO = io.ConsoleIO()): if not tool_requirements.check_and_handle_requirements( console, args.backend): return actual_parameters = { 'project_creation_mode': workflow.ProjectCreationMode.CREATE, 'bucket_name': getattr(args, 'bucket_name', None), 'service_accounts': getattr(args, 'service_accounts', None), 'services': getattr(args, 'services', None), 'appengine_service_name': getattr(args, 'appengine_service_name', None), 'cluster_name': getattr(args, 'cluster_name', None), 'database_instance_name': getattr(args, 'database_instance_name', None), } prompt_args = {**vars(args), **actual_parameters} root_prompt = prompt.RootPrompt() actual_parameters = root_prompt.prompt(prompt.Command.CLOUDIFY, console, prompt_args) workflow_manager = workflow.WorkflowManager( actual_parameters['credentials']) django_directory_path = actual_parameters['django_directory_path_cloudify'] django_project_name = utils.get_django_project_name(django_directory_path) try: admin_url = workflow_manager.create_and_deploy_new_project( project_name=actual_parameters['project_name'], project_id=actual_parameters['project_id'], project_creation_mode=actual_parameters['project_creation_mode'], billing_account_name=actual_parameters['billing_account_name'], django_project_name=django_project_name, django_superuser_name=actual_parameters['django_superuser_login'], django_superuser_email=actual_parameters['django_superuser_email'], django_superuser_password=actual_parameters[ 'django_superuser_password'], django_directory_path=django_directory_path, django_requirements_path=actual_parameters.get( 'django_requirements_path'), django_settings_path=actual_parameters['django_settings_path'], database_password=actual_parameters['database_password'], cluster_name=actual_parameters['cluster_name'], database_instance_name=actual_parameters['database_instance_name'], required_services=actual_parameters['services'], required_service_accounts=actual_parameters['service_accounts'], appengine_service_name=actual_parameters['appengine_service_name'], cloud_storage_bucket_name=actual_parameters['bucket_name'], backend=args.backend, deploy_existing_django_project=True) except workflow.ProjectExistsError: console.error('A project with id "{}" already exists'.format( actual_parameters['project_id'])) survey.prompt_for_survey(console) return admin_url
def main(args: argparse.Namespace, console: io.IO = io.ConsoleIO()): vargs = vars(args) if not vargs.get('command_rest'): console.error('Please enter the management command.') return command_rest = vargs.get('command_rest') management_command = command_rest[0] if management_command not in _SUPPORTED_COMMANDS: console.error(('Command "{}" is not supported by Django Cloud ' 'Deploy.').format(management_command)) _execute(command_rest, console)
def _multiple_choice_prompt(question: str, options: List[str], console: io.IO, default: Optional[int] = None) -> Optional[int]: """Used to prompt user to choose from a list of values. Args: question: Question shown to the user on the console. Should have a {} to insert a list of enumerated options. options: Possible values user should choose from. console: Object to use for user I/O. default: Default value if user provides no value. (Presses enter) If default is None the user is forced to choose a value in the option list. Typical usage: # User can press enter if user doesn't want anything. choice = _multiple_choice_prompt('Choose an option:\n{}\n', ['Chicken', 'Salad', 'Burger'], console, default=None) Returns: The choice made by the user. If default is none, it is guaranteed to be an index in the options, else it can possible be the default value. """ assert '{}' in question assert len(options) > 0 options_formatted = [ '{}. {}'.format(str(i), opt) for i, opt in enumerate(options, 1) ] options = '\n'.join(options_formatted) while True: answer = console.ask(question.format(options)) if not answer and default: return default try: _multiple_choice_validate(answer, len(options)) break except ValueError as e: console.error(e) return int(answer) - 1
def check_and_handle_requirements(console: io.IO, backend: str) -> bool: """Checks that requirements are installed. Attempts to install missing ones. Args: console: Handles the input/output with the user. backend: Defines which platform on determines what requirements are needed. Options are 'gke' and 'gae'. Returns: True if all requirements have been satisfied, False otherwise. """ for req in _REQUIREMENTS[backend]: try: req.check_and_handle(console) except MissingRequirementError as e: console.error(e.how_to_install_message) return False return True
def _execute(args: List[str], console: io.IO): """Wraps the execution of a management command with Cloud SQL Proxy. Args: args: The full arguments of a Django management command. For example, ['migrate'], ['showmigrations'] console: Handles the input/output with the user. """ current_dir = os.path.abspath(os.path.expanduser('.')) if not config.Configuration.exist(current_dir): console.error( ('The Django project in "{}" is not deployed yet. Please ' 'deploy it to be able to use the management ' 'commands').format(current_dir)) return # Assume the project was previously deployed with Django Cloud Deply, # then we should be able to get the following parameters config_obj = config.Configuration(current_dir) django_settings_path = config_obj.get('django_settings_path') root, _ = os.path.splitext(django_settings_path) settings_module = '.'.join(root.split('/')[:-1] + ['cloud_settings']) instance_name = config_obj.get('database_instance_name') project_id = config_obj.get('project_id') creds = auth.AuthClient.get_default_credentials() if not creds: creds = auth.AuthClient.create_default_credentials() database_client = database.DatabaseClient.from_credentials(creds) cloud_sql_proxy_port = portpicker.pick_unused_port() os.environ['CLOUD_SQL_PROXY_PORT'] = str(cloud_sql_proxy_port) with database_client.with_cloud_sql_proxy(project_id=project_id, instance_name=instance_name, port=cloud_sql_proxy_port): arguments = [ 'django-admin', *args, '='.join(['--pythonpath', current_dir]), '='.join(['--settings', settings_module]) ] try: subprocess.check_call(arguments) except subprocess.CalledProcessError: # Only show error messages from Django, ignore traceback from DCD pass
def prompt(cls, console: io.IO, step_prompt: str, arguments: Dict[str, Any], credentials: Optional[credentials.Credentials] = None) -> str: """Prompt the user to enter a file system path for their project. Args: console: Object to use for user I/O. step_prompt: A prefix showing the current step number e.g. "[1/3]". arguments: The arguments that have already been collected from the user e.g. {"project_id", "project-123"} credentials: The OAuth2 Credentials object to use for api calls during prompt. Returns: The value entered by the user. """ home_dir = os.path.expanduser('~') # TODO: Remove filesystem-unsafe characters. Implement a validation # method that checks for these. default_directory = os.path.join( home_dir, arguments.get('project_name', 'django-project').lower().replace(' ', '-')) while True: console.tell( ('{} Enter a new directory path to store project source, ' 'or leave blank to use').format(step_prompt)) directory = console.ask('[{}]: '.format(default_directory)) if not directory.strip(): directory = default_directory try: cls.validate(directory) except ValueError as e: console.error(e) continue if os.path.exists(directory): if not cls._prompt_replace(console, directory): continue return directory
def prompt(cls, console: io.IO, step_prompt: str, arguments: Dict[str, Any], credentials: Optional[credentials.Credentials] = None) -> str: """Prompt the user to a Google Cloud Platform project id. If the user supplies the project_id as a flag we want to validate that it exists. We tell the user to supply a new one if it does not. Args: console: Object to use for user I/O. step_prompt: A prefix showing the current step number e.g. "[1/3]". arguments: The arguments that have already been collected from the user e.g. {"project_id", "project-123"} credentials: The OAuth2 Credentials object to use for api calls during prompt. Returns: The value entered by the user. """ project_id = arguments.get('project_id', None) valid_project_id = False while not valid_project_id: if not project_id: console.tell( ('{} Enter the existing Google Cloud Platform Project ID ' 'to use.').format(step_prompt)) project_id = console.ask('Project ID: ') try: cls.validate(project_id, credentials) if (arguments.get( 'project_creation_mode', False) == workflow.ProjectCreationMode.MUST_EXIST): console.tell(('{} Google Cloud Platform Project ID {}' ' is valid').format(step_prompt, project_id)) valid_project_id = True except ValueError as e: console.error(e) project_id = None continue return project_id
def prompt(cls, console: io.IO, step_prompt: str, arguments: Dict[str, Any], credentials: Optional[credentials.Credentials] = None) -> str: """Prompt the user to enter a file system path for their project. Args: console: Object to use for user I/O. step_prompt: A prefix showing the current step number e.g. "[1/3]". arguments: The arguments that have already been collected from the user e.g. {"project_id", "project-123"} credentials: The OAuth2 Credentials object to use for api calls during prompt. Returns: The value entered by the user. """ home_dir = os.path.expanduser('~') default_directory = os.path.join( home_dir, arguments.get('project_name', 'django-project').lower().replace(' ', '-')) while True: console.tell( ('{} Enter the directory of the Django project you want to ' 'update:'.format(step_prompt))) directory = console.ask('[{}]: '.format(default_directory)) if not directory.strip(): directory = default_directory directory = os.path.abspath(os.path.expanduser(directory)) try: cls.validate(directory) except ValueError as e: console.error(e) continue return directory
def _password_prompt(question: str, console: io.IO) -> str: """Used to prompt user to choose a password field. Args: console: Object to use for user I/O. question: Question shown to the user on the console. Returns: The password provided by the user. """ console.tell(question) while True: password1 = console.getpass('Password: '******'Password (again): ') if password1 != password2: console.error('Passwords do not match, please try again') continue return password1
def main(args: argparse.Namespace, console: io.IO = io.ConsoleIO()): if not tool_requirements.check_and_handle_requirements( console, args.backend): return prompt_order = [ 'credentials', 'project_id', 'project_name', 'billing_account_name', 'database_password', 'django_directory_path', 'django_project_name', 'django_app_name', 'django_superuser_login', 'django_superuser_password', 'django_superuser_email', ] required_parameters_to_prompt = { 'credentials': prompt.CredentialsPrompt, 'project_id': prompt.ProjectIdPrompt, 'project_name': prompt.GoogleCloudProjectNamePrompt, 'billing_account_name': prompt.BillingPrompt, 'database_password': prompt.PostgresPasswordPrompt, 'django_directory_path': prompt.DjangoFilesystemPath, 'django_project_name': prompt.DjangoProjectNamePrompt, 'django_app_name': prompt.DjangoAppNamePrompt, 'django_superuser_login': prompt.DjangoSuperuserLoginPrompt, 'django_superuser_password': prompt.DjangoSuperuserPasswordPrompt, 'django_superuser_email': prompt.DjangoSuperuserEmailPrompt } # Parameters that were *not* provided as command flags. remaining_parameters_to_prompt = {} actual_parameters = { 'project_creation_mode': workflow.ProjectCreationMode.CREATE, 'bucket_name': getattr(args, 'bucket_name', None), 'service_accounts': getattr(args, 'service_accounts', None), 'services': getattr(args, 'services', None) } for parameter_name, prompter in required_parameters_to_prompt.items(): value = getattr(args, parameter_name, None) if value is not None: try: prompter.validate(value) except ValueError as e: print(e, file=sys.stderr) sys.exit(1) actual_parameters[parameter_name] = value else: remaining_parameters_to_prompt[parameter_name] = prompter if args.use_existing_project: actual_parameters['project_creation_mode'] = ( workflow.ProjectCreationMode.MUST_EXIST) remaining_parameters_to_prompt['project_name'] = ( prompt.GoogleCloudProjectNamePrompt) remaining_parameters_to_prompt['project_id'] = ( prompt.ExistingProjectIdPrompt) if remaining_parameters_to_prompt: num_steps = len(remaining_parameters_to_prompt) console.tell( '<b>{} steps to setup your new project</b>'.format(num_steps)) console.tell() parameter_and_prompt = sorted(remaining_parameters_to_prompt.items(), key=lambda i: prompt_order.index(i[0])) for step, (parameter_name, prompter) in enumerate(parameter_and_prompt): step = '<b>[{}/{}]</b>'.format(step + 1, num_steps) actual_parameters[parameter_name] = prompter.prompt( console, step, actual_parameters, actual_parameters.get('credentials', None)) workflow_manager = workflow.WorkflowManager( actual_parameters['credentials'], args.backend) try: admin_url = workflow_manager.create_and_deploy_new_project( project_name=actual_parameters['project_name'], project_id=actual_parameters['project_id'], project_creation_mode=actual_parameters['project_creation_mode'], billing_account_name=actual_parameters['billing_account_name'], django_project_name=actual_parameters['django_project_name'], django_app_name=actual_parameters['django_app_name'], django_superuser_name=actual_parameters['django_superuser_login'], django_superuser_email=actual_parameters['django_superuser_email'], django_superuser_password=actual_parameters[ 'django_superuser_password'], django_directory_path=actual_parameters['django_directory_path'], database_password=actual_parameters['database_password'], required_services=actual_parameters['services'], required_service_accounts=actual_parameters['service_accounts'], cloud_storage_bucket_name=actual_parameters['bucket_name'], backend=args.backend) return admin_url except workflow.ProjectExistsError: console.error('A project with id "{}" already exists'.format( actual_parameters['project_id']))