fcmc 0.6.3

A CLI tool to check whether CTF challenge meta.toml configs are valid, and optionally test Docker setup
Documentation
use std::collections::HashMap;

use bollard::{
    Docker,
    network::CreateNetworkOptions,
    query_parameters::{
        CreateContainerOptions, CreateContainerOptionsBuilder, StartContainerOptions,
    },
    secret::{ContainerCreateBody, EndpointIpamConfig, Ipam},
};
use clap::builder::Str;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct GameBoxMeta {
    pub name: String,
    pub author: String,
    pub category: String,
    pub description: String,
    pub gamebox: GameBoxConfig,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GameBoxConfig {
    pub username: String,
    pub image_tag: String,
    pub break_point: f32,
    pub fix_point: f32,
    pub down_point: f32,
    pub first_bouns: f32,
}

impl GameBoxMeta {
    pub fn from_toml_str(toml: &str) -> Result<Self, toml::de::Error> {
        toml::from_str(toml)
    }

    // 用户名 & 密码
    pub async fn create_and_start(
        &self,
        docker: &Docker,
        identifier: &str,
        net_bridge: Option<String>,
        container_ip: Option<String>,
        ctf_password: String,
    ) -> Result<(), Box<dyn std::error::Error>> {
        // docker run --rm -d --network=br-awd --ip 172.20.2.100 --name ctf-target-team2 floatctf/ctf-target
        let container_name = format!("{}", identifier);
        let options = CreateContainerOptionsBuilder::new()
            .name(&container_name)
            .build();
        let network_name = net_bridge.unwrap_or_else(|| "bridge".to_string());
        let config = ContainerCreateBody {
            image: Some(self.gamebox.image_tag.clone()),
            env: Some(vec![
                format!("{}={}", "CTF_USER", self.gamebox.username),
                format!("{}={}", "CTF_PASSWORD", ctf_password),
            ]),
            host_config: Some(bollard::models::HostConfig {
                network_mode: network_name.clone().into(),
                ..Default::default()
            }),
            networking_config: Some(bollard::models::NetworkingConfig {
                endpoints_config: Some(std::collections::HashMap::from([(
                    network_name,
                    bollard::models::EndpointSettings {
                        ipam_config: Some(EndpointIpamConfig {
                            ipv4_address: container_ip,
                            ..Default::default()
                        }),
                        ..Default::default()
                    },
                )])),
                ..Default::default()
            }),
            ..Default::default()
        };

        let container = docker.create_container(Some(options), config).await?;
        docker
            .start_container(&container.id, None::<StartContainerOptions>)
            .await?;

        // connect network
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[tokio::test]
    async fn test_create_and_start() {
        let docker = Docker::connect_with_local_defaults().unwrap();
        let meta = GameBoxMeta {
            name: "hello-floatctf".to_string(),
            author: "fb0sh@outlook.com".to_string(),
            category: "Web".to_string(),
            description: "hello floatctf".to_string(),
            gamebox: GameBoxConfig {
                username: "floatctf".to_string(),
                image_tag: "floatctf/hello-floatctf:gamebox-web_v1.0.0".to_string(),
                break_point: 100.0,
                fix_point: 100.0,
                down_point: 200.0,
                first_bouns: 0.2,
            },
        };
        crate::remove_and_create_bridge_net(
            &docker,
            "br-awd".to_string(),
            "172.20.0.0/16".to_string(),
        )
        .await
        .unwrap();
        meta.create_and_start(
            &docker,
            "gamebox-hello-floatctf",
            "br-awd".to_string().into(),
            "172.20.1.100".to_string().into(),
            "floatctf".to_string(),
        )
        .await
        .unwrap();
        tokio::time::sleep(tokio::time::Duration::from_secs(30)).await;
        crate::stop_and_remove(&docker, "gamebox-hello-floatctf")
            .await
            .unwrap();

        meta.create_and_start(
            &docker,
            "gamebox-hello-floatctf",
            "br-awd".to_string().into(),
            "172.20.1.100".to_string().into(),
            "floatctf".to_string(),
        )
        .await
        .unwrap();
    }
}