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)
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')
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')
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')
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')
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)
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')
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')
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')
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)
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" )
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))
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()
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()
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/""")