goosebit 0.1.0__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 +8 -5
- goosebit/api/__init__.py +1 -1
- goosebit/api/devices.py +60 -36
- goosebit/api/download.py +28 -14
- goosebit/api/firmware.py +37 -44
- goosebit/api/helper.py +30 -0
- goosebit/api/rollouts.py +87 -0
- goosebit/api/routes.py +15 -7
- goosebit/auth/__init__.py +37 -21
- goosebit/db.py +5 -0
- goosebit/models.py +125 -6
- goosebit/permissions.py +33 -13
- goosebit/realtime/__init__.py +1 -1
- goosebit/realtime/logs.py +4 -6
- goosebit/settings.py +38 -29
- goosebit/telemetry/__init__.py +28 -0
- goosebit/telemetry/prometheus.py +10 -0
- goosebit/ui/__init__.py +1 -1
- goosebit/ui/routes.py +36 -39
- goosebit/ui/static/js/devices.js +191 -239
- goosebit/ui/static/js/firmware.js +234 -88
- goosebit/ui/static/js/index.js +83 -84
- goosebit/ui/static/js/logs.js +17 -10
- goosebit/ui/static/js/rollouts.js +198 -0
- goosebit/ui/static/js/util.js +66 -0
- goosebit/ui/templates/devices.html +75 -42
- goosebit/ui/templates/firmware.html +150 -34
- goosebit/ui/templates/index.html +9 -23
- goosebit/ui/templates/login.html +58 -27
- goosebit/ui/templates/logs.html +18 -3
- goosebit/ui/templates/nav.html +78 -25
- goosebit/ui/templates/rollouts.html +76 -0
- 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 +112 -24
- goosebit/updater/manager.py +237 -94
- goosebit/updater/routes.py +7 -8
- goosebit/updates/__init__.py +70 -0
- goosebit/updates/swdesc.py +83 -0
- goosebit-0.1.2.dist-info/METADATA +123 -0
- 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 -26
- goosebit/updater/misc.py +0 -69
- goosebit/updater/updates.py +0 -93
- goosebit-0.1.0.dist-info/METADATA +0 -37
- goosebit-0.1.0.dist-info/RECORD +0 -48
- {goosebit-0.1.0.dist-info → goosebit-0.1.2.dist-info}/LICENSE +0 -0
- {goosebit-0.1.0.dist-info → goosebit-0.1.2.dist-info}/WHEEL +0 -0
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">
|
@@ -7,6 +6,22 @@
|
|
7
6
|
<div class="card-header">
|
8
7
|
<h3>Logs - {{ device }}</h3>
|
9
8
|
</div>
|
9
|
+
<div class="card-header">
|
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>
|
23
|
+
</div>
|
24
|
+
</div>
|
10
25
|
<div class="card-body">
|
11
26
|
<pre id="device-log"></pre>
|
12
27
|
</div>
|
@@ -15,7 +30,7 @@
|
|
15
30
|
</div>
|
16
31
|
</div>
|
17
32
|
<script>
|
18
|
-
device = "{{ device }}"
|
33
|
+
device = "{{ device }}";
|
19
34
|
</script>
|
20
35
|
<script src="{{ url_for('static', path='js/logs.js') }}"></script>
|
21
36
|
{% endblock content %}
|
goosebit/ui/templates/nav.html
CHANGED
@@ -1,64 +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
|
37
|
-
|
56
|
+
<img
|
57
|
+
src="{{ request.url_for('static', path='svg/goosebit-logo.svg') }}"
|
58
|
+
class="me-2"
|
59
|
+
style="height: 30px; width: 30px"
|
60
|
+
/>
|
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
|
-
|
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
|
+
>
|
52
103
|
{% endif %}
|
53
|
-
|
54
104
|
</div>
|
55
|
-
<div
|
56
|
-
|
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>
|
57
111
|
</div>
|
58
112
|
</div>
|
59
113
|
</div>
|
60
114
|
</nav>
|
61
|
-
{% block content %}
|
62
|
-
{% endblock content %}
|
115
|
+
{% block content %} {% endblock content %}
|
63
116
|
</body>
|
64
117
|
</html>
|
@@ -0,0 +1,76 @@
|
|
1
|
+
{% extends "nav.html" %} {% block content %}
|
2
|
+
<div class="container-fluid">
|
3
|
+
<div class="row p-2 d-flex justify-content-center">
|
4
|
+
<div class="col">
|
5
|
+
<table id="rollout-table" class="table table-hover">
|
6
|
+
<thead>
|
7
|
+
<tr>
|
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>
|
18
|
+
</tr>
|
19
|
+
</thead>
|
20
|
+
<tbody id="rollouts-list"></tbody>
|
21
|
+
</table>
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
</div>
|
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>
|
75
|
+
<script src="{{ url_for('static', path='js/rollouts.js') }}"></script>
|
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,11 +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
|
|
6
|
-
from goosebit.
|
7
|
+
from goosebit.models import Firmware, UpdateStateEnum
|
8
|
+
from goosebit.settings import POLL_TIME_REGISTRATION
|
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")
|
7
13
|
|
8
|
-
# v1 is hardware revision
|
9
14
|
router = APIRouter(prefix="/v1")
|
10
15
|
|
11
16
|
|
@@ -16,17 +21,49 @@ async def polling(
|
|
16
21
|
dev_id: str,
|
17
22
|
updater: UpdateManager = Depends(get_update_manager),
|
18
23
|
):
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
24
|
+
links = {}
|
25
|
+
|
26
|
+
sleep = updater.poll_time
|
27
|
+
device = await updater.get_device()
|
28
|
+
|
29
|
+
if device.last_state == UpdateStateEnum.UNKNOWN:
|
30
|
+
# device registration
|
31
|
+
sleep = POLL_TIME_REGISTRATION
|
32
|
+
links["configData"] = {
|
33
|
+
"href": str(
|
34
|
+
request.url_for(
|
35
|
+
"config_data",
|
36
|
+
tenant=tenant,
|
37
|
+
dev_id=dev_id,
|
38
|
+
)
|
39
|
+
)
|
40
|
+
}
|
41
|
+
logger.info(f"Skip: registration required, device={updater.dev_id}")
|
42
|
+
|
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}")
|
45
|
+
pass
|
46
|
+
|
47
|
+
else:
|
48
|
+
# provide update if available. Note: this is also required while in state "running", otherwise swupdate
|
49
|
+
# won't confirm a successful testing (might be a bug/problem in swupdate)
|
50
|
+
handling_type, firmware = await updater.get_update()
|
51
|
+
if handling_type != HandlingType.SKIP:
|
52
|
+
links["deploymentBase"] = {
|
23
53
|
"href": str(
|
24
54
|
request.url_for(
|
25
|
-
"deployment_base",
|
55
|
+
"deployment_base",
|
56
|
+
tenant=tenant,
|
57
|
+
dev_id=dev_id,
|
58
|
+
action_id=firmware.id,
|
26
59
|
)
|
27
60
|
)
|
28
|
-
}
|
29
|
-
|
61
|
+
}
|
62
|
+
logger.info(f"Forced: update available, device={updater.dev_id}")
|
63
|
+
|
64
|
+
return {
|
65
|
+
"config": {"polling": {"sleep": sleep}},
|
66
|
+
"_links": links,
|
30
67
|
}
|
31
68
|
|
32
69
|
|
@@ -34,11 +71,13 @@ async def polling(
|
|
34
71
|
async def config_data(
|
35
72
|
request: Request,
|
36
73
|
dev_id: str,
|
74
|
+
tenant: str,
|
37
75
|
updater: UpdateManager = Depends(get_update_manager),
|
38
76
|
):
|
39
77
|
data = await request.json()
|
40
78
|
# TODO: make standard schema to deal with this
|
41
|
-
|
79
|
+
await updater.update_config_data(**data["data"])
|
80
|
+
logger.info(f"Updating config data, device={updater.dev_id}")
|
42
81
|
return {"success": True, "message": "Updated swupdate data."}
|
43
82
|
|
44
83
|
|
@@ -50,16 +89,16 @@ async def deployment_base(
|
|
50
89
|
action_id: int,
|
51
90
|
updater: UpdateManager = Depends(get_update_manager),
|
52
91
|
):
|
53
|
-
|
54
|
-
|
55
|
-
|
92
|
+
handling_type, firmware = await updater.get_update()
|
93
|
+
|
94
|
+
logger.info(f"Request deployment base, device={updater.dev_id}")
|
56
95
|
|
57
96
|
return {
|
58
97
|
"id": f"{action_id}",
|
59
98
|
"deployment": {
|
60
|
-
"download":
|
61
|
-
"update":
|
62
|
-
"chunks":
|
99
|
+
"download": str(handling_type),
|
100
|
+
"update": str(handling_type),
|
101
|
+
"chunks": generate_chunk(request, firmware),
|
63
102
|
},
|
64
103
|
}
|
65
104
|
|
@@ -68,25 +107,74 @@ async def deployment_base(
|
|
68
107
|
async def deployment_feedback(
|
69
108
|
request: Request,
|
70
109
|
tenant: str,
|
71
|
-
dev_id: str,
|
72
110
|
action_id: int,
|
73
111
|
updater: UpdateManager = Depends(get_update_manager),
|
74
112
|
):
|
75
113
|
try:
|
76
114
|
data = await request.json()
|
77
|
-
except json.JSONDecodeError:
|
115
|
+
except json.JSONDecodeError as e:
|
116
|
+
logging.warning(f"Parsing deployment feedback failed, error={e}, device={updater.dev_id}")
|
78
117
|
return
|
79
118
|
try:
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
119
|
+
execution = data["status"]["execution"]
|
120
|
+
|
121
|
+
if execution == "proceeding":
|
122
|
+
await updater.update_device_state(UpdateStateEnum.RUNNING)
|
123
|
+
logger.debug(f"Installation in progress, device={updater.dev_id}")
|
124
|
+
|
125
|
+
elif execution == "closed":
|
126
|
+
state = data["status"]["result"]["finished"]
|
127
|
+
|
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"])
|
132
|
+
|
133
|
+
# From hawkBit docu: DDI defines also a status NONE which will not be interpreted by the update server
|
134
|
+
# and handled like SUCCESS.
|
135
|
+
if state == "success" or state == "none":
|
136
|
+
await updater.update_device_state(UpdateStateEnum.FINISHED)
|
137
|
+
|
138
|
+
# not guaranteed to be the correct rollout - see next comment.
|
139
|
+
rollout = await updater.get_rollout()
|
140
|
+
if rollout:
|
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
|
+
)
|
148
|
+
|
149
|
+
# setting the currently installed version based on the current assigned firmware / existing rollouts
|
150
|
+
# is problematic. Better to assign custom action_id for each update (rollout id? firmware id? new id?).
|
151
|
+
# Alternatively - but requires customization on the gateway side - use version reported by the gateway.
|
152
|
+
await updater.update_fw_version(reported_firmware.version)
|
153
|
+
logger.debug(f"Installation successful, firmware={reported_firmware.version}, device={updater.dev_id}")
|
154
|
+
|
155
|
+
elif state == "failure":
|
156
|
+
await updater.update_device_state(UpdateStateEnum.ERROR)
|
157
|
+
|
158
|
+
# not guaranteed to be the correct rollout - see comment above.
|
159
|
+
rollout = await updater.get_rollout()
|
160
|
+
if rollout:
|
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
|
+
)
|
168
|
+
|
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}")
|
84
173
|
|
85
174
|
try:
|
86
175
|
log = data["status"]["details"]
|
87
176
|
await updater.update_log("\n".join(log))
|
88
177
|
except KeyError:
|
89
|
-
|
178
|
+
logging.warning(f"No details to update update log, device={updater.dev_id}")
|
90
179
|
|
91
|
-
await updater.save()
|
92
180
|
return {"id": str(action_id)}
|