mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-21 01:45:45 +03:00
Git: Minor fixes. Refactor. Added tests.
This commit is contained in:
parent
d311345aa5
commit
f44cd5d740
8 changed files with 781 additions and 36 deletions
|
@ -96,6 +96,8 @@ func gitParse(c *Controller) (*git.Repo, error) {
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
repo.Then = strings.Join(thenArgs, " ")
|
repo.Then = strings.Join(thenArgs, " ")
|
||||||
|
default:
|
||||||
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,8 +126,8 @@ func gitParse(c *Controller) (*git.Repo, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate git availability in PATH
|
// validate git requirements
|
||||||
if err = git.InitGit(); err != nil {
|
if err = git.Init(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,19 +155,39 @@ func sanitizeHttp(repoUrl string) (string, string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
repoUrl = "https://" + url.Host + url.Path
|
repoUrl = "https://" + url.Host + url.Path
|
||||||
|
|
||||||
|
// add .git suffix if missing
|
||||||
|
if !strings.HasSuffix(repoUrl, ".git") {
|
||||||
|
repoUrl += ".git"
|
||||||
|
}
|
||||||
|
|
||||||
return repoUrl, url.Host, nil
|
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)
|
// Returns sanitized url, hostName (e.g. github.com, bitbucket.com)
|
||||||
// and possible error
|
// and possible error
|
||||||
func sanitizeGit(repoUrl string) (string, string, error) {
|
func sanitizeGit(repoUrl string) (string, string, error) {
|
||||||
repoUrl = strings.TrimSpace(repoUrl)
|
repoUrl = strings.TrimSpace(repoUrl)
|
||||||
|
|
||||||
|
// check if valid ssh format
|
||||||
if !strings.HasPrefix(repoUrl, "git@") || strings.Index(repoUrl, ":") < len("git@a:") {
|
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@"):]
|
hostUrl := repoUrl[len("git@"):]
|
||||||
i := strings.Index(hostUrl, ":")
|
i := strings.Index(hostUrl, ":")
|
||||||
host := hostUrl[:i]
|
host := hostUrl[:i]
|
||||||
|
|
||||||
|
// add .git suffix if missing
|
||||||
|
if !strings.HasSuffix(repoUrl, ".git") {
|
||||||
|
repoUrl += ".git"
|
||||||
|
}
|
||||||
|
|
||||||
return repoUrl, host, nil
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/git/gitos"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultInterval is the minimum interval to delay before
|
// DefaultInterval is the minimum interval to delay before
|
||||||
|
@ -24,8 +23,11 @@ const numRetries = 3
|
||||||
// gitBinary holds the absolute path to git executable
|
// gitBinary holds the absolute path to git executable
|
||||||
var gitBinary string
|
var gitBinary string
|
||||||
|
|
||||||
|
// shell holds the shell to be used. Either sh or bash.
|
||||||
|
var shell string
|
||||||
|
|
||||||
// initMutex prevents parallel attempt to validate
|
// initMutex prevents parallel attempt to validate
|
||||||
// git availability in PATH
|
// git requirements.
|
||||||
var initMutex sync.Mutex = sync.Mutex{}
|
var initMutex sync.Mutex = sync.Mutex{}
|
||||||
|
|
||||||
// Logger is used to log errors; if nil, the default log.Logger is used.
|
// 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.
|
// pullWithKey is used for private repositories and requires an ssh key.
|
||||||
// Note: currently only limited to Linux and OSX.
|
// Note: currently only limited to Linux and OSX.
|
||||||
func (r *Repo) pullWithKey(params []string) error {
|
func (r *Repo) pullWithKey(params []string) error {
|
||||||
var gitSsh, script *os.File
|
var gitSsh, script gitos.File
|
||||||
// ensure temporary files deleted after usage
|
// ensure temporary files deleted after usage
|
||||||
defer func() {
|
defer func() {
|
||||||
if gitSsh != nil {
|
if gitSsh != nil {
|
||||||
os.Remove(gitSsh.Name())
|
gos.Remove(gitSsh.Name())
|
||||||
}
|
}
|
||||||
if script != nil {
|
if script != nil {
|
||||||
os.Remove(script.Name())
|
gos.Remove(script.Name())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
// write git.sh script to temp file
|
// write git.sh script to temp file
|
||||||
gitSsh, err = writeScriptFile(gitWrapperScript(gitBinary))
|
gitSsh, err = writeScriptFile(gitWrapperScript())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -163,9 +165,9 @@ func (r *Repo) pullWithKey(params []string) error {
|
||||||
func (r *Repo) Prepare() error {
|
func (r *Repo) Prepare() error {
|
||||||
// check if directory exists or is empty
|
// check if directory exists or is empty
|
||||||
// if not, create directory
|
// if not, create directory
|
||||||
fs, err := ioutil.ReadDir(r.Path)
|
fs, err := gos.ReadDir(r.Path)
|
||||||
if err != nil || len(fs) == 0 {
|
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
|
// validate git repo
|
||||||
|
@ -180,9 +182,15 @@ func (r *Repo) Prepare() error {
|
||||||
if isGit {
|
if isGit {
|
||||||
// check if same repository
|
// check if same repository
|
||||||
var repoUrl string
|
var repoUrl string
|
||||||
if repoUrl, err = r.getRepoUrl(); err == nil && repoUrl == r.Url {
|
if repoUrl, err = r.getRepoUrl(); err == nil {
|
||||||
r.pulled = true
|
// add .git suffix if missing for adequate comparison.
|
||||||
return nil
|
if !strings.HasSuffix(repoUrl, ".git") {
|
||||||
|
repoUrl += ".git"
|
||||||
|
}
|
||||||
|
if repoUrl == r.Url {
|
||||||
|
r.pulled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Cannot retrieve repo url for %v Error: %v", r.Path, err)
|
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
|
// getRepoUrl retrieves remote origin url for the git repository at path
|
||||||
func (r *Repo) getRepoUrl() (string, error) {
|
func (r *Repo) getRepoUrl() (string, error) {
|
||||||
_, err := os.Stat(r.Path)
|
_, err := gos.Stat(r.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -230,9 +238,9 @@ func (r *Repo) postPullCommand() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitGit validates git installation and locates the git executable
|
// Init validates git installation, locates the git executable
|
||||||
// binary in PATH
|
// binary in PATH and check for available shell to use.
|
||||||
func InitGit() error {
|
func Init() error {
|
||||||
// prevent concurrent call
|
// prevent concurrent call
|
||||||
initMutex.Lock()
|
initMutex.Lock()
|
||||||
defer initMutex.Unlock()
|
defer initMutex.Unlock()
|
||||||
|
@ -245,18 +253,30 @@ func InitGit() error {
|
||||||
|
|
||||||
// locate git binary in path
|
// locate git binary in path
|
||||||
var err error
|
var err error
|
||||||
gitBinary, err = exec.LookPath("git")
|
if gitBinary, err = gos.LookPath("git"); err != nil {
|
||||||
return err
|
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.
|
// runCmd is a helper function to run commands.
|
||||||
// It runs command with args from directory at dir.
|
// It runs command with args from directory at dir.
|
||||||
// The executed process outputs to os.Stderr
|
// The executed process outputs to os.Stderr
|
||||||
func runCmd(command string, args []string, dir string) error {
|
func runCmd(command string, args []string, dir string) error {
|
||||||
cmd := exec.Command(command, args...)
|
cmd := gos.Command(command, args...)
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stdout(os.Stderr)
|
||||||
cmd.Stdout = os.Stderr
|
cmd.Stderr(os.Stderr)
|
||||||
cmd.Dir = dir
|
cmd.Dir(dir)
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -267,8 +287,8 @@ func runCmd(command string, args []string, dir string) error {
|
||||||
// It runs command with args from directory at dir.
|
// It runs command with args from directory at dir.
|
||||||
// If successful, returns output and nil error
|
// If successful, returns output and nil error
|
||||||
func runCmdOutput(command string, args []string, dir string) (string, error) {
|
func runCmdOutput(command string, args []string, dir string) (string, error) {
|
||||||
cmd := exec.Command(command, args...)
|
cmd := gos.Command(command, args...)
|
||||||
cmd.Dir = dir
|
cmd.Dir(dir)
|
||||||
var err error
|
var err error
|
||||||
if output, err := cmd.Output(); err == nil {
|
if output, err := cmd.Output(); err == nil {
|
||||||
return string(bytes.TrimSpace(output)), 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.
|
// writeScriptFile writes content to a temporary file.
|
||||||
// It changes the temporary file mode to executable and
|
// It changes the temporary file mode to executable and
|
||||||
// closes it to prepare it for execution.
|
// closes it to prepare it for execution.
|
||||||
func writeScriptFile(content []byte) (file *os.File, err error) {
|
func writeScriptFile(content []byte) (file gitos.File, err error) {
|
||||||
if file, err = ioutil.TempFile("", "caddy"); err != nil {
|
if file, err = gos.TempFile("", "caddy"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if _, err = file.Write(content); err != nil {
|
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
|
// gitWrapperScript forms content for git.sh script
|
||||||
var gitWrapperScript = func(gitBinary string) []byte {
|
func gitWrapperScript() []byte {
|
||||||
return []byte(fmt.Sprintf(`#!/bin/bash
|
return []byte(fmt.Sprintf(`#!/bin/%v
|
||||||
|
|
||||||
# The MIT License (MIT)
|
# The MIT License (MIT)
|
||||||
# Copyright (c) 2013 Alvin Abad
|
# Copyright (c) 2013 Alvin Abad
|
||||||
|
@ -323,17 +343,17 @@ fi
|
||||||
# Run the git command
|
# Run the git command
|
||||||
%v "$@"
|
%v "$@"
|
||||||
|
|
||||||
`, gitBinary))
|
`, shell, gitBinary))
|
||||||
}
|
}
|
||||||
|
|
||||||
// bashScript forms content of bash script to clone or update a repo using ssh
|
// bashScript forms content of bash script to clone or update a repo using ssh
|
||||||
var bashScript = func(gitShPath string, repo *Repo, params []string) []byte {
|
func bashScript(gitShPath string, repo *Repo, params []string) []byte {
|
||||||
return []byte(fmt.Sprintf(`#!/bin/bash
|
return []byte(fmt.Sprintf(`#!/bin/%v
|
||||||
|
|
||||||
mkdir -p ~/.ssh;
|
mkdir -p ~/.ssh;
|
||||||
touch ~/.ssh/known_hosts;
|
touch ~/.ssh/known_hosts;
|
||||||
ssh-keyscan -t rsa,dsa %v 2>&1 | sort -u - ~/.ssh/known_hosts > ~/.ssh/tmp_hosts;
|
ssh-keyscan -t rsa,dsa %v 2>&1 | sort -u - ~/.ssh/known_hosts > ~/.ssh/tmp_hosts;
|
||||||
cat ~/.ssh/tmp_hosts >> ~/.ssh/known_hosts;
|
cat ~/.ssh/tmp_hosts >> ~/.ssh/known_hosts;
|
||||||
%v -i %v %v;
|
%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{"/abc/", "ab", "/abc/{file}", ".html|"},
|
||||||
[]string{"/abcd/", "ab", "/a/{dir}/{file}", ".html|"},
|
[]string{"/abcd/", "ab", "/a/{dir}/{file}", ".html|"},
|
||||||
[]string{"/abcde/", "ab", "/a#{frag}", ".html|"},
|
[]string{"/abcde/", "ab", "/a#{frag}", ".html|"},
|
||||||
|
[]string{"/ab/", `.*\.jpg`, "/ajpg", ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, regexpRule := range regexpRules {
|
for _, regexpRule := range regexpRules {
|
||||||
|
@ -76,6 +77,7 @@ func TestRewrite(t *testing.T) {
|
||||||
{"/abcd/abcd.html", "/a/abcd/abcd.html"},
|
{"/abcd/abcd.html", "/a/abcd/abcd.html"},
|
||||||
{"/abcde/abcde.html", "/a"},
|
{"/abcde/abcde.html", "/a"},
|
||||||
{"/abcde/abcde.html#1234", "/a#1234"},
|
{"/abcde/abcde.html#1234", "/a#1234"},
|
||||||
|
{"/ab/ab.jpg", "/ajpg"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
|
|
Loading…
Reference in a new issue