Compare commits

...

16 Commits

Author SHA1 Message Date
szEvEz 98d8757c69 Merge branch 'main' into 'main'
Add possibility to push the changelog to the repository

Closes #7432

See merge request https://gitlab.com/gitlab-org/cli/-/merge_requests/1385

Merged-by: szEvEz <szivos.john@gmail.com>
Reviewed-by: Oscar Tovar <otovar@gitlab.com>
Co-authored-by: Oscar Tovar <otovar@gitlab.com>
Co-authored-by: Amy Qualls <aqualls@gitlab.com>
2024-04-27 23:46:57 +00:00
Oscar Tovar 9ed43f6507 Merge branch 'feature/allow-to-set-client-id' into 'main'
feat: allow to provide custom client-id

See merge request https://gitlab.com/gitlab-org/cli/-/merge_requests/1440

Merged-by: Oscar Tovar <otovar@gitlab.com>
Approved-by: Amy Qualls <aqualls@gitlab.com>
Approved-by: Greg Alfaro <galfaro@gitlab.com>
Approved-by: Oscar Tovar <otovar@gitlab.com>
Reviewed-by: Oscar Tovar <otovar@gitlab.com>
Co-authored-by: avoidik <avoidik@gmail.com>
2024-04-24 19:27:12 +00:00
Viacheslav Vasilyev d4663189c2 feat: allow to provide custom client-id 2024-04-24 19:27:12 +00:00
Shekhar Patnaik 7953c99e8e Merge branch 'gmh-fix-environment-variable-tests' into 'main'
test: fix issue with environment variables being set

See merge request https://gitlab.com/gitlab-org/cli/-/merge_requests/1449

Merged-by: Shekhar Patnaik <spatnaik@gitlab.com>
Approved-by: Vitali Tatarintev <vtatarintev@gitlab.com>
Approved-by: Shekhar Patnaik <spatnaik@gitlab.com>
Co-authored-by: Gary Holtz <gholtz@gitlab.com>
2024-04-24 14:51:44 +00:00
Shekhar Patnaik 4b3defdd0f Merge branch 'update-avast' into 'main'
chore(deps): Update retry-go dependency

See merge request https://gitlab.com/gitlab-org/cli/-/merge_requests/1442

Merged-by: Shekhar Patnaik <spatnaik@gitlab.com>
Approved-by: Erran Carey <ecarey@gitlab.com>
Approved-by: Shekhar Patnaik <spatnaik@gitlab.com>
Co-authored-by: Mikel Olasagasti Uranga <mikel@olasagasti.info>
2024-04-24 14:50:58 +00:00
Mikel d2eabc73b9 chore(deps): Update retry-go dependency 2024-04-24 14:50:58 +00:00
Gary Holtz a09c60159e
test: fix issue with environment variables being set 2024-04-20 16:19:20 -05:00
szEvEz 1f60899055 Require the user to pass the version flag when adding the changelog to the repository 2024-01-14 15:17:10 +01:00
szEvEz 289661f557 Omit git.ListTags, as git.DescribeByTags will always fail 2024-01-14 14:21:57 +01:00
Oscar Tovar b919e769d6 Apply 1 suggestion(s) to 1 file(s) 2023-12-22 12:27:18 +00:00
szEvEz d4775e744f Put flag default description in separate line, run gen-docs 2023-12-14 16:48:05 +01:00
szEvEz 70acf52666 run make gen-docs 2023-12-14 16:15:13 +01:00
Amy Qualls d066f90f98 Apply 1 suggestion(s) to 1 file(s) 2023-12-14 15:09:27 +00:00
Amy Qualls 150f3be648 Apply 1 suggestion(s) to 1 file(s) 2023-12-14 15:09:17 +00:00
szEvEz 45061611f1 make gen-docs 2023-12-12 23:36:02 +01:00
szEvEz 0e1ed03125 Add possibility to push the changelog to the repository 2023-12-12 22:10:03 +01:00
21 changed files with 361 additions and 73 deletions

View File

@ -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.

View File

@ -14,3 +14,12 @@ var GenerateChangelog = func(client *gitlab.Client, projectID interface{}, optio
return changelog, nil
}
var AddChangelog = func(client *gitlab.Client, projectID interface{}, opt *gitlab.AddChangelogOptions) error {
if client == nil {
client = apiClient.Lab()
}
_, err := client.Repositories.AddChangelog(projectID, opt)
return err
}

View File

@ -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)"

View File

