shellfoundry 1.2.25__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. shellfoundry/__init__.py +8 -0
  2. shellfoundry/__main__.py +7 -0
  3. shellfoundry/bootstrap.py +169 -0
  4. shellfoundry/commands/__init__.py +0 -0
  5. shellfoundry/commands/config_command.py +87 -0
  6. shellfoundry/commands/delete_command.py +24 -0
  7. shellfoundry/commands/dist_command.py +34 -0
  8. shellfoundry/commands/extend_command.py +182 -0
  9. shellfoundry/commands/generate_command.py +44 -0
  10. shellfoundry/commands/get_templates_command.py +151 -0
  11. shellfoundry/commands/install_command.py +81 -0
  12. shellfoundry/commands/list_command.py +110 -0
  13. shellfoundry/commands/new_command.py +393 -0
  14. shellfoundry/commands/pack_command.py +45 -0
  15. shellfoundry/commands/show_command.py +55 -0
  16. shellfoundry/data/standards.json +44 -0
  17. shellfoundry/data/templates.yml +117 -0
  18. shellfoundry/decorators/__init__.py +1 -0
  19. shellfoundry/decorators/standards.py +20 -0
  20. shellfoundry/decorators/version_check.py +52 -0
  21. shellfoundry/exceptions.py +52 -0
  22. shellfoundry/models/__init__.py +0 -0
  23. shellfoundry/models/install_config.py +82 -0
  24. shellfoundry/models/shell_template.py +22 -0
  25. shellfoundry/models/shellfoundry_settings.py +13 -0
  26. shellfoundry/utilities/__init__.py +114 -0
  27. shellfoundry/utilities/archive_creator.py +35 -0
  28. shellfoundry/utilities/cloudshell_api/__init__.py +1 -0
  29. shellfoundry/utilities/cloudshell_api/client_wrapper.py +71 -0
  30. shellfoundry/utilities/config/__init__.py +0 -0
  31. shellfoundry/utilities/config/config_context.py +44 -0
  32. shellfoundry/utilities/config/config_file_creation.py +32 -0
  33. shellfoundry/utilities/config/config_providers.py +50 -0
  34. shellfoundry/utilities/config/config_record.py +24 -0
  35. shellfoundry/utilities/config_reader.py +154 -0
  36. shellfoundry/utilities/constants.py +37 -0
  37. shellfoundry/utilities/cookiecutter_integration.py +67 -0
  38. shellfoundry/utilities/driver_generator.py +121 -0
  39. shellfoundry/utilities/filters.py +45 -0
  40. shellfoundry/utilities/installer.py +44 -0
  41. shellfoundry/utilities/modifiers/__init__.py +0 -0
  42. shellfoundry/utilities/modifiers/configuration/__init__.py +0 -0
  43. shellfoundry/utilities/modifiers/configuration/aggregated_modifiers.py +26 -0
  44. shellfoundry/utilities/modifiers/configuration/password_modification.py +48 -0
  45. shellfoundry/utilities/modifiers/definition/__init__.py +0 -0
  46. shellfoundry/utilities/modifiers/definition/definition_modification.py +205 -0
  47. shellfoundry/utilities/package_builder.py +152 -0
  48. shellfoundry/utilities/python_dependencies_packager.py +53 -0
  49. shellfoundry/utilities/repository_downloader.py +76 -0
  50. shellfoundry/utilities/shell_config_reader.py +59 -0
  51. shellfoundry/utilities/shell_datamodel_merger.py +44 -0
  52. shellfoundry/utilities/shell_package.py +59 -0
  53. shellfoundry/utilities/shell_package_builder.py +149 -0
  54. shellfoundry/utilities/shell_package_installer.py +231 -0
  55. shellfoundry/utilities/standards/__init__.py +3 -0
  56. shellfoundry/utilities/standards/consts.py +2 -0
  57. shellfoundry/utilities/standards/standards_retriever.py +32 -0
  58. shellfoundry/utilities/standards/standards_versions.py +34 -0
  59. shellfoundry/utilities/temp_dir_context.py +17 -0
  60. shellfoundry/utilities/template_retriever.py +309 -0
  61. shellfoundry/utilities/template_url.py +38 -0
  62. shellfoundry/utilities/template_versions.py +47 -0
  63. shellfoundry/utilities/validations/__init__.py +2 -0
  64. shellfoundry/utilities/validations/shell_generation_validation.py +29 -0
  65. shellfoundry/utilities/validations/shell_name_validations.py +10 -0
  66. shellfoundry/utilities/version_utilities.py +19 -0
  67. shellfoundry-1.2.25.dist-info/LICENSE +201 -0
  68. shellfoundry-1.2.25.dist-info/METADATA +417 -0
  69. shellfoundry-1.2.25.dist-info/RECORD +72 -0
  70. shellfoundry-1.2.25.dist-info/WHEEL +5 -0
  71. shellfoundry-1.2.25.dist-info/entry_points.txt +2 -0
  72. shellfoundry-1.2.25.dist-info/top_level.txt +1 -0
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ import os
4
+ import shutil
5
+ from io import open
6
+
7
+ import click
8
+ import yaml
9
+
10
+ from shellfoundry.utilities.archive_creator import ArchiveCreator
11
+ from shellfoundry.utilities.shell_package import ShellPackage
12
+ from shellfoundry.utilities.temp_dir_context import TempDirContext
13
+
14
+
15
+ class ShellPackageBuilder(object):
16
+ DRIVER_DIR = "src"
17
+ DEPLOY_DIR = "deployments"
18
+
19
+ def pack(self, path):
20
+ """Creates TOSCA based Shell package."""
21
+ self._remove_all_pyc(path)
22
+ shell_package = ShellPackage(path)
23
+ shell_name = shell_package.get_shell_name()
24
+ shell_real_name = shell_package.get_name_from_definition()
25
+ with TempDirContext(shell_name) as package_path:
26
+ self._copy_tosca_meta(package_path, "")
27
+ tosca_meta = self._read_tosca_meta(path)
28
+
29
+ shell_definition_path = tosca_meta["Entry-Definitions"]
30
+
31
+ self._copy_shell_definition(package_path, "", shell_definition_path)
32
+
33
+ with open(shell_definition_path, encoding="utf8") as shell_definition_file:
34
+ shell_definition = yaml.safe_load(shell_definition_file)
35
+
36
+ if "template_icon" in shell_definition["metadata"]:
37
+ self._copy_artifact(
38
+ shell_definition["metadata"]["template_icon"], package_path
39
+ )
40
+
41
+ for node_type in list(shell_definition["node_types"].values()):
42
+ if "artifacts" not in node_type:
43
+ continue
44
+
45
+ artifact_path_list = []
46
+ for artifact_name, artifact in node_type["artifacts"].items():
47
+ if artifact_name == "driver":
48
+ artifact_path_list.append(
49
+ self._create_driver(
50
+ path="",
51
+ package_path=os.curdir,
52
+ dir_path=self.DRIVER_DIR,
53
+ driver_name=os.path.basename(artifact["file"]),
54
+ )
55
+ )
56
+ elif artifact_name == "deployment":
57
+ artifact_path_list.append(
58
+ self._create_driver(
59
+ path="",
60
+ package_path=os.curdir,
61
+ dir_path=self.DEPLOY_DIR,
62
+ driver_name=os.path.basename(artifact["file"]),
63
+ mandatory=False,
64
+ )
65
+ )
66
+
67
+ self._copy_artifact(artifact["file"], package_path)
68
+
69
+ zip_path = self._zip_package(package_path, "", shell_real_name)
70
+
71
+ try:
72
+ self._remove_build_artifacts(artifact_path_list)
73
+ except Exception:
74
+ pass
75
+
76
+ click.echo("Shell package was successfully created: " + zip_path)
77
+
78
+ def _copy_artifact(self, artifact_path, package_path):
79
+ if os.path.exists(artifact_path):
80
+ click.echo("Adding artifact to shell package: " + artifact_path)
81
+ self._copy_file(src_file_path=artifact_path, dest_dir_path=package_path)
82
+ else:
83
+ click.echo("Missing artifact not added to shell package: " + artifact_path)
84
+
85
+ def _read_tosca_meta(self, path):
86
+ tosca_meta = {}
87
+ shell_package = ShellPackage(path)
88
+ with open(shell_package.get_metadata_path(), encoding="utf8") as meta_file:
89
+ for meta_line in meta_file:
90
+ (key, val) = meta_line.split(":")
91
+ tosca_meta[key] = val.strip()
92
+ return tosca_meta
93
+
94
+ def _copy_shell_icon(self, package_path, path):
95
+ self._copy_file(
96
+ src_file_path=os.path.join(path, "shell-icon.png"),
97
+ dest_dir_path=package_path,
98
+ )
99
+
100
+ def _copy_shell_definition(self, package_path, path, shell_definition):
101
+ self._copy_file(
102
+ src_file_path=os.path.join(path, shell_definition),
103
+ dest_dir_path=package_path,
104
+ )
105
+
106
+ def _copy_tosca_meta(self, package_path, path):
107
+ shell_package = ShellPackage(path)
108
+ self._copy_file(
109
+ src_file_path=shell_package.get_metadata_path(),
110
+ dest_dir_path=os.path.join(package_path, "TOSCA-Metadata"),
111
+ )
112
+
113
+ @staticmethod
114
+ def _remove_all_pyc(package_path):
115
+ for root, dirs, files in os.walk(package_path):
116
+ for file in files:
117
+ if file.endswith(".pyc"):
118
+ os.remove(os.path.join(root, file))
119
+
120
+ @staticmethod
121
+ def _create_driver(path, package_path, dir_path, driver_name, mandatory=True):
122
+ dir_to_zip = os.path.join(path, dir_path)
123
+ if os.path.exists(dir_to_zip):
124
+ zip_file_path = os.path.join(package_path, driver_name)
125
+ ArchiveCreator.make_archive(zip_file_path, "zip", dir_to_zip)
126
+ return os.path.abspath(zip_file_path)
127
+ elif mandatory:
128
+ raise click.ClickException(
129
+ "Invalid driver structure. Can't find '{}' driver folder.".format(
130
+ dir_path
131
+ )
132
+ )
133
+
134
+ @staticmethod
135
+ def _copy_file(src_file_path, dest_dir_path):
136
+ if not os.path.exists(dest_dir_path):
137
+ os.makedirs(dest_dir_path)
138
+ shutil.copy(src_file_path, dest_dir_path)
139
+
140
+ @staticmethod
141
+ def _zip_package(package_path, path, package_name):
142
+ zip_file_path = os.path.join(path, "dist", package_name)
143
+ return ArchiveCreator.make_archive(zip_file_path, "zip", package_path)
144
+
145
+ @staticmethod
146
+ def _remove_build_artifacts(artifacts_path_list):
147
+ for artifact_path in artifacts_path_list:
148
+ if artifact_path and os.path.exists(artifact_path):
149
+ os.remove(artifact_path)
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/python
2
+
3
+ import json
4
+ import os
5
+ import time
6
+
7
+ import click
8
+
9
+ try:
10
+ from urllib.error import HTTPError
11
+ except ImportError:
12
+ from urllib2 import HTTPError
13
+
14
+ from cloudshell.rest.api import PackagingRestApiClient
15
+
16
+ try:
17
+ from cloudshell.rest.exceptions import FeatureUnavailable, ShellNotFound
18
+ except ImportError:
19
+ from cloudshell.rest.exceptions import (
20
+ FeatureUnavailable,
21
+ ShellNotFoundException as ShellNotFound,
22
+ )
23
+
24
+ from shellfoundry.exceptions import FatalError
25
+ from shellfoundry.utilities.config_reader import CloudShellConfigReader, Configuration
26
+ from shellfoundry.utilities.constants import (
27
+ CLOUDSHELL_MAX_RETRIES,
28
+ CLOUDSHELL_RETRY_INTERVAL_SEC,
29
+ DEFAULT_TIME_WAIT,
30
+ )
31
+ from shellfoundry.utilities.shell_package import ShellPackage
32
+
33
+ SHELL_IS_OFFICIAL_FLAG = "IsOfficial"
34
+
35
+
36
+ class ShellPackageInstaller(object):
37
+ GLOBAL_DOMAIN = "Global"
38
+
39
+ def __init__(self):
40
+ self.cloudshell_config_reader = Configuration(CloudShellConfigReader())
41
+
42
+ def install(self, path):
43
+ """Install new or Update existed Shell."""
44
+ shell_package = ShellPackage(path)
45
+ shell_name = shell_package.get_name_from_definition()
46
+ shell_filename = shell_name + ".zip"
47
+ package_full_path = os.path.join(path, "dist", shell_filename)
48
+
49
+ cloudshell_config = self.cloudshell_config_reader.read()
50
+
51
+ if cloudshell_config.domain != self.GLOBAL_DOMAIN:
52
+ raise click.UsageError(
53
+ "Gen2 shells could not be installed into non Global domain."
54
+ )
55
+
56
+ cs_connection_label = "Connecting to CloudShell at {}:{}".format(
57
+ cloudshell_config.host, cloudshell_config.port
58
+ )
59
+ with click.progressbar(
60
+ length=CLOUDSHELL_MAX_RETRIES, show_eta=False, label=cs_connection_label
61
+ ) as pbar:
62
+ try:
63
+ client = self._open_connection_to_quali_server(
64
+ cloudshell_config, pbar, retry=CLOUDSHELL_MAX_RETRIES
65
+ )
66
+ finally:
67
+ self._render_pbar_finish(pbar)
68
+
69
+ try:
70
+ is_official = client.get_shell(shell_name=shell_name).get(
71
+ SHELL_IS_OFFICIAL_FLAG, False
72
+ )
73
+
74
+ if is_official:
75
+ click.confirm(
76
+ text="Upgrading to a custom version of the shell will limit you "
77
+ "only to customized versions of this shell from now on. "
78
+ "You won't be able to upgrade it to an official version of the shell in the future." # noqa: E501
79
+ "\nDo you wish to continue?",
80
+ abort=True,
81
+ )
82
+
83
+ except FeatureUnavailable:
84
+ # try to update shell first
85
+ pass
86
+ except ShellNotFound:
87
+ # try to install shell
88
+ pass
89
+ except click.Abort:
90
+ raise
91
+ except Exception as e:
92
+ raise FatalError(
93
+ self._parse_installation_error(
94
+ "Failed to get information about installed shell", e
95
+ )
96
+ )
97
+
98
+ pbar_install_shell_len = 2 # amount of possible actions (update and add)
99
+ installation_label = "Installing shell into CloudShell".ljust(
100
+ len(cs_connection_label)
101
+ )
102
+ with click.progressbar(
103
+ length=pbar_install_shell_len, show_eta=False, label=installation_label
104
+ ) as pbar:
105
+ try:
106
+ client.update_shell(package_full_path)
107
+ except ShellNotFound:
108
+ self._increase_pbar(pbar, DEFAULT_TIME_WAIT)
109
+ self._add_new_shell(client, package_full_path)
110
+ except Exception as e:
111
+ self._increase_pbar(pbar, DEFAULT_TIME_WAIT)
112
+ raise FatalError(
113
+ self._parse_installation_error("Failed to update shell", e)
114
+ )
115
+ finally:
116
+ self._render_pbar_finish(pbar)
117
+
118
+ def delete(self, shell_name):
119
+ """Delete Shell."""
120
+ cloudshell_config = self.cloudshell_config_reader.read()
121
+
122
+ if cloudshell_config.domain != self.GLOBAL_DOMAIN:
123
+ raise click.UsageError(
124
+ "Gen2 shells could not be deleted from non Global domain."
125
+ )
126
+
127
+ cs_connection_label = "Connecting to CloudShell at {}:{}".format(
128
+ cloudshell_config.host, cloudshell_config.port
129
+ )
130
+ with click.progressbar(
131
+ length=CLOUDSHELL_MAX_RETRIES, show_eta=False, label=cs_connection_label
132
+ ) as pbar:
133
+ try:
134
+ client = self._open_connection_to_quali_server(
135
+ cloudshell_config, pbar, retry=CLOUDSHELL_MAX_RETRIES
136
+ )
137
+ finally:
138
+ self._render_pbar_finish(pbar)
139
+
140
+ pbar_install_shell_len = 2 # amount of possible actions (update and add)
141
+ installation_label = "Deleting shell from CloudShell".ljust(
142
+ len(cs_connection_label)
143
+ )
144
+ with click.progressbar(
145
+ length=pbar_install_shell_len, show_eta=False, label=installation_label
146
+ ) as pbar:
147
+ try:
148
+ client.delete_shell(shell_name)
149
+ except FeatureUnavailable:
150
+ self._increase_pbar(pbar, DEFAULT_TIME_WAIT)
151
+ raise click.ClickException(
152
+ "Delete shell command unavailable (probably due to CloudShell version below 9.2)" # noqa: E501
153
+ )
154
+ except ShellNotFound:
155
+ self._increase_pbar(pbar, DEFAULT_TIME_WAIT)
156
+ raise click.ClickException(
157
+ "Shell '{shell_name}' doesn't exist on CloudShell".format(
158
+ shell_name=shell_name
159
+ )
160
+ )
161
+ except Exception as e:
162
+ self._increase_pbar(pbar, DEFAULT_TIME_WAIT)
163
+ raise click.ClickException(
164
+ self._parse_installation_error("Failed to delete shell", e)
165
+ )
166
+ finally:
167
+ self._render_pbar_finish(pbar)
168
+
169
+ def _open_connection_to_quali_server(self, cloudshell_config, pbar, retry):
170
+ if retry == 0:
171
+ raise FatalError(
172
+ "Connection to CloudShell Server failed. "
173
+ "Please make sure it is up and running properly."
174
+ )
175
+ try:
176
+ try:
177
+ client = PackagingRestApiClient.login(
178
+ host=cloudshell_config.host,
179
+ port=cloudshell_config.port,
180
+ username=cloudshell_config.username,
181
+ password=cloudshell_config.password,
182
+ domain=cloudshell_config.domain,
183
+ )
184
+ return client
185
+ except AttributeError:
186
+ client = PackagingRestApiClient(
187
+ ip=cloudshell_config.host,
188
+ port=cloudshell_config.port,
189
+ username=cloudshell_config.username,
190
+ password=cloudshell_config.password,
191
+ domain=cloudshell_config.domain,
192
+ )
193
+ return client
194
+ except HTTPError as e:
195
+ if e.code == 401:
196
+ raise FatalError(
197
+ "Login to CloudShell failed. "
198
+ "Please verify the credentials in the config"
199
+ )
200
+ raise FatalError(
201
+ "Connection to CloudShell Server failed. "
202
+ "Please make sure it is up and running properly."
203
+ )
204
+ except Exception:
205
+ self._increase_pbar(pbar, time_wait=CLOUDSHELL_RETRY_INTERVAL_SEC)
206
+ return self._open_connection_to_quali_server(
207
+ cloudshell_config, pbar, retry - 1
208
+ )
209
+
210
+ def _add_new_shell(self, client, package_full_path):
211
+ try:
212
+ client.add_shell(package_full_path)
213
+ except Exception as e:
214
+ raise FatalError(
215
+ self._parse_installation_error("Failed to add new shell", e)
216
+ )
217
+
218
+ def _parse_installation_error(self, base_message, error):
219
+ try:
220
+ cs_message = json.loads(str(error))["Message"]
221
+ except Exception:
222
+ cs_message = ""
223
+ return "{}. CloudShell responded with: '{}'".format(base_message, cs_message)
224
+
225
+ def _increase_pbar(self, pbar, time_wait):
226
+ time.sleep(time_wait)
227
+ pbar.make_step(1)
228
+
229
+ def _render_pbar_finish(self, pbar):
230
+ pbar.finish()
231
+ pbar.render_progress()
@@ -0,0 +1,3 @@
1
+ from .consts import STANDARD_NAME_KEY, VERSIONS_KEY # noqa: F401
2
+ from .standards_retriever import Standards # noqa: F401
3
+ from .standards_versions import StandardVersions, StandardVersionsFactory # noqa: F401
@@ -0,0 +1,2 @@
1
+ STANDARD_NAME_KEY = "StandardName"
2
+ VERSIONS_KEY = "Versions"
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import json
5
+ from io import open
6
+
7
+ from ..cloudshell_api import create_cloudshell_client
8
+
9
+ from shellfoundry.decorators.standards import standard_transformation
10
+
11
+
12
+ class Standards(object):
13
+ @standard_transformation
14
+ def fetch(self, **kwargs):
15
+ alternative = kwargs.get("alternative", None)
16
+ if not alternative:
17
+ return self._fetch_from_cloudshell()
18
+ return self._fetch_from_alternative_path(alternative)
19
+
20
+ @staticmethod
21
+ def _fetch_from_cloudshell():
22
+ cs_client = create_cloudshell_client()
23
+ try:
24
+ return cs_client.get_installed_standards()
25
+ except Exception:
26
+ raise
27
+
28
+ @staticmethod
29
+ def _fetch_from_alternative_path(alternative_path):
30
+ with open(alternative_path, mode="r", encoding="utf8") as stream:
31
+ response = stream.read()
32
+ return json.loads(response)
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from pkg_resources import parse_version
5
+
6
+
7
+ class StandardVersionsFactory(object):
8
+ def create(self, standards):
9
+ return StandardVersions(standards)
10
+
11
+
12
+ class StandardVersions(object):
13
+ def __init__(self, standards):
14
+ if not standards:
15
+ import os
16
+
17
+ from shellfoundry import __file__ as sf_file
18
+
19
+ raise Exception(
20
+ "Standards list is empty. Please verify that {} exists".format(
21
+ os.path.join(os.path.dirname(sf_file), "data", "standards.json")
22
+ )
23
+ )
24
+
25
+ self.standards = standards
26
+
27
+ def get_latest_version(self, standard):
28
+ standards = self.standards.get(standard, None)
29
+ if standards is None:
30
+ raise Exception("Failed to find latest version")
31
+
32
+ latest_version = str(max(list(map(parse_version, standards))))
33
+ if latest_version:
34
+ return latest_version
@@ -0,0 +1,17 @@
1
+ import shutil
2
+ import tempfile
3
+
4
+
5
+ class TempDirContext:
6
+ def __init__(self, remove_dir_on_error=True, prefix=""):
7
+ self.temp_dir = None
8
+ self.prefix = prefix
9
+ self._remove_dir_on_error = remove_dir_on_error
10
+
11
+ def __enter__(self):
12
+ self.temp_dir = tempfile.mkdtemp(prefix=self.prefix)
13
+ return self.temp_dir
14
+
15
+ def __exit__(self, exc_type, exc_val, exc_tb):
16
+ if not exc_val or (exc_val and self._remove_dir_on_error):
17
+ shutil.rmtree(self.temp_dir, ignore_errors=True)