Skip to content

Commit

Permalink
feat(fvm): support to update artifacts in current fluvio release (#4013)
Browse files Browse the repository at this point in the history
  • Loading branch information
EstebanBorai committed May 17, 2024
1 parent f773515 commit 247cccb
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 33 deletions.
3 changes: 1 addition & 2 deletions crates/fluvio-hub-util/src/fvm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,7 @@ impl PackageSet {
///
/// Whenever a version mismatch is found, such artifact is returned as part
/// of the output.
#[allow(dead_code)]
fn artifacts_diff(&self, upstream: &PackageSet) -> Vec<Artifact> {
pub fn artifacts_diff(&self, upstream: &PackageSet) -> Vec<Artifact> {
let ours: HashMap<String, Artifact> =
self.artifacts.iter().fold(HashMap::new(), |mut map, art| {
map.insert(art.name.to_owned(), art.to_owned());
Expand Down
21 changes: 21 additions & 0 deletions crates/fluvio-version-manager/src/command/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use url::Url;
use fluvio_hub_util::HUB_REMOTE;
use fluvio_hub_util::fvm::{Client, Channel, PackageSet};

use crate::common::version_directory::VersionDirectory;
use crate::common::workdir::fvm_versions_path;
use crate::common::TARGET;
use crate::common::notify::Notify;
use crate::common::settings::Settings;
Expand Down Expand Up @@ -60,6 +62,25 @@ impl UpdateOpt {
.await;
}

if ps_version == ch_version {
// Check for patches
let curr_version_path = fvm_versions_path()?.join(channel.to_string());
let curr_version_dir = VersionDirectory::open(curr_version_path)?;
let curr_version_pkgset = curr_version_dir.as_package_set()?;
let upstream_artifacts = curr_version_pkgset.artifacts_diff(&latest_pkgset);

if !upstream_artifacts.is_empty() {
notify.info(format!(
"Found {} packages in this version that needs update.",
upstream_artifacts.len(),
));

return VersionInstaller::new(channel, latest_pkgset, notify)
.update(&upstream_artifacts)
.await;
}
}

notify.done("You are already up to date");
}
Channel::Latest => {
Expand Down
2 changes: 1 addition & 1 deletion crates/fluvio-version-manager/src/common/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use fluvio_hub_util::fvm::Channel;
/// The name of the manifest file for the Package Set
pub const PACKAGE_SET_MANIFEST_FILENAME: &str = "manifest.json";

#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct VersionedArtifact {
pub name: String,
pub version: String,
Expand Down
98 changes: 96 additions & 2 deletions crates/fluvio-version-manager/src/common/version_directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ use std::fs::remove_file;

use std::path::PathBuf;

use anyhow::Result;
use anyhow::{bail, Result};

use fluvio_hub_util::fvm::Channel;
use fluvio_hub_util::fvm::{Artifact, Channel, PackageSet};
use semver::Version;

use crate::common::manifest::{PACKAGE_SET_MANIFEST_FILENAME, VersionManifest};
use crate::common::settings::Settings;
use crate::common::workdir::fluvio_binaries_path;
use crate::common::TARGET;

/// Represents the contents of a version directory (`~/.fvm/versions/<version>`)
/// where binaries and the manifest are stored.
Expand All @@ -19,6 +21,7 @@ use crate::common::workdir::fluvio_binaries_path;
///
/// - `contents`: Every file in the directory except for the manifest
/// - `manifest`: The manifest file
#[derive(Debug)]
pub struct VersionDirectory {
/// The Path to this [`VersionDirectory`]
pub path: PathBuf,
Expand Down Expand Up @@ -147,6 +150,43 @@ impl VersionDirectory {

Ok((manifests, active_version))
}

/// Builds a "dummy" [`PackageSet`] from the current `manifest.json`.
///
/// This is useful to perform operations that require a [`PackageSet`] instance,
/// for example, version diffing.
pub fn as_package_set(&self) -> Result<PackageSet> {
if let Some(ref versioned_artifacts) = self.manifest.contents {
let artifacts = versioned_artifacts
.iter()
.filter_map(|va| {
let Ok(version) = Version::parse(&va.version) else {
return None;
};

Some(Artifact {
version,
name: va.name.clone(),
download_url: String::from("N/A"),
sha256_url: String::from("N/A"),
})
})
.collect();
let pkgset = PackageSet {
pkgset: self.manifest.version.clone(),
arch: String::from(TARGET),
artifacts,
};

return Ok(pkgset);
}

tracing::debug!(version=%self.manifest.version, "The manifest containing artifacts details is not available");
bail!(
"No versioned artifacts manifest available for version: {}",
self.manifest.version
);
}
}

#[cfg(test)]
Expand All @@ -160,6 +200,7 @@ mod tests {

use fluvio_hub_util::sha256_digest;

use crate::common::manifest::VersionedArtifact;
use crate::common::settings::tests::{create_fvm_dir, delete_fvm_dir};

use super::*;
Expand Down Expand Up @@ -337,4 +378,57 @@ mod tests {

assert_eq!(active.unwrap().channel, Channel::Stable);
}

#[test]
fn creates_pakageset_instance_from_version_dir() {
let version_manifest = VersionManifest {
channel: Channel::Stable,
version: Version::parse("0.11.8").unwrap(),
contents: Some(vec![
VersionedArtifact {
name: String::from("fluvio"),
version: String::from("0.11.8"),
},
VersionedArtifact {
name: String::from("fluvio-cloud"),
version: String::from("0.2.22"),
},
VersionedArtifact {
name: String::from("cdk"),
version: String::from("0.11.8"),
},
]),
};
let version_directory = VersionDirectory {
manifest: version_manifest,
path: PathBuf::new(),
contents: vec![],
};
let package_set = PackageSet {
pkgset: Version::parse("0.11.8").unwrap(),
arch: TARGET.to_owned(),
artifacts: vec![
Artifact {
name: String::from("fluvio"),
version: Version::parse("0.11.8").unwrap(),
download_url: String::from("N/A"),
sha256_url: String::from("N/A"),
},
Artifact {
name: String::from("fluvio-cloud"),
version: Version::parse("0.2.22").unwrap(),
download_url: String::from("N/A"),
sha256_url: String::from("N/A"),
},
Artifact {
name: String::from("cdk"),
version: Version::parse("0.11.8").unwrap(),
download_url: String::from("N/A"),
sha256_url: String::from("N/A"),
},
],
};

assert_eq!(version_directory.as_package_set().unwrap(), package_set);
}
}
125 changes: 97 additions & 28 deletions crates/fluvio-version-manager/src/common/version_installer.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::path::PathBuf;
use std::fs::{create_dir, copy, rename};
use std::fs::{copy, create_dir, remove_file, rename};

use anyhow::{anyhow, Result};
use tempfile::TempDir;

use fluvio_hub_util::fvm::{PackageSet, Download, Channel};
use fluvio_hub_util::fvm::{Artifact, Channel, Download, PackageSet};

use super::manifest::{VersionedArtifact, VersionManifest};
use super::manifest::{VersionManifest, VersionedArtifact, PACKAGE_SET_MANIFEST_FILENAME};
use super::notify::Notify;
use super::version_directory::VersionDirectory;
use super::workdir::fvm_versions_path;
Expand All @@ -27,25 +27,10 @@ impl VersionInstaller {
}

pub async fn install(&self) -> Result<()> {
// 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()?;

for (idx, artf) in self.package_set.artifacts.iter().enumerate() {
self.notify.info(format!(
"Downloading ({}/{}): {}@{}",
idx + 1,
self.package_set.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, &self.package_set).await?;
let tmp_dir = self.download(&self.package_set.artifacts).await?;
let version_path = self
.store_artifacts(&tmp_dir, &self.package_set.artifacts)
.await?;
let contents = self
.package_set
.artifacts
Expand Down Expand Up @@ -74,22 +59,106 @@ impl VersionInstaller {
Ok(())
}

pub async fn update(&self, upstream_artifacts: &[Artifact]) -> Result<()> {
let tmp_dir = self.download(upstream_artifacts).await?;
let version_path = self.store_artifacts(&tmp_dir, upstream_artifacts).await?;
let mut manifest = VersionManifest::open(version_path.join(PACKAGE_SET_MANIFEST_FILENAME))?;
let mut old_versions: Vec<VersionedArtifact> = Vec::with_capacity(upstream_artifacts.len());

if let Some(ref contents) = manifest.contents {
let next: Vec<VersionedArtifact> =
contents
.iter()
.fold(Vec::with_capacity(contents.len()), |mut acc, vers_artf| {
if let Some(upstr_art) = upstream_artifacts
.iter()
.find(|art| art.name == vers_artf.name)
{
acc.push(VersionedArtifact::new(
upstr_art.name.to_owned(),
upstr_art.version.to_string(),
));
old_versions.push(vers_artf.to_owned());
} else {
acc.push(vers_artf.to_owned());
}

acc
});

manifest.contents = Some(next);
}

manifest.write(&version_path)?;

old_versions.iter().for_each(|old_var| {
if let Some(new_var) = upstream_artifacts
.iter()
.find(|art| art.name == old_var.name)
{
self.notify.info(format!(
"Updated {} from {} to {}",
old_var.name, old_var.version, new_var.version
));
}
});

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

Ok(())
}

/// Downloads the specified artifacts to the temporary directory and
/// returns a reference to the temporary directory [`TempDir`].
///
/// The `tmp_dir` must be dropped after copying the binaries to the
/// destination directory. By dropping [`TempDir`] the directory will be
/// deleted from the filesystem.
async fn download(&self, artifacts: &[Artifact]) -> Result<TempDir> {
let tmp_dir = TempDir::new()?;

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

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

Ok(tmp_dir)
}

/// 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,
package_set: &PackageSet,
) -> Result<PathBuf> {
///
/// If an artifact with the same name exists in the destination directory,
/// it will be removed before copying the new artifact.
async fn store_artifacts(&self, tmp_dir: &TempDir, artifacts: &[Artifact]) -> Result<PathBuf> {
let version_path = fvm_versions_path()?.join(self.channel.to_string());

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

for artif in package_set.artifacts.iter() {
for artif in artifacts.iter() {
let src = tmp_dir.path().join(&artif.name);
let dst = version_path.join(&artif.name);

// If the artifact exists in `dst` it should be deleted before copying
if dst.exists() {
tracing::debug!(
?dst,
"Removing existing artifact in place of upstream artifact"
);
remove_file(&dst)?;
}

if rename(src.clone(), dst.clone()).is_err() {
copy(src.clone(), dst.clone()).map_err(|e| {
anyhow!(
Expand Down
Loading

0 comments on commit 247cccb

Please sign in to comment.