Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Source scan integration configurable image #142

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion cargo-near/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ eula = false
[dependencies]
bs58 = "0.4"
camino = "1.1.1"
cargo_metadata = "0.14"
cargo_metadata = "0.18.1"
clap = { version = "4.0.18", features = ["derive", "env"] }
colored = "2.0"
env_logger = "0.9"
log = "0.4"
rustc_version = "0.4"
serde = "1.0.197"
serde_json = "1.0"
sha2 = "0.10"
symbolic-debuginfo = "8.8"
Expand Down
214 changes: 170 additions & 44 deletions cargo-near/src/commands/build_command/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
use std::process::{Command, id};
use std::ops::Deref;
use std::process::{id, Command};
use std::time::{SystemTime, UNIX_EPOCH};

#[cfg(unix)]
use nix::unistd::{getuid, getgid};
use nix::unistd::{getgid, getuid};

use color_eyre::{
eyre::{ContextCompat, WrapErr},
owo_colors::OwoColorize,
};
use env_logger::fmt::Timestamp;
use serde::Deserialize;

use crate::{
types::{manifest::CargoManifestPath, metadata::CrateMetadata},
util,
};

pub mod build;

Expand Down Expand Up @@ -73,6 +79,103 @@ impl BuildCommandlContext {
}
}

pub struct ClonedRepo {
pub tmp_repo: git2::Repository,
pub tmp_contract_path: std::path::PathBuf,
pub contract_path: camino::Utf8PathBuf,
#[allow(unused)]
tmp_contract_dir: tempfile::TempDir,
}

fn clone_repo(args: &BuildCommand) -> color_eyre::eyre::Result<ClonedRepo> {
let contract_path: camino::Utf8PathBuf = if let Some(manifest_path) = &args.manifest_path {
let manifest_path = CargoManifestPath::try_from(manifest_path.deref().clone())?;
manifest_path.directory()?.to_path_buf()
} else {
camino::Utf8PathBuf::from_path_buf(std::env::current_dir()?).map_err(|err| {
color_eyre::eyre::eyre!("Failed to convert path {}", err.to_string_lossy())
})?
};
log::debug!("ClonedRepo.contract_path: {:?}", contract_path,);

let tmp_contract_dir = tempfile::tempdir()?;
let tmp_contract_path = tmp_contract_dir.path().to_path_buf();
log::debug!("ClonedRepo.tmp_contract_path: {:?}", tmp_contract_path);
let tmp_repo = git2::Repository::clone(contract_path.as_str(), &tmp_contract_path)?;
Ok(ClonedRepo {
tmp_repo,
tmp_contract_path,
tmp_contract_dir,
contract_path,
})
}

#[derive(Deserialize, Debug)]
struct ReproducibleBuildMeta {
image: String,
image_digest: String,
}
impl ReproducibleBuildMeta {
pub fn concat_image(&self) -> String {
let mut result = String::new();
result.push_str(&self.image);
result.push('@');
result.push_str(&self.image_digest);
let result = result
.chars()
.filter(|c| c.is_ascii())
.filter(|c| !c.is_ascii_control())
.filter(|c| !c.is_ascii_whitespace())
.collect();
println!("{}", format!("docker image to be used: {}", result).green());
result
}
}

fn get_metadata(manifest_path: camino::Utf8PathBuf) -> color_eyre::eyre::Result<CrateMetadata> {
log::debug!(
"crate in cloned location manifest path : {:?}",
manifest_path
);
let crate_metadata = util::handle_step("Collecting cargo project metadata...", || {
CrateMetadata::collect(CargoManifestPath::try_from(manifest_path)?)
})?;
log::trace!("crate metadata : {:#?}", crate_metadata);
Ok(crate_metadata)
}

fn get_docker_build_meta(
cargo_metadata: &CrateMetadata,
) -> color_eyre::eyre::Result<ReproducibleBuildMeta> {
let build_meta_value = cargo_metadata
.root_package
.metadata
.get("near")
.and_then(|value| value.get("reproducible_build"));

let build_meta: ReproducibleBuildMeta = match build_meta_value {
None => {
return Err(color_eyre::eyre::eyre!(
"Missing `[package.metadata.near.reproducible_build]` in Cargo.toml"
))
}
Some(build_meta_value) => {
serde_json::from_value(build_meta_value.clone()).map_err(|err| {
color_eyre::eyre::eyre!(
"Malformed `[package.metadata.near.reproducible_build]` in Cargo.toml: {}",
err
)
})?
}
};

println!(
"{}",
format!("reproducible build metadata: {:#?}", build_meta).green()
);
Ok(build_meta)
}

