mirror of
https://github.com/go-i2p/gitlab-to-gitea.git
synced 2025-07-16 18:03:16 -04:00
check in forkfix and johnconnor
This commit is contained in:
@ -4,7 +4,7 @@ Go-based tool for migrating GitLab repositories, users, groups, issues and relat
|
|||||||
|
|
||||||
More-or-less a port of [gitlab-to-gitea](https://git.autonomic.zone/kawaiipunk/gitlab-to-gitea) from python to Go because *fixing* python appears to be a thing I just can't get my mind around, but *rewriting* it? I'm actually OK at that.
|
More-or-less a port of [gitlab-to-gitea](https://git.autonomic.zone/kawaiipunk/gitlab-to-gitea) from python to Go because *fixing* python appears to be a thing I just can't get my mind around, but *rewriting* it? I'm actually OK at that.
|
||||||
|
|
||||||
Also includes: `cmd/forkfix`, for fixing fork relationships between migrated repositories by manipulating the gitea mysql database and `cmd/unmigrate` to delete everything from a gitea instance except for the admin users.
|
Also includes: `cmd/forkfix`, for fixing fork relationships between migrated repositories by manipulating the gitea mysql database, `cmd/unmigrate` to delete everything from a gitea instance except for the admin users, and `cmd/johnconnor` which is a super-dangerous script for eliminating spam accounts from gitlab instances.
|
||||||
|
|
||||||
## Core Functionality
|
## Core Functionality
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ Also includes: `cmd/forkfix`, for fixing fork relationships between migrated rep
|
|||||||
|
|
||||||
- Modular package structure instead of monolithic script
|
- Modular package structure instead of monolithic script
|
||||||
- Configuration via environment variables rather than hardcoded values
|
- Configuration via environment variables rather than hardcoded values
|
||||||
- Added utility tools (`forkfix` and `unmigrate`)
|
- Added utility tools (`forkfix`, `unmigrate`, `johnconnor`)
|
||||||
- Database connectivity for commit action imports
|
- Database connectivity for commit action imports
|
||||||
- Improved error handling with recovery mechanisms
|
- Improved error handling with recovery mechanisms
|
||||||
- Separation of API client code from migration logic
|
- Separation of API client code from migration logic
|
||||||
|
224
cmd/forkfix/main.go
Normal file
224
cmd/forkfix/main.go
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repository represents a Gitea repository record
|
||||||
|
type Repository struct {
|
||||||
|
ID int64
|
||||||
|
Name string
|
||||||
|
OwnerName string
|
||||||
|
IsFork bool
|
||||||
|
ForkID sql.NullInt64
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Define command line flags
|
||||||
|
dbPath := flag.String("db", "", "Path to gitea.db (required)")
|
||||||
|
forkID := flag.Int64("fork", 0, "ID of the repository to mark as fork (required)")
|
||||||
|
originalID := flag.Int64("original", 0, "ID of the original repository (required)")
|
||||||
|
unsetFork := flag.Bool("unset", false, "Unset fork relationship instead of setting it")
|
||||||
|
listRepos := flag.Bool("list", false, "List all repositories in the database")
|
||||||
|
backup := flag.Bool("backup", true, "Create a backup of the database before making changes")
|
||||||
|
help := flag.Bool("help", false, "Show help")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Display help if requested or if no arguments provided
|
||||||
|
if *help || flag.NFlag() == 0 {
|
||||||
|
printUsage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// List repositories if requested
|
||||||
|
if *listRepos {
|
||||||
|
if *dbPath == "" {
|
||||||
|
log.Fatal("Database path (-db) is required")
|
||||||
|
}
|
||||||
|
listRepositories(*dbPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required arguments
|
||||||
|
if *dbPath == "" {
|
||||||
|
log.Fatal("Database path (-db) is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !*unsetFork && (*forkID == 0 || *originalID == 0) {
|
||||||
|
log.Fatal("Both fork ID (-fork) and original repository ID (-original) are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *unsetFork && *forkID == 0 {
|
||||||
|
log.Fatal("Fork ID (-fork) is required to unset a fork relationship")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create backup if requested
|
||||||
|
if *backup {
|
||||||
|
backupFile := backupDatabase(*dbPath)
|
||||||
|
fmt.Printf("Created backup at: %s\n", backupFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open database connection
|
||||||
|
db, err := sql.Open("sqlite3", *dbPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Verify the database connection
|
||||||
|
if err := db.Ping(); err != nil {
|
||||||
|
log.Fatalf("Failed to connect to database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify repositories exist before making changes
|
||||||
|
if !*unsetFork {
|
||||||
|
verifyRepository(db, *forkID, "fork")
|
||||||
|
verifyRepository(db, *originalID, "original")
|
||||||
|
} else {
|
||||||
|
verifyRepository(db, *forkID, "fork")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the operation
|
||||||
|
if !*unsetFork {
|
||||||
|
// Set fork relationship
|
||||||
|
updateForkRelationship(db, *forkID, *originalID)
|
||||||
|
fmt.Printf("Repository ID %d is now marked as a fork of repository ID %d\n", *forkID, *originalID)
|
||||||
|
} else {
|
||||||
|
// Unset fork relationship
|
||||||
|
unsetForkRelationship(db, *forkID)
|
||||||
|
fmt.Printf("Repository ID %d is no longer marked as a fork\n", *forkID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printUsage displays the help information
|
||||||
|
func printUsage() {
|
||||||
|
fmt.Println("Gitea Repository Fork Relationship Fixer")
|
||||||
|
fmt.Println("\nThis tool helps set, update, or remove fork relationships between repositories in a Gitea instance using SQLite.")
|
||||||
|
fmt.Println("\nUsage:")
|
||||||
|
fmt.Println(" Set a fork relationship:")
|
||||||
|
fmt.Println(" gitea-fork-fixer -db /path/to/gitea.db -fork 123 -original 456")
|
||||||
|
fmt.Println("\n Remove a fork relationship:")
|
||||||
|
fmt.Println(" gitea-fork-fixer -db /path/to/gitea.db -fork 123 -unset")
|
||||||
|
fmt.Println("\n List all repositories:")
|
||||||
|
fmt.Println(" gitea-fork-fixer -db /path/to/gitea.db -list")
|
||||||
|
fmt.Println("\nOptions:")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
fmt.Println("\nNOTE: Always restart Gitea after making changes for them to take effect.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// backupDatabase creates a backup of the database file
|
||||||
|
func backupDatabase(dbPath string) string {
|
||||||
|
backupPath := dbPath + ".backup-" + fmt.Sprintf("%d", os.Getpid())
|
||||||
|
|
||||||
|
// Read the original database file
|
||||||
|
data, err := os.ReadFile(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to read database for backup: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to the backup file
|
||||||
|
err = os.WriteFile(backupPath, data, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create backup: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return backupPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyRepository checks if a repository exists in the database
|
||||||
|
func verifyRepository(db *sql.DB, repoID int64, repoType string) {
|
||||||
|
var count int
|
||||||
|
err := db.QueryRow("SELECT COUNT(*) FROM repository WHERE id = ?", repoID).Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to verify %s repository: %v", repoType, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
log.Fatalf("%s repository with ID %d does not exist", repoType, repoID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateForkRelationship sets a repository as a fork of another
|
||||||
|
func updateForkRelationship(db *sql.DB, forkID, originalID int64) {
|
||||||
|
tx, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to begin transaction: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec("UPDATE repository SET fork_id = ?, is_fork = 1 WHERE id = ?", originalID, forkID)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
log.Fatalf("Failed to update fork relationship: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
log.Fatalf("Failed to commit changes: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsetForkRelationship removes a fork relationship
|
||||||
|
func unsetForkRelationship(db *sql.DB, forkID int64) {
|
||||||
|
tx, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to begin transaction: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec("UPDATE repository SET fork_id = NULL, is_fork = 0 WHERE id = ?", forkID)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
log.Fatalf("Failed to unset fork relationship: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
log.Fatalf("Failed to commit changes: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// listRepositories displays all repositories in the database
|
||||||
|
func listRepositories(dbPath string) {
|
||||||
|
db, err := sql.Open("sqlite3", dbPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
rows, err := db.Query(`
|
||||||
|
SELECT r.id, r.name, u.name as owner_name, r.is_fork, r.fork_id
|
||||||
|
FROM repository r
|
||||||
|
JOIN user u ON r.owner_id = u.id
|
||||||
|
ORDER BY r.id
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to query repositories: %v", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
fmt.Println("ID\tOwner/Name\tIs Fork\tFork of ID")
|
||||||
|
fmt.Println("--\t----------\t-------\t----------")
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var repo Repository
|
||||||
|
if err := rows.Scan(&repo.ID, &repo.Name, &repo.OwnerName, &repo.IsFork, &repo.ForkID); err != nil {
|
||||||
|
log.Fatalf("Failed to scan row: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
forkID := "NULL"
|
||||||
|
if repo.ForkID.Valid {
|
||||||
|
forkID = fmt.Sprintf("%d", repo.ForkID.Int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%d\t%s/%s\t%t\t%s\n",
|
||||||
|
repo.ID, repo.OwnerName, repo.Name, repo.IsFork, forkID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
log.Fatalf("Error iterating through results: %v", err)
|
||||||
|
}
|
||||||
|
}
|
275
cmd/johnconner/main.go
Normal file
275
cmd/johnconner/main.go
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
// This is different, and worse, than everything else in this repo.
|
||||||
|
// It is a script to delete users and groups from a GitLab instance.
|
||||||
|
// It still contains I2P-specific behavior.
|
||||||
|
// It does not use the official gitlab API.
|
||||||
|
|
||||||
|
// I SERIOUSLY DON'T THINK THIS IS A GOOD IDEA.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
disclaimerMessage = `
|
||||||
|
# This is a gitlab anti-bot script for selfhosted instances.
|
||||||
|
# It was used on i2pgit.org after a bot attack.
|
||||||
|
# You **WILL** need to modify it for other instances.
|
||||||
|
# You **WILL** need to set up an API key with very dangerous perms to use it.
|
||||||
|
# It **WILL** break your shit in terrible, unfixable ways if you aren't very very careful.
|
||||||
|
# You **SHOULD NEVER RUN THIS SCRIPT WITHOUT READING IT TWICE FIRST**
|
||||||
|
# FOR REAL this is like a BIG RED BUTTON UNDER A BULLETPROOF GLASS CASE WITH TWO KEYS.
|
||||||
|
# IN CASE OF BOT ARMY consider NOT using this thing but maybe if you really have to, then ask for help, then use it.
|
||||||
|
# IT'S WEEDEATING WITH A FLAMETHROWER. IT'S CHAINSAW SURGERY. I CANNOT EMPHASIZE THAT ENOUGH.
|
||||||
|
# I HAVE NO DIRECT KNOWLEDGE OF IT KILLING ANY KITTENS BUT I CANNOT RULE IT OUT.
|
||||||
|
# That said, it did take care of the bot problem.
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
gitlabAPIURL = "https://gitlab.example.com/api/v4" // Modify to your GitLab instance
|
||||||
|
gitlabToken = "" // Set your API token here or via env var
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
LastActivityOn interface{} `json:"last_activity_on"` // Could be null
|
||||||
|
IsFollowed bool `json:"is_followed"`
|
||||||
|
CanCreateProject bool `json:"can_create_project"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Membership struct {
|
||||||
|
SourceType string `json:"source_type"`
|
||||||
|
SourceName string `json:"source_name"`
|
||||||
|
SourceID int `json:"source_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Check for token in env var
|
||||||
|
if envToken := os.Getenv("GITLAB_API_TOKEN"); envToken != "" {
|
||||||
|
gitlabToken = envToken
|
||||||
|
}
|
||||||
|
|
||||||
|
if gitlabToken == "" {
|
||||||
|
fmt.Println("ERROR: GITLAB_API_TOKEN not set. Please set it as an environment variable or in the code.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display disclaimer
|
||||||
|
fmt.Println(disclaimerMessage)
|
||||||
|
fmt.Println("Are you ABSOLUTELY SURE you want to continue? This can cause IRREVERSIBLE DAMAGE.")
|
||||||
|
fmt.Print("Type 'YES I UNDERSTAND THE CONSEQUENCES' to continue: ")
|
||||||
|
|
||||||
|
var confirmation string
|
||||||
|
fmt.Scanln(&confirmation)
|
||||||
|
if confirmation != "YES I UNDERSTAND THE CONSEQUENCES" {
|
||||||
|
fmt.Println("Aborted.")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
users, err := getAllUsers()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting users: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
fmt.Println("begin loop")
|
||||||
|
fmt.Printf("User ID: %d\n", user.ID)
|
||||||
|
|
||||||
|
if user.IsFollowed {
|
||||||
|
fmt.Println("User is followed, skipping")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.LastActivityOn == nil {
|
||||||
|
fmt.Printf("User %d has no activity, deleting\n", user.ID)
|
||||||
|
deleteUser(user.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.CanCreateProject {
|
||||||
|
fmt.Printf("User %d cannot create projects, deleting\n", user.ID)
|
||||||
|
deleteUser(user.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
memberships, err := getUserMemberships(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting memberships for user %d: %v\n", user.ID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(memberships) == 0 {
|
||||||
|
fmt.Printf("User %d has no memberships, deleting\n", user.ID)
|
||||||
|
deleteUser(user.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hasI2PProject := false
|
||||||
|
for _, membership := range memberships {
|
||||||
|
fmt.Printf("Project is: %s\n", membership.SourceType)
|
||||||
|
|
||||||
|
if membership.SourceType == "Project" {
|
||||||
|
fmt.Printf("Project name is: %s\n", membership.SourceName)
|
||||||
|
|
||||||
|
if isI2PRelated(membership.SourceName) {
|
||||||
|
fmt.Printf("%s contained an I2P related term\n", membership.SourceName)
|
||||||
|
hasI2PProject = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if membership.SourceType == "Namespace" {
|
||||||
|
fmt.Printf("Group name is: %s\n", membership.SourceName)
|
||||||
|
|
||||||
|
if !isI2PRelated(membership.SourceName) {
|
||||||
|
fmt.Printf("Deleting non-I2P related group %d\n", membership.SourceID)
|
||||||
|
deleteGroup(membership.SourceID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasI2PProject {
|
||||||
|
fmt.Printf("User %d has I2P related project.\n", user.ID)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("User %d has no I2P related projects, deleting\n", user.ID)
|
||||||
|
deleteUser(user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isI2PRelated(name string) bool {
|
||||||
|
return regexp.MustCompile(`(?i)i2p`).MatchString(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAllUsers() ([]User, error) {
|
||||||
|
var allUsers []User
|
||||||
|
page := 1
|
||||||
|
|
||||||
|
for {
|
||||||
|
url := fmt.Sprintf("%s/users?page=%d&per_page=100", gitlabAPIURL, page)
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("PRIVATE-TOKEN", gitlabToken)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("API returned status code %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var users []User
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&users); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(users) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
allUsers = append(allUsers, users...)
|
||||||
|
page++
|
||||||
|
}
|
||||||
|
|
||||||
|
return allUsers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserMemberships(userID int) ([]Membership, error) {
|
||||||
|
url := fmt.Sprintf("%s/users/%d/memberships", gitlabAPIURL, userID)
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("PRIVATE-TOKEN", gitlabToken)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("API returned status code %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var memberships []Membership
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&memberships); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return memberships, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteUser(userID int) {
|
||||||
|
url := fmt.Sprintf("%s/users/%d", gitlabAPIURL, userID)
|
||||||
|
req, err := http.NewRequest("DELETE", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error creating delete request for user %d: %v\n", userID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("PRIVATE-TOKEN", gitlabToken)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error deleting user %d: %v\n", userID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||||
|
fmt.Printf("Successfully deleted user %d\n", userID)
|
||||||
|
} else {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
fmt.Printf("Failed to delete user %d. Status: %d, Response: %s\n", userID, resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteGroup(groupID int) {
|
||||||
|
url := fmt.Sprintf("%s/groups/%d", gitlabAPIURL, groupID)
|
||||||
|
req, err := http.NewRequest("DELETE", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error creating delete request for group %d: %v\n", groupID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("PRIVATE-TOKEN", gitlabToken)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error deleting group %d: %v\n", groupID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||||
|
fmt.Printf("Successfully deleted group %d\n", groupID)
|
||||||
|
} else {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
fmt.Printf("Failed to delete group %d. Status: %d, Response: %s\n", groupID, resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user