diff --git a/crates/build/src/manifest.rs b/crates/build/src/manifest.rs index a4c295b0e..b28d6547e 100644 --- a/crates/build/src/manifest.rs +++ b/crates/build/src/manifest.rs @@ -7,7 +7,7 @@ use spin_manifest::{schema::v2, ManifestVersion}; pub enum ManifestBuildInfo { Loadable { components: Vec, - deployment_targets: Vec, + deployment_targets: Vec, manifest: spin_manifest::schema::v2::AppManifest, }, Unloadable { @@ -32,7 +32,7 @@ impl ManifestBuildInfo { } } - pub fn deployment_targets(&self) -> &[String] { + pub fn deployment_targets(&self) -> &[spin_manifest::schema::v2::TargetEnvironmentRef] { match self { Self::Loadable { deployment_targets, .. @@ -113,7 +113,7 @@ fn build_configs_from_manifest( fn deployment_targets_from_manifest( manifest: &spin_manifest::schema::v2::AppManifest, -) -> Vec { +) -> Vec { manifest.application.targets.clone() } diff --git a/crates/environments/src/environment_definition.rs b/crates/environments/src/environment_definition.rs index 60915ee22..613d7a684 100644 --- a/crates/environments/src/environment_definition.rs +++ b/crates/environments/src/environment_definition.rs @@ -1,17 +1,46 @@ +use std::path::Path; + use anyhow::Context; +use spin_common::ui::quoted_path; +use spin_manifest::schema::v2::TargetEnvironmentRef; + +const DEFAULT_REGISTRY: &str = "fermyon.com"; /// Loads the given `TargetEnvironment` from a registry. -pub async fn load_environment(env_id: impl AsRef) -> anyhow::Result { - use futures_util::TryStreamExt; +pub async fn load_environment(env_id: &TargetEnvironmentRef) -> anyhow::Result { + match env_id { + TargetEnvironmentRef::DefaultRegistry(package) => { + load_environment_from_registry(DEFAULT_REGISTRY, package).await + } + TargetEnvironmentRef::Registry { registry, package } => { + load_environment_from_registry(registry, package).await + } + TargetEnvironmentRef::WitDirectory { path } => load_environment_from_dir(path), + } +} - let env_id = env_id.as_ref(); +async fn load_environment_from_registry( + registry: &str, + env_id: &str, +) -> anyhow::Result { + use futures_util::TryStreamExt; let (pkg_name, pkg_ver) = env_id.split_once('@').with_context(|| format!("Failed to parse target environment {env_id} as package reference - is the target correct?"))?; + let env_pkg_ref: wasm_pkg_loader::PackageRef = pkg_name + .parse() + .with_context(|| format!("Environment {pkg_name} is not a valid package name"))?; + + let registry: wasm_pkg_loader::Registry = registry + .parse() + .with_context(|| format!("Registry {registry} is not a valid registry name"))?; // TODO: this requires wkg configuration which shouldn't be on users: // is there a better way to handle it? - let mut client = wasm_pkg_loader::Client::with_global_defaults() - .context("Failed to create a package loader from your global settings")?; + let mut wkg_config = wasm_pkg_loader::Config::global_defaults() + .unwrap_or_else(|_| wasm_pkg_loader::Config::empty()); + wkg_config.set_package_registry_override(env_pkg_ref, registry); + + let mut client = wasm_pkg_loader::Client::new(wkg_config); let package = pkg_name .to_owned() @@ -35,7 +64,14 @@ pub async fn load_environment(env_id: impl AsRef) -> anyhow::Result anyhow::Result { + let mut resolve = wit_parser::Resolve::default(); + let (pkg_id, _) = resolve.push_dir(path)?; + let decoded = wit_parser::decoding::DecodedWasm::WitPackage(resolve, pkg_id); + TargetEnvironment::from_decoded_wasm(path, decoded) } /// A parsed document representing a deployment environment, e.g. Spin 2.7, @@ -57,7 +93,7 @@ pub struct TargetEnvironment { } impl TargetEnvironment { - fn new(name: String, bytes: Vec) -> anyhow::Result { + fn from_package_bytes(name: String, bytes: Vec) -> anyhow::Result { let decoded = wit_component::decode(&bytes) .with_context(|| format!("Failed to decode package for environment {name}"))?; let package_id = decoded.package(); @@ -79,6 +115,38 @@ impl TargetEnvironment { }) } + fn from_decoded_wasm( + source: &Path, + decoded: wit_parser::decoding::DecodedWasm, + ) -> anyhow::Result { + let package_id = decoded.package(); + let package = decoded + .resolve() + .packages + .get(package_id) + .with_context(|| { + format!( + "The {} environment is invalid (no package for decoded package ID)", + quoted_path(source) + ) + })? + .clone(); + let name = package.name.to_string(); + + // This versionm of wit_component requires a flag for v2 encoding. + // v1 encoding is retired in wit_component main. You can remove the + // flag when this breaks next time we upgrade the crate! + let bytes = wit_component::encode(Some(true), decoded.resolve(), package_id)?; + + Ok(Self { + name, + decoded, + package, + package_id, + package_bytes: bytes, + }) + } + /// Returns true if the given trigger type provides the world identified by /// `world` in this environment. pub fn is_world_for(&self, trigger_type: &TriggerType, world: &wit_parser::World) -> bool { diff --git a/crates/environments/src/lib.rs b/crates/environments/src/lib.rs index 657bf94c0..0afb033cc 100644 --- a/crates/environments/src/lib.rs +++ b/crates/environments/src/lib.rs @@ -7,10 +7,11 @@ use environment_definition::{load_environment, TargetEnvironment, TriggerType}; use futures::future::try_join_all; pub use loader::ApplicationToValidate; use loader::ComponentToValidate; +use spin_manifest::schema::v2::TargetEnvironmentRef; pub async fn validate_application_against_environment_ids( application: &ApplicationToValidate, - env_ids: &[impl AsRef], + env_ids: &[TargetEnvironmentRef], ) -> anyhow::Result> { if env_ids.is_empty() { return Ok(Default::default()); diff --git a/crates/manifest/src/schema/v2.rs b/crates/manifest/src/schema/v2.rs index ffe510fd3..f7fbdb8b2 100644 --- a/crates/manifest/src/schema/v2.rs +++ b/crates/manifest/src/schema/v2.rs @@ -58,7 +58,7 @@ pub struct AppDetails { pub authors: Vec, /// `targets = ["spin-2.5", "fermyon-cloud", "spinkube-0.4"]` #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub targets: Vec, + pub targets: Vec, /// `[application.triggers.]` #[serde(rename = "trigger", default, skip_serializing_if = "Map::is_empty")] pub trigger_global_configs: Map, @@ -340,6 +340,27 @@ impl ComponentDependencies { } } +/// Identifies a deployment target. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged, deny_unknown_fields)] +pub enum TargetEnvironmentRef { + /// Environment package reference e.g. `spin:cli@3.0`. This is looked up + /// in the default environment registry. + DefaultRegistry(String), + /// A target package in a registry other than the default + Registry { + /// Registry hosting the environment package e.g. `fermyon.com``. + registry: String, + /// Environment package reference e.g. `my:spin-env@1.2`. + package: String, + }, + /// A filesystem directory. This is expected to contain a WIT package. + WitDirectory { + /// The directory containing the environment WIT. + path: PathBuf, + }, +} + mod kebab_or_snake_case { use serde::{Deserialize, Serialize}; pub use spin_serde::{KebabId, SnakeId};