Initial commit: Harbor batch management tools
Add scripts for Harbor user and project management: - register.py: Bulk user registration from Excel - delete_users.py: Complete user deletion with resource cleanup - delete_projects.py: Targeted deletion of student projects (stu01-stu49) - CLAUDE.md: Project documentation and API guidance - requirements.txt: Python dependencies 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
e3401ec380
10
.claude/settings.local.json
Normal file
10
.claude/settings.local.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(git init:*)",
|
||||||
|
"Bash(git remote add:*)",
|
||||||
|
"Bash(git add:*)"
|
||||||
|
],
|
||||||
|
"deny": []
|
||||||
|
}
|
||||||
|
}
|
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Virtual Environment
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Excel files (sensitive data)
|
||||||
|
*.xlsx
|
||||||
|
*.xls
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
110
CLAUDE.md
Normal file
110
CLAUDE.md
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is a Harbor container registry user management tool written in Python. It provides functionality to:
|
||||||
|
- Register/create Harbor users from Excel data (`register.py`)
|
||||||
|
- Delete Harbor users individually or in batches (`delete_users.py`, `delete_users_fixed.py`)
|
||||||
|
|
||||||
|
The application interacts with Harbor's REST API v2.0 at `https://harbor.seahi.me`.
|
||||||
|
|
||||||
|
## Setup and Environment
|
||||||
|
|
||||||
|
### Dependencies Installation
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Virtual Environment Setup
|
||||||
|
```bash
|
||||||
|
# Activate existing virtual environment
|
||||||
|
source venv/bin/activate # On Unix/macOS
|
||||||
|
# or
|
||||||
|
venv\Scripts\activate # On Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Architecture
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
1. **Input**: Excel file (`users.xlsx`) with user data (username, realname, password, email)
|
||||||
|
2. **Authentication**: HTTP Basic Auth with Harbor admin credentials
|
||||||
|
3. **API Operations**: REST API calls to Harbor's `/api/v2.0/users` endpoint
|
||||||
|
4. **Error Handling**: Comprehensive exception handling with Chinese language user feedback
|
||||||
|
|
||||||
|
### Key Components
|
||||||
|
|
||||||
|
- **Authentication**: `get_admin_credentials()` - Interactive credential collection with getpass
|
||||||
|
- **User Operations**:
|
||||||
|
- `create_harbor_user()` - User creation via POST (register.py:20)
|
||||||
|
- `delete_harbor_user()` - User deletion via DELETE (requires user ID lookup)
|
||||||
|
- `get_user_id_by_username()` - User ID resolution from username
|
||||||
|
- **Excel Processing**: `pd.read_excel()` with `skiprows=1` to handle header row
|
||||||
|
- **Connection Testing**: `test_api_connection()` validates API connectivity and auth (delete_users_fixed.py:20)
|
||||||
|
- **Interactive Modes**: `delete_users_fixed.py` provides menu-driven user interaction
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
- `register.py` - User registration from Excel
|
||||||
|
- `delete_users.py` - Basic user deletion tool
|
||||||
|
- `delete_users_fixed.py` - Enhanced deletion tool with better error handling and interactive modes
|
||||||
|
- `users.xlsx` - Excel data source (not in version control)
|
||||||
|
- `requirements.txt` - Python dependencies
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
### Environment Setup
|
||||||
|
```bash
|
||||||
|
# Activate virtual environment (required before running scripts)
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Scripts
|
||||||
|
```bash
|
||||||
|
# Register users from Excel file
|
||||||
|
python register.py
|
||||||
|
|
||||||
|
# Delete users (recommended enhanced version)
|
||||||
|
python delete_users_fixed.py
|
||||||
|
|
||||||
|
# Delete users (basic version)
|
||||||
|
python delete_users.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
# Check Python syntax
|
||||||
|
python -m py_compile register.py
|
||||||
|
python -m py_compile delete_users_fixed.py
|
||||||
|
|
||||||
|
# Run with verbose output for debugging
|
||||||
|
python -v register.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Configuration
|
||||||
|
|
||||||
|
- Harbor URL: `https://harbor.seahi.me`
|
||||||
|
- API Version: v2.0
|
||||||
|
- Authentication: HTTP Basic Auth
|
||||||
|
- SSL Verification: Disabled (`verify=False`)
|
||||||
|
- HTTPS Warnings: Suppressed via urllib3
|
||||||
|
|
||||||
|
## Excel File Format
|
||||||
|
|
||||||
|
Expected structure for `users.xlsx`:
|
||||||
|
- Row 1: Headers (skipped)
|
||||||
|
- Column 0: Username
|
||||||
|
- Column 1: Real name
|
||||||
|
- Column 2: Password
|
||||||
|
- Column 4: Email address
|
||||||
|
|
||||||
|
## Error Handling Patterns
|
||||||
|
|
||||||
|
- HTTP 409: User already exists (treated as success for registration)
|
||||||
|
- HTTP 401: Authentication failure
|
||||||
|
- HTTP 404: User not found
|
||||||
|
- HTTP 412: User has dependencies (cannot delete)
|
||||||
|
- Connection timeouts and network errors are handled gracefully
|
271
delete_projects.py
Normal file
271
delete_projects.py
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
import requests
|
||||||
|
import getpass
|
||||||
|
import re
|
||||||
|
from urllib3.exceptions import InsecureRequestWarning
|
||||||
|
from requests.auth import HTTPBasicAuth
|
||||||
|
|
||||||
|
# 禁用不安全HTTPS警告
|
||||||
|
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||||
|
|
||||||
|
HARBOR_URL = "https://harbor.seahi.me"
|
||||||
|
API_BASE = f"{HARBOR_URL}/api/v2.0"
|
||||||
|
|
||||||
|
def get_admin_credentials():
|
||||||
|
"""获取Harbor管理员凭据"""
|
||||||
|
print("请输入Harbor管理员账号信息:")
|
||||||
|
username = input("用户名: ")
|
||||||
|
password = getpass.getpass("密码: ")
|
||||||
|
return username, password
|
||||||
|
|
||||||
|
def test_api_connection(auth):
|
||||||
|
"""测试API连接和认证"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE}/projects", auth=auth, verify=False, timeout=10)
|
||||||
|
if response.status_code == 401:
|
||||||
|
print("认证失败:用户名或密码错误")
|
||||||
|
return False
|
||||||
|
elif response.status_code == 200:
|
||||||
|
print("API连接成功")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"API连接失败: {response.status_code} - {response.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"API连接出错: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_all_projects(auth):
|
||||||
|
"""获取所有项目列表(支持分页)"""
|
||||||
|
all_projects = []
|
||||||
|
page = 1
|
||||||
|
page_size = 100 # 每页获取100个项目
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
params = {
|
||||||
|
'page': page,
|
||||||
|
'page_size': page_size
|
||||||
|
}
|
||||||
|
response = requests.get(f"{API_BASE}/projects", params=params,
|
||||||
|
auth=auth, verify=False, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
projects = response.json()
|
||||||
|
if not projects: # 没有更多数据
|
||||||
|
break
|
||||||
|
all_projects.extend(projects)
|
||||||
|
print(f" 已获取第 {page} 页,共 {len(projects)} 个项目")
|
||||||
|
page += 1
|
||||||
|
else:
|
||||||
|
print(f"获取项目列表失败: {response.status_code} - {response.text}")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取项目列表出错: {str(e)}")
|
||||||
|
break
|
||||||
|
|
||||||
|
print(f"总共获取 {len(all_projects)} 个项目")
|
||||||
|
return all_projects
|
||||||
|
|
||||||
|
def get_student_projects(auth):
|
||||||
|
"""获取所有学生项目 (stu01~stu49)"""
|
||||||
|
all_projects = get_all_projects(auth)
|
||||||
|
student_projects = []
|
||||||
|
|
||||||
|
# 匹配 stu01 到 stu49 开头的项目名
|
||||||
|
pattern = re.compile(r'^stu(0[1-9]|[1-4][0-9]).*')
|
||||||
|
|
||||||
|
for project in all_projects:
|
||||||
|
project_name = project.get("name", "")
|
||||||
|
if pattern.match(project_name):
|
||||||
|
student_projects.append(project)
|
||||||
|
|
||||||
|
return student_projects
|
||||||
|
|
||||||
|
def get_project_repositories(auth, project_name):
|
||||||
|
"""获取项目下的所有仓库(支持分页)"""
|
||||||
|
all_repositories = []
|
||||||
|
page = 1
|
||||||
|
page_size = 100 # 每页获取100个仓库
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
params = {
|
||||||
|
'page': page,
|
||||||
|
'page_size': page_size
|
||||||
|
}
|
||||||
|
response = requests.get(f"{API_BASE}/projects/{project_name}/repositories",
|
||||||
|
params=params, auth=auth, verify=False, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
repositories = response.json()
|
||||||
|
if not repositories: # 没有更多数据
|
||||||
|
break
|
||||||
|
all_repositories.extend(repositories)
|
||||||
|
page += 1
|
||||||
|
else:
|
||||||
|
print(f" 获取项目 {project_name} 仓库列表失败: {response.status_code}")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f" 获取项目 {project_name} 仓库列表出错: {str(e)}")
|
||||||
|
break
|
||||||
|
|
||||||
|
return all_repositories
|
||||||
|
|
||||||
|
def delete_repository(auth, project_name, repo_name):
|
||||||
|
"""删除仓库"""
|
||||||
|
try:
|
||||||
|
# 仓库名格式为 "project_name/repository_name",需要提取仓库名部分
|
||||||
|
if '/' in repo_name:
|
||||||
|
actual_repo_name = repo_name.split('/', 1)[1] # 取第一个/后面的部分
|
||||||
|
else:
|
||||||
|
actual_repo_name = repo_name
|
||||||
|
|
||||||
|
response = requests.delete(f"{API_BASE}/projects/{project_name}/repositories/{actual_repo_name}",
|
||||||
|
auth=auth, verify=False, timeout=30)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f" ✓ 删除仓库: {repo_name}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f" ✗ 删除仓库失败 {repo_name}: {response.status_code}")
|
||||||
|
if response.text:
|
||||||
|
print(f" 错误详情: {response.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 删除仓库出错 {repo_name}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_project(auth, project_name):
|
||||||
|
"""删除项目"""
|
||||||
|
try:
|
||||||
|
response = requests.delete(f"{API_BASE}/projects/{project_name}",
|
||||||
|
auth=auth, verify=False, timeout=30)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f" ✓ 删除项目: {project_name}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f" ✗ 删除项目失败 {project_name}: {response.status_code}")
|
||||||
|
if response.text:
|
||||||
|
print(f" 错误详情: {response.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 删除项目出错 {project_name}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_student_projects_and_repositories(auth):
|
||||||
|
"""删除所有学生项目和仓库"""
|
||||||
|
print("正在查找学生项目 (stu01~stu49)...")
|
||||||
|
|
||||||
|
student_projects = get_student_projects(auth)
|
||||||
|
|
||||||
|
if not student_projects:
|
||||||
|
print("未找到任何学生项目")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"找到 {len(student_projects)} 个学生项目:")
|
||||||
|
for project in student_projects:
|
||||||
|
print(f" - {project.get('name')} (ID: {project.get('project_id')})")
|
||||||
|
|
||||||
|
# 确认删除
|
||||||
|
print(f"\n警告:即将删除 {len(student_projects)} 个项目及其所有仓库!")
|
||||||
|
confirm = input("确认要继续吗?(输入 'DELETE' 确认): ").strip()
|
||||||
|
if confirm != 'DELETE':
|
||||||
|
print("已取消操作")
|
||||||
|
return
|
||||||
|
|
||||||
|
success_count = 0
|
||||||
|
total_count = len(student_projects)
|
||||||
|
|
||||||
|
for i, project in enumerate(student_projects, 1):
|
||||||
|
project_name = project.get("name")
|
||||||
|
print(f"\n[{i}/{total_count}] 处理项目: {project_name}")
|
||||||
|
|
||||||
|
# 获取项目下的仓库
|
||||||
|
repositories = get_project_repositories(auth, project_name)
|
||||||
|
|
||||||
|
if repositories:
|
||||||
|
print(f" 发现 {len(repositories)} 个仓库,开始删除...")
|
||||||
|
repo_success = 0
|
||||||
|
for repo in repositories:
|
||||||
|
repo_name = repo.get("name")
|
||||||
|
if delete_repository(auth, project_name, repo_name):
|
||||||
|
repo_success += 1
|
||||||
|
print(f" 仓库删除完成: {repo_success}/{len(repositories)}")
|
||||||
|
else:
|
||||||
|
print(f" 项目 {project_name} 中没有仓库")
|
||||||
|
|
||||||
|
# 删除项目
|
||||||
|
if delete_project(auth, project_name):
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
print(f"\n" + "="*60)
|
||||||
|
print(f"删除完成!")
|
||||||
|
print(f"成功删除项目: {success_count}/{total_count}")
|
||||||
|
|
||||||
|
def list_student_projects(auth):
|
||||||
|
"""列出所有学生项目(预览模式)"""
|
||||||
|
print("正在查找学生项目 (stu01~stu49)...")
|
||||||
|
|
||||||
|
student_projects = get_student_projects(auth)
|
||||||
|
|
||||||
|
if not student_projects:
|
||||||
|
print("未找到任何学生项目")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n找到 {len(student_projects)} 个学生项目:")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
for project in student_projects:
|
||||||
|
project_name = project.get("name")
|
||||||
|
project_id = project.get("project_id")
|
||||||
|
owner_name = project.get("owner_name", "N/A")
|
||||||
|
creation_time = project.get("creation_time", "N/A")
|
||||||
|
|
||||||
|
print(f"项目: {project_name}")
|
||||||
|
print(f" ID: {project_id}")
|
||||||
|
print(f" 所有者: {owner_name}")
|
||||||
|
print(f" 创建时间: {creation_time}")
|
||||||
|
|
||||||
|
# 获取仓库信息
|
||||||
|
repositories = get_project_repositories(auth, project_name)
|
||||||
|
if repositories:
|
||||||
|
print(f" 仓库 ({len(repositories)}个):")
|
||||||
|
for repo in repositories:
|
||||||
|
print(f" - {repo.get('name')} (拉取次数: {repo.get('pull_count', 0)})")
|
||||||
|
else:
|
||||||
|
print(f" 仓库: 无")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Harbor 学生项目删除工具")
|
||||||
|
print("=" * 40)
|
||||||
|
print("此工具专门删除 stu01~stu49 开头的项目和仓库")
|
||||||
|
|
||||||
|
# 获取管理员凭据
|
||||||
|
admin_username, admin_password = get_admin_credentials()
|
||||||
|
auth = HTTPBasicAuth(admin_username, admin_password)
|
||||||
|
|
||||||
|
# 测试API连接
|
||||||
|
if not test_api_connection(auth):
|
||||||
|
return
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print("\n请选择操作:")
|
||||||
|
print("1. 预览学生项目(不删除)")
|
||||||
|
print("2. 删除所有学生项目和仓库")
|
||||||
|
print("3. 退出")
|
||||||
|
|
||||||
|
choice = input("请输入选项 (1-3): ").strip()
|
||||||
|
|
||||||
|
if choice == '1':
|
||||||
|
list_student_projects(auth)
|
||||||
|
elif choice == '2':
|
||||||
|
delete_student_projects_and_repositories(auth)
|
||||||
|
break
|
||||||
|
elif choice == '3':
|
||||||
|
print("退出程序")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("无效选项,请重试")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
258
delete_users.py
Normal file
258
delete_users.py
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import requests
|
||||||
|
import getpass
|
||||||
|
import sys
|
||||||
|
from urllib3.exceptions import InsecureRequestWarning
|
||||||
|
from requests.auth import HTTPBasicAuth
|
||||||
|
|
||||||
|
# 禁用不安全HTTPS警告
|
||||||
|
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||||
|
|
||||||
|
HARBOR_URL = "https://harbor.seahi.me"
|
||||||
|
API_BASE = f"{HARBOR_URL}/api/v2.0"
|
||||||
|
|
||||||
|
def get_admin_credentials():
|
||||||
|
"""获取Harbor管理员凭据"""
|
||||||
|
print("请输入Harbor管理员账号信息:")
|
||||||
|
username = input("用户名: ")
|
||||||
|
password = getpass.getpass("密码: ")
|
||||||
|
return username, password
|
||||||
|
|
||||||
|
def test_api_connection(auth):
|
||||||
|
"""测试API连接和认证"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE}/users", auth=auth, verify=False, timeout=10)
|
||||||
|
if response.status_code == 401:
|
||||||
|
print("认证失败:用户名或密码错误")
|
||||||
|
return False
|
||||||
|
elif response.status_code == 200:
|
||||||
|
print("API连接成功")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"API连接失败: {response.status_code} - {response.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"API连接出错: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_all_users(auth):
|
||||||
|
"""获取所有用户列表"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE}/users", auth=auth, verify=False, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"获取用户列表失败: {response.status_code} - {response.text}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取用户列表出错: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_user_by_username(auth, username):
|
||||||
|
"""根据用户名获取用户信息"""
|
||||||
|
users = get_all_users(auth)
|
||||||
|
if users is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
if user.get("username") == username:
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_user_projects(auth, user_id):
|
||||||
|
"""获取用户拥有的所有项目"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE}/projects", auth=auth, verify=False, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
projects = response.json()
|
||||||
|
# 过滤出属于该用户的项目
|
||||||
|
user_projects = [p for p in projects if p.get("owner_id") == user_id]
|
||||||
|
return user_projects
|
||||||
|
else:
|
||||||
|
print(f"获取项目列表失败: {response.status_code} - {response.text}")
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取项目列表出错: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_project_repositories(auth, project_name):
|
||||||
|
"""获取项目下的所有仓库"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE}/projects/{project_name}/repositories",
|
||||||
|
auth=auth, verify=False, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"获取项目 {project_name} 仓库列表失败: {response.status_code}")
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取项目 {project_name} 仓库列表出错: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def delete_repository(auth, project_name, repo_name):
|
||||||
|
"""删除仓库"""
|
||||||
|
try:
|
||||||
|
# 仓库名称可能包含项目名,需要提取仓库名部分
|
||||||
|
if '/' in repo_name:
|
||||||
|
repo_name = repo_name.split('/')[-1]
|
||||||
|
|
||||||
|
response = requests.delete(f"{API_BASE}/projects/{project_name}/repositories/{repo_name}",
|
||||||
|
auth=auth, verify=False, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f" ✓ 删除仓库: {project_name}/{repo_name}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f" ✗ 删除仓库失败 {project_name}/{repo_name}: {response.status_code}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 删除仓库出错 {project_name}/{repo_name}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_project(auth, project_name):
|
||||||
|
"""删除项目"""
|
||||||
|
try:
|
||||||
|
response = requests.delete(f"{API_BASE}/projects/{project_name}",
|
||||||
|
auth=auth, verify=False, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f" ✓ 删除项目: {project_name}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f" ✗ 删除项目失败 {project_name}: {response.status_code} - {response.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 删除项目出错 {project_name}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_user_resources(auth, user):
|
||||||
|
"""删除用户的所有资源(项目和仓库)"""
|
||||||
|
user_id = user.get("user_id")
|
||||||
|
username = user.get("username")
|
||||||
|
|
||||||
|
print(f"正在清理用户 {username} 的资源...")
|
||||||
|
|
||||||
|
# 获取用户拥有的项目
|
||||||
|
projects = get_user_projects(auth, user_id)
|
||||||
|
if not projects:
|
||||||
|
print(f" 用户 {username} 没有拥有的项目")
|
||||||
|
return True
|
||||||
|
|
||||||
|
print(f" 发现 {len(projects)} 个项目需要删除")
|
||||||
|
|
||||||
|
success = True
|
||||||
|
for project in projects:
|
||||||
|
project_name = project.get("name")
|
||||||
|
print(f" 处理项目: {project_name}")
|
||||||
|
|
||||||
|
# 获取项目下的仓库
|
||||||
|
repositories = get_project_repositories(auth, project_name)
|
||||||
|
|
||||||
|
# 删除所有仓库
|
||||||
|
for repo in repositories:
|
||||||
|
repo_name = repo.get("name")
|
||||||
|
if not delete_repository(auth, project_name, repo_name):
|
||||||
|
success = False
|
||||||
|
|
||||||
|
# 删除项目
|
||||||
|
if not delete_project(auth, project_name):
|
||||||
|
success = False
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
def delete_harbor_user(auth, username):
|
||||||
|
"""删除Harbor用户及其所有资源"""
|
||||||
|
# 获取用户信息
|
||||||
|
user = get_user_by_username(auth, username)
|
||||||
|
if user is None:
|
||||||
|
print(f"! 用户不存在: {username}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
user_id = user.get("user_id")
|
||||||
|
print(f"开始删除用户: {username} (ID: {user_id})")
|
||||||
|
|
||||||
|
# 1. 先删除用户的所有资源
|
||||||
|
if not delete_user_resources(auth, user):
|
||||||
|
print(f"! 清理用户 {username} 的资源时遇到问题,但继续尝试删除用户")
|
||||||
|
|
||||||
|
# 2. 删除用户
|
||||||
|
try:
|
||||||
|
response = requests.delete(f"{API_BASE}/users/{user_id}",
|
||||||
|
auth=auth, verify=False, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f"✓ 成功删除用户: {username}")
|
||||||
|
return True
|
||||||
|
elif response.status_code == 404:
|
||||||
|
print(f"! 用户不存在: {username}")
|
||||||
|
return False
|
||||||
|
elif response.status_code == 412:
|
||||||
|
print(f"! 无法删除用户 {username}:用户仍有关联资源")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f"✗ 删除用户失败 {username}: {response.status_code} - {response.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ 删除用户出错 {username}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_users_from_excel():
|
||||||
|
"""从Excel文件中读取用户并删除"""
|
||||||
|
try:
|
||||||
|
# 读取Excel文件,跳过第一行(标题行)
|
||||||
|
df = pd.read_excel('users.xlsx', skiprows=1)
|
||||||
|
|
||||||
|
# 获取管理员凭据
|
||||||
|
admin_username, admin_password = get_admin_credentials()
|
||||||
|
|
||||||
|
# 创建认证对象
|
||||||
|
auth = HTTPBasicAuth(admin_username, admin_password)
|
||||||
|
|
||||||
|
# 测试API连接
|
||||||
|
if not test_api_connection(auth):
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\n开始删除用户...")
|
||||||
|
success_count = 0
|
||||||
|
total_count = 0
|
||||||
|
failed_users = []
|
||||||
|
|
||||||
|
# 遍历Excel中的用户并删除
|
||||||
|
for _, row in df.iterrows():
|
||||||
|
username = str(row.iloc[0]).strip()
|
||||||
|
|
||||||
|
# 跳过空行
|
||||||
|
if username == 'nan' or not username:
|
||||||
|
continue
|
||||||
|
|
||||||
|
total_count += 1
|
||||||
|
print(f"\n[{total_count}] 处理用户: {username}")
|
||||||
|
|
||||||
|
if delete_harbor_user(auth, username):
|
||||||
|
success_count += 1
|
||||||
|
else:
|
||||||
|
failed_users.append(username)
|
||||||
|
|
||||||
|
print(f"\n" + "="*50)
|
||||||
|
print(f"删除完成!")
|
||||||
|
print(f"成功删除: {success_count}/{total_count} 个用户")
|
||||||
|
|
||||||
|
if failed_users:
|
||||||
|
print(f"删除失败的用户: {', '.join(failed_users)}")
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("错误:找不到 users.xlsx 文件,请确保文件在当前目录中")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"发生错误: {e}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Harbor 用户删除工具")
|
||||||
|
print("=" * 30)
|
||||||
|
print("注意:此工具会删除用户及其所有项目和仓库!")
|
||||||
|
|
||||||
|
confirm = input("\n确认要继续吗?(y/N): ").strip().lower()
|
||||||
|
if confirm != 'y':
|
||||||
|
print("已取消操作")
|
||||||
|
return
|
||||||
|
|
||||||
|
delete_users_from_excel()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
98
register.py
Normal file
98
register.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import getpass
|
||||||
|
import sys
|
||||||
|
from urllib3.exceptions import InsecureRequestWarning
|
||||||
|
from requests.auth import HTTPBasicAuth
|
||||||
|
|
||||||
|
# 禁用不安全HTTPS警告
|
||||||
|
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||||
|
|
||||||
|
HARBOR_URL = "https://harbor.seahi.me"
|
||||||
|
|
||||||
|
def get_admin_credentials():
|
||||||
|
print("请输入Harbor管理员账号信息:")
|
||||||
|
username = input("用户名: ")
|
||||||
|
password = getpass.getpass("密码: ")
|
||||||
|
return username, password
|
||||||
|
|
||||||
|
def create_harbor_user(auth, user_data):
|
||||||
|
"""创建Harbor用户"""
|
||||||
|
url = f"{HARBOR_URL}/api/v2.0/users"
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
"username": user_data["username"],
|
||||||
|
"email": user_data["email"],
|
||||||
|
"realname": user_data["realname"],
|
||||||
|
"password": user_data["password"],
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, json=payload, headers=headers, auth=auth, verify=False)
|
||||||
|
if response.status_code == 201:
|
||||||
|
print(f"✓ 成功创建用户: {user_data['username']}")
|
||||||
|
return True
|
||||||
|
elif response.status_code == 409:
|
||||||
|
print(f"! 用户已存在: {user_data['username']}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"✗ 创建用户失败 {user_data['username']}: {response.status_code} - {response.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ 创建用户出错 {user_data['username']}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def convert_excel_to_json():
|
||||||
|
try:
|
||||||
|
# 读取Excel文件,跳过第一行(标题行)
|
||||||
|
df = pd.read_excel('users.xlsx', skiprows=1)
|
||||||
|
|
||||||
|
# 获取管理员凭据
|
||||||
|
admin_username, admin_password = get_admin_credentials()
|
||||||
|
|
||||||
|
# 创建认证对象
|
||||||
|
auth = HTTPBasicAuth(admin_username, admin_password)
|
||||||
|
|
||||||
|
# 测试认证
|
||||||
|
test_response = requests.get(f"{HARBOR_URL}/api/v2.0/users", auth=auth, verify=False)
|
||||||
|
if test_response.status_code == 401:
|
||||||
|
print("认证失败:用户名或密码错误")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\n开始创建用户...")
|
||||||
|
success_count = 0
|
||||||
|
total_count = 0
|
||||||
|
|
||||||
|
# 将DataFrame转换为JSON格式并创建用户
|
||||||
|
for _, row in df.iterrows():
|
||||||
|
# 确保所有值都转换为字符串,并处理 NaN 值
|
||||||
|
username = str(row.iloc[0]).strip()
|
||||||
|
realname = str(row.iloc[1]).strip()
|
||||||
|
password = str(row.iloc[2]).strip()
|
||||||
|
email = str(row.iloc[4]).strip()
|
||||||
|
|
||||||
|
# 跳过空行
|
||||||
|
if username == 'nan' or realname == 'nan':
|
||||||
|
continue
|
||||||
|
|
||||||
|
user_dict = {
|
||||||
|
"username": username,
|
||||||
|
"realname": realname,
|
||||||
|
"password": password,
|
||||||
|
"email": email
|
||||||
|
}
|
||||||
|
|
||||||
|
total_count += 1
|
||||||
|
if create_harbor_user(auth, user_dict):
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
print(f"\n完成!成功创建 {success_count}/{total_count} 个用户")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"发生错误: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
convert_excel_to_json()
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pandas>=1.3.0
|
||||||
|
requests>=2.26.0
|
||||||
|
urllib3>=1.26.7
|
||||||
|
openpyxl>=3.0.9
|
Loading…
x
Reference in New Issue
Block a user