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 _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 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 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 _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 _get_new_billing_account(console: io.IO, existing_billing_accounts: List[Dict[str, Any]], billing_client: billing.BillingClient) -> str: """Ask the user to create a new billing account and return name of it. Args: console: Object to use for user I/O. existing_billing_accounts: User's billing accounts before creation of new accounts. billing_client: A client to query user's existing billing accounts. Returns: Name of the user's newly created billing account. """ webbrowser.open('https://console.cloud.google.com/billing/create') existing_billing_account_names = [ account['name'] for account in existing_billing_accounts ] console.tell('Waiting for billing account to be created.') while True: billing_accounts = billing_client.list_billing_accounts( only_open_accounts=True) if len(existing_billing_accounts) != len(billing_accounts): billing_account_names = [ account['name'] for account in billing_accounts ] diff = list( set(billing_account_names) - set(existing_billing_account_names)) return diff[0] time.sleep(2)
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 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 _handle_existing_project(self, console: io.IO, step: str, args: Dict[str, Any]) -> str: assert 'project_id' in args, 'project_id must be set' project_id = args['project_id'] project_name = self.project_client.get_project(project_id)['name'] message = '{} {}: {}'.format(step, self.PARAMETER, project_name) console.tell(message) return project_name
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()): if not tool_requirements.check_and_handle_requirements( console, args.backend): return prompt_order = [ 'credentials', 'database_password', 'django_directory_path', ] required_parameters_to_prompt = { 'credentials': prompt.CredentialsPrompt, 'database_password': prompt.PostgresPasswordUpdatePrompt, 'django_directory_path': prompt.DjangoFilesystemPathUpdate, } # Parameters that were *not* provided as command flags. remaining_parameters_to_prompt = {} actual_parameters = {} 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 remaining_parameters_to_prompt: num_steps = len(remaining_parameters_to_prompt) console.tell('<b>{} steps to update 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) workflow_manager = workflow.WorkflowManager( actual_parameters['credentials'], args.backend) workflow_manager.update_project( actual_parameters['django_directory_path'], actual_parameters['database_password'], backend=args.backend)
def _has_existing_billing_account(self, console: io.IO, step: str, args: Dict[str, Any]) -> (Optional[str]): assert 'project_id' in args, 'project_id must be set' project_id = args['project_id'] billing_account = (self.billing_client.get_billing_account(project_id)) if not billing_account.get('billingEnabled', False): return None msg = ('{} Billing is already enabled on this project.'.format(step)) console.tell(msg) return billing_account.get('billingAccountName')
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 handle_crash(err: Exception, command: str, console: io.IO = io.ConsoleIO()): """The tool's crashing handler. Args: err: The exception that was raised. command: The command causing the exception to get thrown, e.g. 'django-cloud-deploy new'. console: Object to use for user I/O. """ # Only handle crashes caused by our code, not user's code. # When deploying, our tool will run the code of user's Django project. # If user's code has a bug, then an UserError will be raised. In this case, # we do not want users to create a Github issue. if any( isinstance(err, exception_class) for exception_class in _DISPLAYABLE_EXCEPTIONS): # https://github.com/google/pytype/issues/225 raise err.__cause__ # pytype: disable=attribute-error log_fd, log_file_path = tempfile.mkstemp( prefix='django-deploy-bug-report-') issue_content = _create_issue_body(command) issue_title = _create_issue_title(err, command) log_file = os.fdopen(log_fd, 'wt') log_file.write(issue_content) log_file.close() console.tell( ('Your "{}" failed due to an internal error.' '\n\n' 'You can report this error by filing a bug on Github. If you agree,\n' 'a browser window will open and an Github issue will be\n' 'pre-populated with the details of this crash.\n' 'For more details, see: {}').format(command, log_file_path)) while True: ans = console.ask('Would you like to file a bug? [y/N]: ') ans = ans.strip().lower() if not ans: # 'N' is default. break if ans in ['y', 'n']: break if ans.lower() == 'y': _create_issue(issue_title, issue_content)
def handle(cls, console: io.IO): """Attempts to install the requirement. Raises: UnableToAutomaticallyInstall: If the installation fails. """ if shutil.which('gcloud') is None: msg = "gcloud is needed to install Cloud SQL Proxy" raise UnableToAutomaticallyInstallError(cls.NAME, msg) while True: answer = console.ask('Cloud SQL Proxy is required by Django ' 'Deploy. Would you like us to install it ' 'automatically (Y/n)? ').lower().strip() if answer == 'n': raise NotImplementedError elif answer in ['y', '']: break command = ['gcloud', '-q', 'components', 'install', 'cloud_sql_proxy'] if subprocess.call(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) != 0: raise UnableToAutomaticallyInstallError( cls.NAME, cls._AUTOMATIC_INSTALLATION_ERROR)
def handle(cls, console: io.IO): """Attempts to install the requirement. Raises: UnableToAutomaticallyInstall: If the installation fails. """ if shutil.which('gcloud') is None: msg = "Gcloud is needed to install Cloud Sql Proxy" raise UnableToAutomaticallyInstallError(cls.NAME, msg) while True: answer = console.ask('Cloud Sql Proxy is required by Django Cloud ' 'Deploy. Would you like us to install ' 'automatically (Y/n)? ').lower().strip() if answer not in ['y', 'n']: continue if answer == 'n': raise NotImplementedError break try: args = ['components', 'install', 'cloud_sql_proxy'] process = pexpect.spawn('gcloud', args) process.expect('Do you want to continue (Y/n)?') process.sendline('Y') process.expect('Update done!') except (pexpect.exceptions.TIMEOUT, pexpect.exceptions.EOF): dl_link = 'https://cloud.google.com/sql/docs/mysql/sql-proxy' msg = ('Unable to download Cloud Sql Proxy directly from Gcloud. ' 'This is caused when Gcloud was not downloaded directly from' ' https://cloud.google.com/sdk/docs/downloads-interactive\n' 'Please install Cloud SQL Proxy from {}').format(dl_link) raise UnableToAutomaticallyInstallError(cls.NAME, msg) finally: process.close()
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 ) -> credentials.Credentials: """Prompt the user for access to the Google credentials. 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 user's credentials. """ console.tell( ('{} In order to deploy your application, you must allow Django ' 'Deploy to access your Google account.').format(step_prompt)) auth_client = auth.AuthClient() create_new_credentials = True active_account = auth_client.get_active_account() if active_account: # The user has already logged in before while True: ans = console.ask( ('You have logged in with account [{}]. Do you want to ' 'use it? [Y/n]: ').format(active_account)) ans = ans.lower() if ans not in ['y', 'n', '']: continue elif ans in ['y', '']: create_new_credentials = False break if not create_new_credentials: cred = auth_client.get_default_credentials() if cred: return cred return auth_client.create_default_credentials()
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: if ('project_creation_mode' not in arguments or (arguments['project_creation_mode'] != workflow.ProjectCreationMode.MUST_EXIST)): return (super().prompt(console, step_prompt, arguments, credentials)) assert 'project_id' in arguments, 'project_id must be set' project_id = arguments['project_id'] project_client = project.ProjectClient.from_credentials(credentials) project_name = project_client.get_project(project_id)['name'] message = 'Project name found: {}'.format(project_name) console.tell(('{} {}').format(step_prompt, message)) return project_name
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 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), lambda x: x): return new_args console.tell( ('{} In order to deploy your application, you must allow Django ' 'Deploy to access your Google account.').format(step)) create_new_credentials = True active_account = self.auth_client.get_active_account() if active_account: # The user has already logged in before msg = ('You have logged in with account [{}]. Do you want to ' 'use it? [Y/n]: ').format(active_account) use_active_credentials = _binary_prompt(msg, console, default='Y') create_new_credentials = not use_active_credentials if create_new_credentials: creds = self.auth_client.create_default_credentials() else: creds = self.auth_client.get_default_credentials() new_args[self.PARAMETER] = creds return new_args
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 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(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 project_creation_mode = args.get('project_creation_mode') if self._does_project_exist(project_creation_mode): billing_account = self._has_existing_billing_account( console, step, args) if billing_account is not None: new_args[self.PARAMETER] = billing_account return new_args billing_accounts = self.billing_client.list_billing_accounts( only_open_accounts=True) console.tell( ('{} In order to deploy your application, you must enable billing ' 'for your Google Cloud Project.').format(step)) # If the user has existing billing accounts, we let the user pick one if billing_accounts: val = self._handle_existing_billing_accounts( console, billing_accounts) new_args[self.PARAMETER] = val return new_args # If the user does not have existing billing accounts, we direct # the user to create a new one. console.tell('You do not have existing billing accounts.') console.ask('Press [Enter] to create a new billing account.') val = self._get_new_billing_account(console, billing_accounts) new_args[self.PARAMETER] = val return new_args
def handle(cls, console: io.IO): """Attempts to install the requirement. Raises: UnableToAutomaticallyInstall: If the installation fails. """ gcloud_path = shutil.which('gcloud') if gcloud_path is None: msg = "gcloud is needed to install Cloud SQL Proxy" raise UnableToAutomaticallyInstallError(cls.NAME, msg) while True: answer = console.ask('Cloud SQL Proxy is required by Django ' 'Deploy. Would you like us to install it ' 'automatically (Y/n)? ').lower().strip() if answer == 'n': raise NotImplementedError elif answer in ['y', '']: break command = [ gcloud_path, '-q', 'components', 'install', 'cloud_sql_proxy' ] install_result = subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, universal_newlines=True) if install_result.returncode != 0: if 'gcloud components update' in install_result.stderr: raise UnableToAutomaticallyInstallError( cls.NAME, cls._OLD_GCLOUD_VERSION) elif 'non-interactive mode' in install_result.stderr: raise UnableToAutomaticallyInstallError( cls.NAME, cls._MUST_INSTALL_INTERACTIVE) else: raise UnableToAutomaticallyInstallError( cls.NAME, cls._AUTOMATIC_INSTALLATION_ERROR)
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']))