diff --git a/Cargo.lock b/Cargo.lock index 7e78856..c15c1d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4995,11 +4995,10 @@ dependencies = [ "hyper", "ink_metadata", "migration", - "once_cell", "paste", "rand 0.8.5", - "regex", "schemars", + "semver", "serde", "serde_json", "serde_plain", diff --git a/crates/builder/src/commands/serve.rs b/crates/builder/src/commands/serve.rs index bbde74d..4fd6243 100644 --- a/crates/builder/src/commands/serve.rs +++ b/crates/builder/src/commands/serve.rs @@ -22,10 +22,12 @@ pub enum ServeError { pub async fn serve( builder_config: config::Builder, storage_config: config::Storage, + supported_cargo_contract_versions: Vec, database: DatabaseConnection, ) -> Result<(), Error> { let builder_config = Arc::new(builder_config); let storage_config = Arc::new(storage_config); + let supported_cargo_contract_versions = Arc::new(supported_cargo_contract_versions); let docker = Arc::new(Docker::connect_with_socket_defaults()?); let database = Arc::new(database); @@ -40,6 +42,7 @@ pub async fn serve( tokio::spawn(worker::spawn( builder_config.clone(), storage_config.clone(), + supported_cargo_contract_versions.clone(), docker.clone(), database.clone(), sender.clone(), diff --git a/crates/builder/src/main.rs b/crates/builder/src/main.rs index 09c9f40..ba70a8c 100644 --- a/crates/builder/src/main.rs +++ b/crates/builder/src/main.rs @@ -73,7 +73,15 @@ async fn main() -> Result<(), anyhow::Error> { info!("database connection established"); match cli.command { - Command::Serve => commands::serve(builder_config, config.storage, database).await?, + Command::Serve => { + commands::serve( + builder_config, + config.storage, + config.supported_cargo_contract_versions, + database, + ) + .await? + } } Ok(()) diff --git a/crates/builder/src/process/worker.rs b/crates/builder/src/process/worker.rs index 94b5fce..21aa724 100644 --- a/crates/builder/src/process/worker.rs +++ b/crates/builder/src/process/worker.rs @@ -49,6 +49,7 @@ pub(crate) enum WorkerError { pub(crate) async fn spawn( builder_config: Arc, storage_config: Arc, + supported_cargo_contract_versions: Arc>, docker: Arc, db: Arc, log_sender: UnboundedSender, @@ -58,6 +59,7 @@ pub(crate) async fn spawn( .transaction::<_, _, WorkerError>(|txn| { let builder_config = builder_config.clone(); let storage_config = storage_config.clone(); + let supported_cargo_contract_versions = supported_cargo_contract_versions.clone(); let docker = docker.clone(); let log_sender = log_sender.clone(); @@ -94,7 +96,7 @@ pub(crate) async fn spawn( ) .unarchive() .await? - .build(log_sender) + .build(log_sender, &supported_cargo_contract_versions) .await? .get_files(wasm_buf, metadata_buf) .await @@ -196,6 +198,10 @@ enum SessionError { /// Container ran out of time to complete the build. #[display(fmt = "container timed out")] TimedOut, + + /// Unsupported cargo-contract version. + #[display(fmt = "unsupported cargo-contract version")] + UnsupportedCargoContractVersion, } /// Archived build session instance. @@ -319,9 +325,35 @@ impl<'a> UnarchivedInstance<'a> { pub async fn build( self, log_sender: UnboundedSender, + supported_cargo_contract_versions: &[String], ) -> Result, SessionError> { debug!("spawning container for building purposes"); + if !supported_cargo_contract_versions.contains(&self.build_session.cargo_contract_version) { + let result = log_sender + .send(LogEntry { + build_session_id: self.build_session.id, + text: String::from("Provided cargo-contract version is not supported.\n"), + }) + .and_then(|_| { + log_sender.send(LogEntry { + build_session_id: self.build_session.id, + text: format!( + "Consider using version {}", + supported_cargo_contract_versions.first().expect( + "at least one cargo-contract version is expected to be supported" + ) + ), + }) + }); + + if let Err(e) = result { + error!(%e, "unable to send log entry") + } + + return Err(SessionError::UnsupportedCargoContractVersion); + } + let container = match Container::new( self.builder_config, self.docker, diff --git a/crates/common/src/config.rs b/crates/common/src/config.rs index 76853f9..c614108 100644 --- a/crates/common/src/config.rs +++ b/crates/common/src/config.rs @@ -166,11 +166,21 @@ pub struct Config { /// Storage configuration. pub storage: Storage, + /// Supported cargo-contract tooling versions. + /// + /// Docker Hub tags can be used for reference. + #[serde(default = "default_supported_cargo_contract_versions")] + pub supported_cargo_contract_versions: Vec, + /// Enable payments support. #[serde(default = "default_payments")] pub payments: bool, } +fn default_supported_cargo_contract_versions() -> Vec { + vec![String::from("4.0.0-alpha"), String::from("3.1.0")] +} + fn default_payments() -> bool { false } @@ -207,6 +217,7 @@ impl Config { endpoint_url: String::new(), source_code_bucket: String::new(), }, + supported_cargo_contract_versions: default_supported_cargo_contract_versions(), payments: false, } } diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index 03b2814..2487825 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -13,10 +13,9 @@ derive_more = "0.99.17" futures-util = "0.3.28" hex = { version = "0.4.3", features = ["serde"] } ink_metadata = "4.2.0" -once_cell = "1.17.1" paste = "1.0.12" -regex = "1.8.1" schemars = "0.8.12" +semver = "1.0.18" serde = { version = "1.0.162", features = ["derive"] } serde_plain = "1.0.1" serde_json = "1.0.96" diff --git a/crates/server/src/handlers/build_sessions/create.rs b/crates/server/src/handlers/build_sessions/create.rs index a12c544..0fb3726 100644 --- a/crates/server/src/handlers/build_sessions/create.rs +++ b/crates/server/src/handlers/build_sessions/create.rs @@ -8,22 +8,14 @@ use db::{ EntityTrait, QuerySelect, SelectExt, TransactionErrorExt, TransactionTrait, }; use derive_more::{Display, Error, From}; -use once_cell::sync::Lazy; -use regex::Regex; use schemars::JsonSchema; +use semver::Version; use serde::{Deserialize, Serialize}; use serde_json::Value; -use validator::Validate; +use validator::{Validate, ValidationError}; use crate::{auth::AuthenticatedUserId, schema::example_error, validation::ValidatedJson}; -/// Regular expression to match stable versions of `cargo-contract`. -/// -/// Currently, this regex does not support any nightly or unstable versions of the previously mentioned tooling. -static VERSION_REGEX: Lazy = Lazy::new(|| { - Regex::new(r#"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$"#).expect("invalid regex string") -}); - /// Errors that may occur during the build session creation process. #[derive(ErrorResponse, Display, From, Error, OperationIo)] #[aide(output)] @@ -50,11 +42,18 @@ pub(super) struct BuildSessionCreateRequest { source_code_id: i64, /// `cargo-contract` tooling version. - #[validate(regex = "VERSION_REGEX")] + #[validate(length(max = 32), custom = "validate_cargo_contract_version")] #[schemars(example = "crate::schema::example_cargo_contract_version")] cargo_contract_version: String, } +/// Validate the provided cargo-contract version to be a valid Semver string. +fn validate_cargo_contract_version(cargo_contract_version: &str) -> Result<(), ValidationError> { + Version::parse(cargo_contract_version) + .map(|_| ()) + .map_err(|_| ValidationError::new("invalid cargo-contract version")) +} + /// JSON response body. #[derive(Serialize, JsonSchema)] pub(super) struct BuildSessionCreateResponse { diff --git a/crates/server/src/schema.rs b/crates/server/src/schema.rs index 74c4e6f..2514a42 100644 --- a/crates/server/src/schema.rs +++ b/crates/server/src/schema.rs @@ -41,7 +41,7 @@ pub(crate) fn example_error(err: E) -> Value { generate_examples!( database_identifier, i64, 1; hex_hash, HexHash, HexHash([200; 32]); - cargo_contract_version, String, String::from("3.0.1"); + cargo_contract_version, String, String::from("4.0.0-alpha"); build_session_status, build_session::Status, build_session::Status::Completed; log_position, Option, Some(40); log_entry, String, String::from("Compiling futures-util v0.3.28");