Skip to content

Commit 86a8955

Browse files
committed
Validate against target environments during build
Signed-off-by: itowlson <ivan.towlson@fermyon.com>
1 parent 49694dd commit 86a8955

File tree

10 files changed

+539
-21
lines changed

10 files changed

+539
-21
lines changed

Cargo.lock

Lines changed: 105 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ spin-app = { path = "crates/app" }
4747
spin-build = { path = "crates/build" }
4848
spin-common = { path = "crates/common" }
4949
spin-doctor = { path = "crates/doctor" }
50+
spin-environments = { path = "crates/environments" }
5051
spin-expressions = { path = "crates/expressions" }
5152
spin-http = { path = "crates/http" }
5253
spin-loader = { path = "crates/loader" }

crates/build/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ anyhow = "1.0.57"
99
futures = "0.3.21"
1010
serde = { version = "1.0", features = ["derive"] }
1111
spin-common = { path = "../common" }
12+
spin-environments = { path = "../environments" } # TODO: it would be good to break this dependency because not everything should have to drag this dep around
1213
spin-manifest = { path = "../manifest" }
1314
subprocess = "0.2.8"
1415
terminal = { path = "../terminal" }

crates/build/src/deployment.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#[derive(Default)]
2+
pub struct DeploymentTargets {
3+
target_environments: Vec<DeploymentTarget>,
4+
}
5+
pub type DeploymentTarget = String;
6+
7+
impl DeploymentTargets {
8+
pub fn new(envs: Vec<String>) -> Self {
9+
Self {
10+
target_environments: envs,
11+
}
12+
}
13+
14+
pub fn iter(&self) -> impl Iterator<Item = &str> {
15+
self.target_environments.iter().map(|s| s.as_str())
16+
}
17+
}

crates/build/src/lib.rs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
//! A library for building Spin components.
44
5+
mod deployment;
56
mod manifest;
67

78
use anyhow::{anyhow, bail, Context, Result};
@@ -17,24 +18,37 @@ use crate::manifest::component_build_configs;
1718

