From ac2a103215e4400204f7b266f1ff70f938724ab3 Mon Sep 17 00:00:00 2001 From: Daniel McKnight <34697904+NeonDaniel@users.noreply.github.com> Date: Mon, 11 Mar 2024 17:32:15 -0700 Subject: [PATCH 1/7] Refactor to use neon-os Releases (#15) * Start refactor to accommodate neon-os repo * Add methods to get latest GH release and metadata Support neon-os repo updates (squashfs and initramfs) * Add `neon.device_updater.check_update` handler for update skill support * Add `neon.device_updater.get_build_info` handler for update skill support * Update logging * Catch and remove invalid downloaded updates with unit tests * Update unit test to handle refactored beta tags * Fix update url ref * Update tests to use smaller test files to download * Fix unit test to handle param refactored to property * Extend test timeout to account for slow downloads --------- Co-authored-by: Daniel McKnight --- .github/workflows/unit_tests.yml | 2 +- neon_phal_plugin_device_updater/__init__.py | 324 ++++++++++++++------ tests/unit_tests.py | 84 ++++- 3 files changed, 311 insertions(+), 99 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 92f7bb4..2be59c8 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -13,7 +13,7 @@ jobs: with: python_version: "3.8" unit_tests: - timeout-minutes: 15 + timeout-minutes: 25 strategy: matrix: python-version: [ 3.7, 3.8, 3.9, '3.10' ] diff --git a/neon_phal_plugin_device_updater/__init__.py b/neon_phal_plugin_device_updater/__init__.py index 5419dcc..5072303 100644 --- a/neon_phal_plugin_device_updater/__init__.py +++ b/neon_phal_plugin_device_updater/__init__.py @@ -34,10 +34,12 @@ from datetime import datetime from typing import Optional, Tuple, Union from os import remove -from os.path import isfile, join, dirname +from os.path import isfile, join, dirname, getsize from subprocess import Popen + +import yaml from ovos_bus_client.message import Message -from ovos_utils.log import LOG +from ovos_utils.log import LOG, log_deprecation from ovos_plugin_manager.phal import PHALPlugin from neon_utils.web_utils import scrape_page_for_links @@ -46,18 +48,12 @@ class DeviceUpdater(PHALPlugin): def __init__(self, bus=None, name="neon-phal-plugin-device-updater", config=None): PHALPlugin.__init__(self, bus, name, config) - self.initramfs_url = self.config.get("initramfs_url", - "https://github.com/NeonGeckoCom/" - "neon_debos/raw/{}/overlays/" - "02-rpi4/boot/firmware/initramfs") - self.initramfs_real_path = self.config.get("initramfs_path", - "/opt/neon/firmware/initramfs") + self.initramfs_real_path = self.config.get( + "initramfs_path", "/opt/neon/firmware/initramfs") self.initramfs_update_path = self.config.get("initramfs_upadate_path", "/opt/neon/initramfs") - self.squashfs_url = self.config.get("squashfs_url", - "https://2222.us/app/files/" - "neon_images/pi/mycroft_mark_2/" - "updates/{}/") + self.release_repo = self.config.get("release_repo", + "NeonGeckoCom/neon-os") self.squashfs_path = self.config.get("squashfs_path", "/opt/neon/update.squashfs") @@ -70,6 +66,23 @@ def __init__(self, bus=None, name="neon-phal-plugin-device-updater", self.bus.on("neon.update_initramfs", self.update_initramfs) self.bus.on("neon.check_update_squashfs", self.check_update_squashfs) self.bus.on("neon.update_squashfs", self.update_squashfs) + self.bus.on("neon.device_updater.check_update", + self.check_update_available) + self.bus.on("neon.device_updater.get_build_info", + self.get_build_info) + + @property + def squashfs_url(self): + log_deprecation("FTP update references are deprecated.", "1.0.0") + return self.config.get("squashfs_url", "https://2222.us/app/files/" + "neon_images/pi/mycroft_mark_2/" + "updates/{}/") + + @property + def initramfs_url(self): + return self.config.get("initramfs_url", + "https://github.com/NeonGeckoCom/neon_debos/raw/" + "{}/overlays/02-rpi4/boot/firmware/initramfs") @property def initramfs_hash(self) -> Optional[str]: @@ -103,7 +116,8 @@ def build_info(self) -> dict: self._build_info = dict() return self._build_info - def _check_initramfs_update_available(self, branch: str = None) -> bool: + def _legacy_check_initramfs_update_available(self, + branch: str = None) -> bool: """ Check if there is a newer initramfs version available by comparing MD5 @param branch: branch to format into initramfs_url @@ -127,13 +141,15 @@ def _check_initramfs_update_available(self, branch: str = None) -> bool: if new_hash == self.initramfs_hash: LOG.info("initramfs not changed") return False - LOG.info("initramfs update available") + LOG.info(f"initramfs update available (new={new_hash}|" + f"old={self.initramfs_hash}") return True def _get_initramfs_latest(self, branch: str = None) -> bool: """ Get the latest initramfs image and check if it is different from the - current installed initramfs + current installed initramfs. This will save the updated file locally, + but will not apply the update. @param branch: branch to format into initramfs_url @return: True if the downloaded initramfs file is different from current """ @@ -161,46 +177,13 @@ def _get_initramfs_latest(self, branch: str = None) -> bool: return False return True - @staticmethod - def check_version_is_newer(current: Union[str, int, float], - latest: Union[str, int, float]) -> bool: - """ - Compare two image versions to check if an update is available - @param current: currently installed version (timestamp or formatted) - @param latest: latest available version from remote - @return: True if latest is newer than current - """ - try: - date_format = "%Y-%m-%d_%H_%M" - if isinstance(current, str): - current_datetime = datetime.strptime(current, date_format) - elif isinstance(current, (int, float)): - current_datetime = datetime.fromtimestamp(current) - else: - raise TypeError(f"Expected formatted time or timestamp. " - f"Got: {current}") - if isinstance(latest, str): - latest_datetime = datetime.strptime(latest, date_format) - elif isinstance(latest, (int, float)): - latest_datetime = datetime.fromtimestamp(latest) - else: - raise TypeError(f"Expected formatted time or timestamp. " - f"Got: {latest}") - if latest_datetime > current_datetime: - return True - return False - except Exception as e: - LOG.exception(e) - # Parse failure, assume there's an update - return True - - def _check_squashfs_update_available(self, track: str = None) \ + def _legacy_check_squashfs_update_available(self, track: str = None) \ -> Optional[Tuple[str, str]]: """ Check if a newer squashFS image is available and return the new version and download link. @param track: Update track (subdirectory) to check - @return: new version and download link if available, else None + @return: new version (filename) and download link if available, else None """ track = track or self._default_branch # Get all available update files from the configured URL @@ -229,7 +212,7 @@ def _check_squashfs_update_available(self, track: str = None) \ LOG.info(f"Installed image ({installed_image_time}) is newer " f"than latest ({new_image_time})") - def _get_squashfs_latest(self, track: str = None) -> Optional[str]: + def _legacy_get_squashfs_latest(self, track: str = None) -> Optional[str]: """ Get the latest squashfs image if different from the installed version @param track: update track (subdirectory) to check @@ -237,7 +220,7 @@ def _get_squashfs_latest(self, track: str = None) -> Optional[str]: """ track = track or self._default_branch # Check for an available update - update = self._check_squashfs_update_available(track) + update = self._legacy_check_squashfs_update_available(track) if not update: # Already updated return None @@ -250,6 +233,19 @@ def _get_squashfs_latest(self, track: str = None) -> Optional[str]: LOG.info("Update already downloaded") return download_path + return self._stream_download_file(download_url, download_path) + + @staticmethod + def _stream_download_file(download_url: str, + download_path: str) -> Optional[str]: + """ + Download a remote resource to a local path and return the path to the + written file. This will provide some trivial validation that the output + file is an OS update. + @param download_url: URL of file to download + @param download_path: path of output file + @return: actual path to output file + """ # Download the update LOG.info(f"Downloading update from {download_url}") temp_dl_path = f"{download_path}.download" @@ -259,20 +255,114 @@ def _get_squashfs_latest(self, track: str = None) -> Optional[str]: for chunk in stream.iter_content(4096): if chunk: f.write(chunk) + # Update should be > 100MiB + file_mib = getsize(temp_dl_path) / 1048576 + if file_mib < 100: + LOG.error(f"Downloaded file is too small ({file_mib}MiB)") + remove(temp_dl_path) + return shutil.move(temp_dl_path, download_path) + LOG.info(f"Saved download to {download_path}") return download_path except Exception as e: LOG.exception(e) if isfile(temp_dl_path): remove(temp_dl_path) + def _get_gh_latest_release_tag(self, track: str = None) -> str: + """ + Get the GitHub release tag associated with the latest version of the + installed OS (on the requested track). Note that the latest GitHub + release may not be relevant to the installed OS + @param track: "beta" or "stable" release track. An invalid request will + default to "stable" + @return: String tag in `self.release_repo` corresponding to the newest + valid release + """ + include_prerelease = (track or self._default_branch) in ("dev", "beta") + + default_time = "2000-01-01T00:00:00Z" + url = f'https://api.github.com/repos/{self.release_repo}/releases' + releases: list = requests.get(url).json() + if not include_prerelease: + releases = [r for r in releases if not r.get('prerelease', True)] + installed_os = self.build_info.get("base_os", {}).get("name") + if not installed_os: + raise RuntimeError(f"Unable to determine installed OS from: " + f"{self.build_info}") + releases = [r for r in releases if installed_os in r.get('body', '')] + releases.sort(key=lambda r: datetime.strptime(r.get('created_at', + default_time), + "%Y-%m-%dT%H:%M:%SZ"), + reverse=True) + return releases[0].get('tag_name') + + def _get_gh_release_meta_from_tag(self, tag: str) -> dict: + """ + Get release metadata for the installed OS at the requested `tag` + @param tag: Release tag to get metadata from + @return: dict latest release data for the requested tag + """ + installed_os = self.build_info.get("base_os", {}).get("name") + if not installed_os: + raise RuntimeError(f"Unable to determine installed OS from: " + f"{self.build_info}") + meta_url = (f"https://raw.githubusercontent.com/{self.release_repo}/" + f"{tag}/{installed_os}.yaml") + resp = requests.get(meta_url) + if not resp.ok: + raise ValueError(f"Unable to get metadata for tag={tag}") + meta_text = resp.text + release_meta = yaml.safe_load(meta_text) + + return release_meta[0] + + @staticmethod + def check_version_is_newer(current: Union[str, int, float], + latest: Union[str, int, float]) -> bool: + """ + Compare two image versions to check if an update is available + @param current: currently installed version (timestamp or formatted) + @param latest: latest available version from remote + @return: True if latest is newer than current + """ + try: + date_format = "%Y-%m-%d_%H_%M" + if isinstance(current, str): + current_datetime = datetime.strptime(current, date_format) + elif isinstance(current, (int, float)): + current_datetime = datetime.fromtimestamp(current) + else: + raise TypeError(f"Expected formatted time or timestamp. " + f"Got: {current}") + if isinstance(latest, str): + latest_datetime = datetime.strptime(latest, date_format) + elif isinstance(latest, (int, float)): + latest_datetime = datetime.fromtimestamp(latest) + else: + raise TypeError(f"Expected formatted time or timestamp. " + f"Got: {latest}") + if latest_datetime > current_datetime: + return True + return False + except Exception as e: + LOG.exception(e) + # Parse failure, assume there's an update + return True + def check_update_initramfs(self, message: Message): """ Handle a request to check for initramfs updates @param message: `neon.check_update_initramfs` Message """ branch = message.data.get("track") or self._default_branch - update_available = self._check_initramfs_update_available(branch) + try: + meta = self._get_gh_release_meta_from_tag( + self._get_gh_latest_release_tag(self._default_branch)) + update_available = meta['initramfs']['md5'] != self.initramfs_hash + except Exception as e: + LOG.exception(e) + update_available = self._legacy_check_initramfs_update_available(branch) self.bus.emit(message.response({"update_available": update_available, "track": branch})) @@ -282,26 +372,41 @@ def check_update_squashfs(self, message: Message): @param message: `neon.check_update_squashfs` Message """ track = message.data.get("track") or self._default_branch - response = self._check_squashfs_update_available(track) + try: + tag = self._get_gh_latest_release_tag(track) + if self._build_info.get('version') and \ + self._build_info['version'] == tag: + LOG.debug(f"Already up to date") + update_available = False + update_meta = None + else: + update_meta = self._get_gh_release_meta_from_tag(tag) + update_available = ( + update_meta['base_os'] != self._build_info['base_os']) + except Exception as e: + LOG.info(f"Falling back to legacy update check: {e}") + response = self._legacy_check_squashfs_update_available(track) - if response: - update_available = True - new_version, download_url = response - # Get metadata for new version - meta_url = download_url.replace(".squashfs", ".json") - try: - resp = requests.get(meta_url) - if resp.ok: - update_meta = resp.json() - else: - LOG.warning(f"Unable to get metadata: {resp.status_code}") + if response: + update_available = True + new_version, download_url = response + # Get metadata for new version + meta_url = download_url.replace(".squashfs", ".json") + try: + resp = requests.get(meta_url) + if resp.ok: + update_meta = resp.json() + else: + LOG.warning(f"Unable to get metadata: " + f"{resp.status_code}") + update_meta = dict() + except Exception as e: + LOG.exception(e) update_meta = dict() - except Exception as e: - LOG.exception(e) - update_meta = dict() - else: - update_available = False - update_meta = None + update_meta["download_url"] = download_url + else: + update_available = False + update_meta = None self.bus.emit(message.response({"update_available": update_available, "update_metadata": update_meta, @@ -312,14 +417,33 @@ def update_squashfs(self, message: Message): Handle a request to update squashfs @param message: `neon.update_squashfs` Message """ + track = message.data.get("track") or self._default_branch + LOG.info(f"Checking squashfs update: {track}") + update_metadata = message.data.get("update_metadata") try: - track = message.data.get("track") or self._default_branch - LOG.info(f"Checking squashfs update: {track}") - update = self._get_squashfs_latest(track) - if update: - LOG.info("Update available and will be installed on restart") - shutil.copyfile(update, self.squashfs_path) - response = message.response({"new_version": update}) + if not update_metadata: + update_metadata = self._get_gh_release_meta_from_tag( + self._get_gh_latest_release_tag(track)) + platform = self.build_info['base_os']['platform'] + download_url = update_metadata['download_url'].replace( + f"/{platform}/", f"/{platform}/updates/").replace(".img.xz", + ".squashfs") + download_path = join(dirname(self.initramfs_update_path), + update_metadata['build_version']) + if isfile(download_path): + LOG.info("Update already downloaded") + return download_path + update_file = self._stream_download_file(download_url, + download_path) + except Exception as e: + LOG.exception(f"Failed to get download_url: {e}") + update_file = self._legacy_get_squashfs_latest(track) + + try: + if update_file: + LOG.info("Update downloaded and will be installed on restart") + shutil.copyfile(update_file, self.squashfs_path) + response = message.response({"new_version": update_file}) else: LOG.info("Already updated") response = message.response({"new_version": None}) @@ -330,18 +454,26 @@ def update_squashfs(self, message: Message): def update_initramfs(self, message: Message): """ - Handle a request to update initramfs + Handle a request to update initramfs. @param message: `neon.update_initramfs` Message """ + branch = message.data.get("track") or self._default_branch + LOG.info("Performing initramfs update") + if not isfile(self.initramfs_real_path) and \ + not message.data.get("force_update"): + LOG.debug("No initramfs to update") + response = message.response({"updated": None, + "error": "No initramfs to update"}) + self.bus.emit(response) + return + try: + meta = self._get_gh_release_meta_from_tag( + self._get_gh_latest_release_tag(branch)) + branch = meta['image']['version'] + except Exception as e: + LOG.error(f"Failed to get image version for branch {branch}: {e}") try: - branch = message.data.get("track") or self._default_branch - LOG.info("Performing initramfs update") - if not isfile(self.initramfs_real_path) and \ - not message.data.get("force_update"): - LOG.debug("No initramfs to update") - response = message.response({"updated": None, - "error": "No initramfs to update"}) - elif not self._get_initramfs_latest(branch): + if not self._get_initramfs_latest(branch): LOG.info("No initramfs update") response = message.response({"updated": False}) else: @@ -361,3 +493,21 @@ def update_initramfs(self, message: Message): response = message.response({"updated": None, "error": repr(e)}) self.bus.emit(response) + + def check_update_available(self, message: Message): + """ + Handle a request to check for OS updates + @param message: `neon.device_updater.check_update` Message + """ + track = "beta" if message.data.get("include_prerelease") else "stable" + installed_version = self.build_info.get("build_version") + latest_version = self._get_gh_latest_release_tag(track) + self.bus.emit(message.response({"installed_version": installed_version, + "latest_version": latest_version})) + + def get_build_info(self, message: Message): + """ + Handle a request to check for current OS build info + @param message: `neon.device_updater.get_build_info` Message + """ + self.bus.emit(message.response(self.build_info)) diff --git a/tests/unit_tests.py b/tests/unit_tests.py index 38affc2..70296c3 100644 --- a/tests/unit_tests.py +++ b/tests/unit_tests.py @@ -27,6 +27,7 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import logging import unittest +from tempfile import mkstemp from time import time import requests @@ -60,7 +61,7 @@ def test_check_initramfs_update_available(self): with open(self.plugin.initramfs_real_path, 'w+') as f: f.write("test") self.plugin._initramfs_hash = None - self.assertTrue(self.plugin._check_initramfs_update_available()) + self.assertTrue(self.plugin._legacy_check_initramfs_update_available()) # Explicitly get valid initramfs with open(self.plugin.initramfs_real_path, 'wb') as f: @@ -68,7 +69,7 @@ def test_check_initramfs_update_available(self): self.plugin.initramfs_url.format( self.plugin._default_branch)).content) self.plugin._initramfs_hash = None - self.assertFalse(self.plugin._check_initramfs_update_available()) + self.assertFalse(self.plugin._legacy_check_initramfs_update_available()) remove(self.plugin.initramfs_real_path) @@ -77,10 +78,10 @@ def test_get_initramfs_latest(self): self.plugin._initramfs_hash = None # Check invalid URL - self.plugin.initramfs_url = None + self.plugin.config["initramfs_url"] = None with self.assertRaises(RuntimeError): self.plugin._get_initramfs_latest() - self.plugin.initramfs_url = real_url + self.plugin.config["initramfs_url"] = real_url # Update already downloaded and applied self.plugin._initramfs_hash = None @@ -125,24 +126,24 @@ def test_check_version_is_newer(self): def test_check_squashfs_update_available(self): self.plugin._build_info = dict() - version, url = self.plugin._check_squashfs_update_available() + version, url = self.plugin._legacy_check_squashfs_update_available() self.assertIsInstance(version, str) self.assertTrue(url.startswith('http')) new_image_time = version.split('_', 1)[1].rsplit('.', 1)[0] # Current equals remote self.plugin._build_info = {"base_os": {"time": new_image_time}} - self.assertIsNone(self.plugin._check_squashfs_update_available()) + self.assertIsNone(self.plugin._legacy_check_squashfs_update_available()) # Current newer than remote new_image_time = f"3000-{new_image_time.split('-', 1)[1]}" self.plugin._build_info["base_os"]["time"] = new_image_time - self.assertIsNone(self.plugin._check_squashfs_update_available()) + self.assertIsNone(self.plugin._legacy_check_squashfs_update_available()) # Current older than remote old_image_time = f"2020-{new_image_time.split('_', 1)[1]}" self.plugin._build_info["base_os"]["time"] = old_image_time - version2, url2 = self.plugin._check_squashfs_update_available() + version2, url2 = self.plugin._legacy_check_squashfs_update_available() self.assertEqual(version, version2) self.assertEqual(url, url2) @@ -154,17 +155,54 @@ def test_get_squashfs_latest(self): self.assertFalse(isfile(self.plugin.initramfs_update_path)) # Download valid update - file = self.plugin._get_squashfs_latest() + file = self.plugin._legacy_get_squashfs_latest() self.assertTrue(isfile(file)) # Already downloaded - self.assertEqual(self.plugin._get_squashfs_latest(), file) + self.assertEqual(self.plugin._legacy_get_squashfs_latest(), file) # Already updated image_name, image_time = basename(file).rsplit('.', 1)[0].split('_', 1) self.plugin._build_info = {'base_os': {'name': image_name, 'time': image_time}} - self.assertIsNone(self.plugin._get_squashfs_latest()) + self.assertIsNone(self.plugin._legacy_get_squashfs_latest()) + + def test_get_gh_latest_release(self): + self.plugin._build_info = {"base_os": {"name": ""}} + + # Validate unknown image + with self.assertRaises(RuntimeError): + self.plugin._get_gh_latest_release_tag() + + # Validate pre-release + self.plugin._build_info['base_os']['name'] = "debian-neon-image-rpi4" + latest_dev_release = self.plugin._get_gh_latest_release_tag("dev") + self.assertIsInstance(latest_dev_release, str) + self.assertTrue("b" in latest_dev_release) + + def test_get_gh_release_meta_from_tag(self): + self.plugin._build_info = {"base_os": {"name": ""}} + + # Validate unknown image + with self.assertRaises(RuntimeError): + self.plugin._get_gh_release_meta_from_tag("master") + + self.plugin._build_info['base_os']['name'] = "debian-neon-image-rpi4" + + # Validate unknown tag + with self.assertRaises(ValueError): + self.plugin._get_gh_release_meta_from_tag("00.01.01") + + # Validate valid release + tag = "24.02.28.beta1" + rpi_meta = self.plugin._get_gh_release_meta_from_tag(tag) + self.assertEqual(rpi_meta['version'], '24.02.28b1') + self.assertTrue('/rpi4/' in rpi_meta['download_url']) + + self.plugin._build_info['base_os']['name'] = "debian-neon-image-opi5" + opi_meta = self.plugin._get_gh_release_meta_from_tag(tag) + self.assertEqual(opi_meta['version'], '24.02.28b1') + self.assertTrue('/opi5/' in opi_meta['download_url']) def test_check_update_initramfs(self): # TODO @@ -182,6 +220,30 @@ def test_update_initramfs(self): # TODO pass + def test_stream_download_file(self): + valid_os_url = "https://2222.us/app/files/neon_images/test_images/test_os.img.xz" + valid_update_file = "https://2222.us/app/files/neon_images/test_images/update_file.squashfs" + invalid_update_file = "https://2222.us/app/files/neon_images/test_images/metadata.json" + valid_path = "https://2222.us/app/files/neon_images/test_images/" + invalid_path = "https://2222.us/app/files/neon_images/invalid_directory/" + + _, output_path = mkstemp() + remove(output_path) + + self.plugin._stream_download_file(invalid_update_file, output_path) + self.assertFalse(isfile(output_path)) + self.plugin._stream_download_file(valid_path, output_path) + self.assertFalse(isfile(output_path)) + self.plugin._stream_download_file(invalid_path, output_path) + self.assertFalse(isfile(output_path)) + + self.plugin._stream_download_file(valid_os_url, output_path) + self.assertTrue(isfile(output_path)) + remove(output_path) + self.plugin._stream_download_file(valid_update_file, output_path) + self.assertTrue(isfile(output_path)) + remove(output_path) + if __name__ == '__main__': unittest.main() From 5fd86dd8ecd8bbe4a48f7ac776c8c68000579d49 Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Tue, 12 Mar 2024 00:32:47 +0000 Subject: [PATCH 2/7] Increment Version to 0.1.1a1 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 4488dcb..fa01ac9 100644 --- a/version.py +++ b/version.py @@ -26,4 +26,4 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = "0.1.0" +__version__ = "0.1.1a1" From dba7a2babc1c55288163ec3d226c0bc54c241c52 Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Tue, 12 Mar 2024 00:33:11 +0000 Subject: [PATCH 3/7] Update Changelog --- CHANGELOG.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e04d4..b6a2aa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,12 @@ # Changelog -## [0.0.2a2](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.0.2a2) (2023-08-23) +## [0.1.1a1](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.1.1a1) (2024-03-12) -[Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.0.2a1...0.0.2a2) +[Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.1.0...0.1.1a1) **Merged pull requests:** -- Include Update Metadata in responses [\#12](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/pull/12) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [0.0.2a1](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.0.2a1) (2023-08-16) - -[Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.0.1...0.0.2a1) - -**Merged pull requests:** - -- More flexible metadata handling [\#10](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/pull/10) ([NeonDaniel](https://github.com/NeonDaniel)) +- Refactor to use neon-os Releases [\#15](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/pull/15) ([NeonDaniel](https://github.com/NeonDaniel)) From 2f5b22a7a4ad97361a3417f4f49ad14483c81505 Mon Sep 17 00:00:00 2001 From: Daniel McKnight <34697904+NeonDaniel@users.noreply.github.com> Date: Fri, 5 Apr 2024 10:35:28 -0700 Subject: [PATCH 4/7] Update ovos-utils to allow 0.X versions (#16) Co-authored-by: Daniel McKnight --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index e3e4488..e35ec82 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -2,4 +2,4 @@ requests neon-utils[network]~=1.6 ovos-plugin-manager~=0.0.20 ovos-bus-client~=0.0.3 -ovos-utils~=0.0.30 \ No newline at end of file +ovos-utils~=0.0,>=0.0.30 \ No newline at end of file From dd4f6fe76ca492af93da7b7facdad607749792af Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Fri, 5 Apr 2024 17:35:46 +0000 Subject: [PATCH 5/7] Increment Version to 0.1.1a2 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index fa01ac9..f948c1a 100644 --- a/version.py +++ b/version.py @@ -26,4 +26,4 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = "0.1.1a1" +__version__ = "0.1.1a2" From 086fc5924ef27b74d576d6d50db2521a1decca97 Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Fri, 5 Apr 2024 17:36:14 +0000 Subject: [PATCH 6/7] Update Changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6a2aa8..68ede8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.1.1a2](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.1.1a2) (2024-04-05) + +[Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.1.1a1...0.1.1a2) + +**Merged pull requests:** + +- Update ovos-utils dependency spec [\#16](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/pull/16) ([NeonDaniel](https://github.com/NeonDaniel)) + ## [0.1.1a1](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/tree/0.1.1a1) (2024-03-12) [Full Changelog](https://github.com/NeonGeckoCom/neon-phal-plugin-device-updater/compare/0.1.0...0.1.1a1) From ff0e1b870fb1d4d7c61d98a697070b01bf76a94c Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Mon, 22 Apr 2024 19:53:14 +0000 Subject: [PATCH 7/7] Increment Version to 0.2.0 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index f948c1a..96062fc 100644 --- a/version.py +++ b/version.py @@ -26,4 +26,4 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = "0.1.1a2" +__version__ = "0.2.0"