Skip to content

Commit

Permalink
M2
Browse files Browse the repository at this point in the history
  • Loading branch information
o-tsaruk committed Aug 10, 2023
1 parent d6bb21b commit 80b146c
Show file tree
Hide file tree
Showing 62 changed files with 3,281 additions and 1,964 deletions.
2,061 changes: 1,358 additions & 703 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions crates/builder/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::path::PathBuf;

use clap::{Parser, Subcommand};

/// CLI configuration, provided for the [`clap`] crate.
Expand All @@ -7,6 +9,10 @@ pub(crate) struct Cli {
/// Selected subcommand.
#[command(subcommand)]
pub command: Command,

/// Path to configuration file.
#[arg(short, long, value_parser)]
pub config: Option<PathBuf>,
}

/// Available subcommands.
Expand Down
1 change: 1 addition & 0 deletions crates/builder/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// `serve` subcommand.
mod serve;

pub use serve::serve;
9 changes: 7 additions & 2 deletions crates/builder/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
//!
//! See [`log_collector`] for more details.

#![deny(missing_docs)]
#![deny(clippy::missing_docs_in_private_items)]

/// CLI configuration and available subcommands.
mod cli;

Expand All @@ -55,7 +58,9 @@ use tracing::info;
/// Smart contract builder entrypoint.
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let config = Config::new()?;
let cli = Cli::parse();

let config = Config::new(cli.config)?;

logging::init(&config);

Expand All @@ -67,7 +72,7 @@ async fn main() -> Result<(), anyhow::Error> {
let database = Database::connect(&config.database.url).await?;
info!("database connection established");

match Cli::parse().command {
match cli.command {
Command::Serve => commands::serve(builder_config, config.storage, database).await?,
}

Expand Down
136 changes: 93 additions & 43 deletions crates/builder/src/process/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use bollard::{
LogOutput, RemoveContainerOptions,
},
errors::Error,
image::{CreateImageOptions, ListImagesOptions},
service::MountTypeEnum,
service::{
ContainerWaitResponse, HostConfig, Mount, MountVolumeOptions,
Expand All @@ -20,6 +21,7 @@ use bollard::{
use common::config;
use derive_more::{Display, Error, From};
use futures_util::{Stream, TryStreamExt};
use tracing::info;

use crate::process::volume::{Volume, VolumeError};

Expand Down Expand Up @@ -51,6 +53,31 @@ pub enum DownloadFromContainerError {
FileNotFound,
}

/// Supported container images.
pub enum Image<'a> {
/// Unarchive image, produced using Nix.
Unarchive,

/// Build image, automatically downloaded from Docker registry.
Build {
/// `cargo-contract` version to use during image download process.
version: &'a str,
},

/// Artifact rename image, produced using Nix.
Move,
}

impl<'a> fmt::Display for Image<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Image::Unarchive => write!(f, "stage-unarchive"),
Image::Build { version } => write!(f, "paritytech/contracts-verifiable:{version}"),
Image::Move => write!(f, "stage-move"),
}
}
}

/// A single running Docker container instance.
pub struct Container {
/// Docker-specific container identifier.
Expand All @@ -60,32 +87,16 @@ pub struct Container {
volume: Volume,
}

/// Container environment variables.
pub struct Environment<'a, U: fmt::Display> {
/// Build session file upload token.
pub build_session_token: &'a str,

/// Rust toolchain version used to build the contract.
pub rustc_version: &'a str,

/// `cargo-contract` version used to build the contract.
pub cargo_contract_version: &'a str,

/// S3 pre-signed URL to the source code archive.
pub source_code_url: U,

/// API server URL used to upload the source code archive contents.
pub api_server_url: &'a str,
}

