1
|
|
|
# !/usr/bin/env python |
2
|
|
|
# -*- coding: UTF-8 -*- |
3
|
|
|
|
4
|
|
|
# Isomer - The distributed application framework |
5
|
|
|
# ============================================== |
6
|
|
|
# Copyright (C) 2011-2020 Heiko 'riot' Weinen <[email protected]> and others. |
7
|
|
|
# |
8
|
|
|
# This program is free software: you can redistribute it and/or modify |
9
|
|
|
# it under the terms of the GNU Affero General Public License as published by |
10
|
|
|
# the Free Software Foundation, either version 3 of the License, or |
11
|
|
|
# (at your option) any later version. |
12
|
|
|
# |
13
|
|
|
# This program is distributed in the hope that it will be useful, |
14
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
15
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16
|
|
|
# GNU Affero General Public License for more details. |
17
|
|
|
# |
18
|
|
|
# You should have received a copy of the GNU Affero General Public License |
19
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
20
|
|
|
|
21
|
|
|
""" |
22
|
|
|
|
23
|
|
|
Module: Store |
24
|
|
|
============= |
25
|
|
|
|
26
|
|
|
Software inventory functionality |
27
|
|
|
|
28
|
|
|
""" |
29
|
|
|
|
30
|
|
|
import os |
31
|
|
|
import base64 |
32
|
|
|
from docutils import core |
33
|
|
|
from typing import Tuple |
34
|
|
|
|
35
|
|
|
import requests |
36
|
|
|
import zipfile |
37
|
|
|
|
38
|
|
|
from urllib.parse import urljoin |
39
|
|
|
|
40
|
|
|
from isomer.misc.std import std_datetime |
41
|
|
|
from isomer.logger import isolog, debug |
42
|
|
|
from isomer.ui.store import DEFAULT_STORE_URL |
43
|
|
|
|
44
|
|
|
_STORE = {} |
45
|
|
|
|
46
|
|
|
|
47
|
|
|
def log(*args, **kwargs): |
48
|
|
|
"""Log as store emitter""" |
49
|
|
|
kwargs.update({"emitter": "STORE", "frame_ref": 2}) |
50
|
|
|
isolog(*args, **kwargs) |
51
|
|
|
|
52
|
|
|
|
53
|
|
|
def get_store(source=DEFAULT_STORE_URL, |
54
|
|
|
auth: Tuple[str, str] = None): |
55
|
|
|
"""Return current store |
56
|
|
|
|
57
|
|
|
This function acts as cache for |
58
|
|
|
:func: `~isomer.store.inventory.populate_store` |
59
|
|
|
|
60
|
|
|
:param: source: Optionally, specify alternative store url |
61
|
|
|
:param: auth: Optional tuple containing username and password""" |
62
|
|
|
|
63
|
|
|
if _STORE == {}: |
64
|
|
|
return populate_store(source, auth) |
65
|
|
|
else: |
66
|
|
|
return _STORE |
67
|
|
|
|
68
|
|
|
|
69
|
|
|
def inspect_wheel(wheel: str): |
70
|
|
|
"""Inspeggtor reporting for duty! |
71
|
|
|
|
72
|
|
|
This thing opens up Python Eggs to analyze their content in an isomeric way. |
73
|
|
|
Some data pulled out is specific to isomer modules but could be used for general |
74
|
|
|
structures. Specifically, a `docs/README.rst` and a `docs/preview.png` is looked |
75
|
|
|
for. |
76
|
|
|
|
77
|
|
|
:param wheel: Filename of the python egg to inspect |
78
|
|
|
:return: Dictionary with |
79
|
|
|
`info` - content of docs/README.rst if found |
80
|
|
|
`preview` - content of docs/preview.png if found |
81
|
|
|
`date` - publishing date according to EGG-INFO/PKG-INFO timestamp |
82
|
|
|
`requires` - any required external python packages |
83
|
|
|
:rtype: dict |
84
|
|
|
""" |
85
|
|
|
archive = zipfile.ZipFile(wheel) |
86
|
|
|
|
87
|
|
|
meta_name = os.path.basename(wheel).split("-py")[0] + ".dist-info/METADATA" |
88
|
|
|
|
89
|
|
|
try: |
90
|
|
|
info = archive.read("docs/README.rst").decode('utf-8') |
91
|
|
|
|
92
|
|
|
info = core.publish_parts(info, writer_name="html")["html_body"] |
93
|
|
|
except KeyError: |
94
|
|
|
info = "No information provided" |
95
|
|
|
|
96
|
|
|
try: |
97
|
|
|
preview = base64.b64encode(archive.read("docs/preview.png")).decode('utf-8') |
98
|
|
|
except KeyError: |
99
|
|
|
preview = "" |
100
|
|
|
|
101
|
|
|
pkg_info_info = archive.getinfo(meta_name) |
102
|
|
|
|
103
|
|
|
date = std_datetime(pkg_info_info.date_time) |
104
|
|
|
|
105
|
|
|
requires = [] |
106
|
|
|
|
107
|
|
|
homepage = "" |
108
|
|
|
author = "" |
109
|
|
|
contact = "" |
110
|
|
|
package_license = "" |
111
|
|
|
|
112
|
|
|
try: |
113
|
|
|
lines = str(archive.read(meta_name), encoding="ascii").split("\n") |
114
|
|
|
|
115
|
|
|
for line in lines: |
116
|
|
|
if line.startswith("Home-page:"): |
117
|
|
|
homepage = line.split("Home-page: ")[1] |
118
|
|
|
if line.startswith("Author:"): |
119
|
|
|
author = line.split("Author: ")[1] |
120
|
|
|
if line.startswith("Author-email:"): |
121
|
|
|
contact = line.split("Author-email: ")[1] |
122
|
|
|
if line.startswith("License:"): |
123
|
|
|
package_license = line.split("License: ")[1] |
124
|
|
|
if line.startswith("Requires-Dist:"): |
125
|
|
|
req = line.split("Requires-Dist: ")[1] |
126
|
|
|
req = req.replace(" (", "").replace(")", "") |
127
|
|
|
requires.append(req) |
128
|
|
|
except KeyError: |
129
|
|
|
log("No metadata found") |
130
|
|
|
|
131
|
|
|
result = { |
132
|
|
|
'info': info, |
133
|
|
|
'preview': preview, |
134
|
|
|
'date': date, |
135
|
|
|
'requires': requires, |
136
|
|
|
'homepage': homepage, |
137
|
|
|
'author': author, |
138
|
|
|
'contact': contact, |
139
|
|
|
'license': package_license, |
140
|
|
|
'downloads': "-", |
141
|
|
|
'stars': "-" |
142
|
|
|
} |
143
|
|
|
log(result, pretty=True) |
144
|
|
|
|
145
|
|
|
return result |
146
|
|
|
|
147
|
|
|
|
148
|
|
|
def populate_store(source=DEFAULT_STORE_URL, |
149
|
|
|
auth: Tuple[str, str] = None): |
150
|
|
|
"""Grab data from the isomer software store""" |
151
|
|
|
|
152
|
|
|
global _STORE |
153
|
|
|
|
154
|
|
|
log("Getting store information for", source, lvl=debug) |
155
|
|
|
|
156
|
|
|
url = urljoin(source, "/store") |
157
|
|
|
data = requests.get(url, auth=auth) |
158
|
|
|
index = data.json() |
159
|
|
|
|
160
|
|
|
_STORE = { |
161
|
|
|
'source': source, |
162
|
|
|
'auth': auth, |
163
|
|
|
'packages': index |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
return _STORE |
167
|
|
|
|
168
|
|
|
|
169
|
|
|
def get_inventory(ctx): |
170
|
|
|
"""Check local instance and its environments for installed modules""" |
171
|
|
|
|
172
|
|
|
instance_configuration = ctx.obj['instance_configuration'] |
173
|
|
|
environment = instance_configuration['environment'] |
174
|
|
|
environment_modules = instance_configuration['environments'][environment]['modules'] |
175
|
|
|
|
176
|
|
|
instance_modules = instance_configuration['modules'] |
177
|
|
|
|
178
|
|
|
log("Instance:", instance_modules, pretty=True) |
179
|
|
|
|
180
|
|
|
result = { |
181
|
|
|
'instance': instance_modules, |
182
|
|
|
'current': environment_modules |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
return result |
186
|
|
|
|