bridge: record the login used during the configure and use it as default credential

Michael Muré created

fix #338

Change summary

bridge/github/config.go      |  6 +++-
bridge/github/export.go      | 47 ++++++++++++++++---------------------
bridge/github/export_test.go | 10 ++++---
bridge/github/github.go      |  5 ++-
bridge/github/import.go      | 10 +++++--
bridge/github/import_test.go |  5 ++-
bridge/gitlab/config.go      |  8 ++++-
bridge/gitlab/export_test.go |  2 +
bridge/gitlab/gitlab.go      |  1 
bridge/gitlab/import.go      |  3 +
bridge/gitlab/import_test.go |  1 
bridge/jira/config.go        | 13 ++++++++-
bridge/jira/import.go        |  6 +++-
bridge/jira/jira.go          |  1 
bridge/launchpad/config.go   |  1 
input/prompt.go              |  5 ++++
16 files changed, 76 insertions(+), 48 deletions(-)

Detailed changes

bridge/github/config.go 🔗

@@ -126,6 +126,7 @@ func (g *Github) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor
 	conf[core.ConfigKeyTarget] = target
 	conf[confKeyOwner] = owner
 	conf[confKeyProject] = project
+	conf[confKeyDefaultLogin] = login
 
 	err = g.ValidateConfig(conf)
 	if err != nil {
@@ -149,14 +150,15 @@ func (*Github) ValidateConfig(conf core.Configuration) error {
 	} else if v != target {
 		return fmt.Errorf("unexpected target name: %v", v)
 	}
-
 	if _, ok := conf[confKeyOwner]; !ok {
 		return fmt.Errorf("missing %s key", confKeyOwner)
 	}
-
 	if _, ok := conf[confKeyProject]; !ok {
 		return fmt.Errorf("missing %s key", confKeyProject)
 	}
+	if _, ok := conf[confKeyDefaultLogin]; !ok {
+		return fmt.Errorf("missing %s key", confKeyDefaultLogin)
+	}
 
 	return nil
 }

bridge/github/export.go 🔗

@@ -35,10 +35,13 @@ type githubExporter struct {
 	identityClient map[entity.Id]*githubv4.Client
 
 	// the client to use for non user-specific queries
-	// should be the client of the default user
+	// it's the client associated to the "default-login" config
+	// used for the github V4 API (graphql)
 	defaultClient *githubv4.Client
 
 	// the token of the default user
+	// it's the token associated to the "default-login" config
+	// used for the github V3 API (REST)
 	defaultToken *auth.Token
 
 	// github repository ID
@@ -59,34 +62,12 @@ func (ge *githubExporter) Init(_ context.Context, repo *cache.RepoCache, conf co
 	ge.cachedOperationIDs = make(map[entity.Id]string)
 	ge.cachedLabels = make(map[string]string)
 
-	user, err := repo.GetUserIdentity()
-	if err != nil {
-		return err
-	}
-
 	// preload all clients
-	err = ge.cacheAllClient(repo)
+	err := ge.cacheAllClient(repo)
 	if err != nil {
 		return err
 	}
 
-	ge.defaultClient, err = ge.getClientForIdentity(user.Id())
-	if err != nil {
-		return err
-	}
-
-	login := user.ImmutableMetadata()[metaKeyGithubLogin]
-	creds, err := auth.List(repo, auth.WithMeta(auth.MetaKeyLogin, login), auth.WithTarget(target), auth.WithKind(auth.KindToken))
-	if err != nil {
-		return err
-	}
-
-	if len(creds) == 0 {
-		return ErrMissingIdentityToken
-	}
-
-	ge.defaultToken = creds[0].(*auth.Token)
-
 	return nil
 }
 
@@ -111,12 +92,24 @@ func (ge *githubExporter) cacheAllClient(repo *cache.RepoCache) error {
 			return nil
 		}
 
-		if _, ok := ge.identityClient[user.Id()]; !ok {
-			client := buildClient(creds[0].(*auth.Token))
-			ge.identityClient[user.Id()] = client
+		if _, ok := ge.identityClient[user.Id()]; ok {
+			continue
+		}
+
+		client := buildClient(creds[0].(*auth.Token))
+		ge.identityClient[user.Id()] = client
+
+		// assign the default client and token as well
+		if ge.defaultClient == nil && login == ge.conf[confKeyDefaultLogin] {
+			ge.defaultClient = client
+			ge.defaultToken = creds[0].(*auth.Token)
 		}
 	}
 
+	if ge.defaultClient == nil {
+		return fmt.Errorf("no token found for the default login \"%s\"", ge.conf[confKeyDefaultLogin])
+	}
+
 	return nil
 }
 

