Skip to content

Commit

Permalink
Read latest version from product-versions.json (#609)
Browse files Browse the repository at this point in the history
The "aziot-version" check in `aziotctl check` reads the latest identity service version from https://github.com/Azure/azure-iotedge/blob/main/latest-aziot-identity-service.json. This JSON file has no ability to accomodate multiple releases (e.g., 1.4 and 1.5). However, https://github.com/Azure/azure-iotedge/blob/main/product-versions.json was recently introduced and, while it has a more complex structure, it is capable of providing information about multiple versions of the same product.

This change updates `aziotctl check` to read the latest identity service version from product-versions.json. Since the logic needs to contend with multiple versions, it now uses the MAJOR.MINOR version from the actual version string to match the expected version. For example, if the user has 1.5.0 installed and 1.5.2 and 1.4.10 are the latest versions, it will match on 1.5 and return 1.5.2 as the expected version. It does not differentiate between the same versions on different channels (e.g., stable vs. LTS); it assumes they are the same (in other words, it assumes product-versions.json will never legitimately show version 1.5.3 in the stable channel and 1.5.2 in the LTS channel).
  • Loading branch information
damonbarry authored Apr 23, 2024
1 parent db52f9b commit b9fff6b
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 18 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions aziotctl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
77 changes: 62 additions & 15 deletions aziotctl/src/internal/check/checks/aziot_version.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -33,6 +34,8 @@ impl AziotVersion {
shared: &CheckerShared,
cache: &mut CheckerCache,
) -> Result<CheckResult> {
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
{
Expand All @@ -52,22 +55,18 @@ 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();
req
};

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
Expand All @@ -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;
}

Expand All @@ -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<String> = 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::<Result<Vec<_>>>()?;

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 {
Expand All @@ -121,6 +152,22 @@ impl AziotVersion {

#[derive(Debug, Deserialize)]
struct LatestVersions {
#[serde(rename = "aziot-identity-service")]
aziot_identity_service: String,
channels: Vec<Channel>,
}

#[derive(Debug, Deserialize)]
struct Channel {
products: Vec<Product>,
}

#[derive(Debug, Deserialize)]
struct Product {
id: String,
components: Vec<Component>,
}

#[derive(Debug, Deserialize)]
struct Component {
name: String,
version: String,
}
2 changes: 1 addition & 1 deletion aziotctl/src/internal/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct CheckerCfg {
pub proxy_uri: Option<hyper::Uri>,

/// If set, the check compares the installed package version to this string.
/// Otherwise, the version is fetched from <http://aka.ms/latest-aziot-identity-service>
/// Otherwise, the version is fetched from <http://aka.ms/azure-iotedge-latest-versions>
#[arg(long, value_name = "VERSION")]
pub expected_aziot_version: Option<String>,
}
Expand Down
14 changes: 12 additions & 2 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit b9fff6b

Please sign in to comment.