pub fn docker_run(args: BuildCommand) -> color_eyre::eyre::Result<camino::Utf8PathBuf> {
let mut cargo_args = vec![];
// Use this in new release version:
Expand All @@ -94,32 +197,34 @@ pub fn docker_run(args: BuildCommand) -> color_eyre::eyre::Result<camino::Utf8Pa
.to_string();
cargo_args.extend(&["--color", &color]);

let mut contract_path: camino::Utf8PathBuf = if let Some(manifest_path) = &args.manifest_path {
manifest_path.into()
} else {
camino::Utf8PathBuf::from_path_buf(std::env::current_dir()?).map_err(|err| {
color_eyre::eyre::eyre!("Failed to convert path {}", err.to_string_lossy())
})?
};

let tmp_contract_dir = tempfile::tempdir()?;
let mut tmp_contract_path = tmp_contract_dir.path().to_path_buf();
let mut cloned_repo = clone_repo(&args)?;

let tmp_repo = git2::Repository::clone(contract_path.as_str(), &tmp_contract_path)?;
let cargo_toml_path: camino::Utf8PathBuf = {
let mut cloned_path: std::path::PathBuf = cloned_repo.tmp_contract_path.clone();
cloned_path.push("Cargo.toml");
cloned_path.try_into()?
};
let cargo_metadata = get_metadata(cargo_toml_path)?;
let docker_build_meta = get_docker_build_meta(&cargo_metadata)?;

// Cross-platform process ID and timestamp
let pid = id().to_string();
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs().to_string();
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
.to_string();

let volume = format!(
"{}:/host",
tmp_repo
cloned_repo
.tmp_repo
.workdir()
.wrap_err("Could not get the working directory for the repository")?
.to_string_lossy()
);
let docker_image = "docker.io/sourcescan/cargo-near:0.6.0-builder"; //XXX need to fix version!!! image from cargo.toml for contract
let docker_container_name = format!("cargo-near-{}-{}", timestamp, pid);
let docker_image = docker_build_meta.concat_image();
let near_build_env_ref = format!("NEAR_BUILD_ENVIRONMENT_REF={}", docker_image);

// Platform-specific UID/GID retrieval
Expand All @@ -129,70 +234,91 @@ pub fn docker_run(args: BuildCommand) -> color_eyre::eyre::Result<camino::Utf8Pa
let uid_gid = "1000:1000".to_string();

let mut docker_args = vec![
"-u", &uid_gid,
"-u",
&uid_gid,
"-it",
"--name", &docker_container_name,
"--volume", &volume,
"--name",
&docker_container_name,
"--volume",
&volume,
"--rm",
"--workdir", "/host",
"--env", &near_build_env_ref,
docker_image,
"/bin/bash", "-c"
"--workdir",
"/host",
"--env",
&near_build_env_ref,
&docker_image,
"/bin/bash",
"-c",
];

let mut cargo_cmd_list = vec![
"cargo",
"near",
"build",
];
let mut cargo_cmd_list = vec!["cargo", "near", "build"];
cargo_cmd_list.extend(&cargo_args);

let cargo_cmd = cargo_cmd_list.join(" ");

docker_args.push(&cargo_cmd);

log::debug!("docker command : {:?}", docker_args);

let mut docker_cmd = Command::new("docker");
docker_cmd.arg("run");
docker_cmd.args(docker_args);

let status = match docker_cmd.status() {
Ok(exit_status) => exit_status,
Err(io_err) => {
println!("Error obtaining status from executing SourceScan command `{:?}`", docker_cmd);
println!("Error `{:?}`", io_err);
println!();
println!(
"{}",
format!(
"Error obtaining status from executing SourceScan command `{:?}`",
docker_cmd
)
.yellow()
);
println!("{}", format!("Error `{:?}`", io_err).yellow());
return Err(color_eyre::eyre::eyre!(
"Reproducible build in docker container failed"
))
));
}
};

if status.success() {
tmp_contract_path.push("target");
tmp_contract_path.push("near");
// TODO: make this a `ClonedRepo` `copy_artifact` method
cloned_repo.tmp_contract_path.push("target");
cloned_repo.tmp_contract_path.push("near");

let dir = tmp_contract_path
.read_dir()
.wrap_err_with(|| format!("No artifacts directory found: `{tmp_contract_path:?}`."))?;
let dir = cloned_repo.tmp_contract_path.read_dir().wrap_err_with(|| {
format!(
"No artifacts directory found: `{:?}`.",
cloned_repo.tmp_contract_path
)
})?;

for entry in dir.flatten() {
if entry.path().extension().unwrap().to_str().unwrap() == "wasm" {
contract_path.push("contract.wasm");
cloned_repo.contract_path.push("contract.wasm");
std::fs::copy::<std::path::PathBuf, camino::Utf8PathBuf>(
entry.path(),
contract_path.clone(),
cloned_repo.contract_path.clone(),
)?;

return Ok(contract_path);
return Ok(cloned_repo.contract_path);
}
}

Err(color_eyre::eyre::eyre!(
"Wasm file not found in directory: `{tmp_contract_path:?}`."
"Wasm file not found in directory: `{:?}`.",
cloned_repo.tmp_contract_path
))
} else {
println!();
println!(
"SourceScan command `{:?}` failed with exit status: {status}",
docker_cmd
"{}",
format!(
"See output above ↑↑↑.\nSourceScan command `{:?}` failed with exit status: {status}.",
docker_cmd
).yellow()
);

Err(color_eyre::eyre::eyre!(
Expand Down
Loading
Loading