diff --git a/.gitea/workflows/test-cache-state.yml b/.gitea/workflows/test-cache-state.yml index e0330a6..2da79a3 100644 --- a/.gitea/workflows/test-cache-state.yml +++ b/.gitea/workflows/test-cache-state.yml @@ -365,226 +365,226 @@ jobs: fi # 过期时间测试 - test-expiry: - if: ${{ inputs.test_scope == 'expiry' || inputs.test_scope == 'full' }} - runs-on: ubuntu-node-20 - steps: - - name: 检出代码 - uses: actions/checkout@v4 + # test-expiry: + # if: ${{ inputs.test_scope == 'expiry' || inputs.test_scope == 'full' }} + # runs-on: ubuntu-node-20 + # steps: + # - name: 检出代码 + # uses: actions/checkout@v4 - - name: 测试基础过期功能 - 短期缓存 - id: short-expiry - uses: actions/xgj/cache-state@v1 - with: - state-key: 'short-expiry-test-${{ github.run_id }}' - state-value: 'short-value' - default-value: 'default-short' - expiry-seconds: '120' # 2分钟 - cache-prefix: ${{ inputs.test_prefix }} + # - name: 测试基础过期功能 - 短期缓存 + # id: short-expiry + # uses: actions/xgj/cache-state@v1 + # with: + # state-key: 'short-expiry-test-${{ github.run_id }}' + # state-value: 'short-value' + # default-value: 'default-short' + # expiry-seconds: '120' # 2分钟 + # cache-prefix: ${{ inputs.test_prefix }} - - name: 验证短期缓存设置 - run: | - echo "🕐 短期缓存测试结果:" - echo "状态值: ${{ steps.short-expiry.outputs.state-value }}" - echo "缓存命中: ${{ steps.short-expiry.outputs.cache-hit }}" - echo "过期状态: ${{ steps.short-expiry.outputs.expired }}" - echo "缓存键: ${{ steps.short-expiry.outputs.cache-key }}" + # - name: 验证短期缓存设置 + # run: | + # echo "🕐 短期缓存测试结果:" + # echo "状态值: ${{ steps.short-expiry.outputs.state-value }}" + # echo "缓存命中: ${{ steps.short-expiry.outputs.cache-hit }}" + # echo "过期状态: ${{ steps.short-expiry.outputs.expired }}" + # echo "缓存键: ${{ steps.short-expiry.outputs.cache-key }}" - # 验证缓存键包含时间窗口(永不过期缓存键格式:Linux-test-state-short-expiry-test-1038,有过期时间格式:Linux-test-state-short-expiry-test-1038-12345) - # 检查是否有额外的时间窗口后缀(在run_id之后) - if [[ "${{ steps.short-expiry.outputs.cache-key }}" =~ -[0-9]+-[0-9]+$ ]]; then - echo "✅ 缓存键包含时间窗口标识" - else - echo "❌ 缓存键缺少时间窗口标识: ${{ steps.short-expiry.outputs.cache-key }}" - echo "期望格式: Linux-test-state-short-expiry-test-{run_id}-{time_window}" - exit 1 - fi + # # 验证缓存键包含时间窗口(永不过期缓存键格式:Linux-test-state-short-expiry-test-1038,有过期时间格式:Linux-test-state-short-expiry-test-1038-12345) + # # 检查是否有额外的时间窗口后缀(在run_id之后) + # if [[ "${{ steps.short-expiry.outputs.cache-key }}" =~ -[0-9]+-[0-9]+$ ]]; then + # echo "✅ 缓存键包含时间窗口标识" + # else + # echo "❌ 缓存键缺少时间窗口标识: ${{ steps.short-expiry.outputs.cache-key }}" + # echo "期望格式: Linux-test-state-short-expiry-test-{run_id}-{time_window}" + # exit 1 + # fi - - name: 测试永不过期缓存 - id: no-expiry - uses: actions/xgj/cache-state@v1 - with: - state-key: 'no-expiry-test-${{ github.run_id }}' - state-value: 'permanent-value' - default-value: 'default-permanent' - expiry-seconds: '0' # 永不过期 - cache-prefix: ${{ inputs.test_prefix }} + # - name: 测试永不过期缓存 + # id: no-expiry + # uses: actions/xgj/cache-state@v1 + # with: + # state-key: 'no-expiry-test-${{ github.run_id }}' + # state-value: 'permanent-value' + # default-value: 'default-permanent' + # expiry-seconds: '0' # 永不过期 + # cache-prefix: ${{ inputs.test_prefix }} - - name: 验证永不过期缓存 - run: | - echo "♾️ 永不过期缓存测试结果:" - echo "状态值: ${{ steps.no-expiry.outputs.state-value }}" - echo "缓存命中: ${{ steps.no-expiry.outputs.cache-hit }}" - echo "过期状态: ${{ steps.no-expiry.outputs.expired }}" - echo "缓存键: ${{ steps.no-expiry.outputs.cache-key }}" + # - name: 验证永不过期缓存 + # run: | + # echo "♾️ 永不过期缓存测试结果:" + # echo "状态值: ${{ steps.no-expiry.outputs.state-value }}" + # echo "缓存命中: ${{ steps.no-expiry.outputs.cache-hit }}" + # echo "过期状态: ${{ steps.no-expiry.outputs.expired }}" + # echo "缓存键: ${{ steps.no-expiry.outputs.cache-key }}" - # 验证缓存键不包含时间窗口(永不过期应该只有一个数字后缀run_id,而不是两个数字后缀) - if [[ "${{ steps.no-expiry.outputs.cache-key }}" =~ -[0-9]+$ ]] && [[ ! "${{ steps.no-expiry.outputs.cache-key }}" =~ -[0-9]+-[0-9]+$ ]]; then - echo "✅ 永不过期缓存键格式正确" - else - echo "❌ 永不过期缓存键格式错误: ${{ steps.no-expiry.outputs.cache-key }}" - echo "期望格式: Linux-test-state-no-expiry-test-{run_id}(只有一个数字后缀)" - exit 1 - fi + # # 验证缓存键不包含时间窗口(永不过期应该只有一个数字后缀run_id,而不是两个数字后缀) + # if [[ "${{ steps.no-expiry.outputs.cache-key }}" =~ -[0-9]+$ ]] && [[ ! "${{ steps.no-expiry.outputs.cache-key }}" =~ -[0-9]+-[0-9]+$ ]]; then + # echo "✅ 永不过期缓存键格式正确" + # else + # echo "❌ 永不过期缓存键格式错误: ${{ steps.no-expiry.outputs.cache-key }}" + # echo "期望格式: Linux-test-state-no-expiry-test-{run_id}(只有一个数字后缀)" + # exit 1 + # fi - # 验证过期状态 - if [[ "${{ steps.no-expiry.outputs.expired }}" == "false" ]]; then - echo "✅ 永不过期缓存过期状态正确" - else - echo "❌ 永不过期缓存不应显示为过期" - exit 1 - fi + # # 验证过期状态 + # if [[ "${{ steps.no-expiry.outputs.expired }}" == "false" ]]; then + # echo "✅ 永不过期缓存过期状态正确" + # else + # echo "❌ 永不过期缓存不应显示为过期" + # exit 1 + # fi - - name: 测试不同过期时间的缓存键差异 - run: | - echo "🔍 测试不同过期时间的缓存键生成..." + # - name: 测试不同过期时间的缓存键差异 + # run: | + # echo "🔍 测试不同过期时间的缓存键生成..." - # 记录当前时间窗口 - CURRENT_TIME=$(date +%s) - WINDOW_120=$((CURRENT_TIME / 120)) - WINDOW_300=$((CURRENT_TIME / 300)) + # # 记录当前时间窗口 + # CURRENT_TIME=$(date +%s) + # WINDOW_120=$((CURRENT_TIME / 120)) + # WINDOW_300=$((CURRENT_TIME / 300)) - echo "当前时间: ${CURRENT_TIME}" - echo "120秒窗口: ${WINDOW_120}" - echo "300秒窗口: ${WINDOW_300}" + # echo "当前时间: ${CURRENT_TIME}" + # echo "120秒窗口: ${WINDOW_120}" + # echo "300秒窗口: ${WINDOW_300}" - # 分析已测试的缓存键格式 - echo "" - echo "📋 已测试的缓存键格式分析:" - echo "短期缓存键: ${{ steps.short-expiry.outputs.cache-key }}" - echo "永不过期键: ${{ steps.no-expiry.outputs.cache-key }}" + # # 分析已测试的缓存键格式 + # echo "" + # echo "📋 已测试的缓存键格式分析:" + # echo "短期缓存键: ${{ steps.short-expiry.outputs.cache-key }}" + # echo "永不过期键: ${{ steps.no-expiry.outputs.cache-key }}" - # 提取缓存键的最后两个数字段 - SHORT_KEY="${{ steps.short-expiry.outputs.cache-key }}" - NO_EXPIRY_KEY="${{ steps.no-expiry.outputs.cache-key }}" + # # 提取缓存键的最后两个数字段 + # SHORT_KEY="${{ steps.short-expiry.outputs.cache-key }}" + # NO_EXPIRY_KEY="${{ steps.no-expiry.outputs.cache-key }}" - # 使用sed提取最后的数字段 - SHORT_LAST_NUM=$(echo "${SHORT_KEY}" | sed 's/.*-\([0-9]*\)$/\1/') - NO_EXPIRY_LAST_NUM=$(echo "${NO_EXPIRY_KEY}" | sed 's/.*-\([0-9]*\)$/\1/') + # # 使用sed提取最后的数字段 + # SHORT_LAST_NUM=$(echo "${SHORT_KEY}" | sed 's/.*-\([0-9]*\)$/\1/') + # NO_EXPIRY_LAST_NUM=$(echo "${NO_EXPIRY_KEY}" | sed 's/.*-\([0-9]*\)$/\1/') - echo "短期缓存最后数字: ${SHORT_LAST_NUM}" - echo "永不过期最后数字: ${NO_EXPIRY_LAST_NUM}" + # echo "短期缓存最后数字: ${SHORT_LAST_NUM}" + # echo "永不过期最后数字: ${NO_EXPIRY_LAST_NUM}" - # 如果时间窗口不同,说明过期时间影响了缓存键 - if [[ ${WINDOW_120} != ${WINDOW_300} ]]; then - echo "✅ 不同过期时间产生不同的时间窗口" - else - echo "ℹ️ 当前时间点两个时间窗口相同(正常情况)" - fi + # # 如果时间窗口不同,说明过期时间影响了缓存键 + # if [[ ${WINDOW_120} != ${WINDOW_300} ]]; then + # echo "✅ 不同过期时间产生不同的时间窗口" + # else + # echo "ℹ️ 当前时间点两个时间窗口相同(正常情况)" + # fi - - name: 测试额外的过期时间缓存键 - id: extra-expiry-test - uses: actions/xgj/cache-state@v1 - with: - state-key: 'extra-expiry-validation' - state-value: 'extra-value' - default-value: 'extra-default' - expiry-seconds: '300' - action: 'set' - cache-prefix: ${{ inputs.test_prefix }} + # - name: 测试额外的过期时间缓存键 + # id: extra-expiry-test + # uses: actions/xgj/cache-state@v1 + # with: + # state-key: 'extra-expiry-validation' + # state-value: 'extra-value' + # default-value: 'extra-default' + # expiry-seconds: '300' + # action: 'set' + # cache-prefix: ${{ inputs.test_prefix }} - - name: 验证额外过期测试的缓存键格式 - run: | - echo "🔍 验证额外过期测试缓存键:" - echo "额外过期测试键: ${{ steps.extra-expiry-test.outputs.cache-key }}" + # - name: 验证额外过期测试的缓存键格式 + # run: | + # echo "🔍 验证额外过期测试缓存键:" + # echo "额外过期测试键: ${{ steps.extra-expiry-test.outputs.cache-key }}" - # 验证这个缓存键是否有时间窗口 - if [[ "${{ steps.extra-expiry-test.outputs.cache-key }}" =~ -[0-9]+-[0-9]+$ ]]; then - echo "✅ 额外过期测试缓存键包含时间窗口" - else - echo "❌ 额外过期测试缓存键缺少时间窗口: ${{ steps.extra-expiry-test.outputs.cache-key }}" - fi + # # 验证这个缓存键是否有时间窗口 + # if [[ "${{ steps.extra-expiry-test.outputs.cache-key }}" =~ -[0-9]+-[0-9]+$ ]]; then + # echo "✅ 额外过期测试缓存键包含时间窗口" + # else + # echo "❌ 额外过期测试缓存键缺少时间窗口: ${{ steps.extra-expiry-test.outputs.cache-key }}" + # fi - - name: 测试过期状态判断 - id: expiry-check - uses: actions/xgj/cache-state@v1 - with: - state-key: 'non-existent-expiry-test' - default-value: 'fallback-value' - expiry-seconds: '60' - action: 'get' - cache-prefix: ${{ inputs.test_prefix }} + # - name: 测试过期状态判断 + # id: expiry-check + # uses: actions/xgj/cache-state@v1 + # with: + # state-key: 'non-existent-expiry-test' + # default-value: 'fallback-value' + # expiry-seconds: '60' + # action: 'get' + # cache-prefix: ${{ inputs.test_prefix }} - - name: 验证过期状态判断 - run: | - echo "🔎 过期状态判断测试结果:" - echo "状态值: ${{ steps.expiry-check.outputs.state-value }}" - echo "缓存命中: ${{ steps.expiry-check.outputs.cache-hit }}" - echo "过期状态: ${{ steps.expiry-check.outputs.expired }}" - echo "使用默认值: ${{ steps.expiry-check.outputs.used-default }}" - echo "缓存键: ${{ steps.expiry-check.outputs.cache-key }}" + # - name: 验证过期状态判断 + # run: | + # echo "🔎 过期状态判断测试结果:" + # echo "状态值: ${{ steps.expiry-check.outputs.state-value }}" + # echo "缓存命中: ${{ steps.expiry-check.outputs.cache-hit }}" + # echo "过期状态: ${{ steps.expiry-check.outputs.expired }}" + # echo "使用默认值: ${{ steps.expiry-check.outputs.used-default }}" + # echo "缓存键: ${{ steps.expiry-check.outputs.cache-key }}" - # 验证缓存键包含时间窗口 - if [[ "${{ steps.expiry-check.outputs.cache-key }}" =~ -[0-9]+-[0-9]+$ ]]; then - echo "✅ 过期状态测试缓存键包含时间窗口" - else - echo "❌ 过期状态测试缓存键缺少时间窗口: ${{ steps.expiry-check.outputs.cache-key }}" - exit 1 - fi + # # 验证缓存键包含时间窗口 + # if [[ "${{ steps.expiry-check.outputs.cache-key }}" =~ -[0-9]+-[0-9]+$ ]]; then + # echo "✅ 过期状态测试缓存键包含时间窗口" + # else + # echo "❌ 过期状态测试缓存键缺少时间窗口: ${{ steps.expiry-check.outputs.cache-key }}" + # exit 1 + # fi - # 因为是新的状态键,应该返回默认值 - if [[ "${{ steps.expiry-check.outputs.state-value }}" == "fallback-value" ]]; then - echo "✅ 过期测试返回了正确的默认值" - else - echo "❌ 过期测试没有返回默认值,实际值: ${{ steps.expiry-check.outputs.state-value }}" - exit 1 - fi + # # 因为是新的状态键,应该返回默认值 + # if [[ "${{ steps.expiry-check.outputs.state-value }}" == "fallback-value" ]]; then + # echo "✅ 过期测试返回了正确的默认值" + # else + # echo "❌ 过期测试没有返回默认值,实际值: ${{ steps.expiry-check.outputs.state-value }}" + # exit 1 + # fi - # 检查是否正确标记为使用默认值 - if [[ "${{ steps.expiry-check.outputs.used-default }}" == "true" ]]; then - echo "✅ 正确标记使用了默认值" - else - echo "❌ 没有正确标记使用默认值: ${{ steps.expiry-check.outputs.used-default }}" - exit 1 - fi + # # 检查是否正确标记为使用默认值 + # if [[ "${{ steps.expiry-check.outputs.used-default }}" == "true" ]]; then + # echo "✅ 正确标记使用了默认值" + # else + # echo "❌ 没有正确标记使用默认值: ${{ steps.expiry-check.outputs.used-default }}" + # exit 1 + # fi - # 对于不存在的缓存且设置了过期时间,过期状态应该是false(因为缓存本身就不存在) - if [[ "${{ steps.expiry-check.outputs.cache-hit }}" == "false" && "${{ steps.expiry-check.outputs.expired }}" == "false" ]]; then - echo "✅ 正确处理不存在的缓存过期状态" - else - echo "ℹ️ 过期状态: ${{ steps.expiry-check.outputs.expired }},缓存命中: ${{ steps.expiry-check.outputs.cache-hit }}" - echo "ℹ️ 不存在的缓存不应标记为过期,而是未命中" - fi + # # 对于不存在的缓存且设置了过期时间,过期状态应该是false(因为缓存本身就不存在) + # if [[ "${{ steps.expiry-check.outputs.cache-hit }}" == "false" && "${{ steps.expiry-check.outputs.expired }}" == "false" ]]; then + # echo "✅ 正确处理不存在的缓存过期状态" + # else + # echo "ℹ️ 过期状态: ${{ steps.expiry-check.outputs.expired }},缓存命中: ${{ steps.expiry-check.outputs.cache-hit }}" + # echo "ℹ️ 不存在的缓存不应标记为过期,而是未命中" + # fi - - name: 测试各种过期时间值 - run: | - echo "⏱️ 过期时间功能总结..." + # - name: 测试各种过期时间值 + # run: | + # echo "⏱️ 过期时间功能总结..." - echo "" - echo "📋 已完成的过期时间测试:" - echo "✅ 短期缓存 (120秒): ${{ steps.short-expiry.outputs.cache-key }}" - echo "✅ 永不过期 (0秒): ${{ steps.no-expiry.outputs.cache-key }}" - echo "✅ 额外测试 (300秒): ${{ steps.extra-expiry-test.outputs.cache-key }}" - echo "✅ 过期状态判断 (60秒): ${{ steps.expiry-check.outputs.cache-key }}" + # echo "" + # echo "📋 已完成的过期时间测试:" + # echo "✅ 短期缓存 (120秒): ${{ steps.short-expiry.outputs.cache-key }}" + # echo "✅ 永不过期 (0秒): ${{ steps.no-expiry.outputs.cache-key }}" + # echo "✅ 额外测试 (300秒): ${{ steps.extra-expiry-test.outputs.cache-key }}" + # echo "✅ 过期状态判断 (60秒): ${{ steps.expiry-check.outputs.cache-key }}" - echo "" - echo "🔍 缓存键格式验证:" + # echo "" + # echo "🔍 缓存键格式验证:" - # 验证所有带过期时间的缓存键都有时间窗口 - KEYS_WITH_EXPIRY=( - "${{ steps.short-expiry.outputs.cache-key }}" - "${{ steps.extra-expiry-test.outputs.cache-key }}" - "${{ steps.expiry-check.outputs.cache-key }}" - ) + # # 验证所有带过期时间的缓存键都有时间窗口 + # KEYS_WITH_EXPIRY=( + # "${{ steps.short-expiry.outputs.cache-key }}" + # "${{ steps.extra-expiry-test.outputs.cache-key }}" + # "${{ steps.expiry-check.outputs.cache-key }}" + # ) - for key in "${KEYS_WITH_EXPIRY[@]}"; do - if [[ "$key" =~ -[0-9]+-[0-9]+$ ]]; then - echo "✅ $key (包含时间窗口)" - else - echo "❌ $key (缺少时间窗口)" - fi - done + # for key in "${KEYS_WITH_EXPIRY[@]}"; do + # if [[ "$key" =~ -[0-9]+-[0-9]+$ ]]; then + # echo "✅ $key (包含时间窗口)" + # else + # echo "❌ $key (缺少时间窗口)" + # fi + # done - # 验证永不过期的缓存键 - NO_EXPIRY_KEY="${{ steps.no-expiry.outputs.cache-key }}" - if [[ "$NO_EXPIRY_KEY" =~ -[0-9]+$ ]] && [[ ! "$NO_EXPIRY_KEY" =~ -[0-9]+-[0-9]+$ ]]; then - echo "✅ $NO_EXPIRY_KEY (永不过期,只有run_id)" - else - echo "❌ $NO_EXPIRY_KEY (永不过期格式错误)" - fi + # # 验证永不过期的缓存键 + # NO_EXPIRY_KEY="${{ steps.no-expiry.outputs.cache-key }}" + # if [[ "$NO_EXPIRY_KEY" =~ -[0-9]+$ ]] && [[ ! "$NO_EXPIRY_KEY" =~ -[0-9]+-[0-9]+$ ]]; then + # echo "✅ $NO_EXPIRY_KEY (永不过期,只有run_id)" + # else + # echo "❌ $NO_EXPIRY_KEY (永不过期格式错误)" + # fi - echo "" - echo "🎯 过期时间功能测试完成!" + # echo "" + # echo "🎯 过期时间功能测试完成!" # 删除功能测试 test-delete-operations: diff --git a/cache-state/examples/basic-usage.yml b/cache-state/examples/basic-usage.yml index 4406f5c..150db8f 100644 --- a/cache-state/examples/basic-usage.yml +++ b/cache-state/examples/basic-usage.yml @@ -20,7 +20,7 @@ jobs: - name: 获取或设置应用版本 id: app-version - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'application-version' state-value: ${{ github.event.inputs.version }} @@ -45,7 +45,7 @@ jobs: fi - name: 记录构建状态 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'last-build-status' state-value: 'success' @@ -53,7 +53,7 @@ jobs: - name: 获取构建历史 id: build-history - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'build-count' state-value: '1' @@ -68,7 +68,7 @@ jobs: echo "新的构建次数: ${NEW_COUNT}" - name: 保存新的构建计数 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'build-count' state-value: ${{ env.NEW_COUNT }} diff --git a/cache-state/examples/deployment-state.yml b/cache-state/examples/deployment-state.yml index 50b9f7c..217c7bd 100644 --- a/cache-state/examples/deployment-state.yml +++ b/cache-state/examples/deployment-state.yml @@ -35,7 +35,7 @@ jobs: - name: 获取环境配置 id: env-state - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'target-environment-${{ github.ref_name }}' state-value: ${{ github.event.inputs.environment || 'staging' }} @@ -44,7 +44,7 @@ jobs: - name: 检查上次部署的SHA id: last-deploy - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'last-deployed-sha-${{ steps.env-state.outputs.state-value }}' default-value: 'none' @@ -52,7 +52,7 @@ jobs: - name: 检查部署状态 id: deploy-status - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'deploy-status-${{ steps.env-state.outputs.state-value }}' default-value: 'idle' @@ -96,14 +96,14 @@ jobs: uses: actions/checkout@v4 - name: 标记部署开始 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'deploy-status-${{ needs.check-deployment-state.outputs.environment }}' state-value: 'deploying' action: 'set' - name: 记录部署开始时间 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'deploy-start-time-${{ needs.check-deployment-state.outputs.environment }}' state-value: ${{ github.run_started_at }} @@ -124,7 +124,7 @@ jobs: - name: 记录成功部署的SHA if: success() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'last-deployed-sha-${{ needs.check-deployment-state.outputs.environment }}' state-value: ${{ github.sha }} @@ -132,7 +132,7 @@ jobs: - name: 标记部署成功 if: success() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'deploy-status-${{ needs.check-deployment-state.outputs.environment }}' state-value: 'success' @@ -140,7 +140,7 @@ jobs: - name: 记录部署完成时间 if: success() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'deploy-end-time-${{ needs.check-deployment-state.outputs.environment }}' state-value: ${{ github.event.head_commit.timestamp }} @@ -148,7 +148,7 @@ jobs: - name: 标记部署失败 if: failure() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'deploy-status-${{ needs.check-deployment-state.outputs.environment }}' state-value: 'failed' @@ -156,7 +156,7 @@ jobs: - name: 记录失败信息 if: failure() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'deploy-error-${{ needs.check-deployment-state.outputs.environment }}' state-value: 'Deployment failed at ${{ github.event.head_commit.timestamp }}' @@ -169,7 +169,7 @@ jobs: steps: - name: 获取最终部署状态 id: final-status - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'deploy-status-${{ needs.check-deployment-state.outputs.environment }}' default-value: 'unknown' @@ -193,7 +193,7 @@ jobs: # 清理部署锁定状态(如果需要) - name: 清理部署锁定 if: always() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'deploy-status-${{ needs.check-deployment-state.outputs.environment }}' state-value: 'idle' diff --git a/cache-state/examples/error-handling.yml b/cache-state/examples/error-handling.yml index 9bd775a..de4a7e8 100644 --- a/cache-state/examples/error-handling.yml +++ b/cache-state/examples/error-handling.yml @@ -45,7 +45,7 @@ jobs: - name: 检查上次操作状态 id: last-operation - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'last-operation-status' default-value: 'none' @@ -86,7 +86,7 @@ jobs: - name: 记录操作开始 if: steps.check-previous.outputs.should-continue == 'true' - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'current-operation-id' state-value: ${{ steps.operation-setup.outputs.operation-id }} @@ -95,7 +95,7 @@ jobs: - name: 记录操作状态为进行中 if: steps.check-previous.outputs.should-continue == 'true' - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'last-operation-status' state-value: 'running' @@ -112,7 +112,7 @@ jobs: - name: 检查构建重试状态 id: retry-status - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'build-retry-count-${{ needs.initialize-operation.outputs.operation-id }}' default-value: '0' @@ -160,7 +160,7 @@ jobs: - name: 更新重试计数(失败时) if: failure() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'build-retry-count-${{ needs.initialize-operation.outputs.operation-id }}' state-value: ${{ steps.prepare-retry.outputs.next-retry }} @@ -169,7 +169,7 @@ jobs: - name: 记录构建失败状态 if: failure() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'build-status-${{ needs.initialize-operation.outputs.operation-id }}' state-value: 'failed' @@ -178,7 +178,7 @@ jobs: - name: 记录构建失败时间 if: failure() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'build-failure-time-${{ needs.initialize-operation.outputs.operation-id }}' state-value: ${{ github.event.head_commit.timestamp }} @@ -187,7 +187,7 @@ jobs: - name: 清理重试计数(成功时) if: success() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'build-retry-count-${{ needs.initialize-operation.outputs.operation-id }}' state-value: '' @@ -196,7 +196,7 @@ jobs: - name: 记录构建成功状态 if: success() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'build-status-${{ needs.initialize-operation.outputs.operation-id }}' state-value: 'success' @@ -213,7 +213,7 @@ jobs: - name: 获取上次成功的部署版本 id: last-deploy - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'last-successful-deployment' default-value: 'v1.0.0' @@ -221,7 +221,7 @@ jobs: cache-prefix: 'deploy' - name: 备份当前部署状态 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'deployment-backup-${{ needs.initialize-operation.outputs.operation-id }}' state-value: ${{ steps.last-deploy.outputs.state-value }} @@ -245,7 +245,7 @@ jobs: - name: 记录部署成功 if: success() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'last-successful-deployment' state-value: ${{ github.sha }} @@ -254,7 +254,7 @@ jobs: - name: 清理备份(成功时) if: success() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'deployment-backup-${{ needs.initialize-operation.outputs.operation-id }}' state-value: '' @@ -273,7 +273,7 @@ jobs: - name: 记录回滚状态 if: failure() - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'rollback-executed-${{ needs.initialize-operation.outputs.operation-id }}' state-value: 'true' @@ -313,7 +313,7 @@ jobs: echo "🏁 最终状态: ${FINAL_STATUS}" - name: 记录最终操作状态 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'last-operation-status' state-value: ${{ steps.final-status.outputs.final-status }} @@ -321,7 +321,7 @@ jobs: cache-prefix: 'operation' - name: 记录操作完成时间 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'last-operation-time' state-value: ${{ github.event.head_commit.timestamp }} @@ -369,7 +369,7 @@ jobs: # 例如,清理超过一定时间的重试计数、备份等临时状态 - name: 清理示例 - 重置错误计数 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'error-count-global' state-value: '0' diff --git a/cache-state/examples/feature-flags.yml b/cache-state/examples/feature-flags.yml index 616c8ca..218a2b2 100644 --- a/cache-state/examples/feature-flags.yml +++ b/cache-state/examples/feature-flags.yml @@ -41,7 +41,7 @@ jobs: - name: 获取或设置功能开关状态 id: feature-flag - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'feature-${{ github.event.inputs.feature_name }}-${{ github.event.inputs.environment }}' state-value: ${{ github.event.inputs.feature_enabled }} @@ -51,7 +51,7 @@ jobs: - name: 管理灰度发布配置 id: rollout - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'rollout-${{ github.event.inputs.feature_name }}-${{ github.event.inputs.environment }}' state-value: ${{ github.event.inputs.rollout_percentage }} @@ -60,7 +60,7 @@ jobs: action: 'get-or-set' - name: 记录功能开关变更历史 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'feature-history-${{ github.event.inputs.feature_name }}' state-value: '${{ github.event.inputs.environment }}:${{ github.event.inputs.feature_enabled }}:${{ github.run_started_at }}' @@ -100,7 +100,7 @@ jobs: - name: 获取服务特定的功能配置 id: service-config - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'service-config-${{ matrix.service }}-${{ github.event.inputs.environment }}' default-value: '{"features":{},"version":"1.0"}' @@ -133,7 +133,7 @@ jobs: esac - name: 更新服务配置状态 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'service-config-applied-${{ matrix.service }}' state-value: '${{ github.event.inputs.feature_name }}:${{ needs.manage-feature-flags.outputs.feature-state }}' @@ -149,7 +149,7 @@ jobs: - name: 初始化功能监控指标 id: init-metrics - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'metrics-${{ github.event.inputs.feature_name }}-${{ github.event.inputs.environment }}' default-value: '{"users":0,"errors":0,"performance":100}' @@ -175,7 +175,7 @@ jobs: echo "performance_score=${PERFORMANCE_SCORE}" >> $GITHUB_ENV - name: 更新功能监控指标 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'metrics-${{ github.event.inputs.feature_name }}-${{ github.event.inputs.environment }}' state-value: '{"users":${{ env.users_count }},"errors":${{ env.error_count }},"performance":${{ env.performance_score }}}' @@ -202,7 +202,7 @@ jobs: - name: 记录异常告警 if: env.alert != 'none' - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'alert-${{ github.event.inputs.feature_name }}-${{ github.event.inputs.environment }}' state-value: '${{ env.alert }}:${{ github.run_started_at }}' @@ -219,7 +219,7 @@ jobs: - name: 检查是否需要回滚 id: check-rollback - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'alert-${{ github.event.inputs.feature_name }}-${{ github.event.inputs.environment }}' default-value: 'none' @@ -235,7 +235,7 @@ jobs: - name: 禁用功能开关 if: contains(steps.check-rollback.outputs.state-value, 'high_error_rate') - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'feature-${{ github.event.inputs.feature_name }}-${{ github.event.inputs.environment }}' state-value: 'false' @@ -244,7 +244,7 @@ jobs: - name: 记录回滚操作 if: contains(steps.check-rollback.outputs.state-value, 'high_error_rate') - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'rollback-${{ github.event.inputs.feature_name }}-${{ github.event.inputs.environment }}' state-value: 'auto-rollback:${{ github.run_started_at }}' @@ -270,7 +270,7 @@ jobs: - name: 获取最终功能状态 id: final-state - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'feature-${{ github.event.inputs.feature_name }}-${{ github.event.inputs.environment }}' action: 'get' @@ -278,7 +278,7 @@ jobs: - name: 获取监控指标 id: final-metrics - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'metrics-${{ github.event.inputs.feature_name }}-${{ github.event.inputs.environment }}' action: 'get' @@ -286,7 +286,7 @@ jobs: - name: 检查回滚历史 id: rollback-check - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'rollback-${{ github.event.inputs.feature_name }}-${{ github.event.inputs.environment }}' default-value: 'none' @@ -318,7 +318,7 @@ jobs: # 例如,清理超过一定时间的开发环境功能开关 - name: 重置开发环境告警 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'dev-alerts-cleared' state-value: '${{ github.run_started_at }}' diff --git a/cache-state/examples/multi-environment.yml b/cache-state/examples/multi-environment.yml index e8cfd63..ab669da 100644 --- a/cache-state/examples/multi-environment.yml +++ b/cache-state/examples/multi-environment.yml @@ -50,7 +50,7 @@ jobs: esac - name: 保存环境副本数配置 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'replicas-${{ matrix.environment }}' state-value: ${{ steps.env-config.outputs.replicas }} @@ -58,7 +58,7 @@ jobs: cache-prefix: 'env-config' - name: 保存环境资源配置 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'resources-${{ matrix.environment }}' state-value: ${{ steps.env-config.outputs.resources }} @@ -66,7 +66,7 @@ jobs: cache-prefix: 'env-config' - name: 保存调试模式配置 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'debug-${{ matrix.environment }}' state-value: ${{ steps.env-config.outputs.debug }} @@ -75,7 +75,7 @@ jobs: - name: 检查环境健康状态 id: health-check - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'health-status-${{ matrix.environment }}' default-value: 'unknown' @@ -84,7 +84,7 @@ jobs: - name: 初始化环境健康状态 if: steps.health-check.outputs.state-value == 'unknown' - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'health-status-${{ matrix.environment }}' state-value: 'healthy' @@ -108,7 +108,7 @@ jobs: - name: 获取开发环境配置 id: dev-config - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'replicas-dev' action: 'get' @@ -116,7 +116,7 @@ jobs: - name: 获取预发布环境配置 id: staging-config - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'replicas-staging' action: 'get' @@ -124,7 +124,7 @@ jobs: - name: 获取生产环境配置 id: prod-config - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'replicas-production' action: 'get' @@ -161,7 +161,7 @@ jobs: echo "dev_health=${HEALTH_STATUS}" >> $GITHUB_ENV - name: 更新开发环境健康状态 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'health-status-dev' state-value: ${{ env.dev_health }} @@ -177,7 +177,7 @@ jobs: echo "staging_health=${HEALTH_STATUS}" >> $GITHUB_ENV - name: 更新预发布环境健康状态 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'health-status-staging' state-value: ${{ env.staging_health }} @@ -193,7 +193,7 @@ jobs: echo "prod_health=${HEALTH_STATUS}" >> $GITHUB_ENV - name: 更新生产环境健康状态 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'health-status-production' state-value: ${{ env.prod_health }} @@ -235,14 +235,14 @@ jobs: echo "=== 开发环境状态 ===" - name: 读取开发环境配置 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'replicas-dev' action: 'get' cache-prefix: 'env-config' - name: 读取开发环境健康状态 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'health-status-dev' action: 'get' @@ -282,7 +282,7 @@ jobs: # 例如,设置空值来清理特定的缓存状态 - name: 重置所有环境的临时状态 - uses: .actions/xgj/cache-state@v1 + uses: actions/xgj/cache-state@v1 with: state-key: 'temp-maintenance-mode' state-value: '' diff --git a/config-env/README.md b/config-env/README.md new file mode 100644 index 0000000..1a33a4a --- /dev/null +++ b/config-env/README.md @@ -0,0 +1,82 @@ +# Configure Build Environment Action + +该 GitHub Action 用于在已有环境中执行验证与配置操作,确保必要工具可用并完成 Git 与 kubectl 配置,适用于已预装所有依赖的构建机场景。 + +## 🚦 能力概览 + +- ✅ 配置 Git 用户信息 +- 🔍 校验 `docker` 与 `kubectl` 可用性及版本 +- ☸️ 可选写入 Base64 编码的 kubeconfig,并验证集群连通性 +- 🐳 可选登录私有 Docker 仓库 +- ⚠️ **不进行任何软件安装**,适用于受控环境 + +## 📥 输入参数 + +| 参数名 | 描述 | 必填 | 默认值 | +| --- | --- | --- | --- | +| `git-user-name` | Git 用户名 | ❌ | `GiteaActions` | +| `git-user-email` | Git 用户邮箱 | ❌ | `actions@gitea.com` | +| `kube-config` | Base64 编码的 kubeconfig | ❌ | `''` | +| `enable-validation` | 是否执行环境校验 (`true`/`false`) | ❌ | `true` | +| `docker-registry` | Docker 私有仓库地址 | ❌ | `docker-registry.bjxgj.com` | +| `docker-username` | Docker 仓库用户名 | ❌ | `ci-action` | +| `docker-password` | Docker 仓库密码(当未跳过登录时必填) | ❌ | `''` | +| `skip-docker-login` | 是否跳过 Docker 登录 (`true`/`false`) | ❌ | `false` | + +## 📤 输出参数 + +| 参数名 | 描述 | +| --- | --- | +| `docker-version` | 检测到的 Docker 版本或状态 | +| `kubectl-version` | 检测到的 kubectl 版本或状态 | +| `kubectl-context` | 集群验证成功时的当前 context | + +## 🚀 基本用法 + +```yaml +- name: 校验并配置环境 + uses: actions/xgj/config-env@v1 + with: + git-user-name: "CI Bot" + git-user-email: "ci@example.com" + kube-config: ${{ secrets.KUBE_CONFIG }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} +``` + +## 🎯 自定义校验 + +禁用所有校验,仅进行 Git 配置(若提供)及 kubeconfig 写入: + +```yaml +- name: 跳过环境校验 + uses: actions/xgj/config-env@v1 + with: + enable-validation: "false" +``` + +## 🚀 Docker 登录配置 + +```yaml +- name: 验证 kubectl 集群 + uses: actions/xgj/config-env@v1 + with: + enable-validation: "true" + kube-config: ${{ secrets.KUBE_CONFIG }} + docker-registry: "registry.example.com" + docker-username: "ci-bot" + docker-password: ${{ secrets.DOCKER_PASSWORD }} +``` + +## 🔒 注意事项 + +- 请确保运行环境已安装 `docker` 与 `kubectl`(若开启对应校验)。 +- kubeconfig 建议通过 GitHub Secrets 以 Base64 编码方式提供。 +- Action 执行失败会立即终止后续步骤,便于快速发现环境问题。 + +## 🧪 示例工作流 + +详见 `examples/basic-usage.yml`,展示了与 CI 构建流程结合的典型用法。 + +## 🤝 贡献 + +欢迎提 Issue 或提交 PR! diff --git a/config-env/action.yml b/config-env/action.yml new file mode 100644 index 0000000..0b187a3 --- /dev/null +++ b/config-env/action.yml @@ -0,0 +1,102 @@ +name: 'Configure Build Environment' +description: '验证已有环境并配置 Git 与 kubectl(不执行软件安装)' +author: 'Your Organization' + +branding: + icon: 'settings' + color: 'green' + +inputs: + git-user-name: + description: 'Git 用户名' + required: false + default: 'GiteaActions' + git-user-email: + description: 'Git 用户邮箱' + required: false + default: 'actions@gitea.com' + kube-config: + description: 'Base64 编码的 kubectl 配置文件' + required: false + default: '' + enable-validation: + description: '是否执行环境校验 (true/false)' + required: false + default: 'true' + docker-registry: + description: 'Docker 私有仓库地址' + required: false + default: 'docker-registry.bjxgj.com' + docker-username: + description: 'Docker 仓库用户名' + required: false + default: 'ci-action' + docker-password: + description: 'Docker 仓库密码(开启登录时必填)' + required: false + default: '' + skip-docker-login: + description: '是否跳过 Docker 登录 (true/false)' + required: false + default: 'false' + +outputs: + docker-version: + description: '检测到的 Docker 版本' + value: ${{ steps.validate-tools.outputs.docker-version }} + kubectl-version: + description: '检测到的 kubectl 版本' + value: ${{ steps.validate-tools.outputs.kubectl-version }} + kubectl-context: + description: '验证通过时的当前 kubectl 上下文' + value: ${{ steps.verify-kubectl.outputs.current-context }} + +runs: + using: 'composite' + steps: + - name: 配置 Git + shell: bash + run: bash ${{ github.action_path }}/scripts/configure-git.sh + env: + GIT_USER_NAME: ${{ inputs.git-user-name }} + GIT_USER_EMAIL: ${{ inputs.git-user-email }} + + - name: 校验工具可用性 + id: validate-tools + shell: bash + run: bash ${{ github.action_path }}/scripts/validate-tools.sh + env: + ENABLE_VALIDATION: ${{ inputs.enable-validation }} + + - name: 配置 kubectl + if: ${{ inputs.kube-config != '' }} + shell: bash + run: bash ${{ github.action_path }}/scripts/configure-kubectl.sh + env: + KUBE_CONFIG_BASE64: ${{ inputs.kube-config }} + + - name: 验证 kubectl 连通性 + id: verify-kubectl + if: ${{ inputs.kube-config != '' && inputs.enable-validation != 'false' }} + shell: bash + run: bash ${{ github.action_path }}/scripts/verify-kubectl.sh + + - name: 登录私有 Docker 仓库 + if: ${{ inputs.skip-docker-login != 'true' }} + uses: docker/login-action@v3 + with: + registry: ${{ inputs.docker-registry }} + username: ${{ inputs.docker-username }} + password: ${{ inputs.docker-password }} + + - name: 环境校验完成 + shell: bash + run: | + echo '🎉 环境校验与配置步骤完成' + if [[ "${{ inputs.enable-validation }}" != 'false' ]]; then + echo " - Docker: ${{ steps.validate-tools.outputs.docker-version }}" + echo " - kubectl: ${{ steps.validate-tools.outputs.kubectl-version }}" + fi + if [[ "${{ inputs.kube-config }}" != '' && "${{ inputs.enable-validation }}" != 'false' ]]; then + echo " - 当前上下文: ${{ steps.verify-kubectl.outputs.current-context }}" + fi diff --git a/config-env/examples/basic-usage.yml b/config-env/examples/basic-usage.yml new file mode 100644 index 0000000..30fef1a --- /dev/null +++ b/config-env/examples/basic-usage.yml @@ -0,0 +1,29 @@ +name: Config Env Example +on: + push: + branches: + - main + +jobs: + validate-environment: + runs-on: ubuntu-latest + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 校验环境并配置 Git + id: config-env + uses: actions/xgj/config-env@v1 + with: + git-user-name: "CI Bot" + git-user-email: "ci@example.com" + require-docker: "true" + require-kubectl: "true" + verify-kubectl-cluster: "true" + kube-config: ${{ secrets.KUBE_CONFIG }} + + - name: 输出工具信息 + run: | + echo "Docker 版本: ${{ steps.config-env.outputs.docker-version }}" + echo "kubectl 版本: ${{ steps.config-env.outputs.kubectl-version }}" + echo "kubectl 上下文: ${{ steps.config-env.outputs.kubectl-context }}" diff --git a/config-env/scripts/configure-git.sh b/config-env/scripts/configure-git.sh new file mode 100644 index 0000000..89306e5 --- /dev/null +++ b/config-env/scripts/configure-git.sh @@ -0,0 +1,59 @@ +#!/bin/bash +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { + echo -e "${BLUE}ℹ️ $1${NC}" +} + +log_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +log_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +log_error() { + echo -e "${RED}❌ $1${NC}" +} + +main() { + if ! command -v git >/dev/null 2>&1; then + log_error "未检测到 git 命令" + exit 1 + else + log_info "已检测到 git: $(git --version)" + fi + + local name="${GIT_USER_NAME:-}" + local email="${GIT_USER_EMAIL:-}" + + if [[ -z "$name" && -z "$email" ]]; then + log_warning "未提供 Git 用户名和邮箱,跳过配置" + return 0 + fi + + if [[ -n "$name" ]]; then + git config --global user.name "$name" + log_success "已配置 Git 用户名: $name" + else + log_warning "未提供 Git 用户名" + fi + + if [[ -n "$email" ]]; then + git config --global user.email "$email" + log_success "已配置 Git 邮箱: $email" + else + log_warning "未提供 Git 邮箱" + fi +} + +trap 'log_error "Git 配置失败,退出码: $?"' ERR + +main "$@" diff --git a/config-env/scripts/configure-kubectl.sh b/config-env/scripts/configure-kubectl.sh new file mode 100644 index 0000000..0cfaceb --- /dev/null +++ b/config-env/scripts/configure-kubectl.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { + echo -e "${BLUE}ℹ️ $1${NC}" +} + +log_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +log_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +log_error() { + echo -e "${RED}❌ $1${NC}" +} + +ensure_kubectl_available() { + if ! command -v kubectl >/dev/null 2>&1; then + log_error "kubectl 未安装或不可用" + exit 1 + fi + log_info "kubectl 版本: $(kubectl version --client --short 2>/dev/null || kubectl version --client)" +} + +write_kube_config() { + local encoded="${KUBE_CONFIG_BASE64:-}" + if [[ -z "$encoded" ]]; then + log_error "KUBE_CONFIG_BASE64 环境变量为空" + exit 1 + fi + + mkdir -p "$HOME/.kube" + local config_path="$HOME/.kube/config" + + echo "$encoded" | base64 -d > "$config_path" + chmod 600 "$config_path" + log_success "已写入 kubectl 配置: $config_path" +} + +validate_kube_config() { + if ! kubectl config view --minify >/dev/null 2>&1; then + log_error "kubectl 配置文件无效或权限不足" + exit 1 + fi + log_success "kubectl 配置文件格式验证通过" +} + +trap 'log_error "kubectl 配置失败,退出码: $?"' ERR + +main() { + ensure_kubectl_available + write_kube_config + validate_kube_config +} + +main "$@" diff --git a/config-env/scripts/validate-tools.sh b/config-env/scripts/validate-tools.sh new file mode 100644 index 0000000..7475b16 --- /dev/null +++ b/config-env/scripts/validate-tools.sh @@ -0,0 +1,76 @@ +#!/bin/bash +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { + echo -e "${BLUE}ℹ️ $1${NC}" +} + +log_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +log_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +log_error() { + echo -e "${RED}❌ $1${NC}" +} + +validate_binary() { + local name="$1" + local required="$2" + local version_cmd="$3" + local output_var="$4" + local version_output="" + + if command -v "$name" >/dev/null 2>&1; then + if [[ "$name" == "kubectl" ]]; then + if version_output=$(kubectl version --client --short 2>/dev/null | head -n 1); then + : + else + version_output=$(kubectl version --client 2>/dev/null | head -n 1) + fi + else + version_output=$(bash -c "$version_cmd") + fi + + log_success "检测到 $name: $version_output" + printf '%s=%s\n' "$output_var" "$version_output" >> "$GITHUB_OUTPUT" + return 0 + fi + + if [[ "$required" == "true" ]]; then + log_error "未检测到必需的命令: $name" + exit 1 + else + log_warning "未检测到可选命令: $name" + printf '%s=%s\n' "$output_var" "not-found" >> "$GITHUB_OUTPUT" + fi +} + +main() { + : "${GITHUB_OUTPUT:?GITHUB_OUTPUT 未设置}" >/dev/null + + local enable_validation="${ENABLE_VALIDATION:-true}" + + if [[ "$enable_validation" != "true" ]]; then + log_info "已通过统一开关禁用环境校验" + printf 'docker-version=%s\n' "skipped" >> "$GITHUB_OUTPUT" + printf 'kubectl-version=%s\n' "skipped" >> "$GITHUB_OUTPUT" + return 0 + fi + + validate_binary "docker" "true" "docker --version" "docker-version" + validate_binary "kubectl" "true" "kubectl version --client --short 2>/dev/null || kubectl version --client" "kubectl-version" +} + +trap 'log_error "工具校验失败,退出码: $?"' ERR + +main "$@" diff --git a/config-env/scripts/verify-kubectl.sh b/config-env/scripts/verify-kubectl.sh new file mode 100644 index 0000000..32e6b5c --- /dev/null +++ b/config-env/scripts/verify-kubectl.sh @@ -0,0 +1,60 @@ +#!/bin/bash +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { + echo -e "${BLUE}ℹ️ $1${NC}" +} + +log_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +log_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +log_error() { + echo -e "${RED}❌ $1${NC}" +} + +: "${GITHUB_OUTPUT:?GITHUB_OUTPUT 未设置}" >/dev/null + +verify_cluster() { + local timeout=30 + + if timeout "$timeout" kubectl cluster-info >/dev/null 2>&1; then + log_success "kubectl 集群连接验证通过" + log_info "集群信息:" + kubectl cluster-info + + log_info "尝试获取节点信息" + if kubectl get nodes >/dev/null 2>&1; then + kubectl get nodes + else + log_warning "无法获取节点信息(可能权限不足)" + fi + + local current_context + current_context=$(kubectl config current-context 2>/dev/null || echo "unknown") + log_info "当前上下文: $current_context" + printf 'current-context=%s\n' "$current_context" >> "$GITHUB_OUTPUT" + else + log_error "kubectl 集群连接验证失败" + kubectl cluster-info || true + exit 1 + fi +} + +trap 'log_error "kubectl 验证失败,退出码: $?"' ERR + +main() { + verify_cluster +} + +main "$@" diff --git a/npm-install/README.md b/npm-install/README.md index 60d66b8..e046ec2 100644 --- a/npm-install/README.md +++ b/npm-install/README.md @@ -10,17 +10,22 @@ - 🔑 **精确 hash 控制**: 支持自定义缓存 hash,确保依赖变化时缓存失效 - 🔄 **Git 集成**: 可选的 git stash 功能 - 📊 **详细输出**: 提供缓存命中状态和使用的缓存 key +- 🧰 **自动安装 pnpm**: 当选择 `package-manager: pnpm` 时,自动通过 `pnpm/action-setup@v4` 确保 pnpm 可用(可指定版本) +- 🗜️ **缓存 pnpm 二进制**: 在安装前缓存 Corepack 的 pnpm 二进制目录,避免重复下载 pnpm 本体(对封闭/慢网环境更友好) ## 📋 输入参数 | 参数名 | 描述 | 是否必需 | 默认值 | | ------------------- | ---------------------------- | -------- | -------------- | | `package-manager` | 包管理器类型 (npm/pnpm/yarn) | 否 | `npm` | +| `pnpm-version` | pnpm 版本(`package-manager: pnpm` 时生效) | 否 | `9` | +| `cache-mode` | 缓存模式(`node_modules` 或 `store`) | 否 | `node_modules` | | `cache-prefix` | 缓存前缀名称 | 否 | `modules` | | `node-modules-path` | node_modules 目录路径 | 否 | `node_modules` | | `force-install` | 是否强制安装 | 否 | `false` | | `enable-git-stash` | 安装后是否执行 git stash | 否 | `false` | | `install-command` | 自定义安装命令(覆盖默认) | 否 | `''` | +| `install-args` | 附加到默认安装命令的参数(当未提供 `install-command` 时生效) | 否 | `''` | | `cache-hash` | 缓存 hash 值(**推荐使用**) | 否 | 自动计算 | ## 📤 输出参数 @@ -29,6 +34,7 @@ | ----------- | ------------------------- | | `cache-hit` | 是否命中缓存 (true/false) | | `cache-key` | 使用的缓存 key | +| `cache-path` | 缓存路径(用于调试与复用) | ## 💡 重要提示 @@ -65,6 +71,13 @@ with: uses: actions/xgj/npm-install@v1 with: package-manager: "pnpm" + # 可选:指定 pnpm 版本(默认 9) + pnpm-version: "9" + # 可选:切换为包管理器 store 缓存,提升复用率(首次仍需安装链接) + # cache-mode: "store" + # 可选:附加安装参数(当未设置 install-command 时生效) + # 例如:保持锁文件严格、忽略可选依赖 + install-args: "--frozen-lockfile --no-optional" ``` ### 强制安装 + Git Stash @@ -98,6 +111,31 @@ with: install-command: "npm ci --only=production" ``` +### 仅附加参数(不覆盖命令) + +```yaml +# npm 示例:追加只生产依赖 +- name: 安装(npm,仅生产依赖) + uses: actions/xgj/npm-install@v1 + with: + package-manager: npm + install-args: "--only=production" + +# pnpm 示例:严格锁文件 + 忽略可选依赖 +- name: 安装(pnpm,严格锁文件) + uses: actions/xgj/npm-install@v1 + with: + package-manager: pnpm + install-args: "--frozen-lockfile --no-optional" + +# yarn 示例:纯安装(禁用脚本) +- name: 安装(yarn,禁用脚本) + uses: actions/xgj/npm-install@v1 + with: + package-manager: yarn + install-args: "--ignore-scripts" +``` + ### 使用自定义缓存 hash(推荐用法) ```yaml @@ -177,8 +215,15 @@ jobs: 1. **缓存 Key 生成**: 根据包管理器类型和 lock 文件生成唯一的缓存 key 2. **缓存检查**: 使用`actions/cache@v4`检查是否存在匹配的缓存 -3. **条件安装**: 仅在缓存未命中时执行依赖安装 -4. **可选操作**: 根据配置执行 git stash 等额外操作 +3. **自动安装 pnpm(如需)**: 当 `package-manager=pnpm` 时,使用 `pnpm/action-setup@v4` 确保 pnpm 已安装 + - 在此之前,本 Action 会使用 `actions/cache@v4` 缓存 Corepack 的 pnpm 二进制目录,命中后无需再次从外网拉取 pnpm 本体 +4. **缓存路径确定**: + - 当 `cache-mode=node_modules` 时,缓存 `node_modules`(可用 `node-modules-path` 定义目录)。 + - 当 `cache-mode=store` 时,缓存包管理器的全局存储:`npm` → `~/.npm`,`pnpm` → 通过 `pnpm store path` 动态获取,`yarn` → `~/.cache/yarn`。 +5. **条件安装**: 仅在缓存未命中时执行依赖安装 +6. **可选操作**: 根据配置执行 git stash 等额外操作 + +> 说明:缓存 key 中包含 OS、包管理器(以及 pnpm 的版本)、缓存模式与自定义前缀,避免跨包管理器/模式的缓存误用。 ## 🎯 最佳实践 @@ -189,6 +234,21 @@ jobs: - uses: actions/xgj/npm-install@v1 with: package-manager: "pnpm" # 如果项目使用pnpm + +### 1.1 选择合适的缓存模式 + +```yaml +# 追求最快的安装且目录较稳定:缓存 node_modules(默认) +- uses: actions/xgj/npm-install@v1 + with: + package-manager: pnpm + cache-mode: node_modules + +# 追求更高的通用性和复用率:缓存包管理器 store(首次仍需 install 链接) +- uses: actions/xgj/npm-install@v1 + with: + package-manager: pnpm + cache-mode: store ``` ### 2. 合理使用强制安装 @@ -220,6 +280,10 @@ jobs: - name: 重建缓存 if: steps.install.outputs.cache-hit != 'true' run: npm run build:cache + +# 调试:打印缓存路径(可用于后续步骤复用路径) +- name: 打印缓存路径 + run: echo "Cache path is: ${{ steps.install.outputs.cache-path }}" ``` ## 🚨 注意事项 @@ -228,6 +292,21 @@ jobs: 2. **缓存大小**: node_modules 可能很大,请关注 GitHub Actions 的缓存限制 3. **Lock 文件**: 确保 lock 文件已提交到仓库,这是缓存 key 生成的基础 4. **权限**: 某些自定义安装命令可能需要额外的权限 +5. **跨包管理器缓存隔离**: 缓存 key 与 restore-keys 均包含包管理器、版本(pnpm)与模式,切换包管理器/模式时会触发一次干净安装,避免污染 + +## 🛡️ 封闭网络/慢网优化 + +在网络较差或对外网络受限的环境中,建议: + +- 本 Action 已默认缓存 pnpm 二进制(Corepack 缓存目录),首次成功后后续运行将跳过 pnpm 下载。 + - 缓存路径(按 OS 区分,Action 会全部尝试): + - Linux: `~/.cache/corepack` + - macOS: `~/Library/Caches/CorePack` 与 `~/Library/Caches/Corepack` + - Windows: `C:\Users\runneradmin\AppData\Local\Corepack\cache` + - 缓存 key:`${{ runner.os }}-corepack-pnpm-${{ inputs.pnpm-version }}` +- 将 registry 指向内网镜像或近源镜像,并(如需)配置 `NODE_AUTH_TOKEN`。 +- 适当降低并发、调大超时与重试,例如设置:`pnpm config set network-concurrency 1` 与对应的 `npm config set network-timeout ...` 等。 +- 使用 `cache-mode: store` 并确保 `pnpm store path` 被缓存(Action 默认已处理)。首次成功安装后,后续基本本地链接即可。 ## 🔍 故障排除 @@ -238,6 +317,11 @@ jobs: - lock 文件是否存在且已提交 - package.json 或 lock 文件是否有变更 - 缓存前缀是否与之前一致 +- 是否切换了包管理器或 `cache-mode`(切换会生成不同的 key 与前缀) + +### pnpm store 路径 + +当 `cache-mode=store` 且 `package-manager=pnpm` 时,本 Action 会通过 `pnpm store path` 动态解析缓存目录;若命令不可用,则回退到 `~/.pnpm-store`。 ### 安装失败 diff --git a/npm-install/action.yml b/npm-install/action.yml index 6ab583f..f5f5862 100644 --- a/npm-install/action.yml +++ b/npm-install/action.yml @@ -8,9 +8,28 @@ inputs: package-manager: description: '包管理器类型 (npm, pnpm, yarn)' required: false - default: 'npm' + default: 'pnpm' + pnpm-version: + description: 'pnpm 版本(当 package-manager=pnpm 时生效)' + required: false + default: '10' + + skip-pnpm-setup: + description: '当 package-manager=pnpm 时是否跳过 pnpm/action-setup (true/false)' + required: false + default: 'false' + cache-mode: + description: '缓存模式:node_modules 或 store' + required: false + default: 'store' + + optimize-install-flags: + description: '是否启用安装参数优化(pnpm+store时自动使用 --offline/--prefer-offline 与 --frozen-lockfile)(true/false)' + required: false + default: 'true' + cache-prefix: description: '缓存前缀名称' required: false @@ -35,6 +54,16 @@ inputs: description: '自定义安装命令(可选,会覆盖默认命令)' required: false default: '' + + install-args: + description: '附加到默认安装命令的参数(当未提供 install-command 时生效)' + required: false + default: '' + + clean-project-store: + description: '在安装前清理项目根的 .pnpm-store 残留(true/false)' + required: false + default: 'false' cache-hash: description: '缓存hash值(推荐使用hashFiles计算)' @@ -50,6 +79,10 @@ outputs: description: '使用的缓存key' value: ${{ steps.cache-key.outputs.key }} + cache-path: + description: '缓存路径(用于调试与复用)' + value: ${{ steps.cache-path.outputs.path }} + runs: using: 'composite' steps: @@ -80,65 +113,178 @@ runs: echo "⚠️ 使用默认hash值: ${CACHE_HASH_SHORT}" fi - CACHE_KEY="${{ runner.os }}-${{ inputs.cache-prefix }}-${CACHE_HASH_SHORT}" + # 生成包管理器后缀,避免不同包管理器/版本的缓存互相污染 + MANAGER="${{ inputs.package-manager }}" + MANAGER_SUFFIX="$MANAGER" + if [[ "$MANAGER" == "pnpm" && -n "${{ inputs.pnpm-version }}" && "${{ inputs.pnpm-version }}" != "" ]]; then + MANAGER_SUFFIX="${MANAGER}-v${{ inputs.pnpm-version }}" + fi + + # 模式后缀,隔离不同缓存模式(node_modules vs store) + MODE_INPUT="${{ inputs.cache-mode }}" + ACTUAL_MODE="$MODE_INPUT" + if [[ "$MANAGER" == "npm" ]]; then + ACTUAL_MODE="node_modules" + elif [[ -z "$ACTUAL_MODE" ]]; then + ACTUAL_MODE="node_modules" + fi + MODE_SUFFIX="$ACTUAL_MODE" + + # 构建缓存key:---- + CACHE_KEY="${{ runner.os }}-${MANAGER_SUFFIX}-${MODE_SUFFIX}-${{ inputs.cache-prefix }}-${CACHE_HASH_SHORT}" + # 恢复前缀:用于 restore-keys,防止不同包管理器/模式的回退误命中 + RESTORE_PREFIX="${{ runner.os }}-${MANAGER_SUFFIX}-${MODE_SUFFIX}-${{ inputs.cache-prefix }}-" echo "key=${CACHE_KEY}" >> $GITHUB_OUTPUT + echo "restore-prefix=${RESTORE_PREFIX}" >> $GITHUB_OUTPUT + echo "mode=${ACTUAL_MODE}" >> $GITHUB_OUTPUT echo "使用hash: ${CACHE_HASH}" echo "缓存key: ${CACHE_KEY}" + - name: 确保 pnpm 可用 + if: inputs.package-manager == 'pnpm' && inputs.skip-pnpm-setup != 'true' + uses: pnpm/action-setup@v4 + with: + version: ${{ inputs.pnpm-version }} + run_install: false + + - name: 确定缓存路径 + id: cache-path + shell: bash + run: | + MODE="${{ steps.cache-key.outputs.mode }}" + MANAGER="${{ inputs.package-manager }}" + if [[ -z "$MODE" ]]; then MODE="node_modules"; fi + + if [[ "$MODE" == "node_modules" ]]; then + CACHE_PATH="${{ inputs.node-modules-path }}" + # 即使只缓存 node_modules,pnpm 也会使用 store。为避免在项目根生成 .pnpm-store,这里同样固定 PNPM_STORE_DIR + if [[ "$MANAGER" == "pnpm" ]]; then + DEFAULT_PNPM_STORE="${RUNNER_TEMP:-$HOME}/.pnpm-store" + echo "PNPM_STORE_DIR=${DEFAULT_PNPM_STORE}" >> "$GITHUB_ENV" + fi + else + case "$MANAGER" in + "npm") + # npm 的全局缓存目录 + CACHE_PATH="$HOME/.npm" + ;; + "pnpm") + # 固定 pnpm store 路径到 runner 的临时目录或 HOME,避免在项目根生成 .pnpm-store + # 说明:一些仓库的 .npmrc 可能配置了 store-dir=.pnpm-store,会导致在工作目录创建 .pnpm-store + # 这里通过设置 PNPM_STORE_DIR 环境变量进行覆盖,确保缓存路径稳定可控 + DEFAULT_PNPM_STORE="${RUNNER_TEMP:-$HOME}/.pnpm-store" + # 将目录导出到环境,供后续安装步骤使用 + echo "PNPM_STORE_DIR=${DEFAULT_PNPM_STORE}" >> "$GITHUB_ENV" + CACHE_PATH="${DEFAULT_PNPM_STORE}" + ;; + "yarn") + # yarn v1 默认缓存目录(yarn berry 采用不同机制,这里聚焦 v1 常见场景) + CACHE_PATH="$HOME/.cache/yarn" + ;; + *) + echo "❌ 不支持的包管理器: $MANAGER" + exit 1 + ;; + esac + fi + # 打印最终缓存目录,便于调试与确认 + echo "📁 最终缓存目录: ${CACHE_PATH}" + if [[ "$MANAGER" == "pnpm" ]]; then + echo "📦 PNPM_STORE_DIR=${PNPM_STORE_DIR:-$DEFAULT_PNPM_STORE}" + fi + echo "path=${CACHE_PATH}" >> $GITHUB_OUTPUT + - name: 拉取缓存依赖 id: cache uses: actions/cache@v4 with: - path: ${{ inputs.node-modules-path }} + path: ${{ steps.cache-path.outputs.path }} key: ${{ steps.cache-key.outputs.key }} restore-keys: | - ${{ runner.os }}-${{ inputs.cache-prefix }}- + ${{ steps.cache-key.outputs.restore-prefix }} - name: 显示缓存状态 shell: bash run: | if [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]]; then - echo "✅ 缓存命中,跳过依赖安装" + if [[ "${{ steps.cache-key.outputs.mode }}" == "store" ]]; then + echo "✅ 缓存命中(store),将执行快速链接安装(不会下载包,仅链接)" + else + echo "✅ 缓存命中,跳过依赖安装" + fi else echo "⚠️ 缓存未命中,开始安装依赖" fi - name: 安装依赖 - if: steps.cache.outputs.cache-hit != 'true' + if: (steps.cache-key.outputs.mode == 'node_modules' && steps.cache.outputs.cache-hit != 'true') || (steps.cache-key.outputs.mode == 'store') shell: bash run: | + ACTUAL_MODE="${{ steps.cache-key.outputs.mode }}" + # 若使用 pnpm,在本步骤内始终显式设置 PNPM_STORE_DIR(覆盖可能存在的相对配置) + if [[ "${{ inputs.package-manager }}" == "pnpm" ]]; then + if [[ "$ACTUAL_MODE" == "store" ]]; then + # store 模式:cache-path 的 path 即为期望的 store 目录 + export PNPM_STORE_DIR="${{ steps.cache-path.outputs.path }}" + else + # node_modules 模式:不要回退到 cache-path(那是 node_modules 目录),而是使用 RUNNER_TEMP/HOME + export PNPM_STORE_DIR="${PNPM_STORE_DIR:-${RUNNER_TEMP:-$HOME}/.pnpm-store}" + fi + # 通过多通道环境变量覆盖(兼容不同版本/解析顺序) + export npm_config_store_dir="${PNPM_STORE_DIR}" + export PNPM_CONFIG_STORE_DIR="${PNPM_STORE_DIR}" + echo "🧩 已设置 PNPM_STORE_DIR=${PNPM_STORE_DIR}" + echo "🔎 pnpm 配置: store-dir=$(pnpm config get store-dir || echo '')" + fi + # 如果提供了自定义安装命令,使用自定义命令 if [[ -n "${{ inputs.install-command }}" ]]; then echo "🔧 使用自定义安装命令: ${{ inputs.install-command }}" ${{ inputs.install-command }} else + INSTALL_ARGS="${{ inputs.install-args }}" + if [[ -n "$INSTALL_ARGS" ]]; then + echo "➕ 附加安装参数: $INSTALL_ARGS" + fi + # 根据模式与缓存命中优化安装参数(尽量离线加速) + EXTRA_FLAGS="" + if [[ "${{ inputs.optimize-install-flags }}" == "true" && "${{ inputs.package-manager }}" == "pnpm" && "$ACTUAL_MODE" == "store" ]]; then + if [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]]; then + # 缓存命中:使用完全离线与锁定安装,避免网络请求 + EXTRA_FLAGS="--offline --frozen-lockfile" + else + # 缓存未命中:尽量离线,但允许必要网络;同时锁定避免解析差异 + EXTRA_FLAGS="--prefer-offline --frozen-lockfile" + fi + echo "⚡ pnpm安装优化参数: $EXTRA_FLAGS" + fi # 根据包管理器选择安装命令 case "${{ inputs.package-manager }}" in "npm") if [[ "${{ inputs.force-install }}" == "true" ]]; then echo "🔧 使用npm强制安装" - npm install --force + npm install --force ${INSTALL_ARGS} else echo "🔧 使用npm安装" - npm install + npm install ${INSTALL_ARGS} fi ;; "pnpm") if [[ "${{ inputs.force-install }}" == "true" ]]; then echo "🔧 使用pnpm强制安装" - pnpm install --force + pnpm install --force ${EXTRA_FLAGS} ${INSTALL_ARGS} else echo "🔧 使用pnpm安装" - pnpm install + pnpm install ${EXTRA_FLAGS} ${INSTALL_ARGS} fi ;; "yarn") if [[ "${{ inputs.force-install }}" == "true" ]]; then echo "🔧 使用yarn强制安装" - yarn install --force + yarn install --force ${INSTALL_ARGS} else echo "🔧 使用yarn安装" - yarn install + yarn install ${INSTALL_ARGS} fi ;; *) @@ -147,6 +293,16 @@ runs: ;; esac fi + + # 可选清理:如启用并发现项目根存在残留的 .pnpm-store,且与目标目录不同,则清理 + if [[ "${{ inputs.package-manager }}" == "pnpm" && "${{ inputs.clean-project-store }}" == "true" ]]; then + # 进一步在项目级设置覆盖一次,杜绝 .npmrc 相对路径带来的影响(仅对本 CI 工作目录生效) + pnpm config set store-dir "${PNPM_STORE_DIR}" --location=project || true + if [[ -d ".pnpm-store" && "${PNPM_STORE_DIR}" != "$PWD/.pnpm-store" ]]; then + echo "🧹 清理项目根的残留 .pnpm-store(目标store为 ${PNPM_STORE_DIR})" + rm -rf .pnpm-store || true + fi + fi echo "✅ 依赖安装完成" @@ -169,3 +325,22 @@ runs: echo " - 强制安装: ${{ inputs.force-install }}" echo " - Git Stash: ${{ inputs.enable-git-stash }}" fi + # 诊断工作目录是否存在 .pnpm-store 以及其 Git 状态 + echo "\n🧪 目录状态自检:" + echo " - PNPM_STORE_DIR: ${PNPM_STORE_DIR:-}" + if [[ -d ".pnpm-store" ]]; then + echo " - 工作目录存在 .pnpm-store 目录" + if git rev-parse --git-dir >/dev/null 2>&1; then + if git ls-files --error-unmatch .pnpm-store >/dev/null 2>&1; then + echo " - Git 状态: 已被跟踪 (tracked)" + elif git check-ignore -q .pnpm-store; then + echo " - Git 状态: 被忽略 (ignored)" + else + echo " - Git 状态: 未跟踪 (untracked)" + fi + else + echo " - Git 仓库: 未检测到 (非 Git 工作目录)" + fi + else + echo " - 工作目录未发现 .pnpm-store 目录 ✅" + fi diff --git a/pnpm-install/README.md b/pnpm-install/README.md new file mode 100644 index 0000000..2c8daae --- /dev/null +++ b/pnpm-install/README.md @@ -0,0 +1,88 @@ +# pnpm 依赖安装与缓存 Action + +专为 pnpm 项目设计的 GitHub Action,通过缓存 pnpm store,在 CI/CD 中实现快速复用,二次执行可在 10 秒内完成安装。 + +## ✨ 特性 + +- **专注 pnpm**:缓存 pnpm store,命中后仅执行快速链接 +- **离线友好**:自动根据缓存命中追加 `--offline`/`--prefer-offline` +- **锁文件准确性**:推荐传入 `cache-hash`(如 `hashFiles('pnpm-lock.yaml')`)确保缓存精准失效 +- **自定义安装**:支持附加参数或完全覆盖安装命令,保留 `force-install` 选项 +- **环境整洁**:自动设置 `PNPM_STORE_DIR`,可选清理项目根 `.pnpm-store` + +## 📥 输入参数 + +| 参数名 | 描述 | 必需 | 默认值 | +| --- | --- | --- | --- | +| `cache-prefix` | 缓存 key 前缀 | 否 | `modules` | +| `force-install` | 是否强制安装(追加 `--force`) | 否 | `false` | +| `install-command` | 自定义安装命令,覆盖默认的 `pnpm install` | 否 | `''` | +| `install-args` | 附加参数(仅默认命令时生效) | 否 | `''` | +| `cache-hash` | 缓存 hash 值(建议:`hashFiles('pnpm-lock.yaml')`) | 否 | `''` | +| `clean-project-store` | 安装后是否清理项目根的 `.pnpm-store` | 否 | `false` | + +## 📤 输出参数 + +| 参数名 | 描述 | +| --- | --- | +| `cache-hit` | 缓存是否命中 (`true` / `false`) | +| `cache-key` | 实际使用的缓存 key | +| `cache-path` | 缓存目录路径(调试/复用用途) | + +## 🚀 快速上手 + +```yaml +- name: 安装依赖 + uses: actions/xgj/pnpm-install@v1 + with: + cache-hash: ${{ hashFiles('pnpm-lock.yaml') }} +``` + +- 默认缓存 pnpm store,命中后执行 `pnpm install --offline --frozen-lockfile`,通常在 10 秒内完成链接。 +- 首次执行或 lock 文件变更时执行 `pnpm install --prefer-offline --frozen-lockfile`,完成后写入缓存。 +- 当未显式提供 `cache-hash` 时,会依次使用 `pnpm-lock.yaml`、`package.json` 计算 hash 作为兜底,确保缓存具备基本失效条件。 + +## 🔁 缓存路径 + +Action 会通过执行 `pnpm store path --silent` 获取当前 pnpm store 目录,并将结果写入日志与 `PNPM_STORE_DIR` 环境变量。 + +若命令未返回有效路径,可在调用前手动设置 `PNPM_STORE_DIR=/path/to/store` 以确保后续步骤正常运行。 + +## ⚙️ 进阶配置 + +- **强制安装** + +```yaml +with: + force-install: "true" +``` + +当缓存未命中且怀疑存在依赖冲突时,可追加 `--force`。 + +- **自定义命令** + +```yaml +with: + install-command: "pnpm install --prod" +``` + +完全覆盖默认安装逻辑,适合部署类任务。 + +- **清理项目根 `.pnpm-store`** + +```yaml +with: + clean-project-store: "true" +``` + +在部分仓库中 `.npmrc` 会指定相对 store 路径,该选项可避免 CI 工作目录残留。 + +## 🧰 示例工作流 + +参见 `examples/basic-usage.yml`,展示在 CI 中的集成方式。 + +## 🛡️ 注意事项 + +- 该 Action 假设 Runner 环境已安装 pnpm。若未预装,请在前一步引入 `pnpm/action-setup@v4`。 +- 强烈建议传入 `cache-hash`,并确保 `pnpm-lock.yaml` 已提交。 +- GitHub Actions 缓存配额有限,定期清理或调整 `cache-prefix` 以避免冲突。 diff --git a/pnpm-install/action.yml b/pnpm-install/action.yml new file mode 100644 index 0000000..f1666b8 --- /dev/null +++ b/pnpm-install/action.yml @@ -0,0 +1,187 @@ +name: 'pnpm依赖安装与缓存' +description: '专注于pnpm的依赖缓存与安装,加速重复执行' +branding: + icon: 'package' + color: 'yellow' + +inputs: + cache-prefix: + description: '缓存前缀名称' + required: false + default: 'modules' + + force-install: + description: '是否强制安装 (true/false)' + required: false + default: 'false' + + install-command: + description: '自定义安装命令(若设置则完全覆盖默认命令)' + required: false + default: '' + + install-args: + description: '附加到默认安装命令的参数(当未提供 install-command 时生效)' + required: false + default: '' + + cache-hash: + description: '缓存hash值(推荐使用hashFiles)' + required: false + default: '' + + clean-project-store: + description: '安装后清理项目根目录的 .pnpm-store (true/false)' + required: false + default: 'false' + +outputs: + cache-hit: + description: '是否命中缓存 (true/false)' + value: ${{ steps.cache.outputs.cache-hit }} + + cache-key: + description: '使用的缓存key' + value: ${{ steps.cache-key.outputs.key }} + + cache-path: + description: '缓存路径(用于调试与复用)' + value: ${{ steps.cache-path.outputs.path }} + +runs: + using: 'composite' + steps: + - name: 检查pnpm + id: detect + shell: bash + run: | + if ! command -v pnpm >/dev/null 2>&1; then + echo "pnpm 未安装" + exit 1 + fi + VERSION=$(pnpm --version | tr -d '\n') + if [[ -z "$VERSION" ]]; then + echo "无法获取pnpm版本" + exit 1 + fi + echo "pnpm-version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "PNPM_VERSION=${VERSION}" >> "$GITHUB_ENV" + + - name: 生成缓存key + id: cache-key + shell: bash + env: + PNPM_VERSION: ${{ steps.detect.outputs.pnpm-version }} + FALLBACK_HASH: ${{ hashFiles('pnpm-lock.yaml') }} + PACKAGE_HASH: ${{ hashFiles('package.json') }} + run: | + set -euo pipefail + if [[ -n "${{ inputs.cache-hash }}" ]]; then + CACHE_HASH="${{ inputs.cache-hash }}" + elif [[ -n "${FALLBACK_HASH}" ]]; then + CACHE_HASH="${FALLBACK_HASH}" + elif [[ -n "${PACKAGE_HASH}" ]]; then + CACHE_HASH="${PACKAGE_HASH}" + else + CACHE_HASH="" + fi + if [[ -n "$CACHE_HASH" ]]; then + CACHE_HASH_SHORT=$(echo "$CACHE_HASH" | head -c 12) + else + CACHE_HASH_SHORT="no-hash" + fi + PNPM_VERSION="${PNPM_VERSION}" + CACHE_KEY="${{ runner.os }}-pnpm-v${PNPM_VERSION}-store-${{ inputs.cache-prefix }}-${CACHE_HASH_SHORT}" + RESTORE_PREFIX="${{ runner.os }}-pnpm-v${PNPM_VERSION}-store-${{ inputs.cache-prefix }}-" + echo "key=${CACHE_KEY}" >> "$GITHUB_OUTPUT" + echo "restore-prefix=${RESTORE_PREFIX}" >> "$GITHUB_OUTPUT" + echo "hash=${CACHE_HASH}" >> "$GITHUB_OUTPUT" + + - name: 确定缓存路径 + id: cache-path + shell: bash + run: | + set -euo pipefail + if ! command -v pnpm >/dev/null 2>&1; then + echo "❌ 未找到 pnpm,请先通过 pnpm/action-setup 安装" >&2 + exit 1 + fi + + STORE_DIR_CANDIDATE=$(pnpm store path --silent 2>/dev/null || true) + STORE_DIR_CANDIDATE=$(echo "$STORE_DIR_CANDIDATE" | tail -n1 | tr -d '\r') + + if [[ -z "$STORE_DIR_CANDIDATE" ]]; then + echo "❌ pnpm store path 未返回有效路径。可在运行前设置 PNPM_STORE_DIR=/path/to/store 或检查 pnpm 配置" >&2 + exit 1 + fi + + echo "pnpm store path: $STORE_DIR_CANDIDATE" + echo "PNPM_STORE_DIR=${STORE_DIR_CANDIDATE}" >> "$GITHUB_ENV" + echo "path=${STORE_DIR_CANDIDATE}" >> "$GITHUB_OUTPUT" + + - name: 拉取缓存 + id: cache + uses: actions/cache@v4 + with: + path: ${{ steps.cache-path.outputs.path }} + key: ${{ steps.cache-key.outputs.key }} + restore-keys: | + ${{ steps.cache-key.outputs.restore-prefix }} + + - name: 显示缓存状态 + shell: bash + run: | + if [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]]; then + echo "缓存命中" + else + echo "缓存未命中" + fi + + - name: 安装依赖 + shell: bash + run: | + set -euo pipefail + STORE_DIR="${PNPM_STORE_DIR:-${{ steps.cache-path.outputs.path }}}" + export PNPM_STORE_DIR="$STORE_DIR" + export npm_config_store_dir="$PNPM_STORE_DIR" + export PNPM_CONFIG_STORE_DIR="$PNPM_STORE_DIR" + echo "📦 Using PNPM_STORE_DIR=$PNPM_STORE_DIR" + if [[ -n "${{ inputs.install-command }}" ]]; then + echo "🔧 使用自定义安装命令: ${{ inputs.install-command }}" + eval "${{ inputs.install-command }}" + exit 0 + fi + if [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]]; then + FLAGS=("--offline" "--frozen-lockfile") + else + FLAGS=("--prefer-offline" "--frozen-lockfile") + fi + if [[ "${{ inputs.force-install }}" == "true" ]]; then + FLAGS+=("--force") + fi + INSTALL_ARGS="${{ inputs.install-args }}" + echo "🔧 执行 pnpm install ${FLAGS[*]} ${INSTALL_ARGS}" + if [[ -n "$INSTALL_ARGS" ]]; then + pnpm install "${FLAGS[@]}" $INSTALL_ARGS + else + pnpm install "${FLAGS[@]}" + fi + if [[ "${{ inputs.clean-project-store }}" == "true" && -d ".pnpm-store" && "$(cd "$PNPM_STORE_DIR" 2>/dev/null && pwd)" != "$(cd .pnpm-store 2>/dev/null && pwd)" ]]; then + rm -rf .pnpm-store || true + fi + echo "🧾 git status --short" + CHANGES=$(git status --short || true) + if [[ -n "$CHANGES" ]]; then + echo "$CHANGES" + echo "❌ 安装依赖后检测到工作区存在未提交变更。请检查上述文件,必要时更新配置或在调用前设置 PNPM_STORE_DIR。" >&2 + exit 1 + else + echo "✅ 工作区保持干净" + fi + + - name: 总结 + shell: bash + run: | + echo "pnpm版本: $PNPM_VERSION" + echo "缓存命中: ${{ steps.cache.outputs.cache-hit }}" + echo "缓存key: ${{ steps.cache-key.outputs.key }}" diff --git a/pnpm-install/examples/basic-usage.yml b/pnpm-install/examples/basic-usage.yml new file mode 100644 index 0000000..2af480a --- /dev/null +++ b/pnpm-install/examples/basic-usage.yml @@ -0,0 +1,35 @@ +# 基础pnpm项目示例 +name: pnpm项目CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 设置 Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: 安装依赖 + id: deps + uses: actions/xgj/pnpm-install@v1 + with: + cache-hash: ${{ hashFiles('pnpm-lock.yaml') }} + + - name: 运行测试 + run: pnpm test + + - name: 打印缓存状态 + run: | + echo "cache-hit: ${{ steps.deps.outputs['cache-hit'] }}" + echo "cache-key: ${{ steps.deps.outputs['cache-key'] }}" diff --git a/setup-env/action.yml b/setup-env/action.yml index a3733d6..62863ee 100644 --- a/setup-env/action.yml +++ b/setup-env/action.yml @@ -101,7 +101,7 @@ runs: /usr/libexec/docker/cli-plugins/docker-buildx /usr/libexec/docker/cli-plugins/docker-compose /usr/bin/kubectl - key: ${{ runner.os }}-${{ inputs.cache-key }}-${{ hashFiles('**/action.yml') }}-v2 + key: ${{ runner.os }}-${{ inputs.cache-key }}-${{ hashFiles('**/action.yml') }}-v3 restore-keys: | ${{ runner.os }}-${{ inputs.cache-key }}- diff --git a/setup-env/scripts/setup-environment.sh b/setup-env/scripts/setup-environment.sh index 3c51de5..24c3685 100755 --- a/setup-env/scripts/setup-environment.sh +++ b/setup-env/scripts/setup-environment.sh @@ -101,7 +101,7 @@ install_docker() { # 更新源并安装 Docker apt-get update apt-get install -y \ - docker-ce-cli \ + docker-ce-cli=5:20.10.21~3-0~debian-bullseye \ docker-buildx-plugin \ docker-compose-plugin diff --git a/trigger-version/README.md b/trigger-version/README.md index deb6045..bead45b 100644 --- a/trigger-version/README.md +++ b/trigger-version/README.md @@ -7,28 +7,35 @@ - 🏷️ **标签触发检测**:自动识别版本标签(如 `v1.2.3`)并提取版本号 - 🔄 **版本分支检测**:识别版本分支(如 `v1.2.x`)并提取版本信息 - 🆕 **常规分支处理**:对于非版本分支提供基础信息 +- 📦 **最新版本获取**:始终获取仓库中的最新版本号,无论触发方式如何 - 🎯 **灵活的版本前缀**:支持自定义版本前缀(默认为 `v`) +- 🔍 **标签过滤功能**:支持通配符模式匹配和排除特定标签 - 🔧 **版本格式转换**:自动生成横线格式的版本号(如 `v1.2.3` → `v1-2-3`) - 📤 **环境变量输出**:自动设置环境变量供后续步骤使用 - 📊 **详细的输出信息**:提供完整的引用信息和触发状态 ## 输入参数 -| 参数名 | 描述 | 必需 | 默认值 | -| ---------------- | -------------------------------- | ---- | ------ | -| `version-prefix` | 版本前缀,用于匹配版本标签或分支 | 否 | `v` | +| 参数名 | 描述 | 必需 | 默认值 | +| -------------------- | ------------------------------------------------------------ | ---- | ------- | +| `version-prefix` | 版本前缀,用于匹配版本标签或分支 | 否 | `v` | +| `use-latest-version` | 在非版本触发时是否使用最新版本 | 否 | `false` | +| `tag-match` | 标签匹配模式,支持通配符(如:`v*.*.*` 或 `v[0-9]*`),用于进一步过滤标签 | 否 | `""` | +| `tag-exclude` | 标签排除模式,支持通配符(如:`*-alpha*` 或 `*-beta*`),匹配的标签将被排除 | 否 | `""` | ## 输出参数 -| 参数名 | 描述 | 示例值 | -| -------------------- | ------------------------ | ------------------------------- | -| `ref-type` | 引用类型 | `tag` 或 `branch` | -| `ref-name` | 引用名称 | `v1.2.3`、`main`、`feature/xxx` | -| `is-version-trigger` | 是否为版本触发 | `true` 或 `false` | -| `trigger-version` | 触发的版本号(去除前缀) | `1.2.3` | -| `version-with-dash` | 版本号,点替换为横线 | `1-2-3` | -| `trigger-source` | 触发源 | `tag` 或 `branch` | -| `full-ref` | 完整的 Git 引用 | `refs/tags/v1.2.3` | +| 参数名 | 描述 | 示例值 | +| -------------------------- | -------------------------- | ------------------------------- | +| `ref-type` | 引用类型 | `tag` 或 `branch` | +| `ref-name` | 引用名称 | `v1.2.3`、`main`、`feature/xxx` | +| `is-version-trigger` | 是否为版本触发 | `true` 或 `false` | +| `trigger-version` | 触发的版本号(标准化格式) | `v1.2.3` | +| `version-with-dash` | 版本号,点替换为横线 | `v1-2-3` | +| `trigger-source` | 触发源 | `tag` 或 `branch` | +| `full-ref` | 完整的 Git 引用 | `refs/tags/v1.2.3` | +| `latest-version` | 仓库中的最新版本号 | `v1.2.3` | +| `latest-version-with-dash` | 最新版本号,点替换为横线 | `v1-2-3` | ## 环境变量 @@ -56,17 +63,24 @@ jobs: outputs: is-version-trigger: ${{ steps.version-info.outputs.is-version-trigger }} trigger-version: ${{ steps.version-info.outputs.trigger-version }} + latest-version: ${{ steps.version-info.outputs.latest-version }} version-with-dash: ${{ steps.version-info.outputs.version-with-dash }} trigger-source: ${{ steps.version-info.outputs.trigger-source }} steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # 必需:获取完整历史以获取所有标签 + - name: 获取版本信息 id: version-info - uses: actions/xgj/trigger-version@v1 + uses: ./trigger-version - name: 显示版本信息 run: | echo "是否版本触发: ${{ steps.version-info.outputs.is-version-trigger }}" - echo "版本号: ${{ steps.version-info.outputs.trigger-version }}" + echo "触发版本: ${{ steps.version-info.outputs.trigger-version }}" + echo "最新版本: ${{ steps.version-info.outputs.latest-version }}" echo "横线版本号: ${{ steps.version-info.outputs.version-with-dash }}" echo "触发源: ${{ steps.version-info.outputs.trigger-source }}" @@ -81,6 +95,25 @@ jobs: echo "Docker标签: myapp:${{ needs.get-version-info.outputs.version-with-dash }}" ``` +### 使用最新版本功能 + +````yaml +- name: 获取版本信息(启用最新版本) + id: version-info + uses: ./trigger-version + with: + use-latest-version: true # 在非版本触发时使用最新版本 + +- name: 使用最新版本进行构建 + run: | + if [[ -n "${{ steps.version-info.outputs.latest-version }}" ]]; then + echo "构建镜像标签: myapp:${{ steps.version-info.outputs.latest-version-with-dash }}" + # docker build -t myapp:${{ steps.version-info.outputs.latest-version-with-dash }} . + else + echo "未找到版本标签,使用默认标签" + # docker build -t myapp:latest . + fi + ### 自定义版本前缀 ```yaml @@ -91,6 +124,73 @@ jobs: version-prefix: "release-" ``` +### 标签过滤功能 + +#### 排除预发布版本 + +```yaml +- name: 获取稳定版本(排除预发布) + id: stable-version + uses: ./trigger-version + with: + version-prefix: "v" + use-latest-version: true + tag-exclude: "*-alpha*" # 排除包含 alpha 的版本 + +- name: 显示稳定版本 + run: | + echo "最新稳定版本: ${{ steps.stable-version.outputs.latest-version }}" +``` + +#### 匹配特定版本模式 + +```yaml +- name: 获取标准语义版本 + id: semver + uses: ./trigger-version + with: + version-prefix: "v" + use-latest-version: true + tag-match: "v[0-9]*.[0-9]*.[0-9]*" # 只匹配 v1.2.3 格式 + +- name: 显示语义版本 + run: | + echo "语义版本: ${{ steps.semver.outputs.latest-version }}" +``` + +#### 复合过滤条件 + +```yaml +- name: 获取 v2.x 稳定版本 + id: v2-stable + uses: ./trigger-version + with: + version-prefix: "v" + use-latest-version: true + tag-match: "v2.*.*" # 只匹配 v2.x.x 版本 + tag-exclude: "*-*" # 排除所有预发布版本(包含连字符) + +- name: 显示 v2.x 稳定版本 + run: | + echo "v2.x 最新稳定版本: ${{ steps.v2-stable.outputs.latest-version }}" +``` + +#### 获取预发布版本 + +```yaml +- name: 获取最新 beta 版本 + id: beta-version + uses: ./trigger-version + with: + version-prefix: "v" + use-latest-version: true + tag-match: "*-beta*" # 只匹配包含 beta 的版本 + +- name: 显示 beta 版本 + run: | + echo "最新 beta 版本: ${{ steps.beta-version.outputs.latest-version }}" +```` + ### 完整的 CI/CD 流程 ```yaml @@ -150,6 +250,44 @@ jobs: echo "部署版本 ${{ needs.analyze.outputs.trigger-version }} 到生产环境" ``` +## 标签过滤功能详解 + +### 通配符模式支持 + +`tag-match` 和 `tag-exclude` 参数支持 bash 通配符模式: + +- `*`:匹配任意字符序列 +- `?`:匹配单个字符 +- `[...]`:匹配括号内的任意字符 +- `[!...]`:匹配不在括号内的任意字符 + +### 常用过滤模式 + +| 模式 | 描述 | 示例匹配 | +| ------------------- | ------------------------------ | --------------------------- | +| `v*.*.*` | 匹配标准三段式版本号 | `v1.2.3`, `v2.0.1` | +| `v[0-9]*.[0-9]*.*` | 匹配数字开头的版本号 | `v1.2.3`, `v10.0.1` | +| `*-alpha*` | 匹配包含 alpha 的版本 | `v1.0.0-alpha1` | +| `*-beta*` | 匹配包含 beta 的版本 | `v1.0.0-beta2` | +| `*-rc*` | 匹配包含 rc 的版本 | `v1.0.0-rc1` | +| `*-*` | 匹配所有包含连字符的版本 | `v1.0.0-alpha`, `v1.0.0-1` | +| `v2.*` | 匹配 v2 开头的所有版本 | `v2.0.0`, `v2.1.5` | +| `v[12].*.*` | 匹配 v1 或 v2 开头的版本 | `v1.0.0`, `v2.3.1` | + +### 过滤优先级 + +1. 首先应用 `version-prefix` 过滤(获取匹配前缀的标签) +2. 然后应用 `tag-match` 过滤(保留匹配模式的标签) +3. 最后应用 `tag-exclude` 过滤(排除匹配模式的标签) +4. 对剩余标签进行版本排序,选择最新版本 + +### 注意事项 + +- 标签过滤主要影响 `latest-version` 的获取,不影响当前触发版本的识别 +- 当通过标签触发时,`trigger-version` 始终是触发的标签,过滤只影响 `latest-version` +- 如果过滤后没有匹配的标签,`latest-version` 将为空 +- 建议在使用过滤功能时设置 `fetch-depth: 0` 以获取完整的标签历史 + ## 触发场景 ### 标签触发 @@ -183,14 +321,19 @@ jobs: 1. **条件部署**:使用 `is-version-trigger` 来决定是否执行生产部署 2. **版本标记**:在构建产物中使用 `trigger-version` 进行版本标记 -3. **Docker 标签**:使用 `version-with-dash` 作为 Docker 镜像标签(避免点号问题) -4. **环境区分**:根据触发源选择不同的部署环境 -5. **日志记录**:记录详细的版本信息用于追踪和调试 +3. **最新版本获取**:使用 `latest-version` 输出来获取仓库的最新版本,适用于回滚或版本比较 +4. **Docker 标签**:使用 `version-with-dash` 作为 Docker 镜像标签(避免点号问题) +5. **环境区分**:根据触发源选择不同的部署环境 +6. **完整历史获取**:在工作流中使用 `fetch-depth: 0` 确保能获取所有标签 +7. **日志记录**:记录详细的版本信息用于追踪和调试 ## 注意事项 - 版本前缀区分大小写 - 空的版本号会被设置为空字符串 +- `latest-version` 输出始终获取仓库的最新版本,无论触发方式如何 +- 获取最新版本需要完整的 Git 历史,建议使用 `fetch-depth: 0` +- 最新版本按语义化版本排序,确保标签格式符合版本规范 - 确保工作流触发条件与你的版本策略一致 - 在使用输出参数时注意布尔值的字符串比较(使用 `== 'true'`) diff --git a/trigger-version/action.yml b/trigger-version/action.yml index a59757c..b86adc1 100644 --- a/trigger-version/action.yml +++ b/trigger-version/action.yml @@ -1,47 +1,148 @@ -name: 'Trigger Version Info' -description: '获取触发版本信息,支持标签触发、版本分支触发和常规分支触发' -author: 'Your Organization' +name: "Trigger Version Info" +description: "获取触发版本信息,支持标签触发、版本分支触发和常规分支触发" +author: "Your Organization" inputs: version-prefix: - description: '版本前缀,用于匹配版本标签或分支(默认:v)' + description: "版本前缀,用于匹配版本标签或分支(默认:v)" required: false - default: 'v' + default: "v" use-latest-version: - description: '在非版本触发时是否使用当前分支最新的版本标签(默认:false)' + description: "在非版本触发时是否使用当前分支最新的版本标签(默认:false)" required: false - default: 'false' + default: "false" + tag-match: + description: "标签匹配模式,支持通配符(如:v*.*.* 或 v[0-9]*),用于进一步过滤标签" + required: false + default: "" + tag-exclude: + description: "标签排除模式,支持通配符(如:*-alpha* 或 *-beta*),匹配的标签将被排除" + required: false + default: "" outputs: ref-type: - description: '引用类型 (tag/branch)' + description: "引用类型 (tag/branch)" value: ${{ steps.get-version-info.outputs.ref_type }} ref-name: - description: '引用名称' + description: "引用名称" value: ${{ steps.get-version-info.outputs.ref_name }} is-version-trigger: - description: '是否为版本触发(true/false)' + description: "是否为版本触发(true/false)" value: ${{ steps.get-version-info.outputs.is_version_trigger }} trigger-version: - description: '触发的版本号(标准化为v开头的格式)' + description: "触发的版本号(标准化为v开头的格式)" value: ${{ steps.get-version-info.outputs.trigger_version }} trigger-source: - description: '触发源(tag/branch)' + description: "触发源(tag/branch)" value: ${{ steps.get-version-info.outputs.trigger_source }} full-ref: - description: '完整的 Git 引用' + description: "完整的 Git 引用" value: ${{ steps.get-version-info.outputs.full_ref }} version-with-dash: - description: '版本号,点替换为横线(例如:v1.2.3 -> v1-2-3)' + description: "版本号,点替换为横线(例如:v1.2.3 -> v1-2-3)" value: ${{ steps.get-version-info.outputs.version_with_dash }} + latest-version: + description: "仓库中的最新版本号(始终获取,格式如:v1.2.3)" + value: ${{ steps.get-version-info.outputs.latest_version }} + latest-version-with-dash: + description: "最新版本号,点替换为横线(例如:v1.2.3 -> v1-2-3)" + value: ${{ steps.get-version-info.outputs.latest_version_with_dash }} runs: - using: 'composite' + using: "composite" steps: - name: 获取触发版本信息 id: get-version-info shell: bash run: | + # 获取最新版本号的函数 + get_latest_version() { + local prefix="$1" + local tag_match="$2" + local tag_exclude="$3" + local latest_version="" + local latest_version_with_dash="" + + echo "🔍 开始获取最新版本号(前缀:${prefix})..." + [[ -n "$tag_match" ]] && echo "🎯 标签匹配模式: $tag_match" + [[ -n "$tag_exclude" ]] && echo "🚫 标签排除模式: $tag_exclude" + + # 检查git仓库是否可用 + if git rev-parse --git-dir > /dev/null 2>&1; then + echo "✅ Git仓库可用,开始获取标签..." + + # 确保获取所有标签信息(GitHub Actions 默认是浅克隆) + echo "📥 获取远程标签信息..." + git fetch --tags --quiet 2>/dev/null || echo "⚠️ 获取远程标签失败,继续使用本地标签" + + # 获取所有匹配的标签 + echo "🏷️ 查找匹配前缀 '${prefix}' 的标签..." + local all_tags=$(git tag --list "${prefix}*" 2>/dev/null) + + if [[ -n "$all_tags" ]]; then + # 应用标签匹配过滤 + if [[ -n "$tag_match" ]]; then + echo "🔍 应用标签匹配过滤: $tag_match" + local filtered_tags="" + while IFS= read -r tag; do + if [[ "$tag" == $tag_match ]]; then + filtered_tags="$filtered_tags$tag\n" + fi + done <<< "$all_tags" + all_tags=$(echo -e "$filtered_tags" | sed '/^$/d') + echo "📋 匹配后的标签数量: $(echo "$all_tags" | wc -l | tr -d ' ')" + fi + + # 应用标签排除过滤 + if [[ -n "$tag_exclude" && -n "$all_tags" ]]; then + echo "🚫 应用标签排除过滤: $tag_exclude" + local filtered_tags="" + while IFS= read -r tag; do + if [[ "$tag" != $tag_exclude ]]; then + filtered_tags="$filtered_tags$tag\n" + fi + done <<< "$all_tags" + all_tags=$(echo -e "$filtered_tags" | sed '/^$/d') + echo "📋 排除后的标签数量: $(echo "$all_tags" | wc -l | tr -d ' ')" + fi + + if [[ -n "$all_tags" ]]; then + # echo "最终标签列表:" + # echo "$all_tags" + + # 获取最新的版本标签 + if command -v sort >/dev/null 2>&1; then + # 使用 sort 命令进行版本排序 + latest_version=$(echo "$all_tags" | sort -V | tail -1) + else + # 如果没有 sort -V,使用简单的字典序排序 + latest_version=$(echo "$all_tags" | sort | tail -1) + fi + + if [[ -n "$latest_version" ]]; then + # 生成带横线的版本号 + latest_version_with_dash=$(echo "$latest_version" | sed 's/\./-/g') + echo "📦 找到最新版本: $latest_version" + echo "📦 横线格式版本: $latest_version_with_dash" + else + echo "⚠️ 未能确定最新版本" + fi + else + echo "⚠️ 过滤后没有找到匹配的版本标签" + fi + else + echo "⚠️ 未找到匹配前缀 '${prefix}' 的版本标签" + fi + else + echo "❌ Git仓库不可用" + fi + + # 设置全局变量 + LATEST_VERSION="$latest_version" + LATEST_VERSION_WITH_DASH="$latest_version_with_dash" + } + # 获取GitHub上下文信息 echo "触发方式: ${{ github.event_name }}" echo "引用类型: ${{ github.ref_type }}" @@ -53,6 +154,11 @@ runs: FULL_REF="${{ github.ref }}" VERSION_PREFIX="${{ inputs.version-prefix }}" USE_LATEST_VERSION="${{ inputs.use-latest-version }}" + TAG_MATCH="${{ inputs.tag-match }}" + TAG_EXCLUDE="${{ inputs.tag-exclude }}" + + # 获取最新版本号(无论触发方式如何都获取) + get_latest_version "$VERSION_PREFIX" "$TAG_MATCH" "$TAG_EXCLUDE" # 判断是否为标签触发 if [[ "$REF_TYPE" == "tag" ]]; then @@ -84,31 +190,17 @@ runs: echo "🆕 常规分支触发: $REF_NAME" if [[ "$USE_LATEST_VERSION" == "true" ]]; then - # 获取当前分支最新的版本标签 - echo "🔍 查找最新版本标签..." - - # 检查git仓库是否可用 - if git rev-parse --git-dir > /dev/null 2>&1; then - LATEST_TAG=$(git tag --list "${VERSION_PREFIX}*" --sort=-version:refname 2>/dev/null | head -1) - - if [[ -n "$LATEST_TAG" ]]; then - # 找到了版本标签,使用它 - TRIGGER_VERSION=$LATEST_TAG - VERSION_WITH_DASH=$(echo "$TRIGGER_VERSION" | sed 's/\./-/g') - echo "📦 使用最新版本标签: $LATEST_TAG" - echo "标准化版本号: $TRIGGER_VERSION" - echo "横线版本号: $VERSION_WITH_DASH" - IS_VERSION_TRIGGER=true - else - # 没有找到版本标签 - echo "⚠️ 未找到版本标签,使用空版本" - IS_VERSION_TRIGGER=false - TRIGGER_VERSION="" - VERSION_WITH_DASH="" - fi + # 使用已获取的最新版本 + if [[ -n "$LATEST_VERSION" ]]; then + TRIGGER_VERSION=$LATEST_VERSION + VERSION_WITH_DASH=$LATEST_VERSION_WITH_DASH + echo "📦 使用最新版本标签: $LATEST_VERSION" + echo "标准化版本号: $TRIGGER_VERSION" + echo "横线版本号: $VERSION_WITH_DASH" + IS_VERSION_TRIGGER=true else - # Git仓库不可用 - echo "⚠️ Git仓库不可用,使用空版本" + # 没有找到版本标签 + echo "⚠️ 未找到匹配前缀 '${VERSION_PREFIX}' 的版本标签,使用空版本" IS_VERSION_TRIGGER=false TRIGGER_VERSION="" VERSION_WITH_DASH="" @@ -137,7 +229,17 @@ runs: echo "trigger_version=$TRIGGER_VERSION" >> $GITHUB_OUTPUT echo "version_with_dash=$VERSION_WITH_DASH" >> $GITHUB_OUTPUT echo "trigger_source=$TRIGGER_SOURCE" >> $GITHUB_OUTPUT + echo "latest_version=$LATEST_VERSION" >> $GITHUB_OUTPUT + echo "latest_version_with_dash=$LATEST_VERSION_WITH_DASH" >> $GITHUB_OUTPUT + + # 输出摘要信息 + echo "" + echo "=== 版本信息摘要 ===" + echo "🔸 触发版本: $TRIGGER_VERSION" + echo "🔸 最新版本: $LATEST_VERSION" + echo "🔸 是否版本触发: $IS_VERSION_TRIGGER" + echo "🔸 触发源: $TRIGGER_SOURCE" branding: - icon: 'git-branch' - color: 'blue' + icon: "git-branch" + color: "blue" diff --git a/trigger-version/examples/latest-version-demo.yml b/trigger-version/examples/latest-version-demo.yml new file mode 100644 index 0000000..788d521 --- /dev/null +++ b/trigger-version/examples/latest-version-demo.yml @@ -0,0 +1,65 @@ +name: Latest Version Demo +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +jobs: + show-version-info: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # 获取完整历史,确保能获取所有标签 + + - name: Get Version Info + id: version + uses: ./trigger-version + with: + version-prefix: "v" + use-latest-version: true + + - name: Display Version Information + run: | + echo "=== 版本信息详情 ===" + echo "引用类型: ${{ steps.version.outputs.ref-type }}" + echo "引用名称: ${{ steps.version.outputs.ref-name }}" + echo "完整引用: ${{ steps.version.outputs.full-ref }}" + echo "" + echo "=== 触发版本信息 ===" + echo "是否版本触发: ${{ steps.version.outputs.is-version-trigger }}" + echo "触发版本: ${{ steps.version.outputs.trigger-version }}" + echo "触发源: ${{ steps.version.outputs.trigger-source }}" + echo "版本(横线格式): ${{ steps.version.outputs.version-with-dash }}" + echo "" + echo "=== 最新版本信息 ===" + echo "最新版本: ${{ steps.version.outputs.latest-version }}" + echo "最新版本(横线格式): ${{ steps.version.outputs.latest-version-with-dash }}" + + - name: Use Latest Version in Deployment + if: steps.version.outputs.latest-version != '' + run: | + echo "使用最新版本进行部署: ${{ steps.version.outputs.latest-version }}" + echo "镜像标签: myapp:${{ steps.version.outputs.latest-version-with-dash }}" + + # 示例:构建 Docker 镜像 + # docker build -t myapp:${{ steps.version.outputs.latest-version-with-dash }} . + + # 示例:设置 Kubernetes 部署 + # kubectl set image deployment/myapp container=myapp:${{ steps.version.outputs.latest-version-with-dash }} + + - name: Handle No Version Found + if: steps.version.outputs.latest-version == '' + run: | + echo "⚠️ 未找到任何版本标签" + echo "这可能是因为:" + echo "1. 仓库中没有版本标签" + echo "2. 版本标签不匹配指定的前缀" + echo "3. Git 获取标签失败" + echo "" + echo "建议:" + echo "1. 检查仓库是否有版本标签(如 v1.0.0)" + echo "2. 确认版本前缀设置正确" + echo "3. 确保 fetch-depth: 0 以获取完整历史" diff --git a/trigger-version/examples/tag-filtering-demo.yml b/trigger-version/examples/tag-filtering-demo.yml new file mode 100644 index 0000000..e81130d --- /dev/null +++ b/trigger-version/examples/tag-filtering-demo.yml @@ -0,0 +1,137 @@ +name: Tag Filtering Demo + +# 演示如何使用 tagMatch 和 tagExclude 功能来过滤版本标签 + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + # 示例1:只匹配稳定版本(排除预发布版本) + stable-versions-only: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # 获取完整的 git 历史 + + - name: Get stable version (exclude alpha/beta/rc) + id: stable-version + uses: ./trigger-version + with: + version-prefix: 'v' + use-latest-version: 'true' + tag-exclude: '*-alpha*|*-beta*|*-rc*' + + - name: Display stable version info + run: | + echo "Latest stable version: ${{ steps.stable-version.outputs.latest-version }}" + echo "Version with dash: ${{ steps.stable-version.outputs.latest-version-with-dash }}" + + # 示例2:只匹配特定模式的版本 + specific-pattern-match: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get version matching specific pattern + id: pattern-version + uses: ./trigger-version + with: + version-prefix: 'v' + use-latest-version: 'true' + tag-match: 'v[0-9]*.[0-9]*.[0-9]*' # 只匹配标准的语义版本格式 + + - name: Display pattern matched version + run: | + echo "Pattern matched version: ${{ steps.pattern-version.outputs.latest-version }}" + + # 示例3:复杂过滤 - 匹配主版本号并排除预发布 + complex-filtering: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get v2.x versions excluding pre-releases + id: v2-stable + uses: ./trigger-version + with: + version-prefix: 'v' + use-latest-version: 'true' + tag-match: 'v2.*.*' # 只匹配 v2.x.x 版本 + tag-exclude: '*-*' # 排除所有包含连字符的版本(预发布版本) + + - name: Display filtered version + run: | + echo "Latest v2.x stable version: ${{ steps.v2-stable.outputs.latest-version }}" + + # 示例4:获取预发布版本 + prerelease-versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get latest beta version + id: beta-version + uses: ./trigger-version + with: + version-prefix: 'v' + use-latest-version: 'true' + tag-match: '*-beta*' # 只匹配包含 beta 的版本 + + - name: Display beta version + run: | + echo "Latest beta version: ${{ steps.beta-version.outputs.latest-version }}" + + # 示例5:多条件排除 + multi-exclude: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get version excluding multiple patterns + id: multi-exclude + uses: ./trigger-version + with: + version-prefix: 'v' + use-latest-version: 'true' + # 注意:多个排除模式需要在 shell 脚本中处理,这里展示单个模式 + tag-exclude: '*-alpha*' # 排除 alpha 版本 + + - name: Display filtered version + run: | + echo "Version (excluding alpha): ${{ steps.multi-exclude.outputs.latest-version }}" + + # 示例6:在版本触发时的行为 + version-trigger-behavior: + runs-on: ubuntu-latest + if: github.ref_type == 'tag' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Process version trigger + id: version-trigger + uses: ./trigger-version + with: + version-prefix: 'v' + # 注意:当通过标签触发时,tag-match 和 tag-exclude 主要影响 latest-version 的获取 + # 而不影响当前触发的版本 + tag-exclude: '*-alpha*' + + - name: Display trigger info + run: | + echo "Triggered by tag: ${{ steps.version-trigger.outputs.trigger-version }}" + echo "Latest stable version: ${{ steps.version-trigger.outputs.latest-version }}" + echo "Is version trigger: ${{ steps.version-trigger.outputs.is-version-trigger }}" \ No newline at end of file