mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
303 lines
10 KiB
303 lines
10 KiB
// Copyright 2015 The Gogs Authors. All rights reserved. |
|
// Use of this source code is governed by a MIT-style |
|
// license that can be found in the LICENSE file. |
|
|
|
package importer |
|
|
|
import ( |
|
"errors" |
|
"fmt" |
|
"log" |
|
"net/url" |
|
"sort" |
|
"time" |
|
|
|
"github.com/gogits/gogs/models" |
|
) |
|
|
|
func ImportGitLab(baseUrl *url.URL, token string) error { |
|
if err := importGitLabUsers(baseUrl, token); err != nil { |
|
return err |
|
} |
|
if err := importGitLabOrgs(baseUrl, token); err != nil { |
|
return err |
|
} |
|
if err := importGitLabRepos(baseUrl, token); err != nil { |
|
return err |
|
} |
|
return nil |
|
} |
|
|
|
// https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api |
|
// https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/api/users.md |
|
|
|
type GitLabUser struct { |
|
Id int64 `json:"id"` |
|
Name string `json:"username"` |
|
FullName string `json:"name"` |
|
Email string `json:"email"` |
|
Passwd string `json:"password_hash"` // this requires API patch (returns bcrypt hash) |
|
State string `json:"state"` // "active", "inactive" |
|
IsAdmin bool `json:"is_admin"` |
|
Website string `json:"website_url"` |
|
Created time.Time `json:"created_at"` |
|
} |
|
type GitLabUsers []GitLabUser |
|
|
|
func (slice GitLabUsers) Len() int { return len(slice) } |
|
func (slice GitLabUsers) Less(i, j int) bool { return slice[i].Id < slice[j].Id } |
|
func (slice GitLabUsers) Swap(i, j int) { slice[i], slice[j] = slice[j], slice[i] } |
|
|
|
type GitLabKey struct { |
|
Id int64 `json:"id"` |
|
Name string `json:"title"` |
|
Key string `json:"key"` |
|
Created time.Time `json:"created_at"` |
|
} |
|
type GitLabKeys []GitLabKey |
|
|
|
func (slice GitLabKeys) Len() int { return len(slice) } |
|
func (slice GitLabKeys) Less(i, j int) bool { return slice[i].Id < slice[j].Id } |
|
func (slice GitLabKeys) Swap(i, j int) { slice[i], slice[j] = slice[j], slice[i] } |
|
|
|
func importGitLabUsers(baseUrl *url.URL, token string) error { |
|
var remoteUsers GitLabUsers |
|
if err := fetchObjects("/api/v3/users", baseUrl, token, &remoteUsers); err != nil { |
|
return err |
|
} |
|
sort.Sort(remoteUsers) |
|
|
|
for _, remoteUser := range remoteUsers { |
|
if user, _ := models.GetUserByName(remoteUser.Name); user == nil { |
|
log.Printf("User: %s (%s)", remoteUser.Name, remoteUser.FullName) |
|
user := &models.User{ |
|
Name: remoteUser.Name, |
|
FullName: remoteUser.FullName, |
|
Email: remoteUser.Email, |
|
IsActive: remoteUser.State == "active", |
|
IsAdmin: remoteUser.IsAdmin, |
|
Website: remoteUser.Website, |
|
Created: remoteUser.Created, |
|
LoginType: models.PLAIN, |
|
} |
|
err := models.CreateUser(user) |
|
if err != nil { |
|
log.Fatalf("Cannot create user: %s", err) |
|
return err |
|
} |
|
// extra step needed to put bcrypt hash directly |
|
user.Passwd = remoteUser.Passwd |
|
models.UpdateUser(user) |
|
var remoteKeys GitLabKeys |
|
if err := fetchObjects(fmt.Sprintf("/api/v3/users/%d/keys", remoteUser.Id), baseUrl, token, &remoteKeys); err != nil { |
|
return err |
|
} else { |
|
sort.Sort(remoteKeys) |
|
for _, remoteKey := range remoteKeys { |
|
if err := models.AddPublicKeyCreated(user.Id, remoteKey.Name, remoteKey.Key, remoteKey.Created); err != nil { |
|
if models.IsErrKeyNameAlreadyUsed(err) { |
|
log.Printf("Duplicate key: %s", remoteKey.Name, remoteUser.Name, err) |
|
} else { |
|
log.Fatalf("Cannot add public key %s to user %s: %s", remoteKey.Name, remoteUser.Name, err) |
|
return err |
|
} |
|
} else { |
|
log.Printf(" Keys <- %s", remoteKey.Name) |
|
} |
|
} |
|
} |
|
} else { |
|
log.Printf("User %s already exists!", remoteUser.Name) |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
// https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/api/groups.md |
|
|
|
type GitLabOrg struct { |
|
Id int64 `json:"id"` |
|
Name string `json:"path"` |
|
FullName string `json:"name"` |
|
Email string `json:"email"` |
|
Description string `json:"description"` |
|
} |
|
type GitLabOrgs []GitLabOrg |
|
|
|
func (slice GitLabOrgs) Len() int { return len(slice) } |
|
func (slice GitLabOrgs) Less(i, j int) bool { return slice[i].Id < slice[j].Id } |
|
func (slice GitLabOrgs) Swap(i, j int) { slice[i], slice[j] = slice[j], slice[i] } |
|
|
|
type GitLabAccessLevel int |
|
|
|
const ( |
|
GitLabNoAccess GitLabAccessLevel = iota |
|
GitLabGuest = 10 |
|
GitLabReporter = 20 |
|
GitLabDeveloper = 30 |
|
GitLabMaster = 40 |
|
GitLabOwner = 50 |
|
) |
|
|
|
type GitLabMember struct { |
|
Id int64 `json:"id"` |
|
Name string `json:"username"` |
|
FullName string `json:"name"` |
|
Email string `json:"email"` |
|
AccessLevel GitLabAccessLevel `json:"access_level"` |
|
} |
|
type GitLabMembers []GitLabMember |
|
|
|
func (slice GitLabMembers) Len() int { return len(slice) } |
|
func (slice GitLabMembers) Less(i, j int) bool { return slice[i].AccessLevel > slice[j].AccessLevel } |
|
func (slice GitLabMembers) Swap(i, j int) { slice[i], slice[j] = slice[j], slice[i] } |
|
|
|
func importGitLabOrgs(baseUrl *url.URL, token string) error { |
|
var remoteOrgs GitLabOrgs |
|
if err := fetchObjects("/api/v3/groups", baseUrl, token, &remoteOrgs); err != nil { |
|
return err |
|
} |
|
sort.Sort(remoteOrgs) |
|
|
|
for _, remoteOrg := range remoteOrgs { |
|
if org, _ := models.GetOrgByName(remoteOrg.Name); org == nil { |
|
var remoteMembers GitLabMembers |
|
if err := fetchObjects(fmt.Sprintf("/api/v3/groups/%d/members", remoteOrg.Id), baseUrl, token, &remoteMembers); err != nil { |
|
return err |
|
} |
|
if len(remoteMembers) == 0 { |
|
log.Fatalf("No members in org: %s! Skipping", remoteOrg.Name) |
|
continue |
|
} |
|
if owner, _ := models.GetUserByName(remoteMembers[0].Name); owner == nil { |
|
log.Fatalf("Inexistent owner %s (%s) for org: %s!", remoteMembers[0].FullName, remoteMembers[0].Name, remoteOrg.Name) |
|
} else { |
|
log.Printf("Org: %s (%s)", remoteOrg.Name, remoteOrg.FullName) |
|
log.Printf(" %s (%s) -> Owners", owner.FullName, owner.Name) |
|
org := &models.User{ |
|
Name: remoteOrg.Name, |
|
FullName: remoteOrg.FullName, |
|
Created: owner.Created, // GitLab does not expose creation time, use owner's |
|
IsActive: true, |
|
Type: models.ORGANIZATION, |
|
} |
|
err := models.CreateOrganization(org, owner) |
|
if err != nil { |
|
log.Fatalf("Cannot create org: %s", err) |
|
return err |
|
} |
|
ownersTeam, err := org.GetOwnerTeam() |
|
if err != nil { |
|
log.Fatalf("Cannot get org owners team: %s", err) |
|
return err |
|
} |
|
var adminsTeam, writersTeam, readersTeam *models.Team |
|
for index, remoteMember := range remoteMembers { |
|
if index == 0 { |
|
continue |
|
} |
|
if member, _ := models.GetUserByName(remoteMember.Name); owner == nil { |
|
log.Fatalf("Inexistent member %s (%s) for org: %s!", remoteMember.FullName, remoteMember.Name, remoteOrg.Name) |
|
} else { |
|
var team *models.Team |
|
switch { |
|
case remoteMember.AccessLevel >= GitLabOwner: |
|
team = ownersTeam |
|
err = nil |
|
case remoteMember.AccessLevel >= GitLabMaster: |
|
team, err = getOrgTeam(org, &adminsTeam, "Admins", models.ACCESS_MODE_ADMIN) |
|
case remoteMember.AccessLevel >= GitLabDeveloper: |
|
team, err = getOrgTeam(org, &writersTeam, "Writers", models.ACCESS_MODE_WRITE) |
|
case remoteMember.AccessLevel >= GitLabGuest: |
|
team, err = getOrgTeam(org, &readersTeam, "Readers", models.ACCESS_MODE_READ) |
|
} |
|
if err != nil { |
|
return err |
|
} |
|
if err := team.AddMember(member.Id); err != nil { |
|
log.Fatalf("Cannot add member %s (%s) to org: %s, %s", member.FullName, member.Name, remoteOrg.Name, err) |
|
} |
|
log.Printf(" %s (%s) -> %s", member.FullName, member.Name, team.Name) |
|
} |
|
} |
|
} |
|
} else { |
|
log.Printf("Org %s already exists!", remoteOrg.Name) |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
// https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/api/projects.md |
|
|
|
type GitLabNamespace struct { |
|
Id int64 `json:"id"` |
|
Name string `json:"path"` |
|
FullName string `json:"name"` |
|
Description string `json:"description"` |
|
} |
|
type GitLabRepo struct { |
|
Id int64 `json:"id"` |
|
Name string `json:"path"` |
|
Description string `json:"description"` |
|
Created time.Time `json:"created_at"` |
|
IsPublic bool `json:"public"` |
|
VisibilityLevel int `json:"visibility_level"` // (0) private, (10) internal, (20) public |
|
Owner GitLabUser `json:"owner"` |
|
Namespace GitLabNamespace `json:"namespace"` |
|
DefaultBranch string |
|
} |
|
type GitLabRepos []GitLabRepo |
|
|
|
func importGitLabRepos(baseUrl *url.URL, token string) error { |
|
var remoteRepos GitLabRepos |
|
if err := fetchObjects("/api/v3/projects/all?order_by=id&sort=asc", baseUrl, token, &remoteRepos); err != nil { |
|
return err |
|
} |
|
|
|
for _, remoteRepo := range remoteRepos { |
|
if owner, _ := models.GetUserByName(remoteRepo.Namespace.Name); owner == nil { |
|
log.Fatalf("Cannot find owner: %s for repo: %s", remoteRepo.Namespace.Name, remoteRepo.Name) |
|
return errors.New("Cannot find repo owner") |
|
} else { |
|
if repo, _ := models.GetRepositoryByName(owner.Id, remoteRepo.Name); repo == nil { |
|
log.Printf("Repo: %s (%s)", remoteRepo.Name, remoteRepo.Namespace.Name) |
|
repo, err := models.CreateRepository(owner, models.CreateRepoOptions{ |
|
Name: remoteRepo.Name, |
|
Description: remoteRepo.Description, |
|
IsPrivate: remoteRepo.VisibilityLevel == 0, |
|
Created: remoteRepo.Created, |
|
IsMirror: false, |
|
AutoInit: true, |
|
Readme: "Default", |
|
}) |
|
if err != nil { |
|
log.Fatalf("Cannot create repo: %s", err) |
|
return err |
|
} |
|
// NOTE: Gogs only supports plain list of collaborators in project, while GitLab has fine grained access control |
|
var remoteMembers GitLabMembers |
|
if err := fetchObjects(fmt.Sprintf("/api/v3/projects/%d/members", remoteRepo.Id), baseUrl, token, &remoteMembers); err != nil { |
|
return err |
|
} |
|
for _, remoteMember := range remoteMembers { |
|
if member, _ := models.GetUserByName(remoteMember.Name); owner == nil { |
|
log.Fatalf("Inexistent member %s (%s) for repo: %s!", remoteMember.FullName, remoteMember.Name, remoteRepo.Name) |
|
} else { |
|
if member.Id == owner.Id { |
|
continue // skip if we got owner here |
|
} |
|
if err := repo.AddCollaborator(member); err != nil { |
|
log.Fatalf("Cannot add collaborator %s (%s) to repo: %s, %s", member.FullName, member.Name, remoteRepo.Name, err) |
|
return err |
|
} |
|
log.Printf(" %s (%s) -> Collaborators", member.FullName, member.Name) |
|
} |
|
} |
|
} else { |
|
log.Printf("Repo %s already exists!", remoteRepo.Name) |
|
} |
|
} |
|
} |
|
return nil |
|
}
|
|
|