Skip to content

Commit

Permalink
fix: git directory independent commands
Browse files Browse the repository at this point in the history
This commit fixes issues with glab requiring commands that are git-independent to be run in a git repository.
Commands like repo clone, repo archive, etc should be able to run outside git repos

Fixes profclems#198
  • Loading branch information
profclems committed Nov 23, 2020
1 parent 715cc98 commit 4465c16
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 50 deletions.
4 changes: 2 additions & 2 deletions commands/auth/login/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func loginRun() error {
existingToken, _, _ := cfg.GetWithSource(hostname, "token")

if existingToken != "" && opts.Interactive {
apiClient, err := cmdutils.HttpClientFunc(hostname, cfg)
apiClient, err := cmdutils.HttpClientFunc(hostname, cfg, false)
if err != nil {
return err
}
Expand Down Expand Up @@ -224,7 +224,7 @@ func loginRun() error {
fmt.Fprintf(opts.IO.StdErr, "%s Configured git protocol\n", utils.GreenCheck())
}

apiClient, err := cmdutils.HttpClientFunc(hostname, cfg)
apiClient, err := cmdutils.HttpClientFunc(hostname, cfg, false)
if err != nil {
return err
}
Expand Down
18 changes: 12 additions & 6 deletions commands/cmdutils/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package cmdutils
import (
"fmt"

"github.com/profclems/glab/internal/glinstance"

"github.com/profclems/glab/internal/utils"

"github.com/profclems/glab/internal/config"
Expand Down Expand Up @@ -41,20 +43,23 @@ func (f *Factory) RepoOverride(repo string) error {
OverrideAPIProtocol(cfg, newRepo)
}
f.HttpClient = func() (*gitlab.Client, error) {
return HttpClientFunc(newRepo.RepoHost(), cfg)
return HttpClientFunc(newRepo.RepoHost(), cfg, false)
}
return nil
}

func HttpClientFunc(repoHost string, cfg config.Config) (*gitlab.Client, error) {
func HttpClientFunc(repoHost string, cfg config.Config, isGraphQL bool) (*gitlab.Client, error) {
if repoHost == "" {
repoHost = glinstance.OverridableDefault()
}
token, _ := cfg.Get(repoHost, "token")
tlsVerify, _ := cfg.Get(repoHost, "skip_tls_verify")
skipTlsVerify := tlsVerify == "true" || tlsVerify == "1"
caCert, _ := cfg.Get(repoHost, "ca_cert")
if caCert != "" {
return api.InitWithCustomCA(repoHost, token, caCert)
return api.InitWithCustomCA(repoHost, token, caCert, isGraphQL)
}
return api.Init(repoHost, token, skipTlsVerify)
return api.Init(repoHost, token, skipTlsVerify, isGraphQL)
}

func remotesFunc() (glrepo.Remotes, error) {
Expand Down Expand Up @@ -95,10 +100,11 @@ func HTTPClientFactory(f *Factory) {
}
repo, err := baseRepoFunc()
if err != nil {
return nil, err
// use default hostname if remote resolver fails
repo = glrepo.NewWithHost("", "", glinstance.OverridableDefault())
}
OverrideAPIProtocol(cfg, repo)
return HttpClientFunc(repo.RepoHost(), cfg)
return HttpClientFunc(repo.RepoHost(), cfg, false)
}
}

Expand Down
77 changes: 51 additions & 26 deletions commands/pipeline/ci/trace/pipeline_ci_trace_test.go
Original file line number Diff line number Diff line change
@@ -1,89 +1,114 @@
package trace

import (
"bytes"
"os/exec"
"testing"

"github.com/profclems/glab/commands/cmdutils"
"github.com/profclems/glab/internal/config"
"github.com/profclems/glab/internal/utils"
"github.com/spf13/cobra"

"github.com/profclems/glab/commands/cmdtest"
"github.com/stretchr/testify/assert"
)

var (
stubFactory *cmdutils.Factory
cmd *cobra.Command
stdout *bytes.Buffer
stderr *bytes.Buffer
)

func TestMain(m *testing.M) {
cmdtest.InitTest(m, "pipeline_ci_trace_test")
}

func Test_ciTrace(t *testing.T) {
t.Parallel()
defer config.StubConfig(`---
git_protocol: https
hosts:
gitlab.com:
username: root
`, "")()

var io *utils.IOStreams
io, _, stdout, stderr = utils.IOTest()
stubFactory, _ = cmdtest.StubFactoryWithConfig("https://gitlab.com/glab-cli/test.git")
stubFactory.IO = io
stubFactory.IO.IsaTTY = true
stubFactory.IO.IsErrTTY = true

repo := cmdtest.CopyTestRepo(t, "pipeline_ci_trace_test")
cmd := exec.Command("git", "fetch", "origin")
cmd.Dir = repo
if b, err := cmd.CombinedOutput(); err != nil {
gitCmd := exec.Command("git", "fetch", "origin")
gitCmd.Dir = repo
if b, err := gitCmd.CombinedOutput(); err != nil {
t.Log(string(b))
t.Fatal(err)
}

cmd = exec.Command("git", "checkout", "origin/test-ci")
cmd.Dir = repo
if b, err := cmd.CombinedOutput(); err != nil {
gitCmd = exec.Command("git", "checkout", "origin/test-ci")
gitCmd.Dir = repo
if b, err := gitCmd.CombinedOutput(); err != nil {
t.Log(string(b))
t.Fatal(err)
//t.Fatal(err)
}

cmd = exec.Command("git", "checkout", "-b", "test-ci")
cmd.Dir = repo
if b, err := cmd.CombinedOutput(); err != nil {
gitCmd = exec.Command("git", "checkout", "test-ci")
gitCmd.Dir = repo
if b, err := gitCmd.CombinedOutput(); err != nil {
t.Log(string(b))
t.Fatal(err)
}

tests := []struct {
desc string
args []string
args string
assertContains func(t *testing.T, out string)
}{
// TODO: better test for survey prompt when no argument is provided
{
desc: "Has no arg",
args: []string{},
args: ``,
assertContains: func(t *testing.T, out string) {
assert.Contains(t, out, "Getting job trace...")
assert.Contains(t, out, "Showing logs for build1 job #732481769")
assert.Contains(t, out, "Showing logs for ")
assert.Contains(t, out, "Preparing the \"docker+machine\"")
assert.Contains(t, out, "Checking out 6caeb21d as test-ci...")
assert.Contains(t, out, "Do your build here")
assert.Contains(t, out, "$ echo \"Let's do some cleanup\"")
assert.Contains(t, out, "Job succeeded")
},
},
{
desc: "Has arg with job-id",
args: []string{"732481782"},
args: `716449943`,
assertContains: func(t *testing.T, out string) {
assert.Contains(t, out, "Getting job trace...")
assert.Contains(t, out, "Getting job trace...\n")
assert.Contains(t, out, "Job succeeded")
},
},
{
desc: "On a specified repo with job ID",
args: []string{"-Rglab-cli/test", "716449943"},
args: "716449943 -X glab-cli/test",
assertContains: func(t *testing.T, out string) {
assert.Contains(t, out, "Getting job trace...")
assert.Contains(t, out, "Getting job trace...\n")
assert.Contains(t, out, "Job succeeded")
},
},
}

cmd = NewCmdTrace(stubFactory)
cmd.Flags().StringP("repo", "X", "", "")

for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
t.Parallel()
cmd = exec.Command(cmdtest.GlabBinaryPath, append([]string{"pipe", "ci", "trace"}, tt.args...)...)
cmd.Dir = repo

b, err := cmd.CombinedOutput()
_, err := cmdtest.RunCommand(cmd, tt.args)
if err != nil {
t.Log(string(b))
t.Fatal(err)
}
out := string(b)
tt.assertContains(t, out)
tt.assertContains(t, stdout.String())
})
}

Expand Down
4 changes: 2 additions & 2 deletions commands/project/clone/repo_clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ func NewCmdClone(f *cmdutils.Factory) *cobra.Command {
skipTlsVerify, _ := strconv.ParseBool(tlsVerify)
caCert, _ := cfg.Get(host, "ca_cert")
if caCert != "" {
apiClient, _ = api.InitWithCustomCA(host, token, caCert)
apiClient, _ = api.InitWithCustomCA(host, token, caCert, false)
} else {
apiClient, _ = api.Init(host, token, skipTlsVerify)
apiClient, _ = api.Init(host, token, skipTlsVerify, false)
}

repo := args[0]
Expand Down
29 changes: 28 additions & 1 deletion internal/glinstance/host.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package glinstance

import (
"errors"
"fmt"
"strings"
)
Expand Down Expand Up @@ -56,7 +57,7 @@ func StripHostProtocol(h string) (hostname, protocol string) {
return
}

// APIEndpoint returns the API endpoint prefix for a GitLab instance :)
// APIEndpoint returns the REST API endpoint prefix for a GitLab instance :)
func APIEndpoint(hostname, protocol string) string {
if protocol == "" {
protocol = "https"
Expand All @@ -66,3 +67,29 @@ func APIEndpoint(hostname, protocol string) string {
}
return "https://gitlab.com/api/v4/"
}

// GraphQLEndpoint returns the GraphQL API endpoint prefix for a GitLab instance :)
func GraphQLEndpoint(hostname, protocol string) string {
if protocol == "" {
protocol = "https"
}
if IsSelfHosted(hostname) {
return fmt.Sprintf("%s://%s/api/graphql/", protocol, hostname)
}
return "https://gitlab.com/api/graphql/"
}

func HostnameValidator(v interface{}) error {
hostname, valid := v.(string)
if !valid {
return errors.New("hostname is not a string")
}

if len(strings.TrimSpace(hostname)) < 1 {
return errors.New("a value is required")
}
if strings.ContainsRune(hostname, '/') || strings.ContainsRune(hostname, ':') {
return errors.New("invalid hostname")
}
return nil
}
8 changes: 8 additions & 0 deletions internal/glrepo/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ type Remote struct {
Repo Interface
}

func (r Remote) RepoNamespace() string {
return r.Repo.RepoNamespace()
}

func (r Remote) RepoGroup() string {
return r.Repo.RepoGroup()
}

func (r Remote) FullName() string {
return r.Repo.FullName()
}
Expand Down
44 changes: 39 additions & 5 deletions internal/glrepo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,15 @@ func FullNameFromURL(remoteURL string) (string, error) {
}

// Interface describes an object that represents a GitLab repository
// Contains methods for these methods representing these placeholders for a
// project path with :host/:group/:namespace/:repo
// RepoHost = :host, RepoOwner = :group/:namespace, RepoNamespace = :namespace,
// FullName = :group/:namespace/:repo, RepoGroup = :group, RepoName = :repo
type Interface interface {
RepoName() string
RepoOwner() string
RepoNamespace() string
RepoGroup() string
RepoHost() string
FullName() string
}
Expand All @@ -80,12 +86,19 @@ func New(owner, repo string) Interface {

// NewWithHost is like New with an explicit host name
func NewWithHost(owner, repo, hostname string) Interface {
return &glRepo{
rp := &glRepo{
owner: owner,
name: repo,
fullname: fmt.Sprintf("%s/%s", owner, repo),
hostname: normalizeHostname(hostname),
}
if ri := strings.SplitN(owner, "/", 2); len(ri) == 2 {
rp.group = ri[0]
rp.namespace = ri[1]
} else {
rp.namespace = owner
}
return rp
}

// FromFullName extracts the GitLab repository information from the following
Expand Down Expand Up @@ -140,24 +153,45 @@ func IsSame(a, b Interface) bool {
}

type glRepo struct {
owner string
name string
fullname string
hostname string
group string
owner string
name string
fullname string
hostname string
namespace string
}

// RepoNamespace returns the namespace of the project. Eg. if project path is :group/:namespace:/repo
// RepoNamespace returns the :namespace
func (r glRepo) RepoNamespace() string {
return r.namespace
}

// RepoGroup returns the group namespace of the project. Eg. if project path is :group/:namespace:/repo
// RepoGroup returns the :group
func (r glRepo) RepoGroup() string {
return r.group
}

// RepoOwner returns the group and namespace in the form "group/namespace". Returns "namespace" if group is not present
func (r glRepo) RepoOwner() string {
if r.group != "" {
return r.group + "/" + r.namespace
}
return r.owner
}

// RepoName returns the repo name without the path or namespace.
func (r glRepo) RepoName() string {
return r.name
}

// RepoHost returns the hostname
func (r glRepo) RepoHost() string {
return r.hostname
}

// FullName returns the full project path :group/:namespace/:repo or :namespace/:repo if group is not present
func (r glRepo) FullName() string {
return r.fullname
}
6 changes: 5 additions & 1 deletion internal/utils/iostreams.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func InitIOStream() *IOStreams {
ioStream.IsInTTY = IsTerminal(stdin)
}

_isColorEnabled = isColorEnabled() && stdoutIsTTY
_isColorEnabled = isColorEnabled() && stdoutIsTTY && stderrIsTTY

return ioStream
}
Expand All @@ -61,6 +61,10 @@ func (s *IOStreams) PromptEnabled() bool {
return s.IsInTTY && s.IsaTTY
}

func (s *IOStreams) ColorEnabled() bool {
return isColorEnabled() && s.IsaTTY && s.IsErrTTY
}

func (s *IOStreams) SetPrompt(promptDisabled string) {
if promptDisabled == "true" || promptDisabled == "1" {
s.promptDisabled = true
Expand Down
Loading

0 comments on commit 4465c16

Please sign in to comment.