Skip to content

Commit

Permalink
feat: fvm update command (#3645)
Browse files Browse the repository at this point in the history
Introduces the `fvm update` command which checks current channel to be up to date.
Channels `stable` and `latest` are both "updateable", in the other hand the static
tag is not "updateable".

### How it works

When a user has either `stable` or `latest` as the active channel, FVM fetches the most
recent release for that channel.

- Stable: Will check if the upstream version is greater than the current using Semver.
- Latest: Will check if the upstream tag is different. We cant tell which is greater because we use SHAs to determine versions.
- Static Tag: When the user has an static tag as version using for instance `fvm install 0.10.14`, FVM will not attempt to update.

Then the same process as for `fvm install` is followed, this is why the logic is first
disengaged from `fvm install` into its own module on commit: [0a8b819](0a8b819).

In order to determine the current version for the channel FVM uses the already implemented `settings.toml` file.

## Demo

![CleanShot 2023-10-31 at 20 58 13](https://github.com/infinyon/fluvio/assets/34756077/e96d7ac1-043a-4f0b-aa71-2641be254699)

> By changing the version in the settings.toml, we can mimic an outdated version for the active channel.
  • Loading branch information
EstebanBorai committed Nov 1, 2023
1 parent e2fb300 commit 37a4a3b
Show file tree
Hide file tree
Showing 7 changed files with 352 additions and 110 deletions.
116 changes: 6 additions & 110 deletions crates/fluvio-version-manager/src/command/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,18 @@
//! Downloads and stores the sepecific Fluvio Version binaries in the local
//! FVM cache.
use std::fs::{File, create_dir, create_dir_all, rename};

use std::path::PathBuf;
use std::fs::create_dir_all;

use anyhow::Result;
use clap::Parser;
use tempfile::TempDir;
use url::Url;

use fluvio_hub_util::HUB_REMOTE;
use fluvio_hub_util::fvm::{Client, Download, PackageSet, DEFAULT_PKGSET, Channel};
use fluvio_hub_util::fvm::{Client, DEFAULT_PKGSET, Channel};

use crate::common::TARGET;
use crate::common::manifest::VersionManifest;
use crate::common::notify::Notify;
use crate::common::version_directory::VersionDirectory;
use crate::common::version_installer::VersionInstaller;
use crate::common::workdir::fvm_versions_path;

/// The `install` command is responsible of installing the desired Package Set
Expand All @@ -41,113 +37,13 @@ impl InstallOpt {
create_dir_all(&versions_path)?;
}

// The `tmp_dir` must be dropped after copying the binaries to the
// destination directory. By dropping `tmp_dir` the directory will be
// deleted from the filesystem.
let tmp_dir = TempDir::new()?;
let client = Client::new(self.registry.as_str())?;
let pkgset = client
.fetch_package_set(DEFAULT_PKGSET, &self.version, TARGET)
.await?;

for (idx, artf) in pkgset.artifacts.iter().enumerate() {
notify.info(format!(
"Downloading ({}/{}): {}@{}",
idx + 1,
pkgset.artifacts.len(),
artf.name,
artf.version
));

let artf_path = artf.download(tmp_dir.path().to_path_buf()).await?;
Self::set_executable_mode(artf_path)?;
}

let version_path = self.store_artifacts(&tmp_dir, &pkgset).await?;
let manifest = VersionManifest::new(self.version.to_owned(), pkgset.version.clone());

manifest.write(&version_path)?;
notify.done(format!("Installed fluvio version {}", self.version));

let version_dir = VersionDirectory::open(version_path)?;

version_dir.set_active()?;

notify.done(format!("Now using fluvio version {}", manifest.version,));

Ok(())
}

/// Allocates artifacts in the FVM `versions` directory for future use.
/// Returns the path to the allocated version directory.
async fn store_artifacts(&self, tmp_dir: &TempDir, pkgset: &PackageSet) -> Result<PathBuf> {
let version_path = fvm_versions_path()?.join(&self.version.to_string());

if !version_path.exists() {
create_dir(&version_path)?;
}

for artif in pkgset.artifacts.iter() {
rename(
tmp_dir.path().join(&artif.name),
version_path.join(&artif.name),
)?;
}

Ok(version_path)
}

/// Sets the executable mode for the specified file in Unix systems.
/// This is no-op in non-Unix systems.
#[cfg(unix)]
fn set_executable_mode(path: PathBuf) -> Result<()> {
use std::os::unix::fs::PermissionsExt;

const EXECUTABLE_MODE: u32 = 0o700;

// Add u+rwx mode to the existing file permissions, leaving others unchanged
let file = File::open(path)?;
let mut permissions = file.metadata()?.permissions();
let mut mode = permissions.mode();

mode |= EXECUTABLE_MODE;
permissions.set_mode(mode);
file.set_permissions(permissions)?;

Ok(())
}

/// Setting binary executable mode is a no-op in non-Unix systems.
#[cfg(not(unix))]
fn set_executable_mode(path: PathBuf) -> Result<()> {
Ok(())
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn sets_unix_execution_permissions() {
use std::os::unix::fs::PermissionsExt;

let tmpdir = TempDir::new().unwrap();
let path = tmpdir.path().join("test");
let file = File::create(&path).unwrap();
let meta = file.metadata().unwrap();
let perm = meta.permissions();
let is_executable = perm.mode() & 0o111 != 0;

assert!(!is_executable, "should not be executable");

InstallOpt::set_executable_mode(path.clone()).unwrap();

let file = File::open(&path).unwrap();
let meta = file.metadata().unwrap();
let perm = meta.permissions();
let is_executable = perm.mode() & 0o111 != 0;

assert!(is_executable, "should be executable");
VersionInstaller::new(self.version.to_owned(), pkgset, notify)
.install()
.await
}
}
1 change: 1 addition & 0 deletions crates/fluvio-version-manager/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ pub mod install;
pub mod itself;
pub mod show;
pub mod switch;
pub mod update;
pub mod version;
104 changes: 104 additions & 0 deletions crates/fluvio-version-manager/src/command/update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! Updates version of the current channel to the most recent one
use anyhow::{Result, Error};
use clap::Args;
use colored::Colorize;
use url::Url;

use fluvio_hub_util::HUB_REMOTE;
use fluvio_hub_util::fvm::{Client, DEFAULT_PKGSET, Channel, PackageSet};

use crate::common::TARGET;
use crate::common::notify::Notify;
use crate::common::settings::Settings;
use crate::common::version_installer::VersionInstaller;

#[derive(Debug, Args)]
pub struct UpdateOpt {
/// Registry used to fetch Fluvio Versions
#[arg(long, env = "HUB_REGISTRY_URL", default_value = HUB_REMOTE)]
registry: Url,
}

impl UpdateOpt {
pub async fn process(self, notify: Notify) -> Result<()> {
let settings = Settings::open()?;
let Some(channel) = settings.channel else {
notify.info("No channel set, please set a channel first using `fvm switch`");
return Ok(());
};

if channel.is_version_tag() {
// Abort early if the user is not using a Channel and instead has
// a static tag set as active
notify.info("Cannot update a static version tag. You must use a channel.");
return Ok(());
}

let latest_pkgset = self.fetch_latest_version(&channel).await?;
let Some(version) = settings.version else {
notify.info(
"No installed version detected, please install a version first using `fvm install`",
);
return Ok(());
};

match channel {
Channel::Stable => {
if latest_pkgset.version > version {
notify.info(format!(
"Updating fluvio {} to version {}. Current version is {}.",
channel.to_string().bold(),
latest_pkgset.version,
version
));

return VersionInstaller::new(channel, latest_pkgset, notify)
.install()
.await;
}

notify.done("You are already up to date");
}
Channel::Latest => {
// The latest tag can be very dynamic, so we just check for this
// tag to be different than the current version assuming
// upstream is always up to date
if latest_pkgset.version != version {
notify.info(format!(
"Updating fluvio {} to version {}. Current version is {}.",
channel.to_string().bold(),
latest_pkgset.version,
version
));

return VersionInstaller::new(channel, latest_pkgset, notify)
.install()
.await;
}

notify.done("You are already up to date");
}
Channel::Tag(_) => {
notify.warn("Static tags cannot be updated. No changes made.");
}
}

Ok(())
}

async fn fetch_latest_version(&self, channel: &Channel) -> Result<PackageSet> {
if channel.is_version_tag() {
return Err(Error::msg(
"Cannot update a static version tag. You must use a channel.",
));
}

let client = Client::new(self.registry.as_str())?;
let pkgset = client
.fetch_package_set(DEFAULT_PKGSET, channel, TARGET)
.await?;

Ok(pkgset)
}
}
1 change: 1 addition & 0 deletions crates/fluvio-version-manager/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod manifest;
pub mod notify;
pub mod settings;
pub mod version_directory;
pub mod version_installer;
pub mod workdir;

use std::path::PathBuf;
Expand Down
Loading

0 comments on commit 37a4a3b

Please sign in to comment.