github.com/anuaimi/terraform@v0.6.4-0.20150904235404-2bf9aec61da8/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 }