updates2mqtt 1.6.0__tar.gz → 1.7.2__tar.gz
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.
- {updates2mqtt-1.6.0 → updates2mqtt-1.7.2}/PKG-INFO +21 -19
- {updates2mqtt-1.6.0 → updates2mqtt-1.7.2}/README.md +13 -12
- {updates2mqtt-1.6.0 → updates2mqtt-1.7.2}/pyproject.toml +10 -7
- {updates2mqtt-1.6.0 → updates2mqtt-1.7.2}/src/updates2mqtt/app.py +10 -7
- updates2mqtt-1.7.2/src/updates2mqtt/cli.py +150 -0
- {updates2mqtt-1.6.0 → updates2mqtt-1.7.2}/src/updates2mqtt/config.py +61 -20
- {updates2mqtt-1.6.0 → updates2mqtt-1.7.2}/src/updates2mqtt/hass_formatter.py +12 -21
- updates2mqtt-1.7.2/src/updates2mqtt/helpers.py +226 -0
- updates2mqtt-1.7.2/src/updates2mqtt/integrations/docker.py +662 -0
- updates2mqtt-1.7.2/src/updates2mqtt/integrations/docker_enrich.py +876 -0
- {updates2mqtt-1.6.0 → updates2mqtt-1.7.2}/src/updates2mqtt/integrations/git_utils.py +5 -5
- updates2mqtt-1.7.2/src/updates2mqtt/model.py +251 -0
- {updates2mqtt-1.6.0 → updates2mqtt-1.7.2}/src/updates2mqtt/mqtt.py +31 -5
- updates2mqtt-1.6.0/src/updates2mqtt/integrations/docker.py +0 -607
- updates2mqtt-1.6.0/src/updates2mqtt/model.py +0 -128
- {updates2mqtt-1.6.0 → updates2mqtt-1.7.2}/src/updates2mqtt/__init__.py +0 -0
- {updates2mqtt-1.6.0 → updates2mqtt-1.7.2}/src/updates2mqtt/__main__.py +0 -0
- {updates2mqtt-1.6.0 → updates2mqtt-1.7.2}/src/updates2mqtt/integrations/__init__.py +0 -0
- {updates2mqtt-1.6.0 → updates2mqtt-1.7.2}/src/updates2mqtt/py.typed +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: updates2mqtt
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.2
|
|
4
4
|
Summary: System update and docker image notification and execution over MQTT
|
|
5
|
-
Keywords: mqtt,docker,updates,automation,home-assistant,homeassistant,selfhosting
|
|
5
|
+
Keywords: mqtt,docker,oci,container,updates,automation,home-assistant,homeassistant,selfhosting
|
|
6
6
|
Author: jey burrows
|
|
7
7
|
Author-email: jey burrows <jrb@rhizomatics.org.uk>
|
|
8
8
|
License-Expression: Apache-2.0
|
|
@@ -18,21 +18,22 @@ Classifier: Intended Audience :: System Administrators
|
|
|
18
18
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
19
19
|
Classifier: Typing :: Typed
|
|
20
20
|
Classifier: Programming Language :: Python
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
22
|
Requires-Dist: docker>=7.1.0
|
|
23
23
|
Requires-Dist: paho-mqtt>=2.1.0
|
|
24
24
|
Requires-Dist: omegaconf>=2.3.0
|
|
25
25
|
Requires-Dist: structlog>=25.4.0
|
|
26
26
|
Requires-Dist: rich>=14.0.0
|
|
27
27
|
Requires-Dist: httpx>=0.28.1
|
|
28
|
-
Requires-Dist: hishel[httpx]>=
|
|
28
|
+
Requires-Dist: hishel[httpx]>=1.1.0
|
|
29
29
|
Requires-Dist: usingversion>=0.1.2
|
|
30
|
+
Requires-Dist: tzlocal>=5.3.1
|
|
30
31
|
Requires-Python: >=3.13
|
|
31
|
-
Project-URL: Changelog, https://github.com/rhizomatics/updates2mqtt/blob/main/CHANGELOG.md
|
|
32
|
-
Project-URL: Documentation, https://updates2mqtt.rhizomatics.org.uk
|
|
33
32
|
Project-URL: Homepage, https://updates2mqtt.rhizomatics.org.uk
|
|
34
|
-
Project-URL: Issues, https://github.com/rhizomatics/updates2mqtt/issues
|
|
35
33
|
Project-URL: Repository, https://github.com/rhizomatics/updates2mqtt
|
|
34
|
+
Project-URL: Documentation, https://updates2mqtt.rhizomatics.org.uk
|
|
35
|
+
Project-URL: Issues, https://github.com/rhizomatics/updates2mqtt/issues
|
|
36
|
+
Project-URL: Changelog, https://github.com/rhizomatics/updates2mqtt/blob/main/CHANGELOG.md
|
|
36
37
|
Description-Content-Type: text/markdown
|
|
37
38
|
|
|
38
39
|
{ align=left }
|
|
@@ -60,7 +61,7 @@ Description-Content-Type: text/markdown
|
|
|
60
61
|
|
|
61
62
|
Let Home Assistant tell you about new updates to Docker images for your containers.
|
|
62
63
|
|
|
63
|
-

