diff options
Diffstat (limited to 'bridge/github')
-rw-r--r-- | bridge/github/config.go | 228 | ||||
-rw-r--r-- | bridge/github/config_test.go | 4 | ||||
-rw-r--r-- | bridge/github/export.go | 18 | ||||
-rw-r--r-- | bridge/github/export_test.go | 17 | ||||
-rw-r--r-- | bridge/github/github.go | 4 | ||||
-rw-r--r-- | bridge/github/import.go | 4 | ||||
-rw-r--r-- | bridge/github/import_test.go | 11 |
7 files changed, 99 insertions, 187 deletions
diff --git a/bridge/github/config.go b/bridge/github/config.go index cc312230..b6f69d74 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -1,7 +1,6 @@ package github import ( - "bufio" "bytes" "context" "encoding/json" @@ -10,14 +9,11 @@ import ( "io/ioutil" "math/rand" "net/http" - "os" "regexp" "sort" - "strconv" "strings" "time" - text "github.com/MichaelMure/go-term-text" "github.com/pkg/errors" "github.com/MichaelMure/git-bug/bridge/core" @@ -25,19 +21,24 @@ import ( "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/input" "github.com/MichaelMure/git-bug/repository" - "github.com/MichaelMure/git-bug/util/colors" ) var ( ErrBadProjectURL = errors.New("bad project url") ) -func (g *Github) Configure(repo *cache.RepoCache, params core.BridgeParams) (core.Configuration, error) { - if params.BaseURL != "" { - fmt.Println("warning: --base-url is ineffective for a Github bridge") +func (g *Github) ValidParams() map[string]interface{} { + return map[string]interface{}{ + "URL": nil, + "Login": nil, + "CredPrefix": nil, + "TokenRaw": nil, + "Owner": nil, + "Project": nil, } +} - conf := make(core.Configuration) +func (g *Github) Configure(repo *cache.RepoCache, params core.BridgeParams) (core.Configuration, error) { var err error var owner string var project string @@ -86,7 +87,7 @@ func (g *Github) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor } login = l case params.TokenRaw != "": - token := auth.NewToken(params.TokenRaw, target) + token := auth.NewToken(target, params.TokenRaw) login, err = getLoginFromToken(token) if err != nil { return nil, err @@ -121,9 +122,10 @@ func (g *Github) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor return nil, fmt.Errorf("project doesn't exist or authentication token has an incorrect scope") } + conf := make(core.Configuration) conf[core.ConfigKeyTarget] = target - conf[keyOwner] = owner - conf[keyProject] = project + conf[confKeyOwner] = owner + conf[confKeyProject] = project err = g.ValidateConfig(conf) if err != nil { @@ -148,18 +150,18 @@ func (*Github) ValidateConfig(conf core.Configuration) error { return fmt.Errorf("unexpected target name: %v", v) } - if _, ok := conf[keyOwner]; !ok { - return fmt.Errorf("missing %s key", keyOwner) + if _, ok := conf[confKeyOwner]; !ok { + return fmt.Errorf("missing %s key", confKeyOwner) } - if _, ok := conf[keyProject]; !ok { - return fmt.Errorf("missing %s key", keyProject) + if _, ok := conf[confKeyProject]; !ok { + return fmt.Errorf("missing %s key", confKeyProject) } return nil } -func usernameValidator(name string, value string) (string, error) { +func usernameValidator(_ string, value string) (string, error) { ok, err := validateUsername(value) if err != nil { return "", err @@ -241,67 +243,36 @@ func randomFingerprint() string { } func promptTokenOptions(repo repository.RepoConfig, login, owner, project string) (auth.Credential, error) { - for { - creds, err := auth.List(repo, - auth.WithTarget(target), - auth.WithKind(auth.KindToken), - auth.WithMeta(auth.MetaKeyLogin, login), - ) - if err != nil { - return nil, err - } - - fmt.Println() - fmt.Println("[1]: enter my token") - fmt.Println("[2]: interactive token creation") - - if len(creds) > 0 { - sort.Sort(auth.ById(creds)) - - fmt.Println() - fmt.Println("Existing tokens for Github:") - for i, cred := range creds { - token := cred.(*auth.Token) - fmt.Printf("[%d]: %s => %s (login: %s, %s)\n", - i+3, - colors.Cyan(token.ID().Human()), - colors.Red(text.TruncateMax(token.Value, 10)), - token.Metadata()[auth.MetaKeyLogin], - token.CreateTime().Format(time.RFC822), - ) - } - } - - fmt.Println() - fmt.Print("Select option: ") + creds, err := auth.List(repo, + auth.WithTarget(target), + auth.WithKind(auth.KindToken), + auth.WithMeta(auth.MetaKeyLogin, login), + ) + if err != nil { + return nil, err + } - line, err := bufio.NewReader(os.Stdin).ReadString('\n') - fmt.Println() + cred, index, err := input.PromptCredential(target, "token", creds, []string{ + "enter my token", + "interactive token creation", + }) + switch { + case err != nil: + return nil, err + case cred != nil: + return cred, nil + case index == 0: + return promptToken() + case index == 1: + value, err := loginAndRequestToken(login, owner, project) if err != nil { return nil, err } - - line = strings.TrimSpace(line) - index, err := strconv.Atoi(line) - if err != nil || index < 1 || index > len(creds)+2 { - fmt.Println("invalid input") - continue - } - - switch index { - case 1: - return promptToken() - case 2: - value, err := loginAndRequestToken(login, owner, project) - if err != nil { - return nil, err - } - token := auth.NewToken(value, target) - token.SetMetadata(auth.MetaKeyLogin, login) - return token, nil - default: - return creds[index-3], nil - } + token := auth.NewToken(target, value) + token.SetMetadata(auth.MetaKeyLogin, login) + return token, nil + default: + panic("missed case") } } @@ -316,10 +287,7 @@ func promptToken() (*auth.Token, error) { fmt.Println(" - 'repo' : to be able to read private repositories") fmt.Println() - re, err := regexp.Compile(`^[a-zA-Z0-9]{40}$`) - if err != nil { - panic("regexp compile:" + err.Error()) - } + re := regexp.MustCompile(`^[a-zA-Z0-9]{40}$`) var login string @@ -327,7 +295,7 @@ func promptToken() (*auth.Token, error) { if !re.MatchString(value) { return "token has incorrect format", nil } - login, err = getLoginFromToken(auth.NewToken(value, target)) + login, err = getLoginFromToken(auth.NewToken(target, value)) if err != nil { return fmt.Sprintf("token is invalid: %v", err), nil } @@ -339,7 +307,7 @@ func promptToken() (*auth.Token, error) { return nil, err } - token := auth.NewToken(rawToken, target) + token := auth.NewToken(target, rawToken) token.SetMetadata(auth.MetaKeyLogin, login) return token, nil @@ -356,31 +324,19 @@ func loginAndRequestToken(login, owner, project string) (string, error) { fmt.Println() // prompt project visibility to know the token scope needed for the repository - i, err := input.PromptChoice("repository visibility", []string{"public", "private"}) + index, err := input.PromptChoice("repository visibility", []string{"public", "private"}) if err != nil { return "", err } - isPublic := i == 0 + scope := []string{"public_repo", "repo"}[index] password, err := input.PromptPassword("Password", "password", input.Required) if err != nil { return "", err } - var scope string - if isPublic { - // public_repo is requested to be able to read public repositories - scope = "public_repo" - } else { - // 'repo' is request to be able to read private repositories - // /!\ token will have read/write rights on every private repository you have access to - scope = "repo" - } - // Attempt to authenticate and create a token - note := fmt.Sprintf("git-bug - %s/%s", owner, project) - resp, err := requestToken(note, login, password, scope) if err != nil { return "", err @@ -413,73 +369,25 @@ func loginAndRequestToken(login, owner, project string) (string, error) { } func promptURL(repo repository.RepoCommon) (string, string, error) { - // remote suggestions - remotes, err := repo.GetRemotes() + validRemotes, err := getValidGithubRemoteURLs(repo) if err != nil { return "", "", err } - validRemotes := getValidGithubRemoteURLs(remotes) - if len(validRemotes) > 0 { - for { - fmt.Println("\nDetected projects:") - - // print valid remote github urls - for i, remote := range validRemotes { - fmt.Printf("[%d]: %v\n", i+1, remote) - } - - fmt.Printf("\n[0]: Another project\n\n") - fmt.Printf("Select option: ") - - line, err := bufio.NewReader(os.Stdin).ReadString('\n') - if err != nil { - return "", "", err - } - - line = strings.TrimSpace(line) - - index, err := strconv.Atoi(line) - if err != nil || index < 0 || index > len(validRemotes) { - fmt.Println("invalid input") - continue - } - - // if user want to enter another project url break this loop - if index == 0 { - break - } - - // get owner and project with index - owner, project, _ := splitURL(validRemotes[index-1]) - return owner, project, nil - } - } - - // manually enter github url - for { - fmt.Print("Github project URL: ") - - line, err := bufio.NewReader(os.Stdin).ReadString('\n') + validator := func(name, value string) (string, error) { + _, _, err := splitURL(value) if err != nil { - return "", "", err - } - - line = strings.TrimSpace(line) - if line == "" { - fmt.Println("URL is empty") - continue - } - - // get owner and project from url - owner, project, err := splitURL(line) - if err != nil { - fmt.Println(err) - continue + return err.Error(), nil } + return "", nil + } - return owner, project, nil + url, err := input.PromptURLWithRemote("Github project URL", "URL", validRemotes, input.Required, validator) + if err != nil { + return "", "", err } + + return splitURL(url) } // splitURL extract the owner and project from a github repository URL. It will remove the @@ -488,10 +396,7 @@ func promptURL(repo repository.RepoCommon) (string, string, error) { func splitURL(url string) (owner string, project string, err error) { cleanURL := strings.TrimSuffix(url, ".git") - re, err := regexp.Compile(`github\.com[/:]([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_.]+)`) - if err != nil { - panic("regexp compile:" + err.Error()) - } + re := regexp.MustCompile(`github\.com[/:]([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_.]+)`) res := re.FindStringSubmatch(cleanURL) if res == nil { @@ -503,7 +408,12 @@ func splitURL(url string) (owner string, project string, err error) { return } -func getValidGithubRemoteURLs(remotes map[string]string) []string { +func getValidGithubRemoteURLs(repo repository.RepoCommon) ([]string, error) { + remotes, err := repo.GetRemotes() + if err != nil { + return nil, err + } + urls := make([]string, 0, len(remotes)) for _, url := range remotes { // split url can work again with shortURL @@ -516,7 +426,7 @@ func getValidGithubRemoteURLs(remotes map[string]string) []string { sort.Strings(urls) - return urls + return urls, nil } func validateUsername(username string) (bool, error) { diff --git a/bridge/github/config_test.go b/bridge/github/config_test.go index d7b1b38d..fe54c209 100644 --- a/bridge/github/config_test.go +++ b/bridge/github/config_test.go @@ -154,8 +154,8 @@ func TestValidateProject(t *testing.T) { t.Skip("Env var GITHUB_TOKEN_PUBLIC missing") } - tokenPrivate := auth.NewToken(envPrivate, target) - tokenPublic := auth.NewToken(envPublic, target) + tokenPrivate := auth.NewToken(target, envPrivate) + tokenPublic := auth.NewToken(target, envPublic) type args struct { owner string diff --git a/bridge/github/export.go b/bridge/github/export.go index c363e188..12b62fa6 100644 --- a/bridge/github/export.go +++ b/bridge/github/export.go @@ -53,7 +53,7 @@ type githubExporter struct { } // Init . -func (ge *githubExporter) Init(repo *cache.RepoCache, conf core.Configuration) error { +func (ge *githubExporter) Init(_ context.Context, repo *cache.RepoCache, conf core.Configuration) error { ge.conf = conf ge.identityClient = make(map[entity.Id]*githubv4.Client) ge.cachedOperationIDs = make(map[entity.Id]string) @@ -139,8 +139,8 @@ func (ge *githubExporter) ExportAll(ctx context.Context, repo *cache.RepoCache, ge.repositoryID, err = getRepositoryNodeID( ctx, ge.defaultToken, - ge.conf[keyOwner], - ge.conf[keyProject], + ge.conf[confKeyOwner], + ge.conf[confKeyProject], ) if err != nil { return nil, err @@ -187,7 +187,7 @@ func (ge *githubExporter) ExportAll(ctx context.Context, repo *cache.RepoCache, if snapshot.HasAnyActor(allIdentitiesIds...) { // try to export the bug and it associated events - ge.exportBug(ctx, b, since, out) + ge.exportBug(ctx, b, out) } } } @@ -197,7 +197,7 @@ func (ge *githubExporter) ExportAll(ctx context.Context, repo *cache.RepoCache, } // exportBug publish bugs and related events -func (ge *githubExporter) exportBug(ctx context.Context, b *cache.BugCache, since time.Time, out chan<- core.ExportResult) { +func (ge *githubExporter) exportBug(ctx context.Context, b *cache.BugCache, out chan<- core.ExportResult) { snapshot := b.Snapshot() var bugUpdated bool @@ -238,7 +238,7 @@ func (ge *githubExporter) exportBug(ctx context.Context, b *cache.BugCache, sinc } // ignore issue coming from other repositories - if owner != ge.conf[keyOwner] && project != ge.conf[keyProject] { + if owner != ge.conf[confKeyOwner] && project != ge.conf[confKeyProject] { out <- core.NewExportNothing(b.Id(), fmt.Sprintf("skipping issue from url:%s", githubURL)) return } @@ -481,8 +481,8 @@ func markOperationAsExported(b *cache.BugCache, target entity.Id, githubID, gith func (ge *githubExporter) cacheGithubLabels(ctx context.Context, gc *githubv4.Client) error { variables := map[string]interface{}{ - "owner": githubv4.String(ge.conf[keyOwner]), - "name": githubv4.String(ge.conf[keyProject]), + "owner": githubv4.String(ge.conf[confKeyOwner]), + "name": githubv4.String(ge.conf[confKeyProject]), "first": githubv4.Int(10), "after": (*githubv4.String)(nil), } @@ -526,7 +526,7 @@ func (ge *githubExporter) getLabelID(gc *githubv4.Client, label string) (string, // NOTE: since createLabel mutation is still in preview mode we use github api v3 to create labels // see https://developer.github.com/v4/mutation/createlabel/ and https://developer.github.com/v4/previews/#labels-preview func (ge *githubExporter) createGithubLabel(ctx context.Context, label, color string) (string, error) { - url := fmt.Sprintf("%s/repos/%s/%s/labels", githubV3Url, ge.conf[keyOwner], ge.conf[keyProject]) + url := fmt.Sprintf("%s/repos/%s/%s/labels", githubV3Url, ge.conf[confKeyOwner], ge.conf[confKeyProject]) client := &http.Client{} params := struct { diff --git a/bridge/github/export_test.go b/bridge/github/export_test.go index 7d6e6fb1..acbd657a 100644 --- a/bridge/github/export_test.go +++ b/bridge/github/export_test.go @@ -157,7 +157,7 @@ func TestPushPull(t *testing.T) { defer backend.Close() interrupt.RegisterCleaner(backend.Close) - token := auth.NewToken(envToken, target) + token := auth.NewToken(target, envToken) token.SetMetadata(auth.MetaKeyLogin, login) err = auth.Store(repo, token) require.NoError(t, err) @@ -185,15 +185,16 @@ func TestPushPull(t *testing.T) { return deleteRepository(projectName, envUser, envToken) }) + ctx := context.Background() + // initialize exporter exporter := &githubExporter{} - err = exporter.Init(backend, core.Configuration{ - keyOwner: envUser, - keyProject: projectName, + err = exporter.Init(ctx, backend, core.Configuration{ + confKeyOwner: envUser, + confKeyProject: projectName, }) require.NoError(t, err) - ctx := context.Background() start := time.Now() // export all bugs @@ -215,9 +216,9 @@ func TestPushPull(t *testing.T) { require.NoError(t, err) importer := &githubImporter{} - err = importer.Init(backend, core.Configuration{ - keyOwner: envUser, - keyProject: projectName, + err = importer.Init(ctx, backend, core.Configuration{ + confKeyOwner: envUser, + confKeyProject: projectName, }) require.NoError(t, err) diff --git a/bridge/github/github.go b/bridge/github/github.go index 19dc8a08..3a99cec7 100644 --- a/bridge/github/github.go +++ b/bridge/github/github.go @@ -19,8 +19,8 @@ const ( metaKeyGithubUrl = "github-url" metaKeyGithubLogin = "github-login" - keyOwner = "owner" - keyProject = "project" + confKeyOwner = "owner" + confKeyProject = "project" githubV3Url = "https://api.github.com" defaultTimeout = 60 * time.Second diff --git a/bridge/github/import.go b/bridge/github/import.go index f7840217..e80b9cfd 100644 --- a/bridge/github/import.go +++ b/bridge/github/import.go @@ -29,7 +29,7 @@ type githubImporter struct { out chan<- core.ImportResult } -func (gi *githubImporter) Init(repo *cache.RepoCache, conf core.Configuration) error { +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)) @@ -49,7 +49,7 @@ func (gi *githubImporter) Init(repo *cache.RepoCache, conf core.Configuration) e // ImportAll iterate over all the configured repository issues and ensure the creation of the // missing issues / timeline items / edits / label events ... func (gi *githubImporter) ImportAll(ctx context.Context, repo *cache.RepoCache, since time.Time) (<-chan core.ImportResult, error) { - gi.iterator = NewIterator(ctx, gi.client, 10, gi.conf[keyOwner], gi.conf[keyProject], since) + gi.iterator = NewIterator(ctx, gi.client, 10, gi.conf[confKeyOwner], gi.conf[confKeyProject], since) out := make(chan core.ImportResult) gi.out = out diff --git a/bridge/github/import_test.go b/bridge/github/import_test.go index a8f8e346..20b1b71e 100644 --- a/bridge/github/import_test.go +++ b/bridge/github/import_test.go @@ -144,19 +144,20 @@ func Test_Importer(t *testing.T) { login := "test-identity" author.SetMetadata(metaKeyGithubLogin, login) - token := auth.NewToken(envToken, target) + token := auth.NewToken(target, envToken) token.SetMetadata(auth.MetaKeyLogin, login) err = auth.Store(repo, token) require.NoError(t, err) + ctx := context.Background() + importer := &githubImporter{} - err = importer.Init(backend, core.Configuration{ - keyOwner: "MichaelMure", - keyProject: "git-bug-test-github-bridge", + err = importer.Init(ctx, backend, core.Configuration{ + confKeyOwner: "MichaelMure", + confKeyProject: "git-bug-test-github-bridge", }) require.NoError(t, err) - ctx := context.Background() start := time.Now() events, err := importer.ImportAll(ctx, backend, time.Time{}) |