bridge/github/export_test.go 🔗

@@ -190,8 +190,9 @@ func TestPushPull(t *testing.T) {
 	// initialize exporter
 	exporter := &githubExporter{}
 	err = exporter.Init(ctx, backend, core.Configuration{
-		confKeyOwner:   envUser,
-		confKeyProject: projectName,
+		confKeyOwner:        envUser,
+		confKeyProject:      projectName,
+		confKeyDefaultLogin: login,
 	})
 	require.NoError(t, err)
 
@@ -217,8 +218,9 @@ func TestPushPull(t *testing.T) {
 
 	importer := &githubImporter{}
 	err = importer.Init(ctx, backend, core.Configuration{
-		confKeyOwner:   envUser,
-		confKeyProject: projectName,
+		confKeyOwner:        envUser,
+		confKeyProject:      projectName,
+		confKeyDefaultLogin: login,
 	})
 	require.NoError(t, err)
 

bridge/github/github.go 🔗

@@ -19,8 +19,9 @@ const (
 	metaKeyGithubUrl   = "github-url"
 	metaKeyGithubLogin = "github-login"
 
-	confKeyOwner   = "owner"
-	confKeyProject = "project"
+	confKeyOwner        = "owner"
+	confKeyProject      = "project"
+	confKeyDefaultLogin = "default-login"
 
 	githubV3Url    = "https://api.github.com"
 	defaultTimeout = 60 * time.Second

bridge/github/import.go 🔗

@@ -19,7 +19,7 @@ import (
 type githubImporter struct {
 	conf core.Configuration
 
-	// default user client
+	// default client
 	client *githubv4.Client
 
 	// iterator
@@ -32,7 +32,11 @@ type githubImporter struct {
 func (gi *githubImporter) Init(_ context.Context, repo *cache.RepoCache, conf core.Configuration) error {
 	gi.conf = conf
 
-	creds, err := auth.List(repo, auth.WithTarget(target), auth.WithKind(auth.KindToken))
+	creds, err := auth.List(repo,
+		auth.WithTarget(target),
+		auth.WithKind(auth.KindToken),
+		auth.WithMeta(auth.MetaKeyLogin, conf[confKeyDefaultLogin]),
+	)
 	if err != nil {
 		return err
 	}
@@ -434,7 +438,7 @@ func (gi *githubImporter) ensureTimelineComment(repo *cache.RepoCache, b *cache.
 				}
 				gi.out <- core.NewImportComment(op.Id())
 
-				// set target for the nexr edit now that the comment is created
+				// set target for the next edit now that the comment is created
 				targetOpID = op.Id()
 				continue
 			}

bridge/github/import_test.go 🔗

@@ -153,8 +153,9 @@ func Test_Importer(t *testing.T) {
 
 	importer := &githubImporter{}
 	err = importer.Init(ctx, backend, core.Configuration{
-		confKeyOwner:   "MichaelMure",
-		confKeyProject: "git-bug-test-github-bridge",
+		confKeyOwner:        "MichaelMure",
+		confKeyProject:      "git-bug-test-github-bridge",
+		confKeyDefaultLogin: login,
 	})
 	require.NoError(t, err)
 

bridge/gitlab/config.go 🔗

@@ -117,6 +117,7 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor
 	conf[core.ConfigKeyTarget] = target
 	conf[confKeyProjectID] = strconv.Itoa(id)
 	conf[confKeyGitlabBaseUrl] = baseUrl
+	conf[confKeyDefaultLogin] = login
 
 	err = g.ValidateConfig(conf)
 	if err != nil {
@@ -146,6 +147,9 @@ func (g *Gitlab) ValidateConfig(conf core.Configuration) error {
 	if _, ok := conf[confKeyProjectID]; !ok {
 		return fmt.Errorf("missing %s key", confKeyProjectID)
 	}
+	if _, ok := conf[confKeyDefaultLogin]; !ok {
+		return fmt.Errorf("missing %s key", confKeyDefaultLogin)
+	}
 
 	return nil
 }
@@ -249,12 +253,12 @@ func getValidGitlabRemoteURLs(repo repository.RepoCommon, baseUrl string) ([]str
 
 	urls := make([]string, 0, len(remotes))
 	for _, u := range remotes {
-		path, err := getProjectPath(baseUrl, u)
+		p, err := getProjectPath(baseUrl, u)
 		if err != nil {
 			continue
 		}
 
-		urls = append(urls, fmt.Sprintf("%s/%s", baseUrl, path))
+		urls = append(urls, fmt.Sprintf("%s/%s", baseUrl, p))
 	}
 
 	return urls, nil

bridge/gitlab/export_test.go 🔗

@@ -198,6 +198,7 @@ func TestPushPull(t *testing.T) {
 	err = exporter.Init(ctx, backend, core.Configuration{
 		confKeyProjectID:     strconv.Itoa(projectID),
 		confKeyGitlabBaseUrl: defaultBaseURL,
+		confKeyDefaultLogin:  login,
 	})
 	require.NoError(t, err)
 
@@ -225,6 +226,7 @@ func TestPushPull(t *testing.T) {
 	err = importer.Init(ctx, backend, core.Configuration{
 		confKeyProjectID:     strconv.Itoa(projectID),
 		confKeyGitlabBaseUrl: defaultBaseURL,
+		confKeyDefaultLogin:  login,
 	})
 	require.NoError(t, err)
 

bridge/gitlab/gitlab.go 🔗

@@ -21,6 +21,7 @@ const (
 
 	confKeyProjectID     = "project-id"
 	confKeyGitlabBaseUrl = "base-url"
+	confKeyDefaultLogin  = "default-login"
 
 	defaultBaseURL = "https://gitlab.com/"
 	defaultTimeout = 60 * time.Second

bridge/gitlab/import.go 🔗

@@ -20,7 +20,7 @@ import (
 type gitlabImporter struct {
 	conf core.Configuration
 
-	// default user client
+	// default client
 	client *gitlab.Client
 
 	// iterator
@@ -37,6 +37,7 @@ func (gi *gitlabImporter) Init(_ context.Context, repo *cache.RepoCache, conf co
 		auth.WithTarget(target),
 		auth.WithKind(auth.KindToken),
 		auth.WithMeta(auth.MetaKeyBaseURL, conf[confKeyGitlabBaseUrl]),
+		auth.WithMeta(auth.MetaKeyLogin, conf[confKeyDefaultLogin]),
 	)
 	if err != nil {
 		return err

bridge/gitlab/import_test.go 🔗

@@ -110,6 +110,7 @@ func TestImport(t *testing.T) {
 	err = importer.Init(ctx, backend, core.Configuration{
 		confKeyProjectID:     projectID,
 		confKeyGitlabBaseUrl: defaultBaseURL,
+		confKeyDefaultLogin:  login,
 	})
 	require.NoError(t, err)
 

bridge/jira/config.go 🔗

@@ -79,7 +79,7 @@ func (j *Jira) Configure(repo *cache.RepoCache, params core.BridgeParams) (core.
 		}
 		login = l
 	default:
-		login := params.Login
+		login = params.Login
 		if login == "" {
 			// TODO: validate username
 			login, err = input.Prompt("JIRA login", "login", input.Required)
@@ -98,6 +98,7 @@ func (j *Jira) Configure(repo *cache.RepoCache, params core.BridgeParams) (core.
 	conf[confKeyBaseUrl] = baseURL
 	conf[confKeyProject] = project
 	conf[confKeyCredentialType] = credType
+	conf[confKeyDefaultLogin] = login
 
 	err = j.ValidateConfig(conf)
 	if err != nil {
@@ -144,10 +145,18 @@ func (*Jira) ValidateConfig(conf core.Configuration) error {
 	} else if v != target {
 		return fmt.Errorf("unexpected target name: %v", v)
 	}
-
+	if _, ok := conf[confKeyBaseUrl]; !ok {
+		return fmt.Errorf("missing %s key", confKeyBaseUrl)
+	}
 	if _, ok := conf[confKeyProject]; !ok {
 		return fmt.Errorf("missing %s key", confKeyProject)
 	}
+	if _, ok := conf[confKeyCredentialType]; !ok {
+		return fmt.Errorf("missing %s key", confKeyCredentialType)
+	}
+	if _, ok := conf[confKeyDefaultLogin]; !ok {
+		return fmt.Errorf("missing %s key", confKeyDefaultLogin)
+	}
 
 	return nil
 }

bridge/jira/import.go 🔗

@@ -40,8 +40,9 @@ func (ji *jiraImporter) Init(ctx context.Context, repo *cache.RepoCache, conf co
 	// Prioritize LoginPassword credentials to avoid a prompt
 	creds, err := auth.List(repo,
 		auth.WithTarget(target),
-		auth.WithMeta(auth.MetaKeyBaseURL, conf[confKeyBaseUrl]),
 		auth.WithKind(auth.KindLoginPassword),
+		auth.WithMeta(auth.MetaKeyBaseURL, conf[confKeyBaseUrl]),
+		auth.WithMeta(auth.MetaKeyLogin, conf[confKeyDefaultLogin]),
 	)
 	if err != nil {
 		return err
@@ -53,8 +54,9 @@ func (ji *jiraImporter) Init(ctx context.Context, repo *cache.RepoCache, conf co
 
 	creds, err = auth.List(repo,
 		auth.WithTarget(target),
-		auth.WithMeta(auth.MetaKeyBaseURL, conf[confKeyBaseUrl]),
 		auth.WithKind(auth.KindLogin),
+		auth.WithMeta(auth.MetaKeyBaseURL, conf[confKeyBaseUrl]),
+		auth.WithMeta(auth.MetaKeyLogin, conf[confKeyDefaultLogin]),
 	)
 	if err != nil {
 		return err

bridge/jira/jira.go 🔗

@@ -25,6 +25,7 @@ const (
 
 	confKeyBaseUrl        = "base-url"
 	confKeyProject        = "project"
+	confKeyDefaultLogin   = "default-login"
 	confKeyCredentialType = "credentials-type" // "SESSION" or "TOKEN"
 	confKeyIDMap          = "bug-id-map"
 	confKeyIDRevMap       = "bug-id-revmap"

bridge/launchpad/config.go 🔗

@@ -66,7 +66,6 @@ func (*Launchpad) ValidateConfig(conf core.Configuration) error {
 	} else if v != target {
 		return fmt.Errorf("unexpected target name: %v", v)
 	}
-
 	if _, ok := conf[confKeyProject]; !ok {
 		return fmt.Errorf("missing %s key", confKeyProject)
 	}

input/prompt.go 🔗

@@ -48,10 +48,12 @@ func IsURL(name string, value string) (string, error) {
 
 // Prompts
 
+// Prompt is a simple text input.
 func Prompt(prompt, name string, validators ...PromptValidator) (string, error) {
 	return PromptDefault(prompt, name, "", validators...)
 }
 
+// PromptDefault is a simple text input with a default value.
 func PromptDefault(prompt, name, preValue string, validators ...PromptValidator) (string, error) {
 loop:
 	for {
@@ -87,6 +89,7 @@ loop:
 	}
 }
 
+// PromptPassword is a specialized text input that doesn't display the characters entered.
 func PromptPassword(prompt, name string, validators ...PromptValidator) (string, error) {
 	termState, err := terminal.GetState(syscall.Stdin)
 	if err != nil {
@@ -128,6 +131,8 @@ loop:
 	}
 }
 
+// PromptChoice is a prompt giving possible choices
+// Return the index starting at zero of the choice selected.
 func PromptChoice(prompt string, choices []string) (int, error) {
 	for {
 		for i, choice := range choices {