# Woodpecker CI - S3 对象存储上传指南 本指南介绍如何在 Woodpecker CI 中使用 S3 插件将文件和文件夹上传到对象存储(兼容 S3 API 的存储服务,如 AWS S3、MinIO、RustFS 等)。 ## 📋 目录 - [前置准备](#前置准备) - [配置密钥](#配置密钥) - [基础用法](#基础用法) - [上传单个文件](#上传单个文件) - [上传多个文件](#上传多个文件) - [上传整个文件夹](#上传整个文件夹) - [高级用法](#高级用法) - [完整示例](#完整示例) - [故障排查](#故障排查) --- ## 前置准备 ### 1. 安装 plugin-s3 确保您的 Woodpecker Agent 环境中已安装 `plugin-s3`: ```bash # 使用 Homebrew (macOS) brew install plugin-s3 # 或使用 Go 安装 go install github.com/woodpecker-ci/plugin-s3@latest # 验证安装 which plugin-s3 ``` ### 2. 准备 S3 凭证 您需要以下信息: - **Access Key ID**: S3 访问密钥 ID - **Secret Access Key**: S3 密钥 - **Bucket 名称**: 目标存储桶 - **Endpoint**: S3 服务端点(如使用 AWS S3 可省略) - **Region**: 区域(如 `us-east-1`) --- ## 配置密钥 ### 通过 Woodpecker UI 配置 1. 进入您的仓库页面 2. 点击 **Settings** → **Secrets** 3. 添加以下密钥: | 密钥名称 | 说明 | 示例值 | |---------|------|--------| | `AWS_ACCESS_KEY_ID` | S3 访问密钥 ID | `AKIAIOSFODNN7EXAMPLE` | | `AWS_SECRET_ACCESS_KEY` | S3 密钥 | `wJalrXUtnFEMI/K7MDENG/...` | | `S3_BUCKET` | 存储桶名称 | `my-bucket` | | `S3_ENDPOINT` | S3 端点(自建服务) | `https://s3.example.com:9000` | | `AWS_DEFAULT_REGION` | AWS 区域 | `us-east-1` | 4. 确保勾选适当的事件类型(如 `push`、`manual`、`pull_request`) ### 通过 CLI 配置 ```bash # 添加 Access Key woodpecker-cli repo secret add \ --repository your-org/your-repo \ --name AWS_ACCESS_KEY_ID \ --value "your-access-key-id" # 添加 Secret Key woodpecker-cli repo secret add \ --repository your-org/your-repo \ --name AWS_SECRET_ACCESS_KEY \ --value "your-secret-access-key" # 添加 Bucket woodpecker-cli repo secret add \ --repository your-org/your-repo \ --name S3_BUCKET \ --value "my-bucket" # 添加 Endpoint (可选,用于自建 S3 服务) woodpecker-cli repo secret add \ --repository your-org/your-repo \ --name S3_ENDPOINT \ --value "https://s3.example.com:9000" # 添加 Region woodpecker-cli repo secret add \ --repository your-org/your-repo \ --name AWS_DEFAULT_REGION \ --value "us-east-1" ``` --- ## 基础用法 ### 上传单个文件 上传单个文件到 S3 存储桶: ```yaml steps: - name: upload-single-file image: /bin/zsh environment: AWS_ACCESS_KEY_ID: from_secret: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY: from_secret: AWS_SECRET_ACCESS_KEY S3_BUCKET: from_secret: S3_BUCKET S3_ENDPOINT: from_secret: S3_ENDPOINT commands: - | # 创建测试文件 echo "Hello from Woodpecker CI" > hello.txt # 上传到 S3 export PLUGIN_SOURCE="hello.txt" export PLUGIN_BUCKET="$S3_BUCKET" export PLUGIN_TARGET="uploads/" export PLUGIN_ENDPOINT="$S3_ENDPOINT" export PLUGIN_PATH_STYLE=true plugin-s3 ``` **结果**: `hello.txt` 将被上传到 `s3://your-bucket/uploads/hello.txt` --- ### 上传多个文件 使用通配符上传多个文件: ```yaml steps: - name: upload-multiple-files image: /bin/zsh environment: AWS_ACCESS_KEY_ID: from_secret: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY: from_secret: AWS_SECRET_ACCESS_KEY S3_BUCKET: from_secret: S3_BUCKET S3_ENDPOINT: from_secret: S3_ENDPOINT commands: - | # 创建多个测试文件 echo "File 1" > file1.txt echo "File 2" > file2.txt echo "File 3" > file3.log # 上传所有 .txt 文件 export PLUGIN_SOURCE="*.txt" export PLUGIN_BUCKET="$S3_BUCKET" export PLUGIN_TARGET="logs/" export PLUGIN_ENDPOINT="$S3_ENDPOINT" export PLUGIN_PATH_STYLE=true plugin-s3 ``` **结果**: 所有 `.txt` 文件将被上传到 `s3://your-bucket/logs/` --- ### 上传整个文件夹 递归上传整个目录及其子目录: ```yaml steps: - name: upload-folder image: /bin/zsh environment: AWS_ACCESS_KEY_ID: from_secret: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY: from_secret: AWS_SECRET_ACCESS_KEY S3_BUCKET: from_secret: S3_BUCKET S3_ENDPOINT: from_secret: S3_ENDPOINT commands: - | # 创建目录结构 mkdir -p dist/css dist/js dist/images echo "body { color: red; }" > dist/css/style.css echo "console.log('hello');" > dist/js/app.js echo "placeholder" > dist/images/logo.png echo "Hello" > dist/index.html # 上传整个 dist 文件夹 export PLUGIN_SOURCE="dist/**/*" export PLUGIN_BUCKET="$S3_BUCKET" export PLUGIN_TARGET="website/" export PLUGIN_ENDPOINT="$S3_ENDPOINT" export PLUGIN_PATH_STYLE=true export PLUGIN_STRIP_PREFIX="dist/" plugin-s3 ``` **结果**: ``` s3://your-bucket/website/css/style.css s3://your-bucket/website/js/app.js s3://your-bucket/website/images/logo.png s3://your-bucket/website/index.html ``` --- ## 高级用法 ### 设置文件访问权限 ```yaml export PLUGIN_ACL="public-read" # 公开可读 # 可选值: private, public-read, public-read-write, authenticated-read ``` ### 设置缓存控制 ```yaml export PLUGIN_CACHE_CONTROL="max-age=3600" # 缓存 1 小时 ``` ### 设置内容类型 ```yaml export PLUGIN_CONTENT_TYPE="text/html" export PLUGIN_CONTENT_ENCODING="gzip" ``` ### 删除目标文件夹中的旧文件 ```yaml export PLUGIN_DELETE=true # 上传前删除目标路径的现有文件 ``` ### 使用服务器端加密 ```yaml export PLUGIN_ENCRYPTION="AES256" # 或 "aws:kms" ``` ### 完整高级示例 ```yaml steps: - name: deploy-website image: /bin/zsh environment: AWS_ACCESS_KEY_ID: from_secret: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY: from_secret: AWS_SECRET_ACCESS_KEY S3_BUCKET: from_secret: S3_BUCKET S3_ENDPOINT: from_secret: S3_ENDPOINT commands: - | export PLUGIN_SOURCE="public/**/*" export PLUGIN_BUCKET="$S3_BUCKET" export PLUGIN_TARGET="production/" export PLUGIN_ENDPOINT="$S3_ENDPOINT" export PLUGIN_PATH_STYLE=true export PLUGIN_STRIP_PREFIX="public/" export PLUGIN_ACL="public-read" export PLUGIN_CACHE_CONTROL="max-age=31536000" export PLUGIN_DELETE=true plugin-s3 ``` --- ## 完整示例 ### 最小化示例 - `.woodpecker.yml` 这是一个完整的 Woodpecker 配置文件,演示如何上传文件和文件夹: ```yaml # .woodpecker.yml labels: platform: linux/amd64 when: event: [push, manual] steps: # 第一步:构建项目(示例) - name: build image: node:18-alpine commands: - echo "Building project..." - mkdir -p dist/assets - echo '

