github.com/maheshbr/terraform@v0.3.1-0.20141020033300-deec7194a3ea/config/module/get.go (about)

     1  package module
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/url"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strings"
    13  	"syscall"
    14  )
    15  
    16  // Getter defines the interface that schemes must implement to download
    17  // and update modules.
    18  type Getter interface {
    19  	// Get downloads the given URL into the given directory. This always
    20  	// assumes that we're updating and gets the latest version that it can.
    21  	//
    22  	// The directory may already exist (if we're updating). If it is in a
    23  	// format that isn't understood, an error should be returned. Get shouldn't
    24  	// simply nuke the directory.
    25  	Get(string, *url.URL) error
    26  }
    27  
    28  // Getters is the mapping of scheme to the Getter implementation that will
    29  // be used to get a dependency.
    30  var Getters map[string]Getter
    31  
    32  // forcedRegexp is the regular expression that finds forced getters. This
    33  // syntax is schema::url, example: git::https://foo.com
    34  var forcedRegexp = regexp.MustCompile(`^([A-Za-z]+)::(.+)$`)
    35  
    36  func init() {
    37  	httpGetter := new(HttpGetter)
    38  
    39  	Getters = map[string]Getter{
    40  		"file":  new(FileGetter),
    41  		"git":   new(GitGetter),
    42  		"hg":    new(HgGetter),
    43  		"http":  httpGetter,
    44  		"https": httpGetter,
    45  	}
    46  }
    47  
    48  // Get downloads the module specified by src into the folder specified by
    49  // dst. If dst already exists, Get will attempt to update it.
    50  //
    51  // src is a URL, whereas dst is always just a file path to a folder. This
    52  // folder doesn't need to exist. It will be created if it doesn't exist.
    53  func Get(dst, src string) error {
    54  	var force string
    55  	force, src = getForcedGetter(src)
    56  
    57  	// If there is a subdir component, then we download the root separately
    58  	// and then copy over the proper subdir.
    59  	var realDst string
    60  	src, subDir := getDirSubdir(src)
    61  	if subDir != "" {
    62  		tmpDir, err := ioutil.TempDir("", "tf")
    63  		if err != nil {
    64  			return err
    65  		}
    66  		if err := os.RemoveAll(tmpDir); err != nil {
    67  			return err
    68  		}
    69  		defer os.RemoveAll(tmpDir)
    70  
    71  		realDst = dst
    72  		dst = tmpDir
    73  	}
    74  
    75  	u, err := url.Parse(src)
    76  	if err != nil {
    77  		return err
    78  	}
    79  	if force == "" {
    80  		force = u.Scheme
    81  	}
    82  
    83  	g, ok := Getters[force]
    84  	if !ok {
    85  		return fmt.Errorf(
    86  			"module download not supported for scheme '%s'", force)
    87  	}
    88  
    89  	err = g.Get(dst, u)
    90  	if err != nil {
    91  		err = fmt.Errorf("error downloading module '%s': %s", src, err)
    92  		return err
    93  	}
    94  
    95  	// If we have a subdir, copy that over
    96  	if subDir != "" {
    97  		if err := os.RemoveAll(realDst); err != nil {
    98  			return err
    99  		}
   100  		if err := os.MkdirAll(realDst, 0755); err != nil {
   101  			return err
   102  		}
   103  
   104  		return copyDir(realDst, filepath.Join(dst, subDir))
   105  	}
   106  
   107  	return nil
   108  }
   109  
   110  // GetCopy is the same as Get except that it downloads a copy of the
   111  // module represented by source.
   112  //
   113  // This copy will omit and dot-prefixed files (such as .git/, .hg/) and
   114  // can't be updated on its own.
   115  func GetCopy(dst, src string) error {
   116  	// Create the temporary directory to do the real Get to
   117  	tmpDir, err := ioutil.TempDir("", "tf")
   118  	if err != nil {
   119  		return err
   120  	}
   121  	if err := os.RemoveAll(tmpDir); err != nil {
   122  		return err
   123  	}
   124  	defer os.RemoveAll(tmpDir)
   125  
   126  	// Get to that temporary dir
   127  	if err := Get(tmpDir, src); err != nil {
   128  		return err
   129  	}
   130  
   131  	// Make sure the destination exists
   132  	if err := os.MkdirAll(dst, 0755); err != nil {
   133  		return err
   134  	}
   135  
   136  	// Copy to the final location
   137  	return copyDir(dst, tmpDir)
   138  }
   139  
   140  // getRunCommand is a helper that will run a command and capture the output
   141  // in the case an error happens.
   142  func getRunCommand(cmd *exec.Cmd) error {
   143  	var buf bytes.Buffer
   144  	cmd.Stdout = &buf
   145  	cmd.Stderr = &buf
   146  	err := cmd.Run()
   147  	if err == nil {
   148  		return nil
   149  	}
   150  	if exiterr, ok := err.(*exec.ExitError); ok {
   151  		// The program has exited with an exit code != 0
   152  		if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
   153  			return fmt.Errorf(
   154  				"%s exited with %d: %s",
   155  				cmd.Path,
   156  				status.ExitStatus(),
   157  				buf.String())
   158  		}
   159  	}
   160  
   161  	return fmt.Errorf("error running %s: %s", cmd.Path, buf.String())
   162  }
   163  
   164  // getDirSubdir takes a source and returns a tuple of the URL without
   165  // the subdir and the URL with the subdir.
   166  func getDirSubdir(src string) (string, string) {
   167  	// Calcaulate an offset to avoid accidentally marking the scheme
   168  	// as the dir.
   169  	var offset int
   170  	if idx := strings.Index(src, "://"); idx > -1 {
   171  		offset = idx + 3
   172  	}
   173  
   174  	// First see if we even have an explicit subdir
   175  	idx := strings.Index(src[offset:], "//")
   176  	if idx == -1 {
   177  		return src, ""
   178  	}
   179  
   180  	idx += offset
   181  	subdir := src[idx+2:]
   182  	src = src[:idx]
   183  
   184  	// Next, check if we have query parameters and push them onto the
   185  	// URL.
   186  	if idx = strings.Index(subdir, "?"); idx > -1 {
   187  		query := subdir[idx:]
   188  		subdir = subdir[:idx]
   189  		src += query
   190  	}
   191  
   192  	return src, subdir
   193  }
   194  
   195  // getForcedGetter takes a source and returns the tuple of the forced
   196  // getter and the raw URL (without the force syntax).
   197  func getForcedGetter(src string) (string, string) {
   198  	var forced string
   199  	if ms := forcedRegexp.FindStringSubmatch(src); ms != nil {
   200  		forced = ms[1]
   201  		src = ms[2]
   202  	}
   203  
   204  	return forced, src
   205  }