#![allow(missing_docs)]
use std::vec::Vec;
use std::io::{Read, Write};
use std::fs::File;
use std::path::{Path, PathBuf};
#[cfg(feature = "upgrade")]
use semver::Version;
use serde_json;
use sha1;
use hyper::{self, Client};
use hyper::net::HttpsConnector;
use hyper::header::{Authorization, Basic};
use hyper::status::StatusCode;
use hyper_native_tls::NativeTlsClient;
use core::{CliError, LalResult};
#[derive(Serialize, Deserialize, Clone)]
pub struct Credentials {
pub username: String,
pub password: String,
}
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct ArtifactoryConfig {
pub master: String,
pub slave: String,
pub release: String,
pub vgroup: String,
pub credentials: Option<Credentials>,
}
#[derive(Deserialize)]
struct ArtifactoryVersion {
uri: String, }
#[derive(Deserialize)]
struct ArtifactoryStorageResponse {
children: Vec<ArtifactoryVersion>,
}
fn hyper_req(url: &str) -> LalResult<String> {
let client = Client::with_connector(HttpsConnector::new(NativeTlsClient::new().unwrap()));
let mut res = client.get(url).send()?;
if res.status != hyper::Ok {
return Err(CliError::BackendFailure(format!("GET request with {}", res.status)));
}
let mut body = String::new();
res.read_to_string(&mut body)?;
Ok(body)
}
pub fn http_download_to_path(url: &str, save: &PathBuf) -> LalResult<()> {
debug!("GET {}", url);
let client = Client::with_connector(HttpsConnector::new(NativeTlsClient::new().unwrap()));
let mut res = client.get(url).send()?;
if res.status != hyper::Ok {
return Err(CliError::BackendFailure(format!("GET request with {}", res.status)));
}
if cfg!(feature = "progress") {
#[cfg(feature = "progress")]
{
use indicatif::{ProgressBar, ProgressStyle};
let total_size = res.headers.get::<hyper::header::ContentLength>().unwrap().0;
let mut downloaded = 0;
let mut buffer = [0; 1024 * 64];
let mut f = File::create(save)?;
let pb = ProgressBar::new(total_size);
pb.set_style(ProgressStyle::default_bar()
.template("{bar:40.yellow/black} {bytes}/{total_bytes} ({eta})"));
while downloaded < total_size {
let read = res.read(&mut buffer)?;
f.write_all(&buffer[0..read])?;
downloaded += read as u64;
pb.set_position(downloaded);
}
f.flush()?;
}
} else {
let mut buffer: Vec<u8> = Vec::new();
res.read_to_end(&mut buffer)?;
let mut f = File::create(save)?;
f.write_all(&buffer)?;
}
Ok(())
}
fn get_storage_versions(uri: &str) -> LalResult<Vec<u32>> {
debug!("GET {}", uri);
let resp = hyper_req(uri)
.map_err(|e| {
warn!("Failed to GET {}: {}", uri, e);
CliError::BackendFailure("No version information found on API".into())
})?;
trace!("Got body {}", resp);
let res: ArtifactoryStorageResponse = serde_json::from_str(&resp)?;
let mut builds: Vec<u32> = res.children
.iter()
.map(|r| r.uri.as_str())
.map(|r| r.trim_matches('/'))
.filter_map(|b| b.parse().ok())
.collect();
builds.sort_by(|a, b| b.cmp(a)); Ok(builds)
}
header! {(XCheckSumDeploy, "X-Checksum-Deploy") => [String]}
header! {(XCheckSumSha1, "X-Checksum-Sha1") => [String]}
fn upload_artifact(arti: &ArtifactoryConfig, uri: &str, f: &mut File) -> LalResult<()> {
if let Some(creds) = arti.credentials.clone() {
let client = Client::new();
let mut buffer: Vec<u8> = Vec::new();
f.read_to_end(&mut buffer)?;
let full_uri = format!("{}/{}/{}", arti.slave, arti.release, uri);
let mut sha = sha1::Sha1::new();
sha.update(&buffer);
let auth = Authorization(Basic {
username: creds.username,
password: Some(creds.password),
});
info!("PUT {}", full_uri);
let resp = client.put(&full_uri[..]).header(auth.clone()).body(&buffer[..]).send()?;
debug!("resp={:?}", resp);
let respstr = format!("{} from PUT {}", resp.status, full_uri);
if resp.status != StatusCode::Created {
return Err(CliError::UploadFailure(respstr));
}
debug!("{}", respstr);
info!("PUT {} (X-Checksum-Sha1)", full_uri);
let respsha = client
.put(&full_uri[..])
.header(XCheckSumDeploy("true".into()))
.header(XCheckSumSha1(sha.digest().to_string()))
.header(auth)
.send()?;
debug!("respsha={:?}", respsha);
let respshastr = format!("{} from PUT {} (X-Checksum-Sha1)", respsha.status, full_uri);
if respsha.status != StatusCode::Created {
return Err(CliError::UploadFailure(respshastr));
}
debug!("{}", respshastr);
Ok(())
} else {
Err(CliError::MissingBackendCredentials)
}
}
fn get_storage_as_u32(uri: &str) -> LalResult<u32> {
if let Some(&latest) = get_storage_versions(uri)?.iter().max() {
Ok(latest)
} else {
Err(CliError::BackendFailure("No version information found on API".into()))
}
}
fn get_dependency_env_url(
art_cfg: &ArtifactoryConfig,
name: &str,
version: u32,
env: &str,
) -> String {
let tar_url = format!("{}/{}/env/{}/{}/{}/{}.tar.gz",
art_cfg.slave,
art_cfg.vgroup,
env,
name,
version.to_string(),
name);
trace!("Inferring tarball location as {}", tar_url);
tar_url
}
fn get_dependency_url_latest(
art_cfg: &ArtifactoryConfig,
name: &str,
env: &str,
) -> LalResult<Component> {
let url = format!("{}/api/storage/{}/{}/{}/{}",
art_cfg.master,
art_cfg.release,
"env",
env,
name);
let v = get_storage_as_u32(&url)?;
debug!("Found latest version as {}", v);
Ok(Component {
location: get_dependency_env_url(art_cfg, name, v, env),
version: v,
name: name.into(),
})
}
fn get_latest_versions(art_cfg: &ArtifactoryConfig, name: &str, env: &str) -> LalResult<Vec<u32>> {
let url = format!("{}/api/storage/{}/{}/{}/{}",
art_cfg.master,
art_cfg.release,
"env",
env,
name);
get_storage_versions(&url)
}
fn get_tarball_uri(
art_cfg: &ArtifactoryConfig,
name: &str,
version: Option<u32>,
env: &str,
) -> LalResult<Component> {
if let Some(v) = version {
Ok(Component {
location: get_dependency_env_url(art_cfg, name, v, env),
version: v,
name: name.into(),
})
} else {
get_dependency_url_latest(art_cfg, name, env)
}
}
#[cfg(feature = "upgrade")]
pub struct LatestLal {
pub url: String,
pub version: Version,
}
#[cfg(feature = "upgrade")]
pub fn get_latest_lal_version() -> LalResult<LatestLal> {
let uri = "https://engci-maven-master.cisco.com/artifactory/api/storage/CME-release/lal";
debug!("GET {}", uri);
let resp = hyper_req(uri)
.map_err(|e| {
warn!("Failed to GET {}: {}", uri, e);
CliError::BackendFailure("No version information found on API".into())
})?;
trace!("Got body {}", resp);
let res: ArtifactoryStorageResponse = serde_json::from_str(&resp)?;
let latest: Option<Version> = res.children
.iter()
.map(|r| r.uri.trim_matches('/').to_string())
.inspect(|v| trace!("Found lal version {}", v))
.filter_map(|v| Version::parse(&v).ok())
.max();
if let Some(l) = latest {
Ok(LatestLal {
version: l.clone(),
url: format!("https://engci-maven.cisco.com/artifactory/CME-group/lal/{}/lal.tar",
l),
})
} else {
warn!("Failed to parse version information from artifactory storage api for lal");
Err(CliError::BackendFailure("No version information found on API".into()))
}
}
use super::{Backend, Component};
pub struct ArtifactoryBackend {
pub config: ArtifactoryConfig,
pub cache: String,
}
impl ArtifactoryBackend {
pub fn new(cfg: &ArtifactoryConfig, cache: &str) -> Self {
ArtifactoryBackend {
config: cfg.clone(),
cache: cache.into(),
}
}
}
impl Backend for ArtifactoryBackend {
fn get_versions(&self, name: &str, loc: &str) -> LalResult<Vec<u32>> {
get_latest_versions(&self.config, name, loc)
}
fn get_latest_version(&self, name: &str, loc: &str) -> LalResult<u32> {
let latest = get_dependency_url_latest(&self.config, name, loc)?;
Ok(latest.version)
}
fn get_component_info(
&self,
name: &str,
version: Option<u32>,
loc: &str,
) -> LalResult<Component> {
get_tarball_uri(&self.config, name, version, loc)
}
fn publish_artifact(&self, name: &str, version: u32, env: &str) -> LalResult<()> {
let artdir = Path::new("./ARTIFACT");
let tarball = artdir.join(format!("{}.tar.gz", name));
let lockfile = artdir.join("lockfile.json");
let prefix = format!("env/{}/", env);
let tar_uri = format!("{}{}/{}/{}.tar.gz", prefix, name, version, name);
let mut tarf = File::open(tarball)?;
upload_artifact(&self.config, &tar_uri, &mut tarf)?;
let mut lockf = File::open(lockfile)?;
let lf_uri = format!("{}{}/{}/lockfile.json", prefix, name, version);
upload_artifact(&self.config, &lf_uri, &mut lockf)?;
Ok(())
}
fn get_cache_dir(&self) -> String { self.cache.clone() }
fn raw_fetch(&self, url: &str, dest: &PathBuf) -> LalResult<()> {
http_download_to_path(url, dest)
}
}