Skip to content
Merged
Show file tree
Hide file tree
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
10 changes: 7 additions & 3 deletions crates/environments/src/environment/catalogue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,14 @@ impl Catalogue {
Ok(Some(env_def))
}

pub async fn list(&self) -> anyhow::Result<Vec<String>> {
pub async fn list(&self) -> Vec<String> {
let mut envs = vec![];

for ns_entry in self.envs_root.read_dir()? {
let Ok(read_dir) = self.envs_root.read_dir() else {
return Default::default();
};

for ns_entry in read_dir {
let Ok(ns_entry) = ns_entry else {
continue; // avoid blocking the list for one error
};
Expand All @@ -158,7 +162,7 @@ impl Catalogue {
}
}

Ok(envs)
envs
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub mod new;
pub mod plugins;
/// Commands for working with OCI registries.
pub mod registry;
/// Commands for the target environments catalogue.
pub mod target_environments;
/// Commands for working with templates.
pub mod templates;
/// Commands for starting the runtime.
Expand Down
164 changes: 164 additions & 0 deletions src/commands/target_environments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use clap::Subcommand;
use semver::Version;
use std::cmp::Ordering;

/// Commands for the target environments catalogue.
#[derive(Subcommand, Debug)]
pub enum TargetEnvironmentCommands {
/// List known target environments.
List,

/// Update the target environments from the remote repository.
Update,
}

impl TargetEnvironmentCommands {
pub async fn run(self) -> anyhow::Result<()> {
match self {
Self::List => list().await,
Self::Update => update().await,
}
}
}

async fn list() -> anyhow::Result<()> {
let catalogue = spin_environments::Catalogue::try_default()?;

let mut envs = catalogue.list().await;
envs.sort_by(|p, q| order_versioned(p, q));

if envs.is_empty() {
Comment thread
itowlson marked this conversation as resolved.
eprintln!(
"No target environments found. Run `spin targets update` to fetch target environments."
);
}

for env in &envs {
println!("{env}");
}

Ok(())
}

async fn update() -> anyhow::Result<()> {
Comment thread
itowlson marked this conversation as resolved.
let catalogue = spin_environments::Catalogue::try_default()?;

catalogue.update().await?;

eprintln!("Target environments updated");
Ok(())
}

fn order_versioned(first: &str, second: &str) -> Ordering {
let (name1, ver1) = name_and_version(first);
let (name2, ver2) = name_and_version(second);

match name1.cmp(name2) {
Ordering::Equal => match (ver1, ver2) {
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(Some(ver1), Some(ver2)) => order_env_ver(ver1, ver2),
},
other => other,
}
}

fn name_and_version(env_name: &str) -> (&str, Option<&str>) {
match env_name.rsplit_once('@') {
Some((n, v)) => (n, Some(v)),
None => (env_name, None),
}
}

fn order_env_ver(first: &str, second: &str) -> Ordering {
// Environment versions are expected to be major.minor, but this allows for
// some crazy future where a bold young experimentalist creates a major-only
// or patch-specific env.

// Leverage semver for parsing and ordering
fn padded_version(v: &str) -> Option<Version> {
let padded = match v.split('.').count() {
1 => format!("{v}.0.0"),
2 => format!("{v}.0"),
_ => v.to_string(),
};
Version::parse(&padded).ok()
}

match (padded_version(first), padded_version(second)) {
(Some(v1), Some(v2)) => v1.cmp(&v2),
_ => first.cmp(second),
}
}

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

#[test]
fn compare_understands_versions() {
assert_eq!(Ordering::Less, order_versioned("capybara", "cassowary"));
assert_eq!(Ordering::Equal, order_versioned("capybara", "capybara"));
assert_eq!(Ordering::Greater, order_versioned("capybara", "amphipod"));

assert_eq!(
Ordering::Less,
order_versioned("capybara@3.2", "cassowary@1.2")
);
assert_eq!(
Ordering::Equal,
order_versioned("capybara@3.2", "capybara@3.2")
);
assert_eq!(
Ordering::Greater,
order_versioned("capybara@3.2", "amphipod@1.2")
);

assert_eq!(Ordering::Less, order_versioned("capybara@3.2", "cassowary"));
assert_eq!(
Ordering::Greater,
order_versioned("capybara@3.2", "capybara")
);
assert_eq!(
Ordering::Greater,
order_versioned("capybara@3.2", "amphipod")
);

assert_eq!(
Ordering::Less,
order_versioned("capybara@3.2", "cassowary@1.2")
);
assert_eq!(Ordering::Less, order_versioned("capybara", "capybara@3.2"));
assert_eq!(
Ordering::Greater,
order_versioned("capybara", "amphipod@1.2")
);

assert_eq!(
Ordering::Less,
order_versioned("capybara@1.2", "capybara@3.2")
);
assert_eq!(
Ordering::Less,
order_versioned("capybara@1.3", "capybara@1.29")
);
assert_eq!(
Ordering::Less,
order_versioned("capybara@3.2", "capybara@222.111")
);

assert_eq!(
Ordering::Greater,
order_versioned("capybara@3.2", "capybara@1.2")
);
assert_eq!(
Ordering::Greater,
order_versioned("capybara@1.29", "capybara@1.3")
);
assert_eq!(
Ordering::Greater,
order_versioned("capybara@222.111", "capybara@3.2")
);
}
}
2 changes: 1 addition & 1 deletion src/completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub fn environments() -> Vec<CompletionCandidate> {
return vec![];
};

let envs = catalogue.list().await.unwrap_or_default();
let envs = catalogue.list().await;

envs.into_iter().map(CompletionCandidate::new).collect()
};
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ use spin_trigger_redis::RedisTrigger;

pub use opts::HELP_ARGS_ONLY_TRIGGER_TYPE;

use crate::commands::target_environments::TargetEnvironmentCommands;

pub async fn run() -> anyhow::Result<()> {
if is_completion_request() {
return (MaintenanceCommands::GenerateCompletions).run().await;
Expand Down Expand Up @@ -111,6 +113,8 @@ enum SpinApp {
Build(BuildCommand),
#[clap(subcommand, alias = "plugin")]
Plugins(PluginCommands),
#[clap(subcommand, alias = "environments")]
Targets(TargetEnvironmentCommands),
#[clap(subcommand, hide = true)]
Trigger(TriggerCommands),
#[clap(external_subcommand)]
Expand Down Expand Up @@ -147,6 +151,7 @@ impl SpinApp {
Self::Trigger(TriggerCommands::Redis(cmd)) => cmd.run().await,
Self::Trigger(TriggerCommands::HelpArgsOnly(cmd)) => cmd.run().await,
Self::Plugins(cmd) => cmd.run().await,
Self::Targets(cmd) => cmd.run().await,
Self::External(args) => execute_external_subcommand(args, SpinApp::command()).await,
Self::Watch(cmd) => cmd.run().await,
Self::Doctor(cmd) => cmd.run().await,
Expand Down
Loading