github.com/grafana/pyroscope@v1.18.0/pkg/embedded/grafana/assets.go (about) 1 package grafana 2 3 import ( 4 "archive/tar" 5 "archive/zip" 6 "compress/gzip" 7 "context" 8 "crypto/sha256" 9 "encoding/hex" 10 "errors" 11 "fmt" 12 "io" 13 "io/fs" 14 "net/http" 15 "os" 16 "path/filepath" 17 "strings" 18 19 "github.com/go-kit/log" 20 "github.com/go-kit/log/level" 21 ) 22 23 type CompressType int 24 25 const ( 26 CompressTypeNone CompressType = iota 27 CompressTypeGzip 28 CompressTypeZip 29 ) 30 31 const ( 32 modeDir = 0755 33 modeFile = 0644 34 ) 35 36 type releaseArtifacts []releaseArtifact 37 38 func (releases releaseArtifacts) selectBy(os, arch string) *releaseArtifact { 39 var nonArch *releaseArtifact 40 for idx, r := range releases { 41 if r.OS == "" && r.Arch == "" && nonArch == nil { 42 nonArch = &releases[idx] 43 continue 44 } 45 if r.OS == os && r.Arch == arch { 46 return &r 47 } 48 } 49 return nonArch 50 } 51 52 type releaseArtifact struct { 53 URL string 54 Sha256Sum []byte 55 OS string 56 Arch string 57 CompressType CompressType 58 StripComponents int 59 } 60 61 func (release *releaseArtifact) download(ctx context.Context, logger log.Logger, destPath string) (string, error) { 62 targetPath := filepath.Join(destPath, "assets", hex.EncodeToString(release.Sha256Sum)) 63 64 // check if already exists 65 if len(release.Sha256Sum) > 0 { 66 stat, err := os.Stat(targetPath) 67 if err != nil { 68 if !os.IsNotExist(err) { 69 return "", err 70 } 71 } 72 if err == nil && stat.IsDir() { 73 level.Info(logger).Log("msg", "release exists already", "url", release.URL, "hash", hex.EncodeToString(release.Sha256Sum)) 74 return targetPath, nil 75 } 76 } 77 78 level.Info(logger).Log("msg", "download new release", "url", release.URL) 79 req, err := http.NewRequestWithContext(ctx, "GET", release.URL, nil) 80 req.Header.Set("User-Agent", "pyroscope/embedded-grafana") 81 if err != nil { 82 return "", err 83 } 84 85 resp, err := http.DefaultClient.Do(req) 86 if err != nil { 87 return "", err 88 } 89 defer resp.Body.Close() 90 91 if resp.StatusCode != http.StatusOK { 92 return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode) 93 } 94 95 file, err := os.CreateTemp("", "pyroscope-download") 96 if err != nil { 97 return "", err 98 } 99 defer os.Remove(file.Name()) 100 101 hash := sha256.New() 102 r := io.TeeReader(resp.Body, hash) 103 104 _, err = io.Copy(file, r) 105 if err != nil { 106 return "", err 107 } 108 109 err = file.Close() 110 if err != nil { 111 return "", err 112 } 113 114 actHashSum := hex.EncodeToString(hash.Sum(nil)) 115 if expHashSum := hex.EncodeToString(release.Sha256Sum); actHashSum != expHashSum { 116 return "", fmt.Errorf("hash mismatch: expected %s, got %s", expHashSum, actHashSum) 117 } 118 119 switch release.CompressType { 120 case CompressTypeNone: 121 return targetPath, os.Rename(file.Name(), targetPath) 122 case CompressTypeGzip: 123 file, err = os.Open(file.Name()) 124 if err != nil { 125 return "", err 126 } 127 defer file.Close() 128 129 err = extractTarGz(file, targetPath, release.StripComponents) 130 if err != nil { 131 return "", err 132 } 133 case CompressTypeZip: 134 file, err = os.Open(file.Name()) 135 if err != nil { 136 return "", err 137 } 138 defer file.Close() 139 140 stat, err := file.Stat() 141 if err != nil { 142 return "", err 143 } 144 145 err = extractZip(file, stat.Size(), targetPath, release.StripComponents) 146 if err != nil { 147 return "", err 148 } 149 } 150 151 return targetPath, nil 152 } 153 154 func clearPath(name string, destPath string, stripComponents int) string { 155 isSeparator := func(r rune) bool { 156 return r == os.PathSeparator 157 } 158 list := strings.FieldsFunc(name, isSeparator) 159 if len(list) > stripComponents { 160 list = list[stripComponents:] 161 } 162 return filepath.Join(append([]string{destPath}, list...)...) 163 } 164 165 func extractZip(zipStream io.ReaderAt, size int64, destPath string, stripComponents int) error { 166 zipReader, err := zip.NewReader(zipStream, size) 167 if err != nil { 168 return fmt.Errorf("ExtractZip: NewReader failed: %s", err.Error()) 169 } 170 171 for _, f := range zipReader.File { 172 p := clearPath(f.Name, destPath, stripComponents) 173 if f.FileInfo().IsDir() { 174 err := os.MkdirAll(p, modeDir) 175 if err != nil { 176 return fmt.Errorf("ExtractZip: MkdirAll() failed: %s", err.Error()) 177 } 178 continue 179 } 180 181 dir, _ := filepath.Split(p) 182 if _, err := os.Stat(dir); os.IsNotExist(err) { 183 if err := os.MkdirAll(dir, modeDir); err != nil { 184 return fmt.Errorf("ExtractZip: MkdirAll() failed: %s", err.Error()) 185 } 186 } 187 188 fileInArchive, err := f.Open() 189 if err != nil { 190 return fmt.Errorf("ExtractZip: Open() failed: %s", err.Error()) 191 } 192 193 outFile, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_TRUNC, f.FileInfo().Mode()) 194 if err != nil { 195 return fmt.Errorf("ExtractZip: OpenFile() failed: %s", err.Error()) 196 } 197 if _, err := io.Copy(outFile, fileInArchive); err != nil { 198 return fmt.Errorf("ExtractZip: Copy() failed: %s", err.Error()) 199 } 200 } 201 202 return nil 203 204 } 205 206 func extractTarGz(gzipStream io.Reader, destPath string, stripComponents int) error { 207 uncompressedStream, err := gzip.NewReader(gzipStream) 208 if err != nil { 209 return errors.New("ExtractTarGz: NewReader failed") 210 } 211 212 tarReader := tar.NewReader(uncompressedStream) 213 214 for { 215 header, err := tarReader.Next() 216 217 if err == io.EOF { 218 break 219 } 220 221 if err != nil { 222 return fmt.Errorf("ExtractTarGz: Next() failed: %s", err.Error()) 223 } 224 225 p := clearPath(header.Name, destPath, stripComponents) 226 switch header.Typeflag { 227 case tar.TypeDir: 228 if err := os.MkdirAll(p, modeDir); err != nil { 229 return fmt.Errorf("ExtractTarGz: Mkdir() failed: %s", err.Error()) 230 } 231 case tar.TypeReg: 232 dir, _ := filepath.Split(p) 233 if _, err := os.Stat(dir); os.IsNotExist(err) { 234 if err := os.MkdirAll(dir, modeDir); err != nil { 235 return fmt.Errorf("ExtractTarGz: MkdirAll() failed: %s", err.Error()) 236 } 237 } 238 outFile, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fs.FileMode(header.Mode)) 239 if err != nil { 240 return fmt.Errorf("ExtractTarGz: OpenFile() failed: %s", err.Error()) 241 } 242 if _, err := io.Copy(outFile, tarReader); err != nil { 243 return fmt.Errorf("ExtractTarGz: Copy() failed: %s", err.Error()) 244 } 245 outFile.Close() 246 247 default: 248 return fmt.Errorf( 249 "ExtractTarGz: unknown type: %v in %s", 250 header.Typeflag, 251 header.Name) 252 } 253 } 254 255 return nil 256 }