Files
tools/gcp/create_ai_projects.sh
Wang Defa b34e1ef872 feat: 为所有脚本添加远程库加载支持
## 功能概述

实现了智能远程库加载机制,允许脚本从远程 Gitea 仓库动态下载公共库,
同时保留本地库优先的策略,确保脚本可以独立运行而不依赖本地项目结构。

## 变更内容

### 新增文件

- **common/remote_loader.sh**: 核心远程加载器
  - 支持 curl/wget 双重下载方式
  - 临时文件自动清理机制
  - 环境变量可配置仓库 URL

- **examples/remote_example.sh**: 纯远程模式示例
  - 演示完全独立的远程加载脚本
  - 适合单文件分发场景

- **examples/hybrid_loader_template.sh**: 混合模式模板
  - 本地优先 + 远程回退策略
  - 适合仓库内脚本

- **examples/REMOTE_LOADER_README.md**: 完整使用文档
  - 三种使用模式详解
  - 故障排除指南
  - 最佳实践建议

### 更新脚本(版本 2.1.0)

所有主要脚本均升级到 2.1.0 版本,支持智能库加载:

- **linux/create_raid0_array.sh**
- **linux/repartition_disks.sh**
- **linux/install_oh_my_zsh.sh**
- **gcp/create_ai_projects.sh**
- **gcp/delete_all_projects.sh**
- **oci/create_instance.sh**
- **common/demo_usage.sh**

## 加载策略

1. 检查 FORCE_REMOTE=1 环境变量 → 强制远程
2. 检查本地库存在性 → 优先使用本地
3. 本地不存在 → 自动回退远程
4. 远程失败 → 友好错误提示

## 使用示例

### 本地模式(默认)
```bash
./linux/create_raid0_array.sh
```

### 强制远程模式
```bash
FORCE_REMOTE=1 ./linux/create_raid0_array.sh
```

### 自定义仓库 URL
```bash
REMOTE_LIB_URL=https://example.com/repo ./script.sh
```

## 技术特性

-  向后兼容:默认使用本地库,行为不变
-  离线可用:本地库存在时无需网络
-  独立分发:支持单文件脚本分发
-  错误处理:详细的错误信息和诊断建议
-  环境可配:REMOTE_LIB_URL 和 FORCE_REMOTE
-  自动清理:临时文件通过 trap 自动删除
2025-12-26 15:00:06 +08:00

