Add digests to download targets
This updates the definition of download targets within a manifest to
include a `digest` field, specifying what checksum should be calculated
on the downloaded file.
Change-Id: Ic3139f389ef47ab69c390b5b4729d6641e7970b8
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/qg/+/126212
Commit-Queue: Alexei Frolov <frolv@google.com>
Reviewed-by: Erik Gilling <konkers@google.com>
diff --git a/examples/simple_project/qg.toml b/examples/simple_project/qg.toml
index 7a9d84c..7326390 100644
--- a/examples/simple_project/qg.toml
+++ b/examples/simple_project/qg.toml
@@ -12,14 +12,17 @@
[[targets.cipd.variants]]
match = { os = "linux", arch = "x64" }
url_parameters = { platform = "linux-amd64" }
+digest = "sha256:e9b210f087d12fdf5af68e732228a26753b5ab4bd83c551efb9822252804723e"
[[targets.cipd.variants]]
match = { os = "macos", arch = "x64" }
url_parameters = { platform = "mac-amd64" }
+digest = "sha256:9f7ab348bfb8dbac3f3d70ce591f6fa6992524b0f98d756bc1c3820cd113926d"
[[targets.cipd.variants]]
match = { os = "windows", arch = "x64" }
url_parameters = { platform = "windows-amd64" }
+digest = "sha256:c98d59b02a9251b24f4466a2ce61a870df0416482bbe2c5011abdbde7c96c4dc"
# TODO(frolv): Support archive downloads.
diff --git a/qg/src/digest.rs b/qg/src/digest.rs
new file mode 100644
index 0000000..abaa34d
--- /dev/null
+++ b/qg/src/digest.rs
@@ -0,0 +1,96 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+use std::str::FromStr;
+
+use crate::Error;
+
+#[derive(Debug, PartialEq)]
+#[allow(clippy::module_name_repetitions)]
+pub enum ExpectedDigest {
+ Ignore,
+ Sha256(String),
+}
+
+impl FromStr for ExpectedDigest {
+ type Err = Error;
+
+ /// Parses a string of the format "algorithm:digest" or the special value
+ /// "ignore".
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if s == "ignore" {
+ return Ok(Self::Ignore);
+ }
+
+ let parts: Vec<_> = s.split(':').collect();
+ if parts.len() != 2 || parts[1].is_empty() {
+ // TODO(frolv): Missing digest string.
+ return Err(Error::GenericErrorPlaceholder);
+ }
+
+ match *parts.first().expect("guaranteed to have two elements") {
+ "sha256" => Ok(Self::Sha256(parts[1].to_string())),
+ _ => Err(Error::GenericErrorPlaceholder),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_parse_valid() {
+ assert_eq!(
+ "sha256:abcdef".parse::<ExpectedDigest>().unwrap(),
+ ExpectedDigest::Sha256("abcdef".into())
+ );
+ assert_eq!(
+ "ignore".parse::<ExpectedDigest>().unwrap(),
+ ExpectedDigest::Ignore
+ );
+ }
+
+ #[test]
+ fn test_parse_invalid() {
+ assert!(matches!(
+ "".parse::<ExpectedDigest>().unwrap_err(),
+ Error::GenericErrorPlaceholder,
+ ));
+ assert!(matches!(
+ ":".parse::<ExpectedDigest>().unwrap_err(),
+ Error::GenericErrorPlaceholder,
+ ));
+ assert!(matches!(
+ "abcdef".parse::<ExpectedDigest>().unwrap_err(),
+ Error::GenericErrorPlaceholder,
+ ));
+ assert!(matches!(
+ ":abcdef".parse::<ExpectedDigest>().unwrap_err(),
+ Error::GenericErrorPlaceholder,
+ ));
+ assert!(matches!(
+ "md6:abcdef".parse::<ExpectedDigest>().unwrap_err(),
+ Error::GenericErrorPlaceholder,
+ ));
+ assert!(matches!(
+ "sha256:".parse::<ExpectedDigest>().unwrap_err(),
+ Error::GenericErrorPlaceholder,
+ ));
+ assert!(matches!(
+ "sha256:abc:def".parse::<ExpectedDigest>().unwrap_err(),
+ Error::GenericErrorPlaceholder,
+ ));
+ }
+}
diff --git a/qg/src/lib.rs b/qg/src/lib.rs
index 7d945b1..2aeafe7 100644
--- a/qg/src/lib.rs
+++ b/qg/src/lib.rs
@@ -23,6 +23,7 @@
pub mod registry;
pub mod target;
+mod digest;
mod download;
mod util;
diff --git a/qg/src/project/manifest.rs b/qg/src/project/manifest.rs
index 590144f..647b62d 100644
--- a/qg/src/project/manifest.rs
+++ b/qg/src/project/manifest.rs
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
@@ -156,8 +156,11 @@
///
pub format: Option<String>,
- // For a `bin` format, what to rename the downloaded binary.
+ /// For a `bin` format, what to rename the downloaded binary.
pub bin_name: Option<PathBuf>,
+
+ /// Checksum of the file.
+ pub digest: Option<String>,
}
/// An override of the default `url_parameters` for a downloadable package.
@@ -176,6 +179,9 @@
/// Parameter overrides to apply if this variant is matched.
#[serde(default)]
pub url_parameters: HashMap<String, String>,
+
+ /// Checksum override for this variant's file.
+ pub digest: Option<String>,
}
/// A fake target for testing.
diff --git a/qg/src/target.rs b/qg/src/target.rs
index 1894f7c..28619b1 100644
--- a/qg/src/target.rs
+++ b/qg/src/target.rs
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
@@ -15,6 +15,7 @@
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
+use crate::digest::ExpectedDigest;
use crate::project::manifest;
use crate::util::StringSub;
use crate::{download, platform, Error, Result};
@@ -103,12 +104,14 @@
pub url: StringSub,
pub url_parameters: HashMap<String, String>,
pub variants: Vec<DownloadVariant>,
+ pub digest: Option<ExpectedDigest>,
}
#[derive(Debug)]
pub struct DownloadVariant {
pub matches: VariantMatch,
url_parameters: HashMap<String, String>,
+ pub digest: Option<ExpectedDigest>,
}
#[derive(Debug)]
@@ -144,9 +147,15 @@
}
}
+ let digest = match variant.digest {
+ Some(s) => Some(s.parse()?),
+ None => None,
+ };
+
Ok(DownloadVariant {
matches: variant_match,
url_parameters: variant.url_parameters,
+ digest,
})
})
.collect::<Result<Vec<_>>>()?;
@@ -186,11 +195,17 @@
_ => return Err(Error::GenericErrorPlaceholder),
};
+ let digest = match value.digest {
+ Some(s) => Some(s.parse()?),
+ None => None,
+ };
+
Ok(Self {
format,
url,
url_parameters,
variants,
+ digest,
})
}
}
@@ -344,6 +359,7 @@
variants: vec![],
format: Some("bin".into()),
bin_name: Some("qg".into()),
+ digest: None,
},
)),
};
@@ -368,6 +384,7 @@
variants: vec![],
format: Some("bin".into()),
bin_name: Some("qg".into()),
+ digest: None,
},
)),
};
@@ -392,6 +409,7 @@
variants: vec![],
format: Some("bin".into()),
bin_name: Some("qg".into()),
+ digest: None,
},
)),
};
@@ -411,6 +429,7 @@
variants: vec![],
format: Some("bin".into()),
bin_name: Some("qg".into()),
+ digest: None,
},
)),
};
@@ -430,6 +449,7 @@
variants: vec![],
format: Some("bin".into()),
bin_name: Some("qg".into()),
+ digest: None,
},
)),
};