syncloud-lib 333__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.
- syncloud_lib-333.dist-info/METADATA +21 -0
- syncloud_lib-333.dist-info/RECORD +31 -0
- syncloud_lib-333.dist-info/WHEEL +5 -0
- syncloud_lib-333.dist-info/licenses/LICENSE +675 -0
- syncloud_lib-333.dist-info/top_level.txt +1 -0
- syncloudlib/__init__.py +0 -0
- syncloudlib/application/__init__.py +0 -0
- syncloudlib/application/config.py +9 -0
- syncloudlib/application/connection.py +40 -0
- syncloudlib/application/paths.py +9 -0
- syncloudlib/application/ports.py +9 -0
- syncloudlib/application/service.py +6 -0
- syncloudlib/application/storage.py +9 -0
- syncloudlib/application/urls.py +11 -0
- syncloudlib/application/users.py +5 -0
- syncloudlib/error.py +6 -0
- syncloudlib/fs.py +44 -0
- syncloudlib/http.py +28 -0
- syncloudlib/integration/__init__.py +0 -0
- syncloudlib/integration/conftest.py +247 -0
- syncloudlib/integration/device.py +111 -0
- syncloudlib/integration/hosts.py +9 -0
- syncloudlib/integration/installer.py +78 -0
- syncloudlib/integration/loop.py +65 -0
- syncloudlib/integration/screenshots.py +7 -0
- syncloudlib/integration/selenium_wrapper.py +127 -0
- syncloudlib/integration/ssh.py +51 -0
- syncloudlib/json/__init__.py +0 -0
- syncloudlib/json/convertible.py +114 -0
- syncloudlib/linux.py +15 -0
- syncloudlib/logger.py +54 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import requests_unixsocket
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
socket_file = '/var/snap/platform/common/api.socket'
|
|
6
|
+
socket = 'http+unix://{0}'.format(socket_file.replace('/', '%2F'))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def api_post(url, data):
|
|
10
|
+
session = requests_unixsocket.Session()
|
|
11
|
+
try:
|
|
12
|
+
response = session.post('{0}{1}'.format(socket, url), data=data)
|
|
13
|
+
if response.status_code == 200:
|
|
14
|
+
response_json = json.loads(response.text)
|
|
15
|
+
if 'success' in response_json and response_json['success']:
|
|
16
|
+
return response_json['data']
|
|
17
|
+
else:
|
|
18
|
+
raise Exception('service error: {0}'.format(response_json['message']))
|
|
19
|
+
|
|
20
|
+
else:
|
|
21
|
+
raise Exception('unable to connect to {0} with error code: {1}'.format(socket, response.status_code))
|
|
22
|
+
except Exception as e:
|
|
23
|
+
raise Exception('unable to connect to {0}'.format(socket), e)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def api_get(url):
|
|
27
|
+
session = requests_unixsocket.Session()
|
|
28
|
+
try:
|
|
29
|
+
response = session.get('{0}{1}'.format(socket, url))
|
|
30
|
+
if response.status_code == 200:
|
|
31
|
+
response_json = json.loads(response.text)
|
|
32
|
+
if 'success' in response_json and response_json['success']:
|
|
33
|
+
return response_json['data']
|
|
34
|
+
else:
|
|
35
|
+
raise Exception('service error: {0}'.format(response_json['message']))
|
|
36
|
+
|
|
37
|
+
else:
|
|
38
|
+
raise Exception('unable to connect to {0} with error code: {1}'.format(socket, response.status_code))
|
|
39
|
+
except Exception as e:
|
|
40
|
+
raise Exception('unable to connect to {0}'.format(socket), e)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from syncloudlib.application.connection import api_post
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def add_port(port, protocol):
|
|
5
|
+
return api_post('/port/add', data={"port": port, "protocol": protocol})
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def remove_port(port, protocol):
|
|
9
|
+
return api_post('/port/remove', data={"port": port, "protocol": protocol})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from syncloudlib.application.connection import api_post, api_get
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def init_storage(app, user):
|
|
5
|
+
return api_post('/app/init_storage', data={"app_name": app, "user_name": user})
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_storage_dir(app):
|
|
9
|
+
return api_get('/app/storage_dir?name={0}'.format(app))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from syncloudlib.application.connection import api_get
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_app_url(app):
|
|
5
|
+
return api_get('/app/url?name={0}'.format(app))
|
|
6
|
+
|
|
7
|
+
def get_device_domain_name():
|
|
8
|
+
return api_get('/app/device_domain_name')
|
|
9
|
+
|
|
10
|
+
def get_app_domain_name(app):
|
|
11
|
+
return api_get('/app/domain_name?name={0}'.format(app))
|
syncloudlib/error.py
ADDED
syncloudlib/fs.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from os import makedirs, chown, utime
|
|
2
|
+
from os.path import isdir
|
|
3
|
+
from grp import getgrnam
|
|
4
|
+
from pwd import getpwnam
|
|
5
|
+
from shutil import rmtree
|
|
6
|
+
from subprocess import check_output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def makepath(path):
|
|
10
|
+
if not isdir(path):
|
|
11
|
+
makedirs(path)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def removepath(path):
|
|
15
|
+
if isdir(path):
|
|
16
|
+
rmtree(path, ignore_errors=True)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def createfile(filepath):
|
|
20
|
+
try:
|
|
21
|
+
f = open(filepath, 'w+')
|
|
22
|
+
f.close()
|
|
23
|
+
except:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def chownpath(path, user, recursive=False):
|
|
28
|
+
if recursive:
|
|
29
|
+
chownrecursive(path, user)
|
|
30
|
+
else:
|
|
31
|
+
chown(path, getpwnam(user).pw_uid, getgrnam(user).gr_gid)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def chownrecursive(path, user):
|
|
35
|
+
estimate_count = int(check_output('find {0} -maxdepth 3 | wc -l'.format(path), shell=True).decode())
|
|
36
|
+
if estimate_count > 1000:
|
|
37
|
+
return 'not changing permissions, too many files'
|
|
38
|
+
|
|
39
|
+
return check_output('chown -RLf {0}. {1}'.format(user, path), shell=True).decode()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def touchfile(file):
|
|
43
|
+
with open(file, 'a'):
|
|
44
|
+
utime(file, None)
|
syncloudlib/http.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def wait_for_rest(web_session, url, code, attempts=10):
|
|
5
|
+
def pred(resp):
|
|
6
|
+
return resp.status_code == code
|
|
7
|
+
|
|
8
|
+
wait_for_response(web_session, url, pred, attempts)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def wait_for_response(web_session, url, resp_predicate, attempts=10):
|
|
12
|
+
|
|
13
|
+
attempt=0
|
|
14
|
+
attempt_limit=attempts
|
|
15
|
+
response = None
|
|
16
|
+
while attempt < attempt_limit:
|
|
17
|
+
try:
|
|
18
|
+
response = web_session.get(url, verify=False)
|
|
19
|
+
print('code: {0}'.format(response.status_code))
|
|
20
|
+
if resp_predicate(response):
|
|
21
|
+
return
|
|
22
|
+
except Exception as e:
|
|
23
|
+
print(str(e))
|
|
24
|
+
time.sleep(10)
|
|
25
|
+
attempt = attempt + 1
|
|
26
|
+
if response and response.text:
|
|
27
|
+
print(response.text)
|
|
28
|
+
raise Exception('exhausted')
|
|
File without changes
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import os
|
|
3
|
+
from os.path import join, exists
|
|
4
|
+
from selenium import webdriver
|
|
5
|
+
|
|
6
|
+
from syncloudlib.integration.installer import get_data_dir, get_app_dir, get_service_prefix, get_ssh_env_vars, get_snap_data_dir
|
|
7
|
+
from syncloudlib.integration.device import Device
|
|
8
|
+
from syncloudlib.integration.selenium_wrapper import SeleniumWrapper
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def pytest_addoption(parser):
|
|
12
|
+
parser.addoption("--domain", action="store", default="device.com")
|
|
13
|
+
parser.addoption("--device-host", action="store")
|
|
14
|
+
parser.addoption("--app-archive-path", action="store")
|
|
15
|
+
parser.addoption("--app", action="store")
|
|
16
|
+
parser.addoption("--ui-mode", action="store", default="desktop")
|
|
17
|
+
parser.addoption("--device-user", action="store", default="user")
|
|
18
|
+
parser.addoption("--build-number", action="store", default="local")
|
|
19
|
+
parser.addoption("--browser", action="store", default="firefox")
|
|
20
|
+
parser.addoption("--browser-height", action="store", default=1000)
|
|
21
|
+
parser.addoption("--redirect-user", action="store", default="redirect-user-notset")
|
|
22
|
+
parser.addoption("--redirect-password", action="store", default="redirect-password-notset")
|
|
23
|
+
parser.addoption("--distro", action="store", default="distro")
|
|
24
|
+
parser.addoption("--arch", action="store", default="unset-arch")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.fixture(scope='session')
|
|
28
|
+
def build_number(request):
|
|
29
|
+
return request.config.getoption("--build-number")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.fixture(scope='session')
|
|
33
|
+
def device_user(request):
|
|
34
|
+
return request.config.getoption("--device-user")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture(scope='session')
|
|
38
|
+
def device_password():
|
|
39
|
+
return 'Password1'
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture(scope='session')
|
|
43
|
+
def redirect_user(request):
|
|
44
|
+
return request.config.getoption("--redirect-user")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture(scope='session')
|
|
48
|
+
def redirect_password(request):
|
|
49
|
+
return request.config.getoption("--redirect-password")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.fixture(scope='session')
|
|
53
|
+
def app(request):
|
|
54
|
+
return request.config.getoption("--app")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.fixture(scope='session')
|
|
58
|
+
def ui_mode(request):
|
|
59
|
+
return request.config.getoption("--ui-mode")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.fixture(scope='session')
|
|
63
|
+
def app_archive_path(request):
|
|
64
|
+
return request.config.getoption("--app-archive-path")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.fixture(scope='session')
|
|
68
|
+
def device_host(request, app, domain):
|
|
69
|
+
device_host = request.config.getoption("--device-host")
|
|
70
|
+
if device_host:
|
|
71
|
+
return device_host
|
|
72
|
+
return '{0}.{1}'.format(app, domain)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.fixture(scope='session')
|
|
76
|
+
def domain(request, distro):
|
|
77
|
+
domain = request.config.getoption("--domain")
|
|
78
|
+
if domain:
|
|
79
|
+
return domain
|
|
80
|
+
return '{0}.com'.format(distro)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@pytest.fixture(scope='session')
|
|
84
|
+
def browser(request):
|
|
85
|
+
return request.config.getoption("--browser")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@pytest.fixture(scope='session')
|
|
89
|
+
def browser_height(request):
|
|
90
|
+
return int(request.config.getoption("--browser-height"))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.fixture(scope='session')
|
|
94
|
+
def distro(request):
|
|
95
|
+
return request.config.getoption("--distro")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.fixture(scope='session')
|
|
99
|
+
def arch(request):
|
|
100
|
+
return request.config.getoption("--arch")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@pytest.fixture(scope='session')
|
|
104
|
+
def app_domain(app, domain):
|
|
105
|
+
return '{0}.{1}'.format(app, domain)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@pytest.fixture(scope="session")
|
|
109
|
+
def platform_data_dir():
|
|
110
|
+
return get_data_dir('platform')
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@pytest.fixture(scope="session")
|
|
114
|
+
def data_dir(app):
|
|
115
|
+
return get_data_dir(app)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@pytest.fixture(scope="session")
|
|
119
|
+
def snap_data_dir(app):
|
|
120
|
+
return get_snap_data_dir(app)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@pytest.fixture(scope="session")
|
|
124
|
+
def app_dir(app):
|
|
125
|
+
return get_app_dir(app)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@pytest.fixture(scope="session")
|
|
129
|
+
def service_prefix():
|
|
130
|
+
return get_service_prefix()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def new_firefox_driver(hub_url, ui_mode):
|
|
134
|
+
#desktop_agent = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/100.0"
|
|
135
|
+
mobile_agent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Mobile/15E148 Safari/604.1"
|
|
136
|
+
|
|
137
|
+
options = webdriver.FirefoxOptions()
|
|
138
|
+
options.set_preference('app.update.auto', False)
|
|
139
|
+
options.set_preference('app.update.enabled', False)
|
|
140
|
+
if ui_mode == "mobile":
|
|
141
|
+
options.set_preference("general.useragent.override", mobile_agent)
|
|
142
|
+
options.set_preference("devtools.console.stdout.content", True)
|
|
143
|
+
options.set_capability('acceptInsecureCerts', True)
|
|
144
|
+
options.set_capability('se:recordVideo', True)
|
|
145
|
+
options.set_preference("media.navigator.streams.fake", True)
|
|
146
|
+
options.set_preference("media.navigator.permission.disabled", True)
|
|
147
|
+
|
|
148
|
+
return webdriver.Remote(
|
|
149
|
+
command_executor=hub_url,
|
|
150
|
+
options=options
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def new_chrome_driver(hub_url, ui_mode):
|
|
155
|
+
#desktop_agent = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/100.0"
|
|
156
|
+
mobile_agent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Mobile/15E148 Safari/604.1"
|
|
157
|
+
|
|
158
|
+
options = webdriver.ChromeOptions()
|
|
159
|
+
if ui_mode == "mobile":
|
|
160
|
+
options.add_argument('user-agent={}'.format(mobile_agent))
|
|
161
|
+
#options.add_argument('--headless')
|
|
162
|
+
options.add_argument('--no-sandbox')
|
|
163
|
+
options.add_argument('--disable-dev-shm-usage')
|
|
164
|
+
options.set_capability('goog:loggingPrefs', {'performance': 'ALL'})
|
|
165
|
+
options.set_capability('acceptInsecureCerts', True)
|
|
166
|
+
options.set_capability('se:recordVideo', True)
|
|
167
|
+
options.add_argument("--use-fake-ui-for-media-stream")
|
|
168
|
+
options.add_argument("--use-fake-device-for-media-stream")
|
|
169
|
+
return webdriver.Remote(
|
|
170
|
+
command_executor=hub_url,
|
|
171
|
+
options=options
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@pytest.fixture(scope="session")
|
|
176
|
+
def driver(ui_mode, browser, browser_height, request):
|
|
177
|
+
hub_url = 'http://selenium:4444/wd/hub'
|
|
178
|
+
width = 1024
|
|
179
|
+
if ui_mode == "mobile":
|
|
180
|
+
width = 400
|
|
181
|
+
|
|
182
|
+
if browser == "firefox":
|
|
183
|
+
driver = new_firefox_driver(hub_url, ui_mode)
|
|
184
|
+
else:
|
|
185
|
+
driver = new_chrome_driver(hub_url, ui_mode)
|
|
186
|
+
driver.set_window_rect(0, 0, width, browser_height)
|
|
187
|
+
|
|
188
|
+
def driver_quit():
|
|
189
|
+
driver.quit()
|
|
190
|
+
|
|
191
|
+
request.addfinalizer(driver_quit)
|
|
192
|
+
|
|
193
|
+
return driver
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@pytest.fixture(scope="session")
|
|
197
|
+
def ssh_env_vars(app):
|
|
198
|
+
return get_ssh_env_vars(app)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@pytest.fixture(scope='function')
|
|
202
|
+
def device_session(device):
|
|
203
|
+
return device.login()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@pytest.fixture(scope="session")
|
|
207
|
+
def device(domain, device_user,
|
|
208
|
+
device_password, redirect_user, redirect_password, ssh_env_vars):
|
|
209
|
+
return Device(domain, device_user,
|
|
210
|
+
device_password, redirect_user, redirect_password, ssh_env_vars)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@pytest.fixture(scope="session")
|
|
214
|
+
def log_dir(project_dir):
|
|
215
|
+
dir = join(project_dir, 'log')
|
|
216
|
+
if not exists(dir):
|
|
217
|
+
os.mkdir(dir)
|
|
218
|
+
return dir
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@pytest.fixture(scope="session")
|
|
222
|
+
def artifact_dir(project_dir, distro):
|
|
223
|
+
dir = join(project_dir, 'artifact', distro)
|
|
224
|
+
if not exists(dir):
|
|
225
|
+
os.mkdir(dir)
|
|
226
|
+
return dir
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@pytest.fixture(scope="session")
|
|
230
|
+
def screenshot_dir(artifact_dir, ui_mode):
|
|
231
|
+
ui_dir = join(artifact_dir, ui_mode)
|
|
232
|
+
if not exists(ui_dir):
|
|
233
|
+
os.mkdir(ui_dir)
|
|
234
|
+
dir = join(ui_dir, 'screenshot')
|
|
235
|
+
if not exists(dir):
|
|
236
|
+
os.mkdir(dir)
|
|
237
|
+
return dir
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@pytest.fixture(scope="session")
|
|
241
|
+
def selenium_timeout():
|
|
242
|
+
return 30
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@pytest.fixture(scope="session")
|
|
246
|
+
def selenium(driver, ui_mode, screenshot_dir, app_domain, selenium_timeout, browser):
|
|
247
|
+
return SeleniumWrapper(driver, ui_mode, screenshot_dir, app_domain, selenium_timeout, browser)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from syncloudlib.integration.installer import wait_for_platform_web, wait_for_installer
|
|
4
|
+
from syncloudlib.http import wait_for_rest
|
|
5
|
+
from syncloudlib.integration.ssh import run_scp, run_ssh
|
|
6
|
+
from requests.adapters import HTTPAdapter
|
|
7
|
+
import socket
|
|
8
|
+
|
|
9
|
+
class Device:
|
|
10
|
+
|
|
11
|
+
def __init__(self, domain, device_user, device_password, redirect_user, redirect_password,
|
|
12
|
+
ssh_env_vars):
|
|
13
|
+
self.domain = domain
|
|
14
|
+
self.device_user = device_user
|
|
15
|
+
self.device_password = device_password
|
|
16
|
+
self.redirect_user = redirect_user
|
|
17
|
+
self.redirect_password = redirect_password
|
|
18
|
+
self.ssh_env_vars = ssh_env_vars
|
|
19
|
+
self.ssh_password = 'syncloud'
|
|
20
|
+
self.session = None
|
|
21
|
+
|
|
22
|
+
def deactivate(self):
|
|
23
|
+
run_ssh(self.domain, 'rm /var/snap/platform/common/platform.db', password=self.ssh_password)
|
|
24
|
+
|
|
25
|
+
def activate(self, channel="stable"):
|
|
26
|
+
ip = socket.gethostbyname(self.domain)
|
|
27
|
+
run_ssh(self.domain, 'echo "{0} auth.{1}" >> /etc/hosts'.format(ip, self.domain), password=self.ssh_password, retries=10)
|
|
28
|
+
run_ssh(self.domain, '/snap/platform/current/bin/upgrade-snapd.sh {0}'.format(channel), password=self.ssh_password, retries=10)
|
|
29
|
+
run_ssh(self.domain, 'snap refresh platform --channel={0}'.format(channel), password=self.ssh_password, retries=10)
|
|
30
|
+
|
|
31
|
+
wait_for_rest(requests.session(), "https://{0}/rest/id".format(self.domain), 200, 10)
|
|
32
|
+
|
|
33
|
+
response = requests.post('https://{0}/rest/activate/managed'.format(self.domain),
|
|
34
|
+
json={'redirect_email': self.redirect_user,
|
|
35
|
+
'redirect_password': self.redirect_password,
|
|
36
|
+
'domain': self.domain,
|
|
37
|
+
'device_username': self.device_user,
|
|
38
|
+
'device_password': self.device_password}, verify=False)
|
|
39
|
+
if response.status_code == 200:
|
|
40
|
+
self.activated()
|
|
41
|
+
self.login()
|
|
42
|
+
return response
|
|
43
|
+
|
|
44
|
+
def activate_custom(self, channel="stable"):
|
|
45
|
+
ip = socket.gethostbyname(self.domain)
|
|
46
|
+
run_ssh(self.domain, 'echo "{0} auth.{1}" >> /etc/hosts'.format(ip, self.domain), password=self.ssh_password, retries=10)
|
|
47
|
+
run_ssh(self.domain, '/snap/platform/current/bin/upgrade-snapd.sh {0}'.format(channel), password=self.ssh_password, retries=10)
|
|
48
|
+
run_ssh(self.domain, 'snap refresh platform --channel={0}'.format(channel), password=self.ssh_password, retries=10)
|
|
49
|
+
|
|
50
|
+
wait_for_rest(requests.session(), "https://{0}/rest/id".format(self.domain), 200, 10)
|
|
51
|
+
response = requests.post('https://{0}/rest/activate/custom'.format(self.domain),
|
|
52
|
+
json={'domain': self.domain,
|
|
53
|
+
'device_username': self.device_user,
|
|
54
|
+
'device_password': self.device_password}, verify=False)
|
|
55
|
+
if response.status_code == 200:
|
|
56
|
+
self.activated()
|
|
57
|
+
self.login()
|
|
58
|
+
return response
|
|
59
|
+
|
|
60
|
+
def activated(self):
|
|
61
|
+
self.ssh_password = self.device_password
|
|
62
|
+
|
|
63
|
+
def login(self, retries=5):
|
|
64
|
+
session = requests.session()
|
|
65
|
+
session.mount('https://{0}'.format(self.domain), HTTPAdapter(max_retries=retries))
|
|
66
|
+
retry = 0
|
|
67
|
+
while True:
|
|
68
|
+
try:
|
|
69
|
+
session.post('https://{0}/rest/login'.format(self.domain), verify=False, allow_redirects=False,
|
|
70
|
+
json={'username': self.device_user, 'password': self.device_password})
|
|
71
|
+
response = session.get('https://{0}/rest/user'.format(self.domain), verify=False,
|
|
72
|
+
allow_redirects=False)
|
|
73
|
+
if response.status_code == 200:
|
|
74
|
+
self.session = session
|
|
75
|
+
return session
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(str(e))
|
|
78
|
+
print('retry {0} of {1}'.format(retry, retries))
|
|
79
|
+
retry += 1
|
|
80
|
+
if retry > retries:
|
|
81
|
+
raise Exception('cannot login')
|
|
82
|
+
|
|
83
|
+
def app_remove(self, app, attempts=200):
|
|
84
|
+
response = self.session.post('https://{0}/rest/app/remove'.format(self.domain), json={'app_id': app},
|
|
85
|
+
verify=False, allow_redirects=False)
|
|
86
|
+
|
|
87
|
+
wait_for_installer(self.session, self.domain, attempts=attempts)
|
|
88
|
+
return response
|
|
89
|
+
|
|
90
|
+
def app_install(self, app, attempts=200):
|
|
91
|
+
response = self.session.post('https://{0}/rest/app/install'.format(self.domain), json={'app_id': app},
|
|
92
|
+
verify=False, allow_redirects=False)
|
|
93
|
+
|
|
94
|
+
wait_for_installer(self.session, self.domain, attempts=attempts)
|
|
95
|
+
return response
|
|
96
|
+
|
|
97
|
+
def run_ssh(self, cmd, retries=0, throw=True, env_vars='', debug=True, sleep=1):
|
|
98
|
+
ssh_env_vars = self.ssh_env_vars + ' ' + env_vars
|
|
99
|
+
return run_ssh(self.domain, cmd, password=self.ssh_password, env_vars=ssh_env_vars, retries=retries,
|
|
100
|
+
throw=throw, debug=debug, sleep=sleep)
|
|
101
|
+
|
|
102
|
+
def scp_from_device(self, dir_from, dir_to, throw=False):
|
|
103
|
+
return run_scp('-r root@{0}:{1} {2}'.format(self.domain, dir_from, dir_to), password=self.ssh_password,
|
|
104
|
+
throw=throw)
|
|
105
|
+
|
|
106
|
+
def scp_to_device(self, dir_from, dir_to, throw=False):
|
|
107
|
+
return run_scp('-r {0} root@{1}:{2}'.format(dir_from, self.domain, dir_to), password=self.ssh_password,
|
|
108
|
+
throw=throw)
|
|
109
|
+
|
|
110
|
+
def http_get(self, url):
|
|
111
|
+
return self.session.get('https://{0}{1}'.format(self.domain, url), allow_redirects=False, verify=False)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
|
|
3
|
+
def add_host_alias(app, host, domain, hosts_file='/etc/hosts'):
|
|
4
|
+
ip = socket.gethostbyname(host)
|
|
5
|
+
with open(hosts_file, "a") as hosts:
|
|
6
|
+
print("adding hosts: {0} {1}".format(ip, domain))
|
|
7
|
+
hosts.write("{0} {1}\n".format(ip, domain))
|
|
8
|
+
print("adding hosts: {0} {1}.{2}".format(ip, app, domain))
|
|
9
|
+
hosts.write("{0} {1}.{2}\n".format(ip, app, domain))
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from syncloudlib.integration.ssh import run_scp, run_ssh
|
|
3
|
+
from subprocess import check_output
|
|
4
|
+
from os.path import split
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
SNAP = 'snap'
|
|
9
|
+
SNAP_INSTALL = '{0} install --devmode'.format(SNAP)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_data_dir(app):
|
|
13
|
+
return '/var/snap/{0}/common'.format(app)
|
|
14
|
+
|
|
15
|
+
def get_snap_data_dir(app):
|
|
16
|
+
return '/var/snap/{0}/current'.format(app)
|
|
17
|
+
|
|
18
|
+
def get_app_dir(app):
|
|
19
|
+
return '/snap/{0}/current'.format(app)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_service_prefix():
|
|
23
|
+
return 'snap.'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_ssh_env_vars(app):
|
|
27
|
+
return 'SNAP={0} SNAP_COMMON={1}'.format(get_app_dir(app), get_data_dir(app))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def local_install(host, password, app_archive_path):
|
|
31
|
+
run_ssh(host, 'ls -la /', password=password)
|
|
32
|
+
_, app_archive = split(app_archive_path)
|
|
33
|
+
run_scp('{0} root@{1}:/'.format(app_archive_path, host), password=password, retries=3)
|
|
34
|
+
run_ssh(host, 'ls -la /{0}'.format(app_archive), password=password)
|
|
35
|
+
run_ssh(host, '{0} /{1}'.format(SNAP_INSTALL, app_archive), password=password)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def local_remove(host, password, app):
|
|
39
|
+
run_ssh(host, '{0} remove {1}'.format(SNAP, app), password=password)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def wait_for_platform_web(host):
|
|
43
|
+
print(check_output('while ! nc -w 1 -z {0} 81; do sleep 1; done'.format(host), shell=True).decode())
|
|
44
|
+
print(check_output('while ! nc -w 1 -z {0} 80; do sleep 1; done'.format(host), shell=True).decode())
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def wait_for_installer(web_session, host, attempts=60, throw_on_error=False):
|
|
48
|
+
is_running = True
|
|
49
|
+
attempt = 0
|
|
50
|
+
while is_running and attempt < attempts:
|
|
51
|
+
try:
|
|
52
|
+
response = web_session.get('https://{0}/rest/installer/status'.format(host), verify=False)
|
|
53
|
+
if response.status_code == 200:
|
|
54
|
+
status = json.loads(response.text)
|
|
55
|
+
is_running = status['data']['is_running']
|
|
56
|
+
else:
|
|
57
|
+
if throw_on_error:
|
|
58
|
+
raise Exception("error http status code: {0}".format(response.status_code))
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(e.message)
|
|
61
|
+
if throw_on_error:
|
|
62
|
+
raise e
|
|
63
|
+
|
|
64
|
+
print("attempt: {0}/{1}".format(attempt, attempts))
|
|
65
|
+
attempt += 1
|
|
66
|
+
time.sleep(10)
|
|
67
|
+
|
|
68
|
+
if is_running:
|
|
69
|
+
raise Exception("time out waiting for thr installer")
|
|
70
|
+
|
|
71
|
+
def wait_for_file(file, attempts=10):
|
|
72
|
+
attempt=0
|
|
73
|
+
attempt_limit=attempts
|
|
74
|
+
while attempt < attempt_limit:
|
|
75
|
+
if os.path.isfile(file):
|
|
76
|
+
return
|
|
77
|
+
time.sleep(10)
|
|
78
|
+
attempt = attempt + 1
|