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