6797a1ee2d
ci/woodpecker/push/woodpecker Pipeline failed
- validate-woodpecker.sh: 全面验证配置文件 - test-step.sh: 单步测试工具,支持 Docker 隔离环境 - README.md: 详细使用文档和最佳实践 这些工具可以在本地验证和测试 CI/CD 配置,避免通过持续提交来测试
254 lines
7.2 KiB
Bash
Executable File
254 lines
7.2 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
set -e
|
||
|
||
WOODPECKER_FILE=".woodpecker.yml"
|
||
|
||
echo "=========================================="
|
||
echo "Woodpecker CI 配置本地验证工具"
|
||
echo "=========================================="
|
||
echo ""
|
||
|
||
# 检查文件是否存在
|
||
if [ ! -f "$WOODPECKER_FILE" ]; then
|
||
echo "❌ 错误: $WOODPECKER_FILE 文件不存在"
|
||
exit 1
|
||
fi
|
||
|
||
echo "✅ 文件存在: $WOODPECKER_FILE"
|
||
echo ""
|
||
|
||
# 1. YAML 语法检查
|
||
echo "1️⃣ YAML 语法检查"
|
||
echo "----------------------------------------"
|
||
if command -v python3 &> /dev/null; then
|
||
if python3 -c "import yaml; yaml.safe_load(open('$WOODPECKER_FILE'))" 2>&1; then
|
||
echo "✅ YAML 语法正确"
|
||
else
|
||
echo "❌ YAML 语法错误"
|
||
exit 1
|
||
fi
|
||
else
|
||
echo "⚠️ Python3 未安装,跳过 YAML 语法检查"
|
||
fi
|
||
echo ""
|
||
|
||
# 2. 检查必需的字段
|
||
echo "2️⃣ 检查必需字段"
|
||
echo "----------------------------------------"
|
||
python3 << 'PYTHON_SCRIPT'
|
||
import yaml
|
||
import sys
|
||
|
||
with open('.woodpecker.yml', 'r') as f:
|
||
config = yaml.safe_load(f)
|
||
|
||
errors = []
|
||
warnings = []
|
||
|
||
# 检查 steps
|
||
if 'steps' not in config:
|
||
errors.append("缺少 'steps' 字段")
|
||
else:
|
||
steps = config['steps']
|
||
if not steps:
|
||
errors.append("'steps' 不能为空")
|
||
else:
|
||
for step_name, step_config in steps.items():
|
||
# 检查 image
|
||
if 'image' not in step_config:
|
||
errors.append(f"步骤 '{step_name}' 缺少 'image' 字段")
|
||
|
||
# 检查 commands
|
||
if 'commands' not in step_config:
|
||
warnings.append(f"步骤 '{step_name}' 没有 'commands' 字段")
|
||
|
||
# 检查 when 条件
|
||
if 'when' in step_config:
|
||
when = step_config['when']
|
||
if 'branch' in when:
|
||
print(f" ✅ 步骤 '{step_name}' 有分支条件: {when['branch']}")
|
||
if 'event' in when:
|
||
print(f" ✅ 步骤 '{step_name}' 有事件条件: {when['event']}")
|
||
|
||
# 检查 workspace
|
||
if 'workspace' in config:
|
||
print(f" ✅ workspace 配置: {config['workspace']}")
|
||
|
||
# 检查 clone
|
||
if 'clone' in config:
|
||
print(f" ✅ clone 配置: {config['clone']}")
|
||
|
||
if errors:
|
||
print("\n❌ 错误:")
|
||
for error in errors:
|
||
print(f" - {error}")
|
||
sys.exit(1)
|
||
|
||
if warnings:
|
||
print("\n⚠️ 警告:")
|
||
for warning in warnings:
|
||
print(f" - {warning}")
|
||
|
||
print("\n✅ 所有必需字段检查通过")
|
||
PYTHON_SCRIPT
|
||
|
||
if [ $? -ne 0 ]; then
|
||
exit 1
|
||
fi
|
||
echo ""
|
||
|
||
# 3. 检查镜像是否存在
|
||
echo "3️⃣ 检查镜像格式"
|
||
echo "----------------------------------------"
|
||
python3 << 'PYTHON_SCRIPT'
|
||
import yaml
|
||
import re
|
||
|
||
with open('.woodpecker.yml', 'r') as f:
|
||
config = yaml.safe_load(f)
|
||
|
||
steps = config.get('steps', {})
|
||
image_pattern = re.compile(r'^[a-z0-9\-_./]+(?::[a-z0-9\-_.]+)?$')
|
||
|
||
for step_name, step_config in steps.items():
|
||
image = step_config.get('image', '')
|
||
if image:
|
||
# 检查镜像格式
|
||
if image.startswith('*'):
|
||
# YAML anchor,跳过
|
||
print(f" ℹ️ 步骤 '{step_name}' 使用 YAML anchor: {image}")
|
||
elif image_pattern.match(image):
|
||
print(f" ✅ 步骤 '{step_name}' 镜像格式正确: {image}")
|
||
else:
|
||
print(f" ⚠️ 步骤 '{step_name}' 镜像格式可能有问题: {image}")
|
||
|
||
print("\n✅ 镜像格式检查完成")
|
||
PYTHON_SCRIPT
|
||
echo ""
|
||
|
||
# 4. 检查环境变量和 secrets
|
||
echo "4️⃣ 检查环境变量和 secrets"
|
||
echo "----------------------------------------"
|
||
python3 << 'PYTHON_SCRIPT'
|
||
import yaml
|
||
|
||
with open('.woodpecker.yml', 'r') as f:
|
||
config = yaml.safe_load(f)
|
||
|
||
steps = config.get('steps', {})
|
||
secrets_used = set()
|
||
|
||
for step_name, step_config in steps.items():
|
||
env = step_config.get('environment', {})
|
||
if isinstance(env, dict):
|
||
for key, value in env.items():
|
||
if isinstance(value, dict) and 'from_secret' in value:
|
||
secret_name = value['from_secret']
|
||
secrets_used.add(secret_name)
|
||
print(f" 🔐 步骤 '{step_name}' 使用 secret: {secret_name}")
|
||
|
||
if secrets_used:
|
||
print(f"\n📋 需要配置的 secrets:")
|
||
for secret in sorted(secrets_used):
|
||
print(f" - {secret}")
|
||
print("\n⚠️ 请确保在 Woodpecker CI 中配置了这些 secrets")
|
||
else:
|
||
print(" ℹ️ 没有使用 secrets")
|
||
|
||
print("\n✅ 环境变量检查完成")
|
||
PYTHON_SCRIPT
|
||
echo ""
|
||
|
||
# 5. 检查 when 条件的逻辑
|
||
echo "5️⃣ 检查 when 条件逻辑"
|
||
echo "----------------------------------------"
|
||
python3 << 'PYTHON_SCRIPT'
|
||
import yaml
|
||
|
||
with open('.woodpecker.yml', 'r') as f:
|
||
config = yaml.safe_load(f)
|
||
|
||
steps = config.get('steps', {})
|
||
steps_with_conditions = []
|
||
steps_without_conditions = []
|
||
|
||
for step_name, step_config in steps.items():
|
||
if 'when' in step_config:
|
||
steps_with_conditions.append(step_name)
|
||
else:
|
||
steps_without_conditions.append(step_name)
|
||
|
||
if steps_with_conditions:
|
||
print(f" ✅ 有条件执行的步骤 ({len(steps_with_conditions)}):")
|
||
for step in steps_with_conditions:
|
||
print(f" - {step}")
|
||
|
||
if steps_without_conditions:
|
||
print(f"\n ⚠️ 无条件执行的步骤 ({len(steps_without_conditions)}):")
|
||
for step in steps_without_conditions:
|
||
print(f" - {step}")
|
||
print("\n 💡 建议: 大部分步骤应该有 when 条件,避免不必要的执行")
|
||
|
||
print("\n✅ when 条件检查完成")
|
||
PYTHON_SCRIPT
|
||
echo ""
|
||
|
||
# 6. 模拟执行顺序
|
||
echo "6️⃣ 模拟执行顺序"
|
||
echo "----------------------------------------"
|
||
python3 << 'PYTHON_SCRIPT'
|
||
import yaml
|
||
|
||
with open('.woodpecker.yml', 'r') as f:
|
||
config = yaml.safe_load(f)
|
||
|
||
steps = config.get('steps', {})
|
||
|
||
print(" 📋 步骤执行顺序:")
|
||
for i, (step_name, step_config) in enumerate(steps.items(), 1):
|
||
image = step_config.get('image', 'N/A')
|
||
when = step_config.get('when', {})
|
||
|
||
conditions = []
|
||
if 'branch' in when:
|
||
branches = when['branch']
|
||
if isinstance(branches, list):
|
||
conditions.append(f"branch: {', '.join(branches)}")
|
||
else:
|
||
conditions.append(f"branch: {branches}")
|
||
|
||
if 'event' in when:
|
||
events = when['event']
|
||
if isinstance(events, list):
|
||
conditions.append(f"event: {', '.join(events)}")
|
||
else:
|
||
conditions.append(f"event: {events}")
|
||
|
||
if 'status' in when:
|
||
statuses = when['status']
|
||
if isinstance(statuses, list):
|
||
conditions.append(f"status: {', '.join(statuses)}")
|
||
else:
|
||
conditions.append(f"status: {statuses}")
|
||
|
||
condition_str = f" [{', '.join(conditions)}]" if conditions else ""
|
||
print(f" {i}. {step_name}{condition_str}")
|
||
print(f" 镜像: {image}")
|
||
|
||
print("\n✅ 执行顺序分析完成")
|
||
PYTHON_SCRIPT
|
||
echo ""
|
||
|
||
echo "=========================================="
|
||
echo "✅ 所有检查完成!"
|
||
echo "=========================================="
|
||
echo ""
|
||
echo "💡 提示:"
|
||
echo " - 如果所有检查都通过,配置文件基本正确"
|
||
echo " - 建议使用 Woodpecker CLI 进行本地测试:"
|
||
echo " woodpecker-cli exec .woodpecker.yml"
|
||
echo " - 或者使用 Docker:"
|
||
echo " docker run --rm -v \$(pwd):/woodpecker/src -w /woodpecker/src woodpeckerci/woodpecker-cli:latest exec .woodpecker.yml"
|
||
echo ""
|