github.com/thanos-io/thanos@v0.32.5/pkg/api/api.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 // Copyright 2016 The Prometheus Authors 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 // This package is a modified copy from 18 // github.com/prometheus/prometheus/web/api/v1@2121b4628baa7d9d9406aa468712a6a332e77aff. 19 20 package api 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "net/http" 26 "os" 27 "runtime" 28 "time" 29 30 "github.com/go-kit/log" 31 "github.com/go-kit/log/level" 32 "github.com/klauspost/compress/gzhttp" 33 "github.com/opentracing/opentracing-go" 34 "github.com/prometheus/common/route" 35 "github.com/prometheus/common/version" 36 37 extpromhttp "github.com/thanos-io/thanos/pkg/extprom/http" 38 "github.com/thanos-io/thanos/pkg/logging" 39 "github.com/thanos-io/thanos/pkg/server/http/middleware" 40 "github.com/thanos-io/thanos/pkg/tracing" 41 ) 42 43 type status string 44 45 const ( 46 StatusSuccess status = "success" 47 StatusError status = "error" 48 ) 49 50 type ErrorType string 51 52 const ( 53 ErrorNone ErrorType = "" 54 ErrorTimeout ErrorType = "timeout" 55 ErrorCanceled ErrorType = "canceled" 56 ErrorExec ErrorType = "execution" 57 ErrorBadData ErrorType = "bad_data" 58 ErrorInternal ErrorType = "internal" 59 ) 60 61 var corsHeaders = map[string]string{ 62 "Access-Control-Allow-Headers": "Accept, Accept-Encoding, Authorization, Content-Type, Origin", 63 "Access-Control-Allow-Methods": "GET, OPTIONS", 64 "Access-Control-Allow-Origin": "*", 65 "Access-Control-Expose-Headers": "Date", 66 } 67 68 // ThanosVersion contains build information about Thanos. 69 type ThanosVersion struct { 70 Version string `json:"version"` 71 Revision string `json:"revision"` 72 Branch string `json:"branch"` 73 BuildUser string `json:"buildUser"` 74 BuildDate string `json:"buildDate"` 75 GoVersion string `json:"goVersion"` 76 } 77 78 var BuildInfo = &ThanosVersion{ 79 Version: version.Version, 80 Revision: version.Revision, 81 Branch: version.Branch, 82 BuildUser: version.BuildUser, 83 BuildDate: version.BuildDate, 84 GoVersion: version.GoVersion, 85 } 86 87 type ApiError struct { 88 Typ ErrorType 89 Err error 90 } 91 92 func (e *ApiError) Error() string { 93 return fmt.Sprintf("%s: %s", e.Typ, e.Err) 94 } 95 96 // RuntimeInfo contains runtime information about Thanos. 97 type RuntimeInfo struct { 98 StartTime time.Time `json:"startTime"` 99 CWD string `json:"CWD"` 100 GoroutineCount int `json:"goroutineCount"` 101 GOMAXPROCS int `json:"GOMAXPROCS"` 102 GOGC string `json:"GOGC"` 103 GODEBUG string `json:"GODEBUG"` 104 } 105 106 // RuntimeInfoFn returns updated runtime information about Thanos. 107 type RuntimeInfoFn func() RuntimeInfo 108 109 type response struct { 110 Status status `json:"status"` 111 Data interface{} `json:"data,omitempty"` 112 ErrorType ErrorType `json:"errorType,omitempty"` 113 Error string `json:"error,omitempty"` 114 Warnings []string `json:"warnings,omitempty"` 115 } 116 117 // SetCORS enables cross-site script calls. 118 func SetCORS(w http.ResponseWriter) { 119 for h, v := range corsHeaders { 120 w.Header().Set(h, v) 121 } 122 } 123 124 type ApiFunc func(r *http.Request) (interface{}, []error, *ApiError, func()) 125 126 type BaseAPI struct { 127 logger log.Logger 128 flagsMap map[string]string 129 runtimeInfo RuntimeInfoFn 130 buildInfo *ThanosVersion 131 Now func() time.Time 132 disableCORS bool 133 } 134 135 // NewBaseAPI returns a new initialized BaseAPI type. 136 func NewBaseAPI(logger log.Logger, disableCORS bool, flagsMap map[string]string) *BaseAPI { 137 138 return &BaseAPI{ 139 logger: logger, 140 flagsMap: flagsMap, 141 runtimeInfo: GetRuntimeInfoFunc(logger), 142 buildInfo: BuildInfo, 143 disableCORS: disableCORS, 144 Now: time.Now, 145 } 146 } 147 148 // Register registers the common API endpoints. 149 func (api *BaseAPI) Register(r *route.Router, tracer opentracing.Tracer, logger log.Logger, ins extpromhttp.InstrumentationMiddleware, logMiddleware *logging.HTTPServerMiddleware) { 150 instr := GetInstr(tracer, logger, ins, logMiddleware, api.disableCORS) 151 152 r.Options("/*path", instr("options", api.options)) 153 154 r.Get("/status/flags", instr("status_flags", api.flags)) 155 r.Get("/status/runtimeinfo", instr("status_runtime", api.serveRuntimeInfo)) 156 r.Get("/status/buildinfo", instr("status_build", api.serveBuildInfo)) 157 } 158 159 func (api *BaseAPI) options(r *http.Request) (interface{}, []error, *ApiError, func()) { 160 return nil, nil, nil, func() {} 161 } 162 163 func (api *BaseAPI) flags(r *http.Request) (interface{}, []error, *ApiError, func()) { 164 return api.flagsMap, nil, nil, func() {} 165 } 166 167 func (api *BaseAPI) serveRuntimeInfo(r *http.Request) (interface{}, []error, *ApiError, func()) { 168 return api.runtimeInfo(), nil, nil, func() {} 169 } 170 171 func (api *BaseAPI) serveBuildInfo(r *http.Request) (interface{}, []error, *ApiError, func()) { 172 return api.buildInfo, nil, nil, func() {} 173 } 174 175 func GetRuntimeInfoFunc(logger log.Logger) RuntimeInfoFn { 176 CWD, err := os.Getwd() 177 if err != nil { 178 CWD = "<error retrieving current working directory>" 179 level.Warn(logger).Log("msg", "failed to retrieve current working directory", "err", err) 180 } 181 182 birth := time.Now() 183 184 return func() RuntimeInfo { 185 return RuntimeInfo{ 186 StartTime: birth, 187 CWD: CWD, 188 GoroutineCount: runtime.NumGoroutine(), 189 GOMAXPROCS: runtime.GOMAXPROCS(0), 190 GOGC: os.Getenv("GOGC"), 191 GODEBUG: os.Getenv("GODEBUG"), 192 } 193 } 194 } 195 196 type InstrFunc func(name string, f ApiFunc) http.HandlerFunc 197 198 // GetInstr returns a http HandlerFunc with the instrumentation middleware. 199 func GetInstr( 200 tracer opentracing.Tracer, 201 logger log.Logger, 202 ins extpromhttp.InstrumentationMiddleware, 203 logMiddleware *logging.HTTPServerMiddleware, 204 disableCORS bool, 205 ) InstrFunc { 206 instr := func(name string, f ApiFunc) http.HandlerFunc { 207 hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 208 if !disableCORS { 209 SetCORS(w) 210 } 211 if data, warnings, err, releaseResources := f(r); err != nil { 212 RespondError(w, err, data) 213 releaseResources() 214 } else if data != nil { 215 Respond(w, data, warnings) 216 releaseResources() 217 } else { 218 w.WriteHeader(http.StatusNoContent) 219 releaseResources() 220 } 221 }) 222 223 return tracing.HTTPMiddleware(tracer, name, logger, 224 ins.NewHandler(name, 225 gzhttp.GzipHandler( 226 middleware.RequestID( 227 logMiddleware.HTTPMiddleware(name, hf), 228 ), 229 ), 230 ), 231 ) 232 } 233 return instr 234 } 235 236 func Respond(w http.ResponseWriter, data interface{}, warnings []error) { 237 w.Header().Set("Content-Type", "application/json") 238 if len(warnings) > 0 { 239 w.Header().Set("Cache-Control", "no-store") 240 } 241 w.WriteHeader(http.StatusOK) 242 243 resp := &response{ 244 Status: StatusSuccess, 245 Data: data, 246 } 247 for _, warn := range warnings { 248 resp.Warnings = append(resp.Warnings, warn.Error()) 249 } 250 _ = json.NewEncoder(w).Encode(resp) 251 } 252 253 func RespondError(w http.ResponseWriter, apiErr *ApiError, data interface{}) { 254 w.Header().Set("Content-Type", "application/json") 255 w.Header().Set("Cache-Control", "no-store") 256 257 var code int 258 switch apiErr.Typ { 259 case ErrorBadData: 260 code = http.StatusBadRequest 261 case ErrorExec: 262 code = 422 263 case ErrorCanceled, ErrorTimeout: 264 code = http.StatusServiceUnavailable 265 case ErrorInternal: 266 code = http.StatusInternalServerError 267 default: 268 code = http.StatusInternalServerError 269 } 270 w.WriteHeader(code) 271 272 _ = json.NewEncoder(w).Encode(&response{ 273 Status: StatusError, 274 ErrorType: apiErr.Typ, 275 Error: apiErr.Err.Error(), 276 Data: data, 277 }) 278 }