basic download facility with SHA1 verification

This commit is contained in:
Shautvast 2024-01-15 12:35:06 +01:00
commit 16f366e542
12 changed files with 1876 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.iml
.idea/
target/

1385
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

17
Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "jargo"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
toml = "0.8"
anyhow = "1.0"
strong-xml = "0.6"
reqwest = {version = "0.11", features = ["blocking"]}
tokio = { version = "1", features = ["full"] }
bytes = "1.5"
home = "0.5"
sha1 = "0.10"
hex = "0.4"

22
src/config.rs Normal file
View file

@ -0,0 +1,22 @@
use std::path::PathBuf;
use std::sync::OnceLock;
pub struct Config {
pub cache_location: String,
pub maven_central: String,
pub user_home: PathBuf,
}
pub static CONFIG: OnceLock<Config> = OnceLock::new();
pub fn get_config() -> &'static Config {
CONFIG.get_or_init(|| {
let user_home = home::home_dir().unwrap();
Config {
cache_location: format!("{}/jargo/repo", user_home.to_str().unwrap()).into(),
user_home,
maven_central: "https://repo.maven.apache.org/maven2".into()
}
});
CONFIG.get().unwrap()
}

115
src/deploader.rs Normal file
View file

@ -0,0 +1,115 @@
use std::fs;
use std::fs::File;
use std::io::prelude::*;
use std::io::Write;
use std::path::Path;
use anyhow::{anyhow, Error};
use bytes::Bytes;
use sha1::{Digest, Sha1};
use strong_xml::XmlRead;
use crate::Artifact;
use crate::config::get_config;
use crate::pom::model::Pom;
pub fn load_artifacts(artifacts: &[Artifact]) -> Result<(), Error> {
for art in artifacts {
load_artifact(art)?;
}
Ok(())
}
pub fn load_artifact(art: &Artifact) -> Result<(), Error> {
let artifact_path = format!("{}/{}/{}",
art.group.replace(".", "/"),
art.name,
art.version,
);
let local_artifact_loc = format!("{}/{}", get_config().cache_location, artifact_path);
if !exists(&local_artifact_loc) {
fs::create_dir_all(&local_artifact_loc)?;
}
let local_artifact_jar_path = format!("{}/{}-{}.jar", local_artifact_loc, art.name, art.version);
if !exists(&local_artifact_jar_path) {
load_verify_artifact_jar(art, &artifact_path, &local_artifact_jar_path)?;
}
let local_artifact_pom_path = format!("{}/{}-{}.pom", local_artifact_loc, art.name, art.version);
let pom_xml = if !exists(&local_artifact_pom_path) {
// download pom
let remote_artifact_pom_url = format!("{}/{}/{}-{}.pom", get_config().maven_central, artifact_path, art.name, art.version);
println!("Downloading {}", remote_artifact_pom_url);
let body = reqwest::blocking::get(&remote_artifact_pom_url)?.text().unwrap();
println!("Downloaded {}", remote_artifact_pom_url);
write_text(&local_artifact_pom_path, &body)?;
body
} else {
// read local pom file
let mut file = File::open(local_artifact_pom_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
contents
};
// parse pom file
let pom = Pom::from_str(&pom_xml).unwrap();
let dependencies: Vec<Artifact> = pom.dependencies.value.into_iter().map(|d| d.into()).collect();
load_artifacts(&dependencies)?;
Ok(())
}
fn load_verify_artifact_jar(art: &Artifact, artifact_path: &str, local_artifact_jar_path: &str) -> Result<(), Error> {
let remote_artifact_jar_url = format!("{}/{}/{}-{}.jar", get_config().maven_central, artifact_path, art.name, art.version);
println!("Downloading {}", remote_artifact_jar_url);
let jar = reqwest::blocking::get(&remote_artifact_jar_url)?.bytes().unwrap();
println!("Downloaded {}", remote_artifact_jar_url);
write_bytes(&local_artifact_jar_path, jar.clone())?;
let local_artifact_jar_sha1_path = format!("{}.sha1", local_artifact_jar_path);
// verify jarfile with SHA1 checksum
let checksum = hex::decode(
if !exists(&local_artifact_jar_sha1_path) {
let remote_artifact_jar_sha1_url = format!("{}.sha1", remote_artifact_jar_url);
println!("{}", remote_artifact_jar_sha1_url);
let jar_checksum = reqwest::blocking::get(&remote_artifact_jar_sha1_url)?.bytes().unwrap();
write_bytes(&local_artifact_jar_sha1_path, jar_checksum.clone())?;
jar_checksum.to_vec()
} else {
let mut file = File::open(local_artifact_jar_sha1_path)?;
let mut contents = Vec::new();
file.read_to_end(&mut contents)?;
contents
})?;
let mut hasher = Sha1::new();
hasher.update(&jar.to_vec());
let result = hasher.finalize();
let validated = result[..] == checksum;
if !validated {
Err(anyhow!("SHA1 checksum for {} is not valid", remote_artifact_jar_url))
} else { Ok(()) }
}
fn exists(path: &str) -> bool {
Path::new(path).exists()
}
fn write_bytes(jar_path: &str, bytes: Bytes) -> Result<(), Error> {
let mut file = File::create(jar_path)?;
file.write_all(&bytes)?;
Ok(())
}
fn write_text(path: &str, contents: &String) -> Result<(), Error> {
let mut file = File::create(path)?;
file.write(contents.as_bytes())?;
Ok(())
}

83
src/lib.rs Normal file
View file

@ -0,0 +1,83 @@
use std::fs;
use anyhow::{anyhow, Error};
use toml::{Table, Value};
pub mod pom;
mod config;
pub mod deploader;
#[derive(Debug)]
pub struct Project {
pub group: String,
pub name: String,
pub version: String,
pub dependencies: Vec<Artifact>,
pub test_dependencies: Vec<Artifact>,
}
#[derive(Debug)]
pub struct Artifact {
pub group: String,
pub name: String,
pub version: String,
}
impl From<pom::model::Dependency> for Artifact {
fn from(value: pom::model::Dependency) -> Self {
Artifact {
group: value.group_id.value,
name: value.artifact_id.value,
version: value.version.value,
}
}
}
impl Artifact {
pub fn from_table_entry(name_group: &str, version: String) -> Result<Self, Error>{
let name_group_split: Vec<&str> = name_group.split(":").collect();
if 2 != name_group_split.len(){
return Err(anyhow!("dependency {} not well formatted", name_group));
}
let group = name_group_split[0].into();
let name = name_group_split[1].into();
Ok(Self{
group,
name,
version: version[1..version.len()-1].to_owned(),
})
}
}
pub fn load_project(jargo_file: Option<&str>) -> Result<Project, Error> {
let project_table = fs::read_to_string(jargo_file.unwrap_or("./Jargo.toml"))?.parse::<Table>()?;
let package = project_table.get("package").expect("package info missing");
let dependencies = get_dependencies(project_table.get("dependencies"))?;
let test_dependencies = get_dependencies(project_table.get("test-dependencies"))?;
Ok(Project {
group: package.get("group").unwrap().to_string(),
name: package.get("name").unwrap().to_string(),
version: package.get("version").unwrap().to_string(),
dependencies,
test_dependencies,
})
}
fn get_dependencies(table: Option<&Value>) -> Result<Vec<Artifact>, Error> {
let mut dependencies = vec![];
if let Some(table) = table {
let table = table.as_table();
if let Some(table) = table {
for dep in table {
dependencies.push(
Artifact::from_table_entry(dep.0, dep.1.to_string())?
)
}
}
}
Ok(dependencies)
}

7
src/main.rs Normal file
View file

@ -0,0 +1,7 @@
use anyhow::Error;
fn main() -> anyhow::Result<(), Error> {
let project = jargo::load_project(Some("tests/sample_project/Jargo.toml"))?;
jargo::deploader::load_artifacts(&project.test_dependencies)?;
Ok(())
}

1
src/pom/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod model;

212
src/pom/model.rs Normal file
View file

@ -0,0 +1,212 @@
use strong_xml::{XmlRead};
/// The Maven variant to parse poms
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "project")]
pub struct Pom {
#[xml(child = "modelVersion")]
pub model_version: ModelVersion,
#[xml(child = "groupId")]
pub group_id: GroupId,
#[xml(child = "artifactId")]
pub artifact_id: ArtifactId,
#[xml(child = "version")]
pub version: Version,
#[xml(child = "name")]
pub name: Name,
#[xml(child = "packaging")]
pub packaging: Packaging,
#[xml(child = "url")]
pub url: Url,
#[xml(child = "description")]
pub description: Description,
#[xml(child = "licenses")]
pub licences: Licenses,
#[xml(child = "scm")]
pub scm: Scm,
#[xml(child = "developers")]
pub developers: Developers,
#[xml(child = "dependencies")]
pub dependencies: Dependencies,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "modelVersion")]
pub struct ModelVersion {
#[xml(text)]
value: String,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "groupId")]
pub struct GroupId {
#[xml(text)]
pub value: String,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "artifactId")]
pub struct ArtifactId {
#[xml(text)]
pub value: String,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "version")]
pub struct Version {
#[xml(text)]
pub value: String,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "name")]
pub struct Name {
#[xml(text)]
value: String,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "id")]
pub struct Id {
#[xml(text)]
value: String,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "packaging")]
pub struct Packaging {
#[xml(text)]
value: String,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "url")]
pub struct Url {
#[xml(text)]
value: String,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "description")]
pub struct Description {
#[xml(text)]
value:String,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "licenses")]
pub struct Licenses {
#[xml(child = "license")]
licenses: Vec<License>,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "distribution")]
pub struct Distribution {
#[xml(text)]
value: String,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "license")]
pub struct License {
#[xml(child = "name")]
name: Name,
#[xml(child = "url")]
url: Url,
#[xml(child = "distribution")]
distribution: Option<Distribution>,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "scm")]
pub struct Scm {
#[xml(child = "url")]
url: Url,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "developers")]
pub struct Developers {
#[xml(child = "developer")]
developers: Vec<Developer>,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "developer")]
struct Developer {
#[xml(child = "id")]
id: Option<Id>,
#[xml(child = "name")]
name: Name,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "dependencies")]
pub struct Dependencies {
#[xml(child = "developer")]
pub value: Vec<Dependency>,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "dependency")]
pub struct Dependency {
#[xml(child = "groupId")]
pub group_id: GroupId,
#[xml(child = "artifactId")]
pub artifact_id: ArtifactId,
#[xml(child = "version")]
pub version: Version,
}
#[cfg(test)]
mod test {
use strong_xml::XmlRead;
use crate::pom::model::Pom;
#[test]
fn parse_should_not_fail() {
Pom::from_str(r#"<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd ">
<modelVersion>4.0.0</modelVersion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.5</version>
<name>Mockito</name>
<packaging>jar</packaging>
<url>http://www.mockito.org</url>
<description>Mock objects library for java</description>
<licenses>
<license>
<name>The MIT License</name>
<url>http://code.google.com/p/mockito/wiki/License</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<url>http://code.google.com/p/mockito/source/browse/</url>
</scm>
<developers>
<developer>
<id>szczepiq</id>
<name>Szczepan Faber</name>
</developer>
</developers>
<dependencies>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>
"#).unwrap();
}
}

View file

@ -0,0 +1,10 @@
[package]
group = "nl.sander"
name = "sample_project"
version = "0.1-SNAPSHOT"
[dependencies]
[test-dependencies]
"junit:junit" = "4.8.2"
"org.mockito:mockito-core" = "1.9.5"

View file

@ -0,0 +1,8 @@
package nl.sander.sample;
public class Sample {
public static int getTheNumber() {
return 42;
}
}

View file

@ -0,0 +1,13 @@
package nl.sander.sample;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class SampleTests {
@Test
public int getTheNumberTest() {
assertEquals(42, Sample.getTheNumber());
}
}