basic download facility with SHA1 verification
This commit is contained in:
commit
16f366e542
12 changed files with 1876 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
*.iml
|
||||
.idea/
|
||||
target/
|
||||
1385
Cargo.lock
generated
Normal file
1385
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal 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
22
src/config.rs
Normal 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
115
src/deploader.rs
Normal 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
83
src/lib.rs
Normal 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
7
src/main.rs
Normal 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
1
src/pom/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod model;
|
||||
212
src/pom/model.rs
Normal file
212
src/pom/model.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
10
tests/sample_project/Jargo.toml
Normal file
10
tests/sample_project/Jargo.toml
Normal 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"
|
||||
8
tests/sample_project/src/main/java/nl/sander/Sample.java
Normal file
8
tests/sample_project/src/main/java/nl/sander/Sample.java
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package nl.sander.sample;
|
||||
|
||||
public class Sample {
|
||||
|
||||
public static int getTheNumber() {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
13
tests/sample_project/src/test/java/nl/sander/SampleTest.java
Normal file
13
tests/sample_project/src/test/java/nl/sander/SampleTest.java
Normal 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());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue