feat: Doing the swap

This commit is contained in:
Justin Miller
2021-10-03 16:19:33 -06:00
committed by Aiden McClelland
parent 316e6e7425
commit 43cb20d390

View File

@@ -1,7 +1,9 @@
use anyhow::anyhow;
use anyhow::{anyhow, bail, Result};
use clap::ArgMatches;
use digest::Digest;
use futures::Stream;
use lazy_static::lazy_static;
use regex::Regex;
use rpc_toolkit::command;
use serde_json::Value;
use sha2::Sha256;
@@ -13,22 +15,98 @@ use crate::context::RpcContext;
use crate::update::latest_information::LatestInformation;
use crate::{Error, ErrorKind, ResultExt};
/// An user/ daemon would call this to update the system to the latest version and do the updates available,
/// and this will return something if there is an update, and in that case there will need to be a restart.
#[command(display(display_properties))]
pub async fn update_system(#[context] ctx: RpcContext) -> Result<UpdateSystem, Error> {
if let None = maybe_do_update(ctx).await? {
return Ok(UpdateSystem::Updated);
}
Ok(UpdateSystem::NoUpdates)
}
/// What is the status of the updates?
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum UpdateSystem {
NoUpdates,
Updated,
}
fn display_properties(status: UpdateSystem, _: &ArgMatches<'_>) {
match status {
UpdateSystem::NoUpdates => {
println!("Updates are ready, please reboot");
}
UpdateSystem::Updated => {
println!("No updates needed");
}
}
}
const URL: &str = "https://beta-registry-0-3.start9labs.com/eos/latest";
const HEADER_KEY: &str = "CHECKSUM";
mod latest_information;
pub fn display_properties(_: (), _: &ArgMatches<'_>) {
println!("Test");
}
#[command(display(display_properties))]
pub async fn update_system(#[context] ctx: RpcContext) -> Result<(), Error> {
if let None = maybe_do_update(ctx).await? {
return Ok(());
}
todo!()
enum WritableDrives {
Green,
Blue,
}
pub async fn maybe_do_update(mut ctx: RpcContext) -> Result<Option<()>, Error> {
struct Boot;
/// We are going to be creating some folders and mounting so
/// we need to know the labels for those types. These labels
/// are the labels that are shipping with the embassy, blue/ green
/// are where the os sits and will do a swap during update.
trait FileType {
fn mount_folder(&self) -> String {
format!("/media/{}", self.label())
}
fn label(&self) -> String;
}
impl FileType for WritableDrives {
fn label(&self) -> String {
match self {
WritableDrives::Green => "green",
WritableDrives::Blue => "blue",
}
.to_string()
}
}
impl FileType for Boot {
fn label(&self) -> String {
"system-boot".to_string()
}
}
/// Proven data that this is mounted, should be consumed in an unmount
struct MountedResource<X: FileType>(X);
impl<X: FileType> MountedResource<X> {
async fn unmount_label(&self) -> Result<()> {
let folder = self.0.mount_folder();
tokio::process::Command::new("umount")
.arg(&folder)
.output()
.await?;
tokio::process::Command::new("rmdir")
.arg(folder)
.output()
.await?;
Ok(())
}
}
/// This will be where we are going to be putting the new update
struct NewLabel<'a>(&'a MountedResource<WritableDrives>);
/// This is our current label where the os is running
struct CurrentLabel<'a>(&'a MountedResource<WritableDrives>);
lazy_static! {
static ref PARSE_COLOR: Regex = Regex::new("#LABEL=(\\w+) /media/root-ro/").unwrap();
}
async fn maybe_do_update(mut ctx: RpcContext) -> Result<Option<()>, Error> {
let mut db = ctx.db.handle();
let latest_version = reqwest::get(URL)
.await
@@ -42,17 +120,72 @@ pub async fn maybe_do_update(mut ctx: RpcContext) -> Result<Option<()>, Error> {
.version()
.get_mut(&mut db)
.await?;
if &latest_version > &current_version {
let file_name = "/tmp/test";
download_file(file_name).await?;
swap(&mut ctx).await?;
Ok(Some(()))
} else {
Ok(None)
if &latest_version <= &current_version {
return Ok(None);
}
let file_name = "/tmp/test";
let mounted_blue = mount_label(WritableDrives::Blue)
.await
.with_kind(ErrorKind::Filesystem)?;
let mounted_green = mount_label(WritableDrives::Green)
.await
.with_kind(ErrorKind::Filesystem)?;
let mounted_boot = mount_label(Boot).await.with_kind(ErrorKind::Filesystem)?;
let potential_error_actions = async {
let (new_label, _current_label) = query_mounted_label(&mounted_blue, &mounted_green)
.await
.with_kind(ErrorKind::Filesystem)?;
download_file(file_name, &new_label).await?;
swap_boot_label(&mut ctx, &new_label, &mounted_boot).await?;
Ok::<_, Error>(())
}
.await;
mounted_blue
.unmount_label()
.await
.with_kind(ErrorKind::Filesystem)?;
mounted_green
.unmount_label()
.await
.with_kind(ErrorKind::Filesystem)?;
mounted_boot
.unmount_label()
.await
.with_kind(ErrorKind::Filesystem)?;
potential_error_actions?;
Ok(Some(()))
}
async fn query_mounted_label<'a>(
mounted_resource_left: &'a MountedResource<WritableDrives>,
mounted_resource_right: &'a MountedResource<WritableDrives>,
) -> Result<(NewLabel<'a>, CurrentLabel<'a>)> {
let output = String::from_utf8(
tokio::process::Command::new("cat")
.arg("/etc/fstab")
.output()
.await?
.stdout,
)?;
match &PARSE_COLOR
.captures(&output)
.ok_or_else(|| anyhow!("Can't find pattern in {}", output))?[1]
{
x if x == &mounted_resource_left.0.label() => Ok((
NewLabel(mounted_resource_left),
CurrentLabel(mounted_resource_right),
)),
x if x == &mounted_resource_right.0.label() => Ok((
NewLabel(mounted_resource_right),
CurrentLabel(mounted_resource_left),
)),
e => bail!("Could not find a mounted resource for {}", e),
}
}
pub async fn download_file(file_name: &str) -> Result<(), Error> {
async fn download_file(file_name: &str, new_label: &NewLabel<'_>) -> Result<(), Error> {
let download_request = reqwest::get(URL).await.with_kind(ErrorKind::Network)?;
let hash_from_header: String = download_request
.headers()
@@ -83,7 +216,7 @@ async fn write_stream_to_file(
Ok(hasher.finalize().to_vec())
}
pub async fn check_download(hash_from_header: &str, file_digest: Vec<u8>) -> Result<(), Error> {
async fn check_download(hash_from_header: &str, file_digest: Vec<u8>) -> Result<(), Error> {
if hex::decode(hash_from_header).with_kind(ErrorKind::Network)? != file_digest {
return Err(Error::new(
anyhow!("Hash sum does not match source"),
@@ -92,9 +225,54 @@ pub async fn check_download(hash_from_header: &str, file_digest: Vec<u8>) -> Res
}
Ok(())
}
pub async fn swap(ctx: &mut RpcContext) -> Result<Value, Error> {
async fn swap_boot_label(
ctx: &mut RpcContext,
new_label: &NewLabel<'_>,
mounted_boot: &MountedResource<Boot>,
) -> Result<(), Error> {
// disk/util add setLabel
todo!("Do swap");
todo!("Let system know that we need a reboot or something")
tokio::process::Command::new("sed")
.arg(format!(r#""r/(blue|green)/{}/g""#, new_label.0 .0.label()))
.arg(format!("{}/etc/fstab", mounted_boot.0.mount_folder()))
.output()
.await?;
Ok(())
}
async fn mount_label<F>(file_type: F) -> Result<MountedResource<F>>
where
F: FileType,
{
let label = file_type.label();
let folder = file_type.mount_folder();
tokio::process::Command::new("mdkir")
.arg(&folder)
.output()
.await?;
tokio::process::Command::new("mount")
.arg("-L")
.arg(label)
.arg(folder)
.output()
.await?;
Ok(MountedResource(file_type))
}
/// Captured from doing an fstab with an embassy box and the cat from the /etc/fstab
#[test]
fn test_capture() {
let output = r#"
#
# This fstab is for overlayroot. The real one can be found at
# /media/root-ro/etc/fstab
# The original entry for '/' and other mounts have been updated to be placed
# under /media/root-ro.
# To permanently modify this (or any other file), you should change-root into
# a writable view of the underlying filesystem using:
# sudo overlayroot-chroot
#
#LABEL=blue /media/root-ro/ ext4 ro,discard,errors=remount-ro,noauto 0 1
/media/root-ro/ / overlay lowerdir=/media/root-ro/,upperdir=/media/root-rw/overlay/,workdir=/media/root-rw/overlay-workdir/_ 0 1
LABEL=system-boot /boot/firmware vfat defaults 0 1 # overlayroot:fs-unsupported
"#;
assert_eq!(&PARSE_COLOR.captures(&output).unwrap()[1], "blue");
}