571 lines
17 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ============================================================================
# 文件名: create_ai_projects.sh
# 描述: 批量创建 Google Cloud Platform AI 项目并配置相关服务
# 作者: Cloud Tools Project
# 版本: 2.1.0(支持远程库加载)
# ============================================================================
set -euo pipefail # 启用严格模式
# ============================================================================
# 远程库加载配置
# ============================================================================
# 远程仓库 URL可通过环境变量覆盖
readonly REMOTE_BASE_URL="${REMOTE_LIB_URL:-https://gitea.bcde.io/wangdefa/tools/raw/branch/main}"
# 获取脚本目录(用于本地加载)
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
#
# 智能加载公共库
#
# 加载策略:
# 1. 如果 FORCE_REMOTE=1强制使用远程库
# 2. 否则尝试使用本地库
# 3. 本地库不存在时自动回退到远程库
#
load_common_libs() {
local use_remote=false
# 检查是否强制远程
if [[ "${FORCE_REMOTE:-0}" == "1" ]]; then
echo "[INFO] 强制使用远程库 (FORCE_REMOTE=1)" >&2
use_remote=true
# 检查本地库是否存在
elif [[ -f "${PROJECT_ROOT}/common/logging.sh" ]] && [[ -f "${PROJECT_ROOT}/common/error_handler.sh" ]]; then
# shellcheck disable=SC1091
source "${PROJECT_ROOT}/common/logging.sh"
# shellcheck disable=SC1091
source "${PROJECT_ROOT}/common/error_handler.sh"
return 0
else
echo "[WARN] 本地库不存在,使用远程库" >&2
use_remote=true
fi
# 使用远程库
if [[ "$use_remote" == "true" ]]; then
if command -v curl &>/dev/null; then
echo "[INFO] 使用 curl 下载远程库..." >&2
# shellcheck disable=SC1090
if source <(curl -fsSL "${REMOTE_BASE_URL}/common/remote_loader.sh" 2>/dev/null); then
return 0
fi
elif command -v wget &>/dev/null; then
echo "[INFO] 使用 wget 下载远程库..." >&2
# shellcheck disable=SC1090
if source <(wget -qO- "${REMOTE_BASE_URL}/common/remote_loader.sh" 2>/dev/null); then
return 0
fi
fi
echo "[ERROR] 无法加载公共库" >&2
echo "[ERROR] - 本地库不存在" >&2
echo "[ERROR] - 远程下载失败(需要 curl 或 wget" >&2
echo "[ERROR] - 仓库 URL: ${REMOTE_BASE_URL}" >&2
exit 1
fi
}
# 加载公共库
load_common_libs
# 默认配置
readonly DEFAULT_PROJECT_ID_PREFIX="project"
readonly DEFAULT_START_NUM=1
readonly DEFAULT_REPEAT_NUM=5
readonly DEFAULT_MAX_RETRIES=3
readonly DEFAULT_RETRY_DELAY=5
# 创建日志文件
readonly LOG_FILE="created_projects_$(date +%Y%m%d_%H%M%S).log"
log_set_file "$LOG_FILE"
#
# 显示脚本使用方法
#
usage() {
log_info "用法: $0 [选项]"
log_info "选项:"
log_info " -p, --prefix PREFIX 项目ID前缀 (默认: $DEFAULT_PROJECT_ID_PREFIX)"
log_info " -s, --start START 项目编号起始值 (默认: $DEFAULT_START_NUM)"
log_info " -n, --number NUMBER 创建项目的数量 (默认: $DEFAULT_REPEAT_NUM)"
log_info " --max-retries RETRIES 单个命令失败时的最大重试次数 (默认: $DEFAULT_MAX_RETRIES)"
log_info " --retry-delay DELAY 每次重试之间的延迟秒数 (默认: $DEFAULT_RETRY_DELAY)"
log_info " --debug 启用调试模式"
log_info " -h, --help 显示此帮助信息"
}
#
# 检查必要的命令是否存在
#
check_command() {
if ! command -v "$1" &> /dev/null; then
log_error "命令 $1 未找到。请确保它已安装并在PATH中。"
exit 1
fi
}
#
# 重试执行命令
#
retry_command() {
local max_attempts=$1
local delay=$2
shift 2
local cmd_to_run=("$@")
local attempt=1
local exit_code
local temp_output
local temp_error
while [ $attempt -le "$max_attempts" ]; do
log_info "尝试执行 (第 $attempt/$max_attempts 次): ${cmd_to_run[*]}"
# 创建临时文件捕获输出和错误
temp_output=$(mktemp)
temp_error=$(mktemp)
if "${cmd_to_run[@]}" > "$temp_output" 2> "$temp_error"; then
log_success "命令成功执行: ${cmd_to_run[*]}"
# 如果有输出,显示它
if [ -s "$temp_output" ]; then
cat "$temp_output"
fi
rm -f "$temp_output" "$temp_error" 2>/dev/null || true
return 0
else
exit_code=$?
log_warning "命令执行失败 (第 $attempt/$max_attempts 次),退出码: $exit_code"
# 显示错误信息用于调试
if [ -s "$temp_error" ]; then
log_error "错误输出: $(cat "$temp_error")"
fi
if [ $attempt -lt "$max_attempts" ]; then
log_info "将在 $delay 秒后重试..."
sleep "$delay"
fi
fi
rm -f "$temp_output" "$temp_error" 2>/dev/null || true
((attempt++))
done
log_error "命令在 $max_attempts 次尝试后仍然失败: ${cmd_to_run[*]}"
return 1
}
#
# 重试并获取命令输出
#
retry_command_with_output() {
local max_attempts=$1
local delay=$2
local output_var=$3
shift 3
local cmd_to_run=("$@")
local attempt=1
local exit_code
local temp_output
local temp_error
while [ $attempt -le "$max_attempts" ]; do
log_info "尝试执行 (第 $attempt/$max_attempts 次): ${cmd_to_run[*]}"
temp_output=$(mktemp)
temp_error=$(mktemp)
if "${cmd_to_run[@]}" > "$temp_output" 2> "$temp_error"; then
log_success "命令成功执行: ${cmd_to_run[*]}"
# 将输出赋值给指定变量
eval "$output_var=\$(cat '$temp_output')"
rm -f "$temp_output" "$temp_error" 2>/dev/null || true
return 0
else
exit_code=$?
log_warning "命令执行失败 (第 $attempt/$max_attempts 次),退出码: $exit_code"
if [ -s "$temp_error" ]; then
log_error "错误输出: $(cat "$temp_error")"
fi
if [ $attempt -lt "$max_attempts" ]; then
log_info "将在 $delay 秒后重试..."
sleep "$delay"
fi
fi
rm -f "$temp_output" "$temp_error" 2>/dev/null || true
((attempt++))
done
log_error "命令在 $max_attempts 次尝试后仍然失败: ${cmd_to_run[*]}"
return 1
}
#
# 创建 GCP 项目
#
create_project() {
local project_id=$1
local max_retries=$2
local retry_delay=$3
log_info "创建项目: $project_id"
# 检查项目是否已存在
if gcloud projects describe "$project_id" &>/dev/null; then
log_warning "项目 $project_id 已存在,跳过创建"
return 0
fi
if retry_command "$max_retries" "$retry_delay" gcloud projects create "$project_id" --name="$project_id"; then
log_success "项目 $project_id 创建成功"
return 0
else
log_error "创建项目 $project_id 失败 (已重试)"
return 1
fi
}
#
# 链接结算账号
#
link_billing() {
local project_id=$1
local billing_id=$2
local max_retries=$3
local retry_delay=$4
log_info "链接结算账号到项目: $project_id"
if retry_command "$max_retries" "$retry_delay" gcloud beta billing projects link "$project_id" --billing-account="$billing_id"; then
log_success "结算账号链接成功"
return 0
else
log_error "链接结算账号到项目 $project_id 失败 (已重试)"
return 1
fi
}
#
# 启用服务
#
enable_services() {
local project_id=$1
local max_retries=$2
local retry_delay=$3
log_info "启用 aiplatform.googleapis.com 服务"
if retry_command "$max_retries" "$retry_delay" gcloud services enable aiplatform.googleapis.com --project="$project_id"; then
log_success "服务启用成功"
return 0
else
log_error "启用 aiplatform.googleapis.com 服务失败 (已重试)"
return 1
fi
}
#
# 创建服务账号
#
create_service_account() {
local project_id=$1
local max_retries=$2
local retry_delay=$3
log_info "创建服务账号 service-account@$project_id.iam.gserviceaccount.com"
# 检查服务账号是否已存在
if gcloud iam service-accounts describe "service-account@$project_id.iam.gserviceaccount.com" --project="$project_id" &>/dev/null; then
log_warning "服务账号已存在,跳过创建"
return 0
fi
if retry_command "$max_retries" "$retry_delay" gcloud iam service-accounts create service-account \
--display-name="AI Platform Service Account" \
--project="$project_id"; then
log_success "服务账号创建成功"
return 0
else
log_error "创建服务账号失败 (已重试)"
return 1
fi
}
#
# 添加 IAM 策略
#
add_iam_policy() {
local project_id=$1
local max_retries=$2
local retry_delay=$3
log_info "授予 aiplatform.serviceAgent 角色"
if retry_command "$max_retries" "$retry_delay" gcloud projects add-iam-policy-binding "$project_id" \
--member="serviceAccount:service-account@$project_id.iam.gserviceaccount.com" \
--role="roles/aiplatform.serviceAgent"; then
log_success "IAM 策略授予成功"
return 0
else
log_error "授予 IAM 策略失败 (已重试)"
return 1
fi
}
#
# 创建服务账号密钥
#
create_service_account_key() {
local project_id=$1
local max_retries=$2
local retry_delay=$3
log_info "创建服务账号密钥"
# 检查密钥文件是否已存在
if [ -f "pass-$project_id.json" ]; then
log_warning "密钥文件 pass-$project_id.json 已存在,跳过创建"
return 0
fi
if retry_command "$max_retries" "$retry_delay" gcloud iam service-accounts keys create "pass-$project_id.json" \
--iam-account="service-account@$project_id.iam.gserviceaccount.com" \
--project="$project_id"; then
log_success "服务账号密钥创建成功: pass-$project_id.json"
return 0
else
log_error "创建服务账号密钥失败 (已重试)"
return 1
fi
}
# 参数解析和验证
PROJECT_ID_PREFIX="$DEFAULT_PROJECT_ID_PREFIX"
START_NUM="$DEFAULT_START_NUM"
REPEAT_NUM="$DEFAULT_REPEAT_NUM"
MAX_RETRIES="$DEFAULT_MAX_RETRIES"
RETRY_DELAY="$DEFAULT_RETRY_DELAY"
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case $1 in
-p|--prefix)
PROJECT_ID_PREFIX="$2"
shift 2
;;
-s|--start)
START_NUM="$2"
shift 2
;;
-n|--number)
REPEAT_NUM="$2"
shift 2
;;
--max-retries)
MAX_RETRIES="$2"
shift 2
;;
--retry-delay)
RETRY_DELAY="$2"
shift 2
;;
--debug)
DEBUG_MODE=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
log_error "未知选项: $1"
usage
exit 1
;;
esac
done
# 检查必要的命令
check_command gcloud
# 验证参数
if ! [[ "$START_NUM" =~ ^[0-9]+$ ]]; then
log_error "起始编号必须是一个正整数"
exit 1
fi
if ! [[ "$REPEAT_NUM" =~ ^[0-9]+$ ]] || [ "$REPEAT_NUM" -lt 1 ]; then
log_error "项目数量必须是一个正整数"
exit 1
fi
if ! [[ "$MAX_RETRIES" =~ ^[0-9]+$ ]] || [ "$MAX_RETRIES" -lt 0 ]; then
log_error "最大重试次数必须是一个非负整数"
exit 1
fi
if ! [[ "$RETRY_DELAY" =~ ^[0-9]+$ ]] || [ "$RETRY_DELAY" -lt 0 ]; then
log_error "重试延迟必须是一个非负整数"
exit 1
fi
log_info "################################################################################"
log_info "开始创建 GCP 项目批量任务"
log_info "################################################################################"
# 调试模式提示
if [ "$DEBUG_MODE" = true ]; then
log_debug "调试模式已启用"
fi
# 获取结算账号
log_info "获取结算账号..."
billing_id=""
if retry_command_with_output "$MAX_RETRIES" "$RETRY_DELAY" billing_id_output gcloud beta billing accounts list --format="value(name.basename())" --filter="open=true"; then
# 提取第一个结算账号ID
billing_id=$(echo "$billing_id_output" | head -n 1 | tr -d '[:space:]')
if [ -z "$billing_id" ]; then
log_error "未找到有效的活动结算账号。请检查您的结算账户。"
exit 1
fi
log_success "结算账号: $billing_id"
else
log_error "获取结算账号失败 (已重试)。请检查您的结算账户。"
exit 1
fi
# 计算结束编号
END_NUM=$((START_NUM + REPEAT_NUM - 1))
log_info "配置信息:"
log_info " 项目前缀: $PROJECT_ID_PREFIX"
log_info " 起始编号: $START_NUM"
log_info " 结束编号: $END_NUM"
log_info " 项目总数: $REPEAT_NUM"
log_info " 最大重试: $MAX_RETRIES"
log_info " 重试延迟: $RETRY_DELAY"
# 显示将要创建的项目列表
log_info "将创建以下项目:"
for i in $(seq "$START_NUM" "$END_NUM"); do
PROJECT_ID="${PROJECT_ID_PREFIX}$(printf "%02d" "$i")"
log_info " - $PROJECT_ID"
done
# 确认开始创建
log_warning "准备开始创建项目..."
sleep 1
# 统计变量
successful_projects=0
failed_projects=0
# 创建项目和相关资源 - 主循环
log_debug "开始主循环,从 $START_NUM$END_NUM"
for i in $(seq "$START_NUM" "$END_NUM"); do
PROJECT_ID="${PROJECT_ID_PREFIX}$(printf "%02d" "$i")"
current_num=$((i - START_NUM + 1))
log_info "-----------------------------------------------------"
log_info "开始处理项目 ($current_num/$REPEAT_NUM): $PROJECT_ID"
log_debug "当前循环变量 i=$i, 项目ID=$PROJECT_ID"
log_info "-----------------------------------------------------"
# 执行所有步骤
project_success=true
log_debug "步骤1: 创建项目"
if ! create_project "$PROJECT_ID" "$MAX_RETRIES" "$RETRY_DELAY"; then
log_debug "创建项目失败"
project_success=false
fi
if [ "$project_success" = true ]; then
log_debug "步骤2: 链接结算账号"
if ! link_billing "$PROJECT_ID" "$billing_id" "$MAX_RETRIES" "$RETRY_DELAY"; then
log_debug "链接结算账号失败"
project_success=false
fi
fi
if [ "$project_success" = true ]; then
log_debug "步骤3: 启用服务"
if ! enable_services "$PROJECT_ID" "$MAX_RETRIES" "$RETRY_DELAY"; then
log_debug "启用服务失败"
project_success=false
fi
fi
if [ "$project_success" = true ]; then
log_debug "步骤4: 创建服务账号"
if ! create_service_account "$PROJECT_ID" "$MAX_RETRIES" "$RETRY_DELAY"; then
log_debug "创建服务账号失败"
project_success=false
fi
fi
if [ "$project_success" = true ]; then
log_debug "步骤5: 添加IAM策略"
if ! add_iam_policy "$PROJECT_ID" "$MAX_RETRIES" "$RETRY_DELAY"; then
log_debug "添加IAM策略失败"
project_success=false
fi
fi
if [ "$project_success" = true ]; then
log_debug "步骤6: 创建服务账号密钥"
if ! create_service_account_key "$PROJECT_ID" "$MAX_RETRIES" "$RETRY_DELAY"; then
log_debug "创建服务账号密钥失败"
project_success=false
fi
fi
# 统计结果
if [ "$project_success" = true ]; then
log_success "项目 $PROJECT_ID 所有操作处理完成"
((successful_projects++))
else
log_warning "项目 $PROJECT_ID 处理过程中出现错误,部分操作可能未完成。"
((failed_projects++))
fi
# 添加延迟,避免 API 限制
log_debug "等待2秒后继续下一个项目..."
sleep 2
log_debug "完成项目 $PROJECT_ID 的处理,准备处理下一个项目"
done
log_debug "主循环结束"
# 显示结果
log_info "================== 执行完成 =================="
log_success "成功创建: $successful_projects 个项目"
log_error "创建失败: $failed_projects 个项目"
log_info "详细日志: $LOG_FILE"
echo "================== 创建完成于: $(date) ==================" >> "$LOG_FILE"
# 显示成功创建的项目列表
if [ "$successful_projects" -gt 0 ]; then
log_success "成功创建的项目:"
for i in $(seq "$START_NUM" "$END_NUM"); do
PROJECT_ID="${PROJECT_ID_PREFIX}$(printf "%02d" "$i")"
if [ -f "pass-$PROJECT_ID.json" ]; then
log_info "$PROJECT_ID (密钥文件: pass-$PROJECT_ID.json)"
fi
done
fi
if [ "$failed_projects" -gt 0 ]; then
log_error "$failed_projects 个项目在处理过程中遇到无法恢复的错误。"
log_info "请检查上面的日志以获取详细信息。"
exit 1
fi
log_success "所有项目创建/配置完成。"