mirror of https://gitlab.com/gitlab-org/cli.git
Compare commits
8 Commits
43386c0110
...
148864a125
Author | SHA1 | Date |
---|---|---|
Erran Carey | 148864a125 | |
Oscar Tovar | 9ed43f6507 | |
Viacheslav Vasilyev | d4663189c2 | |
Shekhar Patnaik | 7953c99e8e | |
Shekhar Patnaik | 4b3defdd0f | |
Mikel | d2eabc73b9 | |
Gary Holtz | a09c60159e | |
Erran Carey | 4a0e82370c |
29
README.md
29
README.md
|
@ -140,9 +140,9 @@ To build from source:
|
|||
|
||||
## Authentication
|
||||
|
||||
### OAuth (GitLab.com only)
|
||||
### OAuth (GitLab.com)
|
||||
|
||||
To authenticate your installation of `glab` with OAuth:
|
||||
To authenticate your installation of `glab` with an OAuth application connected to GitLab.com:
|
||||
|
||||
1. Start interactive setup with `glab auth login`.
|
||||
1. For the GitLab instance you want to sign in to, select **GitLab.com**.
|
||||
|
@ -151,6 +151,30 @@ To authenticate your installation of `glab` with OAuth:
|
|||
1. Select **Authorize**.
|
||||
1. Complete the authentication process in your terminal, selecting the appropriate options for your needs.
|
||||
|
||||
### OAuth (self-managed)
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You've created an OAuth application at the user, group, or instance level, and you
|
||||
have its application ID. For instructions, see how to configure GitLab
|
||||
[as an OAuth 2.0 authentication identity provider](https://docs.gitlab.com/ee/integration/oauth_provider.html)
|
||||
in the GitLab documentation.
|
||||
- Your OAuth application is configured with these parameters:
|
||||
- **Redirect URI** is `http://localhost:7171/auth/redirect`.
|
||||
- **Confidential** is not selected.
|
||||
- **Scopes** are `openid`, `profile`, `read_user`, `write_repository`, and `api`.
|
||||
|
||||
To authenticate your installation of `glab` with an OAuth application connected
|
||||
to your self-managed instance:
|
||||
|
||||
1. Store the application ID with `glab config set client_id <CLIENT_ID> --host <HOSTNAME>`.
|
||||
For `<CLIENT_ID>`, provide your application ID.
|
||||
1. Start interactive setup with `glab auth login --hostname <HOSTNAME>`.
|
||||
1. For the login method, select **Web**. This selection launches your web browser
|
||||
to request authorization for the GitLab CLI to use your self-managed account.
|
||||
1. Select **Authorize**.
|
||||
1. Complete the authentication process in your terminal, selecting the appropriate options for your needs.
|
||||
|
||||
### Personal Access Token
|
||||
|
||||
To authenticate your installation of `glab` with a personal access token:
|
||||
|
@ -235,6 +259,7 @@ glab config set ca_cert /path/to/server.pem --host gitlab.example.com
|
|||
Can be set in the config with `glab config set token xxxxxx`
|
||||
- `GITLAB_URI` or `GITLAB_HOST`: specify the URL of the GitLab server if self-managed (eg: `https://gitlab.example.com`). Default is `https://gitlab.com`.
|
||||
- `GITLAB_API_HOST`: specify the host where the API endpoint is found. Useful when there are separate (sub)domains or hosts for Git and the API endpoint: defaults to the hostname found in the Git URL
|
||||
- `GITLAB_CLIENT_ID`: a custom Client-ID generated by the GitLab OAuth 2.0 application. Defaults to the Client-ID for GitLab.com.
|
||||
- `GITLAB_REPO`: Default GitLab repository used for commands accepting the `--repo` option. Only used if no `--repo` option is given.
|
||||
- `GITLAB_GROUP`: Default GitLab group used for listing merge requests, issues and variables. Only used if no `--group` option is given.
|
||||
- `REMOTE_ALIAS` or `GIT_REMOTE_URL_VAR`: `git remote` variable or alias that contains the GitLab URL.
|
||||
|
|
13
api/issue.go
13
api/issue.go
|
@ -129,6 +129,19 @@ var DeleteIssue = func(client *gitlab.Client, projectID interface{}, issueID int
|
|||
return nil
|
||||
}
|
||||
|
||||
var CreateIssueAwardEmoji = func(client *gitlab.Client, projectID interface{}, mrID int, opts *gitlab.CreateAwardEmojiOptions) (*gitlab.AwardEmoji, error) {
|
||||
if client == nil {
|
||||
client = apiClient.Lab()
|
||||
}
|
||||
|
||||
emoji, _, err := client.AwardEmoji.CreateIssueAwardEmoji(projectID, mrID, opts)
|
||||
if err != nil {
|
||||
return emoji, err
|
||||
}
|
||||
|
||||
return emoji, nil
|
||||
}
|
||||
|
||||
var CreateIssueNote = func(client *gitlab.Client, projectID interface{}, mrID int, opts *gitlab.CreateIssueNoteOptions) (*gitlab.Note, error) {
|
||||
if client == nil {
|
||||
client = apiClient.Lab()
|
||||
|
|
|
@ -195,6 +195,19 @@ var GetMRLinkedIssues = func(client *gitlab.Client, projectID interface{}, mrID
|
|||
return mrIssues, nil
|
||||
}
|
||||
|
||||
var CreateMRAwardEmoji = func(client *gitlab.Client, projectID interface{}, mrID int, opts *gitlab.CreateAwardEmojiOptions) (*gitlab.AwardEmoji, error) {
|
||||
if client == nil {
|
||||
client = apiClient.Lab()
|
||||
}
|
||||
|
||||
emoji, _, err := client.AwardEmoji.CreateMergeRequestAwardEmoji(projectID, mrID, opts)
|
||||
if err != nil {
|
||||
return emoji, err
|
||||
}
|
||||
|
||||
return emoji, nil
|
||||
}
|
||||
|
||||
var CreateMRNote = func(client *gitlab.Client, projectID interface{}, mrID int, opts *gitlab.CreateMergeRequestNoteOptions) (*gitlab.Note, error) {
|
||||
if client == nil {
|
||||
client = apiClient.Lab()
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"gitlab.com/gitlab-org/cli/api"
|
||||
"gitlab.com/gitlab-org/cli/internal/config"
|
||||
"gitlab.com/gitlab-org/cli/test"
|
||||
)
|
||||
|
||||
func Test_groupGraphQLVariables(t *testing.T) {
|
||||
|
@ -96,7 +97,7 @@ hosts:
|
|||
username: monalisa
|
||||
token: OTOKEN
|
||||
`, "")()
|
||||
t.Setenv("GITLAB_TOKEN", "")
|
||||
test.ClearEnvironmentVariables(t)
|
||||
|
||||
api.SetUserAgent("v1.2.3", "darwin", "arm64")
|
||||
versionString := "glab/v1.2.3 (darwin, arm64)"
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zalando/go-keyring"
|
||||
|
||||
"gitlab.com/gitlab-org/cli/api"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
"gitlab.com/gitlab-org/cli/internal/config"
|
||||
|
@ -90,13 +91,15 @@ func NewCmdLogin(f *cmdutils.Factory) *cobra.Command {
|
|||
}
|
||||
}
|
||||
|
||||
if !opts.Interactive {
|
||||
if opts.Hostname == "" {
|
||||
opts.Hostname = glinstance.Default()
|
||||
}
|
||||
if !opts.Interactive && opts.Hostname == "" {
|
||||
opts.Hostname = glinstance.Default()
|
||||
}
|
||||
|
||||
return loginRun()
|
||||
if err := loginRun(opts); err != nil {
|
||||
return cmdutils.WrapError(err, "Could not sign in!")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -108,7 +111,7 @@ func NewCmdLogin(f *cmdutils.Factory) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func loginRun() error {
|
||||
func loginRun(opts *LoginOptions) error {
|
||||
c := opts.IO.Color()
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
|
@ -175,6 +178,8 @@ func loginRun() error {
|
|||
return fmt.Errorf("could not prompt: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isSelfHosted = glinstance.IsSelfHosted(hostname)
|
||||
}
|
||||
|
||||
fmt.Fprintf(opts.IO.StdErr, "- Logging into %s\n", hostname)
|
||||
|
@ -211,36 +216,24 @@ func loginRun() error {
|
|||
}
|
||||
}
|
||||
|
||||
loginType := 0
|
||||
var loginType string
|
||||
|
||||
if opts.Interactive {
|
||||
if isSelfHosted {
|
||||
err := survey.AskOne(&survey.Select{
|
||||
Message: "How would you like to login?",
|
||||
Options: []string{
|
||||
"Token",
|
||||
},
|
||||
}, &loginType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get login type: %w", err)
|
||||
}
|
||||
} else {
|
||||
err := survey.AskOne(&survey.Select{
|
||||
Message: "How would you like to login?",
|
||||
Options: []string{
|
||||
"Token",
|
||||
"Web",
|
||||
},
|
||||
}, &loginType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get login type: %w", err)
|
||||
}
|
||||
err := survey.AskOne(&survey.Select{
|
||||
Message: "How would you like to sign in?",
|
||||
Options: []string{
|
||||
"Token",
|
||||
"Web",
|
||||
},
|
||||
}, &loginType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get login type: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var token string
|
||||
if loginType == 0 {
|
||||
token, err = showTokenPrompt(hostname)
|
||||
if strings.EqualFold(loginType, "token") {
|
||||
token, err = showTokenPrompt(opts.IO, hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -389,9 +382,9 @@ func getAccessTokenTip(hostname string) string {
|
|||
The minimum required scopes are 'api' and 'write_repository'.`, glHostname)
|
||||
}
|
||||
|
||||
func showTokenPrompt(hostname string) (string, error) {
|
||||
fmt.Fprintln(opts.IO.StdErr)
|
||||
fmt.Fprintln(opts.IO.StdErr, heredoc.Doc(getAccessTokenTip(hostname)))
|
||||
func showTokenPrompt(io *iostreams.IOStreams, hostname string) (string, error) {
|
||||
fmt.Fprintln(io.StdErr)
|
||||
fmt.Fprintln(io.StdErr, heredoc.Doc(getAccessTokenTip(hostname)))
|
||||
|
||||
var token string
|
||||
err := survey.AskOne(&survey.Password{
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package award_emoji
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gitlab.com/gitlab-org/cli/commands/issue/issueutils"
|
||||
|
||||
"gitlab.com/gitlab-org/cli/api"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
"gitlab.com/gitlab-org/cli/pkg/utils"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
gitlab "github.com/xanzy/go-gitlab"
|
||||
)
|
||||
|
||||
func NewCmdAwardEmoji(f *cmdutils.Factory) *cobra.Command {
|
||||
issueAwardEmojiCreateCmd := &cobra.Command{
|
||||
Use: "award-emoji <issue-id>",
|
||||
Aliases: []string{"comment"},
|
||||
Short: "Award an emoji to an issue on GitLab",
|
||||
Long: ``,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
out := f.IO.StdOut
|
||||
|
||||
apiClient, err := f.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issue, repo, err := issueutils.IssueFromArg(apiClient, f.BaseRepo, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
|
||||
if name == "" {
|
||||
name = utils.Editor(utils.EditorOptions{
|
||||
Label: "Award Emoji Name:",
|
||||
Help: "Enter the award emoji's name. ",
|
||||
FileName: "ISSUE_AWARD_EMOJI_EDITMSG",
|
||||
})
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
return errors.New("aborted... Award Emoji name is empty")
|
||||
}
|
||||
|
||||
emoji, err := api.CreateIssueAwardEmoji(apiClient, repo.FullName(), issue.IID, &gitlab.CreateAwardEmojiOptions{
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "Added award emoji %d\n", emoji.AwardableID)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
issueAwardEmojiCreateCmd.Flags().StringP("name", "n", "", "Award Emoji name")
|
||||
|
||||
return issueAwardEmojiCreateCmd
|
||||
}
|
|
@ -3,6 +3,7 @@ package issue
|
|||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
issueAwardEmojiCmd "gitlab.com/gitlab-org/cli/commands/issue/award-emoji"
|
||||
issueBoardCmd "gitlab.com/gitlab-org/cli/commands/issue/board"
|
||||
issueCloseCmd "gitlab.com/gitlab-org/cli/commands/issue/close"
|
||||
issueCreateCmd "gitlab.com/gitlab-org/cli/commands/issue/create"
|
||||
|
@ -40,6 +41,7 @@ func NewCmdIssue(f *cmdutils.Factory) *cobra.Command {
|
|||
|
||||
cmdutils.EnableRepoOverride(issueCmd, f)
|
||||
|
||||
issueCmd.AddCommand(issueAwardEmojiCmd.NewCmdAwardEmoji(f))
|
||||
issueCmd.AddCommand(issueCloseCmd.NewCmdClose(f))
|
||||
issueCmd.AddCommand(issueBoardCmd.NewCmdBoard(f))
|
||||
issueCmd.AddCommand(issueCreateCmd.NewCmdCreate(f))
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package award_emoji
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gitlab.com/gitlab-org/cli/commands/mr/mrutils"
|
||||
|
||||
"gitlab.com/gitlab-org/cli/api"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
"gitlab.com/gitlab-org/cli/pkg/utils"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
gitlab "github.com/xanzy/go-gitlab"
|
||||
)
|
||||
|
||||
func NewCmdAwardEmoji(f *cmdutils.Factory) *cobra.Command {
|
||||
mrAwardEmojiCreateCmd := &cobra.Command{
|
||||
Use: "award-emoji <id>",
|
||||
Aliases: []string{"comment"},
|
||||
Short: "Award an emoji to an merge request on GitLab",
|
||||
Long: ``,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
out := f.IO.StdOut
|
||||
|
||||
apiClient, err := f.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mr, repo, err := mrutils.MRFromArgs(f, args, "any")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
|
||||
if name == "" {
|
||||
name = utils.Editor(utils.EditorOptions{
|
||||
Label: "Award Emoji Name:",
|
||||
Help: "Enter the award emoji's name. ",
|
||||
FileName: "MR_AWARD_EMOJI_EDITMSG",
|
||||
})
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
return errors.New("aborted... Award Emoji name is empty")
|
||||
}
|
||||
|
||||
emoji, err := api.CreateMRAwardEmoji(apiClient, repo.FullName(), mr.IID, &gitlab.CreateAwardEmojiOptions{
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "Added award emoji %d\n", emoji.AwardableID)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
mrAwardEmojiCreateCmd.Flags().StringP("name", "n", "", "Award Emoji name")
|
||||
|
||||
return mrAwardEmojiCreateCmd
|
||||
}
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/avast/retry-go"
|
||||
"github.com/avast/retry-go/v4"
|
||||
"gitlab.com/gitlab-org/cli/api"
|
||||
"gitlab.com/gitlab-org/cli/commands/mr/mrutils"
|
||||
"gitlab.com/gitlab-org/cli/pkg/prompt"
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
mrApproveCmd "gitlab.com/gitlab-org/cli/commands/mr/approve"
|
||||
mrApproversCmd "gitlab.com/gitlab-org/cli/commands/mr/approvers"
|
||||
mrAwardEmojiCmd "gitlab.com/gitlab-org/cli/commands/mr/award-emoji"
|
||||
mrCheckoutCmd "gitlab.com/gitlab-org/cli/commands/mr/checkout"
|
||||
mrCloseCmd "gitlab.com/gitlab-org/cli/commands/mr/close"
|
||||
mrCreateCmd "gitlab.com/gitlab-org/cli/commands/mr/create"
|
||||
|
@ -50,6 +51,7 @@ func NewCmdMR(f *cmdutils.Factory) *cobra.Command {
|
|||
|
||||
mrCmd.AddCommand(mrApproveCmd.NewCmdApprove(f))
|
||||
mrCmd.AddCommand(mrApproversCmd.NewCmdApprovers(f))
|
||||
mrCmd.AddCommand(mrAwardEmojiCmd.NewCmdAwardEmoji(f))
|
||||
mrCmd.AddCommand(mrCheckoutCmd.NewCmdCheckout(f))
|
||||
mrCmd.AddCommand(mrCloseCmd.NewCmdClose(f))
|
||||
mrCmd.AddCommand(mrCreateCmd.NewCmdCreate(f, nil))
|
||||
|
|
|
@ -52,6 +52,9 @@ func NewCmdRoot(f *cmdutils.Factory, version, buildDate string) *cobra.Command {
|
|||
GITLAB_HOST or GL_HOST: Specify the URL of the GitLab server if self-managed.
|
||||
(Example: https://gitlab.example.com) Defaults to https://gitlab.com.
|
||||
|
||||
GITLAB_CLIENT_ID: Provide custom 'client_id' generated by GitLab OAuth 2.0 application.
|
||||
Defaults to the 'client-id' for GitLab.com.
|
||||
|
||||
REMOTE_ALIAS or GIT_REMOTE_URL_VAR: A 'git remote' variable or alias that contains
|
||||
the GitLab URL. Can be set in the config with 'glab config set remote_alias origin'.
|
||||
|
||||
|
|
4
go.mod
4
go.mod
|
@ -6,7 +6,7 @@ require (
|
|||
github.com/AlecAivazis/survey/v2 v2.3.6
|
||||
github.com/MakeNowJust/heredoc v1.0.0
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/avast/retry-go v3.0.0+incompatible
|
||||
github.com/avast/retry-go/v4 v4.6.0
|
||||
github.com/briandowns/spinner v1.23.0
|
||||
github.com/charmbracelet/glamour v0.6.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
|
@ -32,7 +32,7 @@ require (
|
|||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.16.0
|
||||
github.com/stretchr/testify v1.8.3
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tidwall/pretty v1.2.1
|
||||
github.com/xanzy/go-gitlab v0.103.0
|
||||
github.com/zalando/go-keyring v0.2.3
|
||||
|
|
10
go.sum
10
go.sum
|
@ -50,8 +50,8 @@ github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbf
|
|||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
|
||||
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA=
|
||||
github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE=
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
|
@ -317,8 +317,9 @@ github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
|
|||
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
|
@ -327,8 +328,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gitlab.com/gitlab-org/cli/test"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
@ -16,13 +17,6 @@ func eq(t *testing.T, got interface{}, expected interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
func clearEnvironmentVariables(t *testing.T) {
|
||||
// prevent using environment variables for test
|
||||
t.Setenv("GITLAB_TOKEN", "")
|
||||
t.Setenv("VISUAL", "")
|
||||
t.Setenv("EDITOR", "")
|
||||
}
|
||||
|
||||
func Test_parseConfig(t *testing.T) {
|
||||
defer StubConfig(`---
|
||||
hosts:
|
||||
|
@ -31,7 +25,7 @@ hosts:
|
|||
token: OTOKEN
|
||||
aliases:
|
||||
`, "")()
|
||||
clearEnvironmentVariables(t)
|
||||
test.ClearEnvironmentVariables(t)
|
||||
|
||||
config, err := ParseConfig("config.yml")
|
||||
eq(t, err, nil)
|
||||
|
@ -53,7 +47,7 @@ hosts:
|
|||
username: monalisa
|
||||
token: OTOKEN
|
||||
`, "")()
|
||||
clearEnvironmentVariables(t)
|
||||
test.ClearEnvironmentVariables(t)
|
||||
|
||||
config, err := ParseConfig("config.yml")
|
||||
eq(t, err, nil)
|
||||
|
@ -73,7 +67,7 @@ hosts:
|
|||
token: OTOKEN
|
||||
`, `
|
||||
`)()
|
||||
clearEnvironmentVariables(t)
|
||||
test.ClearEnvironmentVariables(t)
|
||||
|
||||
config, err := ParseConfig("config.yml")
|
||||
eq(t, err, nil)
|
||||
|
@ -86,6 +80,8 @@ hosts:
|
|||
}
|
||||
|
||||
func Test_parseConfig_Local(t *testing.T) {
|
||||
test.ClearEnvironmentVariables(t)
|
||||
|
||||
defer StubConfig(`---
|
||||
git_protocol: ssh
|
||||
editor: vim
|
||||
|
@ -105,6 +101,8 @@ local:
|
|||
}
|
||||
|
||||
func Test_Get_configReadSequence(t *testing.T) {
|
||||
test.ClearEnvironmentVariables(t)
|
||||
|
||||
defer StubConfig(`---
|
||||
git_protocol: ssh
|
||||
editor: vim
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package config
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ConfigKeyEquivalence returns the equivalent key that's actually used in the config file
|
||||
func ConfigKeyEquivalence(key string) string {
|
||||
|
@ -19,6 +21,8 @@ func ConfigKeyEquivalence(key string) string {
|
|||
return "remote_alias"
|
||||
case "editor", "visual", "glab_editor":
|
||||
return "editor"
|
||||
case "client_id":
|
||||
return "client_id"
|
||||
default:
|
||||
return key
|
||||
}
|
||||
|
@ -41,6 +45,8 @@ func EnvKeyEquivalence(key string) []string {
|
|||
return []string{"GLAB_EDITOR", "VISUAL", "EDITOR"}
|
||||
case "remote_alias":
|
||||
return []string{"GIT_REMOTE_URL_VAR", "GIT_REMOTE_ALIAS", "REMOTE_ALIAS", "REMOTE_NICKNAME", "GIT_REMOTE_NICKNAME"}
|
||||
case "client_id":
|
||||
return []string{"GITLAB_CLIENT_ID"}
|
||||
default:
|
||||
return []string{strings.ToUpper(key)}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package config
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
|
@ -93,7 +94,7 @@ func Test_defaultConfig(t *testing.T) {
|
|||
|
||||
editor, err := cfg.Get("", "editor")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "", editor)
|
||||
assert.Equal(t, os.Getenv("EDITOR"), editor)
|
||||
|
||||
aliases, err := cfg.Aliases()
|
||||
assert.Nil(t, err)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
const (
|
||||
defaultHostname = "gitlab.com"
|
||||
defaultProtocol = "https"
|
||||
defaultClientId = "41d48f9422ebd655dd9cf2947d6979681dfaddc6d0c56f7628f6ada59559af1e"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -26,6 +27,10 @@ func DefaultProtocol() string {
|
|||
return defaultProtocol
|
||||
}
|
||||
|
||||
func DefaultClientID() string {
|
||||
return defaultClientId
|
||||
}
|
||||
|
||||
// OverridableDefault is like Default, except it is overridable by the GITLAB_HOST environment variable
|
||||
func OverridableDefault() string {
|
||||
if hostnameOverride != "" {
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func Test_HelperFunctions(t *testing.T) {
|
||||
// Base ios object that is modifiede as required
|
||||
// Base ios object that is modified as required
|
||||
ios := &IOStreams{
|
||||
In: os.Stdin,
|
||||
StdOut: NewColorable(os.Stdout),
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"gitlab.com/gitlab-org/cli/internal/config"
|
||||
"gitlab.com/gitlab-org/cli/pkg/glinstance"
|
||||
"gitlab.com/gitlab-org/cli/pkg/iostreams"
|
||||
"gitlab.com/gitlab-org/cli/pkg/utils"
|
||||
)
|
||||
|
@ -20,11 +21,29 @@ const (
|
|||
scopes = "openid+profile+read_user+write_repository+api"
|
||||
)
|
||||
|
||||
const clientID = "41d48f9422ebd655dd9cf2947d6979681dfaddc6d0c56f7628f6ada59559af1e"
|
||||
func oAuthClientID(cfg config.Config, hostname string) (string, error) {
|
||||
if glinstance.IsSelfHosted(hostname) {
|
||||
clientID, err := cfg.Get(hostname, "client_id")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if clientID == "" {
|
||||
return "", fmt.Errorf("set 'client_id' first with `glab config set client_id <client_id> -g -h %s`", hostname)
|
||||
}
|
||||
return clientID, nil
|
||||
}
|
||||
return glinstance.DefaultClientID(), nil
|
||||
}
|
||||
|
||||
func StartFlow(cfg config.Config, io *iostreams.IOStreams, hostname string) (string, error) {
|
||||
authURL := fmt.Sprintf("https://%s/oauth/authorize", hostname)
|
||||
|
||||
clientID, err := oAuthClientID(cfg, hostname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
state := randomString()
|
||||
codeVerifier := randomString()
|
||||
codeChallenge := generateCodeChallenge(codeVerifier)
|
||||
|
@ -32,7 +51,7 @@ func StartFlow(cfg config.Config, io *iostreams.IOStreams, hostname string) (str
|
|||
"%s?client_id=%s&redirect_uri=%s&response_type=code&state=%s&scope=%s&code_challenge=%s&code_challenge_method=S256",
|
||||
authURL, clientID, redirectURI, state, scopes, codeChallenge)
|
||||
|
||||
tokenCh := handleAuthRedirect(io, codeVerifier, hostname, "https")
|
||||
tokenCh := handleAuthRedirect(io, codeVerifier, hostname, "https", clientID)
|
||||
defer close(tokenCh)
|
||||
|
||||
browser, _ := cfg.Get(hostname, "browser")
|
||||
|
@ -43,7 +62,7 @@ func StartFlow(cfg config.Config, io *iostreams.IOStreams, hostname string) (str
|
|||
}
|
||||
token := <-tokenCh
|
||||
|
||||
err := token.SetConfig(hostname, cfg)
|
||||
err = token.SetConfig(hostname, cfg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -51,14 +70,14 @@ func StartFlow(cfg config.Config, io *iostreams.IOStreams, hostname string) (str
|
|||
return token.AccessToken, nil
|
||||
}
|
||||
|
||||
func handleAuthRedirect(io *iostreams.IOStreams, codeVerifier, hostname, protocol string) chan *AuthToken {
|
||||
func handleAuthRedirect(io *iostreams.IOStreams, codeVerifier, hostname, protocol, clientID string) chan *AuthToken {
|
||||
tokenCh := make(chan *AuthToken)
|
||||
|
||||
server := &http.Server{Addr: ":7171"}
|
||||
|
||||
http.HandleFunc("/auth/redirect", func(w http.ResponseWriter, r *http.Request) {
|
||||
code := r.URL.Query().Get("code")
|
||||
token, err := requestToken(hostname, protocol, code, codeVerifier)
|
||||
token, err := requestToken(hostname, protocol, clientID, code, codeVerifier)
|
||||
if err != nil {
|
||||
fmt.Fprintf(io.StdErr, "Error occured requesting access token %s", err)
|
||||
tokenCh <- nil
|
||||
|
@ -81,7 +100,7 @@ func handleAuthRedirect(io *iostreams.IOStreams, codeVerifier, hostname, protoco
|
|||
return tokenCh
|
||||
}
|
||||
|
||||
func requestToken(hostname, protocol, code, codeVerifier string) (*AuthToken, error) {
|
||||
func requestToken(hostname, protocol, clientID, code, codeVerifier string) (*AuthToken, error) {
|
||||
tokenURL := fmt.Sprintf("%s://%s/oauth/token", protocol, hostname)
|
||||
|
||||
form := url.Values{
|
||||
|
@ -126,6 +145,11 @@ func RefreshToken(hostname string, cfg config.Config, protocol string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
clientID, err := oAuthClientID(cfg, hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
form := url.Values{
|
||||
"client_id": []string{clientID},
|
||||
"grant_type": []string{"refresh_token"},
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gitlab.com/gitlab-org/cli/pkg/glinstance"
|
||||
"gitlab.com/gitlab-org/cli/pkg/iostreams"
|
||||
)
|
||||
|
||||
|
@ -32,11 +33,12 @@ func TestHandleAuthRedirect(t *testing.T) {
|
|||
"token": "access_token",
|
||||
"oauth2_code_verifier": "123",
|
||||
"oauth2_expiry_date": "13 Mar 23 15:47 GMT",
|
||||
"client_id": "321",
|
||||
}
|
||||
|
||||
ios, _, _, _ := iostreams.Test()
|
||||
|
||||
tokenCh := handleAuthRedirect(ios, "123", hostname, "http")
|
||||
tokenCh := handleAuthRedirect(ios, "123", hostname, "http", "abc")
|
||||
defer close(tokenCh)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
|
@ -69,6 +71,7 @@ func TestRefreshToken(t *testing.T) {
|
|||
"token": "access_token",
|
||||
"oauth2_code_verifier": "123",
|
||||
"oauth2_expiry_date": "13 Mar 23 15:47 GMT",
|
||||
"client_id": "321",
|
||||
}
|
||||
|
||||
err := RefreshToken(hostname, cfg, "http")
|
||||
|
@ -87,3 +90,51 @@ func TestRefreshToken(t *testing.T) {
|
|||
_, err = time.Parse(time.RFC822, expiryDateString)
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestClientID(t *testing.T) {
|
||||
testCasesTable := []struct {
|
||||
name string
|
||||
hostname string
|
||||
configClientID string
|
||||
expectedClientID string
|
||||
}{
|
||||
{
|
||||
name: "managed",
|
||||
hostname: glinstance.Default(),
|
||||
configClientID: "",
|
||||
expectedClientID: glinstance.DefaultClientID(),
|
||||
},
|
||||
{
|
||||
name: "self-managed-complete",
|
||||
hostname: "salsa.debian.org",
|
||||
configClientID: "321",
|
||||
expectedClientID: "321",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCasesTable {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
cfg := stubConfig{
|
||||
hosts: map[string]map[string]string{
|
||||
testCase.hostname: {
|
||||
"client_id": testCase.configClientID,
|
||||
},
|
||||
},
|
||||
}
|
||||
clientID, err := oAuthClientID(cfg, testCase.hostname)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testCase.expectedClientID, clientID)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("invalid self-managed config", func(t *testing.T) {
|
||||
cfg := stubConfig{
|
||||
hosts: map[string]map[string]string{
|
||||
"salsa.debian.org": {},
|
||||
},
|
||||
}
|
||||
clientID, err := oAuthClientID(cfg, "salsa.debian.org")
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, clientID)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -106,6 +106,15 @@ func ExpectLines(t T, output string, lines ...string) {
|
|||
}
|
||||
}
|
||||
|
||||
func ClearEnvironmentVariables(t *testing.T) {
|
||||
// prevent using environment variables for test
|
||||
t.Setenv("GITLAB_TOKEN", "")
|
||||
t.Setenv("VISUAL", "")
|
||||
t.Setenv("EDITOR", "")
|
||||
t.Setenv("GITLAB_ACCESS_TOKEN", "")
|
||||
t.Setenv("OAUTH_TOKEN", "")
|
||||
}
|
||||
|
||||
func GetHostOrSkip(t testing.TB) string {
|
||||
t.Helper()
|
||||
glTestHost := os.Getenv("GITLAB_TEST_HOST")
|
||||
|
|
Loading…
Reference in New Issue