Skip to content

Commit

Permalink
copy from terraform
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchellh committed Oct 12, 2015
0 parents commit 4133bae
Show file tree
Hide file tree
Showing 112 changed files with 3,475 additions and 0 deletions.
76 changes: 76 additions & 0 deletions copy_dir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package getter

import (
"io"
"os"
"path/filepath"
"strings"
)

// copyDir copies the src directory contents into dst. Both directories
// should already exist.
func copyDir(dst, src string) error {
src, err := filepath.EvalSymlinks(src)
if err != nil {
return err
}

walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == src {
return nil
}

if strings.HasPrefix(filepath.Base(path), ".") {
// Skip any dot files
if info.IsDir() {
return filepath.SkipDir
} else {
return nil
}
}

// The "path" has the src prefixed to it. We need to join our
// destination with the path without the src on it.
dstPath := filepath.Join(dst, path[len(src):])

// If we have a directory, make that subdirectory, then continue
// the walk.
if info.IsDir() {
if path == filepath.Join(src, dst) {
// dst is in src; don't walk it.
return nil
}

if err := os.MkdirAll(dstPath, 0755); err != nil {
return err
}

return nil
}

// If we have a file, copy the contents.
srcF, err := os.Open(path)
if err != nil {
return err
}
defer srcF.Close()

dstF, err := os.Create(dstPath)
if err != nil {
return err
}
defer dstF.Close()

if _, err := io.Copy(dstF, srcF); err != nil {
return err
}

// Chmod it
return os.Chmod(dstPath, info.Mode())
}

return filepath.Walk(src, walkFn)
}
92 changes: 92 additions & 0 deletions detect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package getter

import (
"fmt"
"path/filepath"

"github.com/hashicorp/terraform/helper/url"
)

// Detector defines the interface that an invalid URL or a URL with a blank
// scheme is passed through in order to determine if its shorthand for
// something else well-known.
type Detector interface {
// Detect will detect whether the string matches a known pattern to
// turn it into a proper URL.
Detect(string, string) (string, bool, error)
}

// Detectors is the list of detectors that are tried on an invalid URL.
// This is also the order they're tried (index 0 is first).
var Detectors []Detector

func init() {
Detectors = []Detector{
new(GitHubDetector),
new(BitBucketDetector),
new(FileDetector),
}
}

// Detect turns a source string into another source string if it is
// detected to be of a known pattern.
//
// This is safe to be called with an already valid source string: Detect
// will just return it.
func Detect(src string, pwd string) (string, error) {
getForce, getSrc := getForcedGetter(src)

// Separate out the subdir if there is one, we don't pass that to detect
getSrc, subDir := getDirSubdir(getSrc)

u, err := url.Parse(getSrc)
if err == nil && u.Scheme != "" {
// Valid URL
return src, nil
}

for _, d := range Detectors {
result, ok, err := d.Detect(getSrc, pwd)
if err != nil {
return "", err
}
if !ok {
continue
}

var detectForce string
detectForce, result = getForcedGetter(result)
result, detectSubdir := getDirSubdir(result)

// If we have a subdir from the detection, then prepend it to our
// requested subdir.
if detectSubdir != "" {
if subDir != "" {
subDir = filepath.Join(detectSubdir, subDir)
} else {
subDir = detectSubdir
}
}
if subDir != "" {
u, err := url.Parse(result)
if err != nil {
return "", fmt.Errorf("Error parsing URL: %s", err)
}
u.Path += "//" + subDir
result = u.String()
}

// Preserve the forced getter if it exists. We try to use the
// original set force first, followed by any force set by the
// detector.
if getForce != "" {
result = fmt.Sprintf("%s::%s", getForce, result)
} else if detectForce != "" {
result = fmt.Sprintf("%s::%s", detectForce, result)
}

return result, nil
}

return "", fmt.Errorf("invalid source string: %s", src)
}
66 changes: 66 additions & 0 deletions detect_bitbucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package getter

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)

// BitBucketDetector implements Detector to detect BitBucket URLs and turn
// them into URLs that the Git or Hg Getter can understand.
type BitBucketDetector struct{}

func (d *BitBucketDetector) Detect(src, _ string) (string, bool, error) {
if len(src) == 0 {
return "", false, nil
}

if strings.HasPrefix(src, "bitbucket.org/") {
return d.detectHTTP(src)
}

return "", false, nil
}

