class CookiecutterTemplateCompiler(object):
    def __init__(self):
        self.cloudshell_config_reader = Configuration(CloudShellConfigReader())

    def compile_template(self, shell_name, template_path, extra_context, running_on_same_folder):

        extra_context["project_name"] = shell_name
        extra_context["full_name"] = self.cloudshell_config_reader.read().author

        if running_on_same_folder:
            output_dir = os.path.pardir
        else:
            output_dir = os.path.curdir

        cookiecutter(template_path, no_input=True,
                     extra_context=extra_context,
                     overwrite_if_exists=False, output_dir=output_dir)

        # self._remove_template_info_file(output_dir)

    @staticmethod
    def _remove_template_info_file(shell_path):
        """ Remove template info file from shell structure """

        template_info_file_path = os.path.join(shell_path, TEMPLATE_INFO_FILE)
        if os.path.exists(template_info_file_path):
            os.remove(template_info_file_path)
Пример #2
0
    def test_read_shellfoundry_settings_not_config_file_reads_default(self):
        # Arrange
        reader = Configuration(ShellFoundryConfig())

        # Act
        settings = reader.read()

        #Assert
        self.assertEqual(settings.defaultview, 'gen2')
Пример #3
0
    def test_read_file_does_not_exist_default_settings(self):
        # Arrange
        reader = Configuration(CloudShellConfigReader())

        # Act
        config = reader.read()

        # Assert
        self.assertEqual(config.host, 'localhost')
        self.assertEqual(config.port, 9000)
        self.assertEqual(config.username, 'admin')
        self.assertEqual(config.password, 'admin')
        self.assertEqual(config.domain, 'Global')
Пример #4
0
    def test_non_valid_config_file_read_default(self):
        self.fs.CreateFile('shell_name/cloudshell_config.yml', contents="""
invalidsection:
    defaultview: tosca
    """)
        os.chdir('shell_name')
        reader = Configuration(ShellFoundryConfig())

        # Act
        settings = reader.read()

        # Assert
        self.assertEqual(settings.defaultview, 'gen2')
Пример #5
0
    def test_read_shellfoundry_settings_all_config_are_set(self):
        # Arrange
        self.fs.CreateFile('shell_name/cloudshell_config.yml', contents="""
install:
    defaultview: gen2
    """)
        os.chdir('shell_name')
        reader = Configuration(ShellFoundryConfig())

        # Act
        settings = reader.read()

        #Assert
        self.assertEqual(settings.defaultview, 'gen2')
Пример #6
0
class CookiecutterTemplateCompiler(object):
    def __init__(self):
        self.cloudshell_config_reader = Configuration(CloudShellConfigReader())

    def compile_template(self,
                         shell_name,
                         template_path,
                         extra_context,
                         running_on_same_folder,
                         python_version=None):

        if python_version is None:
            python_version = ""
        else:
            python_version = ' PythonVersion="{}"'.format(str(python_version))

        extra_context.update({
            "project_name":
            shell_name,
            "full_name":
            self.cloudshell_config_reader.read().author,
            "release_date":
            datetime.datetime.now().strftime("%B %Y"),
            "python_version":
            str(python_version),
        })

        if running_on_same_folder:
            output_dir = os.path.pardir
        else:
            output_dir = os.path.curdir

        try:
            cookiecutter(template_path,
                         no_input=True,
                         extra_context=extra_context,
                         overwrite_if_exists=False,
                         output_dir=output_dir)
        except OutputDirExistsException as err:
            raise ClickException(str(err))
        # self._remove_template_info_file(output_dir)

    @staticmethod
    def _remove_template_info_file(shell_path):
        """ Remove template info file from shell structure """

        template_info_file_path = os.path.join(shell_path, TEMPLATE_INFO_FILE)
        if os.path.exists(template_info_file_path):
            os.remove(template_info_file_path)
Пример #7
0
    def test_read_file_is_empty_default_settings(self):
        # Arrange
        self.fs.CreateFile('shell_name/cloudshell_config.yml', contents='')
        os.chdir('shell_name')
        reader = Configuration(CloudShellConfigReader())

        # Act
        config = reader.read()

        # Assert
        self.assertEqual(config.host, 'localhost')
        self.assertEqual(config.port, 9000)
        self.assertEqual(config.username, 'admin')
        self.assertEqual(config.password, 'admin')
        self.assertEqual(config.domain, 'Global')
Пример #8
0
    def test_read_config_data_from_global_configuration(self, get_app_dir_mock):
        # Arrange
        self.fs.CreateFile('Quali/shellfoundry/global_config.yml', contents="""
install:
  host: somehostaddress
""")
        get_app_dir_mock.return_value = 'Quali/shellfoundry/'
        reader = Configuration(CloudShellConfigReader())

        # Act
        config = reader.read()

        # Assert
        self.assertEqual(config.host, 'somehostaddress')
        self.assertEqual(config.port, 9000)
        self.assertEqual(config.username, 'admin')
        self.assertEqual(config.password, 'admin')
        self.assertEqual(config.domain, 'Global')
