github.com/tiagovtristao/plz@v13.4.0+incompatible/src/cache/http_cache.go (about) 1 // Http-based cache. 2 3 package cache 4 5 import ( 6 "encoding/base64" 7 "io" 8 "mime" 9 "mime/multipart" 10 "net/http" 11 "os" 12 "path" 13 "time" 14 15 "github.com/thought-machine/please/src/core" 16 "github.com/thought-machine/please/src/fs" 17 ) 18 19 type httpCache struct { 20 URL string 21 Writeable bool 22 Timeout time.Duration 23 } 24 25 func (cache *httpCache) Store(target *core.BuildTarget, key []byte, files ...string) { 26 // TODO(pebers): Change this to upload using multipart, it's quite slow doing every file separately 27 // for targets with many files. 28 if cache.Writeable { 29 for _, out := range cacheArtifacts(target, files...) { 30 if info, err := os.Stat(out); err == nil && info.IsDir() { 31 fs.Walk(out, func(name string, isDir bool) error { 32 if !isDir { 33 cache.StoreExtra(target, key, name) 34 } 35 return nil 36 }) 37 } else { 38 cache.StoreExtra(target, key, out) 39 } 40 } 41 } 42 } 43 44 func (cache *httpCache) StoreExtra(target *core.BuildTarget, key []byte, file string) { 45 if cache.Writeable { 46 artifact := path.Join( 47 core.OsArch, 48 target.Label.PackageName, 49 target.Label.Name, 50 base64.RawURLEncoding.EncodeToString(key), 51 file, 52 ) 53 log.Info("Storing %s: %s in http cache...", target.Label, artifact) 54 55 // NB. Don't need to close this file, http.Post will do it for us. 56 file, err := os.Open(path.Join(target.OutDir(), file)) 57 if err != nil { 58 log.Warning("Failed to read artifact: %s", err) 59 return 60 } 61 response, err := http.Post(cache.URL+"/artifact/"+artifact, "application/octet-stream", file) 62 if err != nil { 63 log.Warning("Failed to send artifact to %s: %s", cache.URL+"/artifact/"+artifact, err) 64 return 65 } else if response.StatusCode < 200 || response.StatusCode > 299 { 66 log.Warning("Failed to send artifact to %s: got response %s", cache.URL+"/artifact/"+artifact, response.Status) 67 } 68 response.Body.Close() 69 } 70 } 71 72 func (cache *httpCache) Retrieve(target *core.BuildTarget, key []byte) bool { 73 // We can't tell from outside if this works or not (as we can for the dir cache) 74 // so we must assume that a target with no artifacts can't be retrieved. It's a weird 75 // case but a test already exists in the plz test suite so... 76 retrieved := false 77 for _, out := range cacheArtifacts(target) { 78 if !cache.RetrieveExtra(target, key, out) { 79 return false 80 } 81 retrieved = true 82 } 83 return retrieved 84 } 85 86 func (cache *httpCache) RetrieveExtra(target *core.BuildTarget, key []byte, file string) bool { 87 log.Debug("Retrieving %s:%s from http cache...", target.Label, file) 88 89 artifact := path.Join( 90 core.OsArch, 91 target.Label.PackageName, 92 target.Label.Name, 93 base64.RawURLEncoding.EncodeToString(key), 94 file, 95 ) 96 97 response, err := http.Get(cache.URL + "/artifact/" + artifact) 98 if err != nil { 99 return false 100 } 101 defer response.Body.Close() 102 if response.StatusCode == 404 { 103 return false 104 } else if response.StatusCode < 200 || response.StatusCode > 299 { 105 log.Warning("Error %d from http cache", response.StatusCode) 106 return false 107 } else if response.Header.Get("Content-Type") == "application/octet-stream" { 108 // Single artifact 109 return cache.writeFile(target, file, response.Body) 110 } else if _, params, err := mime.ParseMediaType(response.Header.Get("Content-Type")); err != nil { 111 log.Warning("Couldn't parse response: %s", err) 112 return false 113 } else { 114 // Directory, comes back in multipart 115 mr := multipart.NewReader(response.Body, params["boundary"]) 116 for { 117 if part, err := mr.NextPart(); err == io.EOF { 118 return true 119 } else if err != nil { 120 log.Warning("Error reading multipart response: %s", err) 121 return false 122 } else if !cache.writeFile(target, part.FileName(), part) { 123 return false 124 } 125 } 126 } 127 } 128 129 func (cache *httpCache) writeFile(target *core.BuildTarget, file string, r io.Reader) bool { 130 outFile := path.Join(target.OutDir(), file) 131 if err := os.MkdirAll(path.Dir(outFile), core.DirPermissions); err != nil { 132 log.Errorf("Failed to create directory: %s", err) 133 return false 134 } 135 f, err := os.OpenFile(outFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, target.OutMode()) 136 if err != nil { 137 log.Errorf("Failed to open file: %s", err) 138 return false 139 } 140 defer f.Close() 141 if _, err := io.Copy(f, r); err != nil { 142 log.Errorf("Failed to write file: %s", err) 143 return false 144 } 145 log.Info("Retrieved %s from http cache", target.Label) 146 return true 147 } 148 149 func (cache *httpCache) Clean(target *core.BuildTarget) { 150 var reader io.Reader 151 artifact := path.Join( 152 core.OsArch, 153 target.Label.PackageName, 154 target.Label.Name, 155 ) 156 req, _ := http.NewRequest("DELETE", cache.URL+"/artifact/"+artifact, reader) 157 response, err := http.DefaultClient.Do(req) 158 if err != nil { 159 log.Warning("Failed to remove artifacts for %s from http cache: %s", target.Label, err) 160 } 161 response.Body.Close() 162 } 163 164 func (cache *httpCache) CleanAll() { 165 req, _ := http.NewRequest("DELETE", cache.URL, nil) 166 if _, err := http.DefaultClient.Do(req); err != nil { 167 log.Warning("Failed to remove artifacts from http cache: %s", err) 168 } 169 } 170 171 func (cache *httpCache) Shutdown() {} 172 173 func newHTTPCache(config *core.Configuration) *httpCache { 174 return &httpCache{ 175 URL: config.Cache.HTTPURL.String(), 176 Writeable: config.Cache.HTTPWriteable, 177 Timeout: time.Duration(config.Cache.HTTPTimeout), 178 } 179 }