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.
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: updates2mqtt
3
- Version: 1.6.0
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.13
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]>=0.1.4
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
  ![updates2mqtt](images/updates2mqtt-dark-256x256.png){ 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
- ![Example Home Assistant update page](images/ha_update_detail.png "Home Assistant Updates")
64
+ ![Example Home Assistant update page](images/ha_update_detail.png "Home Assistant Updates")![Example Home Assistant Release Notes](images/ha_release_notes.png "Home Assistant Release Notes"){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)). 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
+ 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:release
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
- | `UPD2MQTT_VERSION_INCLUDE` | Only recognize versions matching this string or regular expression | |
163
- | `UPD2MQTT_VERSION_EXCLUDE` | Skip update if version matches this string or regular expression | |
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.version_include` | `UPD2MQTT_VERSION_INCLUDE` |
177
- | `updates2mqtt.version_exclude` | `UPD2MQTT_VERSION_EXCLUDE` |
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/1.0/) for caching metadata
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
- ![Example Home Assistant update page](images/ha_update_detail.png "Home Assistant Updates")
26
+ ![Example Home Assistant update page](images/ha_update_detail.png "Home Assistant Updates")![Example Home Assistant Release Notes](images/ha_release_notes.png "Home Assistant Release Notes"){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)). The design is modular, so other update sources can be added, at least for notification. The next anticipated is **apt** for Debian based systems.
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:release
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
- | `UPD2MQTT_VERSION_INCLUDE` | Only recognize versions matching this string or regular expression | |
126
- | `UPD2MQTT_VERSION_EXCLUDE` | Skip update if version matches this string or regular expression | |
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.version_include` | `UPD2MQTT_VERSION_INCLUDE` |
140
- | `updates2mqtt.version_exclude` | `UPD2MQTT_VERSION_EXCLUDE` |
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/1.0/) for caching metadata
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.6.0"
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]>=0.1.4",
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.13"
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, PackageUpdateInfo, load_app_config, load_package_info
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.common_pkg, self.cfg.node, self.self_bounce))
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
- self.publisher.publish_hass_state(discovery)
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 == "Auto"
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
- api_throttle_wait: int = 60 * 15
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"))