Skip to content

Commit

Permalink
Source scan integration configurable image (#142)
Browse files Browse the repository at this point in the history
this adds ability to configure image via
[`Cargo.toml`](https://github.com/dj8yfo/sample_no_workspace/blob/set_metadata/Cargo.toml#L14-L16)
:

```toml
[package.metadata.near.reproducible_build]
image = "docker.io/sourcescan/cargo-near:0.6.0-builder"
image_digest = "sha256:d21001ebc889478deac105a07efbefcd667d6a2e927e8ea5f1526cd1877ae84a"
```

`cargo` doesn't automatically populate `package.metadata` with
`workspace.metadata`
near-examples/update-migrate-rust@d3259c3

```bash
        # package
        ...
        metadata: Object {
            "near": Object {
                "reproducible_build": Object {
                    "image": Object {
                        "workspace": Bool(true),
                    },
                },
            },
        },
        ...
        # workspace
        workspace_metadata: Object {
            "near": Object {
                "reproducible_build": Object {
                    "image": String("docker.io/sourcescan/cargo-near:0.6.0@sha256:bf488476d9c4e49e36862bbdef2c595f88d34a295fd551cc65dc291553849471"),
                },
            },
        },
```
  • Loading branch information
dj8yfo authored Apr 8, 2024
1 parent b8a8bdf commit 7afc79b
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 56 deletions.
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

0 comments on commit 7afc79b

Please sign in to comment.