Пример #9
0
    def test_read_config_all_settings_are_set(self):
        # Arrange
        self.fs.CreateFile('shell_name/cloudshell_config.yml', contents="""
install:
    host: my_server
    port: 123
    username: my_user
    password: my_password
    domain: my_domain
    """)
        os.chdir('shell_name')
        reader = Configuration(CloudShellConfigReader())

        # Act
        config = reader.read()

        # Assert
        self.assertEqual(config.host, 'my_server')
        self.assertEqual(config.port, 123)
        self.assertEqual(config.username, 'my_user')
        self.assertEqual(config.password, 'my_password')
        self.assertEqual(config.domain, 'my_domain')
Пример #10
0
class shellfoundry_version_check(object):
    def __init__(self, abort_if_major=False):
        self.abort_if_major = abort_if_major
        self.cloudshell_config_reader = Configuration(CloudShellConfigReader())

    def __call__(self, f):
        def decorator(*args, **kwargs):
            output = ''
            if self.cloudshell_config_reader.read().online_mode.lower(
            ) == "true":
                try:
                    is_greater_version, is_major_release = is_index_version_greater_than_current(
                    )
                except ShellFoundryVersionException, err:
                    click.secho(err.message, fg='red')
                    raise click.Abort()
                if is_greater_version:
                    if is_major_release:
                        output = 'This version of shellfoundry is not supported anymore, ' \
                                 'please upgrade by running: pip install shellfoundry --upgrade'

                        if self.abort_if_major:
                            click.secho(output, fg='yellow')
                            print ''
                            raise click.Abort()
                    else:
                        output = 'There is a new version of shellfoundry available, ' \
                                 'please upgrade by running: pip install shellfoundry --upgrade'

            f(**kwargs)

            if output:
                print ''
                click.secho(output, fg='yellow')

        return update_wrapper(decorator, f)
