Skip to content
Open
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
46 changes: 44 additions & 2 deletions crates/templates/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,40 @@ pub trait ProgressReporter {
#[derive(Debug)]
pub struct InstallOptions {
exists_behaviour: ExistsBehaviour,
skip_if_all_match: bool,
}

impl InstallOptions {
/// Sets the option to update existing templates. If `update` is true,
/// existing templates are updated. If false, existing templates are
/// skipped.
pub fn update(self, update: bool) -> Self {
pub fn update(mut self, update: bool) -> Self {
let exists_behaviour = if update {
ExistsBehaviour::Update
} else {
ExistsBehaviour::Skip
};

Self { exists_behaviour }
self.exists_behaviour = exists_behaviour;
self
}

/// If set, the installer will skip if the manager has templates,
/// and all templates were installed from the install source.
/// This allows a consumer to avoid downloading from a
/// remote source, at the expense of missing an update if the
/// source is mutable (such as a Git tag or branch).
pub fn skip_if_all_match(mut self, skip_if_all_match: bool) -> Self {
self.skip_if_all_match = skip_if_all_match;
self
}
}

impl Default for InstallOptions {
fn default() -> Self {
Self {
exists_behaviour: ExistsBehaviour::Skip,
skip_if_all_match: false,
}
}
}
Expand Down Expand Up @@ -133,6 +146,18 @@ impl TemplateManager {
options: &InstallOptions,
reporter: &impl ProgressReporter,
) -> anyhow::Result<InstallationResults> {
if options.skip_if_all_match && self.all_match(source).await {
let existing = self.list().await.map(|lr| lr.templates).unwrap_or_default(); // don't fail if we can't list
return Ok(InstallationResults {
installed: Default::default(),
skipped: existing
.into_iter()
.map(|t| (t.id().to_string(), SkippedReason::AlreadyExists))
.collect(),
removed: Default::default(),
});
}

if source.requires_copy() {
reporter.report("Copying remote template source");
}
Expand Down Expand Up @@ -317,6 +342,23 @@ impl TemplateManager {
.map(|l| Template::load_from(&l))
.transpose()
}

async fn all_match(&self, source: &TemplateSource) -> bool {
let existing = self.list().await.map(|lr| lr.templates).unwrap_or_default();
if existing.is_empty() {
// We don't expect a template source to be empty. If the manager is empty, count it as no match.
return false;
}

let (expected_repo, expected_tag) = match source {
TemplateSource::Git(g) => (g.repo(), g.branch()),
_ => return false, // other sources are too mutable
};

existing
.iter()
.all(|template| template.is_installed_from(expected_repo, expected_tag))
}
}

async fn copy_template_over_existing(
Expand Down
2 changes: 1 addition & 1 deletion crates/templates/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ pub(crate) fn parse_manifest_toml(text: impl AsRef<str>) -> anyhow::Result<RawTe
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case", untagged)]
pub(crate) enum RawInstalledFrom {
Git { git: String },
Git { git: String, branch: Option<String> },
File { dir: String },
RemoteTar { url: String },
}
Expand Down
11 changes: 11 additions & 0 deletions crates/templates/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ pub struct GitTemplateSource {
spin_version: String,
}

impl GitTemplateSource {
pub(crate) fn repo(&self) -> &str {
self.url.as_str()
}

pub(crate) fn branch(&self) -> Option<&str> {
self.branch.as_deref()
}
}

impl TemplateSource {
/// Creates a `TemplateSource` referring to the specified Git repository
/// and branch.
Expand All @@ -68,6 +78,7 @@ impl TemplateSource {
match self {
Self::Git(g) => Some(crate::reader::RawInstalledFrom::Git {
git: g.url.to_string(),
branch: g.branch.clone(),
}),
Self::File(p) => {
// Saving a relative path would be meaningless (but should never happen)
Expand Down
20 changes: 16 additions & 4 deletions crates/templates/src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub struct Template {

#[derive(Debug)]
enum InstalledFrom {
Git(String),
Git { url: String, branch: Option<String> },
Directory(String),
RemoteTar(String),
Unknown,
Expand Down Expand Up @@ -244,7 +244,7 @@ impl Template {
// TODO: this is kind of specialised - should we do the discarding of
// non-Git sources at the application layer?
match &self.installed_from {
InstalledFrom::Git(url) => Some(url),
InstalledFrom::Git { url, .. } => Some(url),
_ => None,
}
}
Expand All @@ -254,11 +254,23 @@ impl Template {
.is_some_and(|r| r == source_repo.as_str())
}

/// Determines if the template was installed from the expected repo and
/// branch. This does not prove that the template is up to date, as the
/// tag or branch may have moved in the interim.
pub fn is_installed_from(&self, expected_repo: &str, expected_tag: Option<&str>) -> bool {
match &self.installed_from {
InstalledFrom::Git { url, branch } => {
url == expected_repo && branch.as_deref() == expected_tag
}
_ => false,
}
}

/// A human-readable description of where the template was installed
/// from.
pub fn installed_from_or_empty(&self) -> &str {
match &self.installed_from {
InstalledFrom::Git(repo) => repo,
InstalledFrom::Git { url, .. } => url,
InstalledFrom::Directory(path) => path,
InstalledFrom::RemoteTar(url) => url,
InstalledFrom::Unknown => "",
Expand Down Expand Up @@ -599,7 +611,7 @@ fn read_install_record(layout: &TemplateLayout) -> InstalledFrom {

let installed_from_text = std::fs::read_to_string(layout.installation_record_file()).ok();
match installed_from_text.and_then(parse_installed_from) {
Some(RawInstalledFrom::Git { git }) => InstalledFrom::Git(git),
Some(RawInstalledFrom::Git { git, branch }) => InstalledFrom::Git { url: git, branch },
Some(RawInstalledFrom::File { dir }) => InstalledFrom::Directory(dir),
Some(RawInstalledFrom::RemoteTar { url }) => InstalledFrom::RemoteTar(url),
None => InstalledFrom::Unknown,
Expand Down
4 changes: 3 additions & 1 deletion src/commands/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,9 @@ async fn env_templates_and_plugins(
if let Err(e) = template_manager
.install(
&source,
&spin_templates::InstallOptions::default().update(true),
&spin_templates::InstallOptions::default()
.update(true)
.skip_if_all_match(true),
&DiscardingReporter,
)
.await
Expand Down
Loading