From a92e3302eb4636c6869c0101ccb3e963b5044dbb Mon Sep 17 00:00:00 2001 From: Steve Morgan Date: Sun, 1 Dec 2024 16:31:12 -0500 Subject: [PATCH] chore: add taskfile and address clippy errors --- Taskfile.yml | 130 +++++++++++++++++++++++++++++++++++++++++++++++ src/cli/init.rs | 7 ++- src/state/mod.rs | 94 ++++++++++++++++++++++++++++++++-- 3 files changed, 224 insertions(+), 7 deletions(-) create mode 100644 Taskfile.yml diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..43bae68 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,130 @@ +version: '3' + +vars: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-D warnings" + +tasks: + default: + cmds: + - task --list-all + silent: true + + build: + desc: Build the project + cmds: + - cargo build + sources: + - src/**/*.rs + - Cargo.toml + generates: + - target/debug/tbd + + test: + desc: Run all tests + cmds: + - cargo test --all-features + + check: + desc: Run all checks (format, clippy, test) + cmds: + - task: fmt-check + - task: clippy + - task: test + + clippy: + desc: Run clippy + cmds: + - cargo clippy --all-targets --all-features -- -D warnings + + fmt: + desc: Format code + cmds: + - cargo fmt --all + + fmt-check: + desc: Check code formatting + cmds: + - cargo fmt --all -- --check + + clean: + desc: Clean build artifacts + cmds: + - cargo clean + + doc: + desc: Generate documentation + cmds: + - cargo doc --no-deps + sources: + - src/**/*.rs + generates: + - target/doc + + watch: + desc: Watch for changes and run tests + cmds: + - cargo watch -x test + + release: + desc: Build release version + cmds: + - cargo build --release + sources: + - src/**/*.rs + - Cargo.toml + generates: + - target/release/tbd + + install-tools: + desc: Install development tools + cmds: + - cargo install cargo-watch cargo-audit cargo-outdated + - rustup component add clippy rustfmt + + audit: + desc: Run security audit + cmds: + - cargo audit + + outdated: + desc: Check for outdated dependencies + cmds: + - cargo outdated + + ci: + desc: Run CI pipeline locally + cmds: + - task: clean + - task: check + - task: build + - task: test + - task: audit + + # Development workflow tasks + init: + desc: Initialize a new test project + dir: target + cmds: + - cargo run -- init --name {{.CLI_ARGS | default "test-project"}} + + run: + desc: Run the CLI with arguments + cmds: + - cargo run -- {{.CLI_ARGS}} + + dev: + desc: Start development environment + cmds: + - cargo watch -x 'run -- {{.CLI_ARGS}}' + + # Container tasks + docker-build: + desc: Build Docker image + cmds: + - docker build -t tbdtools/tbd-iac:latest . + + docker-run: + desc: Run Docker container + cmds: + - docker run --rm -it tbdtools/tbd-iac:latest {{.CLI_ARGS}} diff --git a/src/cli/init.rs b/src/cli/init.rs index 3984f1e..55b210a 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -43,8 +43,7 @@ mypy = "^1.0" .context("Failed to create pyproject.toml")?; // Create example stack - let example_stack = format!( - r#"from tbdtools import ( + let example_stack = r#"from tbdtools import ( Stack, aws, Resource, @@ -73,7 +72,7 @@ class MainStack(Stack): # Initialize stack stack = MainStack("main") "# - ); + .to_string(); fs::write(project_dir.join("stacks").join("main.py"), example_stack) .context("Failed to create example stack")?; @@ -91,7 +90,7 @@ Infrastructure as Code project using TBD Tools. ├── stacks/ # Stack definitions ├── modules/ # Reusable infrastructure modules ├── providers/ # Custom provider configurations -└── tests/ # Infrastructure tests +└── tests/ # Infrastructure tests ``` ## Getting Started diff --git a/src/state/mod.rs b/src/state/mod.rs index 876a7a0..92ea786 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -3,21 +3,109 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::RwLock; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct Resource { id: String, provider: String, state: serde_json::Value, } +#[derive(Default)] pub struct StateManager { resources: Arc>>, } impl StateManager { pub fn new() -> Self { - Self { - resources: Arc::new(RwLock::new(Vec::new())), + Self::default() + } + + /// Add a new resource to the state + pub async fn add_resource(&self, resource: Resource) -> Result<()> { + let mut resources = self.resources.write().await; + resources.push(resource); + Ok(()) + } + + /// Get a resource by ID + pub async fn get_resource(&self, id: &str) -> Option { + let resources = self.resources.read().await; + resources.iter().find(|r| r.id == id).cloned() + } + + /// List all resources + pub async fn list_resources(&self) -> Vec { + let resources = self.resources.read().await; + resources.clone() + } + + /// Remove a resource by ID + pub async fn remove_resource(&self, id: &str) -> Result<()> { + let mut resources = self.resources.write().await; + if let Some(index) = resources.iter().position(|r| r.id == id) { + resources.remove(index); } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[tokio::test] + async fn test_add_and_get_resource() { + let state = StateManager::new(); + let resource = Resource { + id: "test-1".to_string(), + provider: "aws".to_string(), + state: json!({ + "type": "aws_vpc", + "id": "vpc-123", + }), + }; + + state.add_resource(resource.clone()).await.unwrap(); + let retrieved = state.get_resource("test-1").await.unwrap(); + assert_eq!(retrieved.id, "test-1"); + assert_eq!(retrieved.provider, "aws"); + } + + #[tokio::test] + async fn test_list_resources() { + let state = StateManager::new(); + let resource1 = Resource { + id: "test-1".to_string(), + provider: "aws".to_string(), + state: json!({"type": "aws_vpc"}), + }; + let resource2 = Resource { + id: "test-2".to_string(), + provider: "aws".to_string(), + state: json!({"type": "aws_subnet"}), + }; + + state.add_resource(resource1).await.unwrap(); + state.add_resource(resource2).await.unwrap(); + + let resources = state.list_resources().await; + assert_eq!(resources.len(), 2); + } + + #[tokio::test] + async fn test_remove_resource() { + let state = StateManager::new(); + let resource = Resource { + id: "test-1".to_string(), + provider: "aws".to_string(), + state: json!({"type": "aws_vpc"}), + }; + + state.add_resource(resource).await.unwrap(); + assert!(state.get_resource("test-1").await.is_some()); + + state.remove_resource("test-1").await.unwrap(); + assert!(state.get_resource("test-1").await.is_none()); } }