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
Exemple #5
0
    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
Exemple #11
0
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
Exemple #12
0
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')
Exemple #14
0
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)
Exemple #17
0
    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
Exemple #20
0
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)
Exemple #30
0
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']))