Пример #11
0
class NewCommandExecutor(object):
    LOCAL_TEMPLATE_URL_PREFIX = 'local:'
    REMOTE_TEMPLATE_URL_PREFIX = 'url:'
    L1_TEMPLATE = "layer-1-switch"

    def __init__(self,
                 template_compiler=None,
                 template_retriever=None,
                 repository_downloader=None,
                 standards=None,
                 standard_versions=None,
                 shell_name_validations=None):
        """
        :param CookiecutterTemplateCompiler template_compiler:
        :param TemplateRetriever template_retriever:
        :param RepositoryDownloader repository_downloader:
        :param Standards standards:
        :param StandardVersionsFactory standard_versions:
        :param ShellNameValidations shell_name_validations:
        """

        self.cloudshell_config_reader = Configuration(CloudShellConfigReader())
        self.template_retriever = template_retriever or TemplateRetriever()
        self.repository_downloader = repository_downloader or RepositoryDownloader(
        )
        self.template_compiler = template_compiler or CookiecutterTemplateCompiler(
        )
        self.standards = standards or Standards()
        self.standard_versions = standard_versions or StandardVersionsFactory()
        self.shell_name_validations = shell_name_validations or ShellNameValidations(
        )

    def new(self, name, template, version=None, python_version=None):
        """ Create a new shell based on a template.
        :param str version: The desired version of the shell template to use
        :param str name: The name of the Shell
        :param str template: The name of the template to use
        """
        # Special handling for the case where the user runs 'shellfoundry .' in such a case the '.'
        # character is substituted for the shell name and the content of the current folder is populated
        running_on_same_folder = False
        if name == os.path.curdir:
            name = os.path.split(os.getcwd())[1]
            running_on_same_folder = True

        if not self.shell_name_validations.validate_shell_name(name):
            raise click.BadParameter(
                "Shell name must begin with a letter and contain only alpha-numeric characters and spaces."
            )

        try:
            standards = self.standards.fetch()
        except FeatureUnavailable:
            # raise click.ClickException("Cannot retrieve standards list. "
            #                            "Feature unavailable (probably due to cloudshell version below 8.1")
            standards = self.standards.fetch(
                alternative=ALTERNATIVE_STANDARDS_PATH)
        except Exception as err:
            raise click.ClickException(
                "Cannot retrieve standards list. Error: {}".format(err))

        # Get template using direct url path. Ignore parameter in configuration file
        if self._is_direct_online_template(template):
            self._import_direct_online_template(name, running_on_same_folder,
                                                template, standards,
                                                python_version)
        # Get template using direct path. Ignore parameter in configuration file
        elif self._is_direct_local_template(template):
            self._import_local_template(name, running_on_same_folder, template,
                                        standards, python_version)
        # Get template from GitHub repository
        elif self.cloudshell_config_reader.read().online_mode.lower(
        ) == "true":
            self._import_online_template(name, running_on_same_folder,
                                         template, version, standards,
                                         python_version)
        # Get template from local from location defined in shellfoundry configuration
        else:
            template = self._get_local_template_full_path(
                template, standards, version)
            self._import_local_template(name, running_on_same_folder, template,
                                        standards, python_version)

        if template == self.L1_TEMPLATE:
            click.secho("WARNING: L1 shells support python 2.7 only!",
                        fg="yellow")

        click.echo('Created shell {0} based on template {1}'.format(
            name, template))

    def _import_direct_online_template(self, name, running_on_same_folder,
                                       template, standards, python_version):
        """ Create shell based on template downloaded by the direct link """

        template_url = self._remove_prefix(
            template, NewCommandExecutor.REMOTE_TEMPLATE_URL_PREFIX)
        with TempDirContext(name) as temp_dir:
            try:
                repo_path = self.repository_downloader.download_template(
                    temp_dir,
                    template_url,
                    branch=None,
                    is_need_construct=False)
            except VersionRequestException:
                raise click.BadParameter(
                    "Failed to download template from provided direct link {}".
                    format(template_url))

            self._verify_template_standards_compatibility(
                template_path=repo_path, standards=standards)

            extra_content = self._get_template_params(repo_path=repo_path)

            self.template_compiler.compile_template(
                shell_name=name,
                template_path=repo_path,
                extra_context=extra_content,
                running_on_same_folder=running_on_same_folder,
                python_version=python_version)

    def _import_online_template(self, name, running_on_same_folder, template,
                                version, standards, python_version):
        """ Create shell based on template downloaded from GitHub by the name """

        # Create a temp folder for the operation to make sure we delete it after
        with TempDirContext(name) as temp_dir:
            try:
                templates = self.template_retriever.get_templates(
                    standards=standards)
            except (SSLError, FatalError):
                raise click.UsageError(
                    "Cannot retrieve templates list, are you offline?")
            except FeatureUnavailable:
                templates = self.template_retriever.get_templates(
                    alternative=ALTERNATIVE_TEMPLATES_PATH,
                    standards=standards)

            templates = {
                template_name: template[0]
                for template_name, template in templates.items()
            }

            if template not in templates:
                raise click.BadParameter(
                    'Template {0} does not exist. '
                    'Supported templates are: {1}'.format(
                        template, self._get_templates_with_comma(templates)))
            template_obj = templates[template]

            if not version and template != self.L1_TEMPLATE:
                version = self._get_template_latest_version(
                    standards, template_obj.standard)

            try:
                repo_path = self.repository_downloader.download_template(
                    temp_dir, template_obj.repository, version)
            except VersionRequestException:
                branches = TemplateVersions(*template_obj.repository.split('/')
                                            [-2:]).get_versions_of_template()
                branches.remove(MASTER_BRANCH_NAME)
                branches_str = ', '.join(branches)
                raise click.BadParameter(
                    'Requested standard version (\'{}\') does not match template version. \n'
                    'Available versions for {}: {}'.format(
                        version, template_obj.name, branches_str))

            self._verify_template_standards_compatibility(
                template_path=repo_path, standards=standards)

            self.template_compiler.compile_template(
                shell_name=name,
                template_path=repo_path,
                extra_context=template_obj.params,
                running_on_same_folder=running_on_same_folder,
                python_version=python_version)

    def _import_local_template(self, name, running_on_same_folder, template,
                               standards, python_version):
        """ Create shell based on direct path to local template """

        repo_path = self._remove_prefix(
            template, NewCommandExecutor.LOCAL_TEMPLATE_URL_PREFIX)

        if not os.path.exists(repo_path) or not os.path.isdir(repo_path):
            raise click.BadParameter(
                "Could not locate a template folder at: {template_path}".
                format(template_path=repo_path))

        extra_content = self._get_template_params(repo_path=repo_path)

        self._verify_template_standards_compatibility(template_path=repo_path,
                                                      standards=standards)

        self.template_compiler.compile_template(
            shell_name=name,
            template_path=repo_path,
            extra_context=extra_content,
            running_on_same_folder=running_on_same_folder,
            python_version=python_version)

    def _get_template_latest_version(self, standards_list, standard):
        try:
            return self.standard_versions.create(
                standards_list).get_latest_version(standard)
        except Exception as e:
            click.ClickException(str(e))

    def _get_local_template_full_path(self,
                                      template_name,
                                      standards,
                                      version=None):
        """ Get full path to local template based on provided template name """

        templates_location = self.cloudshell_config_reader.read(
        ).template_location

        templates = self.template_retriever.get_templates(
            template_location=templates_location, standards=standards)

        template_obj = templates.get(template_name, None)
        if template_obj is None:
            raise click.BadParameter(
                "There is no template with name ({tmpl_name}).\n"
                "Please, run command 'shellfoundry list' "
                "to get all available templates.".format(
                    tmpl_name=template_name))

        avail_standards = set()
        avail_templates = {}
        for template in template_obj:
            avail_standards.update(standards[template.standard])
            avail_templates.update(template.standard_version)

        if version:
            if version in avail_standards:
                if version in avail_templates:
                    return avail_templates[version]["repo"]
                else:
                    raise click.BadParameter(
                        "Requested template version ({version}) "
                        "does not exist at templates location ({path}).\n"
                        "Existing template versions: {existing_versions}".
                        format(version=version,
                               path=templates_location,
                               existing_versions=", ".join(
                                   list(avail_templates.keys()))))
            else:
                raise click.BadParameter(
                    "Requested template version ({version}) "
                    "does not compatible with available Standards on CloudShell Server "
                    "({avail_standards})".format(
                        version=version,
                        avail_standards=", ".join(avail_standards)))
        else:
            # try to find max available template version
            try:
                version = str(
                    max(
                        list(
                            map(parse_version, avail_standards
                                & set(avail_templates.keys())))))
            except ValueError:
                raise click.ClickException(
                    "There are no compatible templates and ")

            return avail_templates[version]["repo"]

    @staticmethod
    def _get_template_params(repo_path):
        """ Determine template additional parameters """

        full_path = os.path.join(repo_path, TEMPLATE_INFO_FILE)
        if not os.path.exists(full_path):
            raise click.ClickException(
                "Wrong template path provided. Provided path: {}".format(
                    repo_path))
        with open(full_path, mode='r') as f:
            templ_data = json.load(f)

        family_name = templ_data.get("family_name")
        if isinstance(family_name, list):
            value = click.prompt(
                "Please, choose one of the possible family name: {}".format(
                    ", ".join(family_name)),
                default=family_name[0])
            if value not in family_name:
                raise click.UsageError("Incorrect family name provided.")
            extra_context = {"family_name": value}
        elif family_name:
            extra_context = {"family_name": family_name}
        else:
            extra_context = {}

        return extra_context

    @staticmethod
    def _is_direct_local_template(template):
        return template.startswith(
            NewCommandExecutor.LOCAL_TEMPLATE_URL_PREFIX)

    @staticmethod
    def _is_direct_online_template(template):
        return template.startswith(
            NewCommandExecutor.REMOTE_TEMPLATE_URL_PREFIX)

    @staticmethod
    def _remove_prefix(string, prefix):
        return string.rpartition(prefix)[-1]

    @staticmethod
    def _get_templates_with_comma(templates):
        return ', '.join(list(templates.keys()))

    @staticmethod
    def _verify_template_standards_compatibility(template_path, standards):
        """ Check is template and available standards on cloudshell are compatible """

        shell_def_path = os.path.join(template_path,
                                      "{{cookiecutter.project_slug}}",
                                      "shell-definition.yaml")
        if os.path.exists(shell_def_path):
            with open(shell_def_path) as stream:
                match = re.search(
                    r"cloudshell_standard:\s*cloudshell_(?P<name>\S+)_standard_(?P<version>\S+)\.\w+$",
                    stream.read(), re.MULTILINE)
                if match:
                    name = str(match.groupdict()["name"]).replace("_", "-")
                    version = str(match.groupdict()["version"].replace(
                        "_", "."))

                    if name not in standards or version not in standards[name]:
                        raise click.ClickException(
                            "Shell template and available standards are not compatible"
                        )
                else:
                    raise click.ClickException(
                        "Can not determine standard version for provided template"
                    )