Hello World

' > dist/index.html - echo 'body { font-family: Arial; }' > dist/assets/style.css - echo 'console.log("App loaded");' > dist/assets/app.js - echo "Build complete!" - ls -R dist/ # 第二步:上传单个日志文件 - name: upload-build-log image: alpine:latest environment: AWS_ACCESS_KEY_ID: from_secret: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY: from_secret: AWS_SECRET_ACCESS_KEY S3_BUCKET: from_secret: S3_BUCKET S3_ENDPOINT: from_secret: S3_ENDPOINT commands: - | # 创建构建日志 date > build.log echo "Build completed successfully" >> build.log # 上传日志文件 export PLUGIN_SOURCE="build.log" export PLUGIN_BUCKET="$S3_BUCKET" export PLUGIN_TARGET="logs/build-${CI_COMMIT_SHA:0:8}.log" export PLUGIN_ENDPOINT="$S3_ENDPOINT" export PLUGIN_PATH_STYLE=true plugin-s3 echo "✅ Build log uploaded to s3://$S3_BUCKET/logs/" # 第三步:上传整个 dist 文件夹 - name: upload-dist-folder image: alpine:latest environment: AWS_ACCESS_KEY_ID: from_secret: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY: from_secret: AWS_SECRET_ACCESS_KEY S3_BUCKET: from_secret: S3_BUCKET S3_ENDPOINT: from_secret: S3_ENDPOINT commands: - | # 上传整个 dist 目录 export PLUGIN_SOURCE="dist/**/*" export PLUGIN_BUCKET="$S3_BUCKET" export PLUGIN_TARGET="website/${CI_COMMIT_BRANCH}/" export PLUGIN_ENDPOINT="$S3_ENDPOINT" export PLUGIN_PATH_STYLE=true export PLUGIN_STRIP_PREFIX="dist/" export PLUGIN_ACL="public-read" plugin-s3 echo "✅ Website deployed to s3://$S3_BUCKET/website/${CI_COMMIT_BRANCH}/" # 第四步:上传构建产物(压缩包) - name: upload-artifacts image: alpine:latest environment: AWS_ACCESS_KEY_ID: from_secret: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY: from_secret: AWS_SECRET_ACCESS_KEY S3_BUCKET: from_secret: S3_BUCKET S3_ENDPOINT: from_secret: S3_ENDPOINT commands: - | # 创建压缩包 apk add --no-cache zip zip -r dist-${CI_COMMIT_SHA:0:8}.zip dist/ # 上传压缩包 export PLUGIN_SOURCE="dist-*.zip" export PLUGIN_BUCKET="$S3_BUCKET" export PLUGIN_TARGET="releases/${CI_COMMIT_BRANCH}/" export PLUGIN_ENDPOINT="$S3_ENDPOINT" export PLUGIN_PATH_STYLE=true plugin-s3 echo "✅ Artifacts uploaded to s3://$S3_BUCKET/releases/${CI_COMMIT_BRANCH}/" ``` --- ### macOS 本地 Agent 示例 如果您使用本地 macOS agent(如您的 Mac mini),配置略有不同: ```yaml # .woodpecker.yml (macOS Local Agent) labels: host: Mac-mini.local platform: darwin/arm64 when: event: [push, manual] steps: - name: build-app image: /bin/zsh commands: - echo "Building on macOS..." - mkdir -p build/output - echo "Binary placeholder" > build/output/app - echo "Config file" > build/output/config.json - ls -R build/ - name: upload-single-file image: /bin/zsh environment: AWS_ACCESS_KEY_ID: from_secret: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY: from_secret: AWS_SECRET_ACCESS_KEY S3_BUCKET: from_secret: S3_BUCKET S3_ENDPOINT: from_secret: S3_ENDPOINT commands: - | echo "📦 Uploading config file..." export PLUGIN_SOURCE="build/output/config.json" export PLUGIN_BUCKET="$S3_BUCKET" export PLUGIN_TARGET="configs/" export PLUGIN_ENDPOINT="$S3_ENDPOINT" export PLUGIN_PATH_STYLE=true plugin-s3 echo "✅ Config uploaded" - name: upload-build-folder image: /bin/zsh environment: AWS_ACCESS_KEY_ID: from_secret: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY: from_secret: AWS_SECRET_ACCESS_KEY S3_BUCKET: from_secret: S3_BUCKET S3_ENDPOINT: from_secret: S3_ENDPOINT commands: - | echo "📦 Uploading entire build folder..." export PLUGIN_SOURCE="build/output/**/*" export PLUGIN_BUCKET="$S3_BUCKET" export PLUGIN_TARGET="builds/macos-$(date +%Y%m%d-%H%M%S)/" export PLUGIN_ENDPOINT="$S3_ENDPOINT" export PLUGIN_PATH_STYLE=true export PLUGIN_STRIP_PREFIX="build/output/" plugin-s3 echo "✅ Build folder uploaded" ``` --- ## 故障排查 ### 问题 1: 密钥未传递 **错误信息**: `No S3 credentials found` **解决方案**: - ✅ 确保使用 `from_secret:` 语法而不是 `${SECRET_NAME}` - ✅ 检查 Woodpecker UI 中密钥名称是否完全匹配 - ✅ 确认密钥事件类型包含当前触发事件(如 `manual`) ```yaml # ❌ 错误 environment: AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} # ✅ 正确 environment: AWS_ACCESS_KEY_ID: from_secret: AWS_ACCESS_KEY_ID ``` ### 问题 2: plugin-s3 未找到 **错误信息**: `plugin-s3: command not found` **解决方案**: ```bash # 安装 plugin-s3 brew install plugin-s3 # 或添加到 PATH export PATH=$PATH:/usr/local/bin ``` ### 问题 3: 端点连接失败 **错误信息**: `connection refused` 或 `timeout` **解决方案**: - ✅ 检查 `S3_ENDPOINT` 格式(需包含协议,如 `https://`) - ✅ 确认防火墙规则允许访问 - ✅ 验证 SSL 证书(自签名证书可能需要额外配置) ### 问题 4: 权限被拒绝 **错误信息**: `Access Denied` 或 `403 Forbidden` **解决方案**: - ✅ 验证 Access Key 和 Secret Key 是否正确 - ✅ 检查 IAM 策略是否允许 `s3:PutObject` 权限 - ✅ 确认 bucket 存在且有写入权限 ### 调试技巧 添加调试输出以检查环境变量: ```yaml commands: - | echo "=== 调试信息 ===" echo "Bucket: ${S3_BUCKET}" echo "Endpoint: ${S3_ENDPOINT}" echo "Access Key (前10位): ${AWS_ACCESS_KEY_ID:0:10}..." echo "=================" # 继续执行上传... ``` --- ## 环境变量参考 | 变量名 | 说明 | 必需 | 示例 | |--------|------|------|------| | `PLUGIN_SOURCE` | 源文件路径(支持通配符) | ✅ | `dist/**/*` | | `PLUGIN_BUCKET` | 目标 bucket | ✅ | `my-bucket` | | `PLUGIN_TARGET` | 目标路径前缀 | ❌ | `uploads/` | | `PLUGIN_ENDPOINT` | S3 端点 URL | ❌ | `https://s3.example.com` | | `PLUGIN_PATH_STYLE` | 使用路径风格 URL | ❌ | `true` | | `PLUGIN_STRIP_PREFIX` | 移除源路径前缀 | ❌ | `dist/` | | `PLUGIN_ACL` | 访问控制列表 | ❌ | `public-read` | | `PLUGIN_CACHE_CONTROL` | 缓存控制头 | ❌ | `max-age=3600` | | `PLUGIN_DELETE` | 上传前删除目标文件 | ❌ | `true` | | `PLUGIN_ENCRYPTION` | 服务器端加密 | ❌ | `AES256` | --- ## 总结 本指南涵盖了在 Woodpecker CI 中使用 S3 上传的所有常见场景: ✅ **单文件上传**: 适用于日志、配置文件等 ✅ **多文件上传**: 使用通配符批量上传 ✅ **文件夹上传**: 递归上传整个目录结构 ✅ **高级配置**: ACL、缓存、加密等 如有问题,请参考 [Woodpecker 官方文档](https://woodpecker-ci.org/docs/) 或提交 Issue。 --- **License**: MIT **维护者**: Your Team **最后更新**: 2025-10-12