github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/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 }