initial commit
This commit is contained in:
commit
d3a068fe2b
|
@ -0,0 +1,2 @@
|
|||
.notes
|
||||
app.yml
|
|
@ -0,0 +1,89 @@
|
|||
# gogstea
|
||||
|
||||
## migration from gogs to gitea with less pain
|
||||
|
||||
High level operation:
|
||||
|
||||
The main idea is to do a copy. The script will take the users from csv data from gogs, previously obtained, and will create gitea users interacting with gitea API
|
||||
|
||||
```go
|
||||
source := gogs.mysql --> select * from user; --> csv
|
||||
|
||||
dst:= gitea.postgres --> API /admin gitea --> go script read from csv and http post to gitea api
|
||||
copy(dst, source)
|
||||
```
|
||||
|
||||
### users migration
|
||||
|
||||
Export results of this query to .csv to migrate the users
|
||||
|
||||
```sql
|
||||
SELECT * FROM user
|
||||
WHERE email !='';
|
||||
```
|
||||
|
||||
**Execution:**
|
||||
|
||||
```sh
|
||||
CONFIG_FILE=./app.yml go run ./cmd/users/main.go
|
||||
```
|
||||
|
||||
### orgs migration
|
||||
|
||||
Export results of this query to .csv to migrate the users
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
u.name,
|
||||
ou.org_id,
|
||||
uo.name AS owner_name,
|
||||
uo.email AS owner_email
|
||||
FROM org_user ou
|
||||
INNER JOIN user u ON ou.org_id = u.id
|
||||
INNER JOIN user uo ON uo.id = ou.is_owner
|
||||
```
|
||||
|
||||
**Execution:**
|
||||
|
||||
```sh
|
||||
CONFIG_FILE=./app.yml go run ./cmd/org/main.go
|
||||
```
|
||||
|
||||
### repositories migration
|
||||
|
||||
You have to options: You can use the API to import them or like I did: manually import using the `unadopted` button from the UI.
|
||||
|
||||
**Execution:**
|
||||
|
||||
```sh
|
||||
CONFIG_FILE=./app.yml go run ./cmd/org/main.go
|
||||
```
|
||||
|
||||
### collaborators migration
|
||||
|
||||
Export results of this query to .csv to migrate the repositories
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
r.name AS repo_name,
|
||||
uo.name AS repo_owner,
|
||||
u.name AS collaborator_user,
|
||||
co.mode,
|
||||
CASE
|
||||
WHEN co.mode = 1 THEN 'read'
|
||||
WHEN co.mode = 2 THEN 'write'
|
||||
WHEN co.mode = 3 THEN 'admin'
|
||||
WHEN co.mode = 4 THEN 'owner'
|
||||
ELSE 'failed'
|
||||
END AS permission
|
||||
FROM collaboration co
|
||||
INNER JOIN repository r ON r.id = co.repo_id
|
||||
INNER JOIN user uo ON uo.id = r.owner_id
|
||||
INNER JOIN user u ON u.id = co.user_id;
|
||||
```
|
||||
|
||||
**Execution:**
|
||||
|
||||
```sh
|
||||
CONFIG_FILE=./app.yml go run ./cmd/collaborator/main.go
|
||||
```
|
|
@ -0,0 +1,12 @@
|
|||
gitea:
|
||||
url: "https://gitea.com/api/v1"
|
||||
api_key: "YOUR-API-KEY-HERE"
|
||||
users:
|
||||
default_password: "SOME-STRONG-PASSS"
|
||||
csv_path: "/home/user/gogstea/csv/user.csv"
|
||||
organizations:
|
||||
csv_path: "/home/user/gogstea/csv/organizations.csv"
|
||||
issues:
|
||||
csv_path: "/home/user/gogstea/csv/issues.csv"
|
||||
collaborators:
|
||||
csv_path: "/home/user/gogstea/csv/collaborators.csv"
|
|
@ -0,0 +1,160 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gitea.urkob.com/mcr-swiss/gogstea/kit/config"
|
||||
)
|
||||
|
||||
type user struct {
|
||||
ID int `json:"id"`
|
||||
Login string `json:"login"`
|
||||
LoginName string `json:"login_name"`
|
||||
SourceID int `json:"source_id"`
|
||||
FullName string `json:"full_name"`
|
||||
Email string `json:"email"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
Language string `json:"language"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
LastLogin time.Time `json:"last_login"`
|
||||
Created time.Time `json:"created"`
|
||||
Restricted bool `json:"restricted"`
|
||||
Active bool `json:"active"`
|
||||
ProhibitLogin bool `json:"prohibit_login"`
|
||||
Location string `json:"location"`
|
||||
Website string `json:"website"`
|
||||
Description string `json:"description"`
|
||||
Visibility string `json:"visibility"`
|
||||
FollowersCount int `json:"followers_count"`
|
||||
FollowingCount int `json:"following_count"`
|
||||
StarredReposCount int `json:"starred_repos_count"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(signalContext(context.Background()))
|
||||
defer cancel()
|
||||
|
||||
cfgFile := os.Getenv("CONFIG_FILE")
|
||||
if cfgFile == "" {
|
||||
// Get root path
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
cfgFile = path.Join(path.Dir(filename), "configs", "app.yml")
|
||||
}
|
||||
cfg, err := config.LoadConfig(cfgFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
outputFile, err := os.Create(wd + "/output.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer outputFile.Close()
|
||||
|
||||
cli := http.DefaultClient
|
||||
|
||||
parsedURL, err := url.Parse(fmt.Sprintf(cfg.Gitea.URL + "/admin/users"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsedURL.String(), http.NoBody)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// TODO: see how to pass the admin token if so
|
||||
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bts, _ := io.ReadAll(resp.Body)
|
||||
|
||||
panic(fmt.Errorf("%d | %s: %w", resp.StatusCode, string(bts), err))
|
||||
}
|
||||
|
||||
bts, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var users []user
|
||||
if err := json.Unmarshal(bts, &users); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if u.Username == "mcradmin" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Delete
|
||||
parsedURL, err := url.Parse(fmt.Sprintf("%s/admin/users/%s", cfg.Gitea.URL, u.Username))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, parsedURL.String(), http.NoBody)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bts, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if _, err := outputFile.WriteString(fmt.Sprintf("FAILED %d | %s | %d: %s\n", u.ID, u.Email, resp.StatusCode, string(bts))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := outputFile.WriteString(fmt.Sprintf("DELETED %d | %s\n", u.ID, u.Email)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func signalContext(ctx context.Context) context.Context {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-sigs
|
||||
signal.Stop(sigs)
|
||||
close(sigs)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return ctx
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"runtime"
|
||||
"slices"
|
||||
"syscall"
|
||||
|
||||
"gitea.urkob.com/mcr-swiss/gogstea/internal/domain"
|
||||
"gitea.urkob.com/mcr-swiss/gogstea/kit/config"
|
||||
)
|
||||
|
||||
var okStatuses = []int{
|
||||
http.StatusOK,
|
||||
http.StatusCreated,
|
||||
http.StatusNoContent,
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(signalContext(context.Background()))
|
||||
defer cancel()
|
||||
|
||||
cfgFile := os.Getenv("CONFIG_FILE")
|
||||
if cfgFile == "" {
|
||||
// Get root path
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
cfgFile = path.Join(path.Dir(filename), "configs", "app.yml")
|
||||
}
|
||||
cfg, err := config.LoadConfig(cfgFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f1, err := os.Open(cfg.Collaborators.CSVPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f1.Close()
|
||||
|
||||
r1 := csv.NewReader(f1)
|
||||
r1.Comma = ',' // Set the delimiter to comma
|
||||
r1.LazyQuotes = true
|
||||
r1.TrimLeadingSpace = false
|
||||
|
||||
outputFile, err := os.Create(wd + "/collab-output.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer outputFile.Close()
|
||||
|
||||
for {
|
||||
record, err := r1.Read()
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
panic(err)
|
||||
}
|
||||
break // Stop on EOF or other errors.
|
||||
}
|
||||
|
||||
cli := http.DefaultClient
|
||||
|
||||
parsedURL, err := url.Parse(fmt.Sprintf("%s/repos/%s/%s/collaborators/%s", cfg.Gitea.URL, record[1], record[0], record[2]))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// permission, err := strconv.Atoi(record[3])
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
collabReq := domain.CollaboratorCreateRequest{
|
||||
Permission: record[4],
|
||||
}
|
||||
|
||||
bts, err := json.Marshal(collabReq)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, parsedURL.String(), bytes.NewReader(bts))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !slices.Contains(okStatuses, resp.StatusCode) {
|
||||
bts, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if _, err := outputFile.WriteString(fmt.Sprintf("ERROR COLLAB | %d | %s | %s\n", resp.StatusCode, parsedURL.String(), string(bts))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
bts, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if _, err := outputFile.WriteString(fmt.Sprintf("OK COLLAB | %s \n", string(bts))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func signalContext(ctx context.Context) context.Context {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-sigs
|
||||
signal.Stop(sigs)
|
||||
close(sigs)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return ctx
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"gitea.urkob.com/mcr-swiss/gogstea/internal/domain"
|
||||
"gitea.urkob.com/mcr-swiss/gogstea/kit/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(signalContext(context.Background()))
|
||||
defer cancel()
|
||||
|
||||
cfgFile := os.Getenv("CONFIG_FILE")
|
||||
if cfgFile == "" {
|
||||
// Get root path
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
cfgFile = path.Join(path.Dir(filename), "configs", "app.yml")
|
||||
}
|
||||
cfg, err := config.LoadConfig(cfgFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f1, err := os.Open(cfg.Issues.CSVPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f1.Close()
|
||||
|
||||
r1 := csv.NewReader(f1)
|
||||
r1.Comma = ',' // Set the delimiter to comma
|
||||
r1.LazyQuotes = true
|
||||
r1.TrimLeadingSpace = false
|
||||
|
||||
outputFile, err := os.Create(wd + "/comments-output.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer outputFile.Close()
|
||||
|
||||
for {
|
||||
record, err := r1.Read()
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
panic(err)
|
||||
}
|
||||
break // Stop on EOF or other errors.
|
||||
}
|
||||
|
||||
cli := http.DefaultClient
|
||||
|
||||
parsedURL, err := url.Parse(fmt.Sprintf("%s/repos/{owner}/{repo}/issues", cfg.Gitea.URL, record[1], record[0]))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
issue := domain.CommentCreateRequest{
|
||||
Body: "",
|
||||
}
|
||||
|
||||
bts, err := json.Marshal(issue)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, parsedURL.String(), bytes.NewReader(bts))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||
bts, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if _, err := outputFile.WriteString(fmt.Sprintf("ERROR ISSUE | %d | %s\n", resp.StatusCode, string(bts))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
bts, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if _, err := outputFile.WriteString(fmt.Sprintf("OK ISSUE | %s \n", string(bts))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func signalContext(ctx context.Context) context.Context {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-sigs
|
||||
signal.Stop(sigs)
|
||||
close(sigs)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return ctx
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gitea.urkob.com/mcr-swiss/gogstea/internal/domain"
|
||||
"gitea.urkob.com/mcr-swiss/gogstea/kit/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(signalContext(context.Background()))
|
||||
defer cancel()
|
||||
|
||||
cfgFile := os.Getenv("CONFIG_FILE")
|
||||
if cfgFile == "" {
|
||||
// Get root path
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
cfgFile = path.Join(path.Dir(filename), "configs", "app.yml")
|
||||
}
|
||||
cfg, err := config.LoadConfig(cfgFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f1, err := os.Open(cfg.Issues.CSVPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f1.Close()
|
||||
|
||||
r1 := csv.NewReader(f1)
|
||||
r1.Comma = ',' // Set the delimiter to comma
|
||||
r1.LazyQuotes = true
|
||||
r1.TrimLeadingSpace = false
|
||||
|
||||
outputFile, err := os.Create(wd + "/issues-output.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer outputFile.Close()
|
||||
|
||||
for {
|
||||
record, err := r1.Read()
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
panic(err)
|
||||
}
|
||||
break // Stop on EOF or other errors.
|
||||
}
|
||||
|
||||
cli := http.DefaultClient
|
||||
|
||||
parsedURL, err := url.Parse(fmt.Sprintf("%s/repos/%s/%s/issues", cfg.Gitea.URL, record[1], record[0]))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
isClosedInt, err := strconv.Atoi(record[7])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var isClosed bool
|
||||
switch isClosedInt {
|
||||
case 0:
|
||||
isClosed = false
|
||||
case 1:
|
||||
isClosed = false
|
||||
default:
|
||||
panic(fmt.Errorf("isclosed is %d", isClosedInt))
|
||||
}
|
||||
|
||||
issue := domain.IssueCreateRequest{
|
||||
Assignee: record[2],
|
||||
Assignees: nil,
|
||||
Body: record[4],
|
||||
Closed: isClosed,
|
||||
DueDate: time.Time{},
|
||||
Labels: []int{},
|
||||
Milestone: 0,
|
||||
Ref: "",
|
||||
Title: record[3],
|
||||
}
|
||||
|
||||
bts, err := json.Marshal(issue)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, parsedURL.String(), bytes.NewReader(bts))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||
bts, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if _, err := outputFile.WriteString(fmt.Sprintf("ERROR ISSUE | %d | %s | %s\n", resp.StatusCode, parsedURL.String(), string(bts))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
bts, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if _, err := outputFile.WriteString(fmt.Sprintf("OK ISSUE | %s \n", string(bts))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func signalContext(ctx context.Context) context.Context {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-sigs
|
||||
signal.Stop(sigs)
|
||||
close(sigs)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return ctx
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"gitea.urkob.com/mcr-swiss/gogstea/internal/domain"
|
||||
"gitea.urkob.com/mcr-swiss/gogstea/kit/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(signalContext(context.Background()))
|
||||
defer cancel()
|
||||
|
||||
cfgFile := os.Getenv("CONFIG_FILE")
|
||||
if cfgFile == "" {
|
||||
// Get root path
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
cfgFile = path.Join(path.Dir(filename), "configs", "app.yml")
|
||||
}
|
||||
cfg, err := config.LoadConfig(cfgFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f1, err := os.Open(cfg.Organizations.CSVPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f1.Close()
|
||||
|
||||
r1 := csv.NewReader(f1)
|
||||
r1.Comma = ',' // Set the delimiter to comma
|
||||
r1.LazyQuotes = true
|
||||
r1.TrimLeadingSpace = false
|
||||
|
||||
outputFile, err := os.Create(wd + "/org-output.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer outputFile.Close()
|
||||
|
||||
for {
|
||||
record, err := r1.Read()
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
panic(err)
|
||||
}
|
||||
break // Stop on EOF or other errors.
|
||||
}
|
||||
|
||||
cli := http.DefaultClient
|
||||
|
||||
parsedURL, err := url.Parse(fmt.Sprintf("%s/admin/users/%s/orgs", cfg.Gitea.URL, record[2]))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bodyReq := domain.OrgCreateRequest{
|
||||
Description: record[0],
|
||||
Email: "",
|
||||
FullName: "",
|
||||
Location: "",
|
||||
RepoAdminChangeTeamAccess: true,
|
||||
Username: record[0],
|
||||
Visibility: "private",
|
||||
Website: "",
|
||||
}
|
||||
|
||||
bts, err := json.Marshal(bodyReq)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, parsedURL.String(), bytes.NewReader(bts))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bts, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if _, err := outputFile.WriteString(fmt.Sprintf("%s | %d: %s\n", record[3], resp.StatusCode, string(bts))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
bts, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if _, err := outputFile.WriteString(fmt.Sprintf("OK REPO %s | %s | OK\n", bodyReq.Username, string(bts))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func signalContext(ctx context.Context) context.Context {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-sigs
|
||||
signal.Stop(sigs)
|
||||
close(sigs)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return ctx
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"gitea.urkob.com/mcr-swiss/gogstea/internal/domain"
|
||||
"gitea.urkob.com/mcr-swiss/gogstea/kit/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(signalContext(context.Background()))
|
||||
defer cancel()
|
||||
|
||||
cfgFile := os.Getenv("CONFIG_FILE")
|
||||
if cfgFile == "" {
|
||||
// Get root path
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
cfgFile = path.Join(path.Dir(filename), "configs", "app.yml")
|
||||
}
|
||||
cfg, err := config.LoadConfig(cfgFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP 1 MIGRATE USERS
|
||||
{
|
||||
f1, err := os.Open(cfg.Users.CSVPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f1.Close()
|
||||
|
||||
r1 := csv.NewReader(f1)
|
||||
r1.Comma = ',' // Set the delimiter to comma
|
||||
r1.LazyQuotes = true
|
||||
r1.TrimLeadingSpace = false
|
||||
|
||||
outputFile, err := os.Create(wd + "/output.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer outputFile.Close()
|
||||
|
||||
for {
|
||||
record, err := r1.Read()
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
panic(err)
|
||||
}
|
||||
break // Stop on EOF or other errors.
|
||||
}
|
||||
|
||||
cli := http.DefaultClient
|
||||
|
||||
parsedURL, err := url.Parse(fmt.Sprintf(cfg.Gitea.URL + "/admin/users"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
u := domain.UserCreateRequest{
|
||||
Email: record[4],
|
||||
FullName: record[2],
|
||||
LoginName: record[1],
|
||||
MustChangePassword: true,
|
||||
Password: cfg.Users.DefaultPassword,
|
||||
Restricted: false,
|
||||
SendNotify: false,
|
||||
SourceID: 0,
|
||||
Username: record[1],
|
||||
}
|
||||
|
||||
bts, err := json.Marshal(u)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, parsedURL.String(), bytes.NewReader(bts))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bts, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if _, err := outputFile.WriteString(fmt.Sprintf("%s | %d: %s\n", u.Email, resp.StatusCode, string(bts))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
bts, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var uResp domain.UserGet
|
||||
if err := json.Unmarshal(bts, &uResp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if _, err := outputFile.WriteString(fmt.Sprintf("USER %d | %s | OK\n", uResp.ID, uResp.Email)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func signalContext(ctx context.Context) context.Context {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-sigs
|
||||
signal.Stop(sigs)
|
||||
close(sigs)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return ctx
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
module gitea.urkob.com/mcr-swiss/gogstea
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
go.uber.org/zap v1.27.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require go.uber.org/multierr v1.10.0 // indirect
|
|
@ -0,0 +1,16 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,5 @@
|
|||
package domain
|
||||
|
||||
type CollaboratorCreateRequest struct {
|
||||
Permission string `json:"permission"`
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package domain
|
||||
|
||||
type CommentCreateRequest struct {
|
||||
Body string `json:"body"`
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
type IssueCreateRequest struct {
|
||||
Assignee string `json:"assignee"`
|
||||
Assignees []string `json:"assignees"`
|
||||
Body string `json:"body"`
|
||||
Closed bool `json:"closed"`
|
||||
DueDate time.Time `json:"due_date"`
|
||||
Labels []int `json:"labels"`
|
||||
Milestone int `json:"milestone"`
|
||||
Ref string `json:"ref"`
|
||||
Title string `json:"title"`
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package domain
|
||||
|
||||
type OrgCreateRequest struct {
|
||||
Description string `json:"description"`
|
||||
Email string `json:"email"`
|
||||
FullName string `json:"full_name"`
|
||||
Location string `json:"location"`
|
||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
|
||||
Username string `json:"username"`
|
||||
Visibility string `json:"visibility"`
|
||||
Website string `json:"website"`
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
type UserCreateRequest struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Email string `json:"email"`
|
||||
FullName string `json:"full_name"`
|
||||
LoginName string `json:"login_name"`
|
||||
MustChangePassword bool `json:"must_change_password"`
|
||||
Password string `json:"password"`
|
||||
Restricted bool `json:"restricted"`
|
||||
SendNotify bool `json:"send_notify"`
|
||||
SourceID int `json:"source_id"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type UserGet struct {
|
||||
ID int `json:"id"`
|
||||
Login string `json:"login"`
|
||||
LoginName string `json:"login_name"`
|
||||
SourceID int `json:"source_id"`
|
||||
FullName string `json:"full_name"`
|
||||
Email string `json:"email"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
Language string `json:"language"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
LastLogin time.Time `json:"last_login"`
|
||||
Created time.Time `json:"created"`
|
||||
Restricted bool `json:"restricted"`
|
||||
Active bool `json:"active"`
|
||||
ProhibitLogin bool `json:"prohibit_login"`
|
||||
Location string `json:"location"`
|
||||
Website string `json:"website"`
|
||||
Description string `json:"description"`
|
||||
Visibility string `json:"visibility"`
|
||||
FollowersCount int `json:"followers_count"`
|
||||
FollowingCount int `json:"following_count"`
|
||||
StarredReposCount int `json:"starred_repos_count"`
|
||||
Username string `json:"username"`
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Gitea struct {
|
||||
URL string `yaml:"url"`
|
||||
ApiKey string `yaml:"api_key"`
|
||||
} `yaml:"gitea"`
|
||||
Users struct {
|
||||
DefaultPassword string `yaml:"default_password"`
|
||||
CSVPath string `yaml:"csv_path"`
|
||||
} `yaml:"users"`
|
||||
Organizations struct {
|
||||
CSVPath string `yaml:"csv_path"`
|
||||
} `yaml:"organizations"`
|
||||
Issues struct {
|
||||
CSVPath string `yaml:"csv_path"`
|
||||
} `yaml:"issues"`
|
||||
Collaborators struct {
|
||||
CSVPath string `yaml:"csv_path"`
|
||||
} `yaml:"collaborators"`
|
||||
}
|
||||
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := yaml.Unmarshal(data, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func NewZapCfg(dev bool) zap.Config {
|
||||
prodConfig := zap.NewProductionConfig()
|
||||
if dev {
|
||||
prodConfig = zap.NewDevelopmentConfig()
|
||||
}
|
||||
return prodConfig
|
||||
}
|
Loading…
Reference in New Issue