Пример #12
0
class GetTemplatesCommandExecutor(object):
    def __init__(self, repository_downloader=None, template_retriever=None):
        """
        :param TemplateRetriever template_retriever:
        :param RepositoryDownloader repository_downloader:
        """

        self.cloudshell_config_reader = Configuration(CloudShellConfigReader())
        self.template_retriever = template_retriever or TemplateRetriever()
        self.repository_downloader = repository_downloader or RepositoryDownloader()

    def get_templates(self, cs_version, output_dir=None):
        """ Download all templates relevant to provided CloudShell Version
        :param str cs_version: The desired version of the CloudShell
        :param str output_dir: Output directory to download templates
        """

        shellfoundry_config = self.cloudshell_config_reader.read()
        online_mode = shellfoundry_config.online_mode.lower() == "true"
        if online_mode:
            try:
                response = self.template_retriever._get_templates_from_github()
                config = yaml.safe_load(response)
                repos = set(template["repository"] for template in config["templates"])

                if not output_dir:
                    output_dir = os.path.abspath(os.path.curdir)

                archive_name = "shellfoundry_templates_{}".format(cs_version)

                archive_path = os.path.join(output_dir, "{}.zip".format(archive_name))
                if os.path.exists(archive_path):
                    click.confirm(
                        text="Templates archive for CloudShell Version {cs_version} already exists in path {path}."
                             "\nDo you wish to overwrite it?".format(cs_version=cs_version,
                                                                     path=archive_path),
                        abort=True)
                    os.remove(archive_path)

                with TempDirContext(archive_name) as temp_dir:
                    templates_path = temp_dir
                    threads = []
                    errors = []
                    for i, repo in enumerate(repos):
                        template_thread = Thread(name=str(i),
                                                 target=self.download_template,
                                                 args=(repo, cs_version, templates_path,
                                                       shellfoundry_config.github_login,
                                                       shellfoundry_config.github_password, errors))
                        threads.append(template_thread)

                    for thread in threads:
                        thread.start()
                    for thread in threads:
                        thread.join()

                    if errors:
                        raise click.ClickException(errors[0])

                    os.chdir(output_dir)
                    shutil.make_archive(archive_name, "zip", templates_path)

                click.echo(
                    "Downloaded templates for CloudShell {cs_version} to {templates}".format(cs_version=cs_version,
                                                                                             templates=os.path.abspath(
                                                                                                 archive_path)))
            except SSLError:
                raise click.UsageError("Could not retrieve the templates list to download. Are you offline?")
        else:
            click.echo("Please, move shellfoundry to online mode. See, shellfoundry config command")

    def download_template(self, repository, cs_version, templates_path, github_login, github_password, errors):
        try:
            result_branch = self.template_retriever.get_latest_template(repository, cs_version,
                                                                        github_login, github_password)

            if result_branch:
                try:
                    template_path = os.path.join(templates_path, currentThread().getName())
                    os.mkdir(template_path)
                    res = self.repository_downloader.download_template(target_dir=template_path,
                                                                       repo_address=repository,
                                                                       branch=result_branch,
                                                                       is_need_construct=True)

                    shutil.copytree(res, os.path.join(templates_path, repository.split("/")[-1]))
                    shutil.rmtree(path=template_path, ignore_errors=True)
                except VersionRequestException:
                    errors.append(
                        "Failed to download template from repository {} version {}".format(repository, result_branch))
                except shutil.Error:
                    errors.append("Failed to build correct template '{}' structure".format(repository))
                finally:
                    pass

        except click.ClickException as err:
            errors.append(str(err))
