goosebit 0.1.1__py3-none-any.whl → 0.1.2__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.
- goosebit/__init__.py +5 -2
- goosebit/api/__init__.py +1 -1
- goosebit/api/devices.py +59 -39
- goosebit/api/download.py +28 -14
- goosebit/api/firmware.py +40 -34
- goosebit/api/helper.py +30 -0
- goosebit/api/rollouts.py +64 -13
- goosebit/api/routes.py +14 -7
- goosebit/auth/__init__.py +14 -6
- goosebit/db.py +5 -0
- goosebit/models.py +110 -10
- goosebit/permissions.py +26 -20
- goosebit/realtime/__init__.py +1 -1
- goosebit/realtime/logs.py +3 -6
- goosebit/settings.py +4 -6
- goosebit/telemetry/__init__.py +28 -0
- goosebit/telemetry/prometheus.py +10 -0
- goosebit/ui/__init__.py +1 -1
- goosebit/ui/routes.py +33 -40
- goosebit/ui/static/js/devices.js +187 -250
- goosebit/ui/static/js/firmware.js +229 -92
- goosebit/ui/static/js/index.js +79 -90
- goosebit/ui/static/js/logs.js +14 -11
- goosebit/ui/static/js/rollouts.js +169 -27
- goosebit/ui/static/js/util.js +66 -0
- goosebit/ui/templates/devices.html +75 -51
- goosebit/ui/templates/firmware.html +149 -35
- goosebit/ui/templates/index.html +9 -26
- goosebit/ui/templates/login.html +58 -27
- goosebit/ui/templates/logs.html +15 -5
- goosebit/ui/templates/nav.html +77 -26
- goosebit/ui/templates/rollouts.html +62 -39
- goosebit/updater/__init__.py +1 -1
- goosebit/updater/controller/__init__.py +1 -1
- goosebit/updater/controller/v1/__init__.py +1 -1
- goosebit/updater/controller/v1/routes.py +53 -35
- goosebit/updater/manager.py +205 -103
- goosebit/updater/routes.py +4 -7
- goosebit/updates/__init__.py +70 -0
- goosebit/updates/swdesc.py +83 -0
- {goosebit-0.1.1.dist-info → goosebit-0.1.2.dist-info}/METADATA +53 -3
- goosebit-0.1.2.dist-info/RECORD +51 -0
- goosebit/updater/download/__init__.py +0 -1
- goosebit/updater/download/routes.py +0 -6
- goosebit/updater/download/v1/__init__.py +0 -1
- goosebit/updater/download/v1/routes.py +0 -13
- goosebit/updater/misc.py +0 -57
- goosebit/updates/artifacts.py +0 -89
- goosebit/updates/version.py +0 -38
- goosebit-0.1.1.dist-info/RECORD +0 -53
- {goosebit-0.1.1.dist-info → goosebit-0.1.2.dist-info}/LICENSE +0 -0
- {goosebit-0.1.1.dist-info → goosebit-0.1.2.dist-info}/WHEEL +0 -0
goosebit/ui/templates/index.html
CHANGED
@@ -1,37 +1,20 @@
|
|
1
|
-
{% extends "nav.html" %}
|
2
|
-
{% block content %}
|
1
|
+
{% extends "nav.html" %} {% block content %}
|
3
2
|
<div class="container-fluid">
|
4
3
|
<div class="row p-2 d-flex justify-content-center">
|
5
4
|
<div class="col">
|
6
5
|
<table id="device-table" class="table table-hover">
|
7
6
|
<thead>
|
8
7
|
<tr>
|
9
|
-
<th>
|
10
|
-
|
11
|
-
</th>
|
12
|
-
<th>
|
13
|
-
|
14
|
-
</th>
|
15
|
-
<th>
|
16
|
-
UUID
|
17
|
-
</th>
|
18
|
-
<th>
|
19
|
-
Firmware
|
20
|
-
</th>
|
21
|
-
<th>
|
22
|
-
Progress
|
23
|
-
</th>
|
24
|
-
<th>
|
25
|
-
Last IP
|
26
|
-
</th>
|
27
|
-
<th>
|
28
|
-
Last Seen
|
29
|
-
</th>
|
8
|
+
<th>Name</th>
|
9
|
+
<th>Up</th>
|
10
|
+
<th>UUID</th>
|
11
|
+
<th>Firmware</th>
|
12
|
+
<th>Progress</th>
|
13
|
+
<th>Last IP</th>
|
14
|
+
<th>Last Seen</th>
|
30
15
|
</tr>
|
31
16
|
</thead>
|
32
|
-
<tbody id="devices-list">
|
33
|
-
|
34
|
-
</tbody>
|
17
|
+
<tbody id="devices-list"></tbody>
|
35
18
|
</table>
|
36
19
|
</div>
|
37
20
|
</div>
|
goosebit/ui/templates/login.html
CHANGED
@@ -1,34 +1,65 @@
|
|
1
|
-
<!
|
1
|
+
<!doctype html>
|
2
2
|
<html lang="en">
|
3
|
-
<head>
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8" />
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
6
|
+
<title>Login</title>
|
7
|
+
<link
|
8
|
+
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
9
|
+
rel="stylesheet"
|
10
|
+
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
|
11
|
+
crossorigin="anonymous"
|
12
|
+
/>
|
13
|
+
<script
|
14
|
+
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
15
|
+
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
16
|
+
crossorigin="anonymous"
|
17
|
+
></script>
|
18
|
+
<link
|
19
|
+
rel="stylesheet"
|
20
|
+
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
21
|
+
/>
|
22
|
+
<link rel="icon" href="{{ url_for('static', path='favicon.svg') }}" />
|
23
|
+
</head>
|
24
|
+
<body data-bs-theme="dark">
|
25
|
+
<div class="container py-5 h-100">
|
26
|
+
<div
|
27
|
+
class="row d-flex justify-content-center align-items-center h-100"
|
28
|
+
>
|
29
|
+
<div class="col-12 col-md-8 col-lg-6 col-xl-5">
|
30
|
+
<div class="mb-md-5 mt-md-4 pb-5">
|
31
|
+
<h2 class="fw-bold mb-3 text-uppercase">Login</h2>
|
32
|
+
<form method="post">
|
33
|
+
<div class="form-outline form-white mb-4">
|
34
|
+
<input
|
35
|
+
type="email"
|
36
|
+
id="username"
|
37
|
+
name="username"
|
38
|
+
placeholder="Email"
|
39
|
+
class="form-control form-control-lg"
|
40
|
+
/>
|
41
|
+
</div>
|
22
42
|
|
23
|
-
|
24
|
-
|
25
|
-
|
43
|
+
<div class="form-outline form-white mb-4">
|
44
|
+
<input
|
45
|
+
type="password"
|
46
|
+
id="password"
|
47
|
+
name="password"
|
48
|
+
placeholder="Password"
|
49
|
+
class="form-control form-control-lg"
|
50
|
+
/>
|
51
|
+
</div>
|
26
52
|
|
27
|
-
|
28
|
-
|
53
|
+
<button
|
54
|
+
class="btn btn-outline-light btn-lg px-5 w-100"
|
55
|
+
type="submit"
|
56
|
+
>
|
57
|
+
Login
|
58
|
+
</button>
|
59
|
+
</form>
|
60
|
+
</div>
|
29
61
|
</div>
|
30
62
|
</div>
|
31
63
|
</div>
|
32
|
-
</
|
33
|
-
</body>
|
64
|
+
</body>
|
34
65
|
</html>
|
goosebit/ui/templates/logs.html
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
{% extends "nav.html" %}
|
2
|
-
{% block content %}
|
1
|
+
{% extends "nav.html" %} {% block content %}
|
3
2
|
<div class="container-fluid">
|
4
3
|
<div class="row p-2 d-flex justify-content-center">
|
5
4
|
<div class="col col-12 col-lg-9">
|
@@ -8,8 +7,19 @@
|
|
8
7
|
<h3>Logs - {{ device }}</h3>
|
9
8
|
</div>
|
10
9
|
<div class="card-header">
|
11
|
-
<div
|
12
|
-
|
10
|
+
<div
|
11
|
+
class="progress m-2"
|
12
|
+
role="progressbar"
|
13
|
+
aria-label="Basic example"
|
14
|
+
aria-valuenow="0"
|
15
|
+
aria-valuemin="0"
|
16
|
+
aria-valuemax="100"
|
17
|
+
>
|
18
|
+
<div
|
19
|
+
class="progress-bar progress-bar-striped progress-bar-animated"
|
20
|
+
id="install-progress"
|
21
|
+
style="width: 0%"
|
22
|
+
></div>
|
13
23
|
</div>
|
14
24
|
</div>
|
15
25
|
<div class="card-body">
|
@@ -20,7 +30,7 @@
|
|
20
30
|
</div>
|
21
31
|
</div>
|
22
32
|
<script>
|
23
|
-
device = "{{ device }}"
|
33
|
+
device = "{{ device }}";
|
24
34
|
</script>
|
25
35
|
<script src="{{ url_for('static', path='js/logs.js') }}"></script>
|
26
36
|
{% endblock content %}
|
goosebit/ui/templates/nav.html
CHANGED
@@ -1,66 +1,117 @@
|
|
1
|
-
<!
|
1
|
+
<!doctype html>
|
2
2
|
<html lang="en">
|
3
3
|
<head>
|
4
4
|
<script>
|
5
5
|
const PERMISSIONS = {{request.user.get_json_permissions() | safe}};
|
6
6
|
</script>
|
7
|
-
<meta charset="utf-8"
|
8
|
-
<meta name="viewport" content="width=device-width, initial-scale=1"
|
7
|
+
<meta charset="utf-8" />
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
9
9
|
<title>{{title}}</title>
|
10
10
|
<!--bootstrap-->
|
11
|
-
<link
|
12
|
-
|
13
|
-
|
11
|
+
<link
|
12
|
+
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
13
|
+
rel="stylesheet"
|
14
|
+
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
|
15
|
+
crossorigin="anonymous"
|
16
|
+
/>
|
17
|
+
<script
|
18
|
+
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
19
|
+
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
20
|
+
crossorigin="anonymous"
|
21
|
+
></script>
|
22
|
+
<link
|
23
|
+
rel="stylesheet"
|
24
|
+
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
25
|
+
/>
|
14
26
|
<!--data tables-->
|
15
|
-
<link
|
27
|
+
<link
|
28
|
+
href="https://cdn.datatables.net/v/bs5/jq-3.7.0/dt-2.0.1/b-3.0.0/r-3.0.0/sl-2.0.0/datatables.min.css"
|
29
|
+
rel="stylesheet"
|
30
|
+
/>
|
16
31
|
<script src="https://cdn.datatables.net/v/bs5/jq-3.7.0/dt-2.0.1/b-3.0.0/r-3.0.0/sl-2.0.0/datatables.min.js"></script>
|
17
32
|
<!--favicon-->
|
18
|
-
<link rel="icon" href="{{ url_for('static', path='favicon.svg') }}"
|
33
|
+
<link rel="icon" href="{{ url_for('static', path='favicon.svg') }}" />
|
19
34
|
<!--data tables alignment fix-->
|
20
35
|
<style>
|
21
36
|
th.dt-type-numeric {
|
22
|
-
text-align: left!important;
|
37
|
+
text-align: left !important;
|
23
38
|
}
|
24
39
|
td.dt-type-numeric {
|
25
|
-
text-align: left!important;
|
40
|
+
text-align: left !important;
|
41
|
+
}
|
42
|
+
.active {
|
43
|
+
color: var(--bs-nav-pills-link-active-color);
|
44
|
+
background-color: transparent;
|
26
45
|
}
|
27
46
|
</style>
|
28
47
|
<script>
|
29
48
|
const TABLE_UPDATE_TIME = 3000;
|
30
49
|
</script>
|
50
|
+
<script src="{{ url_for('static', path='js/util.js') }}"></script>
|
31
51
|
</head>
|
32
52
|
<body data-bs-theme="dark">
|
33
53
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
34
54
|
<div class="container-fluid">
|
35
55
|
<a class="navbar-brand" href="/ui/home">
|
36
|
-
<img
|
56
|
+
<img
|
57
|
+
src="{{ request.url_for('static', path='svg/goosebit-logo.svg') }}"
|
58
|
+
class="me-2"
|
59
|
+
style="height: 30px; width: 30px"
|
60
|
+
/>
|
37
61
|
gooseBit
|
38
62
|
</a>
|
39
|
-
<button
|
63
|
+
<button
|
64
|
+
class="navbar-toggler"
|
65
|
+
type="button"
|
66
|
+
data-bs-toggle="collapse"
|
67
|
+
data-bs-target="#navbar"
|
68
|
+
aria-controls="navbar"
|
69
|
+
aria-expanded="false"
|
70
|
+
aria-label="Toggle navigation"
|
71
|
+
>
|
40
72
|
<span class="navbar-toggler-icon"></span>
|
41
73
|
</button>
|
42
74
|
<div class="collapse navbar-collapse" id="navbar">
|
43
75
|
<div class="navbar-nav">
|
44
76
|
{% if "home.read" in request.user.permissions %}
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
{% if "
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
77
|
+
<a
|
78
|
+
class="nav-link{% if request.url.path.endswith('home') %} active{% endif %}"
|
79
|
+
href="/ui/home"
|
80
|
+
>Home</a
|
81
|
+
>
|
82
|
+
{% endif %} {% if "firmware.read" in
|
83
|
+
request.user.permissions %}
|
84
|
+
<a
|
85
|
+
class="nav-link{% if request.url.path.endswith('firmware') %} active{% endif %}"
|
86
|
+
href="/ui/firmware"
|
87
|
+
>Firmware</a
|
88
|
+
>
|
89
|
+
{% endif %} {% if "device.read" in
|
90
|
+
request.user.permissions %}
|
91
|
+
<a
|
92
|
+
class="nav-link{% if request.url.path.endswith('devices') %} active{% endif %}"
|
93
|
+
href="/ui/devices"
|
94
|
+
>Devices</a
|
95
|
+
>
|
96
|
+
{% endif %} {% if "rollout.read" in
|
97
|
+
request.user.permissions %}
|
98
|
+
<a
|
99
|
+
class="nav-link{% if request.url.path.endswith('rollouts') %} active{% endif %}"
|
100
|
+
href="/ui/rollouts"
|
101
|
+
>Rollouts</a
|
102
|
+
>
|
55
103
|
{% endif %}
|
56
104
|
</div>
|
57
|
-
<div
|
58
|
-
|
105
|
+
<div
|
106
|
+
class="navbar-nav d-flex flex-fill justify-content-end"
|
107
|
+
>
|
108
|
+
<a class="nav-link" href="/logout"
|
109
|
+
>Logout<i class="bi bi-box-arrow-right ps-2"></i
|
110
|
+
></a>
|
59
111
|
</div>
|
60
112
|
</div>
|
61
113
|
</div>
|
62
114
|
</nav>
|
63
|
-
{% block content %}
|
64
|
-
{% endblock content %}
|
115
|
+
{% block content %} {% endblock content %}
|
65
116
|
</body>
|
66
117
|
</html>
|
@@ -1,53 +1,76 @@
|
|
1
|
-
{% extends "nav.html" %}
|
2
|
-
{% block content %}
|
1
|
+
{% extends "nav.html" %} {% block content %}
|
3
2
|
<div class="container-fluid">
|
4
3
|
<div class="row p-2 d-flex justify-content-center">
|
5
4
|
<div class="col">
|
6
5
|
<table id="rollout-table" class="table table-hover">
|
7
6
|
<thead>
|
8
7
|
<tr>
|
9
|
-
<th>
|
10
|
-
|
11
|
-
</th>
|
12
|
-
<th>
|
13
|
-
|
14
|
-
</th>
|
15
|
-
<th>
|
16
|
-
|
17
|
-
</th>
|
18
|
-
<th>
|
19
|
-
Model
|
20
|
-
</th>
|
21
|
-
<th>
|
22
|
-
Revision
|
23
|
-
</th>
|
24
|
-
<th>
|
25
|
-
Feed
|
26
|
-
</th>
|
27
|
-
<th>
|
28
|
-
Flavour
|
29
|
-
</th>
|
30
|
-
<th>
|
31
|
-
Update File
|
32
|
-
</th>
|
33
|
-
<th>
|
34
|
-
Paused
|
35
|
-
</th>
|
36
|
-
<th>
|
37
|
-
Success Count
|
38
|
-
</th>
|
39
|
-
<th>
|
40
|
-
Failure Count
|
41
|
-
</th>
|
8
|
+
<th>Id</th>
|
9
|
+
<th>Created</th>
|
10
|
+
<th>Name</th>
|
11
|
+
<th>Feed</th>
|
12
|
+
<th>Flavour</th>
|
13
|
+
<th>Firmware File</th>
|
14
|
+
<th>Firmware Version</th>
|
15
|
+
<th>Paused</th>
|
16
|
+
<th>Success Count</th>
|
17
|
+
<th>Failure Count</th>
|
42
18
|
</tr>
|
43
19
|
</thead>
|
44
|
-
<tbody id="rollouts-list">
|
45
|
-
|
46
|
-
</tbody>
|
20
|
+
<tbody id="rollouts-list"></tbody>
|
47
21
|
</table>
|
48
22
|
</div>
|
49
23
|
</div>
|
50
24
|
</div>
|
51
|
-
|
25
|
+
<div class="modal" id="rollout-create-modal">
|
26
|
+
<div class="modal-dialog">
|
27
|
+
<div class="modal-content">
|
28
|
+
<div class="modal-header">
|
29
|
+
<h5 class="modal-title">Create Rollout</h5>
|
30
|
+
<button
|
31
|
+
type="button"
|
32
|
+
class="btn-close"
|
33
|
+
data-bs-dismiss="modal"
|
34
|
+
aria-label="Close"
|
35
|
+
></button>
|
36
|
+
</div>
|
37
|
+
<div class="modal-body">
|
38
|
+
<input
|
39
|
+
id="rollout-selected-name"
|
40
|
+
class="form-control mb-3"
|
41
|
+
placeholder="Name"
|
42
|
+
/>
|
43
|
+
<input
|
44
|
+
id="rollout-selected-feed"
|
45
|
+
class="form-control mb-3"
|
46
|
+
placeholder="Feed"
|
47
|
+
/>
|
48
|
+
<input
|
49
|
+
id="rollout-selected-flavor"
|
50
|
+
class="form-control mb-3"
|
51
|
+
placeholder="Flavor"
|
52
|
+
/>
|
53
|
+
<select class="form-select" id="selected-fw"></select>
|
54
|
+
</div>
|
55
|
+
<div class="modal-footer">
|
56
|
+
<button
|
57
|
+
type="button"
|
58
|
+
class="btn btn-secondary"
|
59
|
+
data-bs-dismiss="modal"
|
60
|
+
>
|
61
|
+
Close
|
62
|
+
</button>
|
63
|
+
<button
|
64
|
+
type="button"
|
65
|
+
class="btn btn-outline-light"
|
66
|
+
data-bs-dismiss="modal"
|
67
|
+
onclick="createRollout()"
|
68
|
+
>
|
69
|
+
Save changes
|
70
|
+
</button>
|
71
|
+
</div>
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
</div>
|
52
75
|
<script src="{{ url_for('static', path='js/rollouts.js') }}"></script>
|
53
76
|
{% endblock content %}
|
goosebit/updater/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
from .routes import router
|
1
|
+
from .routes import router # noqa: F401
|
@@ -1 +1 @@
|
|
1
|
-
from .routes import router
|
1
|
+
from .routes import router # noqa: F401
|
@@ -1 +1 @@
|
|
1
|
-
from .routes import router
|
1
|
+
from .routes import router # noqa: F401
|
@@ -1,12 +1,16 @@
|
|
1
1
|
import json
|
2
|
+
import logging
|
2
3
|
|
3
4
|
from fastapi import APIRouter, Depends
|
4
5
|
from fastapi.requests import Request
|
5
6
|
|
7
|
+
from goosebit.models import Firmware, UpdateStateEnum
|
6
8
|
from goosebit.settings import POLL_TIME_REGISTRATION
|
7
|
-
from goosebit.updater.manager import UpdateManager, get_update_manager
|
9
|
+
from goosebit.updater.manager import HandlingType, UpdateManager, get_update_manager
|
10
|
+
from goosebit.updates import generate_chunk
|
11
|
+
|
12
|
+
logger = logging.getLogger("DDI API")
|
8
13
|
|
9
|
-
# v1 is hardware revision
|
10
14
|
router = APIRouter(prefix="/v1")
|
11
15
|
|
12
16
|
|
@@ -20,9 +24,9 @@ async def polling(
|
|
20
24
|
links = {}
|
21
25
|
|
22
26
|
sleep = updater.poll_time
|
23
|
-
|
27
|
+
device = await updater.get_device()
|
24
28
|
|
25
|
-
if last_state ==
|
29
|
+
if device.last_state == UpdateStateEnum.UNKNOWN:
|
26
30
|
# device registration
|
27
31
|
sleep = POLL_TIME_REGISTRATION
|
28
32
|
links["configData"] = {
|
@@ -34,26 +38,28 @@ async def polling(
|
|
34
38
|
)
|
35
39
|
)
|
36
40
|
}
|
41
|
+
logger.info(f"Skip: registration required, device={updater.dev_id}")
|
37
42
|
|
38
|
-
elif last_state ==
|
39
|
-
|
43
|
+
elif device.last_state == UpdateStateEnum.ERROR and not device.force_update:
|
44
|
+
logger.warning(f"Skip: device in error state, device={updater.dev_id}")
|
40
45
|
pass
|
41
46
|
|
42
47
|
else:
|
43
48
|
# provide update if available. Note: this is also required while in state "running", otherwise swupdate
|
44
49
|
# won't confirm a successful testing (might be a bug/problem in swupdate)
|
45
|
-
|
46
|
-
if
|
50
|
+
handling_type, firmware = await updater.get_update()
|
51
|
+
if handling_type != HandlingType.SKIP:
|
47
52
|
links["deploymentBase"] = {
|
48
53
|
"href": str(
|
49
54
|
request.url_for(
|
50
55
|
"deployment_base",
|
51
56
|
tenant=tenant,
|
52
57
|
dev_id=dev_id,
|
53
|
-
action_id=
|
58
|
+
action_id=firmware.id,
|
54
59
|
)
|
55
60
|
)
|
56
61
|
}
|
62
|
+
logger.info(f"Forced: update available, device={updater.dev_id}")
|
57
63
|
|
58
64
|
return {
|
59
65
|
"config": {"polling": {"sleep": sleep}},
|
@@ -71,6 +77,7 @@ async def config_data(
|
|
71
77
|
data = await request.json()
|
72
78
|
# TODO: make standard schema to deal with this
|
73
79
|
await updater.update_config_data(**data["data"])
|
80
|
+
logger.info(f"Updating config data, device={updater.dev_id}")
|
74
81
|
return {"success": True, "message": "Updated swupdate data."}
|
75
82
|
|
76
83
|
|
@@ -82,16 +89,16 @@ async def deployment_base(
|
|
82
89
|
action_id: int,
|
83
90
|
updater: UpdateManager = Depends(get_update_manager),
|
84
91
|
):
|
85
|
-
|
86
|
-
|
87
|
-
|
92
|
+
handling_type, firmware = await updater.get_update()
|
93
|
+
|
94
|
+
logger.info(f"Request deployment base, device={updater.dev_id}")
|
88
95
|
|
89
96
|
return {
|
90
97
|
"id": f"{action_id}",
|
91
98
|
"deployment": {
|
92
|
-
"download":
|
93
|
-
"update":
|
94
|
-
"chunks":
|
99
|
+
"download": str(handling_type),
|
100
|
+
"update": str(handling_type),
|
101
|
+
"chunks": generate_chunk(request, firmware),
|
95
102
|
},
|
96
103
|
}
|
97
104
|
|
@@ -100,63 +107,74 @@ async def deployment_base(
|
|
100
107
|
async def deployment_feedback(
|
101
108
|
request: Request,
|
102
109
|
tenant: str,
|
103
|
-
dev_id: str,
|
104
110
|
action_id: int,
|
105
111
|
updater: UpdateManager = Depends(get_update_manager),
|
106
112
|
):
|
107
113
|
try:
|
108
114
|
data = await request.json()
|
109
|
-
except json.JSONDecodeError:
|
115
|
+
except json.JSONDecodeError as e:
|
116
|
+
logging.warning(f"Parsing deployment feedback failed, error={e}, device={updater.dev_id}")
|
110
117
|
return
|
111
118
|
try:
|
112
119
|
execution = data["status"]["execution"]
|
113
120
|
|
114
121
|
if execution == "proceeding":
|
115
|
-
await updater.update_device_state(
|
122
|
+
await updater.update_device_state(UpdateStateEnum.RUNNING)
|
123
|
+
logger.debug(f"Installation in progress, device={updater.dev_id}")
|
116
124
|
|
117
125
|
elif execution == "closed":
|
118
126
|
state = data["status"]["result"]["finished"]
|
119
127
|
|
120
|
-
updater.
|
121
|
-
updater.
|
128
|
+
await updater.update_force_update(False)
|
129
|
+
await updater.update_log_complete(True)
|
130
|
+
|
131
|
+
reported_firmware = await Firmware.get_or_none(id=data["id"])
|
122
132
|
|
123
133
|
# From hawkBit docu: DDI defines also a status NONE which will not be interpreted by the update server
|
124
134
|
# and handled like SUCCESS.
|
125
135
|
if state == "success" or state == "none":
|
126
|
-
await updater.update_device_state(
|
136
|
+
await updater.update_device_state(UpdateStateEnum.FINISHED)
|
127
137
|
|
128
138
|
# not guaranteed to be the correct rollout - see next comment.
|
129
139
|
rollout = await updater.get_rollout()
|
130
140
|
if rollout:
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
141
|
+
if rollout.firmware == reported_firmware:
|
142
|
+
rollout.success_count += 1
|
143
|
+
await rollout.save()
|
144
|
+
else:
|
145
|
+
logging.warning(
|
146
|
+
f"Updating rollout success stats failed, firmware={reported_firmware.id}, device={updater.dev_id}" # noqa: E501
|
147
|
+
)
|
137
148
|
|
138
149
|
# setting the currently installed version based on the current assigned firmware / existing rollouts
|
139
150
|
# is problematic. Better to assign custom action_id for each update (rollout id? firmware id? new id?).
|
140
151
|
# Alternatively - but requires customization on the gateway side - use version reported by the gateway.
|
141
|
-
await updater.update_fw_version(
|
152
|
+
await updater.update_fw_version(reported_firmware.version)
|
153
|
+
logger.debug(f"Installation successful, firmware={reported_firmware.version}, device={updater.dev_id}")
|
142
154
|
|
143
155
|
elif state == "failure":
|
144
|
-
await updater.update_device_state(
|
156
|
+
await updater.update_device_state(UpdateStateEnum.ERROR)
|
145
157
|
|
146
158
|
# not guaranteed to be the correct rollout - see comment above.
|
147
159
|
rollout = await updater.get_rollout()
|
148
160
|
if rollout:
|
149
|
-
rollout.
|
150
|
-
|
161
|
+
if rollout.firmware == reported_firmware:
|
162
|
+
rollout.failure_count += 1
|
163
|
+
await rollout.save()
|
164
|
+
else:
|
165
|
+
logging.warning(
|
166
|
+
f"Updating rollout failure stats failed, firmware={reported_firmware.id}, device={updater.dev_id}" # noqa: E501
|
167
|
+
)
|
151
168
|
|
152
|
-
|
153
|
-
|
169
|
+
logger.debug(f"Installation failed, firmware={reported_firmware.version}, device={updater.dev_id}")
|
170
|
+
|
171
|
+
except KeyError as e:
|
172
|
+
logging.warning(f"Processing deployment feedback failed, error={e}, device={updater.dev_id}")
|
154
173
|
|
155
174
|
try:
|
156
175
|
log = data["status"]["details"]
|
157
176
|
await updater.update_log("\n".join(log))
|
158
177
|
except KeyError:
|
159
|
-
|
178
|
+
logging.warning(f"No details to update update log, device={updater.dev_id}")
|
160
179
|
|
161
|
-
await updater.save()
|
162
180
|
return {"id": str(action_id)}
|