Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 77 additions & 2 deletions crates/environments/src/environment/catalogue.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::time::{Duration, SystemTime};

const SPIN_ENV_REPO: &str = "https://github.com/spinframework/spin-environments";
const ENVS_DIR_IN_REPO: &str = "envs";

Expand All @@ -7,6 +9,8 @@ pub struct Catalogue {
}

static CATALOGUE_UPDATE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
const JUST_IN_TIME_UPDATE_TIMEOUT: Duration = Duration::from_secs(2);
const RECENCY_WINDOW: Duration = Duration::from_hours(1);

impl Catalogue {
pub fn try_default() -> anyhow::Result<Self> {
Expand All @@ -17,29 +21,96 @@ impl Catalogue {
Ok(Self::new(root))
}

async fn is_recent(&self) -> bool {
let Ok(last_update_file) = self.last_update_file() else {
return false;
};

match tokio::fs::read_to_string(&last_update_file).await {
Err(_) => false,
Ok(text) => {
let Ok(time_since_epoch) = text.parse() else {
return false;
};
let now = SystemTime::now();
let Some(last) =
SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(time_since_epoch))
else {
return false;
};
let Ok(diff) = now.duration_since(last) else {
return false;
};
diff < RECENCY_WINDOW
}
}
}

fn last_update_file(&self) -> Result<PathBuf, ()> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would -> Option<PathBuf> be more idiomatic here?

let Some(parent_dir) = self.git_root.parent() else {
return Err(());
};
let last_update_file = parent_dir.join("environments-last-update.txt");
Ok(last_update_file)
}

async fn save_last_update_time(&self) {
let Ok(last_update_file) = self.last_update_file() else {
return;
};
let Ok(last_update_dur) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) else {
return;
};
let last_update_text = last_update_dur.as_secs().to_string();
_ = tokio::fs::write(&last_update_file, last_update_text).await;
}

fn new(git_root: PathBuf) -> Self {
Self {
git_root: git_root.clone(),
envs_root: git_root.join(ENVS_DIR_IN_REPO),
}
}

/// Updates if we have not updated recently, ignoring
/// failures. The scenario here is unverioned environments,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
/// failures. The scenario here is unverioned environments,
/// failures. The scenario here is unversioned environments,

/// where we don't want them to get stale, but don't want
/// to slow the user down with frequent checks or long stalls
/// while airplane wifi tries to reach the repo, only to
/// find out there's nothing to go...
async fn try_update(&self) {
if self.is_recent().await {
return;
}

_ = tokio::time::timeout(JUST_IN_TIME_UPDATE_TIMEOUT, self.update()).await;
}

pub async fn update(&self) -> anyhow::Result<()> {
// We don't want two git pulls running concurrently
let _guard = CATALOGUE_UPDATE_LOCK.lock();

let url = Url::parse(SPIN_ENV_REPO)?;
let git_source = GitSource::new(&url, None, &self.git_root);
if self.git_root.exists() {
git_source.pull().await
git_source.pull().await.unwrap();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we unwrapping here? This could panic on a network error

Suggested change
git_source.pull().await.unwrap();
git_source.pull().await?;

} else {
tokio::fs::create_dir_all(&self.git_root).await?;
git_source.clone_repo().await
git_source.clone_repo().await?;
}
self.save_last_update_time().await;
Ok(())
}

/// This requires `env_id` to be normalised to the `ns@version` form
pub async fn get(&self, env_id: &str) -> anyhow::Result<Option<EnvironmentDefinition>> {
// We don't want to keep returning old versions of a mutable env
// until we get an unrelated reason to update.
if is_unversioned(env_id) {
// update if we can, in case the unversioned env has changed
self.try_update().await;
}

// We add (redundant) directories to avoid having a single flat
// namespace that becomes unmanageable.
//
Expand Down Expand Up @@ -100,6 +171,10 @@ fn sans_version(id: &str) -> &str {
}
}

fn is_unversioned(id: &str) -> bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should update the readme of spin-environments to specify that versioning is not needed.

id.rsplit_once('@').is_none()
}

// From here on this is a copy of plugins/git.rs, which itself was
// recycled from templates...

Expand Down
Loading