Пример #13
0
class ShellPackageInstaller(object):
    GLOBAL_DOMAIN = 'Global'

    def __init__(self):
        self.cloudshell_config_reader = Configuration(CloudShellConfigReader())

    def install(self, path):
        """ Install new or Update existed Shell """

        shell_package = ShellPackage(path)
        # shell_name = shell_package.get_shell_name()
        shell_name = shell_package.get_name_from_definition()
        shell_filename = shell_name + ".zip"
        package_full_path = os.path.join(path, "dist", shell_filename)

        cloudshell_config = self.cloudshell_config_reader.read()

        if cloudshell_config.domain != self.GLOBAL_DOMAIN:
            raise click.UsageError(
                "Gen2 shells could not be installed into non Global domain.")

        cs_connection_label = "Connecting to CloudShell at {}:{}".format(
            cloudshell_config.host, cloudshell_config.port)
        with click.progressbar(length=CLOUDSHELL_MAX_RETRIES,
                               show_eta=False,
                               label=cs_connection_label) as pbar:
            try:
                client = self._open_connection_to_quali_server(
                    cloudshell_config, pbar, retry=CLOUDSHELL_MAX_RETRIES)
            finally:
                self._render_pbar_finish(pbar)

        try:
            is_official = client.get_shell(shell_name=shell_name).get(
                SHELL_IS_OFFICIAL_FLAG, False)

            if is_official:
                click.confirm(
                    text=
                    "Upgrading to a custom version of the shell will limit you "
                    "only to customized versions of this shell from now on. "
                    "You won't be able to upgrade it to an official version of the shell in the future."
                    "\nDo you wish to continue?",
                    abort=True)

        except FeatureUnavailable:
            # try to update shell first
            pass
        except ShellNotFoundException:
            # try to install shell
            pass
        except click.Abort:
            raise
        except Exception as e:
            raise FatalError(
                self._parse_installation_error(
                    "Failed to get information about installed shell", e))

        pbar_install_shell_len = 2  # amount of possible actions (update and add)
        installation_label = "Installing shell into CloudShell".ljust(
            len(cs_connection_label))
        with click.progressbar(length=pbar_install_shell_len,
                               show_eta=False,
                               label=installation_label) as pbar:
            try:
                client.update_shell(package_full_path)
            except ShellNotFoundException:
                self._increase_pbar(pbar, DEFAULT_TIME_WAIT)
                self._add_new_shell(client, package_full_path)
            except Exception as e:
                self._increase_pbar(pbar, DEFAULT_TIME_WAIT)
                raise FatalError(
                    self._parse_installation_error("Failed to update shell",
                                                   e))
            finally:
                self._render_pbar_finish(pbar)

    def delete(self, shell_name):
        """ Delete Shell """

        cloudshell_config = self.cloudshell_config_reader.read()

        if cloudshell_config.domain != self.GLOBAL_DOMAIN:
            raise click.UsageError(
                "Gen2 shells could not be deleted from non Global domain.")

        cs_connection_label = "Connecting to CloudShell at {}:{}".format(
            cloudshell_config.host, cloudshell_config.port)
        with click.progressbar(length=CLOUDSHELL_MAX_RETRIES,
                               show_eta=False,
                               label=cs_connection_label) as pbar:
            try:
                client = self._open_connection_to_quali_server(
                    cloudshell_config, pbar, retry=CLOUDSHELL_MAX_RETRIES)
            finally:
                self._render_pbar_finish(pbar)

        pbar_install_shell_len = 2  # amount of possible actions (update and add)
        installation_label = "Deleting shell from CloudShell".ljust(
            len(cs_connection_label))
        with click.progressbar(length=pbar_install_shell_len,
                               show_eta=False,
                               label=installation_label) as pbar:
            try:
                client.delete_shell(shell_name)
            except FeatureUnavailable:
                self._increase_pbar(pbar, DEFAULT_TIME_WAIT)
                raise click.ClickException(
                    "Delete shell command unavailable (probably due to CloudShell version below 9.2)"
                )
            except ShellNotFoundException:
                self._increase_pbar(pbar, DEFAULT_TIME_WAIT)
                raise click.ClickException(
                    "Shell '{shell_name}' doesn't exist on CloudShell".format(
                        shell_name=shell_name))
            except Exception as e:
                self._increase_pbar(pbar, DEFAULT_TIME_WAIT)
                raise click.ClickException(
                    self._parse_installation_error("Failed to delete shell",
                                                   e))
            finally:
                self._render_pbar_finish(pbar)

    def _open_connection_to_quali_server(self, cloudshell_config, pbar, retry):
        if retry == 0:
            raise FatalError(
                "Connection to CloudShell Server failed. Please make sure it is up and running properly."
            )

        try:
            client = PackagingRestApiClient(
                ip=cloudshell_config.host,
                username=cloudshell_config.username,
                port=cloudshell_config.port,
                domain=cloudshell_config.domain,
                password=cloudshell_config.password)
            return client
        except HTTPError as e:
            if e.code == 401:
                raise FatalError(
                    "Login to CloudShell failed. Please verify the credentials in the config"
                )
            raise FatalError(
                "Connection to CloudShell Server failed. Please make sure it is up and running properly."
            )
        except:
            self._increase_pbar(pbar, time_wait=CLOUDSHELL_RETRY_INTERVAL_SEC)
            return self._open_connection_to_quali_server(
                cloudshell_config, pbar, retry - 1)

    def _add_new_shell(self, client, package_full_path):
        try:
            client.add_shell(package_full_path)
        except Exception as e:
            raise FatalError(
                self._parse_installation_error("Failed to add new shell", e))

    def _parse_installation_error(self, base_message, error):
        cs_message = json.loads(error.message)["Message"]
        return "{}. CloudShell responded with: '{}'".format(
            base_message, cs_message)

    def _increase_pbar(self, pbar, time_wait):
        time.sleep(time_wait)
        pbar.make_step(1)

    def _render_pbar_finish(self, pbar):
        pbar.finish()
        pbar.render_progress()
