diff --git a/Cargo.lock b/Cargo.lock index fa4eaf6f..ec72a8ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -577,6 +577,7 @@ dependencies = [ "openssl", "openssl-sys2", "openssl2", + "semver", "serde", "serde_json", "sysinfo", @@ -2225,6 +2226,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + [[package]] name = "serde" version = "1.0.164" diff --git a/aziotctl/Cargo.toml b/aziotctl/Cargo.toml index edf3cdaf..547356d0 100644 --- a/aziotctl/Cargo.toml +++ b/aziotctl/Cargo.toml @@ -23,6 +23,7 @@ libc = "0.2" nix = "0.26" log = { version = "0.4", features = ["std"] } openssl = "0.10" +semver = "1.0" serde = { version = "1", features = ["derive"] } serde_json = "1.0.59" clap = { version = "4", features = ["derive"] } diff --git a/aziotctl/src/internal/check/checks/aziot_version.rs b/aziotctl/src/internal/check/checks/aziot_version.rs index bbed0ce4..1e0ab36e 100644 --- a/aziotctl/src/internal/check/checks/aziot_version.rs +++ b/aziotctl/src/internal/check/checks/aziot_version.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. use anyhow::{anyhow, Context, Result}; +use semver::Version; use serde::{Deserialize, Serialize}; use crate::internal::check::{CheckResult, Checker, CheckerCache, CheckerMeta, CheckerShared}; @@ -33,6 +34,8 @@ impl AziotVersion { shared: &CheckerShared, cache: &mut CheckerCache, ) -> Result { + const URI: &str = "https://aka.ms/azure-iotedge-latest-versions"; + let actual_version = env!("CARGO_PKG_VERSION"); let expected_version = if let Some(expected_aziot_version) = &shared.cfg.expected_aziot_version { @@ -52,13 +55,8 @@ impl AziotVersion { http_common::MaybeProxyConnector::new(shared.cfg.proxy_uri.clone(), None, &[]) .context("could not initialize HTTP connector")?; let client: hyper::Client<_, hyper::Body> = hyper::Client::builder().build(connector); - - let mut uri: hyper::Uri = "https://aka.ms/latest-aziot-identity-service" - .parse() - .expect("hard-coded URI cannot fail to parse"); - let LatestVersions { - aziot_identity_service, - } = loop { + let mut uri: hyper::Uri = URI.parse().expect("hard-coded URI cannot fail to parse"); + let latest_versions = loop { let req = { let mut req = hyper::Request::new(Default::default()); *req.uri_mut() = uri.clone(); @@ -66,8 +64,9 @@ impl AziotVersion { }; let res = client.request(req).await.with_context(|| { - format!("could not query {uri} for latest available version") + format!("could not query {URI} for latest available version") })?; + match res.status() { status_code if status_code.is_redirection() => { uri = res @@ -88,8 +87,9 @@ impl AziotVersion { let body = hyper::body::aggregate(res.into_body()) .await .context("could not read HTTP response")?; - let body = serde_json::from_reader(hyper::body::Buf::reader(body)) - .context("could not read HTTP response")?; + let body: LatestVersions = + serde_json::from_reader(hyper::body::Buf::reader(body)) + .context("could not read HTTP response")?; break body; } @@ -98,11 +98,42 @@ impl AziotVersion { } } }; - aziot_identity_service + + let actual_semver = Version::parse(actual_version) + .context("could not parse actual version as semver")?; + + let versions: Vec = latest_versions + .channels + .iter() + .flat_map(|channel| channel.products.iter()) + .filter(|product| product.id == "aziot-edge") + .flat_map(|product| product.components.iter()) + .filter(|component| component.name == "aziot-identity-service") + .map(|component| component.version.clone()) + .collect(); + + let parsed_versions = versions + .iter() + .map(|version| { + Version::parse(version).context("could not parse expected version as semver") + }) + .collect::>>()?; + + let expected_version = parsed_versions + .iter() + .find(|semver| semver.major == actual_semver.major && semver.minor == actual_semver.minor) + .ok_or_else(|| { + anyhow!( + "could not find aziot-identity-service version {}.{}.x in list of supported products at {}", + actual_semver.major, + actual_semver.minor, + URI + ) + })?; + expected_version.to_string() }; - self.expected_version = Some(expected_version.clone()); - let actual_version = env!("CARGO_PKG_VERSION"); + self.expected_version = Some(expected_version.clone()); self.actual_version = Some(actual_version.to_owned()); if expected_version != actual_version { @@ -121,6 +152,22 @@ impl AziotVersion { #[derive(Debug, Deserialize)] struct LatestVersions { - #[serde(rename = "aziot-identity-service")] - aziot_identity_service: String, + channels: Vec, +} + +#[derive(Debug, Deserialize)] +struct Channel { + products: Vec, +} + +#[derive(Debug, Deserialize)] +struct Product { + id: String, + components: Vec, +} + +#[derive(Debug, Deserialize)] +struct Component { + name: String, + version: String, } diff --git a/aziotctl/src/internal/check/mod.rs b/aziotctl/src/internal/check/mod.rs index 12301671..a377eaff 100644 --- a/aziotctl/src/internal/check/mod.rs +++ b/aziotctl/src/internal/check/mod.rs @@ -32,7 +32,7 @@ pub struct CheckerCfg { pub proxy_uri: Option, /// If set, the check compares the installed package version to this string. - /// Otherwise, the version is fetched from + /// Otherwise, the version is fetched from #[arg(long, value_name = "VERSION")] pub expected_aziot_version: Option, } diff --git a/docs/installation.md b/docs/installation.md index b8b464da..cf849ec3 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -19,9 +19,19 @@ Using Ubuntu 22.04 amd64 as an example: ```bash # query GitHub for the latest versions of IoT Edge and IoT Identity Service -wget -qO- https://raw.githubusercontent.com/Azure/azure-iotedge/main/latest-aziot-edge.json | jq -r '."aziot-edge"' +wget -qO- https://raw.githubusercontent.com/Azure/azure-iotedge/main/product-versions.json | jq -r ' + .channels[] + | select(.name == "stable").products[] + | select(.id == "aziot-edge").components[] + | select(.name == "aziot-edge").version +' # example output: 1.4.20 -wget -qO- https://raw.githubusercontent.com/Azure/azure-iotedge/main/latest-aziot-identity-service.json | jq -r '."aziot-identity-service"' +wget -qO- https://raw.githubusercontent.com/Azure/azure-iotedge/main/product-versions.json | jq -r ' + .channels[] + | select(.name == "stable").products[] + | select(.id == "aziot-edge").components[] + | select(.name == "aziot-identity-service").version +' # example output: 1.4.6 # download and install