mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-27 02:33:48 +08:00
Git: Minor fixes. Refactor. Added tests.
This commit is contained in:
parent
d311345aa5
commit
f44cd5d740
|
@ -96,6 +96,8 @@ func gitParse(c *Controller) (*git.Repo, error) {
|
|||
return nil, c.ArgErr()
|
||||
}
|
||||
repo.Then = strings.Join(thenArgs, " ")
|
||||
default:
|
||||
return nil, c.ArgErr()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -124,8 +126,8 @@ func gitParse(c *Controller) (*git.Repo, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// validate git availability in PATH
|
||||
if err = git.InitGit(); err != nil {
|
||||
// validate git requirements
|
||||
if err = git.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -153,19 +155,39 @@ func sanitizeHttp(repoUrl string) (string, string, error) {
|
|||
}
|
||||
|
||||
repoUrl = "https://" + url.Host + url.Path
|
||||
|
||||
// add .git suffix if missing
|
||||
if !strings.HasSuffix(repoUrl, ".git") {
|
||||
repoUrl += ".git"
|
||||
}
|
||||
|
||||
return repoUrl, url.Host, nil
|
||||
}
|
||||
|
||||
// sanitizeGit cleans up repository url and validate ssh format.
|
||||
// sanitizeGit cleans up repository url and converts to ssh format for private
|
||||
// repositories if required.
|
||||
// Returns sanitized url, hostName (e.g. github.com, bitbucket.com)
|
||||
// and possible error
|
||||
func sanitizeGit(repoUrl string) (string, string, error) {
|
||||
repoUrl = strings.TrimSpace(repoUrl)
|
||||
|
||||
// check if valid ssh format
|
||||
if !strings.HasPrefix(repoUrl, "git@") || strings.Index(repoUrl, ":") < len("git@a:") {
|
||||
return "", "", fmt.Errorf("Invalid git url %s", repoUrl)
|
||||
// check if valid http format and convert to ssh
|
||||
if url, err := url.Parse(repoUrl); err == nil && strings.HasPrefix(url.Scheme, "http") {
|
||||
repoUrl = fmt.Sprintf("git@%v:%v", url.Host, url.Path[1:])
|
||||
} else {
|
||||
return "", "", fmt.Errorf("Invalid git url %s", repoUrl)
|
||||
}
|
||||
}
|
||||
hostUrl := repoUrl[len("git@"):]
|
||||
i := strings.Index(hostUrl, ":")
|
||||
host := hostUrl[:i]
|
||||
|
||||
// add .git suffix if missing
|
||||
if !strings.HasSuffix(repoUrl, ".git") {
|
||||
repoUrl += ".git"
|
||||
}
|
||||
|
||||
return repoUrl, host, nil
|
||||
}
|
||||
|
|
144
config/setup/git_test.go
Normal file
144
config/setup/git_test.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
package setup
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/caddy/middleware/git"
|
||||
"github.com/mholt/caddy/middleware/git/gittest"
|
||||
)
|
||||
|
||||
// init sets the OS used to fakeOS
|
||||
func init() {
|
||||
git.SetOS(gittest.FakeOS)
|
||||
}
|
||||
|
||||
func TestGit(t *testing.T) {
|
||||
c := newTestController(`git git@github.com:mholt/caddy.git`)
|
||||
|
||||
mid, err := Git(c)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no errors, but got: %v", err)
|
||||
}
|
||||
if mid != nil {
|
||||
t.Fatal("Git middleware is a background service and expected to be nil.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
shouldErr bool
|
||||
expected *git.Repo
|
||||
}{
|
||||
{`git git@github.com:user/repo`, false, &git.Repo{
|
||||
Url: "https://github.com/user/repo.git",
|
||||
}},
|
||||
{`git github.com/user/repo`, false, &git.Repo{
|
||||
Url: "https://github.com/user/repo.git",
|
||||
}},
|
||||
{`git git@github.com/user/repo`, true, nil},
|
||||
{`git http://github.com/user/repo`, false, &git.Repo{
|
||||
Url: "https://github.com/user/repo.git",
|
||||
}},
|
||||
{`git https://github.com/user/repo`, false, &git.Repo{
|
||||
Url: "https://github.com/user/repo.git",
|
||||
}},
|
||||
{`git http://github.com/user/repo {
|
||||
key ~/.key
|
||||
}`, false, &git.Repo{
|
||||
KeyPath: "~/.key",
|
||||
Url: "git@github.com:user/repo.git",
|
||||
}},
|
||||
{`git git@github.com:user/repo {
|
||||
key ~/.key
|
||||
}`, false, &git.Repo{
|
||||
KeyPath: "~/.key",
|
||||
Url: "git@github.com:user/repo.git",
|
||||
}},
|
||||
{`git `, true, nil},
|
||||
{`git {
|
||||
}`, true, nil},
|
||||
{`git {
|
||||
repo git@github.com:user/repo.git`, true, nil},
|
||||
{`git {
|
||||
repo git@github.com:user/repo
|
||||
key ~/.key
|
||||
}`, false, &git.Repo{
|
||||
KeyPath: "~/.key",
|
||||
Url: "git@github.com:user/repo.git",
|
||||
}},
|
||||
{`git {
|
||||
repo git@github.com:user/repo
|
||||
key ~/.key
|
||||
interval 600
|
||||
}`, false, &git.Repo{
|
||||
KeyPath: "~/.key",
|
||||
Url: "git@github.com:user/repo.git",
|
||||
Interval: time.Second * 600,
|
||||
}},
|
||||
{`git {
|
||||
repo git@github.com:user/repo
|
||||
branch dev
|
||||
}`, false, &git.Repo{
|
||||
Branch: "dev",
|
||||
Url: "https://github.com/user/repo.git",
|
||||
}},
|
||||
{`git {
|
||||
key ~/.key
|
||||
}`, true, nil},
|
||||
{`git {
|
||||
repo git@github.com:user/repo
|
||||
key ~/.key
|
||||
then echo hello world
|
||||
}`, false, &git.Repo{
|
||||
KeyPath: "~/.key",
|
||||
Url: "git@github.com:user/repo.git",
|
||||
Then: "echo hello world",
|
||||
}},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
c := newTestController(test.input)
|
||||
repo, err := gitParse(c)
|
||||
if !test.shouldErr && err != nil {
|
||||
t.Errorf("Test %v should not error but found %v", i, err)
|
||||
continue
|
||||
}
|
||||
if test.shouldErr && err == nil {
|
||||
t.Errorf("Test %v should error but found nil", i)
|
||||
continue
|
||||
}
|
||||
if !reposEqual(test.expected, repo) {
|
||||
t.Errorf("Test %v expects %v but found %v", i, test.expected, repo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reposEqual(expected, repo *git.Repo) bool {
|
||||
if expected == nil {
|
||||
return repo == nil
|
||||
}
|
||||
if expected.Branch != "" && expected.Branch != repo.Branch {
|
||||
return false
|
||||
}
|
||||
if expected.Host != "" && expected.Host != repo.Host {
|
||||
return false
|
||||
}
|
||||
if expected.Interval != 0 && expected.Interval != repo.Interval {
|
||||
return false
|
||||
}
|
||||
if expected.KeyPath != "" && expected.KeyPath != repo.KeyPath {
|
||||
return false
|
||||
}
|
||||
if expected.Path != "" && expected.Path != repo.Path {
|
||||
return false
|
||||
}
|
||||
if expected.Then != "" && expected.Then != repo.Then {
|
||||
return false
|
||||
}
|
||||
if expected.Url != "" && expected.Url != repo.Url {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -3,15 +3,14 @@ package git
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/caddy/middleware"
|
||||
"github.com/mholt/caddy/middleware/git/gitos"
|
||||
)
|
||||
|
||||
// DefaultInterval is the minimum interval to delay before
|
||||
|
@ -24,8 +23,11 @@ const numRetries = 3
|
|||
// gitBinary holds the absolute path to git executable
|
||||
var gitBinary string
|
||||
|
||||
// shell holds the shell to be used. Either sh or bash.
|
||||
var shell string
|
||||
|
||||
// initMutex prevents parallel attempt to validate
|
||||
// git availability in PATH
|
||||
// git requirements.
|
||||
var initMutex sync.Mutex = sync.Mutex{}
|
||||
|
||||
// Logger is used to log errors; if nil, the default log.Logger is used.
|
||||
|
@ -120,20 +122,20 @@ func (r *Repo) pull() error {
|
|||
// pullWithKey is used for private repositories and requires an ssh key.
|
||||
// Note: currently only limited to Linux and OSX.
|
||||
func (r *Repo) pullWithKey(params []string) error {
|
||||
var gitSsh, script *os.File
|
||||
var gitSsh, script gitos.File
|
||||
// ensure temporary files deleted after usage
|
||||
defer func() {
|
||||
if gitSsh != nil {
|
||||
os.Remove(gitSsh.Name())
|
||||
gos.Remove(gitSsh.Name())
|
||||
}
|
||||
if script != nil {
|
||||
os.Remove(script.Name())
|
||||
gos.Remove(script.Name())
|
||||
}
|
||||
}()
|
||||
|
||||
var err error
|
||||
// write git.sh script to temp file
|
||||
gitSsh, err = writeScriptFile(gitWrapperScript(gitBinary))
|
||||
gitSsh, err = writeScriptFile(gitWrapperScript())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -163,9 +165,9 @@ func (r *Repo) pullWithKey(params []string) error {
|
|||
func (r *Repo) Prepare() error {
|
||||
// check if directory exists or is empty
|
||||
// if not, create directory
|
||||
fs, err := ioutil.ReadDir(r.Path)
|
||||
fs, err := gos.ReadDir(r.Path)
|
||||
if err != nil || len(fs) == 0 {
|
||||
return os.MkdirAll(r.Path, os.FileMode(0755))
|
||||
return gos.MkdirAll(r.Path, os.FileMode(0755))
|
||||
}
|
||||
|
||||
// validate git repo
|
||||
|
@ -180,9 +182,15 @@ func (r *Repo) Prepare() error {
|
|||
if isGit {
|
||||
// check if same repository
|
||||
var repoUrl string
|
||||
if repoUrl, err = r.getRepoUrl(); err == nil && repoUrl == r.Url {
|
||||
r.pulled = true
|
||||
return nil
|
||||
if repoUrl, err = r.getRepoUrl(); err == nil {
|
||||
// add .git suffix if missing for adequate comparison.
|
||||
if !strings.HasSuffix(repoUrl, ".git") {
|
||||
repoUrl += ".git"
|
||||
}
|
||||
if repoUrl == r.Url {
|
||||
r.pulled = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot retrieve repo url for %v Error: %v", r.Path, err)
|
||||
|
@ -205,7 +213,7 @@ func (r *Repo) getMostRecentCommit() (string, error) {
|
|||
|
||||
// getRepoUrl retrieves remote origin url for the git repository at path
|
||||
func (r *Repo) getRepoUrl() (string, error) {
|
||||
_, err := os.Stat(r.Path)
|
||||
_, err := gos.Stat(r.Path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -230,9 +238,9 @@ func (r *Repo) postPullCommand() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// InitGit validates git installation and locates the git executable
|
||||
// binary in PATH
|
||||
func InitGit() error {
|
||||
// Init validates git installation, locates the git executable
|
||||
// binary in PATH and check for available shell to use.
|
||||
func Init() error {
|
||||
// prevent concurrent call
|
||||
initMutex.Lock()
|
||||
defer initMutex.Unlock()
|
||||
|
@ -245,18 +253,30 @@ func InitGit() error {
|
|||
|
||||
// locate git binary in path
|
||||
var err error
|
||||
gitBinary, err = exec.LookPath("git")
|
||||
return err
|
||||
if gitBinary, err = gos.LookPath("git"); err != nil {
|
||||
return fmt.Errorf("Git middleware requires git installed. Cannot find git binary in PATH")
|
||||
}
|
||||
|
||||
// locate bash in PATH. If not found, fallback to sh.
|
||||
// If neither is found, return error.
|
||||
shell = "bash"
|
||||
if _, err = gos.LookPath("bash"); err != nil {
|
||||
shell = "sh"
|
||||
if _, err = gos.LookPath("sh"); err != nil {
|
||||
return fmt.Errorf("Git middleware requires either bash or sh.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// runCmd is a helper function to run commands.
|
||||
// It runs command with args from directory at dir.
|
||||
// The executed process outputs to os.Stderr
|
||||
func runCmd(command string, args []string, dir string) error {
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Dir = dir
|
||||
cmd := gos.Command(command, args...)
|
||||
cmd.Stdout(os.Stderr)
|
||||
cmd.Stderr(os.Stderr)
|
||||
cmd.Dir(dir)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -267,8 +287,8 @@ func runCmd(command string, args []string, dir string) error {
|
|||
// It runs command with args from directory at dir.
|
||||
// If successful, returns output and nil error
|
||||
func runCmdOutput(command string, args []string, dir string) (string, error) {
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Dir = dir
|
||||
cmd := gos.Command(command, args...)
|
||||
cmd.Dir(dir)
|
||||
var err error
|
||||
if output, err := cmd.Output(); err == nil {
|
||||
return string(bytes.TrimSpace(output)), nil
|
||||
|
@ -279,8 +299,8 @@ func runCmdOutput(command string, args []string, dir string) (string, error) {
|
|||
// writeScriptFile writes content to a temporary file.
|
||||
// It changes the temporary file mode to executable and
|
||||
// closes it to prepare it for execution.
|
||||
func writeScriptFile(content []byte) (file *os.File, err error) {
|
||||
if file, err = ioutil.TempFile("", "caddy"); err != nil {
|
||||
func writeScriptFile(content []byte) (file gitos.File, err error) {
|
||||
if file, err = gos.TempFile("", "caddy"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = file.Write(content); err != nil {
|
||||
|
@ -293,8 +313,8 @@ func writeScriptFile(content []byte) (file *os.File, err error) {
|
|||
}
|
||||
|
||||
// gitWrapperScript forms content for git.sh script
|
||||
var gitWrapperScript = func(gitBinary string) []byte {
|
||||
return []byte(fmt.Sprintf(`#!/bin/bash
|
||||
func gitWrapperScript() []byte {
|
||||
return []byte(fmt.Sprintf(`#!/bin/%v
|
||||
|
||||
# The MIT License (MIT)
|
||||
# Copyright (c) 2013 Alvin Abad
|
||||
|
@ -323,17 +343,17 @@ fi
|
|||
# Run the git command
|
||||
%v "$@"
|
||||
|
||||
`, gitBinary))
|
||||
`, shell, gitBinary))
|
||||
}
|
||||
|
||||
// bashScript forms content of bash script to clone or update a repo using ssh
|
||||
var bashScript = func(gitShPath string, repo *Repo, params []string) []byte {
|
||||
return []byte(fmt.Sprintf(`#!/bin/bash
|
||||
func bashScript(gitShPath string, repo *Repo, params []string) []byte {
|
||||
return []byte(fmt.Sprintf(`#!/bin/%v
|
||||
|
||||
mkdir -p ~/.ssh;
|
||||
touch ~/.ssh/known_hosts;
|
||||
ssh-keyscan -t rsa,dsa %v 2>&1 | sort -u - ~/.ssh/known_hosts > ~/.ssh/tmp_hosts;
|
||||
cat ~/.ssh/tmp_hosts >> ~/.ssh/known_hosts;
|
||||
%v -i %v %v;
|
||||
`, repo.Host, gitShPath, repo.KeyPath, strings.Join(params, " ")))
|
||||
`, shell, repo.Host, gitShPath, repo.KeyPath, strings.Join(params, " ")))
|
||||
}
|
||||
|
|
218
middleware/git/git_test.go
Normal file
218
middleware/git/git_test.go
Normal file
|
@ -0,0 +1,218 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/caddy/middleware/git/gittest"
|
||||
)
|
||||
|
||||
// init sets the OS used to fakeOS.
|
||||
func init() {
|
||||
SetOS(gittest.FakeOS)
|
||||
}
|
||||
|
||||
func check(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("Error not expected but found %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
err := Init()
|
||||
check(t, err)
|
||||
}
|
||||
|
||||
func TestHelpers(t *testing.T) {
|
||||
f, err := writeScriptFile([]byte("script"))
|
||||
check(t, err)
|
||||
var b [6]byte
|
||||
_, err = f.Read(b[:])
|
||||
check(t, err)
|
||||
if string(b[:]) != "script" {
|
||||
t.Errorf("Expected script found %v", string(b[:]))
|
||||
}
|
||||
|
||||
out, err := runCmdOutput(gitBinary, []string{"-version"}, "")
|
||||
check(t, err)
|
||||
if out != gittest.CmdOutput {
|
||||
t.Errorf("Expected %v found %v", gittest.CmdOutput, out)
|
||||
}
|
||||
|
||||
err = runCmd(gitBinary, []string{"-version"}, "")
|
||||
check(t, err)
|
||||
|
||||
wScript := gitWrapperScript()
|
||||
if string(wScript) != expectedWrapperScript {
|
||||
t.Errorf("Expected %v found %v", expectedWrapperScript, string(wScript))
|
||||
}
|
||||
|
||||
f, err = writeScriptFile(wScript)
|
||||
check(t, err)
|
||||
|
||||
repo := &Repo{Host: "github.com", KeyPath: "~/.key"}
|
||||
script := string(bashScript(f.Name(), repo, []string{"clone", "git@github.com/repo/user"}))
|
||||
if script != expectedBashScript {
|
||||
t.Errorf("Expected %v found %v", expectedBashScript, script)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGit(t *testing.T) {
|
||||
// prepare
|
||||
repos := []*Repo{
|
||||
nil,
|
||||
&Repo{Path: "gitdir", Url: "success.git"},
|
||||
}
|
||||
for _, r := range repos {
|
||||
repo := createRepo(r)
|
||||
err := repo.Prepare()
|
||||
check(t, err)
|
||||
}
|
||||
|
||||
// pull with success
|
||||
logFile := gittest.Open("file")
|
||||
Logger = log.New(logFile, "", 0)
|
||||
tests := []struct {
|
||||
repo *Repo
|
||||
output string
|
||||
}{
|
||||
{
|
||||
&Repo{Path: "gitdir", Url: "git@github.com:user/repo.git", KeyPath: "~/.key", Then: "echo Hello"},
|
||||
`git@github.com:user/repo.git pulled.
|
||||
Command echo Hello successful.
|
||||
`,
|
||||
},
|
||||
{
|
||||
&Repo{Path: "gitdir", Url: "https://github.com/user/repo.git", Then: "echo Hello"},
|
||||
`https://github.com/user/repo.git pulled.
|
||||
Command echo Hello successful.
|
||||
`,
|
||||
},
|
||||
{
|
||||
&Repo{Url: "git@github.com:user/repo"},
|
||||
`git@github.com:user/repo pulled.
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
gittest.CmdOutput = test.repo.Url
|
||||
|
||||
test.repo = createRepo(test.repo)
|
||||
|
||||
err := test.repo.Prepare()
|
||||
check(t, err)
|
||||
|
||||
err = test.repo.Pull()
|
||||
check(t, err)
|
||||
|
||||
out, err := ioutil.ReadAll(logFile)
|
||||
check(t, err)
|
||||
if test.output != string(out) {
|
||||
t.Errorf("Pull with Success %v: Expected %v found %v", i, test.output, string(out))
|
||||
}
|
||||
}
|
||||
|
||||
// pull with error
|
||||
repos = []*Repo{
|
||||
&Repo{Path: "gitdir", Url: "http://github.com:u/repo.git"},
|
||||
&Repo{Path: "gitdir", Url: "https://github.com/user/repo.git", Then: "echo Hello"},
|
||||
&Repo{Path: "gitdir"},
|
||||
&Repo{Path: "gitdir", KeyPath: ".key"},
|
||||
}
|
||||
|
||||
gittest.CmdOutput = "git@github.com:u1/repo.git"
|
||||
for i, repo := range repos {
|
||||
repo = createRepo(repo)
|
||||
|
||||
err := repo.Prepare()
|
||||
if err == nil {
|
||||
t.Errorf("Pull with Error %v: Error expected but not found %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
expected := "Another git repo 'git@github.com:u1/repo.git' exists at gitdir"
|
||||
if expected != err.Error() {
|
||||
t.Errorf("Pull with Error %v: Expected %v found %v", i, expected, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func createRepo(r *Repo) *Repo {
|
||||
repo := &Repo{
|
||||
Url: "git@github.com/user/test",
|
||||
Path: ".",
|
||||
Host: "github.com",
|
||||
Branch: "master",
|
||||
Interval: time.Second * 60,
|
||||
}
|
||||
if r == nil {
|
||||
return repo
|
||||
}
|
||||
if r.Branch != "" {
|
||||
repo.Branch = r.Branch
|
||||
}
|
||||
if r.Host != "" {
|
||||
repo.Branch = r.Branch
|
||||
}
|
||||
if r.Interval != 0 {
|
||||
repo.Interval = r.Interval
|
||||
}
|
||||
if r.KeyPath != "" {
|
||||
repo.KeyPath = r.KeyPath
|
||||
}
|
||||
if r.Path != "" {
|
||||
repo.Path = r.Path
|
||||
}
|
||||
if r.Then != "" {
|
||||
repo.Then = r.Then
|
||||
}
|
||||
if r.Url != "" {
|
||||
repo.Url = r.Url
|
||||
}
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
var expectedBashScript = `#!/bin/bash
|
||||
|
||||
mkdir -p ~/.ssh;
|
||||
touch ~/.ssh/known_hosts;
|
||||
ssh-keyscan -t rsa,dsa github.com 2>&1 | sort -u - ~/.ssh/known_hosts > ~/.ssh/tmp_hosts;
|
||||
cat ~/.ssh/tmp_hosts >> ~/.ssh/known_hosts;
|
||||
` + gittest.TempFileName + ` -i ~/.key clone git@github.com/repo/user;
|
||||
`
|
||||
|
||||
var expectedWrapperScript = `#!/bin/bash
|
||||
|
||||
# The MIT License (MIT)
|
||||
# Copyright (c) 2013 Alvin Abad
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Git wrapper script that can specify an ssh-key file
|
||||
Usage:
|
||||
git.sh -i ssh-key-file git-command
|
||||
"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# remove temporary file on exit
|
||||
trap 'rm -f /tmp/.git_ssh.$$' 0
|
||||
|
||||
if [ "$1" = "-i" ]; then
|
||||
SSH_KEY=$2; shift; shift
|
||||
echo "ssh -i $SSH_KEY \$@" > /tmp/.git_ssh.$$
|
||||
chmod +x /tmp/.git_ssh.$$
|
||||
export GIT_SSH=/tmp/.git_ssh.$$
|
||||
fi
|
||||
|
||||
# in case the git command is repeated
|
||||
[ "$1" = "git" ] && shift
|
||||
|
||||
# Run the git command
|
||||
/usr/bin/git "$@"
|
||||
|
||||
`
|
160
middleware/git/gitos/gitos.go
Normal file
160
middleware/git/gitos/gitos.go
Normal file
|
@ -0,0 +1,160 @@
|
|||
package gitos
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// File is an abstraction for file (os.File).
|
||||
type File interface {
|
||||
// Name returns the name of the file
|
||||
Name() string
|
||||
|
||||
// Stat returns the FileInfo structure describing file.
|
||||
Stat() (os.FileInfo, error)
|
||||
|
||||
// Close closes the File, rendering it unusable for I/O.
|
||||
Close() error
|
||||
|
||||
// Chmod changes the mode of the file.
|
||||
Chmod(os.FileMode) error
|
||||
|
||||
// Read reads up to len(b) bytes from the File. It returns the number of
|
||||
// bytes read and an error, if any.
|
||||
Read([]byte) (int, error)
|
||||
|
||||
// Write writes len(b) bytes to the File. It returns the number of bytes
|
||||
// written and an error, if any.
|
||||
Write([]byte) (int, error)
|
||||
}
|
||||
|
||||
// Cmd is an abstraction for external commands (os.Cmd).
|
||||
type Cmd interface {
|
||||
// Run starts the specified command and waits for it to complete.
|
||||
Run() error
|
||||
|
||||
// Start starts the specified command but does not wait for it to complete.
|
||||
Start() error
|
||||
|
||||
// Wait waits for the command to exit. It must have been started by Start.
|
||||
Wait() error
|
||||
|
||||
// Output runs the command and returns its standard output.
|
||||
Output() ([]byte, error)
|
||||
|
||||
// Dir sets the working directory of the command.
|
||||
Dir(string)
|
||||
|
||||
// Stdin sets the process's standard input.
|
||||
Stdin(io.Reader)
|
||||
|
||||
// Stdout sets the process's standard output.
|
||||
Stdout(io.Writer)
|
||||
|
||||
// Stderr sets the process's standard output.
|
||||
Stderr(io.Writer)
|
||||
}
|
||||
|
||||
// gitCmd represents external commands executed by git.
|
||||
type gitCmd struct {
|
||||
*exec.Cmd
|
||||
}
|
||||
|
||||
// Dir sets the working directory of the command.
|
||||
func (g *gitCmd) Dir(dir string) {
|
||||
g.Cmd.Dir = dir
|
||||
}
|
||||
|
||||
// Stdin sets the process's standard input.
|
||||
func (g *gitCmd) Stdin(stdin io.Reader) {
|
||||
g.Cmd.Stdin = stdin
|
||||
}
|
||||
|
||||
// Stdout sets the process's standard output.
|
||||
func (g *gitCmd) Stdout(stdout io.Writer) {
|
||||
g.Cmd.Stdout = stdout
|
||||
}
|
||||
|
||||
// Stderr sets the process's standard output.
|
||||
func (g *gitCmd) Stderr(stderr io.Writer) {
|
||||
g.Cmd.Stderr = stderr
|
||||
}
|
||||
|
||||
// OS is an abstraction for required OS level functions.
|
||||
type OS interface {
|
||||
// Command returns the Cmd to execute the named program with the
|
||||
// given arguments.
|
||||
Command(string, ...string) Cmd
|
||||
|
||||
// Mkdir creates a new directory with the specified name and permission
|
||||
// bits.
|
||||
Mkdir(string, os.FileMode) error
|
||||
|
||||
// MkdirAll creates a directory named path, along with any necessary
|
||||
// parents.
|
||||
MkdirAll(string, os.FileMode) error
|
||||
|
||||
// Stat returns a FileInfo describing the named file.
|
||||
Stat(string) (os.FileInfo, error)
|
||||
|
||||
// Remove removes the named file or directory.
|
||||
Remove(string) error
|
||||
|
||||
// ReadDir reads the directory named by dirname and returns a list of
|
||||
// directory entries.
|
||||
ReadDir(string) ([]os.FileInfo, error)
|
||||
|
||||
// LookPath searches for an executable binary named file in the directories
|
||||
// named by the PATH environment variable.
|
||||
LookPath(string) (string, error)
|
||||
|
||||
// TempFile creates a new temporary file in the directory dir with a name
|
||||
// beginning with prefix, opens the file for reading and writing, and
|
||||
// returns the resulting File.
|
||||
TempFile(string, string) (File, error)
|
||||
}
|
||||
|
||||
// GitOS is the implementation of OS for git.
|
||||
type GitOS struct{}
|
||||
|
||||
// Mkdir calls os.Mkdir.
|
||||
func (g GitOS) Mkdir(name string, perm os.FileMode) error {
|
||||
return os.Mkdir(name, perm)
|
||||
}
|
||||
|
||||
// MkdirAll calls os.MkdirAll.
|
||||
func (g GitOS) MkdirAll(path string, perm os.FileMode) error {
|
||||
return os.MkdirAll(path, perm)
|
||||
}
|
||||
|
||||
// Stat calls os.Stat.
|
||||
func (g GitOS) Stat(name string) (os.FileInfo, error) {
|
||||
return os.Stat(name)
|
||||
}
|
||||
|
||||
// Remove calls os.Remove.
|
||||
func (g GitOS) Remove(name string) error {
|
||||
return os.Remove(name)
|
||||
}
|
||||
|
||||
// LookPath calls exec.LookPath.
|
||||
func (g GitOS) LookPath(file string) (string, error) {
|
||||
return exec.LookPath(file)
|
||||
}
|
||||
|
||||
// TempFile calls ioutil.TempFile.
|
||||
func (g GitOS) TempFile(dir, prefix string) (File, error) {
|
||||
return ioutil.TempFile(dir, prefix)
|
||||
}
|
||||
|
||||
// ReadDir calls ioutil.ReadDir.
|
||||
func (g GitOS) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||
return ioutil.ReadDir(dirname)
|
||||
}
|
||||
|
||||
// Command calls exec.Command.
|
||||
func (g GitOS) Command(name string, args ...string) Cmd {
|
||||
return &gitCmd{exec.Command(name, args...)}
|
||||
}
|
167
middleware/git/gittest/gittest.go
Normal file
167
middleware/git/gittest/gittest.go
Normal file
|
@ -0,0 +1,167 @@
|
|||
// Package gittest is a test package for the git middleware.
|
||||
// It implements a mock gitos.OS, gitos.Cmd and gitos.File.
|
||||
package gittest
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/caddy/middleware/git/gitos"
|
||||
)
|
||||
|
||||
// FakeOS implements a mock gitos.OS, gitos.Cmd and gitos.File.
|
||||
var FakeOS = fakeOS{}
|
||||
|
||||
// CmdOutput is the output of any call to the mocked gitos.Cmd's Output().
|
||||
var CmdOutput = "success"
|
||||
|
||||
// TempFileName is the name of any file returned by mocked gitos.OS's TempFile().
|
||||
var TempFileName = "tempfile"
|
||||
|
||||
// dirs mocks a fake git dir if filename is "gitdir".
|
||||
var dirs = map[string][]os.FileInfo{
|
||||
"gitdir": {
|
||||
fakeInfo{name: ".git", dir: true},
|
||||
},
|
||||
}
|
||||
|
||||
// Open creates a new mock gitos.File.
|
||||
func Open(name string) gitos.File {
|
||||
return &fakeFile{name: name}
|
||||
}
|
||||
|
||||
// fakeFile is a mock gitos.File.
|
||||
type fakeFile struct {
|
||||
name string
|
||||
dir bool
|
||||
content []byte
|
||||
info fakeInfo
|
||||
}
|
||||
|
||||
func (f fakeFile) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f fakeFile) Stat() (os.FileInfo, error) {
|
||||
return fakeInfo{name: f.name}, nil
|
||||
}
|
||||
|
||||
func (f fakeFile) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeFile) Chmod(mode os.FileMode) error {
|
||||
f.info.mode = mode
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeFile) Read(b []byte) (int, error) {
|
||||
if len(f.content) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n := copy(b, f.content)
|
||||
f.content = f.content[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (f *fakeFile) Write(b []byte) (int, error) {
|
||||
f.content = append(f.content, b...)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// fakeCmd is a mock git.Cmd.
|
||||
type fakeCmd struct{}
|
||||
|
||||
func (f fakeCmd) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeCmd) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeCmd) Wait() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeCmd) Output() ([]byte, error) {
|
||||
return []byte(CmdOutput), nil
|
||||
}
|
||||
|
||||
func (f fakeCmd) Dir(dir string) {}
|
||||
|
||||
func (f fakeCmd) Stdin(stdin io.Reader) {}
|
||||
|
||||
func (f fakeCmd) Stdout(stdout io.Writer) {}
|
||||
|
||||
func (f fakeCmd) Stderr(stderr io.Writer) {}
|
||||
|
||||
// fakeInfo is a mock os.FileInfo.
|
||||
type fakeInfo struct {
|
||||
name string
|
||||
dir bool
|
||||
mode os.FileMode
|
||||
}
|
||||
|
||||
func (f fakeInfo) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f fakeInfo) Size() int64 {
|
||||
return 1024
|
||||
}
|
||||
|
||||
func (f fakeInfo) Mode() os.FileMode {
|
||||
return f.mode
|
||||
}
|
||||
|
||||
func (f fakeInfo) ModTime() time.Time {
|
||||
return time.Now().Truncate(time.Hour)
|
||||
}
|
||||
|
||||
func (f fakeInfo) IsDir() bool {
|
||||
return f.dir
|
||||
}
|
||||
|
||||
func (f fakeInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// fakeOS is a mock git.OS.
|
||||
type fakeOS struct{}
|
||||
|
||||
func (f fakeOS) Mkdir(name string, perm os.FileMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeOS) MkdirAll(path string, perm os.FileMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeOS) Stat(name string) (os.FileInfo, error) {
|
||||
return fakeInfo{name: name}, nil
|
||||
}
|
||||
|
||||
func (f fakeOS) Remove(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeOS) LookPath(file string) (string, error) {
|
||||
return "/usr/bin/" + file, nil
|
||||
}
|
||||
|
||||
func (f fakeOS) TempFile(dir, prefix string) (gitos.File, error) {
|
||||
return &fakeFile{name: TempFileName, info: fakeInfo{name: TempFileName}}, nil
|
||||
}
|
||||
|
||||
func (f fakeOS) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||
if f, ok := dirs[dirname]; ok {
|
||||
return f, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f fakeOS) Command(name string, args ...string) gitos.Cmd {
|
||||
return fakeCmd{}
|
||||
}
|
12
middleware/git/os.go
Normal file
12
middleware/git/os.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package git
|
||||
|
||||
import "github.com/mholt/caddy/middleware/git/gitos"
|
||||
|
||||
// gos is the OS used by git.
|
||||
var gos gitos.OS = gitos.GitOS{}
|
||||
|
||||
// SetOS sets the OS to be used. Intended to be used for tests
|
||||
// to abstract OS level git actions.
|
||||
func SetOS(os gitos.OS) {
|
||||
gos = os
|
||||
}
|
|
@ -28,6 +28,7 @@ func TestRewrite(t *testing.T) {
|
|||
[]string{"/abc/", "ab", "/abc/{file}", ".html|"},
|
||||
[]string{"/abcd/", "ab", "/a/{dir}/{file}", ".html|"},
|
||||
[]string{"/abcde/", "ab", "/a#{frag}", ".html|"},
|
||||
[]string{"/ab/", `.*\.jpg`, "/ajpg", ""},
|
||||
}
|
||||
|
||||
for _, regexpRule := range regexpRules {
|
||||
|
@ -76,6 +77,7 @@ func TestRewrite(t *testing.T) {
|
|||
{"/abcd/abcd.html", "/a/abcd/abcd.html"},
|
||||
{"/abcde/abcde.html", "/a"},
|
||||
{"/abcde/abcde.html#1234", "/a#1234"},
|
||||
{"/ab/ab.jpg", "/ajpg"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
|
|
Loading…
Reference in New Issue
Block a user