adnv 1.5.0

CLI that tracks, checks and auto-updates infrastructure dependency versions across Terraform, Helm, Docker, Git and npm
Documentation
use crate::Result;
use crate::sources::resolve::ResolvedSource;

pub fn apply<'a>(sources: impl IntoIterator<Item = &'a ResolvedSource>) -> Result<()> {
    use itertools::Itertools;

    use crate::util::edit;

    for (path, sources) in &sources
        .into_iter()
        .sorted_by_key(|source| source.source.source.location().unwrap().path())
        .chunk_by(|source| source.source.source.location().unwrap().path())
    {
        let actions: Vec<_> = sources
            .filter(|source| source.wanted.is_some())
            .filter_map(|source| {
                Some((
                    source.source.source.location().unwrap().span()?,
                    &source.wanted,
                ))
            })
            .map(|((start, end), version)| {
                edit::Action::replace(start..=end, format!("\"{}\"", version.as_ref().unwrap()))
            })
            .collect();
        edit::edit_file(path, &actions)?;
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::apply;
    use crate::sources::parse::{Location, ParseOptions, parse_dir};
    use crate::sources::resolve::ResolvedSource;

    /// The two halves of an update are written in different places: the parsers
    /// hand out byte spans — pest's for `.tf`, `util::parse::loc_to_index`'s for
    /// `Chart.yaml` — and `util::edit` consumes them against a rope indexed by
    /// character. Each side has its own tests; this one pins the contract
    /// between them, on files whose versions sit behind accented text so a
    /// byte/character mix-up cannot pass unnoticed.
    #[tokio::test]
    async fn parsed_spans_land_on_the_versions_they_describe() {
        let dir = tempfile::TempDir::with_prefix("adnv-apply").unwrap();

        let terraform = "\
# Déploiement du namespace à créer
module \"namespace\" {
  source  = \"gitlab.com/vigigloo/tf-modules/k8slimitednamespace\"
  version = \"0.3.5\"
}

# Référence : le module précédent, déjà à jour
module \"mattermost\" {
  source  = \"gitlab.com/vigigloo/tools-k8s/mattermost\"
  version = \"0.1.0\"
}
";
        let chart = "\
apiVersion: v2
name: déploiement
version: 1.0.0
dependencies:
  # Révision à suivre
  - name: nginx-ingress
    version: \"0.17.0\"
    repository: \"https://helm.nginx.com/stable\"
";
        std::fs::write(dir.path().join("main.tf"), terraform).unwrap();
        std::fs::write(dir.path().join("Chart.yaml"), chart).unwrap();

        let (parsed, errors) = parse_dir(
            dir.path(),
            &ParseOptions {
                recursive: false,
                hidden: false,
            },
        )
        .await
        .split();
        assert!(errors.is_empty(), "{errors:?}");
        assert_eq!(parsed.len(), 3, "{parsed:?}");

        let resolved: Vec<_> = parsed
            .into_iter()
            .map(|source| {
                // A `Location` holds the path relative to the root that was
                // walked, and `apply` opens it relative to the process' own
                // directory. `resolve_sources` only ever walks the current one,
                // so the two agree there; rebasing keeps that out of the way of
                // what this test is about, which is the spans.
                let location = source.source.location().unwrap();
                let (line, col) = location.loc();
                let (start, end) = location.span().unwrap();
                let rebased = Location::new(dir.path().join(location.path()), line, col)
                    .with_span(start, end);

                ResolvedSource::new(source.with_location(rebased), "9.9.9").with_wanted("9.9.9")
            })
            .collect();

        apply(&resolved).expect("every span describes the file it came from");

        assert_eq!(
            std::fs::read_to_string(dir.path().join("main.tf")).unwrap(),
            terraform
                .replace("\"0.3.5\"", "\"9.9.9\"")
                .replace("\"0.1.0\"", "\"9.9.9\"")
        );
        assert_eq!(
            std::fs::read_to_string(dir.path().join("Chart.yaml")).unwrap(),
            chart.replace("\"0.17.0\"", "\"9.9.9\"")
        );
    }
}