Пример #14
0
class ExtendCommandExecutor(object):
    LOCAL_TEMPLATE_URL_PREFIX = "local:"
    SIGN_FILENAME = "signed"
    ARTIFACTS = {"driver": "src", "deployment": "deployments"}

    def __init__(self,
                 repository_downloader=None,
                 shell_name_validations=None,
                 shell_gen_validations=None):
        """
        :param RepositoryDownloader repository_downloader:
        :param ShellNameValidations shell_name_validations:
        """
        self.repository_downloader = repository_downloader or RepositoryDownloader(
        )
        self.shell_name_validations = shell_name_validations or ShellNameValidations(
        )
        self.shell_gen_validations = shell_gen_validations or ShellGenerationValidations(
        )
        self.cloudshell_config_reader = Configuration(CloudShellConfigReader())

    def extend(self, source, attribute_names):
        """ Create a new shell based on an already existing shell
        :param str source: The path to the existing shell. Can be a url or local path
        :param tuple attribute_names: Sequence of attribute names that should be added
        """

        with TempDirContext("Extended_Shell_Temp_Dir") as temp_dir:
            try:
                if self._is_local(source):
                    temp_shell_path = self._copy_local_shell(
                        self._remove_prefix(
                            source,
                            ExtendCommandExecutor.LOCAL_TEMPLATE_URL_PREFIX),
                        temp_dir)
                else:
                    temp_shell_path = self._copy_online_shell(source, temp_dir)
            except VersionRequestException as err:
                raise click.ClickException(err.message)
            except Exception:
                # raise
                raise click.BadParameter(
                    u"Check correctness of entered attributes")

            # Remove shell version from folder name
            shell_path = re.sub(r"-\d+(\.\d+)*/?$", "", temp_shell_path)
            os.rename(temp_shell_path, shell_path)

            if not self.shell_gen_validations.validate_2nd_gen(shell_path):
                raise click.ClickException(u"Invalid second generation Shell.")

            modificator = DefinitionModification(shell_path)
            self._unpack_driver_archive(shell_path, modificator)
            self._remove_quali_signature(shell_path)
            self._change_author(shell_path, modificator)
            self._add_based_on(shell_path, modificator)
            self._add_attributes(shell_path, attribute_names)

            try:
                # pass
                shutil.move(shell_path, os.path.curdir)
            except shutil.Error as err:
                raise click.BadParameter(err.message)

        click.echo("Created shell based on source {}".format(source))

    def _copy_local_shell(self, source, destination):
        """ Copy shell and extract if needed """

        if os.path.isdir(source):
            source = source.rstrip(os.sep)
            name = os.path.basename(source)
            ext_shell_path = os.path.join(destination, name)
            shutil.copytree(source, ext_shell_path)
        # elif zipfile.is_zipfile(source):
        #
        #     name = os.path.basename(source).replace(".zip", "")
        #     shell_path = self.repository_downloader.repo_extractor.extract_to_folder(source, destination)[0]
        #     if not os.path.isdir(shell_path):
        #         shell_path = os.path.dirname(shell_path)
        #         shell_path = os.path.join(destination, shell_path)
        #
        #     ext_shell_path = os.path.join(os.path.dirname(shell_path.rstrip(os.sep)), name)
        else:
            raise

        return ext_shell_path

    def _copy_online_shell(self, source, destination):
        """ Download shell and extract it """

        archive_path = None
        try:
            archive_path = self.repository_downloader.download_file(
                source, destination)
            ext_shell_path = self.repository_downloader.repo_extractor.extract_to_folder(
                archive_path, destination)
            ext_shell_path = ext_shell_path[0]
        finally:
            if archive_path and os.path.exists(archive_path):
                os.remove(archive_path)

        return os.path.join(destination, ext_shell_path)

    @staticmethod
    def _is_local(source):
        return source.startswith(
            ExtendCommandExecutor.LOCAL_TEMPLATE_URL_PREFIX)

    @staticmethod
    def _remove_prefix(string, prefix):
        return string.rpartition(prefix)[-1]

    def _unpack_driver_archive(self, shell_path, modificator=None):
        """ Unpack driver files from ZIP-archive """

        if not modificator:
            modificator = DefinitionModification(shell_path)

        artifacts = modificator.get_artifacts_files(
            artifact_name_list=self.ARTIFACTS.keys())

        for artifact_name, artifact_path in artifacts.iteritems():

            artifact_path = os.path.join(shell_path, artifact_path)

            if os.path.exists(artifact_path):
                self.repository_downloader.repo_extractor.extract_to_folder(
                    artifact_path,
                    os.path.join(shell_path, self.ARTIFACTS[artifact_name]))
                os.remove(artifact_path)

    @staticmethod
    def _remove_quali_signature(shell_path):
        """ Remove Quali signature from shell """

        signature_file_path = os.path.join(shell_path,
                                           ExtendCommandExecutor.SIGN_FILENAME)
        if os.path.exists(signature_file_path):
            os.remove(signature_file_path)

    def _change_author(self, shell_path, modificator=None):
        """ Change shell authoring """

        author = self.cloudshell_config_reader.read().author

        if not modificator:
            modificator = DefinitionModification(shell_path)

        modificator.edit_definition(field=TEMPLATE_AUTHOR_FIELD, value=author)
        modificator.edit_tosca_meta(field=METADATA_AUTHOR_FIELD, value=author)

    def _add_based_on(self, shell_path, modificator=None):
        """ Add Based_ON field to shell-definition.yaml file """

        if not modificator:
            modificator = DefinitionModification(shell_path)

        modificator.add_field_to_definition(field=TEMPLATE_BASED_ON)

    def _add_attributes(self, shell_path, attribute_names, modificator=None):
        """ Add a commented out attributes to the shell definition """

        if not modificator:
            modificator = DefinitionModification(shell_path)

        modificator.add_properties(attribute_names=attribute_names)
