feat: add workshop-pipeline
Signed-off-by: Catty Steve <4795515+Catty2014@user.noreply.gitee.com> Workshop-pipeline is a crate for parsing prebake/bake/finalize config. Not finished yet, further testing required.
This commit is contained in:
Generated
+375
@@ -0,0 +1,375 @@
|
||||
# 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",
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
[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"
|
||||
@@ -0,0 +1,254 @@
|
||||
#!/bin/bash
|
||||
# .workshop/bake
|
||||
# 构建阶段脚本 - 使用标准的bash语法
|
||||
|
||||
set -euo pipefail # 严格模式:错误退出、未定义变量检查、管道错误检查
|
||||
|
||||
# 颜色定义用于输出
|
||||
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
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $*${NC}"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARN: $*${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*${NC}"
|
||||
}
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $*${NC}"
|
||||
}
|
||||
|
||||
# 环境变量(由CI/CD系统注入)
|
||||
readonly WORKSPACE_DIR="${WORKSPACE_DIR:-/workspace}"
|
||||
readonly BUILD_ID="${BUILD_ID:-unknown}"
|
||||
readonly COMMIT_SHA="${COMMIT_SHA:-}"
|
||||
readonly BRANCH_NAME="${BRANCH_NAME:-main}"
|
||||
readonly BUILD_CACHE_DIR="${BUILD_CACHE_DIR:-/cache}"
|
||||
|
||||
# 构建配置(从prebake和环境变量读取)
|
||||
readonly RUST_VERSION="${RUST_VERSION:-1.70}"
|
||||
readonly BUILD_TYPE="${BUILD_TYPE:-release}"
|
||||
readonly CARGO_REGISTRY="${CARGO_REGISTRY:-$BUILD_CACHE_DIR/cargo/registry}"
|
||||
readonly CARGO_GIT="${CARGO_GIT:-$BUILD_CACHE_DIR/cargo/git}"
|
||||
|
||||
# 切换到工作目录
|
||||
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 "$@"
|
||||
@@ -0,0 +1,286 @@
|
||||
# .workshop/finalize.yaml
|
||||
# 版本号
|
||||
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:
|
||||
# 测试覆盖率要求
|
||||
test_coverage:
|
||||
enabled: true
|
||||
minimum: 80.0
|
||||
file: "coverage/lcov.info"
|
||||
|
||||
# 安全扫描
|
||||
security_scan:
|
||||
enabled: true
|
||||
tools:
|
||||
- "cargo-audit"
|
||||
- "trivy"
|
||||
fail_on: "high" # low, medium, high, critical
|
||||
exceptions:
|
||||
- "CVE-2021-12345" # 已知例外
|
||||
|
||||
# 代码质量
|
||||
code_quality:
|
||||
enabled: true
|
||||
maximum_duplication: 5.0
|
||||
maximum_complexity: 25.0
|
||||
minimum_maintainability: 50.0
|
||||
|
||||
# 部署配置
|
||||
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"
|
||||
|
||||
# 部署策略
|
||||
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}}"
|
||||
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}}"
|
||||
severity: "error"
|
||||
|
||||
# 部署通知
|
||||
on_deploy:
|
||||
- type: "webhook"
|
||||
url: "https://api.example.com/deployments"
|
||||
method: "POST"
|
||||
headers:
|
||||
Authorization: "Bearer {{env.DEPLOY_WEBHOOK_TOKEN}}"
|
||||
|
||||
# 清理策略
|
||||
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"
|
||||
@@ -0,0 +1,180 @@
|
||||
# .workshop/prebake.yaml
|
||||
# 版本号,用于格式兼容性检查
|
||||
version: "1.0"
|
||||
|
||||
# 构建环境定义
|
||||
environment:
|
||||
# 构建机类型
|
||||
builder: "docker" # 或 "firecracker", "custom", "baremetal"
|
||||
|
||||
# 构建机配置
|
||||
config:
|
||||
baremetal:
|
||||
memory_mb: 2048
|
||||
cpus: 2
|
||||
arch: x86_64
|
||||
|
||||
# Docker 构建机配置
|
||||
docker:
|
||||
base_image: "rust:1.70-slim"
|
||||
dockerfile: |
|
||||
FROM rust:1.70-slim
|
||||
WORKDIR /app
|
||||
RUN apt-get update && apt-get install -y pkg-config libssl-dev
|
||||
build_args:
|
||||
- "RUST_VERSION=1.70"
|
||||
|
||||
# Firecracker 构建机配置
|
||||
firecracker:
|
||||
kernel_image: "file:///opt/firecracker/vmlinux"
|
||||
rootfs_image: "file:///opt/firecracker/rootfs.ext4"
|
||||
memory_mb: 1024
|
||||
vcpus: 2
|
||||
boot_args: "console=ttyS0 reboot=k panic=1 pci=off"
|
||||
|
||||
# 自定义构建机配置
|
||||
custom:
|
||||
name: "my-custom-builder"
|
||||
setup_script: "setup-builder.sh"
|
||||
cleanup_script: "cleanup-builder.sh"
|
||||
|
||||
# 硬件资源要求
|
||||
resources:
|
||||
cpu: 2 # CPU核心数
|
||||
memory: "2G" # 内存大小
|
||||
disk: "10G" # 磁盘空间
|
||||
architecture: "x86_64" # 架构要求
|
||||
gpu: false # 是否需要GPU
|
||||
gpu_type: "cuda" # GPU类型(如果需要)
|
||||
|
||||
# 网络配置
|
||||
network:
|
||||
enabled: true
|
||||
outbound: true # 允许出站连接
|
||||
dns_servers:
|
||||
- "8.8.8.8"
|
||||
- "1.1.1.1"
|
||||
|
||||
# 依赖定义
|
||||
dependencies:
|
||||
# 系统包依赖
|
||||
system:
|
||||
packages:
|
||||
- "git"
|
||||
- "curl"
|
||||
- "build-essential"
|
||||
- "pkg-config"
|
||||
- "libssl-dev"
|
||||
repositories:
|
||||
- "ppa:custom/ppa" # 自定义PPA(Ubuntu)
|
||||
|
||||
# 语言特定依赖
|
||||
languages:
|
||||
rust:
|
||||
version: "1.70"
|
||||
toolchain: "stable"
|
||||
components:
|
||||
- "rustfmt"
|
||||
- "clippy"
|
||||
targets:
|
||||
- "x86_64-unknown-linux-gnu"
|
||||
|
||||
node:
|
||||
version: "18"
|
||||
package_manager: "npm" # 或 "yarn", "pnpm"
|
||||
registry: "https://registry.npmjs.org/"
|
||||
|
||||
python:
|
||||
version: "3.11"
|
||||
package_manager: "pip" # 或 "poetry", "conda"
|
||||
requirements_file: "requirements.txt"
|
||||
|
||||
go:
|
||||
version: "1.19"
|
||||
modules: true
|
||||
|
||||
# 自定义依赖步骤
|
||||
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
|
||||
|
||||
# 环境变量配置
|
||||
environment_variables:
|
||||
# 构建环境变量
|
||||
build:
|
||||
RUST_BACKTRACE: "full"
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||||
NODE_ENV: "production"
|
||||
|
||||
# 运行时环境变量(会传递给bake阶段)
|
||||
runtime:
|
||||
DATABASE_URL: "postgresql://user:pass@localhost/db"
|
||||
LOG_LEVEL: "info"
|
||||
|
||||
# 缓存配置
|
||||
cache:
|
||||
# 缓存目录
|
||||
directories:
|
||||
- path: "/usr/local/cargo"
|
||||
strategy: "persistent" # persistent, build, session
|
||||
compression: "zstd"
|
||||
|
||||
- path: "/root/.npm"
|
||||
strategy: "build"
|
||||
compression: "none"
|
||||
|
||||
- path: "/app/target"
|
||||
strategy: "persistent"
|
||||
compression: "zstd"
|
||||
|
||||
# 缓存策略
|
||||
strategy:
|
||||
ttl_days: 30 # 缓存生存时间
|
||||
max_size_gb: 20 # 最大缓存大小
|
||||
cleanup_policy: "lru" # 清理策略:lru, fifo, size
|
||||
|
||||
# 安全配置
|
||||
security:
|
||||
# 权限限制
|
||||
capabilities:
|
||||
- "NET_BIND_SERVICE"
|
||||
|
||||
# 安全策略
|
||||
seccomp: "strict" # strict, filter, disabled
|
||||
apparmor: true
|
||||
no_new_privileges: true
|
||||
|
||||
# 资源限制
|
||||
limits:
|
||||
max_processes: 512
|
||||
max_file_size: "1G"
|
||||
max_open_files: 1024
|
||||
|
||||
# 钩子脚本
|
||||
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"
|
||||
@@ -0,0 +1,27 @@
|
||||
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(())
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
pub mod prebake;
|
||||
pub mod finalize;
|
||||
pub mod scripts;
|
||||
|
||||
pub use prebake::*;
|
||||
pub use finalize::*;
|
||||
pub use scripts::*;
|
||||
// use log::info;
|
||||
|
||||
// use crate::TemplateResolvable;
|
||||
|
||||
// 完整的流水线配置
|
||||
// #[derive(Debug, Clone, serde::Serialize)]
|
||||
// pub struct PipelineConfig {
|
||||
// pub prebake: PrebakeConfig,
|
||||
// pub bake: BakeScript,
|
||||
// pub finalize: FinalizeConfig,
|
||||
// pub variables: std::collections::HashMap<String, crate::variable::VariableValue>,
|
||||
// }
|
||||
|
||||
// impl PipelineConfig {
|
||||
// /// 从文件系统加载完整配置
|
||||
// pub fn load_from_dir<P: AsRef<std::path::Path>>(
|
||||
// dir: P,
|
||||
// user_id: &str,
|
||||
// pipeline_id: &str,
|
||||
// variable_provider: &dyn crate::variable::VariableProvider,
|
||||
// ) -> crate::error::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 = crate::variable::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(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,99 @@
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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>,
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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>,
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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>,
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
mod environment;
|
||||
mod dependencies;
|
||||
mod security;
|
||||
mod hooks;
|
||||
|
||||
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 build: Option<std::collections::HashMap<String, String>>,
|
||||
pub runtime: Option<std::collections::HashMap<String, String>>,
|
||||
}
|
||||
|
||||
/// 缓存配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CacheConfig {
|
||||
pub directories: Vec<CacheDirectory>,
|
||||
pub strategy: CacheStrategy,
|
||||
}
|
||||
|
||||
/// 缓存目录配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CacheDirectory {
|
||||
pub path: String,
|
||||
pub strategy: String,
|
||||
pub compression: String,
|
||||
}
|
||||
|
||||
/// 缓存策略
|
||||
#[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 Metadata {
|
||||
pub description: String,
|
||||
pub maintainer: String,
|
||||
pub tags: Vec<String>,
|
||||
pub project_type: String,
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 依赖配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DependenciesConfig {
|
||||
pub system: Option<SystemDependencies>,
|
||||
pub languages: Option<LanguageDependencies>,
|
||||
pub custom: Option<Vec<CustomDependency>>,
|
||||
}
|
||||
|
||||
/// 系统依赖
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SystemDependencies {
|
||||
pub packages: Vec<String>,
|
||||
pub repositories: Vec<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 version: String,
|
||||
pub toolchain: String,
|
||||
pub components: Vec<String>,
|
||||
pub targets: Vec<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,
|
||||
}
|
||||
|
||||
/// 自定义依赖
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CustomDependency {
|
||||
pub name: String,
|
||||
pub command: String,
|
||||
pub environment: Option<std::collections::HashMap<String, String>>,
|
||||
pub working_dir: Option<String>,
|
||||
pub timeout: Option<u32>,
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
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 {
|
||||
pub memory_mb: u32,
|
||||
pub cpus: u32,
|
||||
pub arch: String,
|
||||
}
|
||||
|
||||
/// Docker配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DockerConfig {
|
||||
pub base_image: String,
|
||||
pub dockerfile: String,
|
||||
pub build_args: Vec<String>,
|
||||
}
|
||||
|
||||
/// Firecracker配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FirecrackerConfig {
|
||||
pub kernel_image: String,
|
||||
pub rootfs_image: String,
|
||||
pub memory_mb: u32,
|
||||
pub vcpus: u32,
|
||||
pub boot_args: String,
|
||||
}
|
||||
|
||||
/// 自定义构建器配置
|
||||
#[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 {
|
||||
pub cpu: u32,
|
||||
pub memory: String,
|
||||
pub disk: String,
|
||||
pub architecture: String,
|
||||
pub gpu: bool,
|
||||
pub gpu_type: Option<String>,
|
||||
}
|
||||
|
||||
/// 网络配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NetworkConfig {
|
||||
pub enabled: bool,
|
||||
pub outbound: bool,
|
||||
pub dns_servers: Vec<String>,
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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>,
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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>;
|
||||
@@ -0,0 +1,88 @@
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
use crate::{
|
||||
config::prebake::PrebakeConfig,
|
||||
config::finalize::FinalizeConfig,
|
||||
variable::VariableResolver,
|
||||
error::PipelineError,
|
||||
};
|
||||
use regex::Regex;
|
||||
use lazy_static::lazy_static;
|
||||
// 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 格式
|
||||
// for cap in SHELL_VAR_REGEX.captures_iter(&result) {
|
||||
// let var_name = &cap[1];
|
||||
// if let Ok(value) = resolver.resolve_variable(var_name) {
|
||||
// result = result.replace(&cap[0], &value);
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl TemplateResolvable for PrebakeConfig {
|
||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
||||
let mut config = self.clone();
|
||||
|
||||
// 解析环境变量
|
||||
if let Some(ref mut env_vars) = config.environment_variables {
|
||||
if let Some(ref mut build) = env_vars.build {
|
||||
for (_key, value) in build.iter_mut() {
|
||||
*value = value.resolve_template(resolver)?;
|
||||
}
|
||||
}
|
||||
if let Some(ref mut runtime) = env_vars.runtime {
|
||||
for (_key, value) in runtime.iter_mut() {
|
||||
*value = value.resolve_template(resolver)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析自定义依赖
|
||||
if let Some(ref mut deps) = config.dependencies {
|
||||
if let Some(ref mut custom) = deps.custom {
|
||||
for dep in custom.iter_mut() {
|
||||
dep.command = dep.command.resolve_template(resolver)?;
|
||||
if let Some(ref mut env) = dep.environment {
|
||||
for (_key, value) in env.iter_mut() {
|
||||
*value = value.resolve_template(resolver)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
impl TemplateResolvable for FinalizeConfig {
|
||||
fn resolve_template(&self, resolver: &mut VariableResolver) -> Result<Self, PipelineError> {
|
||||
let mut config = self.clone();
|
||||
|
||||
// 解析产物路径
|
||||
if let Some(ref mut artifacts) = config.artifacts {
|
||||
if let Some(ref mut primary) = artifacts.primary {
|
||||
for artifact in primary.iter_mut() {
|
||||
artifact.path = artifact.path.resolve_template(resolver)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析打包配置
|
||||
if let Some(ref mut packaging) = config.packaging {
|
||||
if let Some(ref mut primary) = packaging.primary_package {
|
||||
primary.output = primary.output.resolve_template(resolver)?;
|
||||
}
|
||||
|
||||
if let Some(ref mut docker) = packaging.docker {
|
||||
docker.repository = docker.repository.resolve_template(resolver)?;
|
||||
for tag in docker.tags.iter_mut() {
|
||||
*tag = tag.resolve_template(resolver)?;
|
||||
}
|
||||
for build_arg in docker.build_args.iter_mut() {
|
||||
*build_arg = build_arg.resolve_template(resolver)?;
|
||||
}
|
||||
for (_key, value) in docker.labels.iter_mut() {
|
||||
*value = value.resolve_template(resolver)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析发布配置
|
||||
if let Some(ref mut distribution) = config.distribution {
|
||||
if let Some(ref mut local) = distribution.local {
|
||||
local.directory = local.directory.resolve_template(resolver)?;
|
||||
}
|
||||
|
||||
if let Some(ref mut s3) = distribution.s3 {
|
||||
s3.prefix = s3.prefix.resolve_template(resolver)?;
|
||||
}
|
||||
|
||||
if let Some(ref mut registries) = distribution.package_registries {
|
||||
if let Some(ref mut crates_io) = registries.crates_io {
|
||||
crates_io.token = crates_io.token.resolve_template(resolver)?;
|
||||
}
|
||||
if let Some(ref mut npm) = registries.npm {
|
||||
npm.token = npm.token.resolve_template(resolver)?;
|
||||
}
|
||||
if let Some(ref mut docker_registry) = registries.docker_registry {
|
||||
docker_registry.username = docker_registry.username.resolve_template(resolver)?;
|
||||
docker_registry.password = docker_registry.password.resolve_template(resolver)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析通知配置
|
||||
if let Some(ref mut notifications) = config.notifications {
|
||||
for notification in notifications.on_success.iter_mut() {
|
||||
notification.message = notification.message.resolve_template(resolver)?;
|
||||
if let Some(ref mut webhook) = notification.webhook {
|
||||
*webhook = webhook.resolve_template(resolver)?;
|
||||
}
|
||||
}
|
||||
|
||||
for notification in notifications.on_failure.iter_mut() {
|
||||
notification.message = notification.message.resolve_template(resolver)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl FinalizeConfig {
|
||||
pub fn for_output(&self) -> Self {
|
||||
let mut config = self.clone();
|
||||
|
||||
// 遮蔽secret类型的变量
|
||||
if let Some(ref mut distribution) = config.distribution {
|
||||
if let Some(ref mut registries) = distribution.package_registries {
|
||||
if let Some(ref mut crates_io) = registries.crates_io {
|
||||
crates_io.token = "***".to_string();
|
||||
}
|
||||
if let Some(ref mut npm) = registries.npm {
|
||||
npm.token = "***".to_string();
|
||||
}
|
||||
if let Some(ref mut docker_registry) = registries.docker_registry {
|
||||
docker_registry.username = "***".to_string();
|
||||
docker_registry.password = "***".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
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