refactor!: remove workshop-pipeline crate and add repology support
BREAKING CHANGE: workshop-pipeline crate removed entirely - Remove workshop-pipeline/ (config structures, template resolution) - Add Repology package name resolution to workshop-baker - Add UPM system dependency collection for cross-pm resolution - Replace AGENTS.md with concise knowledge base format - Add crate-specific AGENTS.md files - Fix various clippy warnings and derive Default where applicable
This commit is contained in:
@@ -4,3 +4,4 @@ playground
|
|||||||
.ruff_cache
|
.ruff_cache
|
||||||
|
|
||||||
.codeartsdoer
|
.codeartsdoer
|
||||||
|
archived
|
||||||
|
|||||||
@@ -1,145 +1,107 @@
|
|||||||
# Agent Guidelines for HoneyBiscuitWorkshop
|
# PROJECT KNOWLEDGE BASE
|
||||||
|
|
||||||
This repository is a monorepo of independent Rust projects using unified coding style and conventions.
|
**Generated:** 2026-04-04
|
||||||
|
**Commit:** 2133f8b
|
||||||
|
**Branch:** master
|
||||||
|
|
||||||
## Project Structure
|
## OVERVIEW
|
||||||
|
HoneyBiscuitWorkshop is a Rust-based CI/CD system ("蜜饼工坊") with LLM-powered pipeline auto-configuration. 9 independent Cargo crates, no workspace.
|
||||||
|
|
||||||
- Original projects use `workshop-{name}` naming convention (e.g., `workshop-executor`, `workshop-vault`)
|
## STRUCTURE
|
||||||
- Submodules: `RustyVault` and `rust-tongsuo` are external projects
|
```
|
||||||
- Each project is independently built with Cargo (no workspace)
|
./
|
||||||
|
├── workshop-baker/ # Main orchestrator (CLI + daemon)
|
||||||
|
├── workshop-agent/ # HTTP agent server
|
||||||
|
├── workshop-pipeline/ # Config library (prebake/finalize)
|
||||||
|
├── workshop-llm-detector/ # LLM repo analyzer
|
||||||
|
├── workshop-vault/ # Secret management client
|
||||||
|
├── workshop-cert/ # TLS certificate generator
|
||||||
|
├── workshop-deviceid/ # Device ID library
|
||||||
|
├── workshop-builder-native/ # Stub (unused)
|
||||||
|
├── workshop-helper-mac/ # MAC address utility
|
||||||
|
├── docs/ # mdBook documentation (zh-CN)
|
||||||
|
├── playground/ # Experimentation
|
||||||
|
└── third_party/ # Empty
|
||||||
|
```
|
||||||
|
|
||||||
## Build and Test Commands
|
## WHERE TO LOOK
|
||||||
|
| Task | Location | Notes |
|
||||||
|
|------|----------|-------|
|
||||||
|
| Baker (main) | `workshop-baker/src/{bake,prebake,engine}/` | Core pipeline logic |
|
||||||
|
| Agent server | `workshop-agent/src/` | Actix-web HTTP + TLS |
|
||||||
|
| Pipeline config | `workshop-pipeline/src/config/` | prebake/finalize YAML |
|
||||||
|
| LLM integration | `workshop-llm-detector/src/llm/` | Ollama/OpenAI clients |
|
||||||
|
| Secrets | `workshop-vault/src/` | Vault client wrapper |
|
||||||
|
|
||||||
|
## CODE MAP
|
||||||
|
| Symbol | Type | Location | Role |
|
||||||
|
|--------|------|----------|------|
|
||||||
|
| PipelineConfig | struct | workshop-pipeline/src/lib.rs:27 | Full pipeline config |
|
||||||
|
| CICDLLMHelper | struct | workshop-llm-detector/src/lib.rs:16 | LLM analysis entry |
|
||||||
|
| Engine | struct | workshop-baker/src/engine.rs | Build executor |
|
||||||
|
| VaultClient | struct | workshop-vault/src/client.rs | Secret ops |
|
||||||
|
|
||||||
|
## CONVENTIONS
|
||||||
|
|
||||||
|
### Rust
|
||||||
|
- **Edition**: 2024 (non-standard, requires Rust 1.85+)
|
||||||
|
- **Naming**: `snake_case` files/modules, `PascalCase` types, `SCREAMING_SNAKE_CASE` consts
|
||||||
|
- **Error handling**: `thiserror` + `anyhow` combo
|
||||||
|
- **Async**: `#[tokio::main]` + `tokio` with "full" features
|
||||||
|
- **Imports**: `std` → `external_crate` → `crate::module`
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- **Rust**: `#[test]` inline, `#[tokio::test]` async, `tests/*.rs` integration
|
||||||
|
- **Python**: `pytest` with `tests/integration/test_*.py` (Docker-based)
|
||||||
|
- **Benchmarks**: Criterion with `harness = false`
|
||||||
|
|
||||||
|
### Build Pipeline
|
||||||
|
- **prebake.yml**: Environment setup (docker/firecracker/baremetal)
|
||||||
|
- **bake.sh**: Build execution with `# @pipeline` decorators
|
||||||
|
- **finalize.yml**: Packaging, deployment, notifications
|
||||||
|
- **Annotations**: `@timeout()`, `@retry()`, `@parallel`, `@fallible`
|
||||||
|
|
||||||
|
## ANTI-PATTERNS (THIS PROJECT)
|
||||||
|
|
||||||
|
1. **Python in Rust project** — workshop-baker has Python tests (pytest.ini, test_prebake.py)
|
||||||
|
2. **temp/ directory** — Non-standard in Rust projects
|
||||||
|
3. **Certificates in source** — artifact workshop-cert/certs/ not excluded
|
||||||
|
4. **Deprecated projects** — workshop-executor.old, workshop-monitor.old still present
|
||||||
|
5. **Hardcoded mirrors** — Tsinghua University mirrors in configs
|
||||||
|
6. **Rust 2024 edition** — May not work with stable toolchains
|
||||||
|
|
||||||
|
## UNIQUE STYLES
|
||||||
|
|
||||||
|
- **Pipeline DSL**: Shell scripts with `# @decorator` annotations
|
||||||
|
- **Dual-mode baker**: CLI commands + daemon mode
|
||||||
|
- **LLM cookbook system**: YAML cookbooks for language-specific builds
|
||||||
|
- **Chinese docs** — Documentation primarily in zh-CN
|
||||||
|
- **User "vulcan"** — Custom build user (not root/runner)
|
||||||
|
|
||||||
|
## COMMANDS
|
||||||
```bash
|
```bash
|
||||||
# Build specific project
|
# Build
|
||||||
cd workshop-executor && cargo build --release
|
cargo build --release -p workshop-baker
|
||||||
# Run all tests for a project
|
|
||||||
cargo test
|
# Test
|
||||||
# Run a single test
|
cargo test -p workshop-baker
|
||||||
cargo test test_function_name
|
pytest tests/integration/test_prebake.py -v
|
||||||
# Run tests in a specific module
|
|
||||||
cargo test module_name
|
# Lint
|
||||||
# Run tests with verbose output
|
|
||||||
cargo test --verbose
|
|
||||||
# Run ignored tests
|
|
||||||
cargo test -- --ignored
|
|
||||||
# Lint with clippy
|
|
||||||
cargo clippy -- -D warnings
|
cargo clippy -- -D warnings
|
||||||
# Format code
|
cargo fmt --all
|
||||||
cargo fmt
|
|
||||||
# Check formatting without making changes
|
# Run baker
|
||||||
cargo fmt --all -- --check
|
cargo run --bin workshop-baker -- prebake config.yml
|
||||||
# Run tests with specific features (RustyVault)
|
cargo run --bin workshop-baker -- bake script.sh
|
||||||
cd RustyVault && cargo test --features sync_handler --verbose
|
|
||||||
# Generate coverage report
|
# Run agent
|
||||||
cargo install cargo-tarpaulin
|
cargo run --bin workshop-agent
|
||||||
cargo tarpaulin --out Lcov
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Code Style Guidelines
|
## NOTES
|
||||||
|
|
||||||
### Edition and Naming
|
- **Docker required** for integration tests
|
||||||
|
- **workshop-builder-native** is a stub (3-line Hello World)
|
||||||
- Workshop projects: Rust 2024 edition, RustyVault: Rust 2021 edition
|
- No root workspace Cargo.toml — each crate is independent
|
||||||
- **Crates/Packages**: `workshop-{name}` (snake_case)
|
- `AGENTS.md.old` at root — legacy documentation
|
||||||
- **Modules/Files**: `snake_case` (e.g., `error.rs`, `config.rs`)
|
|
||||||
- **Structs/Enums**: `PascalCase` (e.g., `VaultError`, `CertificateBundle`)
|
|
||||||
- **Functions/Methods**: `snake_case` (e.g., `generate_device_id()`)
|
|
||||||
- **Constants**: `SCREAMING_SNAKE_CASE` (e.g., `EXIT_CODE_OK`, `VERSION`)
|
|
||||||
|
|
||||||
### Import Organization
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use anyhow::Result;
|
|
||||||
use log::info;
|
|
||||||
use crate::module_name::Type;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Module Structure and Error Handling
|
|
||||||
|
|
||||||
```rust
|
|
||||||
mod config;
|
|
||||||
mod error;
|
|
||||||
mod handler;
|
|
||||||
pub use config::*;
|
|
||||||
pub use error::*;
|
|
||||||
use thiserror::Error;
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum VaultError {
|
|
||||||
#[error("Vault operation failed: {0}")]
|
|
||||||
OperationFailed(String),
|
|
||||||
}
|
|
||||||
pub type Result<T> = std::result::Result<T, VaultError>;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Async Patterns and Serialization
|
|
||||||
|
|
||||||
Use `tokio` with "full" features for async code:
|
|
||||||
```rust
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
env_logger::init();
|
|
||||||
// async code...
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Artifact {
|
|
||||||
pub name: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub type_: String,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Documentation and Testing
|
|
||||||
|
|
||||||
Add doc comments (`///`) for all public items. Place unit tests in `#[cfg(test)]` modules within source files.
|
|
||||||
|
|
||||||
Use `#[tokio::test]` for async tests, `#[ignore]` for tests requiring external resources. Use `maybe_async` crate for conditional async tests:
|
|
||||||
```rust
|
|
||||||
#[maybe_async::test(feature = "sync_handler", async(all(not(feature = "sync_handler")), tokio::test))]
|
|
||||||
async fn test_with_feature_flag() {
|
|
||||||
// test code...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Formatting and Dependencies
|
|
||||||
|
|
||||||
Follow rustfmt settings from RustyVault:
|
|
||||||
- Max width: 120 characters
|
|
||||||
- Comment width: 120 characters
|
|
||||||
- Group imports: `StdExternalCrate`
|
|
||||||
- Imports granularity: `Crate`
|
|
||||||
- Trailing comma: `Vertical`
|
|
||||||
|
|
||||||
Common dependencies:
|
|
||||||
- `tokio` (v1.40-1.48) with "full" features
|
|
||||||
- `serde` and `serde_json` for serialization
|
|
||||||
- `thiserror` for custom errors
|
|
||||||
- `anyhow` for error propagation
|
|
||||||
- `minijinja` for templating
|
|
||||||
- `clap` for CLI parsing (derive features)
|
|
||||||
- `log` and `env_logger` for logging
|
|
||||||
|
|
||||||
### Clippy Lints
|
|
||||||
|
|
||||||
The following clippy lints are allowed in RustyVault:
|
|
||||||
- `result_large_err`, `ptr_arg`, `let_and_return`, `should_implement_trait`
|
|
||||||
- `new_without_default`, `field_reassign_with_default`, `await_holding_lock`
|
|
||||||
- `too_many_arguments`, `unnecessary_unwrap`, `collapsible_match`
|
|
||||||
- `large_enum_variant`, `unnecessary_map_or`
|
|
||||||
|
|
||||||
Use similar allowances for workshop projects when appropriate.
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
Use environment variable prefixes for configuration:
|
|
||||||
- `WSEXECUTOR_` for workshop-executor
|
|
||||||
- Common pattern: `env_logger::init()` at the start of `main()`
|
|
||||||
|
|
||||||
### Cursor/Copilot Rules
|
|
||||||
|
|
||||||
No Cursor or Copilot rules found in the repository.
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
temp
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# workshop-baker/src
|
||||||
|
|
||||||
|
**Baker CLI + execution engine for HoneyBiscuitWorkshop pipelines.**
|
||||||
|
|
||||||
|
## OVERVIEW
|
||||||
|
|
||||||
|
CLI tool and runtime for executing CI/CD pipelines with resource isolation, privilege dropping, and package management integration.
|
||||||
|
|
||||||
|
## STRUCTURE
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── bake.rs # Top-level bake orchestration
|
||||||
|
├── bake/ # Script parsing and building
|
||||||
|
│ ├── parser.rs # @decorator-aware shell parser
|
||||||
|
│ ├── decorator.rs # @pipeline, @retry, @parallel, etc.
|
||||||
|
│ ├── builder.rs # Script template builder
|
||||||
|
│ └── schedule.rs # Function ordering via @after deps
|
||||||
|
├── engine/ # Execution runtime
|
||||||
|
│ ├── executor.rs # Script execution with cgroups
|
||||||
|
│ ├── cgroups.rs # Linux cgroup resource limits
|
||||||
|
│ ├── repology.rs # Package name resolution
|
||||||
|
│ ├── pm.rs # Package manager detection
|
||||||
|
│ └── upm.rs # User package manager (Nix/Guix)
|
||||||
|
├── prebake/ # Build environment setup
|
||||||
|
│ ├── config.rs # PrebakeConfig YAML schema
|
||||||
|
│ ├── stage/ # Bootstrap, DepsSystem, DepsUser, Hooks
|
||||||
|
│ └── security.rs # Privilege dropping logic
|
||||||
|
├── types/ # Shared type definitions
|
||||||
|
│ ├── repology.rs # RepologyEndpoint enum
|
||||||
|
│ ├── builderconfig.rs
|
||||||
|
│ ├── cache.rs
|
||||||
|
│ └── memsize.rs
|
||||||
|
├── monitor/ # Resource usage monitoring
|
||||||
|
│ ├── cgroups.rs # cgroup-based usage collection
|
||||||
|
│ └── jobobject.rs # Windows job objects (stub)
|
||||||
|
├── daemon.rs # Unix socket daemon (TODO: refactor)
|
||||||
|
└── lib.rs # CLI struct + module exports
|
||||||
|
```
|
||||||
|
|
||||||
|
## WHERE TO LOOK
|
||||||
|
|
||||||
|
| Task | Location |
|
||||||
|
|------|----------|
|
||||||
|
| Script parsing with @decorators | `bake/parser.rs:29` - `parse_script()` |
|
||||||
|
| Decorator types | `bake/decorator.rs` - `Decorator` enum |
|
||||||
|
| Build execution | `engine/executor.rs` - `Executor::execute_script()` |
|
||||||
|
| Resource limits | `engine/types.rs:56` - `ResourceLimits` struct |
|
||||||
|
| Prebake stages | `prebake/stage/` subdirectories |
|
||||||
|
| Package detection | `engine/pm.rs:detection()` |
|
||||||
|
| CLI entry | `lib.rs:26` - `cli::Cli` struct |
|
||||||
|
|
||||||
|
## CONVENTIONS
|
||||||
|
|
||||||
|
- **ExecutionContext**: Passed through all stages, carries task_id, username, env_vars, timeout
|
||||||
|
- **PrebakeStage**: Ordered enum controlling stage execution sequence (Bootstrap -> Ready)
|
||||||
|
- **Decorator**: Attached to shell functions via `# @name(args)` comments above function declarations
|
||||||
|
- **Engine**: Lightweight wrapper around cgroup manager for resource isolation
|
||||||
|
- **Dual-mode**: CLI commands (prebake, bake, finalize) vs daemon mode via Unix socket
|
||||||
@@ -60,7 +60,7 @@ pub async fn bake(
|
|||||||
let script = builder::build_script(
|
let script = builder::build_script(
|
||||||
"bake".to_string(),
|
"bake".to_string(),
|
||||||
script_body,
|
script_body,
|
||||||
&bake_base_path,
|
bake_base_path,
|
||||||
&workspace,
|
&workspace,
|
||||||
None,
|
None,
|
||||||
use_template,
|
use_template,
|
||||||
@@ -72,7 +72,7 @@ pub async fn bake(
|
|||||||
let script = builder::build_script(
|
let script = builder::build_script(
|
||||||
func.name,
|
func.name,
|
||||||
func.body,
|
func.body,
|
||||||
&bake_base_path,
|
bake_base_path,
|
||||||
&workspace,
|
&workspace,
|
||||||
None,
|
None,
|
||||||
use_template,
|
use_template,
|
||||||
@@ -104,6 +104,6 @@ fn privileged(prebake_config: &PrebakeConfig) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let drop_after = get_drop_after(&prebake_config.security).unwrap_or(PrebakeStage::default());
|
let drop_after = get_drop_after(&prebake_config.security).unwrap_or_default();
|
||||||
PrebakeStage::Never == drop_after
|
PrebakeStage::Never == drop_after
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use tempfile::NamedTempFile;
|
|||||||
use crate::error::BakeError;
|
use crate::error::BakeError;
|
||||||
|
|
||||||
/// Template for use_template=false, which just executes the main body without any wrapping.
|
/// Template for use_template=false, which just executes the main body without any wrapping.
|
||||||
const TRIVIAL_TEMPLATE: &'static str = "{{ main }}";
|
const TRIVIAL_TEMPLATE: &str = "{{ main }}";
|
||||||
|
|
||||||
pub fn build_script(
|
pub fn build_script(
|
||||||
name: String,
|
name: String,
|
||||||
@@ -23,7 +23,7 @@ pub fn build_script(
|
|||||||
let target = target_dir.join(filename);
|
let target = target_dir.join(filename);
|
||||||
|
|
||||||
let bake_base_content = if use_template {
|
let bake_base_content = if use_template {
|
||||||
std::fs::read_to_string(&bake_base).map_err(|e| BakeError::ScriptGenerationFailed {
|
std::fs::read_to_string(bake_base).map_err(|e| BakeError::ScriptGenerationFailed {
|
||||||
script: bake_base.display().to_string(),
|
script: bake_base.display().to_string(),
|
||||||
reason: e.to_string(),
|
reason: e.to_string(),
|
||||||
})?
|
})?
|
||||||
|
|||||||
@@ -6,8 +6,9 @@
|
|||||||
pub mod cgroups;
|
pub mod cgroups;
|
||||||
pub mod executor;
|
pub mod executor;
|
||||||
pub mod pm;
|
pub mod pm;
|
||||||
pub mod upm;
|
pub mod repology;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
pub mod upm;
|
||||||
|
|
||||||
// Re-export commonly used types for convenience
|
// Re-export commonly used types for convenience
|
||||||
pub use types::{
|
pub use types::{
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ impl CgroupManager {
|
|||||||
|
|
||||||
pub fn set_cpu_weight(&self, weight: u64) -> io::Result<()> {
|
pub fn set_cpu_weight(&self, weight: u64) -> io::Result<()> {
|
||||||
let cpu_weight_path = self.path.join("cpu.weight");
|
let cpu_weight_path = self.path.join("cpu.weight");
|
||||||
let weight = weight.max(1).min(10000);
|
let weight = weight.clamp(1, 10000);
|
||||||
fs::write(&cpu_weight_path, weight.to_string())?;
|
fs::write(&cpu_weight_path, weight.to_string())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,6 @@ impl CgroupManager {
|
|||||||
pub fn add_process(&self, pid: u32) -> io::Result<()> {
|
pub fn add_process(&self, pid: u32) -> io::Result<()> {
|
||||||
let cgroup_procs_path = self.path.join("cgroup.procs");
|
let cgroup_procs_path = self.path.join("cgroup.procs");
|
||||||
let mut file = fs::OpenOptions::new()
|
let mut file = fs::OpenOptions::new()
|
||||||
.write(true)
|
|
||||||
.append(true)
|
.append(true)
|
||||||
.open(&cgroup_procs_path)?;
|
.open(&cgroup_procs_path)?;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
pub mod local;
|
||||||
|
|
||||||
|
use crate::types::repology::RepologyEndpoint;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RepologyError {
|
||||||
|
Unimplemented,
|
||||||
|
RemoteNotSupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn resolve_package_names(
|
||||||
|
packages: &[String],
|
||||||
|
mapped: bool,
|
||||||
|
endpoint: &RepologyEndpoint,
|
||||||
|
target_pm: &str,
|
||||||
|
) -> Result<HashMap<String, Vec<String>>, RepologyError> {
|
||||||
|
let mut result = HashMap::new();
|
||||||
|
|
||||||
|
if packages.is_empty() {
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if mapped {
|
||||||
|
result.insert(target_pm.to_string(), packages.to_vec());
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
match endpoint {
|
||||||
|
RepologyEndpoint::Local | RepologyEndpoint::Default => {
|
||||||
|
let resolved = local::resolve(packages, target_pm).await;
|
||||||
|
if !resolved.is_empty() {
|
||||||
|
result.insert(target_pm.to_string(), resolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RepologyEndpoint::None => {
|
||||||
|
result.insert(target_pm.to_string(), packages.to_vec());
|
||||||
|
}
|
||||||
|
RepologyEndpoint::Disabled => {}
|
||||||
|
RepologyEndpoint::Remote | RepologyEndpoint::Server => {
|
||||||
|
return Err(RepologyError::Unimplemented);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
type PmName = &'static str;
|
||||||
|
type PkgName = &'static str;
|
||||||
|
|
||||||
|
fn pkg(packages: &[(PmName, PkgName)]) -> Vec<(PmName, PkgName)> {
|
||||||
|
packages.to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref MAPPING: HashMap<PkgName, Vec<(PmName, PkgName)>> = {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
|
||||||
|
m.insert("rustup", pkg(&[
|
||||||
|
("pacman", "rustup"),
|
||||||
|
("apt", "rustup"),
|
||||||
|
("dnf", "rustup"),
|
||||||
|
]));
|
||||||
|
|
||||||
|
m.insert("cargo", pkg(&[
|
||||||
|
("pacman", "cargo"),
|
||||||
|
("apt", "cargo"),
|
||||||
|
("dnf", "cargo"),
|
||||||
|
]));
|
||||||
|
|
||||||
|
m.insert("rustc", pkg(&[
|
||||||
|
("pacman", "rustc"),
|
||||||
|
("apt", "rustc"),
|
||||||
|
("dnf", "rustc"),
|
||||||
|
]));
|
||||||
|
|
||||||
|
m.insert("go", pkg(&[
|
||||||
|
("pacman", "go"),
|
||||||
|
("apt", "golang-go"),
|
||||||
|
("dnf", "golang"),
|
||||||
|
]));
|
||||||
|
|
||||||
|
m.insert("gofmt", pkg(&[
|
||||||
|
("pacman", "go"),
|
||||||
|
("apt", "golang-go"),
|
||||||
|
("dnf", "golang"),
|
||||||
|
]));
|
||||||
|
|
||||||
|
m.insert("nodejs", pkg(&[
|
||||||
|
("pacman", "nodejs"),
|
||||||
|
("apt", "nodejs"),
|
||||||
|
("dnf", "nodejs"),
|
||||||
|
]));
|
||||||
|
|
||||||
|
m.insert("npm", pkg(&[
|
||||||
|
("pacman", "npm"),
|
||||||
|
("apt", "npm"),
|
||||||
|
("dnf", "npm"),
|
||||||
|
]));
|
||||||
|
|
||||||
|
m.insert("yarn", pkg(&[
|
||||||
|
("pacman", "yarn"),
|
||||||
|
("apt", "yarn"),
|
||||||
|
("dnf", "yarn"),
|
||||||
|
]));
|
||||||
|
|
||||||
|
m.insert("pnpm", pkg(&[
|
||||||
|
("pacman", "pnpm"),
|
||||||
|
("apt", "pnpm"),
|
||||||
|
("dnf", "pnpm"),
|
||||||
|
]));
|
||||||
|
|
||||||
|
m.insert("python", pkg(&[
|
||||||
|
("pacman", "python"),
|
||||||
|
("apt", "python3"),
|
||||||
|
("dnf", "python3"),
|
||||||
|
]));
|
||||||
|
|
||||||
|
m.insert("pip", pkg(&[
|
||||||
|
("pacman", "python-pip"),
|
||||||
|
("apt", "python3-pip"),
|
||||||
|
("dnf", "python3-pip"),
|
||||||
|
]));
|
||||||
|
|
||||||
|
m
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn resolve(
|
||||||
|
packages: &[String],
|
||||||
|
target_pm: &str,
|
||||||
|
) -> Vec<String> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for pkg in packages {
|
||||||
|
if let Some(mappings) = MAPPING.get(pkg.as_str()) {
|
||||||
|
for (pm, name) in mappings {
|
||||||
|
if *pm == target_pm {
|
||||||
|
result.push(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
@@ -6,11 +6,14 @@ mod custom;
|
|||||||
use crate::{ExecutionContext, error::DependencyError};
|
use crate::{ExecutionContext, error::DependencyError};
|
||||||
|
|
||||||
pub struct UPMSysDeps {
|
pub struct UPMSysDeps {
|
||||||
/// Packages to be installed
|
pub packages: Vec<String>,
|
||||||
packages: Vec<String>,
|
pub mapped: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether package name is mapped to distro
|
impl UPMSysDeps {
|
||||||
mapped: bool,
|
pub fn new(packages: Vec<String>, mapped: bool) -> Self {
|
||||||
|
Self { packages, mapped }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ impl UserPackageManager for Custom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn system_dependency(&self, _config: &serde_yaml::Value) -> super::UPMSysDeps {
|
fn system_dependency(&self, _config: &serde_yaml::Value) -> super::UPMSysDeps {
|
||||||
UPMSysDeps {
|
UPMSysDeps::new(vec![], true)
|
||||||
packages: vec![],
|
|
||||||
mapped: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn main(
|
async fn main(
|
||||||
@@ -52,7 +49,7 @@ impl UserPackageManager for Custom {
|
|||||||
};
|
};
|
||||||
log::info!("Executing custom command #{}: {}.", index, cmd.name);
|
log::info!("Executing custom command #{}: {}.", index, cmd.name);
|
||||||
let result = engine
|
let result = engine
|
||||||
.execute_command_with_delta(&cmd.command, &ctx, &event_tx, &deltactx)
|
.execute_command_with_delta(&cmd.command, ctx, event_tx, &deltactx)
|
||||||
.await;
|
.await;
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
log::error!("Custom command {} failed: {:?}", index, e);
|
log::error!("Custom command {} failed: {:?}", index, e);
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ pub async fn monitor(
|
|||||||
_timestamp: default_internal_timestamp(),
|
_timestamp: default_internal_timestamp(),
|
||||||
};
|
};
|
||||||
loop {
|
loop {
|
||||||
let usage: UsageStats = cgroups::get_usage(&name, prev_usage)?; // TODO: 多系统支持?
|
let usage: UsageStats = cgroups::get_usage(name, prev_usage)?; // TODO: 多系统支持?
|
||||||
prev_usage = usage.clone();
|
prev_usage = usage.clone();
|
||||||
// dbg!(&usage);
|
// dbg!(&usage);
|
||||||
let usage_info = ResourceUsageInfo {
|
let usage_info = ResourceUsageInfo {
|
||||||
@@ -83,7 +83,7 @@ async fn send_usage_info<S>(stream: &mut S, usage_json: &str) -> anyhow::Result<
|
|||||||
where
|
where
|
||||||
S: tokio::io::AsyncWrite + Unpin,
|
S: tokio::io::AsyncWrite + Unpin,
|
||||||
{
|
{
|
||||||
stream.write(usage_json.as_bytes()).await?;
|
stream.write_all(usage_json.as_bytes()).await?;
|
||||||
stream.write_all(b"\n").await?;
|
stream.write_all(b"\n").await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub fn get_usage(
|
|||||||
let mut process_count: u64 = 0;
|
let mut process_count: u64 = 0;
|
||||||
|
|
||||||
if let Some(cpu) = cgroup.controller_of::<CpuController>() {
|
if let Some(cpu) = cgroup.controller_of::<CpuController>() {
|
||||||
let stats = parse_cpustat(&cpu.cpu().stat.as_str())?;
|
let stats = parse_cpustat(cpu.cpu().stat.as_str())?;
|
||||||
cpu_usage_us = stats.usage_usec;
|
cpu_usage_us = stats.usage_usec;
|
||||||
} else {
|
} else {
|
||||||
log::warn!("Failed to fetch CPU usage info!");
|
log::warn!("Failed to fetch CPU usage info!");
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ pub mod env;
|
|||||||
pub mod security;
|
pub mod security;
|
||||||
pub mod stage;
|
pub mod stage;
|
||||||
|
|
||||||
use std::path::Path;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::Cli,
|
cli::Cli,
|
||||||
error::PrebakeError,
|
error::PrebakeError,
|
||||||
prebake::{security::get_drop_after, stage::PrebakeStage},
|
prebake::{security::get_drop_after, stage::PrebakeStage},
|
||||||
};
|
};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
pub use config::PrebakeConfig;
|
pub use config::PrebakeConfig;
|
||||||
pub use security::prebake_drop_privilege;
|
pub use security::prebake_drop_privilege;
|
||||||
@@ -115,10 +115,10 @@ pub async fn prebake(
|
|||||||
};
|
};
|
||||||
// Wait for bakerd to finish Fetch-1
|
// Wait for bakerd to finish Fetch-1
|
||||||
let stage = stage.unwrap_or(PrebakeStage::Bootstrap);
|
let stage = stage.unwrap_or(PrebakeStage::Bootstrap);
|
||||||
if let Some(env) = &prebake.envvars {
|
if let Some(env) = &prebake.envvars
|
||||||
if let Some(prebake) = &env.prebake {
|
&& let Some(prebake) = &env.prebake
|
||||||
ctx.env_vars.extend(prebake.clone());
|
{
|
||||||
}
|
ctx.env_vars.extend(prebake.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bootstrap stage
|
// Bootstrap stage
|
||||||
@@ -145,24 +145,78 @@ pub async fn prebake(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(dependencies) = prebake.dependencies {
|
if let Some(dependencies) = prebake.dependencies {
|
||||||
// DepsSystem stage
|
let endpoint = dependencies
|
||||||
if stage <= PrebakeStage::DepsSystem {
|
.config
|
||||||
log::info!("Running PBStage: DepsSystem");
|
.as_ref()
|
||||||
prebake_drop_privilege(PrebakeStage::DepsSystem, dropafter, target_user, &mut ctx)?;
|
.map(|c| c.repology_endpoint)
|
||||||
ctx.task_id = format!("{}-{}-depssystem", cli.pipeline, cli.build_id);
|
.unwrap_or(crate::types::repology::RepologyEndpoint::Default);
|
||||||
if let Some(system) = dependencies.system {
|
|
||||||
stage::depssystem::depssystem(&system, &ctx, None).await?;
|
let osinfo = os_info::get();
|
||||||
|
let detected_pm = crate::engine::pm::detect(&osinfo).map(|pm| pm.name().to_string());
|
||||||
|
|
||||||
|
let upm_sys_deps = if let Some(user) = &dependencies.user {
|
||||||
|
if stage <= PrebakeStage::DepsSystem {
|
||||||
|
stage::depsuser::collect_upm_sysdeps(user, &ctx, &endpoint).await?
|
||||||
|
} else {
|
||||||
|
stage::depsuser::CollectedUPMdeps::default()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stage::depsuser::CollectedUPMdeps::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut system_config = dependencies.system.clone().unwrap_or_default();
|
||||||
|
|
||||||
|
if !upm_sys_deps.deps.is_empty() {
|
||||||
|
if let Some(pm_name) = &detected_pm {
|
||||||
|
for (upm_name, sys_deps) in &upm_sys_deps.deps {
|
||||||
|
let resolved = crate::engine::repology::resolve_package_names(
|
||||||
|
&sys_deps.packages,
|
||||||
|
sys_deps.mapped,
|
||||||
|
&endpoint,
|
||||||
|
pm_name,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow::anyhow!("Repology error: {:?}", e))?;
|
||||||
|
|
||||||
|
if !resolved.is_empty() {
|
||||||
|
log::info!(
|
||||||
|
"UPM {} resolved to system packages: {:?}",
|
||||||
|
upm_name,
|
||||||
|
resolved
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (target_pm, packages) in &resolved {
|
||||||
|
system_config
|
||||||
|
.entry(target_pm.clone())
|
||||||
|
.or_insert_with(|| config::SystemDependency {
|
||||||
|
packages: Vec::new(),
|
||||||
|
repositories: None,
|
||||||
|
mirror: None,
|
||||||
|
})
|
||||||
|
.packages
|
||||||
|
.extend(packages.iter().cloned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::warn!("No package manager detected, cannot resolve UPM system dependencies");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DepsUser stage
|
if !system_config.is_empty() && stage <= PrebakeStage::DepsSystem {
|
||||||
if stage <= PrebakeStage::DepsUser {
|
log::info!("Running PBStage: DepsSystem");
|
||||||
|
prebake_drop_privilege(PrebakeStage::DepsSystem, dropafter, target_user, &mut ctx)?;
|
||||||
|
ctx.task_id = format!("{}-{}-depssystem", cli.pipeline, cli.build_id);
|
||||||
|
stage::depssystem::depssystem(&system_config, &ctx, None).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(user) = dependencies.user
|
||||||
|
&& stage <= PrebakeStage::DepsUser
|
||||||
|
{
|
||||||
log::info!("Running PBStage: DepsUser");
|
log::info!("Running PBStage: DepsUser");
|
||||||
prebake_drop_privilege(PrebakeStage::DepsUser, dropafter, target_user, &mut ctx)?;
|
prebake_drop_privilege(PrebakeStage::DepsUser, dropafter, target_user, &mut ctx)?;
|
||||||
ctx.task_id = format!("{}-{}-depsuser", cli.pipeline, cli.build_id);
|
ctx.task_id = format!("{}-{}-depsuser", cli.pipeline, cli.build_id);
|
||||||
if let Some(user) = dependencies.user {
|
stage::depsuser::depsuser(&user, &ctx, None, &endpoint).await?;
|
||||||
stage::depsuser::depsuser(&user, &ctx, None).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use crate::types::builderconfig::{
|
|||||||
use crate::types::cache::{CacheDirectory, CacheStrategy};
|
use crate::types::cache::{CacheDirectory, CacheStrategy};
|
||||||
use crate::types::command::CustomCommand;
|
use crate::types::command::CustomCommand;
|
||||||
use crate::types::memsize::MemSize;
|
use crate::types::memsize::MemSize;
|
||||||
|
use crate::types::repology::RepologyEndpoint;
|
||||||
|
|
||||||
pub const VERSION_REQUIREMENT: &str = "^1.0";
|
pub const VERSION_REQUIREMENT: &str = "^1.0";
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ pub struct PrebakeConfig {
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct Bootstrap {
|
pub struct Bootstrap {
|
||||||
#[serde(default="default_bootstrap_user")]
|
#[serde(default = "default_bootstrap_user")]
|
||||||
pub user: String,
|
pub user: String,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -66,10 +67,10 @@ impl Default for Bootstrap {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct Workspace {
|
pub struct Workspace {
|
||||||
#[serde(default="default_workspace")]
|
#[serde(default = "default_workspace")]
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
|
||||||
#[serde(default="default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub fallback: bool,
|
pub fallback: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,6 +231,14 @@ pub struct Dependencies {
|
|||||||
pub system: Option<HashMap<String, SystemDependency>>,
|
pub system: Option<HashMap<String, SystemDependency>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub user: Option<HashMap<String, Value>>,
|
pub user: Option<HashMap<String, Value>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub config: Option<DependenciesConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
|
pub struct DependenciesConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
pub repology_endpoint: RepologyEndpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
|||||||
@@ -182,14 +182,13 @@ pub fn prebake_drop_privilege(
|
|||||||
if stage_current > stage_expected {
|
if stage_current > stage_expected {
|
||||||
let current_uid = unsafe { libc::geteuid() };
|
let current_uid = unsafe { libc::geteuid() };
|
||||||
|
|
||||||
let target_uid = lookup_uid_by_name(user).map_err(|e| {
|
let target_uid = lookup_uid_by_name(user).inspect_err(|_e| {
|
||||||
log::error!(
|
log::error!(
|
||||||
"Cannot drop privileges: target user '{}' does not exist at stage {}",
|
"Cannot drop privileges: target user '{}' does not exist at stage {}",
|
||||||
user,
|
user,
|
||||||
stage_current
|
stage_current
|
||||||
);
|
);
|
||||||
log::error!("Aborting pipeline.");
|
log::error!("Aborting pipeline.");
|
||||||
e
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if current_uid == target_uid {
|
if current_uid == target_uid {
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ pub mod hook;
|
|||||||
pub mod depssystem;
|
pub mod depssystem;
|
||||||
pub mod depsuser;
|
pub mod depsuser;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||||
pub enum PrebakeStage {
|
pub enum PrebakeStage {
|
||||||
Init,
|
Init,
|
||||||
Bootstrap,
|
Bootstrap,
|
||||||
EarlyHook,
|
EarlyHook,
|
||||||
|
#[default]
|
||||||
DepsSystem,
|
DepsSystem,
|
||||||
DepsUser,
|
DepsUser,
|
||||||
LateHook,
|
LateHook,
|
||||||
@@ -50,9 +51,3 @@ impl FromStr for PrebakeStage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PrebakeStage {
|
|
||||||
fn default() -> Self {
|
|
||||||
PrebakeStage::DepsSystem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
//! and the target operating system, supporting multiple package managers.
|
//! and the target operating system, supporting multiple package managers.
|
||||||
|
|
||||||
use crate::ExecutionContext;
|
use crate::ExecutionContext;
|
||||||
|
use crate::constant::BOOTSTRAP_SCRIPT_PATH;
|
||||||
use crate::engine::{Engine, EventSender, pm};
|
use crate::engine::{Engine, EventSender, pm};
|
||||||
use crate::error::BootstrapError;
|
use crate::error::BootstrapError;
|
||||||
use crate::prebake::config::Bootstrap;
|
use crate::prebake::config::Bootstrap;
|
||||||
use crate::prebake::config::PrebakeConfig;
|
use crate::prebake::config::PrebakeConfig;
|
||||||
use crate::constant::BOOTSTRAP_SCRIPT_PATH;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -54,7 +54,7 @@ pub async fn bootstrap(
|
|||||||
let bootstrap_config = config.bootstrap.clone().unwrap_or_default();
|
let bootstrap_config = config.bootstrap.clone().unwrap_or_default();
|
||||||
|
|
||||||
if bootstrap_config.custom.is_none() {
|
if bootstrap_config.custom.is_none() {
|
||||||
let script = generate_bootstrap(&bootstrap_config, &config, &osinfo)?;
|
let script = generate_bootstrap(&bootstrap_config, config, osinfo)?;
|
||||||
if ctx.dry_run {
|
if ctx.dry_run {
|
||||||
log::info!("[DRY_RUN] Generated bootstrap script:\n{}", script);
|
log::info!("[DRY_RUN] Generated bootstrap script:\n{}", script);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -74,9 +74,9 @@ pub async fn bootstrap(
|
|||||||
}
|
}
|
||||||
|
|
||||||
engine
|
engine
|
||||||
.execute_script(&bootstrap_path, ctx, &event_tx)
|
.execute_script(&bootstrap_path, ctx, event_tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BootstrapError::ExecutionError(e))?;
|
.map_err(BootstrapError::ExecutionError)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ pub async fn bootstrap(
|
|||||||
fn generate_bootstrap(
|
fn generate_bootstrap(
|
||||||
bootstrap_config: &Bootstrap,
|
bootstrap_config: &Bootstrap,
|
||||||
config: &PrebakeConfig,
|
config: &PrebakeConfig,
|
||||||
osinfo: &os_info::Info
|
osinfo: &os_info::Info,
|
||||||
) -> Result<String, BootstrapError> {
|
) -> Result<String, BootstrapError> {
|
||||||
let mut script = String::new();
|
let mut script = String::new();
|
||||||
// Generate header
|
// Generate header
|
||||||
@@ -207,7 +207,10 @@ fn generate_bootstrap(
|
|||||||
///
|
///
|
||||||
/// Returns `Some(Box<dyn PackageManager>)` if a suitable package manager is found,
|
/// Returns `Some(Box<dyn PackageManager>)` if a suitable package manager is found,
|
||||||
/// or `None` if no package manager is configured or detectable.
|
/// or `None` if no package manager is configured or detectable.
|
||||||
fn get_package_manager(config: &PrebakeConfig, osinfo: &os_info::Info) -> Option<Box<dyn pm::PackageManager>> {
|
fn get_package_manager(
|
||||||
|
config: &PrebakeConfig,
|
||||||
|
osinfo: &os_info::Info,
|
||||||
|
) -> Option<Box<dyn pm::PackageManager>> {
|
||||||
config
|
config
|
||||||
.dependencies
|
.dependencies
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -225,14 +228,14 @@ fn get_package_manager(config: &PrebakeConfig, osinfo: &os_info::Info) -> Option
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((name, _)) = sysdeps.iter().next() {
|
if let Some((name, _)) = sysdeps.iter().next()
|
||||||
if let Some(pm) = pm::select(name) {
|
&& let Some(pm) = pm::select(name)
|
||||||
log::warn!(
|
{
|
||||||
"Using package manager: {} (first configured, not detected)",
|
log::warn!(
|
||||||
name
|
"Using package manager: {} (first configured, not detected)",
|
||||||
);
|
name
|
||||||
return Some(pm);
|
);
|
||||||
}
|
return Some(pm);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::warn!("No valid package manager found in configuration");
|
log::warn!("No valid package manager found in configuration");
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ pub async fn depssystem(
|
|||||||
let cmd = package_manager.change_mirror(mirror, Some(&osinfo));
|
let cmd = package_manager.change_mirror(mirror, Some(&osinfo));
|
||||||
log::debug!("Executing: {:?}", &cmd);
|
log::debug!("Executing: {:?}", &cmd);
|
||||||
engine
|
engine
|
||||||
.execute(cmd, &ctx, &event_tx)
|
.execute(cmd, ctx, &event_tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DependencyError::StageFailed {
|
.map_err(|e| DependencyError::StageFailed {
|
||||||
stage: "MirrorChange",
|
stage: "MirrorChange",
|
||||||
@@ -59,7 +59,7 @@ pub async fn depssystem(
|
|||||||
let cmds = package_manager.add_repository(repos, Some(&osinfo));
|
let cmds = package_manager.add_repository(repos, Some(&osinfo));
|
||||||
for cmd in cmds {
|
for cmd in cmds {
|
||||||
log::debug!("Executing: {:?}", &cmd);
|
log::debug!("Executing: {:?}", &cmd);
|
||||||
engine.execute(cmd, &ctx, &event_tx).await.map_err(|e| {
|
engine.execute(cmd, ctx, &event_tx).await.map_err(|e| {
|
||||||
DependencyError::StageFailed {
|
DependencyError::StageFailed {
|
||||||
stage: "CustomRepoAdd",
|
stage: "CustomRepoAdd",
|
||||||
source: e,
|
source: e,
|
||||||
@@ -74,7 +74,7 @@ pub async fn depssystem(
|
|||||||
let cmd = package_manager.update();
|
let cmd = package_manager.update();
|
||||||
log::debug!("Executing: {:?}", &cmd);
|
log::debug!("Executing: {:?}", &cmd);
|
||||||
engine
|
engine
|
||||||
.execute(cmd, &ctx, &event_tx)
|
.execute(cmd, ctx, &event_tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DependencyError::StageFailed {
|
.map_err(|e| DependencyError::StageFailed {
|
||||||
stage: "UpdateIndex",
|
stage: "UpdateIndex",
|
||||||
@@ -86,7 +86,7 @@ pub async fn depssystem(
|
|||||||
if !pm_config.packages.is_empty() {
|
if !pm_config.packages.is_empty() {
|
||||||
log::info!("Installing {} packages", pm_config.packages.len());
|
log::info!("Installing {} packages", pm_config.packages.len());
|
||||||
engine
|
engine
|
||||||
.install_dependency(&pm_config.packages, &*package_manager, &ctx, &event_tx)
|
.install_dependency(&pm_config.packages, &*package_manager, ctx, &event_tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DependencyError::StageFailed {
|
.map_err(|e| DependencyError::StageFailed {
|
||||||
stage: "InstallPackage",
|
stage: "InstallPackage",
|
||||||
|
|||||||
@@ -3,24 +3,73 @@ use serde_yaml::Value;
|
|||||||
use crate::ExecutionContext;
|
use crate::ExecutionContext;
|
||||||
use crate::engine::{EventSender, upm};
|
use crate::engine::{EventSender, upm};
|
||||||
use crate::error::DependencyError;
|
use crate::error::DependencyError;
|
||||||
|
use crate::types::repology::RepologyEndpoint;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CollectedUPMdeps {
|
||||||
|
pub deps: Vec<(String, upm::UPMSysDeps)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CollectedUPMdeps {
|
||||||
|
pub fn extend(&mut self, other: Self) {
|
||||||
|
self.deps.extend(other.deps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn collect_upm_sysdeps(
|
||||||
|
config: &HashMap<String, Value>,
|
||||||
|
_ctx: &ExecutionContext,
|
||||||
|
endpoint: &RepologyEndpoint,
|
||||||
|
) -> Result<CollectedUPMdeps, DependencyError> {
|
||||||
|
let mut collected = CollectedUPMdeps::default();
|
||||||
|
|
||||||
|
if *endpoint == RepologyEndpoint::Disabled {
|
||||||
|
return Ok(collected);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (name, cfg) in config {
|
||||||
|
let package_manager = match upm::select(name) {
|
||||||
|
Some(pm) => pm,
|
||||||
|
None => {
|
||||||
|
log::warn!("Unknown user package manager: {}, skipping sysdep collection", name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let sys_deps = package_manager.system_dependency(cfg);
|
||||||
|
if !sys_deps.packages.is_empty() {
|
||||||
|
log::info!(
|
||||||
|
"UPM {} system deps: {:?} (mapped: {})",
|
||||||
|
name,
|
||||||
|
sys_deps.packages,
|
||||||
|
sys_deps.mapped
|
||||||
|
);
|
||||||
|
collected.deps.push((name.clone(), sys_deps));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(collected)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn depsuser(
|
pub async fn depsuser(
|
||||||
config: &HashMap<String, Value>,
|
config: &HashMap<String, Value>,
|
||||||
ctx: &ExecutionContext,
|
ctx: &ExecutionContext,
|
||||||
event_tx: EventSender,
|
event_tx: EventSender,
|
||||||
|
_endpoint: &RepologyEndpoint,
|
||||||
) -> Result<(), DependencyError> {
|
) -> Result<(), DependencyError> {
|
||||||
for (name, cfg) in config {
|
for (name, cfg) in config {
|
||||||
let package_manager = match upm::select(name){
|
let package_manager = match upm::select(name) {
|
||||||
Some(pm) => pm,
|
Some(pm) => pm,
|
||||||
None => {
|
None => {
|
||||||
log::error!("Unknown user package manager: {}", name);
|
log::error!("Unknown user package manager: {}", name);
|
||||||
return Err(DependencyError::UnknownManager(name.to_string()));
|
return Err(DependencyError::UnknownManager(name.to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
log::info!("Running user package manager: {}", name);
|
|
||||||
|
|
||||||
|
log::info!("Running user package manager: {}", name);
|
||||||
package_manager.main(cfg, ctx, &event_tx).await?;
|
package_manager.main(cfg, ctx, &event_tx).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ pub async fn hook(
|
|||||||
};
|
};
|
||||||
log::info!("Executing hook #{}: {}.", index, hook_cmd.name);
|
log::info!("Executing hook #{}: {}.", index, hook_cmd.name);
|
||||||
let result = engine
|
let result = engine
|
||||||
.execute_command_with_delta(&hook_cmd.command, &ctx, &event_tx, &deltactx)
|
.execute_command_with_delta(&hook_cmd.command, ctx, &event_tx, &deltactx)
|
||||||
.await;
|
.await;
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
log::error!("Hook {} failed: {:?}", index, e);
|
log::error!("Hook {} failed: {:?}", index, e);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod memsize;
|
|
||||||
pub mod architecture;
|
pub mod architecture;
|
||||||
pub mod builderconfig;
|
pub mod builderconfig;
|
||||||
pub mod command;
|
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
|
pub mod command;
|
||||||
|
pub mod memsize;
|
||||||
|
pub mod repology;
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
|
||||||
pub enum Architecture {
|
pub enum Architecture {
|
||||||
|
#[default]
|
||||||
NoArch,
|
NoArch,
|
||||||
X86_64,
|
X86_64,
|
||||||
Amd64, // Same as X86_64, normalized when processing
|
Amd64, // Same as X86_64, normalized when processing
|
||||||
Arm64,
|
Arm64,
|
||||||
Aarch64, // Same as Arm64, normalized when processing
|
Aarch64, // Same as Arm64, normalized when processing
|
||||||
Riscv64,
|
Riscv64,
|
||||||
Loongarch64,
|
Loongarch64,
|
||||||
Loong64, // Same as Loongarch64, normalized when processing, discarding old world Loongarch for simplicity
|
Loong64, // Same as Loongarch64, normalized when processing, discarding old world Loongarch for simplicity
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Architecture {
|
impl Architecture {
|
||||||
@@ -50,9 +51,3 @@ impl FromStr for Architecture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Architecture {
|
|
||||||
fn default() -> Self {
|
|
||||||
Architecture::NoArch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct CustomCommand {
|
pub struct CustomCommand {
|
||||||
#[serde(default="default_name")]
|
#[serde(default = "default_name")]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub command: String,
|
pub command: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ impl MemSize {
|
|||||||
pub const fn from_gib(gib: u64) -> Self {
|
pub const fn from_gib(gib: u64) -> Self {
|
||||||
Self(gib * 1024 * 1024 * 1024)
|
Self(gib * 1024 * 1024 * 1024)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for MemSize {
|
impl<'de> Deserialize<'de> for MemSize {
|
||||||
@@ -129,14 +128,19 @@ pub fn parse_mem_size(s: &str) -> Result<MemSize, String> {
|
|||||||
let (num_str, unit_raw) = split_number_and_unit(s)?;
|
let (num_str, unit_raw) = split_number_and_unit(s)?;
|
||||||
|
|
||||||
// Parse the numeric part
|
// Parse the numeric part
|
||||||
let num: f64 = num_str.parse().map_err(|e: ParseFloatError| e.to_string())?;
|
let num: f64 = num_str
|
||||||
|
.parse()
|
||||||
|
.map_err(|e: ParseFloatError| e.to_string())?;
|
||||||
|
|
||||||
// Normalize unit: remove trailing 'B' if present, but preserve case for error detection
|
// Normalize unit: remove trailing 'B' if present, but preserve case for error detection
|
||||||
let unit = if unit_raw.ends_with('B') || unit_raw.ends_with('b') {
|
let unit = if unit_raw.ends_with('B') || unit_raw.ends_with('b') {
|
||||||
let without_b = &unit_raw[..unit_raw.len() - 1];
|
let without_b = &unit_raw[..unit_raw.len() - 1];
|
||||||
// Check if the original unit ends with lowercase 'b' (bits) before stripping
|
// Check if the original unit ends with lowercase 'b' (bits) before stripping
|
||||||
if unit_raw.ends_with('b') && !unit_raw.ends_with('B') {
|
if unit_raw.ends_with('b') && !unit_raw.ends_with('B') {
|
||||||
return Err(format!("bit unit 'b' is not allowed, use 'B' for bytes: {}", unit_raw));
|
return Err(format!(
|
||||||
|
"bit unit 'b' is not allowed, use 'B' for bytes: {}",
|
||||||
|
unit_raw
|
||||||
|
));
|
||||||
}
|
}
|
||||||
without_b
|
without_b
|
||||||
} else {
|
} else {
|
||||||
@@ -145,7 +149,7 @@ pub fn parse_mem_size(s: &str) -> Result<MemSize, String> {
|
|||||||
|
|
||||||
// Determine multiplier based on unit
|
// Determine multiplier based on unit
|
||||||
let multiplier = match unit {
|
let multiplier = match unit {
|
||||||
"" => 1, // bytes (no unit)
|
"" => 1, // bytes (no unit)
|
||||||
"k" | "K" => 1000,
|
"k" | "K" => 1000,
|
||||||
"m" | "M" => 1000 * 1000,
|
"m" | "M" => 1000 * 1000,
|
||||||
"g" | "G" => 1000 * 1000 * 1000,
|
"g" | "G" => 1000 * 1000 * 1000,
|
||||||
@@ -210,12 +214,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_sizes() {
|
fn test_invalid_sizes() {
|
||||||
assert!(parse_mem_size("780Mb").is_err()); // bits not allowed
|
assert!(parse_mem_size("780Mb").is_err()); // bits not allowed
|
||||||
assert!(parse_mem_size("2BB").is_err()); // malformed
|
assert!(parse_mem_size("2BB").is_err()); // malformed
|
||||||
assert!(parse_mem_size("7KiKiB").is_err()); // malformed
|
assert!(parse_mem_size("7KiKiB").is_err()); // malformed
|
||||||
assert!(parse_mem_size("a_faulty_value").is_err());
|
assert!(parse_mem_size("a_faulty_value").is_err());
|
||||||
assert!(parse_mem_size("").is_err());
|
assert!(parse_mem_size("").is_err());
|
||||||
assert!(parse_mem_size("GB").is_err()); // missing number
|
assert!(parse_mem_size("GB").is_err()); // missing number
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||||
|
pub enum RepologyEndpoint {
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
Disabled,
|
||||||
|
None,
|
||||||
|
Local,
|
||||||
|
Remote,
|
||||||
|
Server,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::Deserialize<'de> for RepologyEndpoint {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
use serde::de::{self, Visitor};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
struct RepologyEndpointVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for RepologyEndpointVisitor {
|
||||||
|
type Value = RepologyEndpoint;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("disabled, none, local, remote, server, or default")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
match v {
|
||||||
|
"disabled" => Ok(RepologyEndpoint::Disabled),
|
||||||
|
"none" => Ok(RepologyEndpoint::None),
|
||||||
|
"local" => Ok(RepologyEndpoint::Local),
|
||||||
|
"remote" => Ok(RepologyEndpoint::Remote),
|
||||||
|
"server" => Ok(RepologyEndpoint::Server),
|
||||||
|
"default" | "" => Ok(RepologyEndpoint::Default),
|
||||||
|
_ => Err(de::Error::unknown_variant(
|
||||||
|
v,
|
||||||
|
&["disabled", "none", "local", "remote", "server", "default"],
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_str(RepologyEndpointVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for RepologyEndpoint {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
RepologyEndpoint::Default => serializer.serialize_str("default"),
|
||||||
|
RepologyEndpoint::Disabled => serializer.serialize_str("disabled"),
|
||||||
|
RepologyEndpoint::None => serializer.serialize_str("none"),
|
||||||
|
RepologyEndpoint::Local => serializer.serialize_str("local"),
|
||||||
|
RepologyEndpoint::Remote => serializer.serialize_str("remote"),
|
||||||
|
RepologyEndpoint::Server => serializer.serialize_str("server"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# workshop-llm-detector
|
||||||
|
|
||||||
|
## OVERVIEW
|
||||||
|
|
||||||
|
LLM-powered repository analyzer that generates pipeline configurations by combining AI analysis with language-specific cookbook templates.
|
||||||
|
|
||||||
|
## STRUCTURE
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── llm/ # LLM client abstraction
|
||||||
|
│ ├── client.rs # LLMClient trait (async_trait)
|
||||||
|
│ ├── ollama.rs # OllamaClient implementation
|
||||||
|
│ └── openai.rs # OpenAIClient implementation
|
||||||
|
├── cookbook/ # Language-specific build configs
|
||||||
|
│ └── mod.rs # CookbookManager + Cookbook struct
|
||||||
|
├── llm_output.rs # LLMOutput parsed response struct
|
||||||
|
└── lib.rs # CICDLLMHelper entry point
|
||||||
|
```
|
||||||
|
|
||||||
|
## WHERE TO LOOK
|
||||||
|
|
||||||
|
| Symbol | Type | Location | Role |
|
||||||
|
|--------|------|----------|------|
|
||||||
|
| CICDLLMHelper | struct | lib.rs:16 | Main entry point; analyze_repository() orchestrates LLM + cookbook merge |
|
||||||
|
| LLMClient | trait | llm/client.rs:5 | Async trait for generate(prompt) -> Result<String> |
|
||||||
|
| OllamaClient | struct | llm/ollama.rs:8 | Ollama local LLM adapter |
|
||||||
|
| OpenAIClient | struct | llm/openai.rs:8 | OpenAI API adapter |
|
||||||
|
| CookbookManager | struct | cookbook/mod.rs:26 | Loads .cookbook YAML files; get_cookbook(project_type) |
|
||||||
|
| Cookbook | struct | cookbook/mod.rs:7 | Language-specific deps, env, artifacts template |
|
||||||
|
| LLMOutput | struct | llm_output.rs:5 | Parsed YAML from LLM (project_type, dependencies, build_steps, artifacts) |
|
||||||
|
| AnalysisResult | struct | lib.rs:157 | Merged LLM + cookbook output |
|
||||||
|
|
||||||
|
## CONVENTIONS
|
||||||
|
|
||||||
|
- **Async trait**: Uses `async_trait::async_trait` macro (not native async fn in traits)
|
||||||
|
- **Cookbook files**: `*.cookbook.yaml` in cookbook directory; maps project_type -> Cookbook
|
||||||
|
- **LLM prompt**: Builds YAML requiring project_type, dependencies, build_steps, artifacts, environment fields
|
||||||
|
- **Output templates**: Writes prebake.tmpl.yaml, finalize.tmpl.yaml, build.sh via serde_yaml
|
||||||
|
- **Error handling**: thiserror for CookbookError, anyhow for runtime errors
|
||||||
Generated
-375
@@ -1,375 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "1.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstream"
|
|
||||||
version = "0.6.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"anstyle-parse",
|
|
||||||
"anstyle-query",
|
|
||||||
"anstyle-wincon",
|
|
||||||
"colorchoice",
|
|
||||||
"is_terminal_polyfill",
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle"
|
|
||||||
version = "1.0.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-parse"
|
|
||||||
version = "0.2.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
|
||||||
dependencies = [
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-query"
|
|
||||||
version = "1.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-wincon"
|
|
||||||
version = "3.0.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"once_cell_polyfill",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorchoice"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "env_filter"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"regex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "env_logger"
|
|
||||||
version = "0.11.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
|
||||||
dependencies = [
|
|
||||||
"anstream",
|
|
||||||
"anstyle",
|
|
||||||
"env_filter",
|
|
||||||
"jiff",
|
|
||||||
"log",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "equivalent"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.16.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indexmap"
|
|
||||||
version = "2.12.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
|
||||||
dependencies = [
|
|
||||||
"equivalent",
|
|
||||||
"hashbrown",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is_terminal_polyfill"
|
|
||||||
version = "1.70.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jiff"
|
|
||||||
version = "0.2.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35"
|
|
||||||
dependencies = [
|
|
||||||
"jiff-static",
|
|
||||||
"log",
|
|
||||||
"portable-atomic",
|
|
||||||
"portable-atomic-util",
|
|
||||||
"serde_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jiff-static"
|
|
||||||
version = "0.2.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazy_static"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.4.28"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.7.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell_polyfill"
|
|
||||||
version = "1.70.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "portable-atomic"
|
|
||||||
version = "1.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "portable-atomic-util"
|
|
||||||
version = "0.2.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
|
||||||
dependencies = [
|
|
||||||
"portable-atomic",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.103"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.42"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "1.12.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-automata",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-automata"
|
|
||||||
version = "0.4.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.8.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ryu"
|
|
||||||
version = "1.0.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
||||||
dependencies = [
|
|
||||||
"serde_core",
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_core"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.145"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_yaml"
|
|
||||||
version = "0.9.34+deprecated"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"itoa",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
"unsafe-libyaml",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.110"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "2.0.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "2.0.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unsafe-libyaml"
|
|
||||||
version = "0.2.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf8parse"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-link"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.61.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "workshop-pipeline"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"env_logger",
|
|
||||||
"lazy_static",
|
|
||||||
"log",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_yaml",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "workshop-pipeline"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
lazy_static = "1.5.0"
|
|
||||||
log = "0.4.28"
|
|
||||||
regex = "1.12.2"
|
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
|
||||||
serde_json = "1.0.145"
|
|
||||||
serde_yaml = "0.9.34"
|
|
||||||
thiserror = "2.0.17"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
env_logger = "0.11.8"
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# .workshop/bake.sh
|
|
||||||
# 构建阶段脚本 - 使用标准的bash语法
|
|
||||||
|
|
||||||
set -euo pipefail # 严格模式:错误退出、未定义变量检查、管道错误检查
|
|
||||||
|
|
||||||
# 切换到工作目录
|
|
||||||
cd "$WORKSPACE_DIR"
|
|
||||||
|
|
||||||
# 创建必要的目录
|
|
||||||
mkdir -p target
|
|
||||||
mkdir -p "$CARGO_REGISTRY"
|
|
||||||
mkdir -p "$CARGO_GIT"
|
|
||||||
|
|
||||||
# 配置Cargo使用缓存目录
|
|
||||||
export CARGO_HOME="$BUILD_CACHE_DIR/cargo"
|
|
||||||
export CARGO_TARGET_DIR="$WORKSPACE_DIR/target"
|
|
||||||
|
|
||||||
# 构建前检查
|
|
||||||
log "Starting build process"
|
|
||||||
log "Build ID: $BUILD_ID"
|
|
||||||
log "Commit: $COMMIT_SHA"
|
|
||||||
log "Branch: $BRANCH_NAME"
|
|
||||||
log "Rust version: $RUST_VERSION"
|
|
||||||
|
|
||||||
# 检查必要的工具
|
|
||||||
check_requirements() {
|
|
||||||
log "Checking build requirements..."
|
|
||||||
|
|
||||||
if ! command -v cargo &> /dev/null; then
|
|
||||||
log_error "Cargo not found. Please ensure Rust is installed."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v git &> /dev/null; then
|
|
||||||
log_error "Git not found. Please install git."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "All requirements satisfied"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 代码质量检查
|
|
||||||
run_lints() {
|
|
||||||
log "Running code quality checks..."
|
|
||||||
|
|
||||||
# Rustfmt检查代码格式
|
|
||||||
if command -v rustfmt &> /dev/null; then
|
|
||||||
log "Checking code formatting with rustfmt..."
|
|
||||||
cargo fmt -- --check || {
|
|
||||||
log_warn "Code formatting issues found"
|
|
||||||
# 非致命错误,继续构建
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clippy静态分析
|
|
||||||
log "Running Clippy..."
|
|
||||||
cargo clippy -- -D warnings || {
|
|
||||||
log_warn "Clippy warnings found"
|
|
||||||
# 非致命错误,继续构建
|
|
||||||
}
|
|
||||||
|
|
||||||
# 安全检查
|
|
||||||
if command -v cargo-audit &> /dev/null; then
|
|
||||||
log "Checking for security vulnerabilities..."
|
|
||||||
cargo audit || {
|
|
||||||
log_warn "Security vulnerabilities found"
|
|
||||||
# 非致命错误,继续构建
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 运行测试
|
|
||||||
run_tests() {
|
|
||||||
log "Running tests..."
|
|
||||||
|
|
||||||
# 单元测试
|
|
||||||
cargo test --lib --bins --tests || {
|
|
||||||
log_error "Unit tests failed"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# 集成测试(如果存在)
|
|
||||||
if [ -d "tests" ] && [ -n "$(find tests -name '*.rs' -print -quit)" ]; then
|
|
||||||
cargo test --tests || {
|
|
||||||
log_error "Integration tests failed"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 文档测试
|
|
||||||
cargo test --doc || {
|
|
||||||
log_warn "Documentation tests failed"
|
|
||||||
# 文档测试失败不阻断构建
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 构建项目
|
|
||||||
build_project() {
|
|
||||||
log "Building project..."
|
|
||||||
|
|
||||||
case "$BUILD_TYPE" in
|
|
||||||
debug)
|
|
||||||
log "Building in debug mode"
|
|
||||||
cargo build
|
|
||||||
;;
|
|
||||||
release)
|
|
||||||
log "Building in release mode"
|
|
||||||
cargo build --release
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
log_error "Unknown build type: $BUILD_TYPE"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# 检查构建是否成功
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
log "Build completed successfully"
|
|
||||||
else
|
|
||||||
log_error "Build failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 生成文档
|
|
||||||
generate_docs() {
|
|
||||||
log "Generating documentation..."
|
|
||||||
|
|
||||||
if [ "$BUILD_TYPE" = "release" ]; then
|
|
||||||
cargo doc --no-deps --release || {
|
|
||||||
log_warn "Documentation generation failed"
|
|
||||||
# 文档生成失败不阻断构建
|
|
||||||
}
|
|
||||||
else
|
|
||||||
cargo doc --no-deps || {
|
|
||||||
log_warn "Documentation generation failed"
|
|
||||||
# 文档生成失败不阻断构建
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 创建构建产物清单
|
|
||||||
create_artifact_manifest() {
|
|
||||||
log "Creating artifact manifest..."
|
|
||||||
|
|
||||||
local manifest_file="$WORKSPACE_DIR/artifacts.json"
|
|
||||||
|
|
||||||
cat > "$manifest_file" << EOF
|
|
||||||
{
|
|
||||||
"build_id": "$BUILD_ID",
|
|
||||||
"commit_sha": "$COMMIT_SHA",
|
|
||||||
"branch": "$BRANCH_NAME",
|
|
||||||
"build_type": "$BUILD_TYPE",
|
|
||||||
"timestamp": "$(date -Iseconds)",
|
|
||||||
"artifacts": [
|
|
||||||
{
|
|
||||||
"name": "binary",
|
|
||||||
"path": "target/$BUILD_TYPE/$(basename "$WORKSPACE_DIR")",
|
|
||||||
"type": "executable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "documentation",
|
|
||||||
"path": "target/doc",
|
|
||||||
"type": "directory"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
log "Artifact manifest created: $manifest_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 构建后清理
|
|
||||||
cleanup() {
|
|
||||||
log "Performing build cleanup..."
|
|
||||||
|
|
||||||
# 清理临时文件
|
|
||||||
find . -name "*.tmp" -delete
|
|
||||||
find . -name "*.log" -delete
|
|
||||||
|
|
||||||
# 保留构建缓存
|
|
||||||
log "Build cache preserved in: $BUILD_CACHE_DIR"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 主构建流程
|
|
||||||
main() {
|
|
||||||
log "Starting bake phase"
|
|
||||||
|
|
||||||
check_requirements
|
|
||||||
run_lints
|
|
||||||
run_tests
|
|
||||||
build_project
|
|
||||||
generate_docs
|
|
||||||
create_artifact_manifest
|
|
||||||
cleanup
|
|
||||||
|
|
||||||
log "Bake phase completed successfully"
|
|
||||||
|
|
||||||
# 输出构建摘要
|
|
||||||
echo "=========================================="
|
|
||||||
log_info "Build Summary"
|
|
||||||
echo "------------------------------------------"
|
|
||||||
log_info "Build ID: $BUILD_ID"
|
|
||||||
log_info "Status: SUCCESS"
|
|
||||||
log_info "Artifacts:"
|
|
||||||
log_info " - Binary: target/$BUILD_TYPE/$(basename "$WORKSPACE_DIR")"
|
|
||||||
log_info " - Documentation: target/doc/"
|
|
||||||
log_info " - Manifest: artifacts.json"
|
|
||||||
echo "=========================================="
|
|
||||||
}
|
|
||||||
|
|
||||||
# 信号处理
|
|
||||||
trap 'log_error "Build interrupted"; exit 130' INT TERM
|
|
||||||
|
|
||||||
# 执行主函数
|
|
||||||
main "$@"
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Color for output
|
|
||||||
# 颜色定义用于输出
|
|
||||||
readonly RED='\033[0;31m'
|
|
||||||
readonly GREEN='\033[0;32m'
|
|
||||||
readonly YELLOW='\033[1;33m'
|
|
||||||
readonly BLUE='\033[0;34m'
|
|
||||||
readonly NC='\033[0m' # No color, 无颜色
|
|
||||||
|
|
||||||
# logging function
|
|
||||||
# 日志辅助函数
|
|
||||||
bake_log() {
|
|
||||||
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $*${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
bake_log_warn() {
|
|
||||||
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARN: $*${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
bake_log_error() {
|
|
||||||
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
bake_log_info() {
|
|
||||||
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $*${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
{{ main }}
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
# finalize.yaml.tmpl
|
|
||||||
# 版本号
|
|
||||||
version: "1.0"
|
|
||||||
|
|
||||||
# 产物定义
|
|
||||||
artifacts:
|
|
||||||
# 主要构建产物
|
|
||||||
primary:
|
|
||||||
- name: "application-binary"
|
|
||||||
type: "executable"
|
|
||||||
path: "target/release/{{.project_name}}"
|
|
||||||
platform: "linux/x86_64"
|
|
||||||
permissions: "755"
|
|
||||||
description: "Main application binary"
|
|
||||||
|
|
||||||
- name: "static-assets"
|
|
||||||
type: "directory"
|
|
||||||
path: "static"
|
|
||||||
include_patterns:
|
|
||||||
- "**/*.css"
|
|
||||||
- "**/*.js"
|
|
||||||
- "**/*.png"
|
|
||||||
- "**/*.jpg"
|
|
||||||
exclude_patterns:
|
|
||||||
- "**/*.test.*"
|
|
||||||
- "**/node_modules/**"
|
|
||||||
|
|
||||||
# 辅助产物
|
|
||||||
secondary:
|
|
||||||
- name: "documentation"
|
|
||||||
type: "directory"
|
|
||||||
path: "target/doc"
|
|
||||||
compress: true
|
|
||||||
format: "tar.gz"
|
|
||||||
|
|
||||||
- name: "build-logs"
|
|
||||||
type: "file"
|
|
||||||
path: "build.log"
|
|
||||||
retention_days: 30
|
|
||||||
|
|
||||||
- name: "test-results"
|
|
||||||
type: "directory"
|
|
||||||
path: "test-results"
|
|
||||||
format: "junit" # 支持junit, tap, custom
|
|
||||||
|
|
||||||
# 打包配置
|
|
||||||
packaging:
|
|
||||||
# 主要包配置
|
|
||||||
primary_package:
|
|
||||||
format: "tar.gz" # tar.gz, zip, docker, oci
|
|
||||||
output: "{{.project_name}}-{{.version}}-{{.platform}}.{{.format}}"
|
|
||||||
include:
|
|
||||||
- "artifacts/application-binary"
|
|
||||||
- "artifacts/static-assets"
|
|
||||||
- "LICENSE"
|
|
||||||
- "README.md"
|
|
||||||
exclude:
|
|
||||||
- "**/*.log"
|
|
||||||
- "**/*.tmp"
|
|
||||||
|
|
||||||
# Docker镜像配置
|
|
||||||
docker:
|
|
||||||
enabled: true
|
|
||||||
registry: "registry.example.com"
|
|
||||||
repository: "{{.project_name}}"
|
|
||||||
tags:
|
|
||||||
- "{{.version}}"
|
|
||||||
- "latest"
|
|
||||||
- "{{.branch}}"
|
|
||||||
dockerfile: "Dockerfile.final"
|
|
||||||
build_args:
|
|
||||||
- "VERSION={{.version}}"
|
|
||||||
- "BUILD_ID={{.build_id}}"
|
|
||||||
labels:
|
|
||||||
org.label-schema.version: "{{.version}}"
|
|
||||||
org.label-schema.build-date: "{{.build_date}}"
|
|
||||||
org.label-schema.vcs-ref: "{{.commit_sha}}"
|
|
||||||
|
|
||||||
# 发布配置
|
|
||||||
distribution:
|
|
||||||
# 本地存储
|
|
||||||
local:
|
|
||||||
directory: "/artifacts/{{.project_name}}/{{.version}}"
|
|
||||||
retention_days: 90
|
|
||||||
|
|
||||||
# 对象存储
|
|
||||||
s3:
|
|
||||||
enabled: true
|
|
||||||
bucket: "my-artifacts"
|
|
||||||
prefix: "{{.project_name}}/{{.version}}"
|
|
||||||
region: "us-east-1"
|
|
||||||
acl: "private"
|
|
||||||
storage_class: "STANDARD"
|
|
||||||
|
|
||||||
# 包管理器仓库
|
|
||||||
package_registries:
|
|
||||||
crates_io:
|
|
||||||
enabled: true
|
|
||||||
token: "{{env.CRATES_IO_TOKEN}}"
|
|
||||||
|
|
||||||
npm:
|
|
||||||
enabled: false
|
|
||||||
registry: "https://registry.npmjs.org/"
|
|
||||||
token: "{{env.NPM_TOKEN}}"
|
|
||||||
|
|
||||||
docker_registry:
|
|
||||||
enabled: true
|
|
||||||
registry: "registry.example.com"
|
|
||||||
username: "{{env.DOCKER_USERNAME}}"
|
|
||||||
password: "{{env.DOCKER_PASSWORD}}"
|
|
||||||
|
|
||||||
# 质量门禁
|
|
||||||
quality_gates:
|
|
||||||
# 测试覆盖率要求
|
|
||||||
|
|
||||||
# 部署配置
|
|
||||||
deployment:
|
|
||||||
# 环境定义
|
|
||||||
environments:
|
|
||||||
staging:
|
|
||||||
type: "kubernetes"
|
|
||||||
namespace: "staging"
|
|
||||||
cluster: "staging-cluster"
|
|
||||||
manifests:
|
|
||||||
- "k8s/deployment.yaml"
|
|
||||||
- "k8s/service.yaml"
|
|
||||||
health_check:
|
|
||||||
endpoint: "/health"
|
|
||||||
timeout: 30
|
|
||||||
interval: 10
|
|
||||||
|
|
||||||
production:
|
|
||||||
type: "kubernetes"
|
|
||||||
namespace: "production"
|
|
||||||
cluster: "production-cluster"
|
|
||||||
manual_approval: true
|
|
||||||
rollout_strategy: "blue-green"
|
|
||||||
manifests:
|
|
||||||
- "k8s/deployment.yaml"
|
|
||||||
|
|
||||||
# 部署策略
|
|
||||||
strategy:
|
|
||||||
type: "rolling" # rolling, blue-green, canary
|
|
||||||
max_unavailable: "25%"
|
|
||||||
max_surge: "25%"
|
|
||||||
wait_timeout: 300
|
|
||||||
|
|
||||||
# 通知配置
|
|
||||||
notifications:
|
|
||||||
# 成功通知
|
|
||||||
on_success:
|
|
||||||
- type: "slack"
|
|
||||||
webhook: "{{env.SLACK_WEBHOOK}}"
|
|
||||||
channel: "#builds"
|
|
||||||
message: "Build {{.build_id}} succeeded for {{.project_name}}"
|
|
||||||
|
|
||||||
- type: "email"
|
|
||||||
to:
|
|
||||||
- "team@example.com"
|
|
||||||
subject: "Build Success: {{.project_name}} v{{.version}}"
|
|
||||||
message: "Build {{.build_id}} succeeded for {{.project_name}} v{{.version}}"
|
|
||||||
template: "success_email.html"
|
|
||||||
|
|
||||||
# 失败通知
|
|
||||||
on_failure:
|
|
||||||
- type: "slack"
|
|
||||||
webhook: "{{env.SLACK_WEBHOOK}}"
|
|
||||||
channel: "#build-alerts"
|
|
||||||
message: "🚨 Build {{.build_id}} failed for {{.project_name}}"
|
|
||||||
|
|
||||||
- type: "pagerduty"
|
|
||||||
service_key: "{{env.PAGERDUTY_KEY}}"
|
|
||||||
message: "Build {{.build_id}} failed for {{.project_name}} v{{.version}}"
|
|
||||||
severity: "error"
|
|
||||||
|
|
||||||
# 部署通知
|
|
||||||
on_deploy:
|
|
||||||
- type: "webhook"
|
|
||||||
url: "https://api.example.com/deployments"
|
|
||||||
method: "POST"
|
|
||||||
headers:
|
|
||||||
Authorization: "Bearer {{env.DEPLOY_WEBHOOK_TOKEN}}"
|
|
||||||
message: ""
|
|
||||||
|
|
||||||
# 清理策略
|
|
||||||
cleanup:
|
|
||||||
# 构建产物清理
|
|
||||||
artifacts:
|
|
||||||
keep_latest: 10
|
|
||||||
keep_tags:
|
|
||||||
- "latest"
|
|
||||||
- "stable"
|
|
||||||
- "v*.*.*"
|
|
||||||
older_than_days: 30
|
|
||||||
|
|
||||||
# 构建环境清理
|
|
||||||
environment:
|
|
||||||
containers: true
|
|
||||||
volumes: true
|
|
||||||
networks: true
|
|
||||||
images: true
|
|
||||||
|
|
||||||
# 缓存清理
|
|
||||||
cache:
|
|
||||||
strategy: "lru"
|
|
||||||
max_size_gb: 50
|
|
||||||
ttl_days: 7
|
|
||||||
|
|
||||||
# 钩子脚本
|
|
||||||
hooks:
|
|
||||||
# 打包前钩子
|
|
||||||
pre_package:
|
|
||||||
- name: "validate-artifacts"
|
|
||||||
command: "./scripts/validate-artifacts.sh"
|
|
||||||
timeout: 60
|
|
||||||
|
|
||||||
- name: "generate-changelog"
|
|
||||||
command: "git log --oneline {{.previous_version}}..{{.current_version}} > CHANGELOG.md"
|
|
||||||
|
|
||||||
# 打包后钩子
|
|
||||||
post_package:
|
|
||||||
- name: "verify-package"
|
|
||||||
command: "./scripts/verify-package.sh {{.package_path}}"
|
|
||||||
|
|
||||||
# 发布前钩子
|
|
||||||
pre_release:
|
|
||||||
- name: "notify-stakeholders"
|
|
||||||
command: "./scripts/notify-stakeholders.sh"
|
|
||||||
|
|
||||||
# 发布后钩子
|
|
||||||
post_release:
|
|
||||||
- name: "update-dashboard"
|
|
||||||
command: "curl -X POST https://dashboard.example.com/api/deployments"
|
|
||||||
|
|
||||||
# 部署后钩子
|
|
||||||
post_deploy:
|
|
||||||
- name: "run-smoke-tests"
|
|
||||||
command: "./scripts/smoke-tests.sh {{.deployment_url}}"
|
|
||||||
timeout: 300
|
|
||||||
|
|
||||||
# 变量替换上下文
|
|
||||||
variables:
|
|
||||||
# 内置变量(由CI/CD系统提供)
|
|
||||||
builtin:
|
|
||||||
- "project_name"
|
|
||||||
- "version"
|
|
||||||
- "build_id"
|
|
||||||
- "commit_sha"
|
|
||||||
- "branch"
|
|
||||||
- "build_date"
|
|
||||||
- "platform"
|
|
||||||
|
|
||||||
# 自定义变量
|
|
||||||
custom:
|
|
||||||
deployment_url: "https://{{.project_name}}-{{.environment}}.example.com"
|
|
||||||
release_notes: "Automated build {{.build_id}}"
|
|
||||||
|
|
||||||
# 环境变量映射
|
|
||||||
environment:
|
|
||||||
- "CRATES_IO_TOKEN"
|
|
||||||
- "DOCKER_USERNAME"
|
|
||||||
- "SLACK_WEBHOOK"
|
|
||||||
- "PAGERDUTY_KEY"
|
|
||||||
|
|
||||||
# 元数据
|
|
||||||
metadata:
|
|
||||||
description: "Finalization configuration for {{.project_name}}"
|
|
||||||
schema_version: "1.0"
|
|
||||||
maintainer: "devops@example.com"
|
|
||||||
last_updated: "2024-01-01"
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
# prebake.yaml.tmpl
|
|
||||||
#
|
|
||||||
# 术语说明:
|
|
||||||
# 可选:该字段可以不给出
|
|
||||||
# 默认为...:隐含“可选”
|
|
||||||
# 留空:尚未定义,留作后续
|
|
||||||
|
|
||||||
# 版本号,用于格式兼容性检查
|
|
||||||
version: "1.0"
|
|
||||||
|
|
||||||
# 构建环境定义
|
|
||||||
environment:
|
|
||||||
# 构建机类型
|
|
||||||
builder: "docker" # 或 "firecracker", "custom", "baremetal"
|
|
||||||
|
|
||||||
# 构建机配置,注意以下只会出现与上述构建机类型对应的组。
|
|
||||||
config:
|
|
||||||
# baremetal:
|
|
||||||
# 不需要
|
|
||||||
|
|
||||||
# docker:
|
|
||||||
# 镜像名
|
|
||||||
image: "rust:1.70-slim"
|
|
||||||
# Dockerfile路径,相对于项目根目录,不能与镜像一同指定,两者必须指定其一
|
|
||||||
dockerfile: "/Dockerfile"
|
|
||||||
# Docker构建参数,可选,只能与dockerfile搭配使用
|
|
||||||
build_args:
|
|
||||||
- "RUST_VERSION=1.70"
|
|
||||||
|
|
||||||
# firecracker:
|
|
||||||
# 留空,还没做
|
|
||||||
|
|
||||||
# custom:
|
|
||||||
# 自定义构建机名称
|
|
||||||
name: "my-custom-builder"
|
|
||||||
# 设置脚本,相对于项目根目录
|
|
||||||
setup_script: "setup-builder.sh"
|
|
||||||
# 清洁脚本,相对于项目根目录
|
|
||||||
cleanup_script: "cleanup-builder.sh"
|
|
||||||
|
|
||||||
# 硬件资源要求,满足最小要求的构建机可以被选中,可选
|
|
||||||
resources:
|
|
||||||
cpu: 2 # CPU核心数,默认为1
|
|
||||||
memory: "2G" # 内存大小,也可以写作2048MB,默认为"1G"
|
|
||||||
disk: "10G" # 磁盘空间,也可以写作10240MB,默认为"1G"
|
|
||||||
architecture: "x86_64" # 架构要求,只能是"noarch"或{"x86_64"/"amd64", "arm64"/"aarch64", "riscv64", "loongarch64"/"loong64"}中的一个或几个
|
|
||||||
gpu: false # 是否需要GPU,默认为false
|
|
||||||
gpu_type: "cuda" # GPU类型(如果需要),可以是"cuda", "rocm", "vulkan", "oneapi"中的一个或几个
|
|
||||||
tags: # 自定义标签,只有满足标签的构建机可以被选中
|
|
||||||
- "custom_tag"
|
|
||||||
|
|
||||||
# 网络配置,可选
|
|
||||||
network:
|
|
||||||
enabled: true # 启用网络,对baremetal不适用,默认为true
|
|
||||||
outbound: true # 允许出站连接,对baremetal不适用,默认为true
|
|
||||||
dns_servers: # 指定构建机的DNS服务器,对baremetal不适用,custom应该自行处理DNS问题,可选
|
|
||||||
- "8.8.8.8"
|
|
||||||
- "1.1.1.1"
|
|
||||||
proxies: # 代理配置,暂时留空,可选
|
|
||||||
|
|
||||||
# 依赖定义,安装依赖时按custom_pre-system-languages-custom顺序进行,可选
|
|
||||||
dependencies:
|
|
||||||
# 系统包依赖,按软件包类型给出或any通配,若存在发行版则选择之,否则选中any,也没有则报警告但继续
|
|
||||||
system:
|
|
||||||
deb:
|
|
||||||
packages: # 安装的软件包
|
|
||||||
- "git"
|
|
||||||
- "curl"
|
|
||||||
- "build-essential"
|
|
||||||
- "pkg-config"
|
|
||||||
- "libssl-dev"
|
|
||||||
repositories:
|
|
||||||
- "ppa:custom/ppa" # 自定义PPA(Ubuntu)
|
|
||||||
mirror: "https://mirrors.tuna.tsinghua.edu.cn" # 指定软件包镜像源,可选
|
|
||||||
pacman:
|
|
||||||
packages:
|
|
||||||
- "git"
|
|
||||||
- "curl"
|
|
||||||
- "base-devel"
|
|
||||||
- "pkg-config"
|
|
||||||
- "openssl"
|
|
||||||
repositories:
|
|
||||||
- "https://mirrors.tuna.tsinghua.edu.cn/arch4edu/$arch" # 自定义仓库(Arch)
|
|
||||||
mirror: "https://mirrors.tuna.tsinghua.edu.cn" # 指定软件包镜像源,可选
|
|
||||||
rpm:
|
|
||||||
# 略
|
|
||||||
|
|
||||||
# 语言特定依赖,可选,此处定义不稳定
|
|
||||||
languages:
|
|
||||||
rust:
|
|
||||||
toolchain: "stable-x86_64-unknown-linux-gnu"
|
|
||||||
|
|
||||||
# 自定义依赖步骤,可选
|
|
||||||
custom:
|
|
||||||
- name: "install-custom-tool" # 名称
|
|
||||||
command: "curl -fsSL https://example.com/install.sh | sh" # 命令
|
|
||||||
environment: # 环境变量
|
|
||||||
CUSTOM_VAR: "value"
|
|
||||||
|
|
||||||
- name: "build-native-dep"
|
|
||||||
command: "make && make install"
|
|
||||||
working_dir: "native-deps" # 工作目录,基于项目根目录
|
|
||||||
timeout: 300 # 超时,默认为300秒
|
|
||||||
|
|
||||||
# 自定义依赖步骤,但是在依赖安装时最先进行,可选
|
|
||||||
custom_pre:
|
|
||||||
- name: "install-custom-tool"
|
|
||||||
command: "curl -fsSL https://example.com/install.sh | sh"
|
|
||||||
environment:
|
|
||||||
CUSTOM_VAR: "value"
|
|
||||||
|
|
||||||
- name: "build-native-dep"
|
|
||||||
command: "make && make install"
|
|
||||||
working_dir: "native-deps"
|
|
||||||
timeout: 300
|
|
||||||
|
|
||||||
# 环境变量配置,可选
|
|
||||||
environment_variables:
|
|
||||||
# 构建环境变量,只在prebake阶段有效
|
|
||||||
prebake:
|
|
||||||
RUST_BACKTRACE: "full"
|
|
||||||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
|
||||||
NODE_ENV: "production"
|
|
||||||
|
|
||||||
# 运行时环境变量(会传递给bake阶段,与该阶段获取的环境一并作为.env保存)
|
|
||||||
bake:
|
|
||||||
DATABASE_URL: "postgresql://user:pass@localhost/db"
|
|
||||||
LOG_LEVEL: "info"
|
|
||||||
__ENV_FILE: ".env" # 特殊变量,指定项目中.env的位置并合并其中的值。该值本身不传入bake阶段
|
|
||||||
|
|
||||||
# 缓存配置,可选
|
|
||||||
cache:
|
|
||||||
# 缓存目录
|
|
||||||
directories:
|
|
||||||
- path: "/usr/local/cargo"
|
|
||||||
strategy: "always" # 在"always","readonly","clean"和"never"中选择一个,可以被流水线启动时配置覆盖,默认为"always"
|
|
||||||
mode: "zstd" # 在"gzip","zstd","none"和"dir"中选择一个
|
|
||||||
|
|
||||||
- path: "/root/.npm"
|
|
||||||
strategy: "clean"
|
|
||||||
mode: "none"
|
|
||||||
|
|
||||||
- path: "/app/target"
|
|
||||||
strategy: "always"
|
|
||||||
mode: "dir"
|
|
||||||
|
|
||||||
# 缓存策略
|
|
||||||
strategy:
|
|
||||||
ttl_days: 30 # 缓存生存时间
|
|
||||||
max_size_gb: 20 # 最大缓存大小
|
|
||||||
cleanup_policy: "lru" # 清理策略:lru, fifo, size
|
|
||||||
|
|
||||||
# 安全配置
|
|
||||||
security:
|
|
||||||
# 留空
|
|
||||||
|
|
||||||
# 钩子脚本,可选
|
|
||||||
hooks:
|
|
||||||
# 依赖安装前
|
|
||||||
pre_dependencies:
|
|
||||||
- command: "echo 'Starting dependency installation'"
|
|
||||||
|
|
||||||
# 依赖安装后
|
|
||||||
post_dependencies:
|
|
||||||
- command: "cargo fetch"
|
|
||||||
working_dir: "/app"
|
|
||||||
|
|
||||||
# 环境准备完成
|
|
||||||
ready:
|
|
||||||
- command: "echo 'Environment ready for build'"
|
|
||||||
|
|
||||||
# 元数据
|
|
||||||
metadata:
|
|
||||||
description: "Rust project build environment"
|
|
||||||
maintainer: "team@example.com"
|
|
||||||
tags: ["rust", "web", "api"]
|
|
||||||
project_type: "rust-cargo"
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
use workshop_pipeline::*;
|
|
||||||
|
|
||||||
fn main() -> PipelineResult<()> {
|
|
||||||
env_logger::init();
|
|
||||||
// 创建变量提供者
|
|
||||||
let variable_provider = DefaultVariableProvider::new()
|
|
||||||
.with_variable("project_name", "my-awesome-project")
|
|
||||||
.with_variable("version", "1.0.0")
|
|
||||||
.with_secret("CRATES_IO_TOKEN", "super-secret-token");
|
|
||||||
|
|
||||||
// 加载流水线配置
|
|
||||||
let config = PipelineConfig::load_from_dir(
|
|
||||||
"./examples",
|
|
||||||
"user123",
|
|
||||||
"pipeline456",
|
|
||||||
&variable_provider,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// 输出完整配置(用于engine)
|
|
||||||
println!("完整配置: {:#?}", config);
|
|
||||||
|
|
||||||
// 输出遮蔽后的配置(用于日志)
|
|
||||||
let output_config = config.for_output();
|
|
||||||
println!("遮蔽配置: {:#?}", output_config);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
pub mod prebake;
|
|
||||||
pub mod finalize;
|
|
||||||
pub mod scripts;
|
|
||||||
|
|
||||||
pub use prebake::*;
|
|
||||||
pub use finalize::*;
|
|
||||||
pub use scripts::*;
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
mod artifacts;
|
|
||||||
mod packaging;
|
|
||||||
mod distribution;
|
|
||||||
mod deployment;
|
|
||||||
mod notifications;
|
|
||||||
mod quality;
|
|
||||||
|
|
||||||
pub use artifacts::*;
|
|
||||||
pub use packaging::*;
|
|
||||||
pub use distribution::*;
|
|
||||||
pub use deployment::*;
|
|
||||||
pub use notifications::*;
|
|
||||||
pub use quality::*;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// Finalize配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct FinalizeConfig {
|
|
||||||
pub version: String,
|
|
||||||
pub artifacts: Option<ArtifactsConfig>,
|
|
||||||
pub packaging: Option<PackagingConfig>,
|
|
||||||
pub distribution: Option<DistributionConfig>,
|
|
||||||
pub quality_gates: Option<QualityGates>,
|
|
||||||
pub deployment: Option<DeploymentConfig>,
|
|
||||||
pub notifications: Option<NotificationsConfig>,
|
|
||||||
pub cleanup: Option<CleanupConfig>,
|
|
||||||
pub hooks: Option<FinalizeHooks>,
|
|
||||||
pub variables: Option<VariableContext>,
|
|
||||||
pub metadata: Option<FinalizeMetadata>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 清理配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct CleanupConfig {
|
|
||||||
pub artifacts: ArtifactCleanup,
|
|
||||||
pub environment: EnvironmentCleanup,
|
|
||||||
pub cache: CacheCleanup,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 产物清理配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct ArtifactCleanup {
|
|
||||||
pub keep_latest: u32,
|
|
||||||
pub keep_tags: Vec<String>,
|
|
||||||
pub older_than_days: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 环境清理配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct EnvironmentCleanup {
|
|
||||||
pub containers: bool,
|
|
||||||
pub volumes: bool,
|
|
||||||
pub networks: bool,
|
|
||||||
pub images: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 缓存清理配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct CacheCleanup {
|
|
||||||
pub strategy: String,
|
|
||||||
pub max_size_gb: u32,
|
|
||||||
pub ttl_days: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finalize钩子配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct FinalizeHooks {
|
|
||||||
pub pre_package: Option<Vec<FinalizeHook>>,
|
|
||||||
pub post_package: Option<Vec<FinalizeHook>>,
|
|
||||||
pub pre_release: Option<Vec<FinalizeHook>>,
|
|
||||||
pub post_release: Option<Vec<FinalizeHook>>,
|
|
||||||
pub post_deploy: Option<Vec<FinalizeHook>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finalize钩子
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct FinalizeHook {
|
|
||||||
pub name: String,
|
|
||||||
pub command: String,
|
|
||||||
pub timeout: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 变量上下文
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct VariableContext {
|
|
||||||
pub builtin: Vec<String>,
|
|
||||||
pub custom: std::collections::HashMap<String, String>,
|
|
||||||
pub environment: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finalize元数据
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct FinalizeMetadata {
|
|
||||||
pub description: String,
|
|
||||||
pub schema_version: String,
|
|
||||||
pub maintainer: String,
|
|
||||||
pub last_updated: String,
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// 产物配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct ArtifactsConfig {
|
|
||||||
pub primary: Option<Vec<Artifact>>,
|
|
||||||
pub secondary: Option<Vec<Artifact>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 产物
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Artifact {
|
|
||||||
pub name: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub type_: String,
|
|
||||||
pub path: String,
|
|
||||||
pub platform: Option<String>,
|
|
||||||
pub permissions: Option<String>,
|
|
||||||
pub description: Option<String>,
|
|
||||||
pub include_patterns: Option<Vec<String>>,
|
|
||||||
pub exclude_patterns: Option<Vec<String>>,
|
|
||||||
pub compress: Option<bool>,
|
|
||||||
pub format: Option<String>,
|
|
||||||
pub retention_days: Option<u32>,
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// 部署配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct DeploymentConfig {
|
|
||||||
pub environments: HashMap<String, Environment>,
|
|
||||||
pub strategy: DeploymentStrategy,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 环境配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Environment {
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub type_: String,
|
|
||||||
pub namespace: String,
|
|
||||||
pub cluster: String,
|
|
||||||
pub manifests: Vec<String>,
|
|
||||||
pub health_check: Option<HealthCheck>,
|
|
||||||
pub manual_approval: Option<bool>,
|
|
||||||
pub rollout_strategy: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 健康检查配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct HealthCheck {
|
|
||||||
pub endpoint: String,
|
|
||||||
pub timeout: u32,
|
|
||||||
pub interval: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 部署策略
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct DeploymentStrategy {
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub type_: String,
|
|
||||||
pub max_unavailable: String,
|
|
||||||
pub max_surge: String,
|
|
||||||
pub wait_timeout: u32,
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// 发布配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct DistributionConfig {
|
|
||||||
pub local: Option<LocalStorage>,
|
|
||||||
pub s3: Option<S3Storage>,
|
|
||||||
pub package_registries: Option<PackageRegistries>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 本地存储配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct LocalStorage {
|
|
||||||
pub directory: String,
|
|
||||||
pub retention_days: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// S3存储配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct S3Storage {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub bucket: String,
|
|
||||||
pub prefix: String,
|
|
||||||
pub region: String,
|
|
||||||
pub acl: String,
|
|
||||||
pub storage_class: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 包注册表配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct PackageRegistries {
|
|
||||||
pub crates_io: Option<CratesIoConfig>,
|
|
||||||
pub npm: Option<NpmConfig>,
|
|
||||||
pub docker_registry: Option<DockerRegistryConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crates.io配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct CratesIoConfig {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NPM配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct NpmConfig {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub registry: String,
|
|
||||||
pub token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Docker注册表配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct DockerRegistryConfig {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub registry: String,
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// 通知配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct NotificationsConfig {
|
|
||||||
pub on_success: Vec<Notification>,
|
|
||||||
pub on_failure: Vec<Notification>,
|
|
||||||
pub on_deploy: Vec<Notification>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 通知
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Notification {
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub type_: String,
|
|
||||||
pub webhook: Option<String>,
|
|
||||||
pub channel: Option<String>,
|
|
||||||
pub message: String,
|
|
||||||
pub to: Option<Vec<String>>,
|
|
||||||
pub subject: Option<String>,
|
|
||||||
pub template: Option<String>,
|
|
||||||
pub url: Option<String>,
|
|
||||||
pub method: Option<String>,
|
|
||||||
pub headers: Option<HashMap<String, String>>,
|
|
||||||
pub service_key: Option<String>,
|
|
||||||
pub severity: Option<String>,
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// 打包配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct PackagingConfig {
|
|
||||||
pub primary_package: Option<PrimaryPackage>,
|
|
||||||
pub docker: Option<DockerPackaging>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 主要包配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct PrimaryPackage {
|
|
||||||
pub format: String,
|
|
||||||
pub output: String,
|
|
||||||
pub include: Vec<String>,
|
|
||||||
pub exclude: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Docker打包配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct DockerPackaging {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub registry: String,
|
|
||||||
pub repository: String,
|
|
||||||
pub tags: Vec<String>,
|
|
||||||
pub dockerfile: String,
|
|
||||||
pub build_args: Vec<String>,
|
|
||||||
pub labels: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// 质量门禁
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct QualityGates {
|
|
||||||
pub test_coverage: Option<TestCoverageGate>,
|
|
||||||
pub security_scan: Option<SecurityScanGate>,
|
|
||||||
pub code_quality: Option<CodeQualityGate>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 测试覆盖率门禁
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct TestCoverageGate {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub minimum: f32,
|
|
||||||
pub file: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 安全扫描门禁
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct SecurityScanGate {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub tools: Vec<String>,
|
|
||||||
pub fail_on: String,
|
|
||||||
pub exceptions: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 代码质量门禁
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct CodeQualityGate {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub maximum_duplication: f32,
|
|
||||||
pub maximum_complexity: f32,
|
|
||||||
pub minimum_maintainability: f32,
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
mod environment;
|
|
||||||
mod dependencies;
|
|
||||||
mod security;
|
|
||||||
mod hooks;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
pub use environment::*;
|
|
||||||
pub use dependencies::*;
|
|
||||||
pub use security::*;
|
|
||||||
pub use hooks::*;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// Prebake配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct PrebakeConfig {
|
|
||||||
pub version: String,
|
|
||||||
pub environment: EnvironmentConfig,
|
|
||||||
pub dependencies: Option<DependenciesConfig>,
|
|
||||||
pub environment_variables: Option<EnvironmentVariables>,
|
|
||||||
pub cache: Option<CacheConfig>,
|
|
||||||
pub security: Option<SecurityConfig>,
|
|
||||||
pub hooks: Option<HooksConfig>,
|
|
||||||
pub metadata: Option<Metadata>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 环境变量配置 - 调整阶段
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct EnvironmentVariables {
|
|
||||||
pub prebake: Option<HashMap<String, String>>,
|
|
||||||
pub bake: Option<HashMap<String, String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 缓存配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct CacheConfig {
|
|
||||||
pub directories: Vec<CacheDirectory>,
|
|
||||||
pub strategy: Option<CacheStrategy>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 缓存目录配置 - 更新字段
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct CacheDirectory {
|
|
||||||
pub path: String,
|
|
||||||
#[serde(default = "default_cache_strategy")]
|
|
||||||
pub strategy: CacheStrategyType,
|
|
||||||
#[serde(default = "default_cache_mode")]
|
|
||||||
pub mode: CacheMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 缓存策略类型
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum CacheStrategyType {
|
|
||||||
#[serde(rename = "always")]
|
|
||||||
Always,
|
|
||||||
#[serde(rename = "readonly")]
|
|
||||||
ReadOnly,
|
|
||||||
#[serde(rename = "clean")]
|
|
||||||
Clean,
|
|
||||||
#[serde(rename = "never")]
|
|
||||||
Never,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 缓存模式
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum CacheMode {
|
|
||||||
#[serde(rename = "gzip")]
|
|
||||||
Gzip,
|
|
||||||
#[serde(rename = "zstd")]
|
|
||||||
Zstd,
|
|
||||||
#[serde(rename = "none")]
|
|
||||||
None,
|
|
||||||
#[serde(rename = "dir")]
|
|
||||||
Dir,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 缓存策略
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct CacheStrategy {
|
|
||||||
pub ttl_days: u32,
|
|
||||||
pub max_size_gb: u32,
|
|
||||||
pub cleanup_policy: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 安全配置 - 留空
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct SecurityConfig {
|
|
||||||
// 根据新模板,暂时留空
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 元数据
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Metadata {
|
|
||||||
pub description: String,
|
|
||||||
pub maintainer: String,
|
|
||||||
pub tags: Vec<String>,
|
|
||||||
pub project_type: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认值函数
|
|
||||||
fn default_cache_strategy() -> CacheStrategyType {
|
|
||||||
CacheStrategyType::Always
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_cache_mode() -> CacheMode {
|
|
||||||
CacheMode::Zstd
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// 依赖配置 - 调整顺序
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct DependenciesConfig {
|
|
||||||
pub custom_pre: Option<Vec<CustomDependency>>,
|
|
||||||
pub system: Option<SystemDependencies>,
|
|
||||||
pub languages: Option<LanguageDependencies>,
|
|
||||||
pub custom: Option<Vec<CustomDependency>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 系统依赖 - 按发行版组织
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct SystemDependencies {
|
|
||||||
pub ubuntu: Option<DistributionDeps>,
|
|
||||||
pub arch: Option<DistributionDeps>,
|
|
||||||
pub fedora: Option<DistributionDeps>,
|
|
||||||
// 可以添加其他发行版...
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 发行版依赖
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct DistributionDeps {
|
|
||||||
pub packages: Vec<String>,
|
|
||||||
pub repositories: Option<Vec<String>>,
|
|
||||||
pub mirror: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 语言依赖 - 简化结构
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct LanguageDependencies {
|
|
||||||
pub rust: Option<RustConfig>,
|
|
||||||
// 其他语言暂时保持不变...
|
|
||||||
pub node: Option<NodeConfig>,
|
|
||||||
pub python: Option<PythonConfig>,
|
|
||||||
pub go: Option<GoConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rust配置 - 简化
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct RustConfig {
|
|
||||||
pub toolchain: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Node配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct NodeConfig {
|
|
||||||
pub version: String,
|
|
||||||
pub package_manager: String,
|
|
||||||
pub registry: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Python配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct PythonConfig {
|
|
||||||
pub version: String,
|
|
||||||
pub package_manager: String,
|
|
||||||
pub requirements_file: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Go配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct GoConfig {
|
|
||||||
pub version: String,
|
|
||||||
pub modules: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 更多语言配置
|
|
||||||
|
|
||||||
/// 自定义依赖
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct CustomDependency {
|
|
||||||
pub name: String,
|
|
||||||
pub command: String,
|
|
||||||
pub environment: Option<HashMap<String, String>>,
|
|
||||||
pub working_dir: Option<String>,
|
|
||||||
#[serde(default = "default_timeout")]
|
|
||||||
pub timeout: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_timeout() -> u32 { 300 }
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// 环境配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct EnvironmentConfig {
|
|
||||||
pub builder: String,
|
|
||||||
pub config: BuilderConfig,
|
|
||||||
pub resources: ResourceRequirements,
|
|
||||||
pub network: NetworkConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 构建器配置 - 现在各个配置是互斥的
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct BuilderConfig {
|
|
||||||
pub baremetal: Option<BaremetalConfig>,
|
|
||||||
pub docker: Option<DockerConfig>,
|
|
||||||
pub firecracker: Option<FirecrackerConfig>,
|
|
||||||
pub custom: Option<CustomBuilderConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 裸机配置 - 现在不需要具体配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct BaremetalConfig {
|
|
||||||
// 根据新模板,baremetal不需要配置项
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Docker配置 - 更新字段
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct DockerConfig {
|
|
||||||
/// 镜像名
|
|
||||||
pub image: Option<String>,
|
|
||||||
/// Dockerfile路径,相对于项目根目录
|
|
||||||
pub dockerfile: Option<String>,
|
|
||||||
/// Docker构建参数,可选,只能与dockerfile搭配使用
|
|
||||||
pub build_args: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Firecracker配置 - 留空
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct FirecrackerConfig {
|
|
||||||
// 根据新模板,暂时留空
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 自定义构建器配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct CustomBuilderConfig {
|
|
||||||
pub name: String,
|
|
||||||
pub setup_script: String,
|
|
||||||
pub cleanup_script: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 资源需求 - 更新字段
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct ResourceRequirements {
|
|
||||||
#[serde(default = "default_cpu")]
|
|
||||||
pub cpu: u32,
|
|
||||||
#[serde(default = "default_memory")]
|
|
||||||
pub memory: String,
|
|
||||||
#[serde(default = "default_disk")]
|
|
||||||
pub disk: String,
|
|
||||||
pub architecture: Architecture,
|
|
||||||
#[serde(default)]
|
|
||||||
pub gpu: bool,
|
|
||||||
pub gpu_type: Option<Vec<GpuType>>,
|
|
||||||
pub tags: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 架构要求
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum Architecture {
|
|
||||||
Single(String),
|
|
||||||
Multiple(Vec<String>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Architecture {
|
|
||||||
fn default() -> Self {
|
|
||||||
Architecture::Single("x86_64".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// GPU类型
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum GpuType {
|
|
||||||
#[serde(rename = "cuda")]
|
|
||||||
Cuda,
|
|
||||||
#[serde(rename = "rocm")]
|
|
||||||
Rocm,
|
|
||||||
#[serde(rename = "vulkan")]
|
|
||||||
Vulkan,
|
|
||||||
#[serde(rename = "oneapi")]
|
|
||||||
OneApi,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 网络配置 - 更新字段
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct NetworkConfig {
|
|
||||||
#[serde(default = "default_network_enabled")]
|
|
||||||
pub enabled: bool,
|
|
||||||
#[serde(default = "default_outbound")]
|
|
||||||
pub outbound: bool,
|
|
||||||
pub dns_servers: Option<Vec<String>>,
|
|
||||||
pub proxies: Option<ProxiesConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 代理配置 - 暂时留空
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct ProxiesConfig {
|
|
||||||
// 暂不定义具体字段
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认值函数
|
|
||||||
fn default_cpu() -> u32 { 1 }
|
|
||||||
fn default_memory() -> String { "1G".to_string() }
|
|
||||||
fn default_disk() -> String { "1G".to_string() }
|
|
||||||
fn default_network_enabled() -> bool { true }
|
|
||||||
fn default_outbound() -> bool { true }
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// 钩子配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct HooksConfig {
|
|
||||||
pub pre_dependencies: Option<Vec<Hook>>,
|
|
||||||
pub post_dependencies: Option<Vec<Hook>>,
|
|
||||||
pub ready: Option<Vec<Hook>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 钩子
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Hook {
|
|
||||||
pub command: String,
|
|
||||||
pub working_dir: Option<String>,
|
|
||||||
pub timeout: Option<u32>,
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// 安全配置
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct SecurityConfig {
|
|
||||||
pub capabilities: Vec<String>,
|
|
||||||
pub seccomp: String,
|
|
||||||
pub apparmor: bool,
|
|
||||||
pub no_new_privileges: bool,
|
|
||||||
pub limits: ResourceLimits,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 资源限制
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct ResourceLimits {
|
|
||||||
pub max_processes: u32,
|
|
||||||
pub max_file_size: String,
|
|
||||||
pub max_open_files: u32,
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// Bake脚本
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct BakeScript {
|
|
||||||
pub content: String,
|
|
||||||
pub path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BakeScript {
|
|
||||||
pub fn new(content: String) -> Self {
|
|
||||||
Self {
|
|
||||||
content,
|
|
||||||
path: ".workshop/bake.sh".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 为bench.sh预留的结构
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct BenchScript {
|
|
||||||
pub content: String,
|
|
||||||
pub path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BenchScript {
|
|
||||||
pub fn new(content: String) -> Self {
|
|
||||||
Self {
|
|
||||||
content,
|
|
||||||
path: ".workshop/bench.sh".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 为test.sh预留的结构
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct TestScript {
|
|
||||||
pub content: String,
|
|
||||||
pub path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestScript {
|
|
||||||
pub fn new(content: String) -> Self {
|
|
||||||
Self {
|
|
||||||
content,
|
|
||||||
path: ".workshop/test.sh".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum PipelineError {
|
|
||||||
#[error("IO error: {0}")]
|
|
||||||
Io(#[from] std::io::Error),
|
|
||||||
#[error("YAML parse error: {0}")]
|
|
||||||
Yaml(#[from] serde_yaml::Error),
|
|
||||||
#[error("Variable error: {0}")]
|
|
||||||
Variable(#[from] crate::variable::VariableError),
|
|
||||||
#[error("Template error: {0}")]
|
|
||||||
Template(String),
|
|
||||||
#[error("Configuration error: {0}")]
|
|
||||||
Config(String),
|
|
||||||
#[error("Validation error: {0}")]
|
|
||||||
Validation(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type PipelineResult<T> = Result<T, PipelineError>;
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
mod error;
|
|
||||||
mod variable;
|
|
||||||
mod template;
|
|
||||||
mod config;
|
|
||||||
|
|
||||||
use std::path::Path;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use serde::Serialize;
|
|
||||||
use log::info;
|
|
||||||
pub use error::*;
|
|
||||||
pub use variable::*;
|
|
||||||
pub use template::*;
|
|
||||||
pub use config::*;
|
|
||||||
|
|
||||||
// 重新导出常用的配置类型
|
|
||||||
#[allow(unused)]
|
|
||||||
pub use config::prebake::*;
|
|
||||||
#[allow(unused)]
|
|
||||||
pub use config::finalize::*;
|
|
||||||
#[allow(unused)]
|
|
||||||
pub use config::scripts::*;
|
|
||||||
|
|
||||||
pub type PipelineResult<T> = Result<T, PipelineError>;
|
|
||||||
|
|
||||||
/// 完整的流水线配置
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
|
||||||
pub struct PipelineConfig {
|
|
||||||
pub prebake: PrebakeConfig,
|
|
||||||
pub bake: BakeScript,
|
|
||||||
pub finalize: FinalizeConfig,
|
|
||||||
pub variables: HashMap<String, VariableValue>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PipelineConfig {
|
|
||||||
/// 从文件系统加载完整配置
|
|
||||||
pub fn load_from_dir<P: AsRef<Path>>(
|
|
||||||
dir: P,
|
|
||||||
user_id: &str,
|
|
||||||
pipeline_id: &str,
|
|
||||||
variable_provider: &dyn VariableProvider,
|
|
||||||
) -> PipelineResult<Self> {
|
|
||||||
let dir = dir.as_ref();
|
|
||||||
|
|
||||||
info!("Reading prebake.yaml...");
|
|
||||||
let prebake_path = dir.join(".workshop/prebake.yaml");
|
|
||||||
info!("Reading bake.sh...");
|
|
||||||
let bake_path = dir.join(".workshop/bake.sh");
|
|
||||||
info!("Reading finalize.yaml...");
|
|
||||||
let finalize_path = dir.join(".workshop/finalize.yaml");
|
|
||||||
|
|
||||||
let mut variable_resolver = VariableResolver::new(user_id, pipeline_id, variable_provider);
|
|
||||||
|
|
||||||
// 加载并处理prebake配置
|
|
||||||
let prebake_content = std::fs::read_to_string(&prebake_path)?;
|
|
||||||
let prebake: PrebakeConfig = serde_yaml::from_str(&prebake_content)?;
|
|
||||||
// let prebake = variable_resolver.resolve_prebake(prebake)?;
|
|
||||||
let prebake = prebake.resolve_template(&mut variable_resolver)?;
|
|
||||||
|
|
||||||
// 加载bake脚本
|
|
||||||
let bake_content = std::fs::read_to_string(&bake_path)?;
|
|
||||||
let bake = BakeScript::new(bake_content);
|
|
||||||
|
|
||||||
// 加载并处理finalize配置
|
|
||||||
let finalize_content = std::fs::read_to_string(&finalize_path)?;
|
|
||||||
let finalize: FinalizeConfig = serde_yaml::from_str(&finalize_content)?;
|
|
||||||
// let finalize = variable_resolver.resolve_finalize(finalize)?;
|
|
||||||
let finalize = finalize.resolve_template(&mut variable_resolver)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
prebake,
|
|
||||||
bake,
|
|
||||||
finalize,
|
|
||||||
variables: variable_resolver.get_resolved_variables(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取用于输出的配置(遮蔽secret)
|
|
||||||
pub fn for_output(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
prebake: self.prebake.clone(),
|
|
||||||
bake: self.bake.clone(),
|
|
||||||
finalize: self.finalize.for_output(),
|
|
||||||
variables: self.variables.iter()
|
|
||||||
.map(|(k, v)| (k.clone(), v.for_output()))
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,353 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
config::finalize::FinalizeConfig,
|
|
||||||
config::prebake::{
|
|
||||||
Architecture, CacheMode, CacheStrategyType, DistributionDeps, GpuType, PrebakeConfig,
|
|
||||||
SystemDependencies,
|
|
||||||
},
|
|
||||||
error::PipelineError,
|
|
||||||
variable::VariableResolver,
|
|
||||||
};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::Regex;
|
|
||||||
// use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref HANDLEBARS_REGEX: Regex =
|
|
||||||
Regex::new(r"\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}").unwrap();
|
|
||||||
static ref SHELL_VAR_REGEX: Regex = Regex::new(r"\$([A-Za-z_][A-Za-z0-9_]*)").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TemplateResolvable {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为基本类型实现模板解析
|
|
||||||
impl TemplateResolvable for String {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
let mut result = self.clone();
|
|
||||||
|
|
||||||
// 处理 {{VARIABLE}} 格式
|
|
||||||
for cap in HANDLEBARS_REGEX.captures_iter(result.clone().as_str()) {
|
|
||||||
let var_name = &cap[1];
|
|
||||||
if let Ok(value) = resolver.resolve_variable(var_name) {
|
|
||||||
result = result.replace(&cap[0], &value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理$VARIABLE格式,TODO
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为Option<String>实现
|
|
||||||
// impl TemplateResolvable for Option<String> {
|
|
||||||
// fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
// match self {
|
|
||||||
// Some(s) => Ok(Some(s.resolve_template(resolver)?)),
|
|
||||||
// None => Ok(None),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 为Vec<String>实现
|
|
||||||
impl TemplateResolvable for Vec<String> {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
for item in self {
|
|
||||||
result.push(item.resolve_template(resolver)?);
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// impl TemplateResolvable for Option<Vec<String>> {
|
|
||||||
// fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
// match self {
|
|
||||||
// Some(vec) => Ok(Some(vec.resolve_template(resolver)?)),
|
|
||||||
// None => Ok(None),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// 为HashMap<String, String>实现
|
|
||||||
impl TemplateResolvable for std::collections::HashMap<String, String> {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
let mut result = std::collections::HashMap::new();
|
|
||||||
for (key, value) in self {
|
|
||||||
result.insert(
|
|
||||||
key.resolve_template(resolver)?,
|
|
||||||
value.resolve_template(resolver)?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: TemplateResolvable> TemplateResolvable for Option<T> {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Some(value) => Ok(Some(value.resolve_template(resolver)?)),
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为Architecture实现
|
|
||||||
impl TemplateResolvable for Architecture {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
match self {
|
|
||||||
Architecture::Single(arch) => {
|
|
||||||
Ok(Architecture::Single(arch.resolve_template(resolver)?))
|
|
||||||
}
|
|
||||||
Architecture::Multiple(archs) => {
|
|
||||||
Ok(Architecture::Multiple(archs.resolve_template(resolver)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为SystemDependencies实现
|
|
||||||
impl TemplateResolvable for SystemDependencies {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
let mut deps = self.clone();
|
|
||||||
|
|
||||||
if let Some(ref mut ubuntu) = deps.ubuntu {
|
|
||||||
*ubuntu = ubuntu.resolve_template(resolver)?;
|
|
||||||
}
|
|
||||||
if let Some(ref mut arch) = deps.arch {
|
|
||||||
*arch = arch.resolve_template(resolver)?;
|
|
||||||
}
|
|
||||||
if let Some(ref mut fedora) = deps.fedora {
|
|
||||||
*fedora = fedora.resolve_template(resolver)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(deps)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为DistributionDeps实现
|
|
||||||
impl TemplateResolvable for DistributionDeps {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(DistributionDeps {
|
|
||||||
packages: self.packages.resolve_template(resolver)?,
|
|
||||||
repositories: self.repositories.resolve_template(resolver)?,
|
|
||||||
mirror: self.mirror.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 枚举类型不需要模板解析,直接克隆
|
|
||||||
impl TemplateResolvable for GpuType {
|
|
||||||
fn resolve_template(&self, _resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(self.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TemplateResolvable for CacheStrategyType {
|
|
||||||
fn resolve_template(&self, _resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(self.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TemplateResolvable for CacheMode {
|
|
||||||
fn resolve_template(&self, _resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(self.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为PrebakeConfig实现模板解析
|
|
||||||
impl TemplateResolvable for PrebakeConfig {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
let mut config = self.clone();
|
|
||||||
|
|
||||||
// 解析环境配置
|
|
||||||
config.environment = config.environment.resolve_template(resolver)?;
|
|
||||||
|
|
||||||
// 解析依赖配置
|
|
||||||
if let Some(ref mut deps) = config.dependencies {
|
|
||||||
*deps = deps.resolve_template(resolver)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析环境变量
|
|
||||||
if let Some(ref mut env_vars) = config.environment_variables {
|
|
||||||
*env_vars = env_vars.resolve_template(resolver)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析缓存配置
|
|
||||||
if let Some(ref mut cache) = config.cache {
|
|
||||||
*cache = cache.resolve_template(resolver)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为EnvironmentConfig实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::EnvironmentConfig {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
builder: self.builder.resolve_template(resolver)?,
|
|
||||||
config: self.config.resolve_template(resolver)?,
|
|
||||||
resources: self.resources.resolve_template(resolver)?,
|
|
||||||
network: self.network.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为BuilderConfig实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::BuilderConfig {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
baremetal: self.baremetal.resolve_template(resolver)?,
|
|
||||||
docker: self.docker.resolve_template(resolver)?,
|
|
||||||
firecracker: self.firecracker.resolve_template(resolver)?,
|
|
||||||
custom: self.custom.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为DockerConfig实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::DockerConfig {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
image: self.image.resolve_template(resolver)?,
|
|
||||||
dockerfile: self.dockerfile.resolve_template(resolver)?,
|
|
||||||
build_args: self.build_args.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为CustomBuilderConfig实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::CustomBuilderConfig {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
name: self.name.resolve_template(resolver)?,
|
|
||||||
setup_script: self.setup_script.resolve_template(resolver)?,
|
|
||||||
cleanup_script: self.cleanup_script.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为ResourceRequirements实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::ResourceRequirements {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
cpu: self.cpu,
|
|
||||||
memory: self.memory.resolve_template(resolver)?,
|
|
||||||
disk: self.disk.resolve_template(resolver)?,
|
|
||||||
architecture: self.architecture.resolve_template(resolver)?,
|
|
||||||
gpu: self.gpu,
|
|
||||||
gpu_type: self.gpu_type.clone(),
|
|
||||||
tags: self.tags.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为NetworkConfig实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::NetworkConfig {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
enabled: self.enabled,
|
|
||||||
outbound: self.outbound,
|
|
||||||
dns_servers: self.dns_servers.resolve_template(resolver)?,
|
|
||||||
proxies: self.proxies.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为DependenciesConfig实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::DependenciesConfig {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
custom_pre: self.custom_pre.resolve_template(resolver)?,
|
|
||||||
system: self.system.resolve_template(resolver)?,
|
|
||||||
languages: self.languages.resolve_template(resolver)?,
|
|
||||||
custom: self.custom.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为LanguageDependencies实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::LanguageDependencies {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
rust: self.rust.resolve_template(resolver)?,
|
|
||||||
node: self.node.resolve_template(resolver)?,
|
|
||||||
python: self.python.resolve_template(resolver)?,
|
|
||||||
go: self.go.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为RustConfig实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::RustConfig {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
toolchain: self.toolchain.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为CustomDependency实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::CustomDependency {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
name: self.name.resolve_template(resolver)?,
|
|
||||||
command: self.command.resolve_template(resolver)?,
|
|
||||||
environment: self.environment.resolve_template(resolver)?,
|
|
||||||
working_dir: self.working_dir.resolve_template(resolver)?,
|
|
||||||
timeout: self.timeout,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为EnvironmentVariables实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::EnvironmentVariables {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
prebake: self.prebake.resolve_template(resolver)?,
|
|
||||||
bake: self.bake.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为CacheConfig实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::CacheConfig {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
directories: self.directories.resolve_template(resolver)?,
|
|
||||||
strategy: self.strategy.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为CacheDirectory实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::CacheDirectory {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
path: self.path.resolve_template(resolver)?,
|
|
||||||
strategy: self.strategy.resolve_template(resolver)?,
|
|
||||||
mode: self.mode.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为CacheStrategy实现
|
|
||||||
impl TemplateResolvable for crate::config::prebake::CacheStrategy {
|
|
||||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
|
||||||
Ok(Self {
|
|
||||||
ttl_days: self.ttl_days,
|
|
||||||
max_size_gb: self.max_size_gb,
|
|
||||||
cleanup_policy: self.cleanup_policy.resolve_template(resolver)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FinalizeConfig的模板解析保持不变...
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum VariableError {
|
|
||||||
#[error("Variable not found: {name}")]
|
|
||||||
NotFound { name: String },
|
|
||||||
#[error("Variable resolution failed: {message}")]
|
|
||||||
Resolution { message: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 变量值类型
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum VariableValue {
|
|
||||||
String(String),
|
|
||||||
Secret(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VariableValue {
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
VariableValue::String(s) => s,
|
|
||||||
VariableValue::Secret(s) => s,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn for_output(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
VariableValue::String(s) => VariableValue::String(s.clone()),
|
|
||||||
VariableValue::Secret(_) => VariableValue::Secret("***".to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 变量提供者trait
|
|
||||||
pub trait VariableProvider: Send + Sync {
|
|
||||||
fn get_variable(&self, user_id: &str, pipeline_id: &str, name: &str) -> Option<VariableValue>;
|
|
||||||
fn get_secret(&self, user_id: &str, pipeline_id: &str, name: &str) -> Option<VariableValue>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 默认的变量提供者实现(用于测试)
|
|
||||||
pub struct DefaultVariableProvider {
|
|
||||||
variables: HashMap<String, VariableValue>,
|
|
||||||
secrets: HashMap<String, VariableValue>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DefaultVariableProvider {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
variables: HashMap::new(),
|
|
||||||
secrets: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_variable(mut self, name: &str, value: &str) -> Self {
|
|
||||||
self.variables.insert(name.to_string(), VariableValue::String(value.to_string()));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_secret(mut self, name: &str, value: &str) -> Self {
|
|
||||||
self.secrets.insert(name.to_string(), VariableValue::Secret(value.to_string()));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VariableProvider for DefaultVariableProvider {
|
|
||||||
fn get_variable(&self, _user_id: &str, _pipeline_id: &str, name: &str) -> Option<VariableValue> {
|
|
||||||
self.variables.get(name).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_secret(&self, _user_id: &str, _pipeline_id: &str, name: &str) -> Option<VariableValue> {
|
|
||||||
self.secrets.get(name).cloned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DefaultVariableProvider {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 变量解析器
|
|
||||||
pub struct VariableResolver<'a> {
|
|
||||||
user_id: String,
|
|
||||||
pipeline_id: String,
|
|
||||||
provider: &'a dyn VariableProvider,
|
|
||||||
resolved_variables: HashMap<String, VariableValue>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> VariableResolver<'a> {
|
|
||||||
pub fn new(
|
|
||||||
user_id: &str,
|
|
||||||
pipeline_id: &str,
|
|
||||||
provider: &'a dyn VariableProvider,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
user_id: user_id.to_string(),
|
|
||||||
pipeline_id: pipeline_id.to_string(),
|
|
||||||
provider,
|
|
||||||
resolved_variables: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_variable(&mut self, name: &str) -> Result<String, VariableError> {
|
|
||||||
// 先尝试普通变量
|
|
||||||
if let Some(value) = self.provider.get_variable(&self.user_id, &self.pipeline_id, name) {
|
|
||||||
let value_str = value.as_str().to_string();
|
|
||||||
self.resolved_variables.insert(name.to_string(), value);
|
|
||||||
return Ok(value_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 再尝试secret
|
|
||||||
if let Some(value) = self.provider.get_secret(&self.user_id, &self.pipeline_id, name) {
|
|
||||||
let value_str = value.as_str().to_string();
|
|
||||||
self.resolved_variables.insert(name.to_string(), value);
|
|
||||||
return Ok(value_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(VariableError::NotFound {
|
|
||||||
name: name.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_resolved_variables(&self) -> HashMap<String, VariableValue> {
|
|
||||||
self.resolved_variables.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user