From d35a7c0789945640639f50c94de6368f71ee8406 Mon Sep 17 00:00:00 2001 From: Mohamed Habib Date: Fri, 1 Nov 2024 12:36:13 +0000 Subject: [PATCH] use git shell (#1791) --- backend/utils/github.go | 22 +------ backend/utils/github_test.go | 11 ++++ backend/utils/gitshell.go | 122 +++++++++++++++++++++++++++++++++++ ee/cli/pkg/utils/github.go | 23 +------ 4 files changed, 139 insertions(+), 39 deletions(-) create mode 100644 backend/utils/gitshell.go diff --git a/backend/utils/github.go b/backend/utils/github.go index 19c2fe153..8e8807773 100644 --- a/backend/utils/github.go +++ b/backend/utils/github.go @@ -15,9 +15,6 @@ import ( "github.com/diggerhq/digger/libs/ci" github2 "github.com/diggerhq/digger/libs/ci/github" "github.com/diggerhq/digger/libs/scheduler" - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/google/go-github/v61/github" ) @@ -33,25 +30,12 @@ type action func(string) error func CloneGitRepoAndDoAction(repoUrl string, branch string, token string, action action) error { dir := createTempDir() - cloneOptions := git.CloneOptions{ - URL: repoUrl, - ReferenceName: plumbing.NewBranchReferenceName(branch), - Depth: 1, - SingleBranch: true, - } - - if token != "" { - cloneOptions.Auth = &http.BasicAuth{ - Username: "x-access-token", // anything except an empty string - Password: token, - } - } - - _, err := git.PlainClone(dir, false, &cloneOptions) + git := NewGitShellWithTokenAuth(dir, token) + err := git.Clone(repoUrl, branch) if err != nil { - log.Printf("PlainClone error: %v\n", err) return err } + defer func() { log.Printf("removing cloned directory %v", dir) ferr := os.RemoveAll(dir) diff --git a/backend/utils/github_test.go b/backend/utils/github_test.go index e369c3cdb..6e35a2466 100644 --- a/backend/utils/github_test.go +++ b/backend/utils/github_test.go @@ -25,6 +25,17 @@ func TestGithubCloneWithPublicRepoThrowsNoError(t *testing.T) { assert.Nil(t, err) } +func TestGithubCloneWithPrivateRepoAndValidTokenThrowsNoError(t *testing.T) { + token := os.Getenv("GITHUB_PAT_TOKEN") + if token == "" { + t.Skip() + return + } + f := func(d string) error { return nil } + err := CloneGitRepoAndDoAction("https://github.com/diggerhq/infra-gcp", "main", token, f) + assert.Nil(t, err) +} + func TestGithubCloneWithInvalidBranchThrowsError(t *testing.T) { token := os.Getenv("GITHUB_PAT_TOKEN") f := func(d string) error { return nil } diff --git a/backend/utils/gitshell.go b/backend/utils/gitshell.go new file mode 100644 index 000000000..9968f52ff --- /dev/null +++ b/backend/utils/gitshell.go @@ -0,0 +1,122 @@ +package utils + +import ( + "bytes" + "context" + "fmt" + "net/url" + "os" + "os/exec" + "strings" + "time" +) + +type GitAuth struct { + Username string + Password string // Can be either password or access token + Token string // x-access-token +} + +type GitShell struct { + workDir string + timeout time.Duration + environment []string + auth *GitAuth +} + +func NewGitShell(workDir string, auth *GitAuth) *GitShell { + env := os.Environ() + + // If authentication is provided, set up credential helper + if auth != nil { + // Add credential helper to avoid interactive password prompts + env = append(env, "GIT_TERMINAL_PROMPT=0") + } + + return &GitShell{ + workDir: workDir, + timeout: 30 * time.Second, + environment: env, + auth: auth, + } +} + +func NewGitShellWithTokenAuth(workDir string, token string) *GitShell { + auth := GitAuth{ + Username: "x-access-token", + Password: "", + Token: token, + } + return NewGitShell(workDir, &auth) +} + +// formatAuthURL injects credentials into the Git URL +func (g *GitShell) formatAuthURL(repoURL string) (string, error) { + if g.auth == nil { + return repoURL, nil + } + + parsedURL, err := url.Parse(repoURL) + if err != nil { + return "", fmt.Errorf("invalid URL: %v", err) + } + + // Handle different auth types + if g.auth.Token != "" { + // X-Access-Token authentication + parsedURL.User = url.UserPassword("x-access-token", g.auth.Token) + } else if g.auth.Username != "" { + // Username/password or personal access token + parsedURL.User = url.UserPassword(g.auth.Username, g.auth.Password) + } + + return parsedURL.String(), nil +} + +func (g *GitShell) runCommand(args ...string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), g.timeout) + defer cancel() + + cmd := exec.CommandContext(ctx, "git", args...) + cmd.Dir = g.workDir + cmd.Env = g.environment + + // Set up credential helper for HTTPS + if g.auth != nil { + cmd.Env = append(cmd.Env, "GIT_ASKPASS=echo") + if g.auth.Token != "" { + cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_ACCESS_TOKEN=%s", g.auth.Token)) + } + } + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + if err != nil { + if stderr.Len() > 0 { + return "", fmt.Errorf("git command failed: %v: %s", err, stderr.String()) + } + return "", err + } + return strings.TrimSpace(stdout.String()), nil +} + +// Clone with authentication +func (g *GitShell) Clone(repoURL, branch string) error { + authURL, err := g.formatAuthURL(repoURL) + if err != nil { + return err + } + + args := []string{"clone"} + if branch != "" { + args = append(args, "-b", branch) + } + args = append(args, "--depth", "1") + args = append(args, "--single-branch", authURL, g.workDir) + + _, err = g.runCommand(args...) + return err +} diff --git a/ee/cli/pkg/utils/github.go b/ee/cli/pkg/utils/github.go index 782dacc23..e977c81ef 100644 --- a/ee/cli/pkg/utils/github.go +++ b/ee/cli/pkg/utils/github.go @@ -1,9 +1,7 @@ package utils import ( - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/diggerhq/digger/backend/utils" "log" "os" ) @@ -20,27 +18,12 @@ type action func(string) error func CloneGitRepoAndDoAction(repoUrl string, branch string, token string, action action) error { dir := createTempDir() - cloneOptions := git.CloneOptions{ - URL: repoUrl, - ReferenceName: plumbing.NewBranchReferenceName(branch), - Depth: 1, - SingleBranch: true, - } - - if token != "" { - cloneOptions.Auth = &http.BasicAuth{ - Username: "x-access-token", // anything except an empty string - Password: token, - } - } - - _, err := git.PlainClone(dir, false, &cloneOptions) + git := utils.NewGitShellWithTokenAuth(dir, token) + err := git.Clone(repoUrl, branch) if err != nil { - log.Printf("PlainClone error: %v\n", err) return err } defer os.RemoveAll(dir) - err = action(dir) if err != nil { log.Printf("error performing action: %v", err)