Skip to content

Commit

Permalink
Merge pull request #1176 from shipwright-io/issue/1131
Browse files Browse the repository at this point in the history
Add custom error for basic auth with HTTP
  • Loading branch information
openshift-merge-robot committed Jan 5, 2023
2 parents 78a29a6 + 11ff2da commit a473610
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 116 deletions.
66 changes: 45 additions & 21 deletions cmd/git/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const (
var useNoTagsFlag = false
var useDepthForSubmodule = false

var displayURL string

// ExitError is an error which has an exit code to be used in os.Exit() to
// return both an exit code and an error message
type ExitError struct {
Expand Down Expand Up @@ -103,8 +105,8 @@ func main() {
exitcode = err.Code
}

if err := writeErrorResults(shpgit.NewErrorResultFromMessage(err.Error())); err != nil {
log.Printf("Could not write error results: %s", err.Error())
if writeErr := writeErrorResults(shpgit.NewErrorResultFromMessage(err.Error())); writeErr != nil {
log.Printf("Could not write error results: %s", writeErr.Error())
}

log.Print(err.Error())
Expand All @@ -127,13 +129,18 @@ func Execute(ctx context.Context) error {
return err
}

checkGitVersionSpecificSettings(ctx)
// Check if Git CLI supports --no-tags for clone
out, _ := git(ctx, "clone", "-h")
useNoTagsFlag = strings.Contains(out, "--no-tags")

if err := runGitClone(ctx); err != nil {
return err
}
// Check if Git CLI support --single-branch and therefore shallow clones using --depth
out, _ = git(ctx, "submodule", "-h")
useDepthForSubmodule = strings.Contains(out, "single-branch")

return nil
// Create clean version of the URL that should be safe to be displayed in logs
displayURL = cleanURL()

return runGitClone(ctx)
}

func runGitClone(ctx context.Context) error {
Expand Down Expand Up @@ -217,16 +224,6 @@ func checkEnvironment(ctx context.Context) error {
return nil
}

func checkGitVersionSpecificSettings(ctx context.Context) {
// Check if Git CLI supports --no-tags for clone
out, _ := git(ctx, "clone", "-h")
useNoTagsFlag = strings.Contains(out, "--no-tags")

// Check if Git CLI support --single-branch and therefore shallow clones using --depth
out, _ = git(ctx, "submodule", "-h")
useDepthForSubmodule = strings.Contains(out, "single-branch")
}

func clone(ctx context.Context) error {
cloneArgs := []string{
"clone",
Expand Down Expand Up @@ -410,7 +407,7 @@ func clone(ctx context.Context) error {
}

log.Printf("Successfully loaded %s (%s) into %s\n",
flagValues.url,
displayURL,
revision,
flagValues.target,
)
Expand All @@ -420,7 +417,9 @@ func clone(ctx context.Context) error {

func git(ctx context.Context, args ...string) (string, error) {
cmd := exec.CommandContext(ctx, "git", args...)
log.Print(cmd.String())

// Print the command to be executed, but replace the URL with a safe version
log.Print(strings.ReplaceAll(cmd.String(), flagValues.url, displayURL))

// Make sure that the spawned process does not try to prompt for infos
os.Setenv("GIT_TERMINAL_PROMPT", "0")
Expand Down Expand Up @@ -488,11 +487,17 @@ func checkCredentials() (credentialType, error) {
// in which case there need to be the files username and password
hasUsername := hasFile(flagValues.secretPath, "username")
hasPassword := hasFile(flagValues.secretPath, "password")
isHTTPSURL := strings.HasPrefix(flagValues.url, "https")
switch {
case hasUsername && hasPassword && isHTTPSURL:
case hasUsername && hasPassword && strings.HasPrefix(flagValues.url, "https://"):
return typeUsernamePassword, nil

case hasUsername && hasPassword && strings.HasPrefix(flagValues.url, "http://"):
return typeUndef, &ExitError{
Code: 110,
Message: shpgit.AuthUnexpectedHTTP.ToMessage(),
Reason: shpgit.AuthUnexpectedHTTP,
}

case hasUsername && !hasPassword || !hasUsername && hasPassword:
return typeUndef, &ExitError{
Code: 110,
Expand Down Expand Up @@ -530,3 +535,22 @@ func writeErrorResults(failure *shpgit.ErrorResult) (err error) {

return nil
}

func cleanURL() string {
// non HTTP/HTTPS URLs are returned as-is (i.e. Git+SSH URLs)
if !strings.HasPrefix(flagValues.url, "http") {
return flagValues.url
}

// return redacted version of the URL if it is a parsable URL
if repoURL, err := url.Parse(flagValues.url); err == nil {
if repoURL.User != nil {
log.Println("URL has inline credentials, which need to be redacted for log out. If possible, use an alternative approach.")
}

return repoURL.Redacted()
}

// in any case, as a fallback, return it as-is
return flagValues.url
}
51 changes: 51 additions & 0 deletions cmd/git/main_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,64 @@
package main_test

import (
"fmt"
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/types"

. "github.com/shipwright-io/build/cmd/git"
shpgit "github.com/shipwright-io/build/pkg/git"
)

func TestGitCmd(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Git Command Suite")
}

type errorClassMatcher struct{ expected shpgit.ErrorClass }

func FailWith(expected shpgit.ErrorClass) types.GomegaMatcher {
return &errorClassMatcher{expected: expected}
}

func (m *errorClassMatcher) Match(actual interface{}) (success bool, err error) {
if actual == nil {
return false, nil
}

switch obj := actual.(type) {
case *ExitError:
return obj.Reason == m.expected, nil

case shpgit.ErrorClass:
return obj == m.expected, nil

default:
return false, fmt.Errorf("type mismatch: %T", actual)
}
}

func (m *errorClassMatcher) asStrings(actual interface{}) (string, string) {
switch obj := actual.(type) {
case *ExitError:
return obj.Reason.String(), m.expected.String()

case shpgit.ErrorClass:
return obj.String(), m.expected.String()

default:
return fmt.Sprintf("%v", obj), m.expected.String()
}
}

func (m *errorClassMatcher) FailureMessage(actual interface{}) string {
act, exp := m.asStrings(actual)
return fmt.Sprintf("\nExpected\n\t%s\nto equal\n\t%s", act, exp)
}

func (m *errorClassMatcher) NegatedFailureMessage(actual interface{}) string {
act, exp := m.asStrings(actual)
return fmt.Sprintf("\nExpected\n\t%s\nto not equal\n\t%s", act, exp)
}

0 comments on commit a473610

Please sign in to comment.