impl Container {
/// Spawn new Docker container with the provided configuration.
pub async fn new<U: fmt::Display>(
pub async fn new(
config: &config::Builder,
client: &Docker,
volume: Volume,
env: Environment<'_, U>,
) -> Result<Self, Error> {
name: &str,
image: Image<'_>,
env: Option<Vec<&str>>,
) -> Result<Self, (Error, Volume)> {
// Attempt to isolate container as much as possible.
//
// The provided container configuration should protect
Expand All @@ -99,7 +110,7 @@ impl Container {
memory_swap: Some(config.memory_swap_limit),
// Mount the passed volume as a home directory of a root user.
mounts: Some(vec![Mount {
target: Some(String::from("/root")),
target: Some(String::from("/contract")),
typ: Some(MountTypeEnum::VOLUME),
volume_options: Some(MountVolumeOptions {
driver_config: Some(MountVolumeOptionsDriverConfig {
Expand All @@ -118,33 +129,43 @@ impl Container {
..Default::default()
};

let container = client
let image_str = image.to_string();

let cmd = if let Image::Build { .. } = image {
if let Err(err) = Self::ensure_image_exists(client, &image_str).await {
return Err((err, volume));
}

Some(vec!["build", "--release"])
} else {
None
};

let container = match client
.create_container(
Some(CreateContainerOptions {
name: env.build_session_token,
..Default::default()
name,
platform: Some("linux/amd64"),
}),
Config {
image: Some("ink-builder"),
// Pass information about the current build session to container
env: Some(vec![
&format!("SOURCE_CODE_URL={}", env.source_code_url),
&format!("CARGO_CONTRACT_VERSION={}", env.cargo_contract_version),
&format!("RUST_VERSION={}", env.rustc_version),
&format!("BUILD_SESSION_TOKEN={}", env.build_session_token),
&format!("API_SERVER_URL={}", env.api_server_url),
]),
image: Some(&*image_str),
cmd,
env,
host_config: Some(host_config),
attach_stdout: Some(true),
attach_stderr: Some(true),
..Default::default()
},
)
.await?;
.await
{
Ok(container) => container,
Err(err) => return Err((err, volume)),
};

client
.start_container::<String>(&container.id, None)
.await?;
if let Err(err) = client.start_container::<String>(&container.id, None).await {
return Err((err, volume));
}

Ok(Self {
id: container.id,
Expand Down Expand Up @@ -181,7 +202,7 @@ impl Container {
client: &Docker,
buf: &'a mut [u8],
) -> Result<&'a [u8], DownloadFromContainerError> {
self.download_from_container_to_buf(client, "/root/artifacts/ink/main.wasm", buf)
self.download_from_container_to_buf(client, "/contract/target/ink/main.wasm", buf)
.await
}

Expand All @@ -193,7 +214,7 @@ impl Container {
client: &Docker,
buf: &'a mut [u8],
) -> Result<&'a [u8], DownloadFromContainerError> {
self.download_from_container_to_buf(client, "/root/artifacts/ink/main.json", buf)
self.download_from_container_to_buf(client, "/contract/target/ink/main.json", buf)
.await
}

Expand All @@ -205,8 +226,8 @@ impl Container {
client.wait_container::<String>(&self.id, None)
}

/// Remove the current Docker container and close the related [`Volume`].
pub async fn remove(self, client: &Docker) -> Result<(), ContainerRemoveError> {
/// Remove the current Docker container and retrieve the inner [`Volume`] value.
pub async fn remove(self, client: &Docker) -> Result<Volume, ContainerRemoveError> {
client
.remove_container(
&self.id,
Expand All @@ -218,7 +239,36 @@ impl Container {
)
.await?;

self.volume.close().await?;
Ok(self.volume)
}

/// Ensure that the image with the provided name exists.
///
/// If it doesn't, an attempt to pull it from Docker registry will be made.
pub async fn ensure_image_exists(client: &Docker, image: &str) -> Result<(), Error> {
let list = client
.list_images(Some(ListImagesOptions {
filters: HashMap::from([("reference", vec![image])]),
..Default::default()
}))
.await?;

if list.is_empty() {
info!(%image, "downloading missing docker image");

client
.create_image(
Some(CreateImageOptions {
from_image: image,
..Default::default()
}),
None,
None,
)
.map_ok(|_| ())
.try_collect::<()>()
.await?;
}

Ok(())
}
Expand Down
Loading

0 comments on commit 80b146c

Please sign in to comment.