ffs_cli/providers/
mod.rs

1use std::io::prelude::*;
2use std::net::TcpStream;
3use std::path::Path;
4
5use async_trait::async_trait;
6use ssh2::Session;
7
8use crate::jobs::Job;
9
10const INSTALL_SCRIPT: &str = r#"#!/bin/bash
11set -e
12apt-get update
13apt-get install -y curl git build-essential pkg-config libssl-dev
14curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
15apt-get install -y nodejs
16curl https://sh.rustup.rs -sSf | sh -s -- -y
17source "$HOME/.cargo/env"
18curl -L https://foundry.paradigm.xyz | bash
19"$HOME"/.foundry/bin/foundryup
20"$HOME"/.cargo/bin/cargo install --locked echidna
21"$HOME"/.cargo/bin/cargo install --locked --git https://github.com/crytic/medusa
22"$HOME"/.cargo/bin/cargo install --locked --git https://github.com/crytic/halmos
23"#;
24
25fn install_over_ssh(
26    ip: &str,
27    key_path: &str,
28) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
29    let tcp = TcpStream::connect((ip, 22))?;
30    let mut sess = Session::new()?;
31    sess.set_tcp_stream(tcp);
32    sess.handshake()?;
33    sess.userauth_pubkey_file("root", None, Path::new(key_path), None)?;
34
35    let mut channel = sess.channel_session()?;
36    channel.exec("bash -s")?;
37    channel.write_all(INSTALL_SCRIPT.as_bytes())?;
38    channel.send_eof()?;
39    channel.wait_close()?;
40
41    Ok(())
42}
43
44pub enum ProviderType {
45    Hetzner,
46    AWS,
47}
48
49#[async_trait]
50pub trait Provider: Send + Sync {
51    async fn start_job(&self, name: &str) -> Result<Job, Box<dyn std::error::Error + Send + Sync>>;
52    async fn get_job(
53        &self,
54        job_id: &str,
55    ) -> Result<Option<Job>, Box<dyn std::error::Error + Send + Sync>>;
56    async fn stop_job(&self, job_id: &str)
57        -> Result<Job, Box<dyn std::error::Error + Send + Sync>>;
58    async fn list_jobs(&self) -> Result<Vec<Job>, Box<dyn std::error::Error + Send + Sync>>;
59    async fn tail(
60        &self,
61        job_id: &str,
62        filename: &str,
63    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
64    async fn scp(
65        &self,
66        job_id: &str,
67        filename: &str,
68        destination: &str,
69    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
70
71    async fn install_dependencies(
72        &self,
73        ip: &str,
74        key_path: &str,
75    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
76        let ip = ip.to_string();
77        let key = key_path.to_string();
78        tokio::task::spawn_blocking(move || install_over_ssh(&ip, &key)).await??;
79        Ok(())
80    }
81}
82
83pub struct ProviderFactory;
84
85impl ProviderFactory {
86    /// Creates a new provider instance based on the provider name.
87    ///
88    /// # Panics
89    ///
90    /// Panics if an invalid provider name is provided.
91    #[must_use]
92    pub fn create_provider(name: &str) -> Box<dyn Provider> {
93        match name {
94            "hetzner" => Box::new(hetzner::HetznerProvider::new()),
95            "aws" => Box::new(aws::AWSProvider::new()),
96            _ => panic!("Invalid provider"),
97        }
98    }
99}
100
101pub mod aws;
102pub mod hetzner;