Skip to content

Commit

Permalink
install forc versions in docker
Browse files Browse the repository at this point in the history
  • Loading branch information
sdankel committed Oct 8, 2024
1 parent fd0ee50 commit 3dac88e
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 47 deletions.
12 changes: 11 additions & 1 deletion deployment/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Stage 1: Build
FROM lukemathwalker/cargo-chef:latest-rust-1.76 as chef
FROM lukemathwalker/cargo-chef:latest-rust-1.79 as chef
WORKDIR /build/
# hadolint ignore=DL3008

Expand All @@ -19,6 +19,15 @@ RUN cargo chef prepare --recipe-path recipe.json

FROM chef as builder

# Install the latest 20 versions of forc with cargo-binstall
RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
RUN tags=$(curl -s "https://api.github.com/repos/FuelLabs/sway/tags?per_page=20" | grep '"name"' | sed -E 's/.*"name": "v?([^"]+)".*/\1/') && \
echo "Tags fetched from the repository:" && \
for tag in $tags; do \
echo "Tag: $tag" && \
cargo binstall --no-confirm --root "/usr/local/cargo/bin/forc-$tag" --pkg-url="https://github.com/FuelLabs/sway/releases/download/v$tag/forc-binaries-linux_arm64.tar.gz" --bin-dir="forc-binaries/forc" --pkg-fmt="tgz" forc; \
done

ENV CARGO_NET_GIT_FETCH_WITH_CLI=true
COPY --from=planner /build/recipe.json recipe.json
# Build our project dependecies, not our application!
Expand All @@ -40,6 +49,7 @@ RUN apt-get update -y \

WORKDIR /root/

COPY --from=builder /usr/local/cargo/bin/forc-* .
COPY --from=builder /build/target/debug/forc_pub .
COPY --from=builder /build/target/debug/forc_pub.d .
COPY --from=builder /build/Rocket.toml .
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod db;
pub mod github;
pub mod middleware;
pub mod models;
pub mod pinata;
pub mod schema;
pub mod upload;
pub mod util;
34 changes: 21 additions & 13 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@ use forc_pub::api::{
auth::{LoginRequest, LoginResponse, UserResponse},
ApiResult, EmptyResponse,
};
use forc_pub::db::{Database};
use forc_pub::db::Database;
use forc_pub::github::handle_login;
use forc_pub::middleware::cors::Cors;
use forc_pub::middleware::session_auth::{SessionAuth, SESSION_COOKIE_NAME};
use forc_pub::middleware::token_auth::TokenAuth;
use forc_pub::pinata::{PinataClient, PinataClientImpl};
use forc_pub::upload::{handle_project_upload, UploadError};
use rocket::fs::TempFile;
use rocket::http::{Cookie, CookieJar};
use rocket::{serde::json::Json, State};
use std::fs::{self};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Instant;
use uuid::Uuid;

