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.

304 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
}