github.com/coreos/mantle@v0.13.0/sdk/download.go (about) 1 // Copyright 2015 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package sdk 16 17 import ( 18 "bytes" 19 "fmt" 20 "io" 21 "net/http" 22 "net/url" 23 "os" 24 "path" 25 "path/filepath" 26 "strings" 27 "time" 28 29 "github.com/coreos/pkg/capnslog" 30 "google.golang.org/api/storage/v1" 31 32 "github.com/coreos/mantle/system" 33 "github.com/coreos/mantle/util" 34 ) 35 36 const ( 37 urlHost = "storage.googleapis.com" 38 urlPath = "/builds.developer.core-os.net/sdk" 39 ) 40 41 var plog = capnslog.NewPackageLogger("github.com/coreos/mantle", "sdk") 42 43 func TarballName(version string) string { 44 arch := system.PortageArch() 45 return fmt.Sprintf("coreos-sdk-%s-%s.tar.bz2", arch, version) 46 } 47 48 func TarballURL(version string) string { 49 arch := system.PortageArch() 50 p := path.Join(urlPath, arch, version, TarballName(version)) 51 u := url.URL{Scheme: "https", Host: urlHost, Path: p} 52 return u.String() 53 } 54 55 func DownloadFile(file, fileURL string, client *http.Client) error { 56 plog.Infof("Downloading %s to %s", fileURL, file) 57 58 // handle bucket urls by using api to get media link 59 parseURL, err := url.Parse(fileURL) 60 if err != nil { 61 return err 62 } 63 resolveGS := func() error { 64 if client == nil { 65 client = http.DefaultClient 66 } 67 api, err := storage.New(client) 68 if err != nil { 69 return err 70 } 71 path := strings.TrimLeft(parseURL.Path, "/") 72 obj, err := api.Objects.Get(parseURL.Host, path).Do() 73 if err != nil { 74 return fmt.Errorf("%s: %s", err, fileURL) 75 } 76 fileURL = obj.MediaLink 77 return nil 78 } 79 if parseURL.Scheme == "gs" { 80 if err := util.Retry(5, 1*time.Second, resolveGS); err != nil { 81 return err 82 } 83 } 84 85 if err := os.MkdirAll(filepath.Dir(file), 0777); err != nil { 86 return err 87 } 88 89 download := func() error { 90 return downloadFile(file, fileURL, client) 91 } 92 if err := util.Retry(5, 1*time.Second, download); err != nil { 93 return err 94 } 95 return nil 96 } 97 98 func downloadFile(file, url string, client *http.Client) error { 99 if client == nil { 100 client = http.DefaultClient 101 } 102 103 dst, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE, 0666) 104 if err != nil { 105 return err 106 } 107 defer dst.Close() 108 109 pos, err := dst.Seek(0, os.SEEK_END) 110 if err != nil { 111 return err 112 } 113 114 req, err := http.NewRequest("GET", url, nil) 115 if err != nil { 116 return err 117 } 118 119 if pos != 0 { 120 req.Header.Add("Range", fmt.Sprintf("bytes=%d-", pos)) 121 } 122 123 resp, err := client.Do(req) 124 if err != nil { 125 return err 126 } 127 defer resp.Body.Close() 128 129 var length int64 130 switch resp.StatusCode { 131 case http.StatusOK: 132 if pos != 0 { 133 if _, err := dst.Seek(0, os.SEEK_SET); err != nil { 134 return err 135 } 136 if err := dst.Truncate(0); err != nil { 137 return err 138 } 139 pos = 0 140 } 141 length = resp.ContentLength 142 case http.StatusPartialContent: 143 var end int64 144 n, _ := fmt.Sscanf(resp.Header.Get("Content-Range"), 145 "bytes %d-%d/%d", &pos, &end, &length) 146 if n != 3 { 147 return fmt.Errorf("Bad Content-Range for %s", resp.Request.URL) 148 } 149 150 if _, err := dst.Seek(pos, os.SEEK_SET); err != nil { 151 return err 152 } 153 plog.Infof("Resuming from byte %d", pos) 154 case http.StatusRequestedRangeNotSatisfiable: 155 plog.Infof("Download already complete") 156 return nil 157 default: 158 return fmt.Errorf("%s: %s", resp.Status, resp.Request.URL) 159 } 160 161 prefix := filepath.Base(file) 162 if n, err := util.CopyProgress(capnslog.INFO, prefix, dst, resp.Body, resp.ContentLength); err != nil { 163 return err 164 } else if n != length-pos { 165 // unsure if this is worth caring about 166 plog.Infof("Downloaded %d bytes, expected %d", n, length-pos) 167 return nil 168 } else { 169 plog.Infof("Downloaded %d bytes", n) 170 return nil 171 } 172 } 173 174 func DownloadSignedFile(file, url string, client *http.Client, verifyKeyFile string) error { 175 176 if _, err := os.Stat(file + ".sig"); err == nil { 177 if e := VerifyFile(file, verifyKeyFile); e == nil { 178 plog.Infof("Verified existing file: %s", file) 179 return nil 180 } 181 } 182 183 if err := DownloadFile(file, url, client); err != nil { 184 return err 185 } 186 187 if err := DownloadFile(file+".sig", url+".sig", client); err != nil { 188 return err 189 } 190 191 if err := VerifyFile(file, verifyKeyFile); err != nil { 192 return err 193 } 194 195 plog.Infof("Verified file: %s", file) 196 return nil 197 } 198 199 func DownloadSDK(version, verifyKeyFile string) error { 200 tarFile := filepath.Join(RepoCache(), "sdks", TarballName(version)) 201 tarURL := TarballURL(version) 202 return DownloadSignedFile(tarFile, tarURL, nil, verifyKeyFile) 203 } 204 205 // false if both files do not exist 206 func cmpFileBytes(file1, file2 string) (bool, error) { 207 info1, err := os.Stat(file1) 208 if err != nil { 209 return false, err 210 } 211 info2, err := os.Stat(file2) 212 if err != nil { 213 return false, err 214 } 215 if info1.Size() != info2.Size() { 216 return false, nil 217 } 218 219 f1, err := os.Open(file1) 220 if err != nil { 221 return false, err 222 } 223 defer f1.Close() 224 f2, err := os.Open(file2) 225 if err != nil { 226 return false, err 227 } 228 defer f2.Close() 229 230 const defaultBufSize = 4096 // same as bufio 231 buf1 := make([]byte, defaultBufSize) 232 buf2 := make([]byte, defaultBufSize) 233 234 for { 235 n1, err1 := io.ReadFull(f1, buf1) 236 n2, err2 := io.ReadFull(f2, buf2) 237 238 if err1 == io.EOF && err2 == io.EOF { 239 return true, nil 240 } else if err1 == io.EOF || err2 == io.EOF { 241 return false, nil 242 } 243 244 if err1 == io.ErrUnexpectedEOF && err2 == io.ErrUnexpectedEOF { 245 return bytes.Equal(buf1[:n1], buf2[:n2]), nil 246 } else if err1 == io.ErrUnexpectedEOF || err2 == io.ErrUnexpectedEOF { 247 return false, nil 248 } 249 250 if err1 != nil { 251 return false, err1 252 } 253 if err2 != nil { 254 return false, err2 255 } 256 257 if !bytes.Equal(buf1, buf2) { 258 return false, nil 259 } 260 } 261 } 262 263 // UpdateFile downloads a file to temp dir and replaces the file only if 264 // contents have changed. If tempDir is "" default will be os.TempDir(). 265 // Leave client nil to use default. 266 func UpdateFile(file, url string, client *http.Client) error { 267 if err := os.MkdirAll(filepath.Dir(file), 0777); err != nil { 268 return err 269 } 270 271 tempFile := file + ".part" 272 if err := DownloadFile(tempFile, url, client); err != nil { 273 return fmt.Errorf("%s: %s", url, err) 274 } 275 defer os.Remove(tempFile) 276 277 equal, err := cmpFileBytes(file, tempFile) 278 if os.IsExist(err) { // file may not exist, that is ok 279 return err 280 } 281 if equal { 282 plog.Infof("%v is up to date", file) 283 return nil 284 } 285 286 // not equal so delete any existing file and rename tempFile to file 287 if err := os.Rename(tempFile, file); err != nil { 288 return err 289 } 290 return nil 291 } 292 293 // UpdateSignedFile will download and replace the local file if the 294 // published signature doesn't match the local copy. Leave client nil to 295 // use default. 296 func UpdateSignedFile(file, url string, client *http.Client, verifyKeyFile string) error { 297 sigFile := file + ".sig" 298 sigURL := url + ".sig" 299 300 // update local sig to latest 301 if err := UpdateFile(sigFile, sigURL, client); err != nil { 302 return err 303 } 304 305 // try to verify with latest sig 306 if e := VerifyFile(file, verifyKeyFile); e == nil { 307 plog.Infof("Verified existing file: %s", file) 308 return nil 309 } 310 311 // download image and try to verify again 312 if err := UpdateFile(file, url, client); err != nil { 313 return err 314 } 315 if err := VerifyFile(file, verifyKeyFile); err != nil { 316 return err 317 } 318 319 plog.Infof("Verified file: %s", file) 320 return nil 321 }