Skip to content

Commit 0c7ea2f

Browse files
Merge pull request #51 from balena-io/refactor-host-info
Refactor `Device` type and accept `os-version` via the CLI
2 parents 360e461 + a283db8 commit 0c7ea2f

File tree

11 files changed

+377
-318
lines changed

11 files changed

+377
-318
lines changed

Cargo.lock

Lines changed: 182 additions & 191 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ axum = { version = "0.8.4", default-features = false, features = [
1717
"json",
1818
"query",
1919
] }
20-
tokio = { version = "1.46.1", default-features = false, features = [
20+
tokio = { version = "1.47.1", default-features = false, features = [
2121
"rt-multi-thread",
2222
"macros",
2323
"time",
@@ -27,7 +27,7 @@ tower-http = { version = "0.6.6", default-features = false, features = [
2727
"trace",
2828
] }
2929
tracing = "0.1.41"
30-
tracing-subscriber = { version = "0.3.19", features = [
30+
tracing-subscriber = { version = "0.3.20", features = [
3131
"env-filter",
3232
"fmt",
3333
"registry",
@@ -45,13 +45,13 @@ thiserror = "2"
4545
rand = "0.9.2"
4646
clap = { version = "4.5", features = ["derive", "env"] }
4747
dirs = "6.0.0"
48-
uuid = { version = "1.17", features = ["v4"] }
49-
mahler = { version = "0.19.5" }
50-
bollard = "0.19.1"
48+
uuid = { version = "1.18", features = ["v4"] }
49+
mahler = { version = "0.19.6" }
50+
bollard = "0.19.2"
5151
sha2 = { version = "0.10.9", default-features = false }
5252
fastrand = "2.3.0"
53-
futures-lite = { version = "2.6.0", default-features = false }
54-
regex = "1.10"
53+
futures-lite = { version = "2.6.1", default-features = false }
54+
regex = "1.11"
5555

5656
[dev-dependencies]
5757
mockito = { version = "1.7", default-features = false }

scripts/start.sh

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,15 @@ unset HELIOS_REMOTE_MIN_INTERVAL_MS
3131
# - io.balena.features.balena-api: '1'
3232

3333
# Read configuration from BALENA_* variables
34-
HELIOS_DEVICE_UUID="${BALENA_DEVICE_UUID}"
34+
# Ignore any credentials env vars and use BALENA_DEVICE_UUID by default
35+
HELIOS_UUID="${BALENA_DEVICE_UUID}"
36+
export HELIOS_UUID
37+
unset HELIOS_REMOTE_API_KEY
38+
39+
if [ -n "${BALENA_HOST_OS_VERSION}" ]; then
40+
HELIOS_OS_VERSION="${BALENA_HOST_OS_VERSION}"
41+
export HELIOS_OS_VERSION
42+
fi
3543

3644
# Run in unmanaged mode if the legacy Supervisor is unmanaged
3745
if [ -n "${BALENA_API_URL}" ] && [ -n "${BALENA_API_KEY}" ]; then
@@ -57,4 +65,4 @@ export XDG_STATE_HOME=/local
5765
rm /tmp/run/helios.sock 2>/dev/null || true
5866

5967
# Start the new supervisor
60-
exec helios --uuid "${HELIOS_DEVICE_UUID}" --local-api-address /tmp/run/helios.sock
68+
exec helios --local-api-address /tmp/run/helios.sock

src/api.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,6 @@ async fn set_app_tgt_state(
235235

236236
#[cfg(test)]
237237
mod tests {
238-
use crate::state::models::Host;
239-
240238
use super::*;
241239
use serde_json::json;
242240
use tokio::net::TcpListener;
@@ -253,14 +251,7 @@ mod tests {
253251
raw_target: None,
254252
});
255253
let (poll_request_tx, poll_rx) = watch::channel(PollRequest::default());
256-
let device = Device::new(
257-
Uuid::default(),
258-
Some("generic-aarch64".into()),
259-
Host {
260-
os: "balenaOS 6.3.1".into(),
261-
arch: "aarch64".into(),
262-
},
263-
);
254+
let device = Device::new(Uuid::default(), "balenaOS 6.3.1".parse().ok());
264255
let local_state = LocalState {
265256
device,
266257
status: UpdateStatus::default(),

src/cli.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::num::ParseIntError;
33
use std::time::Duration;
44

55
use crate::api::LocalAddress;
6-
use crate::types::{ApiKey, Uuid};
6+
use crate::types::{ApiKey, OperatingSystem, Uuid};
77
use crate::util::http::Uri;
88

99
fn parse_duration(s: &str) -> Result<Duration, ParseIntError> {
@@ -18,6 +18,10 @@ pub struct Cli {
1818
#[arg(env = "HELIOS_UUID", long = "uuid", value_name = "uuid")]
1919
pub uuid: Option<Uuid>,
2020

21+
/// Host OS name and version with metadata, eg. "balenaOS 6.5.39+rev1"
22+
#[arg(env = "HELIOS_OS_VERSION", long = "os-version", value_name = "str")]
23+
pub os: Option<OperatingSystem>,
24+
2125
/// Local API listen address
2226
#[arg(
2327
env = "HELIOS_LOCAL_API_ADDRESS",

src/main.rs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use tracing_subscriber::{
1212
util::SubscriberInitExt,
1313
EnvFilter,
1414
};
15-
use types::DeviceType;
1615

1716
mod api;
1817
mod cli;
@@ -29,7 +28,7 @@ use crate::legacy::{LegacyConfig, ProxyConfig, ProxyState};
2928
use crate::remote::{
3029
provision, ProvisioningConfig, ProvisioningError, RemoteConfig, RequestConfig,
3130
};
32-
use crate::types::Uuid;
31+
use crate::types::{OperatingSystem, Uuid};
3332

3433
fn initialize_tracing() {
3534
// Initialize tracing subscriber for human-readable logs
@@ -43,6 +42,7 @@ fn initialize_tracing() {
4342
.add_directive("mahler::planner=warn".parse().unwrap())
4443
.add_directive("mahler::worker=debug".parse().unwrap())
4544
.add_directive("hyper=error".parse().unwrap())
45+
.add_directive("reqwest=debug".parse().unwrap())
4646
.add_directive("bollard=error".parse().unwrap()),
4747
),
4848
)
@@ -82,17 +82,18 @@ async fn main() -> Result<(), Box<dyn Error>> {
8282
.expect("not nil because legacy_api_endpoint isn't nil"),
8383
});
8484

85-
let (uuid, remote_config, device_type) = maybe_provision(&cli).await?;
85+
let (uuid, remote_config) = maybe_provision(&cli).await?;
86+
let os = cli.os.clone();
8687

87-
start_supervisor(uuid, device_type, api_config, remote_config, legacy_config).await?;
88+
start_supervisor(uuid, os, api_config, remote_config, legacy_config).await?;
8889

8990
Ok(())
9091
}
9192

9293
#[instrument(name = "helios", skip_all, err)]
9394
async fn start_supervisor(
9495
uuid: Uuid,
95-
device_type: Option<DeviceType>,
96+
os: Option<OperatingSystem>,
9697
api_config: Option<ApiConfig>,
9798
remote_config: Option<RemoteConfig>,
9899
legacy_config: Option<LegacyConfig>,
@@ -107,7 +108,7 @@ async fn start_supervisor(
107108

108109
// Load the initial state
109110
let docker = Docker::connect_with_defaults()?;
110-
let initial_state = state::read(&docker, uuid.clone(), device_type).await?;
111+
let initial_state = state::read(&docker, uuid.clone(), os).await?;
111112

112113
let registry_auth = remote_config.clone().map(RegistryAuthClient::new);
113114

@@ -227,9 +228,7 @@ where
227228
/// If `remote_config` is not nil, then we are registered with a remote.
228229
/// If `remote_config` is nil, then we aren't registered and need to provision.
229230
/// If `remote_config` is still nil after provisioning, then we'll run in "unmanaged" mode.
230-
async fn maybe_provision(
231-
cli: &Cli,
232-
) -> Result<(Uuid, Option<RemoteConfig>, Option<DeviceType>), ProvisioningError> {
231+
async fn maybe_provision(cli: &Cli) -> Result<(Uuid, Option<RemoteConfig>), ProvisioningError> {
233232
// Load our provisioning config, if one exists
234233
let provisioning_config = config::get::<ProvisioningConfig>()?;
235234

@@ -266,7 +265,7 @@ async fn maybe_provision(
266265
);
267266
}
268267

269-
Ok((uuid.clone(), Some(remote), None))
268+
Ok((uuid.clone(), Some(remote)))
270269
}
271270
// Otherwise use an existing provisioning config, if available
272271
else if let Some(ref provisioning_config) = &provisioning_config {
@@ -291,7 +290,6 @@ async fn maybe_provision(
291290
}
292291

293292
let uuid = &provisioning_config.uuid;
294-
let device_type = provisioning_config.device_type.clone();
295293
let request_defaults = &provisioning_config.remote.request;
296294
let remote = RemoteConfig {
297295
request: RequestConfig {
@@ -311,7 +309,7 @@ async fn maybe_provision(
311309
..provisioning_config.remote.clone()
312310
};
313311

314-
Ok((uuid.clone(), Some(remote), Some(device_type)))
312+
Ok((uuid.clone(), Some(remote)))
315313
}
316314
// We have a provisioning key
317315
else if let Some(provisioning_key) = &cli.provisioning_key {
@@ -355,13 +353,13 @@ async fn maybe_provision(
355353
},
356354
};
357355

358-
let (uuid, remote, device_type) = provision(provisioning_key, &provisioning_config).await?;
356+
let (uuid, remote, _) = provision(provisioning_key, &provisioning_config).await?;
359357

360-
Ok((uuid, Some(remote), Some(device_type)))
358+
Ok((uuid, Some(remote)))
361359
}
362360
// We don't have a remote at all; run in "unmanaged" mode
363361
else {
364362
// Generate a UUID if none provided
365-
Ok((cli.uuid.clone().unwrap_or_default(), None, None))
363+
Ok((cli.uuid.clone().unwrap_or_default(), None))
366364
}
367365
}

src/remote/report.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -150,20 +150,13 @@ pub async fn start_report(config: RemoteConfig, mut state_rx: Receiver<LocalStat
150150
mod tests {
151151
use serde_json::json;
152152

153-
use crate::state::models::{Device, Host};
153+
use crate::state::models::Device;
154154

155155
use super::*;
156156

157157
#[test]
158158
fn it_creates_a_device_report_from_a_device() {
159-
let device = Device::new(
160-
"test-uuid".into(),
161-
Some("generic-aarch64".into()),
162-
Host {
163-
os: "balenaOS 5.3.1".into(),
164-
arch: "aarch64".into(),
165-
},
166-
);
159+
let device = Device::new("test-uuid".into(), "balenaOS 5.3.1".parse().ok());
167160

168161
let report = Report::from(LocalState {
169162
status: crate::state::UpdateStatus::Done,

src/state/models/device.rs

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,14 @@ use serde::{Deserialize, Serialize};
22
use std::collections::{BTreeMap, HashSet};
33

44
use crate::remote::RegistryAuth;
5-
use crate::types::{DeviceType, Uuid};
5+
use crate::types::{OperatingSystem, Uuid};
66
use crate::util::docker::ImageUri;
77

88
use super::app::{App, TargetAppMap};
99
use super::image::Image;
1010

1111
pub type DeviceConfig = BTreeMap<String, String>;
1212

13-
#[derive(Serialize, Deserialize, Debug, Clone)]
14-
pub struct Host {
15-
pub os: String,
16-
pub arch: String,
17-
}
18-
19-
impl Default for Host {
20-
fn default() -> Self {
21-
// See list in https://doc.rust-lang.org/std/env/consts/constant.ARCH.html
22-
let arch: String = match std::env::consts::ARCH {
23-
"x86" => "i386",
24-
"x86_64" => "amd64",
25-
"arm" => "armv7hf",
26-
"aarch64" => "aarch64",
27-
other => other,
28-
}
29-
.into();
30-
31-
// See list in https://doc.rust-lang.org/std/env/consts/constant.OS.html
32-
let os: String = std::env::consts::OS.into();
33-
34-
Self { os, arch }
35-
}
36-
}
37-
3813
pub type RegistryAuthSet = HashSet<RegistryAuth>;
3914

4015
/// The current state of a device that will be stored
@@ -47,13 +22,8 @@ pub struct Device {
4722
#[serde(skip_serializing_if = "Option::is_none")]
4823
pub name: Option<String>,
4924

50-
/// The device type accepted by the backend
5125
#[serde(skip_serializing_if = "Option::is_none")]
52-
pub device_type: Option<String>,
53-
54-
/// Host system info
55-
#[serde(default)]
56-
pub host: Host,
26+
pub os: Option<OperatingSystem>,
5727

5828
#[serde(default)]
5929
pub auths: RegistryAuthSet,
@@ -75,12 +45,11 @@ pub struct Device {
7545
}
7646

7747
impl Device {
78-
pub fn new(uuid: Uuid, device_type: Option<DeviceType>, host: Host) -> Self {
48+
pub fn new(uuid: Uuid, os: Option<OperatingSystem>) -> Self {
7949
Self {
8050
uuid,
8151
name: None,
82-
device_type,
83-
host,
52+
os,
8453
auths: RegistryAuthSet::new(),
8554
images: BTreeMap::new(),
8655
apps: BTreeMap::new(),
@@ -140,9 +109,9 @@ mod tests {
140109
fn device_state_should_be_serializable_into_target() {
141110
let json = json!({
142111
"uuid": "device-uuid",
143-
"host": {
144-
"os": "balenaOS 6.3.1",
145-
"arch": "aarch64"
112+
"os": {
113+
"name": "balenaOS",
114+
"version": "6.5.4",
146115
},
147116
"apps": {
148117
"aaa": {

src/state/read.rs

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
use bollard::secret::SystemInfo;
21
use bollard::{query_parameters::ListImagesOptions, Docker};
32
use thiserror::Error;
43
use tracing::instrument;
54

6-
use crate::types::Uuid;
5+
use crate::types::{OperatingSystem, Uuid};
76
use crate::util::docker::{ImageUri, InvalidImageUriError};
87

9-
use super::models::{Device, Host, Image};
8+
use super::models::{Device, Image};
109

1110
#[derive(Debug, Error)]
1211
pub enum ReadStateError {
@@ -17,50 +16,14 @@ pub enum ReadStateError {
1716
InvalidRegistryUri(#[from] InvalidImageUriError),
1817
}
1918

20-
// Convert an architecture from the string returned by he engine
21-
// to a balenaCloud accepted engine
22-
fn parse_engine_arch(arch: String) -> Option<String> {
23-
// In theory, the list of possible architectures is limited to
24-
// https://go.dev/doc/install/source#environment
25-
// however in practice, some systems use more specific architecture strings
26-
// such as armv6l and armv7l
27-
let arch = match arch.as_ref() {
28-
"amd64" => "amd64",
29-
"arm64" => "aarch644",
30-
"386" => "i386",
31-
"arm" => "armv7hf",
32-
"armv6l" => "rpi",
33-
"armv7l" => "armv7hf",
34-
_ => return None,
35-
};
36-
37-
Some(arch.into())
38-
}
39-
4019
/// Read the state of system
4120
#[instrument(name = "read_state", skip_all)]
4221
pub async fn read(
4322
docker: &Docker,
4423
uuid: Uuid,
45-
device_type: Option<String>,
24+
os: Option<OperatingSystem>,
4625
) -> Result<Device, ReadStateError> {
47-
let SystemInfo {
48-
operating_system,
49-
architecture,
50-
..
51-
} = docker.info().await?;
52-
53-
// XXX: I would like to get the engine name and version but the results
54-
// of the /version endpoint are not consistent accross engines
55-
let default_host = Host::default();
56-
let host = Host {
57-
os: operating_system.unwrap_or(default_host.os),
58-
arch: architecture
59-
.and_then(parse_engine_arch)
60-
.unwrap_or(default_host.arch),
61-
};
62-
63-
let mut device = Device::new(uuid, device_type, host);
26+
let mut device = Device::new(uuid, os);
6427

6528
let installed_images = docker
6629
.list_images(Some(ListImagesOptions {

0 commit comments

Comments
 (0)