func (d *BitBucketDetector) detectHTTP(src string) (string, bool, error) {
u, err := url.Parse("https://" + src)
if err != nil {
return "", true, fmt.Errorf("error parsing BitBucket URL: %s", err)
}

// We need to get info on this BitBucket repository to determine whether
// it is Git or Hg.
var info struct {
SCM string `json:"scm"`
}
infoUrl := "https://api.bitbucket.org/1.0/repositories" + u.Path
resp, err := http.Get(infoUrl)
if err != nil {
return "", true, fmt.Errorf("error looking up BitBucket URL: %s", err)
}
if resp.StatusCode == 403 {
// A private repo
return "", true, fmt.Errorf(
"shorthand BitBucket URL can't be used for private repos, " +
"please use a full URL")
}
dec := json.NewDecoder(resp.Body)
if err := dec.Decode(&info); err != nil {
return "", true, fmt.Errorf("error looking up BitBucket URL: %s", err)
}

switch info.SCM {
case "git":
if !strings.HasSuffix(u.Path, ".git") {
u.Path += ".git"
}

return "git::" + u.String(), true, nil
case "hg":
return "hg::" + u.String(), true, nil
default:
return "", true, fmt.Errorf("unknown BitBucket SCM type: %s", info.SCM)
}
}
67 changes: 67 additions & 0 deletions detect_bitbucket_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package getter

import (
"net/http"
"strings"
"testing"
)

const testBBUrl = "https://bitbucket.org/hashicorp/tf-test-git"

func TestBitBucketDetector(t *testing.T) {
t.Parallel()

if _, err := http.Get(testBBUrl); err != nil {
t.Log("internet may not be working, skipping BB tests")
t.Skip()
}

cases := []struct {
Input string
Output string
}{
// HTTP
{
"bitbucket.org/hashicorp/tf-test-git",
"git::https://bitbucket.org/hashicorp/tf-test-git.git",
},
{
"bitbucket.org/hashicorp/tf-test-git.git",
"git::https://bitbucket.org/hashicorp/tf-test-git.git",
},
{
"bitbucket.org/hashicorp/tf-test-hg",
"hg::https://bitbucket.org/hashicorp/tf-test-hg",
},
}

pwd := "/pwd"
f := new(BitBucketDetector)
for i, tc := range cases {
var err error
for i := 0; i < 3; i++ {
var output string
var ok bool
output, ok, err = f.Detect(tc.Input, pwd)
if err != nil {
if strings.Contains(err.Error(), "invalid character") {
continue
}

t.Fatalf("err: %s", err)
}
if !ok {
t.Fatal("not ok")
}

if output != tc.Output {
t.Fatalf("%d: bad: %#v", i, output)
}

break
}
if i >= 3 {
t.Fatalf("failure from bitbucket: %s", err)
}
}
}
60 changes: 60 additions & 0 deletions detect_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package getter

import (
"fmt"
"os"
"path/filepath"
"runtime"
)

// FileDetector implements Detector to detect file paths.
type FileDetector struct{}

func (d *FileDetector) Detect(src, pwd string) (string, bool, error) {
if len(src) == 0 {
return "", false, nil
}

if !filepath.IsAbs(src) {
if pwd == "" {
return "", true, fmt.Errorf(
"relative paths require a module with a pwd")
}

// Stat the pwd to determine if its a symbolic link. If it is,
// then the pwd becomes the original directory. Otherwise,
// `filepath.Join` below does some weird stuff.
//
// We just ignore if the pwd doesn't exist. That error will be
// caught later when we try to use the URL.
if fi, err := os.Lstat(pwd); !os.IsNotExist(err) {
if err != nil {
return "", true, err
}
if fi.Mode()&os.ModeSymlink != 0 {
pwd, err = os.Readlink(pwd)
if err != nil {
return "", true, err
}
}
}

src = filepath.Join(pwd, src)
}

return fmtFileURL(src), true, nil
}

func fmtFileURL(path string) string {
if runtime.GOOS == "windows" {
// Make sure we're using "/" on Windows. URLs are "/"-based.
path = filepath.ToSlash(path)
return fmt.Sprintf("file://%s", path)
}

// Make sure that we don't start with "/" since we add that below.
if path[0] == '/' {
path = path[1:]
}
return fmt.Sprintf("file:///%s", path)
}
Loading

0 comments on commit 4133bae

Please sign in to comment.