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.
- shellfoundry/__init__.py +8 -0
- shellfoundry/__main__.py +7 -0
- shellfoundry/bootstrap.py +169 -0
- shellfoundry/commands/__init__.py +0 -0
- shellfoundry/commands/config_command.py +87 -0
- shellfoundry/commands/delete_command.py +24 -0
- shellfoundry/commands/dist_command.py +34 -0
- shellfoundry/commands/extend_command.py +182 -0
- shellfoundry/commands/generate_command.py +44 -0
- shellfoundry/commands/get_templates_command.py +151 -0
- shellfoundry/commands/install_command.py +81 -0
- shellfoundry/commands/list_command.py +110 -0
- shellfoundry/commands/new_command.py +393 -0
- shellfoundry/commands/pack_command.py +45 -0
- shellfoundry/commands/show_command.py +55 -0
- shellfoundry/data/standards.json +44 -0
- shellfoundry/data/templates.yml +117 -0
- shellfoundry/decorators/__init__.py +1 -0
- shellfoundry/decorators/standards.py +20 -0
- shellfoundry/decorators/version_check.py +52 -0
- shellfoundry/exceptions.py +52 -0
- shellfoundry/models/__init__.py +0 -0
- shellfoundry/models/install_config.py +82 -0
- shellfoundry/models/shell_template.py +22 -0
- shellfoundry/models/shellfoundry_settings.py +13 -0
- shellfoundry/utilities/__init__.py +114 -0
- shellfoundry/utilities/archive_creator.py +35 -0
- shellfoundry/utilities/cloudshell_api/__init__.py +1 -0
- shellfoundry/utilities/cloudshell_api/client_wrapper.py +71 -0
- shellfoundry/utilities/config/__init__.py +0 -0
- shellfoundry/utilities/config/config_context.py +44 -0
- shellfoundry/utilities/config/config_file_creation.py +32 -0
- shellfoundry/utilities/config/config_providers.py +50 -0
- shellfoundry/utilities/config/config_record.py +24 -0
- shellfoundry/utilities/config_reader.py +154 -0
- shellfoundry/utilities/constants.py +37 -0
- shellfoundry/utilities/cookiecutter_integration.py +67 -0
- shellfoundry/utilities/driver_generator.py +121 -0
- shellfoundry/utilities/filters.py +45 -0
- shellfoundry/utilities/installer.py +44 -0
- shellfoundry/utilities/modifiers/__init__.py +0 -0
- shellfoundry/utilities/modifiers/configuration/__init__.py +0 -0
- shellfoundry/utilities/modifiers/configuration/aggregated_modifiers.py +26 -0
- shellfoundry/utilities/modifiers/configuration/password_modification.py +48 -0
- shellfoundry/utilities/modifiers/definition/__init__.py +0 -0
- shellfoundry/utilities/modifiers/definition/definition_modification.py +205 -0
- shellfoundry/utilities/package_builder.py +152 -0
- shellfoundry/utilities/python_dependencies_packager.py +53 -0
- shellfoundry/utilities/repository_downloader.py +76 -0
- shellfoundry/utilities/shell_config_reader.py +59 -0
- shellfoundry/utilities/shell_datamodel_merger.py +44 -0
- shellfoundry/utilities/shell_package.py +59 -0
- shellfoundry/utilities/shell_package_builder.py +149 -0
- shellfoundry/utilities/shell_package_installer.py +231 -0
- shellfoundry/utilities/standards/__init__.py +3 -0
- shellfoundry/utilities/standards/consts.py +2 -0
- shellfoundry/utilities/standards/standards_retriever.py +32 -0
- shellfoundry/utilities/standards/standards_versions.py +34 -0
- shellfoundry/utilities/temp_dir_context.py +17 -0
- shellfoundry/utilities/template_retriever.py +309 -0
- shellfoundry/utilities/template_url.py +38 -0
- shellfoundry/utilities/template_versions.py +47 -0
- shellfoundry/utilities/validations/__init__.py +2 -0
- shellfoundry/utilities/validations/shell_generation_validation.py +29 -0
- shellfoundry/utilities/validations/shell_name_validations.py +10 -0
- shellfoundry/utilities/version_utilities.py +19 -0
- shellfoundry-1.2.25.dist-info/LICENSE +201 -0
- shellfoundry-1.2.25.dist-info/METADATA +417 -0
- shellfoundry-1.2.25.dist-info/RECORD +72 -0
- shellfoundry-1.2.25.dist-info/WHEEL +5 -0
- shellfoundry-1.2.25.dist-info/entry_points.txt +2 -0
- shellfoundry-1.2.25.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
from io import open
|
|
7
|
+
|
|
8
|
+
import ruamel.yaml as yaml
|
|
9
|
+
|
|
10
|
+
from shellfoundry.exceptions import YmlFieldMissingException
|
|
11
|
+
from shellfoundry.utilities.constants import (
|
|
12
|
+
TEMPLATE_PROPERTY,
|
|
13
|
+
TEMPLATE_VERSION,
|
|
14
|
+
TOSCA_META_LOCATION,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DefinitionModification(object):
|
|
19
|
+
def __init__(self, shell_path):
|
|
20
|
+
self.shell_path = shell_path
|
|
21
|
+
self.entry_definition = os.path.join(
|
|
22
|
+
self.shell_path, self._find_entry_definition()
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def edit_definition(self, field, value):
|
|
26
|
+
"""Modify shell-definition.yaml.
|
|
27
|
+
|
|
28
|
+
:params field str: field name to modify
|
|
29
|
+
:params value str: new value to update
|
|
30
|
+
"""
|
|
31
|
+
self._edit_yaml(self.entry_definition, field, value)
|
|
32
|
+
|
|
33
|
+
def edit_tosca_meta(self, field, value):
|
|
34
|
+
with open(
|
|
35
|
+
os.path.join(self.shell_path, TOSCA_META_LOCATION), "r", encoding="utf8"
|
|
36
|
+
) as tosca_file:
|
|
37
|
+
is_changed = False
|
|
38
|
+
tosca_data = []
|
|
39
|
+
for line in tosca_file:
|
|
40
|
+
if field in line:
|
|
41
|
+
line = re.sub(r":\s+.*", ": {}".format(value), line)
|
|
42
|
+
is_changed = True
|
|
43
|
+
tosca_data.append(line)
|
|
44
|
+
|
|
45
|
+
if not is_changed:
|
|
46
|
+
tosca_data.append("\n{field}: {value}".format(field=field, value=value))
|
|
47
|
+
|
|
48
|
+
with open(
|
|
49
|
+
os.path.join(self.shell_path, TOSCA_META_LOCATION), "w", encoding="utf8"
|
|
50
|
+
) as tosca_file:
|
|
51
|
+
tosca_file.writelines(tosca_data)
|
|
52
|
+
|
|
53
|
+
def add_field_to_definition(self, field, value=None, overwrite=False):
|
|
54
|
+
"""Add new field to shell-definition.yaml.
|
|
55
|
+
|
|
56
|
+
:params field str: field name to add
|
|
57
|
+
:params value str: value to add
|
|
58
|
+
:params overwrite bool: overwrite value if it already exists
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
if overwrite:
|
|
62
|
+
self.edit_definition(field, value)
|
|
63
|
+
except YmlFieldMissingException:
|
|
64
|
+
value = value or self._get_value_from_definition(TEMPLATE_VERSION)
|
|
65
|
+
yaml_parser = yaml.YAML()
|
|
66
|
+
loaded = self._load_yaml(
|
|
67
|
+
yaml_parser=yaml_parser, yaml_file=self.entry_definition
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
section, field_name = field.split("/", 1)
|
|
71
|
+
loaded[section].update({field_name: value})
|
|
72
|
+
self._edit_file(
|
|
73
|
+
yaml_file=self.entry_definition, yaml_parser=yaml_parser, data=loaded
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def add_properties(self, attribute_names):
|
|
77
|
+
"""Add property to shell-definition.yaml file.
|
|
78
|
+
|
|
79
|
+
:params fields tuple/list: sequence of properties name that will be added
|
|
80
|
+
"""
|
|
81
|
+
results = list(map(self._add_property, attribute_names))
|
|
82
|
+
|
|
83
|
+
for item in zip(attribute_names, results):
|
|
84
|
+
self._comment_attribute(*item)
|
|
85
|
+
|
|
86
|
+
def get_artifacts_files(self, artifact_name_list):
|
|
87
|
+
yaml_parser = yaml.YAML()
|
|
88
|
+
shell_definition = self._load_yaml(yaml_parser, self.entry_definition)
|
|
89
|
+
|
|
90
|
+
for node_type in list(shell_definition["node_types"].values()):
|
|
91
|
+
if "artifacts" not in node_type:
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
result = {}
|
|
95
|
+
for artifact_name, artifact in node_type["artifacts"].items():
|
|
96
|
+
if artifact_name in artifact_name_list:
|
|
97
|
+
result.update({artifact_name: artifact["file"]})
|
|
98
|
+
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
def _find_entry_definition(self):
|
|
102
|
+
with open(
|
|
103
|
+
os.path.join(self.shell_path, TOSCA_META_LOCATION), "r"
|
|
104
|
+
) as tosca_file:
|
|
105
|
+
entry_definition = dict(
|
|
106
|
+
list(map(str.strip, str(line).split(":", 1))) for line in tosca_file
|
|
107
|
+
)["Entry-Definitions"]
|
|
108
|
+
|
|
109
|
+
return entry_definition
|
|
110
|
+
|
|
111
|
+
def _load_yaml(self, yaml_parser, yaml_file):
|
|
112
|
+
with open(yaml_file, encoding="utf8") as stream:
|
|
113
|
+
try:
|
|
114
|
+
yaml_parser.indent(offset=2)
|
|
115
|
+
return yaml_parser.load(stream=stream)
|
|
116
|
+
except yaml.YAMLError as exc:
|
|
117
|
+
print(exc) # noqa: T001
|
|
118
|
+
|
|
119
|
+
def _edit_yaml(self, yaml_file, field, value):
|
|
120
|
+
yaml_parser = yaml.YAML()
|
|
121
|
+
loaded = self._load_yaml(yaml_parser=yaml_parser, yaml_file=yaml_file)
|
|
122
|
+
|
|
123
|
+
field_name = field.split("/")[-1]
|
|
124
|
+
self._get_inner_dict_recursively(loaded, field)[field_name] = value
|
|
125
|
+
|
|
126
|
+
self._edit_file(yaml_file=yaml_file, yaml_parser=yaml_parser, data=loaded)
|
|
127
|
+
|
|
128
|
+
def _edit_file(self, yaml_file, yaml_parser, data):
|
|
129
|
+
with open(yaml_file, "wb") as f:
|
|
130
|
+
yaml_parser.dump(data, stream=f)
|
|
131
|
+
|
|
132
|
+
def _get_inner_dict_recursively(self, dic, field):
|
|
133
|
+
split = field.split("/", 1)
|
|
134
|
+
i = dic.get(split[0])
|
|
135
|
+
if not i:
|
|
136
|
+
raise YmlFieldMissingException("Field does not exists")
|
|
137
|
+
if not isinstance(i, dict) and len(split) == 1:
|
|
138
|
+
return dic
|
|
139
|
+
|
|
140
|
+
return self._get_inner_dict_recursively(i, split[1])
|
|
141
|
+
|
|
142
|
+
def _get_value_from_definition(self, field):
|
|
143
|
+
yaml_parser = yaml.YAML()
|
|
144
|
+
loaded = self._load_yaml(yaml_parser, self.entry_definition)
|
|
145
|
+
|
|
146
|
+
field_name = field.split("/")[-1]
|
|
147
|
+
value = self._get_inner_dict_recursively(loaded, field)[field_name]
|
|
148
|
+
return value
|
|
149
|
+
|
|
150
|
+
def _add_property(self, attribute_name):
|
|
151
|
+
"""Add property to shell-definition.yaml file.
|
|
152
|
+
|
|
153
|
+
:params fields list: list of properties name that will be added
|
|
154
|
+
"""
|
|
155
|
+
yaml_parser = yaml.YAML()
|
|
156
|
+
loaded = self._load_yaml(yaml_parser, self.entry_definition)
|
|
157
|
+
|
|
158
|
+
nodes = loaded.get("node_types")
|
|
159
|
+
|
|
160
|
+
is_last = False
|
|
161
|
+
if nodes:
|
|
162
|
+
for key, value in nodes.items():
|
|
163
|
+
if key.startswith("vendor."):
|
|
164
|
+
properties_data = value.get("properties", {})
|
|
165
|
+
if properties_data:
|
|
166
|
+
properties_data.update({attribute_name: TEMPLATE_PROPERTY})
|
|
167
|
+
is_last = False
|
|
168
|
+
else:
|
|
169
|
+
value.insert(
|
|
170
|
+
1, "properties", {attribute_name: TEMPLATE_PROPERTY}
|
|
171
|
+
)
|
|
172
|
+
is_last = True
|
|
173
|
+
break
|
|
174
|
+
|
|
175
|
+
self._edit_file(
|
|
176
|
+
yaml_file=self.entry_definition, yaml_parser=yaml_parser, data=loaded
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return is_last
|
|
180
|
+
|
|
181
|
+
def _comment_attribute(self, attribute_name, is_last=False):
|
|
182
|
+
"""Comment attribute in shell-definishion.yaml file."""
|
|
183
|
+
spaces = None
|
|
184
|
+
need_comment = False
|
|
185
|
+
lines = []
|
|
186
|
+
with open(self.entry_definition, "r", encoding="utf8") as f:
|
|
187
|
+
for line in f:
|
|
188
|
+
stripped = line.lstrip(" ")
|
|
189
|
+
if stripped.startswith("{}:".format(attribute_name)):
|
|
190
|
+
if is_last:
|
|
191
|
+
lines[-1] = "# {}".format(lines[-1])
|
|
192
|
+
spaces = len(line) - len(stripped)
|
|
193
|
+
need_comment = True
|
|
194
|
+
lines.append("# {}".format(line))
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
if need_comment and spaces and (len(line) - len(stripped)) > spaces:
|
|
198
|
+
lines.append("# {}".format(line))
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
need_comment = False
|
|
202
|
+
lines.append(line)
|
|
203
|
+
|
|
204
|
+
with open(self.entry_definition, "w", encoding="utf8") as f:
|
|
205
|
+
f.writelines(lines)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
import mimetypes
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import xml.etree.ElementTree as etree
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from shellfoundry.utilities.archive_creator import ArchiveCreator
|
|
10
|
+
from shellfoundry.utilities.shell_datamodel_merger import ShellDataModelMerger
|
|
11
|
+
from shellfoundry.utilities.version_utilities import DriverVersionTimestampBased
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PackageBuilder(object):
|
|
15
|
+
def __init__(self, driver_version_strategy=None):
|
|
16
|
+
self.driver_version_strategy = (
|
|
17
|
+
driver_version_strategy or DriverVersionTimestampBased()
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def build_package(self, path, package_name, driver_name):
|
|
21
|
+
package_path = os.path.join(path, "package")
|
|
22
|
+
self._copy_metadata(package_path, path)
|
|
23
|
+
self._copy_datamodel(package_path, path)
|
|
24
|
+
self._copy_categories(package_path, path)
|
|
25
|
+
self._copy_images(package_path, path)
|
|
26
|
+
self._copy_shellconfig(package_path, path)
|
|
27
|
+
self._create_driver(package_path, path, driver_name)
|
|
28
|
+
zip_path = self._zip_package(package_path, path, package_name)
|
|
29
|
+
shutil.rmtree(path=package_path, ignore_errors=True)
|
|
30
|
+
click.echo("Shell package was successfully created:")
|
|
31
|
+
click.echo(zip_path)
|
|
32
|
+
|
|
33
|
+
def _copy_metadata(self, package_path, path):
|
|
34
|
+
src_file_path = os.path.join(path, "datamodel", "metadata.xml")
|
|
35
|
+
PackageBuilder._copy_file(package_path, src_file_path)
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def _get_file_content_as_string(path):
|
|
39
|
+
with codecs.open(path, "r", encoding="utf8") as f:
|
|
40
|
+
text = f.read()
|
|
41
|
+
return text
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def _save_to_utf_file(content, dest_path):
|
|
45
|
+
with codecs.open(dest_path, "w", "utf-8-sig") as f:
|
|
46
|
+
if isinstance(content, bytes):
|
|
47
|
+
content = content.decode()
|
|
48
|
+
f.write(content)
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _save_to_file(content, dest_path):
|
|
52
|
+
with codecs.open(dest_path, "w") as f:
|
|
53
|
+
if isinstance(content, bytes):
|
|
54
|
+
content = content.decode()
|
|
55
|
+
f.write(content)
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _copy_datamodel(package_path, path):
|
|
59
|
+
shell_model_path = os.path.join(path, "datamodel", "shell_model.xml")
|
|
60
|
+
src_dm_file_path = os.path.join(path, "datamodel", "datamodel.xml")
|
|
61
|
+
dest_dir_path = os.path.join(package_path, "DataModel")
|
|
62
|
+
|
|
63
|
+
if os.path.exists(shell_model_path):
|
|
64
|
+
shell_model = PackageBuilder._get_file_content_as_string(shell_model_path)
|
|
65
|
+
dm = PackageBuilder._get_file_content_as_string(src_dm_file_path)
|
|
66
|
+
merger = ShellDataModelMerger()
|
|
67
|
+
merged_dm = merger.merge_shell_model(dm, shell_model)
|
|
68
|
+
if not os.path.exists(dest_dir_path):
|
|
69
|
+
os.makedirs(dest_dir_path)
|
|
70
|
+
PackageBuilder._save_to_utf_file(
|
|
71
|
+
merged_dm, os.path.join(dest_dir_path, "datamodel.xml")
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
else:
|
|
75
|
+
PackageBuilder._copy_file(dest_dir_path, src_dm_file_path)
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def _is_image(file):
|
|
79
|
+
file_type, encoding = mimetypes.guess_type(file)
|
|
80
|
+
return file_type and "image" in file_type
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def _copy_images(package_path, path):
|
|
84
|
+
dest_dir_path = os.path.join(package_path, "DataModel")
|
|
85
|
+
datamodel_dir = os.path.join(path, "datamodel")
|
|
86
|
+
for root, _, files in os.walk(datamodel_dir):
|
|
87
|
+
images = [
|
|
88
|
+
dir_file for dir_file in files if PackageBuilder._is_image(dir_file)
|
|
89
|
+
]
|
|
90
|
+
for image in images:
|
|
91
|
+
PackageBuilder._copy_file(dest_dir_path, os.path.join(root, image))
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def _copy_file(dest_dir_path, src_file_path):
|
|
95
|
+
if not os.path.exists(dest_dir_path):
|
|
96
|
+
os.makedirs(dest_dir_path)
|
|
97
|
+
shutil.copy(src_file_path, dest_dir_path)
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def _copy_shellconfig(package_path, path):
|
|
101
|
+
src_file_path = os.path.join(path, "datamodel", "shellconfig.xml")
|
|
102
|
+
if os.path.exists(src_file_path):
|
|
103
|
+
dest_dir_path = os.path.join(package_path, "Configuration")
|
|
104
|
+
PackageBuilder._copy_file(dest_dir_path, src_file_path)
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def _copy_categories(package_path, path):
|
|
108
|
+
src_file_path = os.path.join(path, "categories", "categories.xml")
|
|
109
|
+
if os.path.exists(src_file_path):
|
|
110
|
+
dest_dir_path = os.path.join(package_path, "Categories")
|
|
111
|
+
PackageBuilder._copy_file(dest_dir_path, src_file_path)
|
|
112
|
+
|
|
113
|
+
def _create_driver(self, package_path, path, driver_name):
|
|
114
|
+
dir_to_zip = os.path.join(path, "src")
|
|
115
|
+
drivermetadata_path = os.path.join(dir_to_zip, "drivermetadata.xml")
|
|
116
|
+
version = self._update_driver_version(drivermetadata_path)
|
|
117
|
+
zip_file_path = os.path.join(
|
|
118
|
+
package_path, "Resource Drivers - Python", driver_name
|
|
119
|
+
)
|
|
120
|
+
ArchiveCreator.make_archive(zip_file_path, "zip", dir_to_zip)
|
|
121
|
+
if version: # version was replaced
|
|
122
|
+
self._update_driver_version(drivermetadata_path, version)
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def _parse_xml(xml_string):
|
|
126
|
+
parser = etree.XMLParser(encoding="utf-8")
|
|
127
|
+
return etree.fromstring(xml_string, parser)
|
|
128
|
+
|
|
129
|
+
def _update_driver_version(self, metadata_path, version=""):
|
|
130
|
+
if not os.path.isfile(metadata_path):
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
metadata = self._get_file_content_as_string(metadata_path)
|
|
134
|
+
metadata_xml = self._parse_xml(metadata)
|
|
135
|
+
curver = metadata_xml.get("Version")
|
|
136
|
+
|
|
137
|
+
if version:
|
|
138
|
+
metadata_xml.set("Version", version)
|
|
139
|
+
self._save_to_file(etree.tostring(metadata_xml), metadata_path)
|
|
140
|
+
return None
|
|
141
|
+
elif self.driver_version_strategy.supports_version_pattern(curver):
|
|
142
|
+
newver = self.driver_version_strategy.get_version(curver)
|
|
143
|
+
metadata_xml.set("Version", newver)
|
|
144
|
+
self._save_to_file(etree.tostring(metadata_xml), metadata_path)
|
|
145
|
+
return curver
|
|
146
|
+
else:
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def _zip_package(package_path, path, package_name):
|
|
151
|
+
zip_file_path = os.path.join(path, "dist", package_name)
|
|
152
|
+
return ArchiveCreator.make_archive(zip_file_path, "zip", package_path)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from pip import main as pip_main
|
|
9
|
+
except Exception:
|
|
10
|
+
from pip._internal import main as pip_main
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PythonDependenciesPackager(object):
|
|
14
|
+
CS_PYPI_PORT = 8036
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
def save_offline_dependencies(
|
|
20
|
+
self, requirements_path, dest_path, cs_server_address=None
|
|
21
|
+
):
|
|
22
|
+
|
|
23
|
+
if os.path.isdir(dest_path):
|
|
24
|
+
shutil.rmtree(path=dest_path, ignore_errors=True)
|
|
25
|
+
|
|
26
|
+
if not os.path.exists(requirements_path):
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
proxy = os.environ.get("http_proxy")
|
|
30
|
+
pip_args = ["download"]
|
|
31
|
+
if proxy:
|
|
32
|
+
pip_args.append("--proxy")
|
|
33
|
+
pip_args.append(proxy)
|
|
34
|
+
|
|
35
|
+
if cs_server_address:
|
|
36
|
+
pip_args.append(
|
|
37
|
+
"--trusted-host={cs_server_address}".format(
|
|
38
|
+
cs_server_address=cs_server_address
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
pip_args.append(
|
|
42
|
+
"--extra-index-url=http://{cs_server_address}:{cs_pypi_port}".format(
|
|
43
|
+
cs_server_address=cs_server_address, cs_pypi_port=self.CS_PYPI_PORT
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
pip_args.append(
|
|
48
|
+
"--requirement={requirements_path}".format(
|
|
49
|
+
requirements_path=requirements_path
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
pip_args.append("--dest={dest_path}".format(dest_path=dest_path))
|
|
53
|
+
pip_main(pip_args)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import zipfile
|
|
6
|
+
from abc import ABCMeta, abstractmethod
|
|
7
|
+
from io import open
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from .template_url import construct_template_url
|
|
12
|
+
|
|
13
|
+
from shellfoundry.exceptions import VersionRequestException
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DownloadedRepoExtractor:
|
|
17
|
+
def __init__(self):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
__metaclass__ = ABCMeta
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def extract_to_folder(self, repo_link, folder):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ZipDownloadedRepoExtractor(DownloadedRepoExtractor):
|
|
28
|
+
def extract_to_folder(self, repo_link, folder):
|
|
29
|
+
super(ZipDownloadedRepoExtractor, self).extract_to_folder(repo_link, folder)
|
|
30
|
+
with zipfile.ZipFile(repo_link, "r") as z:
|
|
31
|
+
infos = z.infolist()
|
|
32
|
+
z.extractall(folder)
|
|
33
|
+
return [info.filename for info in infos]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RepositoryDownloader(object):
|
|
37
|
+
def __init__(self, repo_extractor=ZipDownloadedRepoExtractor()):
|
|
38
|
+
self.repo_extractor = repo_extractor
|
|
39
|
+
|
|
40
|
+
def download_template(
|
|
41
|
+
self, target_dir, repo_address, branch, is_need_construct=True
|
|
42
|
+
):
|
|
43
|
+
if is_need_construct:
|
|
44
|
+
download_url = construct_template_url(repo_address, branch)
|
|
45
|
+
else:
|
|
46
|
+
download_url = repo_address
|
|
47
|
+
archive_path = ""
|
|
48
|
+
try:
|
|
49
|
+
archive_path = self.download_file(download_url, target_dir)
|
|
50
|
+
|
|
51
|
+
repo_content = self.repo_extractor.extract_to_folder(
|
|
52
|
+
archive_path, target_dir
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# The first entry is always the root folder by git zipball convention
|
|
56
|
+
root_dir = repo_content[0]
|
|
57
|
+
|
|
58
|
+
return os.path.join(target_dir, root_dir)
|
|
59
|
+
finally:
|
|
60
|
+
if os.path.exists(archive_path):
|
|
61
|
+
os.remove(archive_path)
|
|
62
|
+
|
|
63
|
+
def download_file(self, url, directory):
|
|
64
|
+
local_filename = os.path.join(directory, url.split("/")[-1])
|
|
65
|
+
# NOTE the stream=True parameter
|
|
66
|
+
r = requests.get(url, stream=True)
|
|
67
|
+
if r.status_code != requests.codes.ok:
|
|
68
|
+
raise VersionRequestException(
|
|
69
|
+
"Failed to download zip file from {}".format(url)
|
|
70
|
+
)
|
|
71
|
+
with open(local_filename, "wb") as f:
|
|
72
|
+
for chunk in r.iter_content(chunk_size=1024):
|
|
73
|
+
if chunk: # filter out keep-alive new chunks
|
|
74
|
+
f.write(chunk)
|
|
75
|
+
# f.flush() commented by recommendation from J.F.Sebastian
|
|
76
|
+
return local_filename
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import os
|
|
4
|
+
from io import open
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
from shellfoundry.exceptions import ShellYmlMissingException, WrongShellYmlException
|
|
9
|
+
|
|
10
|
+
VERSION = "version"
|
|
11
|
+
DESCRIPTION = "description"
|
|
12
|
+
EMAIL = "email"
|
|
13
|
+
AUTHOR = "author"
|
|
14
|
+
NAME = "name"
|
|
15
|
+
SHELL = "shell"
|
|
16
|
+
DRIVER_NAME = "driver_name"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ProjectConfig(object):
|
|
20
|
+
def __init__(self, name, author, email, description, version, driver_name):
|
|
21
|
+
self.version = version
|
|
22
|
+
self.description = description
|
|
23
|
+
self.email = email
|
|
24
|
+
self.author = author
|
|
25
|
+
self.name = name
|
|
26
|
+
self.driver_name = driver_name
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ShellConfigReader(object):
|
|
30
|
+
def read(self):
|
|
31
|
+
config_path = os.path.join(os.getcwd(), "shell.yml")
|
|
32
|
+
|
|
33
|
+
if not os.path.isfile(config_path):
|
|
34
|
+
raise ShellYmlMissingException("shell.yml is missing")
|
|
35
|
+
|
|
36
|
+
with open(config_path, encoding="utf8") as stream:
|
|
37
|
+
config = yaml.safe_load(stream.read())
|
|
38
|
+
|
|
39
|
+
if not config or SHELL not in config:
|
|
40
|
+
raise WrongShellYmlException("shell section is missing in shell.yml")
|
|
41
|
+
|
|
42
|
+
install_config = config[SHELL]
|
|
43
|
+
|
|
44
|
+
name = self._get_with_default(install_config, NAME, "")
|
|
45
|
+
author = self._get_with_default(install_config, AUTHOR, "")
|
|
46
|
+
email = self._get_with_default(install_config, EMAIL, "")
|
|
47
|
+
description = self._get_with_default(install_config, DESCRIPTION, "")
|
|
48
|
+
version = self._get_with_default(install_config, VERSION, "")
|
|
49
|
+
driver_name = self._get_with_default(install_config, DRIVER_NAME, "")
|
|
50
|
+
|
|
51
|
+
return ProjectConfig(name, author, email, description, version, driver_name)
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _get_with_default(install_config, parameter_name, default_value):
|
|
55
|
+
return (
|
|
56
|
+
install_config[parameter_name]
|
|
57
|
+
if install_config and parameter_name in install_config
|
|
58
|
+
else default_value
|
|
59
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import xml.etree.ElementTree as etree
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ShellDataModelMerger:
|
|
7
|
+
def _parse_xml(self, xml_string):
|
|
8
|
+
parser = etree.XMLParser(encoding="utf-8")
|
|
9
|
+
return etree.fromstring(xml_string, parser)
|
|
10
|
+
|
|
11
|
+
def merge_shell_model(self, datamodel, shell_model):
|
|
12
|
+
etree.register_namespace(
|
|
13
|
+
"",
|
|
14
|
+
"http://schemas.qualisystems.com/ResourceManagement/DataModelSchema.xsd", # noqa: E501
|
|
15
|
+
)
|
|
16
|
+
datamodel_tree = self._parse_xml(datamodel)
|
|
17
|
+
shell_tree = self._parse_xml(shell_model)
|
|
18
|
+
|
|
19
|
+
shell_family_element = shell_tree.find(".//ShellModel")
|
|
20
|
+
if shell_family_element is None:
|
|
21
|
+
raise Exception("Missing ShellModel element in shell_model.xml file")
|
|
22
|
+
|
|
23
|
+
family_name = shell_family_element.get("Family")
|
|
24
|
+
|
|
25
|
+
family_xpath_expression = ".//{{http://schemas.qualisystems.com/ResourceManagement/DataModelSchema.xsd}}ResourceFamily[@Name='{family_name}']".format( # noqa: E501
|
|
26
|
+
family_name=family_name
|
|
27
|
+
)
|
|
28
|
+
dm_family_element = datamodel_tree.find(family_xpath_expression)
|
|
29
|
+
|
|
30
|
+
if dm_family_element is None:
|
|
31
|
+
raise Exception("Shell family not found:" + family_name)
|
|
32
|
+
model_insertion_point = dm_family_element.find(
|
|
33
|
+
".//{http://schemas.qualisystems.com/ResourceManagement/DataModelSchema.xsd}Models" # noqa: E501
|
|
34
|
+
)
|
|
35
|
+
models = shell_tree.find(".//ShellModel")
|
|
36
|
+
model_insertion_point.extend(models)
|
|
37
|
+
|
|
38
|
+
attributes = shell_tree.find(".//ShellAttributes")
|
|
39
|
+
attributes_insertion_point = datamodel_tree.find(
|
|
40
|
+
".//{http://schemas.qualisystems.com/ResourceManagement/DataModelSchema.xsd}Attributes" # noqa: E501
|
|
41
|
+
)
|
|
42
|
+
attributes_insertion_point.extend(attributes)
|
|
43
|
+
|
|
44
|
+
return etree.tostring(datamodel_tree)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import os
|
|
4
|
+
from io import open
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
LAYER_ONE_PREFIX = "CloudshellL1"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ShellPackage(object):
|
|
12
|
+
def __init__(self, path):
|
|
13
|
+
self.path = path
|
|
14
|
+
self.real_shell_name = None
|
|
15
|
+
|
|
16
|
+
def get_shell_name(self):
|
|
17
|
+
"""Returns shell name."""
|
|
18
|
+
head, shell_name = os.path.split(self.path)
|
|
19
|
+
return shell_name.title().replace("-", "").replace("_", "")
|
|
20
|
+
|
|
21
|
+
def get_name_from_definition(self, should_reload=False):
|
|
22
|
+
"""Get shell name from shell-definition.yaml.
|
|
23
|
+
|
|
24
|
+
:param bool should_reload: Should reload from
|
|
25
|
+
:return: template name section from shell-definition.yml or equivalent written in entry definition in tosca.meta # noqa: E501
|
|
26
|
+
:rtype: str
|
|
27
|
+
"""
|
|
28
|
+
# reload the shell name if persisted member is empty or explicit request for reload # noqa: E501
|
|
29
|
+
if not self.real_shell_name or should_reload:
|
|
30
|
+
self._reload_name()
|
|
31
|
+
return self.real_shell_name
|
|
32
|
+
|
|
33
|
+
def is_layer_one(self):
|
|
34
|
+
"""Determines whether a shell is Layer 1."""
|
|
35
|
+
return bool(LAYER_ONE_PREFIX in self.get_shell_name())
|
|
36
|
+
|
|
37
|
+
def is_tosca(self):
|
|
38
|
+
"""Determines whether a shell is a TOSCA based shell."""
|
|
39
|
+
return os.path.exists(self.get_metadata_path())
|
|
40
|
+
|
|
41
|
+
def get_metadata_path(self):
|
|
42
|
+
"""Returns file path of the TOSCA meta file."""
|
|
43
|
+
return os.path.join(self.path, "TOSCA-Metadata", "TOSCA.meta")
|
|
44
|
+
|
|
45
|
+
def _reload_name(self):
|
|
46
|
+
"""Reloads the name from the entry definition in the tosca.meta file."""
|
|
47
|
+
# fetch entry definition from tosca.meta file
|
|
48
|
+
with open(self.get_metadata_path()) as stream:
|
|
49
|
+
s = str(stream.read())
|
|
50
|
+
entry_definition = dict(
|
|
51
|
+
list(map(str.strip, line.split(":", 1)))
|
|
52
|
+
for line in s.splitlines()
|
|
53
|
+
if line.strip()
|
|
54
|
+
)["Entry-Definitions"]
|
|
55
|
+
|
|
56
|
+
# fetch template name from entry definition file retrieved earlier
|
|
57
|
+
with open(os.path.join(self.path, entry_definition), encoding="utf8") as stream:
|
|
58
|
+
definition = yaml.safe_load(stream)
|
|
59
|
+
self.real_shell_name = definition["metadata"]["template_name"]
|