|
|
64
|
+
{width=300}
|
|
64
65
|
|
|
65
66
|
Read the release notes, and optionally click *Update* to trigger a Docker *pull* (or optionally *build*) and *update*.
|
|
66
67
|
|
|
@@ -71,7 +72,8 @@ Read the release notes, and optionally click *Update* to trigger a Docker *pull*
|
|
|
71
72
|
|
|
72
73
|
Updates2MQTT perioidically checks for new versions of components being available, and publishes new version info to MQTT. HomeAssistant auto discovery is supported, so all updates can be seen in the same place as Home Assistant's own components and add-ins.
|
|
73
74
|
|
|
74
|
-
Currently only Docker containers are supported, either via an image registry check, or a git repo for source (see [Local Builds](local_builds.md))
|
|
75
|
+
Currently only Docker containers are supported, either via an image registry check (using either v1 Docker APIs or the OCI v2 API), or a git repo for source (see [Local Builds](local_builds.md)), with specific handling for Docker, Github Container Registry, Gitlab, Codeberg, Microsoft Container Registry and LinuxServer Registry, with adaptive behaviour to cope with most
|
|
76
|
+
others. The design is modular, so other update sources can be added, at least for notification. The next anticipated is **apt** for Debian based systems.
|
|
75
77
|
|
|
76
78
|
Components can also be updated, either automatically or triggered via MQTT, for example by hitting the *Install* button in the HomeAssistant update dialog. Icons and release notes can be specified for a better HA experience. See [Home Assistant Integration](home_assistant.md) for details.
|
|
77
79
|
|
|
@@ -80,7 +82,7 @@ To get started, read the [Installation](installation.md) and [Configuration](con
|
|
|
80
82
|
For a quick spin, try this:
|
|
81
83
|
|
|
82
84
|
```bash
|
|
83
|
-
docker run -v /var/run/docker.sock:/var/run/docker.sock -e MQTT_USER=user1 -e MQTT_PASS=user1 -e MQTT_HOST=192.168.1.5 ghcr.io/rhizomatics/updates2mqtt:
|
|
85
|
+
docker run -v /var/run/docker.sock:/var/run/docker.sock -e MQTT_USER=user1 -e MQTT_PASS=user1 -e MQTT_HOST=192.168.1.5 ghcr.io/rhizomatics/updates2mqtt:latest
|
|
84
86
|
```
|
|
85
87
|
|
|
86
88
|
or without Docker, using [uv](https://docs.astral.sh/uv/)
|
|
@@ -132,7 +134,8 @@ file, or as `environment` options inside `docker-compose.yaml`.
|
|
|
132
134
|
### Automated updates
|
|
133
135
|
|
|
134
136
|
If Docker containers should be immediately updated, without any confirmation
|
|
135
|
-
or trigger, *e.g.* from the HomeAssistant update dialog, then set an environment variable `UPD2MQTT_UPDATE` in the target container to `Auto` ( it defaults to `Passive`)
|
|
137
|
+
or trigger, *e.g.* from the HomeAssistant update dialog, then set an environment variable `UPD2MQTT_UPDATE` in the target container to `Auto` ( it defaults to `Passive`). If you want it to update without publishing to MQTT and being
|
|
138
|
+
visible to Home Assistant, then use `Silent`.
|
|
136
139
|
|
|
137
140
|
```yaml title="Example Compose Snippet"
|
|
138
141
|
restarter:
|
|
@@ -142,9 +145,6 @@ restarter:
|
|
|
142
145
|
- UPD2MQTT_UPDATE=AUTO
|
|
143
146
|
```
|
|
144
147
|
|
|
145
|
-
This can be used in conjunction with the `UPD2MQTT_VERSION_INCLUDE` and `UPD2MQTT_VERSION_EXCLUDE` to
|
|
146
|
-
limit which updates get automatically applied, for example excluding nightly builds.
|
|
147
|
-
|
|
148
148
|
Automated updates can also apply to local builds, where a `git_repo_path` has been defined - if there are remote
|
|
149
149
|
commits available to pull, then a `git pull`, `docker compose build` and `docker compose up` will be executed.
|
|
150
150
|
|
|
@@ -159,8 +159,9 @@ The following environment variables can be used to configure containers for `upd
|
|
|
159
159
|
| `UPD2MQTT_RELNOTES` | URL to release notes for the package. | |
|
|
160
160
|
| `UPD2MQTT_GIT_REPO_PATH` | Relative path to a local git repo if the image is built locally. | |
|
|
161
161
|
| `UPD2MQTT_IGNORE` | If set to `True`, the container will be ignored by Updates2MQTT. | False |
|
|
162
|
-
|
|
|
163
|
-
| `
|
|
162
|
+
| |
|
|
163
|
+
| `UPD2MQTT_VERSION_POLICY` | Change how version derived from container label or image hash, `Version`,`Digest`,`Version_Digest` with default of `Auto`|
|
|
164
|
+
| `UPD2MQTT_REGISTRY_TOKEN` | Access token for authentication to container distribution API, as alternative to making a call to `token` service |
|
|
164
165
|
|
|
165
166
|
### Docker Labels
|
|
166
167
|
|
|
@@ -173,8 +174,9 @@ Alternatively, use Docker labels
|
|
|
173
174
|
| `updates2mqtt.relnotes` | `UPD2MQTT_RELNOTES` |
|
|
174
175
|
| `updates2mqtt.git_repo_path` | `UPD2MQTT_GIT_REPO_PATH` |
|
|
175
176
|
| `updates2mqtt.ignore` | `UPD2MQTT_IGNORE` |
|
|
176
|
-
| `updates2mqtt.
|
|
177
|
-
| `updates2mqtt.
|
|
177
|
+
| `updates2mqtt.version_policy` | `UPD2MQTT_VERSION_POLICY` |
|
|
178
|
+
| `updates2mqtt.registry_token` | `UPD2MQTT_REGISTRY_TOKEN` |
|
|
179
|
+
|
|
178
180
|
|
|
179
181
|
|
|
180
182
|
```yaml title="Example Compose Snippet"
|
|
@@ -204,7 +206,7 @@ This component relies on several open source packages:
|
|
|
204
206
|
- [Eclipse Paho](https://eclipse.dev/paho/files/paho.mqtt.python/html/client.html) MQTT client
|
|
205
207
|
- [OmegaConf](https://omegaconf.readthedocs.io) for configuration and validation
|
|
206
208
|
- [structlog](https://www.structlog.org/en/stable/) for structured logging and [rich](https://rich.readthedocs.io/en/stable/) for better exception reporting
|
|
207
|
-
- [hishel](https://hishel.com/
|
|
209
|
+
- [hishel](https://hishel.com/) for caching metadata
|
|
208
210
|
- [httpx](https://www.python-httpx.org) for retrieving metadata
|
|
209
211
|
- The Astral [uv](https://docs.astral.sh/uv/) and [ruff](https://docs.astral.sh/ruff/) tools for development and build
|
|
210
212
|
- [pytest](https://docs.pytest.org/en/stable/) and supporting add-ins for automated testing
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
Let Home Assistant tell you about new updates to Docker images for your containers.
|
|
25
25
|
|
|
26
|
-

|
|
26
|
+
{width=300}
|
|
27
27
|
|
|
28
28
|
Read the release notes, and optionally click *Update* to trigger a Docker *pull* (or optionally *build*) and *update*.
|
|
29
29
|
|
|
@@ -34,7 +34,8 @@ Read the release notes, and optionally click *Update* to trigger a Docker *pull*
|
|
|
34
34
|
|
|
35
35
|
Updates2MQTT perioidically checks for new versions of components being available, and publishes new version info to MQTT. HomeAssistant auto discovery is supported, so all updates can be seen in the same place as Home Assistant's own components and add-ins.
|
|
36
36
|
|
|
37
|
-
Currently only Docker containers are supported, either via an image registry check, or a git repo for source (see [Local Builds](local_builds.md))
|
|
37
|
+
Currently only Docker containers are supported, either via an image registry check (using either v1 Docker APIs or the OCI v2 API), or a git repo for source (see [Local Builds](local_builds.md)), with specific handling for Docker, Github Container Registry, Gitlab, Codeberg, Microsoft Container Registry and LinuxServer Registry, with adaptive behaviour to cope with most
|
|
38
|
+
others. The design is modular, so other update sources can be added, at least for notification. The next anticipated is **apt** for Debian based systems.
|
|
38
39
|
|
|
39
40
|
Components can also be updated, either automatically or triggered via MQTT, for example by hitting the *Install* button in the HomeAssistant update dialog. Icons and release notes can be specified for a better HA experience. See [Home Assistant Integration](home_assistant.md) for details.
|
|
40
41
|
|
|
@@ -43,7 +44,7 @@ To get started, read the [Installation](installation.md) and [Configuration](con
|
|
|
43
44
|
For a quick spin, try this:
|
|
44
45
|
|
|
45
46
|
```bash
|
|
46
|
-
docker run -v /var/run/docker.sock:/var/run/docker.sock -e MQTT_USER=user1 -e MQTT_PASS=user1 -e MQTT_HOST=192.168.1.5 ghcr.io/rhizomatics/updates2mqtt:
|
|
47
|
+
docker run -v /var/run/docker.sock:/var/run/docker.sock -e MQTT_USER=user1 -e MQTT_PASS=user1 -e MQTT_HOST=192.168.1.5 ghcr.io/rhizomatics/updates2mqtt:latest
|
|
47
48
|
```
|
|
48
49
|
|
|
49
50
|
or without Docker, using [uv](https://docs.astral.sh/uv/)
|
|
@@ -95,7 +96,8 @@ file, or as `environment` options inside `docker-compose.yaml`.
|
|
|
95
96
|
### Automated updates
|
|
96
97
|
|
|
97
98
|
If Docker containers should be immediately updated, without any confirmation
|
|
98
|
-
or trigger, *e.g.* from the HomeAssistant update dialog, then set an environment variable `UPD2MQTT_UPDATE` in the target container to `Auto` ( it defaults to `Passive`)
|
|
99
|
+
or trigger, *e.g.* from the HomeAssistant update dialog, then set an environment variable `UPD2MQTT_UPDATE` in the target container to `Auto` ( it defaults to `Passive`). If you want it to update without publishing to MQTT and being
|
|
100
|
+
visible to Home Assistant, then use `Silent`.
|
|
99
101
|
|
|
100
102
|
```yaml title="Example Compose Snippet"
|
|
101
103
|
restarter:
|
|
@@ -105,9 +107,6 @@ restarter:
|
|
|
105
107
|
- UPD2MQTT_UPDATE=AUTO
|
|
106
108
|
```
|
|
107
109
|
|
|
108
|
-
This can be used in conjunction with the `UPD2MQTT_VERSION_INCLUDE` and `UPD2MQTT_VERSION_EXCLUDE` to
|
|
109
|
-
limit which updates get automatically applied, for example excluding nightly builds.
|
|
110
|
-
|
|
111
110
|
Automated updates can also apply to local builds, where a `git_repo_path` has been defined - if there are remote
|
|
112
111
|
commits available to pull, then a `git pull`, `docker compose build` and `docker compose up` will be executed.
|
|
113
112
|
|
|
@@ -122,8 +121,9 @@ The following environment variables can be used to configure containers for `upd
|
|
|
122
121
|
| `UPD2MQTT_RELNOTES` | URL to release notes for the package. | |
|
|
123
122
|
| `UPD2MQTT_GIT_REPO_PATH` | Relative path to a local git repo if the image is built locally. | |
|
|
124
123
|
| `UPD2MQTT_IGNORE` | If set to `True`, the container will be ignored by Updates2MQTT. | False |
|
|
125
|
-
|
|
|
126
|
-
| `
|
|
124
|
+
| |
|
|
125
|
+
| `UPD2MQTT_VERSION_POLICY` | Change how version derived from container label or image hash, `Version`,`Digest`,`Version_Digest` with default of `Auto`|
|
|
126
|
+
| `UPD2MQTT_REGISTRY_TOKEN` | Access token for authentication to container distribution API, as alternative to making a call to `token` service |
|
|
127
127
|
|
|
128
128
|
### Docker Labels
|
|
129
129
|
|
|
@@ -136,8 +136,9 @@ Alternatively, use Docker labels
|
|
|
136
136
|
| `updates2mqtt.relnotes` | `UPD2MQTT_RELNOTES` |
|
|
137
137
|
| `updates2mqtt.git_repo_path` | `UPD2MQTT_GIT_REPO_PATH` |
|
|
138
138
|
| `updates2mqtt.ignore` | `UPD2MQTT_IGNORE` |
|
|
139
|
-
| `updates2mqtt.
|
|
140
|
-
| `updates2mqtt.
|
|
139
|
+
| `updates2mqtt.version_policy` | `UPD2MQTT_VERSION_POLICY` |
|
|
140
|
+
| `updates2mqtt.registry_token` | `UPD2MQTT_REGISTRY_TOKEN` |
|
|
141
|
+
|
|
141
142
|
|
|
142
143
|
|
|
143
144
|
```yaml title="Example Compose Snippet"
|
|
@@ -167,7 +168,7 @@ This component relies on several open source packages:
|
|
|
167
168
|
- [Eclipse Paho](https://eclipse.dev/paho/files/paho.mqtt.python/html/client.html) MQTT client
|
|
168
169
|
- [OmegaConf](https://omegaconf.readthedocs.io) for configuration and validation
|
|
169
170
|
- [structlog](https://www.structlog.org/en/stable/) for structured logging and [rich](https://rich.readthedocs.io/en/stable/) for better exception reporting
|
|
170
|
-
- [hishel](https://hishel.com/
|
|
171
|
+
- [hishel](https://hishel.com/) for caching metadata
|
|
171
172
|
- [httpx](https://www.python-httpx.org) for retrieving metadata
|
|
172
173
|
- The Astral [uv](https://docs.astral.sh/uv/) and [ruff](https://docs.astral.sh/ruff/) tools for development and build
|
|
173
174
|
- [pytest](https://docs.pytest.org/en/stable/) and supporting add-ins for automated testing
|
|
@@ -7,9 +7,9 @@ authors = [
|
|
|
7
7
|
]
|
|
8
8
|
|
|
9
9
|
requires-python = ">=3.13"
|
|
10
|
-
version = "1.
|
|
10
|
+
version = "1.7.2"
|
|
11
11
|
license="Apache-2.0"
|
|
12
|
-
keywords=["mqtt", "docker", "updates", "automation","home-assistant","homeassistant","selfhosting"]
|
|
12
|
+
keywords=["mqtt", "docker", "oci","container","updates", "automation","home-assistant","homeassistant","selfhosting"]
|
|
13
13
|
|
|
14
14
|
dependencies = [
|
|
15
15
|
"docker>=7.1.0",
|
|
@@ -18,8 +18,9 @@ dependencies = [
|
|
|
18
18
|
"structlog>=25.4.0",
|
|
19
19
|
"rich>=14.0.0",
|
|
20
20
|
"httpx>=0.28.1",
|
|
21
|
-
"hishel[httpx]>=
|
|
21
|
+
"hishel[httpx]>=1.1.0",
|
|
22
22
|
"usingversion>=0.1.2",
|
|
23
|
+
"tzlocal>=5.3.1",
|
|
23
24
|
]
|
|
24
25
|
classifiers = [
|
|
25
26
|
"Development Status :: 5 - Production/Stable",
|
|
@@ -34,11 +35,12 @@ classifiers = [
|
|
|
34
35
|
"License :: OSI Approved :: Apache Software License",
|
|
35
36
|
"Typing :: Typed",
|
|
36
37
|
"Programming Language :: Python",
|
|
37
|
-
"Programming Language :: Python :: 3.
|
|
38
|
+
"Programming Language :: Python :: 3.14"
|
|
38
39
|
]
|
|
39
40
|
|
|
40
41
|
[project.scripts]
|
|
41
42
|
updates2mqtt = "updates2mqtt.app:run"
|
|
43
|
+
cli = "updates2mqtt.cli:main"
|
|
42
44
|
|
|
43
45
|
[tool.uv]
|
|
44
46
|
compile-bytecode = true
|
|
@@ -53,6 +55,7 @@ dev = [
|
|
|
53
55
|
"pytest-asyncio>=1.2.0",
|
|
54
56
|
"pytest-cov>=7.0.0",
|
|
55
57
|
"pytest-httpx",
|
|
58
|
+
"pytest-xdist",
|
|
56
59
|
"pytest-mqtt>=0.6.0",
|
|
57
60
|
"pytest-subprocess>=1.5.3",
|
|
58
61
|
"coverage",
|
|
@@ -70,7 +73,8 @@ docs=[
|
|
|
70
73
|
"pymdown-extensions",
|
|
71
74
|
"mkdocs-git-revision-date-localized-plugin",
|
|
72
75
|
"mkdocs-meta-descriptions-plugin",
|
|
73
|
-
"pngquant"
|
|
76
|
+
"pngquant",
|
|
77
|
+
"mkdocs-mermaid2-plugin"
|
|
74
78
|
]
|
|
75
79
|
|
|
76
80
|
[build-system]
|
|
@@ -150,7 +154,6 @@ skip-magic-trailing-comma = false
|
|
|
150
154
|
# Like Black, automatically detect the appropriate line ending.
|
|
151
155
|
line-ending = "auto"
|
|
152
156
|
|
|
153
|
-
|
|
154
157
|
[tool.pytest.ini_options]
|
|
155
158
|
asyncio_mode = "auto"
|
|
156
159
|
testpaths = [
|
|
@@ -187,4 +190,4 @@ Homepage="https://updates2mqtt.rhizomatics.org.uk"
|
|
|
187
190
|
Repository="https://github.com/rhizomatics/updates2mqtt"
|
|
188
191
|
Documentation="https://updates2mqtt.rhizomatics.org.uk"
|
|
189
192
|
Issues="https://github.com/rhizomatics/updates2mqtt/issues"
|
|
190
|
-
Changelog="https://github.com/rhizomatics/updates2mqtt/blob/main/CHANGELOG.md"
|
|
193
|
+
Changelog="https://github.com/rhizomatics/updates2mqtt/blob/main/CHANGELOG.md"
|
|
@@ -14,7 +14,7 @@ import structlog
|
|
|
14
14
|
import updates2mqtt
|
|
15
15
|
from updates2mqtt.model import Discovery, ReleaseProvider
|
|
16
16
|
|
|
17
|
-
from .config import Config,
|
|
17
|
+
from .config import Config, PublishPolicy, UpdatePolicy, load_app_config
|
|
18
18
|
from .integrations.docker import DockerProvider
|
|
19
19
|
from .mqtt import MqttPublisher
|
|
20
20
|
|
|
@@ -49,7 +49,6 @@ class App:
|
|
|
49
49
|
|
|
50
50
|
structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(getattr(logging, str(self.cfg.log.level))))
|
|
51
51
|
log.debug("Logging initialized", level=self.cfg.log.level)
|
|
52
|
-
self.common_pkg: dict[str, PackageUpdateInfo] = load_package_info(PKG_INFO_FILE)
|
|
53
52
|
|
|
54
53
|
self.publisher = MqttPublisher(self.cfg.mqtt, self.cfg.node, self.cfg.homeassistant)
|
|
55
54
|
|
|
@@ -57,7 +56,7 @@ class App:
|
|
|
57
56
|
self.scan_count: int = 0
|
|
58
57
|
self.last_scan: str | None = None
|
|
59
58
|
if self.cfg.docker.enabled:
|
|
60
|
-
self.scanners.append(DockerProvider(self.cfg.docker, self.
|
|
59
|
+
self.scanners.append(DockerProvider(self.cfg.docker, self.cfg.node, self.self_bounce))
|
|
61
60
|
self.stopped = Event()
|
|
62
61
|
self.healthcheck_topic = self.cfg.node.healthcheck.topic_template.format(node_name=self.cfg.node.name)
|
|
63
62
|
|
|
@@ -104,6 +103,7 @@ class App:
|
|
|
104
103
|
)
|
|
105
104
|
|
|
106
105
|
for scanner in self.scanners:
|
|
106
|
+
scanner.initialize()
|
|
107
107
|
self.publisher.subscribe_hass_command(scanner)
|
|
108
108
|
|
|
109
109
|
while not self.stopped.is_set() and self.publisher.is_available():
|
|
@@ -122,12 +122,15 @@ class App:
|
|
|
122
122
|
async def on_discovery(self, discovery: Discovery) -> None:
|
|
123
123
|
dlog = log.bind(name=discovery.name)
|
|
124
124
|
try:
|
|
125
|
-
if self.cfg.homeassistant.discovery.enabled:
|
|
125
|
+
if discovery.publish_policy == PublishPolicy.HOMEASSISTANT and self.cfg.homeassistant.discovery.enabled:
|
|
126
|
+
# Switch off MQTT discovery if not Home Assistant enabled
|
|
126
127
|
self.publisher.publish_hass_config(discovery)
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT):
|
|
129
|
+
self.publisher.publish_hass_state(discovery)
|
|
130
|
+
if discovery.publish_policy in (PublishPolicy.HOMEASSISTANT, PublishPolicy.MQTT):
|
|
131
|
+
self.publisher.publish_discovery(discovery)
|
|
129
132
|
if (
|
|
130
|
-
discovery.update_policy ==
|
|
133
|
+
discovery.update_policy == UpdatePolicy.AUTO
|
|
131
134
|
and discovery.can_update
|
|
132
135
|
and discovery.latest_version != discovery.current_version
|
|
133
136
|
):
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
import structlog
|
|
4
|
+
from omegaconf import DictConfig, OmegaConf
|
|
5
|
+
from rich import print_json
|
|
6
|
+
|
|
7
|
+
from updates2mqtt.config import DockerConfig, NodeConfig, RegistryConfig
|
|
8
|
+
from updates2mqtt.helpers import Throttler
|
|
9
|
+
from updates2mqtt.integrations.docker import DockerProvider
|
|
10
|
+
from updates2mqtt.integrations.docker_enrich import (
|
|
11
|
+
REGISTRIES,
|
|
12
|
+
ContainerDistributionAPIVersionLookup,
|
|
13
|
+
DockerImageInfo,
|
|
14
|
+
fetch_url,
|
|
15
|
+
)
|
|
16
|
+
from updates2mqtt.model import Discovery
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from httpx import Response
|
|
20
|
+
|
|
21
|
+
log = structlog.get_logger()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
Super simple CLI
|
|
26
|
+
|
|
27
|
+
python updates2mqtt.cli container=frigate
|
|
28
|
+
|
|
29
|
+
python updates2mqtt.cli container=frigate api=docker_client log_level=DEBUG
|
|
30
|
+
|
|
31
|
+
ython3 updates2mqtt/cli.py blob=ghcr.io/homarr-labs/homarr@sha256:af79a3339de5ed8ef7f5a0186ff3deb86f40b213ba75249291f2f68aef082a25 | jq '.config.Labels'
|
|
32
|
+
|
|
33
|
+
python3 updates2mqtt/cli.py manifest=ghcr.io/blakeblackshear/frigate:stable
|
|
34
|
+
|
|
35
|
+
python3 updates2mqtt/cli.py blob=ghcr.io/blakeblackshear/frigate@sha256:ef8d56a7d50b545af176e950ce328aec7f0b7bc5baebdca189fe661d97924980
|
|
36
|
+
|
|
37
|
+
python3 updates2mqtt/cli.py manifest=ghcr.io/blakeblackshear/frigate@sha256:c68fd78fd3237c9ba81b5aa927f17b54f46705990f43b4b5d5596cfbbb626af4
|
|
38
|
+
""" # noqa: E501
|
|
39
|
+
|
|
40
|
+
OCI_MANIFEST_TYPES: list[str] = [
|
|
41
|
+
"application/vnd.oci.image.manifest.v1+json",
|
|
42
|
+
"application/vnd.oci.image.index.v1+json",
|
|
43
|
+
"application/vnd.oci.descriptor.v1+json",
|
|
44
|
+
"application/vnd.oci.empty.v1+json",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
OCI_CONFIG_TYPES: list[str] = [
|
|
48
|
+
"application/vnd.oci.image.config.v1+json",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
OCI_LAYER_TYPES: list[str] = [
|
|
52
|
+
"application/vnd.oci.image.layer.v1.tar",
|
|
53
|
+
"application/vnd.oci.image.layer.v1.tar+gzip",
|
|
54
|
+
"application/vnd.oci.image.layer.v1.tar+zstd",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
OCI_NONDISTRIBUTABLE_LAYER_TYPES: list[str] = [
|
|
58
|
+
"application/vnd.oci.image.layer.nondistributable.v1.tar",
|
|
59
|
+
"application/vnd.oci.image.layer.nondistributable.v1.tar+gzip",
|
|
60
|
+
"application/vnd.oci.image.layer.nondistributable.v1.tar+zstd",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
# Docker Compatibility MIME Types
|
|
64
|
+
DOCKER_MANIFEST_TYPES: list[str] = [
|
|
65
|
+
"application/vnd.docker.distribution.manifest.v2+json",
|
|
66
|
+
"application/vnd.docker.distribution.manifest.list.v2+json",
|
|
67
|
+
"application/vnd.docker.distribution.manifest.v1+json",
|
|
68
|
+
"application/vnd.docker.distribution.manifest.v1+prettyjws",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
DOCKER_CONFIG_TYPES: list[str] = [
|
|
72
|
+
"application/vnd.docker.container.image.v1+json",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
DOCKER_LAYER_TYPES: list[str] = [
|
|
76
|
+
"application/vnd.docker.image.rootfs.diff.tar.gzip",
|
|
77
|
+
"application/vnd.docker.image.rootfs.foreign.diff.tar.gzip",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
# Combined constants
|
|
81
|
+
ALL_MANIFEST_TYPES: list[str] = OCI_MANIFEST_TYPES + DOCKER_MANIFEST_TYPES
|
|
82
|
+
ALL_CONFIG_TYPES: list[str] = OCI_CONFIG_TYPES + DOCKER_CONFIG_TYPES
|
|
83
|
+
ALL_LAYER_TYPES: list[str] = OCI_LAYER_TYPES + OCI_NONDISTRIBUTABLE_LAYER_TYPES + DOCKER_LAYER_TYPES
|
|
84
|
+
|
|
85
|
+
# All content types that might be returned by the API
|
|
86
|
+
ALL_OCI_MEDIA_TYPES: list[str] = (
|
|
87
|
+
ALL_MANIFEST_TYPES
|
|
88
|
+
+ ALL_CONFIG_TYPES
|
|
89
|
+
+ ALL_LAYER_TYPES
|
|
90
|
+
+ ["application/octet-stream", "application/json"] # Error responses
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def dump_url(doc_type: str, img_ref: str) -> None:
|
|
95
|
+
lookup = ContainerDistributionAPIVersionLookup(Throttler(), RegistryConfig())
|
|
96
|
+
img_info = DockerImageInfo(img_ref)
|
|
97
|
+
if not img_info.index_name or not img_info.name:
|
|
98
|
+
log.error("Unable to parse %ss", img_ref)
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
api_host: str | None = REGISTRIES.get(img_info.index_name, (img_info.index_name, img_info.index_name))[1]
|
|
102
|
+
|
|
103
|
+
if doc_type == "blob":
|
|
104
|
+
if not img_info.pinned_digest:
|
|
105
|
+
log.warning("No digest found in %s", img_ref)
|
|
106
|
+
return
|
|
107
|
+
url: str = f"https://{api_host}/v2/{img_info.name}/blobs/{img_info.pinned_digest}"
|
|
108
|
+
elif doc_type == "manifest":
|
|
109
|
+
if not img_info.tag_or_digest:
|
|
110
|
+
log.warning("No tag or digest found in %s", img_ref)
|
|
111
|
+
return
|
|
112
|
+
url = f"https://{api_host}/v2/{img_info.name}/manifests/{img_info.tag_or_digest}"
|
|
113
|
+
else:
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
token: str | None = lookup.fetch_token(img_info.index_name, img_info.name)
|
|
117
|
+
|
|
118
|
+
response: Response | None = fetch_url(url, bearer_token=token, follow_redirects=True, response_type=ALL_OCI_MEDIA_TYPES)
|
|
119
|
+
if response:
|
|
120
|
+
log.debug(f"{response.status_code}: {url}")
|
|
121
|
+
log.debug("HEADERS")
|
|
122
|
+
for k, v in response.headers.items():
|
|
123
|
+
log.debug(f"{k}: {v}")
|
|
124
|
+
log.debug("CONTENTS")
|
|
125
|
+
print_json(response.text)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def main() -> None:
|
|
129
|
+
# will be a proper cli someday
|
|
130
|
+
cli_conf: DictConfig = OmegaConf.from_cli()
|
|
131
|
+
structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(cli_conf.get("log_level", "WARNING")))
|
|
132
|
+
|
|
133
|
+
if cli_conf.get("blob"):
|
|
134
|
+
dump_url("blob", cli_conf.get("blob"))
|
|
135
|
+
elif cli_conf.get("manifest"):
|
|
136
|
+
dump_url("manifest", cli_conf.get("manifest"))
|
|
137
|
+
|
|
138
|
+
else:
|
|
139
|
+
docker_scanner = DockerProvider(
|
|
140
|
+
DockerConfig(registry=RegistryConfig(api=cli_conf.get("api", "OCI_V2"))), NodeConfig(), None
|
|
141
|
+
)
|
|
142
|
+
discovery: Discovery | None = docker_scanner.rescan(
|
|
143
|
+
Discovery(docker_scanner, cli_conf.get("container", "frigate"), "cli", "manual")
|
|
144
|
+
)
|
|
145
|
+
if discovery:
|
|
146
|
+
log.info(discovery.as_dict())
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
main()
|
|
@@ -9,6 +9,23 @@ from omegaconf import MISSING, DictConfig, MissingMandatoryValue, OmegaConf, Val
|
|
|
9
9
|
|
|
10
10
|
log = structlog.get_logger()
|
|
11
11
|
|
|
12
|
+
PKG_INFO_FILE = Path("./common_packages.yaml")
|
|
13
|
+
UNKNOWN_VERSION = "UNKNOWN"
|
|
14
|
+
VERSION_RE = r"[vVr]?[0-9]+(\.[0-9]+)*"
|
|
15
|
+
# source: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
|
|
16
|
+
SEMVER_RE = r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" # noqa: E501
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UpdatePolicy(StrEnum):
|
|
20
|
+
AUTO = "Auto"
|
|
21
|
+
PASSIVE = "Passive"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PublishPolicy(StrEnum):
|
|
25
|
+
HOMEASSISTANT = "HomeAssistant"
|
|
26
|
+
MQTT = "MQTT"
|
|
27
|
+
SILENT = "Silent"
|
|
28
|
+
|
|
12
29
|
|
|
13
30
|
class LogLevel(StrEnum):
|
|
14
31
|
DEBUG = "DEBUG"
|
|
@@ -18,6 +35,28 @@ class LogLevel(StrEnum):
|
|
|
18
35
|
CRITICAL = "CRITICAL"
|
|
19
36
|
|
|
20
37
|
|
|
38
|
+
class RegistryAPI(StrEnum):
|
|
39
|
+
OCI_V2 = "OCI_V2"
|
|
40
|
+
OCI_V2_MINIMAL = "OCI_V2"
|
|
41
|
+
DOCKER_CLIENT = "DOCKER_CLIENT"
|
|
42
|
+
DISABLED = "DISABLED"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class VersionType:
|
|
46
|
+
SHORT_SHA = "short_sha"
|
|
47
|
+
FULL_SHA = "full_sha"
|
|
48
|
+
VERSION_REVISION = "version_revision"
|
|
49
|
+
VERSION = "version"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class RegistryConfig:
|
|
54
|
+
api: RegistryAPI = RegistryAPI.OCI_V2
|
|
55
|
+
mutable_cache_ttl: int | None = None # default to server cache hint
|
|
56
|
+
immutable_cache_ttl: int | None = 7776000 # 90 days
|
|
57
|
+
token_cache_ttl: int | None = None # default to server cache hint
|
|
58
|
+
|
|
59
|
+
|
|
21
60
|
@dataclass
|
|
22
61
|
class MqttConfig:
|
|
23
62
|
host: str = "${oc.env:MQTT_HOST,localhost}"
|
|
@@ -34,6 +73,19 @@ class MetadataSourceConfig:
|
|
|
34
73
|
cache_ttl: int = 60 * 60 * 24 * 7 # 1 week
|
|
35
74
|
|
|
36
75
|
|
|
76
|
+
@dataclass
|
|
77
|
+
class Selector:
|
|
78
|
+
include: list[str] | None = None
|
|
79
|
+
exclude: list[str] | None = None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class VersionPolicy(StrEnum):
|
|
83
|
+
AUTO = "AUTO"
|
|
84
|
+
VERSION = "VERSION"
|
|
85
|
+
DIGEST = "DIGEST"
|
|
86
|
+
VERSION_DIGEST = "VERSION_DIGEST"
|
|
87
|
+
|
|
88
|
+
|
|
37
89
|
@dataclass
|
|
38
90
|
class DockerConfig:
|
|
39
91
|
enabled: bool = True
|
|
@@ -47,7 +99,12 @@ class DockerConfig:
|
|
|
47
99
|
discover_metadata: dict[str, MetadataSourceConfig] = field(
|
|
48
100
|
default_factory=lambda: {"linuxserver.io": MetadataSourceConfig(enabled=True)}
|
|
49
101
|
)
|
|
50
|
-
|
|
102
|
+
registry: RegistryConfig = field(default_factory=lambda: RegistryConfig())
|
|
103
|
+
default_api_backoff: int = 60 * 15
|
|
104
|
+
image_ref_select: Selector = field(default_factory=lambda: Selector())
|
|
105
|
+
version_select: Selector = field(default_factory=lambda: Selector())
|
|
106
|
+
version_policy: VersionPolicy = VersionPolicy.AUTO
|
|
107
|
+
registry_select: Selector = field(default_factory=lambda: Selector())
|
|
51
108
|
|
|
52
109
|
|
|
53
110
|
@dataclass
|
|
@@ -62,6 +119,7 @@ class HomeAssistantConfig:
|
|
|
62
119
|
state_topic_suffix: str = "state"
|
|
63
120
|
device_creation: bool = True
|
|
64
121
|
force_command_topic: bool = False
|
|
122
|
+
extra_attributes: bool = True
|
|
65
123
|
area: str | None = None
|
|
66
124
|
|
|
67
125
|
|
|
@@ -96,7 +154,7 @@ class Config:
|
|
|
96
154
|
|
|
97
155
|
@dataclass
|
|
98
156
|
class DockerPackageUpdateInfo:
|
|
99
|
-
image_name: str = MISSING
|
|
157
|
+
image_name: str = MISSING # untagged image ref
|
|
100
158
|
|
|
101
159
|
|
|
102
160
|
@dataclass
|
|
@@ -104,6 +162,7 @@ class PackageUpdateInfo:
|
|
|
104
162
|
docker: DockerPackageUpdateInfo | None = field(default_factory=DockerPackageUpdateInfo)
|
|
105
163
|
logo_url: str | None = None
|
|
106
164
|
release_notes_url: str | None = None
|
|
165
|
+
source_repo_url: str | None = None
|
|
107
166
|
|
|
108
167
|
|
|
109
168
|
@dataclass
|
|
@@ -115,24 +174,6 @@ class IncompleteConfigException(BaseException):
|
|
|
115
174
|
pass
|
|
116
175
|
|
|
117
176
|
|
|
118
|
-
def load_package_info(pkginfo_file_path: Path) -> dict[str, PackageUpdateInfo]:
|
|
119
|
-
if pkginfo_file_path.exists():
|
|
120
|
-
log.debug("Loading common package update info", path=pkginfo_file_path)
|
|
121
|
-
cfg = OmegaConf.load(pkginfo_file_path)
|
|
122
|
-
else:
|
|
123
|
-
log.warn("No common package update info found", path=pkginfo_file_path)
|
|
124
|
-
cfg = OmegaConf.structured(UpdateInfoConfig)
|
|
125
|
-
try:
|
|
126
|
-
# omegaconf broken-ness on optional fields and converting to backclasses
|
|
127
|
-
pkg_conf: dict[str, PackageUpdateInfo] = {
|
|
128
|
-
pkg: PackageUpdateInfo(**pkg_cfg) for pkg, pkg_cfg in cfg.common_packages.items()
|
|
129
|
-
}
|
|
130
|
-
return pkg_conf
|
|
131
|
-
except (MissingMandatoryValue, ValidationError) as e:
|
|
132
|
-
log.error("Configuration error %s", e, path=pkginfo_file_path.as_posix())
|
|
133
|
-
raise
|
|
134
|
-
|
|
135
|
-
|
|
136
177
|
def is_autogen_config() -> bool:
|
|
137
178
|
env_var: str | None = os.environ.get("U2M_AUTOGEN_CONFIG")
|
|
138
179
|
return not (env_var and env_var.lower() in ("no", "0", "false"))
|