@ -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{

View File

@ -0,0 +1,114 @@
package add
import (
"time"
"github.com/MakeNowJust/heredoc"
"gitlab.com/gitlab-org/cli/api"
"github.com/spf13/cobra"
"github.com/xanzy/go-gitlab"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
)
func NewCmdAdd(f *cmdutils.Factory) *cobra.Command {
changelogAddCmd := &cobra.Command{
Use: "add [flags]",
Short: `Add a changelog to the repository.`,
Long: ``,
Example: heredoc.Doc(`glab changelog add`),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
apiClient, err := f.HttpClient()
if err != nil {
return err
}
repo, err := f.BaseRepo()
if err != nil {
return err
}
opts := &gitlab.AddChangelogOptions{}
// Set the version
if s, _ := cmd.Flags().GetString("version"); s != "" {
opts.Version = gitlab.String(s)
}
// Set the branch
if s, _ := cmd.Flags().GetString("branch"); s != "" {
opts.Branch = gitlab.String(s)
}
// Set the config file
if s, _ := cmd.Flags().GetString("config-file"); s != "" {
opts.ConfigFile = gitlab.String(s)
}
// Set the date
if s, _ := cmd.Flags().GetString("date"); s != "" {
parsedDate, err := time.Parse(time.RFC3339, s)
if err != nil {
return err
}
t := gitlab.ISOTime(parsedDate)
opts.Date = &t
}
// Set the "file" attribute
if s, _ := cmd.Flags().GetString("file"); s != "" {
opts.File = gitlab.String(s)
}
// Set the "message" attribute
if s, _ := cmd.Flags().GetString("message"); s != "" {
opts.Message = gitlab.String(s)
}
// Set the "from" attribute
if s, _ := cmd.Flags().GetString("from"); s != "" {
opts.From = gitlab.String(s)
}
// Set the "to" attribute
if s, _ := cmd.Flags().GetString("to"); s != "" {
opts.To = gitlab.String(s)
}
// Set the trailer
if s, _ := cmd.Flags().GetString("trailer"); s != "" {
opts.Trailer = gitlab.String(s)
}
project, err := repo.Project(apiClient)
if err != nil {
return err
}
err = api.AddChangelog(apiClient, project.ID, opts)
if err != nil {
return err
}
return nil
},
}
// The options mimic the ones from the REST API.
// https://docs.gitlab.com/ee/api/repositories.html#generate-changelog-data
changelogAddCmd.Flags().StringP("version", "v", "", "Required. The version to generate the changelog for. Must follow semantic versioning.")
changelogAddCmd.Flags().StringP("branch", "", "", "The branch to commit the changelog changes to.\nDefaults to the projects default branch.")
changelogAddCmd.Flags().StringP("config-file", "", "", "The path of changelog configuration file in the project's Git repository.\nDefaults to '.gitlab/changelog_config.yml'.")
changelogAddCmd.Flags().StringP("date", "", "", "The date and time of the release. Uses ISO 8601 (2016-03-11T03:45:40Z) format.\nDefaults to the current time.")
changelogAddCmd.Flags().StringP("file", "", "", "The file to commit the changes to.\nDefaults to 'CHANGELOG.md'.")
changelogAddCmd.Flags().StringP("from", "", "", "The start of the range of commits (as a SHA) to use for generating the changelog. The commit itself isn't included in the list.")
changelogAddCmd.Flags().StringP("message", "", "", "The commit message to use when committing the changes.\nDefaults to 'Add changelog for version X', where X is the value of the version argument.")
changelogAddCmd.Flags().StringP("to", "", "", "The end of the range of commits (as a SHA) to use for the changelog. This commit is included in the list.\nDefaults to the HEAD of the default project branch.")
changelogAddCmd.Flags().StringP("trailer", "", "", "The Git trailer to use for including commits.\nDefaults to 'Changelog'.")
changelogAddCmd.MarkFlagRequired("version")
return changelogAddCmd
}

View File

@ -2,6 +2,7 @@ package changelog
import (
"github.com/spf13/cobra"
changelogAddCmd "gitlab.com/gitlab-org/cli/commands/changelog/add"
changelogGenerateCmd "gitlab.com/gitlab-org/cli/commands/changelog/generate"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
)
@ -15,6 +16,7 @@ func NewCmdChangelog(f *cmdutils.Factory) *cobra.Command {
// Subcommands
changelogCmd.AddCommand(changelogGenerateCmd.NewCmdGenerate(f))
changelogCmd.AddCommand(changelogAddCmd.NewCmdAdd(f))
return changelogCmd
}

View File

@ -1,7 +1,6 @@
package generate
import (
"errors"
"fmt"
"time"
@ -40,15 +39,6 @@ func NewCmdGenerate(f *cmdutils.Factory) *cobra.Command {
if s, _ := cmd.Flags().GetString("version"); s != "" {
opts.Version = gitlab.Ptr(s)
} else {
tags, err := git.ListTags()
if err != nil {
return err
}
if len(tags) == 0 {
return errors.New("no tags found. Either fetch tags or pass a version with --version instead")
}
version, err := git.DescribeByTags()
if err != nil {
return fmt.Errorf("failed to determine version from git describe: %w..", err)

View File

@ -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"

View File

@ -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'.

View File

@ -0,0 +1,54 @@
---
stage: Create
group: Code Review
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
<!--
This documentation is auto generated by a script.
Please do not edit this file directly. Run `make gen-docs` instead.
-->
# `glab changelog add`
Add a changelog to the repository.
```plaintext
glab changelog add [flags]
```
## Examples
```plaintext
glab changelog add
```
## Options
```plaintext
--branch string The branch to commit the changelog changes to.
Defaults to the projects default branch.
--config-file string The path of changelog configuration file in the project's Git repository.
Defaults to '.gitlab/changelog_config.yml'.
--date string The date and time of the release. Uses ISO 8601 (2016-03-11T03:45:40Z) format.
Defaults to the current time.
--file string The file to commit the changes to.
Defaults to 'CHANGELOG.md'.
--from string The start of the range of commits (as a SHA) to use for generating the changelog.
The commit itself isn't included in the list.
--message string The commit message to use when committing the changes.
Defaults to 'Add changelog for version X', where X is the value of the version argument.
--to string The end of the range of commits (as a SHA) to use for the changelog.
This commit is included in the list.
Defaults to the HEAD of the default project branch.
--trailer string The Git trailer to use for including commits.
Defaults to 'Changelog'.
-v, --version string The version to generate the changelog for. Must follow semantic versioning.
Defaults to the version of the local checkout, like using git describe.
```
## Options inherited from parent commands
```plaintext
--help Show help for command
```

View File

@ -21,4 +21,5 @@ Interact with the changelog API
## Subcommands
- [`add`](add.md)
- [`generate`](generate.md)

4
go.mod
View File

@ -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
View File

@ -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=

View File

@ -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

View File

@ -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)}
}

View File

@ -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)

View File

@ -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 != "" {

View File

@ -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),

View File

@ -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"},

View File

@ -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)
})
}

View File

@ -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")