1819
/// If present, run the build command of each component.
1920
pub async fn build(manifest_file: &Path, component_ids: &[String]) -> Result<()> {
20-
let (components, manifest_err) =
21-
component_build_configs(manifest_file)
22-
.await
23-
.with_context(|| {
24-
format!(
25-
"Cannot read manifest file from {}",
26-
quoted_path(manifest_file)
27-
)
28-
})?;
21+
let (components, deployment_targets, manifest) = component_build_configs(manifest_file)
22+
.await
23+
.with_context(|| {
24+
format!(
25+
"Cannot read manifest file from {}",
26+
quoted_path(manifest_file)
27+
)
28+
})?;
2929
let app_dir = parent_dir(manifest_file)?;
3030

3131
let build_result = build_components(component_ids, components, app_dir);
3232

33-
if let Some(e) = manifest_err {
33+
if let Err(e) = &manifest {
3434
terminal::warn!("The manifest has errors not related to the Wasm component build. Error details:\n{e:#}");
3535
}
3636

37-
build_result
37+
build_result?;
38+
39+
if let Ok(manifest) = &manifest {
40+
let resolution_context = spin_environments::ResolutionContext {
41+
base_dir: manifest_file.parent().unwrap().to_owned(),
42+
};
43+
spin_environments::validate_application_against_environment_ids(
44+
deployment_targets.iter(),
45+
manifest,
46+
&resolution_context,
47+
)
48+
.await?;
49+
}
50+
51+
Ok(())
3852
}
3953

4054
fn build_components(

crates/build/src/manifest.rs

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,60 @@ use std::{collections::BTreeMap, path::Path};
44

55
use spin_manifest::{schema::v2, ManifestVersion};
66

7+
use crate::deployment::DeploymentTargets;
8+
79
/// Returns a map of component IDs to [`v2::ComponentBuildConfig`]s for the
810
/// given (v1 or v2) manifest path. If the manifest cannot be loaded, the
911
/// function attempts fallback: if fallback succeeds, result is Ok but the load error
1012
/// is also returned via the second part of the return value tuple.
1113
pub async fn component_build_configs(
1214
manifest_file: impl AsRef<Path>,
13-
) -> Result<(Vec<ComponentBuildInfo>, Option<spin_manifest::Error>)> {
15+
) -> Result<(
16+
Vec<ComponentBuildInfo>,
17+
DeploymentTargets,
18+
Result<spin_manifest::schema::v2::AppManifest, spin_manifest::Error>,
19+
)> {
1420
let manifest = spin_manifest::manifest_from_file(&manifest_file);
1521
match manifest {
16-
Ok(manifest) => Ok((build_configs_from_manifest(manifest), None)),
17-
Err(e) => fallback_load_build_configs(&manifest_file)
18-
.await
19-
.map(|bc| (bc, Some(e))),
22+
Ok(mut manifest) => {
23+
spin_manifest::normalize::normalize_manifest(&mut manifest);
24+
let bc = build_configs_from_manifest(&manifest);
25+
let dt = deployment_targets_from_manifest(&manifest);
26+
Ok((bc, dt, Ok(manifest)))
27+
}
28+
Err(e) => {
29+
let bc = fallback_load_build_configs(&manifest_file).await?;
30+
let dt = fallback_load_deployment_targets(&manifest_file).await?;
31+
Ok((bc, dt, Err(e)))
32+
}
2033
}
2134
}
2235

2336
fn build_configs_from_manifest(
24-
mut manifest: spin_manifest::schema::v2::AppManifest,
37+
manifest: &spin_manifest::schema::v2::AppManifest,
2538
) -> Vec<ComponentBuildInfo> {
26-
spin_manifest::normalize::normalize_manifest(&mut manifest);
27-
2839
manifest
2940
.components
30-
.into_iter()
41+
.iter()
3142
.map(|(id, c)| ComponentBuildInfo {
3243
id: id.to_string(),
33-
build: c.build,
44+
build: c.build.clone(),
3445
})
3546
.collect()
3647
}
3748

49+
fn deployment_targets_from_manifest(
50+
manifest: &spin_manifest::schema::v2::AppManifest,
51+
) -> DeploymentTargets {
52+
let target_environments = manifest.application.targets.clone();
53+
// let components = manifest
54+
// .components
55+
// .iter()
56+
// .map(|(id, c)| (id.to_string(), c.source.clone()))
57+
// .collect();
58+
DeploymentTargets::new(target_environments)
59+
}
60+
3861
async fn fallback_load_build_configs(
3962
manifest_file: impl AsRef<Path>,
4063
) -> Result<Vec<ComponentBuildInfo>> {
@@ -57,6 +80,42 @@ async fn fallback_load_build_configs(
5780
})
5881
}
5982

83+
async fn fallback_load_deployment_targets(
84+
manifest_file: impl AsRef<Path>,
85+
) -> Result<DeploymentTargets> {
86+
// fn try_parse_component_source(c: (&String, &toml::Value)) -> Option<(String, spin_manifest::schema::v2::ComponentSource)> {
87+
// let (id, ctab) = c;
88+
// let cs = ctab.as_table()
89+
// .and_then(|c| c.get("source"))
90+
// .and_then(|cs| spin_manifest::schema::v2::ComponentSource::deserialize(cs.clone()).ok());
91+
// cs.map(|cs| (id.to_string(), cs))
92+
// }
93+
let manifest_text = tokio::fs::read_to_string(manifest_file).await?;
94+
Ok(match ManifestVersion::detect(&manifest_text)? {
95+
ManifestVersion::V1 => Default::default(),
96+
ManifestVersion::V2 => {
97+
let table: toml::value::Table = toml::from_str(&manifest_text)?;
98+
let target_environments = table
99+
.get("application")
100+
.and_then(|a| a.as_table())
101+
.and_then(|t| t.get("targets"))
102+
.and_then(|arr| arr.as_array())
103+
.map(|v| v.as_slice())
104+
.unwrap_or_default()
105+
.iter()
106+
.filter_map(|t| t.as_str())
107+
.map(|s| s.to_owned())
108+
.collect();
109+
// let components = table
110+
// .get("component")
111+
// .and_then(|cs| cs.as_table())
112+
// .map(|table| table.iter().filter_map(try_parse_component_source).collect())
113+
// .unwrap_or_default();
114+
DeploymentTargets::new(target_environments)
115+
}
116+
})
117+
}
118+
60119
#[derive(Deserialize)]
61120
pub struct ComponentBuildInfo {
62121
#[serde(default)]

crates/environments/Cargo.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "spin-environments"
3+
version = { workspace = true }
4+
authors = { workspace = true }
5+
edition = { workspace = true }
6+
7+
[dependencies]
8+
anyhow = { workspace = true }
9+
futures = "0.3"
10+
indexmap = "2.2.6"
11+
miette = "7.2.0"
12+
oci-distribution = { git = "https://github.com/fermyon/oci-distribution", rev = "7e4ce9be9bcd22e78a28f06204931f10c44402ba" }
13+
semver = "1.0"
14+
serde = { version = "1.0", features = ["derive"] }
15+
serde_json = "1.0"
16+
spin-common = { path = "../common" }
17+
spin-componentize = { path = "../componentize" }
18+
spin-manifest = { path = "../manifest" }
19+
tracing = { workspace = true }
20+
wac-parser = "0.5.0"
21+
wac-resolver = "0.5.0"
22+
wac-types = "0.5.0"
23+
wasm-pkg-loader = "0.4.1"
24+
25+
[lints]
26+
workspace = true

0 commit comments

Comments
 (0)