class ShellPackageInstaller(object):
    def __init__(self):
        self.cloudshell_config_reader = Configuration(CloudShellConfigReader())

    def install(self, path):
        shell_package = ShellPackage(path)
        shell_filename = shell_package.get_shell_name() + '.zip'
        package_full_path = os.path.join(path, 'dist', shell_filename)

        cloudshell_config = self.cloudshell_config_reader.read()

        cs_connection_label = 'Connecting to CloudShell at {}:{}'.format(
            cloudshell_config.host, cloudshell_config.port)
        with click.progressbar(length=CloudShell_Max_Retries,
                               show_eta=False,
                               label=cs_connection_label) as pbar:
            try:
                client = self._open_connection_to_quali_server(
                    cloudshell_config, pbar, retry=CloudShell_Max_Retries)
            finally:
                self._render_pbar_finish(pbar)

        pbar_install_shell_len = 2  # amount of possible actions (update and add)
        installation_label = 'Installing shell into CloudShell'.ljust(
            len(cs_connection_label))
        with click.progressbar(length=pbar_install_shell_len,
                               show_eta=False,
                               label=installation_label) as pbar:
            try:
                client.update_shell(package_full_path)
            except ShellNotFoundException:
                self._increase_pbar(pbar, Default_Time_Wait)
                self._add_new_shell(client, package_full_path)
            except Exception as e:
                self._increase_pbar(pbar, Default_Time_Wait)
                raise FatalError(
                    self._parse_installation_error('Failed to update shell',
                                                   e))
            finally:
                self._render_pbar_finish(pbar)

    def _open_connection_to_quali_server(self, cloudshell_config, pbar, retry):
        if retry == 0:
            raise FatalError(
                'Connection to CloudShell Server failed. Please make sure it is up and running properly.'
            )

        try:
            client = PackagingRestApiClient(
                ip=cloudshell_config.host,
                username=cloudshell_config.username,
                port=cloudshell_config.port,
                domain=cloudshell_config.domain,
                password=cloudshell_config.password)
            return client
        except HTTPError as e:
            if e.code == 401:
                raise FatalError(
                    u'Login to CloudShell failed. Please verify the credentials in the config'
                )
            raise FatalError(
                'Connection to CloudShell Server failed. Please make sure it is up and running properly.'
            )
        except:
            self._increase_pbar(pbar, time_wait=CloudShell_Retry_Interval_Sec)
            return self._open_connection_to_quali_server(
                cloudshell_config, pbar, retry - 1)

    def _add_new_shell(self, client, package_full_path):
        try:
            client.add_shell(package_full_path)
        except Exception as e:
            raise FatalError(
                self._parse_installation_error('Failed to add new shell', e))

    def _parse_installation_error(self, base_message, error):
        import json
        cs_message = json.loads(error.message)['Message']
        return "{}. CloudShell responded with: '{}'".format(
            base_message, cs_message)

    def _increase_pbar(self, pbar, time_wait):
        time.sleep(time_wait)
        pbar.next()

    def _render_pbar_finish(self, pbar):
        pbar.finish()
        pbar.render_progress()
