1mod container_engine;
2mod error;
3
4pub use container_engine::ContainerEngine;
5pub use error::*;
6
7use log::{debug, info};
8use std::{
9 env,
10 fs::{self, File},
11 io::Write,
12 process::Command,
13 time::{Duration, SystemTime},
14};
15
16const CACHE_FILE: &str = "srtool-tag-latest.txt";
17
18#[allow(clippy::result_large_err)]
20pub fn fetch_image_tag() -> Result<String> {
21 debug!("Fetching latest version from github");
22 let url = "https://raw.githubusercontent.com/paritytech/srtool/master/RUSTC_VERSION";
23
24 let tag = ureq::get(url).call()?.body_mut().read_to_string()?.trim().to_string();
25 debug!("tag: {}", tag);
26 Ok(tag)
27}
28
29#[allow(clippy::result_large_err)]
31pub fn get_image_tag(cache_validity: Option<u64>) -> Result<String> {
32 let env_tag = env::var("SRTOOL_TAG");
33 if let Ok(tag) = env_tag {
34 info!("using tag from ENV: {:?}", tag);
35 return Ok(tag);
36 }
37
38 let cache_location = std::env::temp_dir().join(CACHE_FILE);
39 debug!("cache_location = {:?}", cache_location);
40
41 let mut use_cache: bool = false;
42 if cache_location.exists() {
43 let metadata = fs::metadata(&cache_location).unwrap();
44 let last_modif = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH);
45 let validity = cache_validity.map(Duration::from_secs);
46
47 if let Some(duration) = validity {
48 let elapsed = last_modif.elapsed().unwrap();
49 if elapsed < duration {
50 use_cache = true;
51 debug!("cache is {:.2?} old (< max {:.2?})", elapsed, duration);
52 }
53 }
54 } else {
55 use_cache = false;
56 debug!("no cache");
57 }
58
59 let cached_value = fs::read_to_string(&cache_location).map(|s| s.trim_end().to_string());
60
61 match cached_value {
62 Ok(value) if use_cache => {
63 info!("using tag from cached value: {:?}", value);
64 Ok(value)
65 }
66 _ => {
67 let value = fetch_image_tag()?;
68 let mut file = File::create(cache_location)?;
69 file.write_all(value.as_bytes())?;
70 info!("using tag from fetched value: {:?}", value);
71
72 Ok(value)
73 }
74 }
75}
76
77pub fn clear_cache() -> Result<()> {
78 let cache_location = std::env::temp_dir().join(CACHE_FILE);
79 debug!("Deleting cache from {}", cache_location.display());
80 fs::remove_file(cache_location).map_err(SrtoolLibError::IO)
81}
82
83pub fn get_image_digest(image: &str, tag: &str) -> Option<String> {
85 let command = format!("docker inspect {image}:{tag}");
86
87 let output = if cfg!(target_os = "windows") {
88 Command::new("cmd").args(["/C", command.as_str()]).output()
89 } else {
90 Command::new("sh").arg("-c").arg(command).output()
91 };
92
93 let output_str = String::from_utf8(output.unwrap().stdout).unwrap_or_else(|_| "".into());
94 let json: serde_json::Value = serde_json::from_str(&output_str).unwrap_or_default();
95 let digest_str = json[0]["RepoDigests"][0].as_str().unwrap_or_default();
96 let digest = digest_str.split(':').nth(1);
97 digest.map(String::from)
98}
99
100#[cfg(test)]
101mod tests {
102 use crate::{fetch_image_tag, get_image_digest};
103
104 #[test]
105 fn it_fetches_the_version() {
106 let tag = fetch_image_tag().unwrap();
107 println!("current tag = {tag:?}");
108 assert!(!tag.is_empty());
109 }
110
111 #[test]
112 #[ignore = "require docker installed + pulling the image"]
113 fn it_gets_the_image_digest() {
114 let image = "paritytech/srtool";
115 let tag = fetch_image_tag().unwrap();
116 println!("Checking digest for {image}:{tag}");
117 let result = get_image_digest(image, &tag);
118 assert!(result.is_some());
119 }
120}