Skip to content

Instantly share code, notes, and snippets.

@ildarusmanov
Created June 29, 2019 12:36
Show Gist options
  • Save ildarusmanov/d0a402301d96c61e59e902c54e3ec412 to your computer and use it in GitHub Desktop.
Save ildarusmanov/d0a402301d96c61e59e902c54e3ec412 to your computer and use it in GitHub Desktop.
package resource
import (
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
// Git interface for testing purposes.
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o fakes/fake_git.go . Git
type Git interface {
Init(string) error
Pull(string, string, int) error
RevParse(string) (string, error)
Fetch(string, int, int) error
Checkout(string, string) error
Merge(string) error
Rebase(string, string) error
GitCryptUnlock(string) error
}
// NewGitClient ...
func NewGitClient(source *Source, dir string, output io.Writer) (*GitClient, error) {
if source.SkipSSLVerification {
os.Setenv("GIT_SSL_NO_VERIFY", "true")
}
return &GitClient{
AccessToken: source.AccessToken,
Directory: dir,
Output: output,
}, nil
}
// GitClient ...
type GitClient struct {
AccessToken string
Directory string
Output io.Writer
}
func (g *GitClient) command(name string, arg ...string) *exec.Cmd {
cmd := exec.Command(name, arg...)
cmd.Dir = g.Directory
cmd.Stdout = g.Output
cmd.Stderr = g.Output
return cmd
}
// Init ...
func (g *GitClient) Init(branch string) error {
if err := g.command("git", "init").Run(); err != nil {
return fmt.Errorf("init failed: %s", err)
}
if err := g.command("git", "checkout", "-b", branch).Run(); err != nil {
return fmt.Errorf("checkout to '%s' failed: %s", branch, err)
}
if err := g.command("git", "config", "user.name", "concourse-ci").Run(); err != nil {
return fmt.Errorf("failed to configure git user: %s", err)
}
if err := g.command("git", "config", "user.email", "concourse@local").Run(); err != nil {
return fmt.Errorf("failed to configure git email: %s", err)
}
if err := g.command("git", "config", "--global", "url.'https://"+g.AccessToken+":[email protected]/'.insteadOf", "'[email protected]:'") {
return fmt.Errorf("failed to configure github url: %s", err)
}
return nil
}
// Pull ...
func (g *GitClient) Pull(uri, branch string, depth int) error {
endpoint, err := g.Endpoint(uri)
if err != nil {
return err
}
args := []string{"pull", endpoint + ".git", branch}
if depth > 0 {
args = append(args, "--depth", strconv.Itoa(depth))
}
cmd := g.command("git", args...)
// Discard output to have zero chance of logging the access token.
cmd.Stdout = ioutil.Discard
cmd.Stderr = ioutil.Discard
if err := cmd.Run(); err != nil {
return fmt.Errorf("clone failed: %s", err)
}
return nil
}
// RevParse retrieves the SHA of the given branch.
func (g *GitClient) RevParse(branch string) (string, error) {
cmd := exec.Command("git", "rev-parse", "--verify", branch)
cmd.Dir = g.Directory
sha, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("rev-parse '%s' failed: %s: %s", branch, err, string(sha))
}
return strings.TrimSpace(string(sha)), nil
}
// Fetch ...
func (g *GitClient) Fetch(uri string, prNumber int, depth int) error {
endpoint, err := g.Endpoint(uri)
if err != nil {
return err
}
args := []string{"fetch", endpoint, fmt.Sprintf("pull/%s/head", strconv.Itoa(prNumber))}
if depth > 0 {
args = append(args, "--depth", strconv.Itoa(depth))
}
cmd := g.command("git", args...)
// Discard output to have zero chance of logging the access token.
cmd.Stdout = ioutil.Discard
cmd.Stderr = ioutil.Discard
if err := cmd.Run(); err != nil {
return fmt.Errorf("fetch failed: %s", err)
}
return nil
}
// CheckOut
func (g *GitClient) Checkout(branch, sha string) error {
if err := g.command("git", "checkout", "-b", branch, sha).Run(); err != nil {
return fmt.Errorf("checkout failed: %s", err)
}
return nil
}
// Merge ...
func (g *GitClient) Merge(sha string) error {
if err := g.command("git", "merge", sha, "--no-stat").Run(); err != nil {
return fmt.Errorf("merge failed: %s", err)
}
return nil
}
// Rebase ...
func (g *GitClient) Rebase(baseRef string, headSha string) error {
if err := g.command("git", "rebase", baseRef, headSha).Run(); err != nil {
return fmt.Errorf("rebase failed: %s", err)
}
return nil
}
// GitCryptUnlock unlocks the repository using git-crypt
func (g *GitClient) GitCryptUnlock(base64key string) error {
keyDir, err := ioutil.TempDir("", "")
if err != nil {
return fmt.Errorf("failed to create temporary directory")
}
defer os.RemoveAll(keyDir)
decodedKey, err := base64.StdEncoding.DecodeString(base64key)
if err != nil {
return fmt.Errorf("failed to decode git-crypt key")
}
keyPath := filepath.Join(keyDir, "git-crypt-key")
if err := ioutil.WriteFile(keyPath, decodedKey, 600); err != nil {
return fmt.Errorf("failed to write git-crypt key to file: %s", err)
}
if err := g.command("git-crypt", "unlock", keyPath).Run(); err != nil {
return fmt.Errorf("git-crypt unlock failed: %s", err)
}
return nil
}
// Endpoint takes an uri and produces an endpoint with the login information baked in.
func (g *GitClient) Endpoint(uri string) (string, error) {
endpoint, err := url.Parse(uri)
if err != nil {
return "", fmt.Errorf("failed to parse commit url: %s", err)
}
endpoint.User = url.UserPassword("x-oauth-basic", g.AccessToken)
return endpoint.String(), nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment