Skip to content

Commit

Permalink
Encapsulate the application and loader
Browse files Browse the repository at this point in the history
Signed-off-by: itowlson <ivan.towlson@fermyon.com>
  • Loading branch information
itowlson committed Sep 24, 2024
1 parent 0fab1a2 commit 531e4fb
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 85 deletions.
10 changes: 6 additions & 4 deletions crates/build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,14 @@ pub async fn build(
};

if !skip_target_checks {
let resolution_context =
spin_environments::ResolutionContext::new(manifest_file.parent().unwrap()).await?;
let application = spin_environments::ApplicationToValidate::new(
manifest.clone(),
manifest_file.parent().unwrap(),
)
.await?;
let errors = spin_environments::validate_application_against_environment_ids(
&application,
build_info.deployment_targets(),
manifest,
&resolution_context,
)
.await?;

Expand Down
24 changes: 7 additions & 17 deletions crates/environments/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,26 @@ mod loader;

use environment_definition::{load_environment, TargetEnvironment, TriggerType};
use futures::future::try_join_all;
pub use loader::ResolutionContext;
use loader::{load_and_resolve_all, ComponentToValidate};
pub use loader::ApplicationToValidate;
use loader::ComponentToValidate;

pub async fn validate_application_against_environment_ids(
application: &ApplicationToValidate,
env_ids: &[impl AsRef<str>],
app: &spin_manifest::schema::v2::AppManifest,
resolution_context: &ResolutionContext,
) -> anyhow::Result<Vec<anyhow::Error>> {
if env_ids.is_empty() {
return Ok(Default::default());
}

let envs = try_join_all(env_ids.iter().map(load_environment)).await?;
validate_application_against_environments(&envs, app, resolution_context).await
validate_application_against_environments(application, &envs).await
}

async fn validate_application_against_environments(
application: &ApplicationToValidate,
envs: &[TargetEnvironment],
app: &spin_manifest::schema::v2::AppManifest,
resolution_context: &ResolutionContext,
) -> anyhow::Result<Vec<anyhow::Error>> {
use futures::FutureExt;

for trigger_type in app.triggers.keys() {
for trigger_type in application.trigger_types() {
if let Some(env) = envs.iter().find(|e| !e.supports_trigger_type(trigger_type)) {
anyhow::bail!(
"Environment {} does not support trigger type {trigger_type}",
Expand All @@ -37,13 +33,7 @@ async fn validate_application_against_environments(
}
}

let components_by_trigger_type_futs = app.triggers.iter().map(|(ty, ts)| {
load_and_resolve_all(app, ts, resolution_context)
.map(|css| css.map(|css| (ty.to_owned(), css)))
});
let components_by_trigger_type = try_join_all(components_by_trigger_type_futs)
.await
.context("Failed to prepare components for target environment checking")?;
let components_by_trigger_type = application.components_by_trigger_type().await?;

let mut errs = vec![];

Expand Down
159 changes: 95 additions & 64 deletions crates/environments/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ pub(crate) struct ComponentToValidate<'a> {
wasm: Vec<u8>,
}

struct ComponentSource<'a> {
id: &'a str,
source: &'a spin_manifest::schema::v2::ComponentSource,
dependencies: WrappedComponentDependencies,
}

impl<'a> ComponentToValidate<'a> {
pub fn id(&self) -> &str {
self.id
Expand All @@ -30,75 +24,112 @@ impl<'a> ComponentToValidate<'a> {
}
}

pub async fn load_and_resolve_all<'a>(
app: &'a spin_manifest::schema::v2::AppManifest,
triggers: &'a [spin_manifest::schema::v2::Trigger],
resolution_context: &'a ResolutionContext,
) -> anyhow::Result<Vec<ComponentToValidate<'a>>> {
let component_futures = triggers
.iter()
.map(|t| load_and_resolve_one(app, t, resolution_context));
try_join_all(component_futures).await
pub struct ApplicationToValidate {
manifest: spin_manifest::schema::v2::AppManifest,
wasm_loader: spin_loader::WasmLoader,
}

async fn load_and_resolve_one<'a>(
app: &'a spin_manifest::schema::v2::AppManifest,
trigger: &'a spin_manifest::schema::v2::Trigger,
resolution_context: &'a ResolutionContext,
) -> anyhow::Result<ComponentToValidate<'a>> {
let component_spec = trigger
.component
.as_ref()
.ok_or_else(|| anyhow!("No component specified for trigger {}", trigger.id))?;
let (id, source, dependencies) = match component_spec {
spin_manifest::schema::v2::ComponentSpec::Inline(c) => {
(trigger.id.as_str(), &c.source, &c.dependencies)
}
spin_manifest::schema::v2::ComponentSpec::Reference(r) => {
let id = r.as_ref();
let Some(component) = app.components.get(r) else {
anyhow::bail!(
"Component {id} specified for trigger {} does not exist",
trigger.id
);
};
(id, &component.source, &component.dependencies)
}
};

let component = ComponentSource {
id,
source,
dependencies: WrappedComponentDependencies::new(dependencies),
};
impl ApplicationToValidate {
pub async fn new(
manifest: spin_manifest::schema::v2::AppManifest,
base_dir: impl AsRef<Path>,
) -> anyhow::Result<Self> {
let wasm_loader =
spin_loader::WasmLoader::new(base_dir.as_ref().to_owned(), None, None).await?;
Ok(Self {
manifest,
wasm_loader,
})
}

let loader = ComponentSourceLoader::new(resolution_context.wasm_loader());
fn component_source<'a>(
&'a self,
trigger: &'a spin_manifest::schema::v2::Trigger,
) -> anyhow::Result<ComponentSource<'a>> {
let component_spec = trigger
.component
.as_ref()
.ok_or_else(|| anyhow!("No component specified for trigger {}", trigger.id))?;
let (id, source, dependencies) = match component_spec {
spin_manifest::schema::v2::ComponentSpec::Inline(c) => {
(trigger.id.as_str(), &c.source, &c.dependencies)
}
spin_manifest::schema::v2::ComponentSpec::Reference(r) => {
let id = r.as_ref();
let Some(component) = self.manifest.components.get(r) else {
anyhow::bail!(
"Component {id} specified for trigger {} does not exist",
trigger.id
);
};
(id, &component.source, &component.dependencies)
}
};

Ok(ComponentSource {
id,
source,
dependencies: WrappedComponentDependencies::new(dependencies),
})
}

let wasm = spin_compose::compose(&loader, &component).await.with_context(|| format!("Spin needed to compose dependencies for {id} as part of target checking, but composition failed"))?;
pub fn trigger_types(&self) -> impl Iterator<Item = &String> {
self.manifest.triggers.keys()
}

Ok(ComponentToValidate {
id,
source_description: source_description(component.source),
wasm,
})
}
pub fn triggers(
&self,
) -> impl Iterator<Item = (&String, &Vec<spin_manifest::schema::v2::Trigger>)> {
self.manifest.triggers.iter()
}