Пример #16
0
class ListCommandExecutor(object):
    def __init__(self, default_view=None, template_retriever=None, standards=None):
        """
        :param str default_view:
        :param Standards standards:
        """
        dv = default_view or Configuration(ShellFoundryConfig()).read().defaultview
        self.template_retriever = template_retriever or FilteredTemplateRetriever(dv, TemplateRetriever())
        self.show_info_msg = default_view is None
        self.standards = standards or Standards()
        self.cloudshell_config_reader = Configuration(CloudShellConfigReader())

    def list(self):
        """  """

        online_mode = self.cloudshell_config_reader.read().online_mode.lower() == "true"
        template_location = self.cloudshell_config_reader.read().template_location

        try:
            standards = self.standards.fetch()
            if online_mode:
                try:
                    templates = self.template_retriever.get_templates(standards=standards)
                except SSLError:
                    raise click.UsageError("Could not retrieve the templates list. Are you offline?")
            else:
                templates = self.template_retriever.get_templates(template_location=template_location,
                                                                  standards=standards)
        except FatalError as err:
            raise click.UsageError(str(err))
        except FeatureUnavailable:
            if online_mode:
                templates = self.template_retriever.get_templates(alternative=ALTERNATIVE_TEMPLATES_PATH)
            else:
                templates = self.template_retriever.get_templates(template_location=template_location)

        if not templates:
            raise click.ClickException("No templates matched the view criteria(gen1/gen2) or "
                                       "available templates and standards are not compatible")

        template_rows = [["Template Name", "CloudShell Ver.", "Description"]]
        for template in list(templates.values()):
            template = template[0]
            cs_ver_txt = str(template.min_cs_ver) + " and up"
            template_rows.append(
                [template.name, cs_ver_txt,
                 template.description])  # description is later wrapped based on the size of the console

        table = AsciiTable(template_rows)
        table.outer_border = False
        table.inner_column_border = False
        max_width = table.column_max_width(2)

        if max_width <= 0:  # verify that the console window is not too small, and if so skip the wrapping logic
            click.echo(table.table)
            return

        row = 1
        for template in list(templates.values()):
            template = template[0]
            wrapped_string = linesep.join(wrap(template.description, max_width))
            table.table_data[row][2] = wrapped_string
            row += 1

        output = table.table
        click.echo(output)

        if self.show_info_msg:
            click.echo("""
As of CloudShell 8.0, CloudShell uses 2nd generation shells, to view the list of 1st generation shells use: shellfoundry list --gen1.
For more information, please visit our devguide: https://qualisystems.github.io/devguide/""")