Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Azure DevOps VersionController #476

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion cmd/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func configureGit(cmd *cobra.Command) {
cmd.Flags().StringP("git-type", "", "go", `The type of git implementation to use.
Available values:
go: Uses go-git, a Go native implementation of git. This is compiled with the multi-gitter binary, and no extra dependencies are needed.
cmd: Calls out to the git command. This requires git to be installed and available with by calling "git".
cmd: Calls out to the git command. This requires git to be installed and available with by calling "git". This must be used when using Azure DevOps.
`)
_ = cmd.RegisterFlagCompletionFunc("git-type", func(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return []string{"go", "cmd"}, cobra.ShellCompDirectiveDefault
Expand Down
4 changes: 3 additions & 1 deletion cmd/other.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ func getToken(flag *flag.FlagSet) (string, error) {
token = ght
} else if ght := os.Getenv("BITBUCKET_SERVER_TOKEN"); ght != "" {
token = ght
} else if ght := os.Getenv("AZURE_DEVOPS_TOKEN"); ght != "" {
token = ght
}
}

if token == "" {
return "", errors.New("either the --token flag or the GITHUB_TOKEN/GITLAB_TOKEN/GITEA_TOKEN/BITBUCKET_SERVER_TOKEN environment variable has to be set")
return "", errors.New("either the --token flag or the GITHUB_TOKEN/GITLAB_TOKEN/GITEA_TOKEN/BITBUCKET_SERVER_TOKEN/AZURE_DEVOPS_TOKEN environment variable has to be set")
}

return token, nil
Expand Down
59 changes: 51 additions & 8 deletions cmd/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/lindell/multi-gitter/internal/http"
"github.com/lindell/multi-gitter/internal/multigitter"
"github.com/lindell/multi-gitter/internal/scm/azuredevops"
"github.com/lindell/multi-gitter/internal/scm/bitbucketserver"
"github.com/lindell/multi-gitter/internal/scm/gitea"
"github.com/lindell/multi-gitter/internal/scm/github"
Expand All @@ -19,26 +20,26 @@ import (
func configurePlatform(cmd *cobra.Command) {
flags := cmd.Flags()

flags.StringP("base-url", "g", "", "Base URL of the target platform, needs to be changed for GitHub enterprise, a self-hosted GitLab instance, Gitea or BitBucket.")
flags.StringP("base-url", "g", "", "Base URL of the target platform, needs to be changed for GitHub enterprise, a self-hosted GitLab instance, Gitea, BitBucket or Azure DevOps.")
flags.BoolP("insecure", "", false, "Insecure controls whether a client verifies the server certificate chain and host name. Used only for Bitbucket server.")
flags.StringP("username", "u", "", "The Bitbucket server username.")
flags.StringP("token", "T", "", "The personal access token for the targeting platform. Can also be set using the GITHUB_TOKEN/GITLAB_TOKEN/GITEA_TOKEN/BITBUCKET_SERVER_TOKEN environment variable.")
flags.StringP("token", "T", "", "The personal access token for the targeting platform. Can also be set using the GITHUB_TOKEN/GITLAB_TOKEN/GITEA_TOKEN/BITBUCKET_SERVER_TOKEN/AZURE_DEVOPS_TOKEN environment variable.")

flags.StringSliceP("org", "O", nil, "The name of a GitHub organization. All repositories in that organization will be used.")
flags.StringSliceP("group", "G", nil, "The name of a GitLab organization. All repositories in that group will be used.")
flags.StringSliceP("user", "U", nil, "The name of a user. All repositories owned by that user will be used.")
flags.StringSliceP("repo", "R", nil, "The name, including owner of a GitHub repository in the format \"ownerName/repoName\".")
flags.StringSliceP("repo", "R", nil, "The repository name, including owner/group/project of the repository in the format \"ownerName/repoName\".")
flags.StringP("repo-search", "", "", "Use a repository search to find repositories to target (GitHub only). Forks are NOT included by default, use `fork:true` to include them. See the GitHub documentation for full syntax: https://docs.github.com/en/search-github/searching-on-github/searching-for-repositories.")
flags.StringP("code-search", "", "", "Use a code search to find a set of repositories to target (GitHub only). Repeated results from a given repository will be ignored, forks are NOT included by default (use `fork:true` to include them). See the GitHub documentation for full syntax: https://docs.github.com/en/search-github/searching-on-github/searching-code.")
flags.StringSliceP("topic", "", nil, "The topic of a GitHub/GitLab/Gitea repository. All repositories having at least one matching topic are targeted.")
flags.StringSliceP("project", "P", nil, "The name, including owner of a GitLab project in the format \"ownerName/repoName\".")
flags.StringSliceP("project", "P", nil, "The name, including owner of a GitLab project in the format \"ownerName/repoName\". In Azure DevOps this is in the format \"projectName\".")
flags.BoolP("include-subgroups", "", false, "Include GitLab subgroups when using the --group flag.")
flags.BoolP("ssh-auth", "", false, `Use SSH cloning URL instead of HTTPS + token. This requires that a setup with ssh keys that have access to all repos and that the server is already in known_hosts.`)
flags.BoolP("skip-forks", "", false, `Skip repositories which are forks.`)

flags.StringP("platform", "p", "github", "The platform that is used. Available values: github, gitlab, gitea, bitbucket_server.")
flags.StringP("platform", "p", "github", "The platform that is used. Available values: github, gitlab, gitea, bitbucket_server, azuredevops.")
_ = cmd.RegisterFlagCompletionFunc("platform", func(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return []string{"github", "gitlab", "gitea", "bitbucket_server"}, cobra.ShellCompDirectiveDefault
return []string{"github", "gitlab", "gitea", "bitbucket_server, azuredevops"}, cobra.ShellCompDirectiveDefault
})

// Autocompletion for organizations
Expand Down Expand Up @@ -85,9 +86,9 @@ func configureRunPlatform(cmd *cobra.Command, prCreating bool) {
}
flags.BoolP("fork", "", false, forkDesc)

forkOwnerDesc := "If set, make the fork to the defined value. Default behavior is for the fork to be on the logged in user."
forkOwnerDesc := "If set, make the fork to the defined value (owner/project/group). Default behavior is for the fork to be on the logged in user."
if !prCreating {
forkOwnerDesc = "If set, use forks from the defined value instead of the logged in user."
forkOwnerDesc = "If set, use forks from the defined value (owner/project/group) instead of the logged in user."
}

flags.StringP("fork-owner", "", "", forkOwnerDesc)
Expand All @@ -114,6 +115,8 @@ func getVersionController(flag *flag.FlagSet, verifyFlags bool, readOnly bool) (
return createGiteaClient(flag, verifyFlags)
case "bitbucket_server":
return createBitbucketServerClient(flag, verifyFlags)
case "azuredevops":
return createAzureDevOpsClient(flag, verifyFlags)
default:
return nil, fmt.Errorf("unknown platform: %s", platform)
}
Expand Down Expand Up @@ -329,6 +332,46 @@ func createBitbucketServerClient(flag *flag.FlagSet, verifyFlags bool) (multigit
return vc, nil
}

func createAzureDevOpsClient(flag *flag.FlagSet, verifyFlags bool) (multigitter.VersionController, error) {
azureDevOpsBaseURL, _ := flag.GetString("base-url")
projects, _ := flag.GetStringSlice("project")
repos, _ := flag.GetStringSlice("repo")
sshAuth, _ := flag.GetBool("ssh-auth")
fork, _ := flag.GetBool("fork")

if verifyFlags && len(projects) == 0 && len(repos) == 0 {
rerikson marked this conversation as resolved.
Show resolved Hide resolved
return nil, errors.New("no projects or repositories set")
}

if azureDevOpsBaseURL == "" {
return nil, errors.New("no base-url set for azure devops")
}

token, err := getToken(flag)
if err != nil {
return nil, err
}

repoRefs := make([]azuredevops.RepositoryReference, len(repos))
for i := range repos {
repoRefs[i], err = azuredevops.ParseRepositoryReference(repos[i])
if err != nil {
return nil, err
}
}

vc, err := azuredevops.New(token, azureDevOpsBaseURL, sshAuth, fork, azuredevops.RepositoryListing{
Projects: projects,
Repositories: repoRefs,
})

if err != nil {
return nil, err
}

return vc, nil
}

// versionControllerCompletion is a helper function to allow for easier implementation of Cobra autocompletions that depend on a version controller
func versionControllerCompletion(cmd *cobra.Command, flagName string, fn func(vc multigitter.VersionController, toComplete string) ([]string, error)) {
_ = cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
Expand Down
7 changes: 5 additions & 2 deletions docs/README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ go install github.com/lindell/multi-gitter@latest
```

## Token

To use multi-gitter, a token that is allowed to list repositories and create pull requests is needed. This token can either be set in the `GITHUB_TOKEN`, `GITLAB_TOKEN`, `GITEA_TOKEN` environment variable, or by using the `--token` flag.
To use multi-gitter, a token that is allowed to list repositories and create pull requests is needed. This token can either be set in the `GITHUB_TOKEN`, `GITLAB_TOKEN`, `GITEA_TOKEN`, `AZURE_DEVOPS_TOKEN` environment variable, or by using the `--token` flag.

### GitHub
[How to generate a GitHub personal access token (classic)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic). Make sure to give it `repo` permissions.
Expand All @@ -90,6 +89,10 @@ To use multi-gitter, a token that is allowed to list repositories and create pul

In Gitea, access tokens can be generated under Settings -> Applications -> Manage Access Tokens

### Azure DevOps

In Azure DevOps, token can be generated by going to User Settings -> Personal Access Tokens (PAT). In the scopes for the PAT, make sure to check "Full" for the Code scope, "Read" for the Identity scope, and "Read" for the Project and Team scope.

## Config file

All configuration in multi-gitter can be done through command line flags, configuration files or a mix of both. If you want to use a configuration file, simply use the `--config=./path/to/config.yaml`. Multi-gitter will also read from the file `~/.multi-gitter/config` and take and configuration from there. The priority of configs are first flags, then defined config file and lastly the static config file.
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ require (
golang.org/x/oauth2 v0.19.0
)

require github.com/google/uuid v1.4.0

require (
dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
Expand All @@ -44,6 +46,7 @@ require (
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ github.com/google/go-github/v59 v59.0.0 h1:7h6bgpF5as0YQLLkEiVqpgtJqjimMYhBkD4jT
github.com/google/go-github/v59 v59.0.0/go.mod h1:rJU4R0rQHFVFDOkqGWxfLNo6vEk4dv40oDjhV/gH6wM=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
Expand Down Expand Up @@ -96,6 +99,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 h1:mmJCWLe63QvybxhW1iBmQWEaCKdc4SKgALfTNZ+OphU=
github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0/go.mod h1:mDunUZ1IUJdJIRHvFb+LPBUtxe3AYB5MI6BMXNg8194=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
Expand Down
Loading