aboutsummaryrefslogtreecommitdiffstats
path: root/bridge/gitlab
diff options
context:
space:
mode:
Diffstat (limited to 'bridge/gitlab')
-rw-r--r--bridge/gitlab/config.go178
-rw-r--r--bridge/gitlab/config_test.go2
-rw-r--r--bridge/gitlab/export.go26
-rw-r--r--bridge/gitlab/export_test.go13
-rw-r--r--bridge/gitlab/gitlab.go6
-rw-r--r--bridge/gitlab/import.go13
-rw-r--r--bridge/gitlab/import_test.go8
7 files changed, 164 insertions, 82 deletions
diff --git a/bridge/gitlab/config.go b/bridge/gitlab/config.go
index 99c27836..fb593819 100644
--- a/bridge/gitlab/config.go
+++ b/bridge/gitlab/config.go
@@ -5,6 +5,7 @@ import (
"fmt"
"net/url"
"os"
+ "path"
"regexp"
"sort"
"strconv"
@@ -18,7 +19,7 @@ import (
"github.com/MichaelMure/git-bug/bridge/core"
"github.com/MichaelMure/git-bug/bridge/core/auth"
"github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/input"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/colors"
)
@@ -34,16 +35,22 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor
if params.Owner != "" {
fmt.Println("warning: --owner is ineffective for a gitlab bridge")
}
+ if params.Login != "" {
+ fmt.Println("warning: --login is ineffective for a gitlab bridge")
+ }
conf := make(core.Configuration)
var err error
+ var baseUrl string
- if (params.CredPrefix != "" || params.TokenRaw != "") && params.URL == "" {
- return nil, fmt.Errorf("you must provide a project URL to configure this bridge with a token")
- }
-
- if params.URL == "" {
- params.URL = defaultBaseURL
+ switch {
+ case params.BaseURL != "":
+ baseUrl = params.BaseURL
+ default:
+ baseUrl, err = promptBaseUrlOptions()
+ if err != nil {
+ return nil, errors.Wrap(err, "base url prompt")
+ }
}
var url string
@@ -54,7 +61,7 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor
url = params.URL
default:
// terminal prompt
- url, err = promptURL(repo)
+ url, err = promptURL(repo, baseUrl)
if err != nil {
return nil, errors.Wrap(err, "url prompt")
}
@@ -64,11 +71,6 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor
return nil, fmt.Errorf("base URL (%s) doesn't match the project URL (%s)", params.BaseURL, url)
}
- user, err := repo.GetUserIdentity()
- if err != nil {
- return nil, err
- }
-
var cred auth.Credential
switch {
@@ -77,13 +79,16 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor
if err != nil {
return nil, err
}
- if cred.UserId() != user.Id() {
- return nil, fmt.Errorf("selected credential don't match the user")
- }
case params.TokenRaw != "":
- cred = auth.NewToken(user.Id(), params.TokenRaw, target)
+ token := auth.NewToken(params.TokenRaw, target)
+ login, err := getLoginFromToken(baseUrl, token)
+ if err != nil {
+ return nil, err
+ }
+ token.SetMetadata(auth.MetaKeyLogin, login)
+ cred = token
default:
- cred, err = promptTokenOptions(repo, user.Id())
+ cred, err = promptTokenOptions(repo, baseUrl)
if err != nil {
return nil, err
}
@@ -95,14 +100,14 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor
}
// validate project url and get its ID
- id, err := validateProjectURL(params.BaseURL, url, token)
+ id, err := validateProjectURL(baseUrl, url, token)
if err != nil {
return nil, errors.Wrap(err, "project validation")
}
conf[core.ConfigKeyTarget] = target
conf[keyProjectID] = strconv.Itoa(id)
- conf[keyGitlabBaseUrl] = params.BaseURL
+ conf[keyGitlabBaseUrl] = baseUrl
err = g.ValidateConfig(conf)
if err != nil {
@@ -126,7 +131,9 @@ func (g *Gitlab) ValidateConfig(conf core.Configuration) error {
} else if v != target {
return fmt.Errorf("unexpected target name: %v", v)
}
-
+ if _, ok := conf[keyGitlabBaseUrl]; !ok {
+ return fmt.Errorf("missing %s key", keyGitlabBaseUrl)
+ }
if _, ok := conf[keyProjectID]; !ok {
return fmt.Errorf("missing %s key", keyProjectID)
}
@@ -134,20 +141,51 @@ func (g *Gitlab) ValidateConfig(conf core.Configuration) error {
return nil
}
-func promptTokenOptions(repo repository.RepoConfig, userId entity.Id) (auth.Credential, error) {
+func promptBaseUrlOptions() (string, error) {
+ index, err := input.PromptChoice("Gitlab base url", []string{
+ "https://gitlab.com",
+ "enter your own base url",
+ })
+
+ if err != nil {
+ return "", err
+ }
+
+ if index == 0 {
+ return defaultBaseURL, nil
+ } else {
+ return promptBaseUrl()
+ }
+}
+
+func promptBaseUrl() (string, error) {
+ validator := func(name string, value string) (string, error) {
+ u, err := url.Parse(value)
+ if err != nil {
+ return err.Error(), nil
+ }
+ if u.Scheme == "" {
+ return "missing scheme", nil
+ }
+ if u.Host == "" {
+ return "missing host", nil
+ }
+ return "", nil
+ }
+
+ return input.Prompt("Base url", "url", input.Required, validator)
+}
+
+func promptTokenOptions(repo repository.RepoConfig, baseUrl string) (auth.Credential, error) {
for {
- creds, err := auth.List(repo, auth.WithUserId(userId), auth.WithTarget(target), auth.WithKind(auth.KindToken))
+ creds, err := auth.List(repo, auth.WithTarget(target), auth.WithKind(auth.KindToken))
if err != nil {
return nil, err
}
// if we don't have existing token, fast-track to the token prompt
if len(creds) == 0 {
- value, err := promptToken()
- if err != nil {
- return nil, err
- }
- return auth.NewToken(userId, value, target), nil
+ return promptToken(baseUrl)
}
fmt.Println()
@@ -185,54 +223,57 @@ func promptTokenOptions(repo repository.RepoConfig, userId entity.Id) (auth.Cred
switch index {
case 1:
- value, err := promptToken()
- if err != nil {
- return nil, err
- }
- return auth.NewToken(userId, value, target), nil
+ return promptToken(baseUrl)
default:
return creds[index-2], nil
}
}
}
-func promptToken() (string, error) {
- fmt.Println("You can generate a new token by visiting https://gitlab.com/profile/personal_access_tokens.")
+func promptToken(baseUrl string) (*auth.Token, error) {
+ fmt.Printf("You can generate a new token by visiting %s.\n", path.Join(baseUrl, "profile/personal_access_tokens"))
fmt.Println("Choose 'Create personal access token' and set the necessary access scope for your repository.")
fmt.Println()
fmt.Println("'api' access scope: to be able to make api calls")
fmt.Println()
- re, err := regexp.Compile(`^[a-zA-Z0-9\-]{20}`)
+ re, err := regexp.Compile(`^[a-zA-Z0-9\-\_]{20}$`)
if err != nil {
panic("regexp compile:" + err.Error())
}
- for {
- fmt.Print("Enter token: ")
+ var login string
- line, err := bufio.NewReader(os.Stdin).ReadString('\n')
- if err != nil {
- return "", err
+ validator := func(name string, value string) (complaint string, err error) {
+ if !re.MatchString(value) {
+ return "token has incorrect format", nil
}
-
- token := strings.TrimSpace(line)
- if re.MatchString(token) {
- return token, nil
+ login, err = getLoginFromToken(baseUrl, auth.NewToken(value, target))
+ if err != nil {
+ return fmt.Sprintf("token is invalid: %v", err), nil
}
+ return "", nil
+ }
- fmt.Println("token format is invalid")
+ rawToken, err := input.Prompt("Enter token", "token", input.Required, validator)
+ if err != nil {
+ return nil, err
}
+
+ token := auth.NewToken(rawToken, target)
+ token.SetMetadata(auth.MetaKeyLogin, login)
+
+ return token, nil
}
-func promptURL(repo repository.RepoCommon) (string, error) {
+func promptURL(repo repository.RepoCommon, baseUrl string) (string, error) {
// remote suggestions
remotes, err := repo.GetRemotes()
if err != nil {
return "", errors.Wrap(err, "getting remotes")
}
- validRemotes := getValidGitlabRemoteURLs(remotes)
+ validRemotes := getValidGitlabRemoteURLs(baseUrl, remotes)
if len(validRemotes) > 0 {
for {
fmt.Println("\nDetected projects:")
@@ -286,7 +327,7 @@ func promptURL(repo repository.RepoCommon) (string, error) {
}
}
-func getProjectPath(projectUrl string) (string, error) {
+func getProjectPath(baseUrl, projectUrl string) (string, error) {
cleanUrl := strings.TrimSuffix(projectUrl, ".git")
cleanUrl = strings.Replace(cleanUrl, "git@", "https://", 1)
objectUrl, err := url.Parse(cleanUrl)
@@ -294,38 +335,63 @@ func getProjectPath(projectUrl string) (string, error) {
return "", ErrBadProjectURL
}
+ objectBaseUrl, err := url.Parse(baseUrl)
+ if err != nil {
+ return "", ErrBadProjectURL
+ }
+
+ if objectUrl.Hostname() != objectBaseUrl.Hostname() {
+ return "", fmt.Errorf("base url and project url hostnames doesn't match")
+ }
return objectUrl.Path[1:], nil
}
-func getValidGitlabRemoteURLs(remotes map[string]string) []string {
+func getValidGitlabRemoteURLs(baseUrl string, remotes map[string]string) []string {
urls := make([]string, 0, len(remotes))
for _, u := range remotes {
- path, err := getProjectPath(u)
+ path, err := getProjectPath(baseUrl, u)
if err != nil {
continue
}
- urls = append(urls, fmt.Sprintf("%s%s", "gitlab.com", path))
+ urls = append(urls, fmt.Sprintf("%s/%s", baseUrl, path))
}
return urls
}
-func validateProjectURL(baseURL, url string, token *auth.Token) (int, error) {
- projectPath, err := getProjectPath(url)
+func validateProjectURL(baseUrl, url string, token *auth.Token) (int, error) {
+ projectPath, err := getProjectPath(baseUrl, url)
if err != nil {
return 0, err
}
- client, err := buildClient(baseURL, token)
+ client, err := buildClient(baseUrl, token)
if err != nil {
return 0, err
}
project, _, err := client.Projects.GetProject(projectPath, &gitlab.GetProjectOptions{})
if err != nil {
- return 0, err
+ return 0, errors.Wrap(err, "wrong token scope ou non-existent project")
}
return project.ID, nil
}
+
+func getLoginFromToken(baseUrl string, token *auth.Token) (string, error) {
+ client, err := buildClient(baseUrl, token)
+ if err != nil {
+ return "", err
+ }
+
+ user, _, err := client.Users.CurrentUser()
+ if err != nil {
+ return "", err
+ }
+ if user.Username == "" {
+ return "", fmt.Errorf("gitlab say username is empty")
+ }
+
+ return user.Username, nil
+}
diff --git a/bridge/gitlab/config_test.go b/bridge/gitlab/config_test.go
index 87469796..43ed649a 100644
--- a/bridge/gitlab/config_test.go
+++ b/bridge/gitlab/config_test.go
@@ -82,7 +82,7 @@ func TestProjectPath(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- path, err := getProjectPath(tt.args.url)
+ path, err := getProjectPath(defaultBaseURL, tt.args.url)
assert.Equal(t, tt.want.path, path)
assert.Equal(t, tt.want.err, err)
})
diff --git a/bridge/gitlab/export.go b/bridge/gitlab/export.go
index d42ef1cd..c5323da4 100644
--- a/bridge/gitlab/export.go
+++ b/bridge/gitlab/export.go
@@ -3,6 +3,7 @@ package gitlab
import (
"context"
"fmt"
+ "os"
"strconv"
"time"
@@ -14,7 +15,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/repository"
+ "github.com/MichaelMure/git-bug/identity"
)
var (
@@ -54,20 +55,33 @@ func (ge *gitlabExporter) Init(repo *cache.RepoCache, conf core.Configuration) e
return nil
}
-func (ge *gitlabExporter) cacheAllClient(repo repository.RepoConfig) error {
+func (ge *gitlabExporter) cacheAllClient(repo *cache.RepoCache) error {
creds, err := auth.List(repo, auth.WithTarget(target), auth.WithKind(auth.KindToken))
if err != nil {
return err
}
for _, cred := range creds {
- if _, ok := ge.identityClient[cred.UserId()]; !ok {
+ login, ok := cred.GetMetadata(auth.MetaKeyLogin)
+ if !ok {
+ _, _ = fmt.Fprintf(os.Stderr, "credential %s is not tagged with a Gitlab login\n", cred.ID().Human())
+ continue
+ }
+
+ user, err := repo.ResolveIdentityImmutableMetadata(metaKeyGitlabLogin, login)
+ if err == identity.ErrIdentityNotExist {
+ continue
+ }
+ if err != nil {
+ return nil
+ }
+
+ if _, ok := ge.identityClient[user.Id()]; !ok {
client, err := buildClient(ge.conf[keyGitlabBaseUrl], creds[0].(*auth.Token))
if err != nil {
return err
}
-
- ge.identityClient[cred.UserId()] = client
+ ge.identityClient[user.Id()] = client
}
}
@@ -159,7 +173,7 @@ func (ge *gitlabExporter) exportBug(ctx context.Context, b *cache.BugCache, sinc
gitlabID, ok := snapshot.GetCreateMetadata(metaKeyGitlabId)
if ok {
gitlabBaseUrl, ok := snapshot.GetCreateMetadata(metaKeyGitlabBaseUrl)
- if ok && gitlabBaseUrl != ge.conf[gitlabBaseUrl] {
+ if ok && gitlabBaseUrl != ge.conf[keyGitlabBaseUrl] {
out <- core.NewExportNothing(b.Id(), "skipping issue imported from another Gitlab instance")
return
}
diff --git a/bridge/gitlab/export_test.go b/bridge/gitlab/export_test.go
index d16defd0..1d387655 100644
--- a/bridge/gitlab/export_test.go
+++ b/bridge/gitlab/export_test.go
@@ -149,8 +149,12 @@ func TestPushPull(t *testing.T) {
require.NoError(t, err)
// set author identity
+ login := "test-identity"
author, err := backend.NewIdentity("test identity", "test@test.org")
require.NoError(t, err)
+ author.SetMetadata(metaKeyGitlabLogin, login)
+ err = author.Commit()
+ require.NoError(t, err)
err = backend.SetUserIdentity(author)
require.NoError(t, err)
@@ -158,12 +162,13 @@ func TestPushPull(t *testing.T) {
defer backend.Close()
interrupt.RegisterCleaner(backend.Close)
- tests := testCases(t, backend)
-
- token := auth.NewToken(author.Id(), envToken, target)
+ token := auth.NewToken(envToken, target)
+ token.SetMetadata(auth.MetaKeyLogin, login)
err = auth.Store(repo, token)
require.NoError(t, err)
+ tests := testCases(t, backend)
+
// generate project name
projectName := generateRepoName()
@@ -260,7 +265,7 @@ func TestPushPull(t *testing.T) {
// verify bug have same number of original operations
require.Len(t, importedBug.Snapshot().Operations, tt.numOpImp)
- // verify bugs are taged with origin=gitlab
+ // verify bugs are tagged with origin=gitlab
issueOrigin, ok := importedBug.Snapshot().GetCreateMetadata(core.MetaKeyOrigin)
require.True(t, ok)
require.Equal(t, issueOrigin, target)
diff --git a/bridge/gitlab/gitlab.go b/bridge/gitlab/gitlab.go
index 9298dc8e..8512379c 100644
--- a/bridge/gitlab/gitlab.go
+++ b/bridge/gitlab/gitlab.go
@@ -26,12 +26,18 @@ const (
defaultTimeout = 60 * time.Second
)
+var _ core.BridgeImpl = &Gitlab{}
+
type Gitlab struct{}
func (*Gitlab) Target() string {
return target
}
+func (g *Gitlab) LoginMetaKey() string {
+ return metaKeyGitlabLogin
+}
+
func (*Gitlab) NewImporter() core.Importer {
return &gitlabImporter{}
}
diff --git a/bridge/gitlab/import.go b/bridge/gitlab/import.go
index 4fa37505..d699554b 100644
--- a/bridge/gitlab/import.go
+++ b/bridge/gitlab/import.go
@@ -33,17 +33,7 @@ type gitlabImporter struct {
func (gi *gitlabImporter) Init(repo *cache.RepoCache, conf core.Configuration) error {
gi.conf = conf
- opts := []auth.Option{
- auth.WithTarget(target),
- auth.WithKind(auth.KindToken),
- }
-
- user, err := repo.GetUserIdentity()
- if err == nil {
- opts = append(opts, auth.WithUserId(user.Id()))
- }
-
- creds, err := auth.List(repo, opts...)
+ creds, err := auth.List(repo, auth.WithTarget(target), auth.WithKind(auth.KindToken))
if err != nil {
return err
}
@@ -399,7 +389,6 @@ func (gi *gitlabImporter) ensurePerson(repo *cache.RepoCache, id int) (*cache.Id
i, err = repo.NewIdentityRaw(
user.Name,
user.PublicEmail,
- user.Username,
user.AvatarURL,
map[string]string{
// because Gitlab
diff --git a/bridge/gitlab/import_test.go b/bridge/gitlab/import_test.go
index 6e378b07..3c0caa55 100644
--- a/bridge/gitlab/import_test.go
+++ b/bridge/gitlab/import_test.go
@@ -21,6 +21,7 @@ import (
func TestImport(t *testing.T) {
author := identity.NewIdentity("Amine Hilaly", "hilalyamine@gmail.com")
+
tests := []struct {
name string
url string
@@ -94,10 +95,11 @@ func TestImport(t *testing.T) {
t.Skip("Env var GITLAB_PROJECT_ID missing")
}
- err = author.Commit(repo)
- require.NoError(t, err)
+ login := "test-identity"
+ author.SetMetadata(metaKeyGitlabLogin, login)
- token := auth.NewToken(author.Id(), envToken, target)
+ token := auth.NewToken(envToken, target)
+ token.SetMetadata(metaKeyGitlabLogin, login)
err = auth.Store(repo, token)
require.NoError(t, err)