pub struct ResolutionContext {
wasm_loader: spin_loader::WasmLoader,
}
pub(crate) async fn components_by_trigger_type(
&self,
) -> anyhow::Result<Vec<(String, Vec<ComponentToValidate<'_>>)>> {
use futures::FutureExt;

let components_by_trigger_type_futs = self.triggers().map(|(ty, ts)| {
self.components_for_trigger(ts)
.map(|css| css.map(|css| (ty.to_owned(), css)))
});
let components_by_trigger_type = try_join_all(components_by_trigger_type_futs)
.await
.context("Failed to prepare components for target environment checking")?;
Ok(components_by_trigger_type)
}

impl ResolutionContext {
pub async fn new(base_dir: impl AsRef<Path>) -> anyhow::Result<Self> {
let wasm_loader =
spin_loader::WasmLoader::new(base_dir.as_ref().to_owned(), None, None).await?;
Ok(Self { wasm_loader })
async fn components_for_trigger<'a>(
&'a self,
triggers: &'a [spin_manifest::schema::v2::Trigger],
) -> anyhow::Result<Vec<ComponentToValidate<'a>>> {
let component_futures = triggers.iter().map(|t| self.load_and_resolve_trigger(t));
try_join_all(component_futures).await
}

fn wasm_loader(&self) -> &spin_loader::WasmLoader {
&self.wasm_loader
async fn load_and_resolve_trigger<'a>(
&'a self,
trigger: &'a spin_manifest::schema::v2::Trigger,
) -> anyhow::Result<ComponentToValidate<'a>> {
let component = self.component_source(trigger)?;

let loader = ComponentSourceLoader::new(&self.wasm_loader);

let wasm = spin_compose::compose(&loader, &component).await.with_context(|| format!("Spin needed to compose dependencies for {} as part of target checking, but composition failed", component.id))?;

Ok(ComponentToValidate {
id: component.id,
source_description: source_description(component.source),
wasm,
})
}
}

struct ComponentSource<'a> {
id: &'a str,
source: &'a spin_manifest::schema::v2::ComponentSource,
dependencies: WrappedComponentDependencies,
}

struct ComponentSourceLoader<'a> {
wasm_loader: &'a spin_loader::WasmLoader,
}
Expand Down Expand Up @@ -135,7 +166,7 @@ impl<'a> spin_compose::ComponentSourceLoader for ComponentSourceLoader<'a> {
}

// This exists only to thwart the orphan rule
pub(crate) struct WrappedComponentDependency {
struct WrappedComponentDependency {
name: spin_serde::DependencyName,
dependency: spin_manifest::schema::v2::ComponentDependency,
}
Expand Down

0 comments on commit 531e4fb

Please sign in to comment.