Skip to content

Commit 2a02056

Browse files
authored
Merge pull request #1864 from itowlson/templates-add-merge-variables
Allow `spin add` to add variables to the manifest
2 parents b13a23c + 11719c8 commit 2a02056

File tree

8 files changed

+344
-7
lines changed

8 files changed

+344
-7
lines changed

Cargo.lock

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

crates/templates/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@ spin-loader = { path = "../loader" }
3030
tempfile = "3.3.0"
3131
tokio = { version = "1.23", features = ["fs", "process", "rt", "macros"] }
3232
toml = "0.5"
33+
toml_edit = "0.20.2"
3334
url = "2.2.2"
3435
walkdir = "2"

crates/templates/src/manager.rs

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,178 @@ mod tests {
953953
assert!(spin_toml.contains("source = \"encore/target/wasm32-wasi/release/hello_2.wasm\""));
954954
}
955955

956+
#[tokio::test]
957+
async fn can_add_variables_from_template() {
958+
let temp_dir = tempdir().unwrap();
959+
let store = TemplateStore::new(temp_dir.path());
960+
let manager = TemplateManager { store };
961+
let source1 = TemplateSource::File(test_data_root());
962+
let source2 = TemplateSource::File(project_root()); // We will need some of the standard templates too
963+
964+
manager
965+
.install(&source1, &InstallOptions::default(), &DiscardingReporter)
966+
.await
967+
.unwrap();
968+
manager
969+
.install(&source2, &InstallOptions::default(), &DiscardingReporter)
970+
.await
971+
.unwrap();
972+
973+
let dest_temp_dir = tempdir().unwrap();
974+
let application_dir = dest_temp_dir.path().join("spinvars");
975+
976+
// Set up the containing app
977+
{
978+
let template = manager.get("http-rust").unwrap().unwrap();
979+
980+
let values = [
981+
("project-description".to_owned(), "my desc".to_owned()),
982+
("http-base".to_owned(), "/".to_owned()),
983+
("http-path".to_owned(), "/...".to_owned()),
984+
]
985+
.into_iter()
986+
.collect();
987+
let options = RunOptions {
988+
variant: crate::template::TemplateVariantInfo::NewApplication,
989+
output_path: application_dir.clone(),
990+
name: "my various project".to_owned(),
991+
values,
992+
accept_defaults: false,
993+
};
994+
995+
template.run(options).silent().await.unwrap();
996+
}
997+
998+
let spin_toml_path = application_dir.join("spin.toml");
999+
assert!(spin_toml_path.exists(), "expected spin.toml to be created");
1000+
1001+
// Now add the variables
1002+
{
1003+
let template = manager.get("add-variables").unwrap().unwrap();
1004+
1005+
let output_dir = "hello";
1006+
let values = [(
1007+
"service-url".to_owned(),
1008+
"https://service.example.com".to_owned(),
1009+
)]
1010+
.into_iter()
1011+
.collect();
1012+
let options = RunOptions {
1013+
variant: crate::template::TemplateVariantInfo::AddComponent {
1014+
manifest_path: spin_toml_path.clone(),
1015+
},
1016+
output_path: PathBuf::from(output_dir),
1017+
name: "insertvars".to_owned(),
1018+
values,
1019+
accept_defaults: false,
1020+
};
1021+
1022+
template.run(options).silent().await.unwrap();
1023+
}
1024+
1025+
let spin_toml = tokio::fs::read_to_string(&spin_toml_path).await.unwrap();
1026+
assert!(spin_toml.contains("[variables]\nsecret"));
1027+
assert!(spin_toml.contains("url = { default = \"https://service.example.com\" }"));
1028+
}
1029+
1030+
#[tokio::test]
1031+
async fn can_overwrite_existing_variables() {
1032+
let temp_dir = tempdir().unwrap();
1033+
let store = TemplateStore::new(temp_dir.path());
1034+
let manager = TemplateManager { store };
1035+
let source1 = TemplateSource::File(test_data_root());
1036+
let source2 = TemplateSource::File(project_root()); // We will need some of the standard templates too
1037+
1038+
manager
1039+
.install(&source1, &InstallOptions::default(), &DiscardingReporter)
1040+
.await
1041+
.unwrap();
1042+
manager
1043+
.install(&source2, &InstallOptions::default(), &DiscardingReporter)
1044+
.await
1045+
.unwrap();
1046+
1047+
let dest_temp_dir = tempdir().unwrap();
1048+
let application_dir = dest_temp_dir.path().join("spinvars");
1049+
1050+
// Set up the containing app
1051+
{
1052+
let template = manager.get("http-rust").unwrap().unwrap();
1053+
1054+
let values = [
1055+
("project-description".to_owned(), "my desc".to_owned()),
1056+
("http-base".to_owned(), "/".to_owned()),
1057+
("http-path".to_owned(), "/...".to_owned()),
1058+
]
1059+
.into_iter()
1060+
.collect();
1061+
let options = RunOptions {
1062+
variant: crate::template::TemplateVariantInfo::NewApplication,
1063+
output_path: application_dir.clone(),
1064+
name: "my various project".to_owned(),
1065+
values,
1066+
accept_defaults: false,
1067+
};
1068+
1069+
template.run(options).silent().await.unwrap();
1070+
}
1071+
1072+
let spin_toml_path = application_dir.join("spin.toml");
1073+
assert!(spin_toml_path.exists(), "expected spin.toml to be created");
1074+
1075+
// Now add the variables
1076+
{
1077+
let template = manager.get("add-variables").unwrap().unwrap();
1078+
1079+
let output_dir = "hello";
1080+
let values = [(
1081+
"service-url".to_owned(),
1082+
"https://service.example.com".to_owned(),
1083+
)]
1084+
.into_iter()
1085+
.collect();
1086+
let options = RunOptions {
1087+
variant: crate::template::TemplateVariantInfo::AddComponent {
1088+
manifest_path: spin_toml_path.clone(),
1089+
},
1090+
output_path: PathBuf::from(output_dir),
1091+
name: "insertvars".to_owned(),
1092+
values,
1093+
accept_defaults: false,
1094+
};
1095+
1096+
template.run(options).silent().await.unwrap();
1097+
}
1098+
1099+
// Now add them again but with different values
1100+
{
1101+
let template = manager.get("add-variables").unwrap().unwrap();
1102+
1103+
let output_dir = "hello";
1104+
let values = [(
1105+
"service-url".to_owned(),
1106+
"https://other.example.com".to_owned(),
1107+
)]
1108+
.into_iter()
1109+
.collect();
1110+
let options = RunOptions {
1111+
variant: crate::template::TemplateVariantInfo::AddComponent {
1112+
manifest_path: spin_toml_path.clone(),
1113+
},
1114+
output_path: PathBuf::from(output_dir),
1115+
name: "insertvars".to_owned(),
1116+
values,
1117+
accept_defaults: false,
1118+
};
1119+
1120+
template.run(options).silent().await.unwrap();
1121+
}
1122+
1123+
let spin_toml = tokio::fs::read_to_string(&spin_toml_path).await.unwrap();
1124+
assert!(spin_toml.contains("url = { default = \"https://other.example.com\" }"));
1125+
assert!(!spin_toml.contains("service.example.com"));
1126+
}
1127+
9561128
#[tokio::test]
9571129
async fn cannot_add_component_that_does_not_match_trigger() {
9581130
let temp_dir = tempdir().unwrap();

crates/templates/src/renderer.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ pub(crate) enum TemplateContent {
1818

1919
pub(crate) enum RenderOperation {
2020
AppendToml(PathBuf, TemplateContent),
21+
MergeToml(PathBuf, MergeTarget, TemplateContent), // file to merge into, table to merge into, content to merge
2122
WriteFile(PathBuf, TemplateContent),
2223
}
2324

25+
pub(crate) enum MergeTarget {
26+
Application(&'static str),
27+
}
28+
2429
impl TemplateRenderer {
2530
pub(crate) fn render(self) -> anyhow::Result<TemplateOutputs> {
2631
let globals = self.renderer_globals();
@@ -64,6 +69,12 @@ impl RenderOperation {
6469
let rendered_text = String::from_utf8(rendered)?;
6570
Ok(TemplateOutput::AppendToml(path, rendered_text))
6671
}
72+
Self::MergeToml(path, target, content) => {
73+
let rendered = content.render(globals)?;
74+
let rendered_text = String::from_utf8(rendered)?;
75+
let MergeTarget::Application(target_table) = target;
76+
Ok(TemplateOutput::MergeToml(path, target_table, rendered_text))
77+
}
6778
}
6879
}
6980
}

crates/templates/src/run.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use walkdir::WalkDir;
1111
use crate::{
1212
cancellable::Cancellable,
1313
interaction::{InteractionStrategy, Interactive, Silent},
14+
renderer::MergeTarget,
1415
template::TemplateVariantInfo,
1516
};
1617
use crate::{
@@ -248,9 +249,20 @@ impl Run {
248249
Err(anyhow::anyhow!("Spin doesn't know what to do with a 'component' snippet outside an 'add component' operation")),
249250
}
250251
},
252+
"variables" => {
253+
match &self.options.variant {
254+
TemplateVariantInfo::AddComponent { manifest_path } =>
255+
Ok(RenderOperation::MergeToml(
256+
manifest_path.clone(),
257+
MergeTarget::Application("variables"),
258+
content,
259+
)),
260+
TemplateVariantInfo::NewApplication =>
261+
Err(anyhow::anyhow!("Spin doesn't know what to do with a 'variables' snippet outside an 'add component' operation")),
262+
}
263+
},
251264
_ => Err(anyhow::anyhow!(
252-
"Spin doesn't know what to do with snippet {}",
253-
id
265+
"Spin doesn't know what to do with snippet {id}",
254266
)),
255267
}
256268
}

0 commit comments

Comments
 (0)