#[derive(Default)]
Expand Down Expand Up @@ -109,27 +111,29 @@ fn publish(request: Json<PublishRequest>, auth: TokenAuth) -> ApiResult<EmptyRes
)]
async fn upload_project(
db: &State<Database>,
pinata_client: &State<PinataClientImpl>,
forc_version: &str,
mut tarball: TempFile<'_>,
) -> ApiResult<UploadResponse> {
// Install the forc version.
eprintln!("Forc version: {:?}", forc_version);

// Install the forc version if it's not already installed.
let forc_path_str = format!("forc-{forc_version}");
let forc_path = fs::canonicalize(PathBuf::from(&forc_path_str)).unwrap();
let forc_path = PathBuf::from(&forc_path_str);
fs::create_dir_all(forc_path.clone()).unwrap();
let forc_path = fs::canonicalize(forc_path.clone()).unwrap();

let output = Command::new("cargo")
.arg("install")
.arg("forc")
.arg("--version")
.arg(forc_version)
.arg("binstall")
.arg("--no-confirm")
.arg("--root")
.arg(&forc_path)
.arg(format!("--pkg-url=https://github.com/FuelLabs/sway/releases/download/{forc_version}/forc-binaries-linux_arm64.tar.gz"))
.arg("--bin-dir=forc-binaries/forc")
.arg("--pkg-fmt=tgz")
.arg("forc")
.output()
.expect("Failed to execute cargo install");

if output.status.success() {
println!("Successfully installed forc with tag {}", forc_version);
} else {
if !output.status.success() {
return Err(ApiError::Upload(UploadError::InvalidForcVersion(
forc_version.to_string(),
)));
Expand All @@ -156,6 +160,7 @@ async fn upload_project(
&orig_tarball_path,
&forc_path,
forc_version.to_string(),
pinata_client.inner(),
)
.await?;
let _ = db.conn().insert_upload(&upload)?;
Expand Down Expand Up @@ -186,9 +191,12 @@ fn health() -> String {

// Launch the rocket server.
#[launch]
fn rocket() -> _ {
async fn rocket() -> _ {
let pinata_client = PinataClientImpl::new().await.expect("pinata client");

rocket::build()
.manage(Database::default())
.manage(pinata_client)
.attach(Cors)
.mount(
"/",
Expand Down
64 changes: 64 additions & 0 deletions src/pinata/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::{env, path::PathBuf};

use dotenvy::dotenv;
use pinata_sdk::{PinByFile, PinataApi};

use crate::upload::UploadError;

pub trait PinataClient: Sized {
fn new() -> impl std::future::Future<Output = Result<Self, UploadError>> + Send;
fn upload_file_to_ipfs(
&self,
path: &PathBuf,
) -> impl std::future::Future<Output = Result<String, UploadError>> + Send;
}

pub struct PinataClientImpl {
pinata_api: PinataApi,
}

impl PinataClient for PinataClientImpl {
async fn new() -> Result<Self, UploadError> {
dotenv().ok();
match (env::var("PINATA_API_KEY"), env::var("PINATA_API_SECRET")) {
(Ok(api_key), Ok(secret_api_key)) => {
let api = PinataApi::new(api_key, secret_api_key)
.map_err(|_| UploadError::Authentication)?;
api.test_authentication()
.await
.map_err(|_| UploadError::Authentication)?;
return Ok(PinataClientImpl { pinata_api: api });
}
_ => {
return Err(UploadError::Ipfs);
}
}
}

/// Uploads a file at the given path to a Pinata IPFS gateway.
async fn upload_file_to_ipfs(&self, path: &PathBuf) -> Result<String, UploadError> {
match self
.pinata_api
.pin_file(PinByFile::new(path.to_string_lossy()))
.await
{
Ok(pinned_object) => Ok(pinned_object.ipfs_hash),
Err(_) => Err(UploadError::Ipfs),
}
}
}

pub struct MockPinataClient;

impl PinataClient for MockPinataClient {
fn new() -> impl std::future::Future<Output = Result<Self, UploadError>> + Send {
async { Ok(MockPinataClient) }
}

fn upload_file_to_ipfs(
&self,
_path: &PathBuf,
) -> impl std::future::Future<Output = Result<String, UploadError>> + Send {
async { Ok("ABC123".to_string()) }
}
}
76 changes: 43 additions & 33 deletions src/upload.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use crate::models::NewUpload;
use crate::pinata::{self, PinataClient, PinataClientImpl};
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
use pinata_sdk::{PinByFile, PinataApi};
use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::env;
use tar::Archive;
use thiserror::Error;
use uuid::Uuid;
Expand Down Expand Up @@ -37,9 +38,8 @@ pub async fn handle_project_upload(
orig_tarball_path: &PathBuf,
forc_path: &PathBuf,
forc_version: String,
pinata_client: &impl PinataClient,
) -> Result<NewUpload, UploadError> {
eprintln!("upload_id: {:?}", upload_id);

let unpacked_dir = upload_dir.join("unpacked");
let release_dir = unpacked_dir.join("out/release");
let project_dir = upload_dir.join("project");
Expand All @@ -53,18 +53,14 @@ pub async fn handle_project_upload(
// Remove `out` directory if it exists.
let _ = fs::remove_dir_all(unpacked_dir.join("out"));

eprintln!("forc_path: {:?}", forc_path);

let output = Command::new(format!("{}/bin/forc", forc_path.to_string_lossy()))
.arg("build")
.arg("--release")
.current_dir(&unpacked_dir)
.output()
.expect("Failed to execute forc build");

if output.status.success() {
println!("Successfully built project with forc");
} else {
if !output.status.success() {
return Err(UploadError::InvalidProject);
}

Expand All @@ -85,9 +81,7 @@ pub async fn handle_project_upload(
.output()
.expect("Failed to copy project files");

if output.status.success() {
println!("Successfully copied project files");
} else {
if !output.status.success() {
return Err(UploadError::CopyFiles);
}

Expand All @@ -108,7 +102,9 @@ pub async fn handle_project_upload(
enc.finish().unwrap();

// Store the tarball in IPFS.
let tarball_ipfs_hash = upload_file_to_ipfs(&final_tarball_path).await?;
let tarball_ipfs_hash = pinata_client
.upload_file_to_ipfs(&final_tarball_path)
.await?;

fn find_abi_file_in_dir(dir: &Path) -> Option<PathBuf> {
if let Ok(dir) = fs::read_dir(dir) {
Expand Down Expand Up @@ -136,7 +132,7 @@ pub async fn handle_project_upload(
// Store the ABI in IPFS.
let (abi_ipfs_hash, bytecode_identifier) =
if let Some(abi_path) = find_abi_file_in_dir(&release_dir) {
let hash = upload_file_to_ipfs(&abi_path).await?;
let hash = pinata_client.upload_file_to_ipfs(&abi_path).await?;

// TODO: https://github.com/FuelLabs/forc.pub/issues/16 Calculate the bytecode identifier and store in the database along with the ABI hash.
let bytecode_identifier = None;
Expand All @@ -157,25 +153,39 @@ pub async fn handle_project_upload(
Ok(upload)
}

async fn upload_file_to_ipfs(path: &PathBuf) -> Result<String, UploadError> {
match (env::var("PINATA_API_KEY"), env::var("PINATA_API_SECRET")) {
(Ok(api_key), Ok(secret_api_key)) => {
// TODO: move to server context

let api =
PinataApi::new(api_key, secret_api_key).map_err(|_| UploadError::Authentication)?;
api.test_authentication()
.await
.map_err(|_| UploadError::Authentication)?;

match api.pin_file(PinByFile::new(path.to_string_lossy())).await {
Ok(pinned_object) => Ok(pinned_object.ipfs_hash),
Err(_) => Err(UploadError::Ipfs),
}
}
_ => {
// TODO: fallback to a local IPFS node for tests
Err(UploadError::Ipfs)
}
#[cfg(test)]
mod tests {
use crate::pinata::MockPinataClient;

use super::*;

#[tokio::test]
async fn handle_project_upload_success() {
let upload_id = Uuid::new_v4();
let upload_dir = PathBuf::from("tmp/uploads/").join(upload_id.to_string());
let orig_tarball_path = PathBuf::from("tests/fixtures/sway-project.tgz");
let forc_version = "0.63.4";
let forc_path = PathBuf::from("tests/fixtures/forc-0.63.4")
.canonicalize()
.unwrap();
let mock_client = MockPinataClient::new().await.expect("mock pinata client");

let result = handle_project_upload(
&upload_dir,
&upload_id,
&orig_tarball_path,
&forc_path,
forc_version.to_string(),
&mock_client,
)
.await
.expect("result ok");

assert!(result.id == upload_id);
assert_eq!(result.source_code_ipfs_hash, "ABC123".to_string());
assert_eq!(result.abi_ipfs_hash, Some("ABC123".to_string()));
assert!(result.forc_version == forc_version);
// TODO: https://github.com/FuelLabs/forc.pub/issues/16
// assert!(result.bytecode_identifier.is_some());
}
}
Binary file added tests/fixtures/sway-project.tgz
Binary file not shown.

0 comments on commit 3dac88e

Please sign in to comment.