router.go 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824
  1. package costmodel
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "os"
  9. "path"
  10. "reflect"
  11. "regexp"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "time"
  16. "github.com/microcosm-cc/bluemonday"
  17. "github.com/opencost/opencost/pkg/config"
  18. "github.com/opencost/opencost/pkg/kubeconfig"
  19. "github.com/opencost/opencost/pkg/metrics"
  20. "github.com/opencost/opencost/pkg/services"
  21. "github.com/opencost/opencost/pkg/util/httputil"
  22. "github.com/opencost/opencost/pkg/util/timeutil"
  23. "github.com/opencost/opencost/pkg/util/watcher"
  24. "github.com/opencost/opencost/pkg/version"
  25. "github.com/spf13/viper"
  26. v1 "k8s.io/api/core/v1"
  27. "github.com/julienschmidt/httprouter"
  28. sentry "github.com/getsentry/sentry-go"
  29. "github.com/opencost/opencost/pkg/cloud"
  30. "github.com/opencost/opencost/pkg/clustercache"
  31. "github.com/opencost/opencost/pkg/costmodel/clusters"
  32. "github.com/opencost/opencost/pkg/env"
  33. "github.com/opencost/opencost/pkg/errors"
  34. "github.com/opencost/opencost/pkg/kubecost"
  35. "github.com/opencost/opencost/pkg/log"
  36. "github.com/opencost/opencost/pkg/prom"
  37. "github.com/opencost/opencost/pkg/thanos"
  38. "github.com/opencost/opencost/pkg/util/json"
  39. prometheus "github.com/prometheus/client_golang/api"
  40. prometheusAPI "github.com/prometheus/client_golang/api/prometheus/v1"
  41. appsv1 "k8s.io/api/apps/v1"
  42. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  43. "github.com/patrickmn/go-cache"
  44. "k8s.io/client-go/kubernetes"
  45. )
  46. var sanitizePolicy = bluemonday.UGCPolicy()
  47. const (
  48. RFC3339Milli = "2006-01-02T15:04:05.000Z"
  49. maxCacheMinutes1d = 11
  50. maxCacheMinutes2d = 17
  51. maxCacheMinutes7d = 37
  52. maxCacheMinutes30d = 137
  53. CustomPricingSetting = "CustomPricing"
  54. DiscountSetting = "Discount"
  55. epRules = apiPrefix + "/rules"
  56. LogSeparator = "+-------------------------------------------------------------------------------------"
  57. )
  58. var (
  59. // gitCommit is set by the build system
  60. gitCommit string
  61. // ANSIRegex matches ANSI escape and colors https://en.wikipedia.org/wiki/ANSI_escape_code
  62. ANSIRegex = regexp.MustCompile("\x1b\\[[0-9;]*m")
  63. )
  64. // Accesses defines a singleton application instance, providing access to
  65. // Prometheus, Kubernetes, the cloud provider, and caches.
  66. type Accesses struct {
  67. Router *httprouter.Router
  68. PrometheusClient prometheus.Client
  69. ThanosClient prometheus.Client
  70. KubeClientSet kubernetes.Interface
  71. ClusterCache clustercache.ClusterCache
  72. ClusterMap clusters.ClusterMap
  73. CloudProvider cloud.Provider
  74. ConfigFileManager *config.ConfigFileManager
  75. ClusterInfoProvider clusters.ClusterInfoProvider
  76. Model *CostModel
  77. MetricsEmitter *CostModelMetricsEmitter
  78. OutOfClusterCache *cache.Cache
  79. AggregateCache *cache.Cache
  80. CostDataCache *cache.Cache
  81. ClusterCostsCache *cache.Cache
  82. CacheExpiration map[time.Duration]time.Duration
  83. AggAPI Aggregator
  84. // SettingsCache stores current state of app settings
  85. SettingsCache *cache.Cache
  86. // settingsSubscribers tracks channels through which changes to different
  87. // settings will be published in a pub/sub model
  88. settingsSubscribers map[string][]chan string
  89. settingsMutex sync.Mutex
  90. // registered http service instances
  91. httpServices services.HTTPServices
  92. }
  93. // GetPrometheusClient decides whether the default Prometheus client or the Thanos client
  94. // should be used.
  95. func (a *Accesses) GetPrometheusClient(remote bool) prometheus.Client {
  96. // Use Thanos Client if it exists (enabled) and remote flag set
  97. var pc prometheus.Client
  98. if remote && a.ThanosClient != nil {
  99. pc = a.ThanosClient
  100. } else {
  101. pc = a.PrometheusClient
  102. }
  103. return pc
  104. }
  105. // GetCacheExpiration looks up and returns custom cache expiration for the given duration.
  106. // If one does not exists, it returns the default cache expiration, which is defined by
  107. // the particular cache.
  108. func (a *Accesses) GetCacheExpiration(dur time.Duration) time.Duration {
  109. if expiration, ok := a.CacheExpiration[dur]; ok {
  110. return expiration
  111. }
  112. return cache.DefaultExpiration
  113. }
  114. // GetCacheRefresh determines how long to wait before refreshing the cache for the given duration,
  115. // which is done 1 minute before we expect the cache to expire, or 1 minute if expiration is
  116. // not found or is less than 2 minutes.
  117. func (a *Accesses) GetCacheRefresh(dur time.Duration) time.Duration {
  118. expiry := a.GetCacheExpiration(dur).Minutes()
  119. if expiry <= 2.0 {
  120. return time.Minute
  121. }
  122. mins := time.Duration(expiry/2.0) * time.Minute
  123. return mins
  124. }
  125. func (a *Accesses) ClusterCostsFromCacheHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  126. w.Header().Set("Content-Type", "application/json")
  127. duration := 24 * time.Hour
  128. offset := time.Minute
  129. durationHrs := "24h"
  130. fmtOffset := "1m"
  131. pClient := a.GetPrometheusClient(true)
  132. key := fmt.Sprintf("%s:%s", durationHrs, fmtOffset)
  133. if data, valid := a.ClusterCostsCache.Get(key); valid {
  134. clusterCosts := data.(map[string]*ClusterCosts)
  135. w.Write(WrapDataWithMessage(clusterCosts, nil, "clusterCosts cache hit"))
  136. } else {
  137. data, err := a.ComputeClusterCosts(pClient, a.CloudProvider, duration, offset, true)
  138. w.Write(WrapDataWithMessage(data, err, fmt.Sprintf("clusterCosts cache miss: %s", key)))
  139. }
  140. }
  141. type Response struct {
  142. Code int `json:"code"`
  143. Status string `json:"status"`
  144. Data interface{} `json:"data"`
  145. Message string `json:"message,omitempty"`
  146. Warning string `json:"warning,omitempty"`
  147. }
  148. // FilterFunc is a filter that returns true iff the given CostData should be filtered out, and the environment that was used as the filter criteria, if it was an aggregate
  149. type FilterFunc func(*CostData) (bool, string)
  150. // FilterCostData allows through only CostData that matches all the given filter functions
  151. func FilterCostData(data map[string]*CostData, retains []FilterFunc, filters []FilterFunc) (map[string]*CostData, int, map[string]int) {
  152. result := make(map[string]*CostData)
  153. filteredEnvironments := make(map[string]int)
  154. filteredContainers := 0
  155. DataLoop:
  156. for key, datum := range data {
  157. for _, rf := range retains {
  158. if ok, _ := rf(datum); ok {
  159. result[key] = datum
  160. // if any retain function passes, the data is retained and move on
  161. continue DataLoop
  162. }
  163. }
  164. for _, ff := range filters {
  165. if ok, environment := ff(datum); !ok {
  166. if environment != "" {
  167. filteredEnvironments[environment]++
  168. }
  169. filteredContainers++
  170. // if any filter function check fails, move on to the next datum
  171. continue DataLoop
  172. }
  173. }
  174. result[key] = datum
  175. }
  176. return result, filteredContainers, filteredEnvironments
  177. }
  178. func filterFields(fields string, data map[string]*CostData) map[string]CostData {
  179. fs := strings.Split(fields, ",")
  180. fmap := make(map[string]bool)
  181. for _, f := range fs {
  182. fieldNameLower := strings.ToLower(f) // convert to go struct name by uppercasing first letter
  183. log.Debugf("to delete: %s", fieldNameLower)
  184. fmap[fieldNameLower] = true
  185. }
  186. filteredData := make(map[string]CostData)
  187. for cname, costdata := range data {
  188. s := reflect.TypeOf(*costdata)
  189. val := reflect.ValueOf(*costdata)
  190. costdata2 := CostData{}
  191. cd2 := reflect.New(reflect.Indirect(reflect.ValueOf(costdata2)).Type()).Elem()
  192. n := s.NumField()
  193. for i := 0; i < n; i++ {
  194. field := s.Field(i)
  195. value := val.Field(i)
  196. value2 := cd2.Field(i)
  197. if _, ok := fmap[strings.ToLower(field.Name)]; !ok {
  198. value2.Set(reflect.Value(value))
  199. }
  200. }
  201. filteredData[cname] = cd2.Interface().(CostData)
  202. }
  203. return filteredData
  204. }
  205. func normalizeTimeParam(param string) (string, error) {
  206. if param == "" {
  207. return "", fmt.Errorf("invalid time param")
  208. }
  209. // convert days to hours
  210. if param[len(param)-1:] == "d" {
  211. count := param[:len(param)-1]
  212. val, err := strconv.ParseInt(count, 10, 64)
  213. if err != nil {
  214. return "", err
  215. }
  216. val = val * 24
  217. param = fmt.Sprintf("%dh", val)
  218. }
  219. return param, nil
  220. }
  221. // ParsePercentString takes a string of expected format "N%" and returns a floating point 0.0N.
  222. // If the "%" symbol is missing, it just returns 0.0N. Empty string is interpreted as "0%" and
  223. // return 0.0.
  224. func ParsePercentString(percentStr string) (float64, error) {
  225. if len(percentStr) == 0 {
  226. return 0.0, nil
  227. }
  228. if percentStr[len(percentStr)-1:] == "%" {
  229. percentStr = percentStr[:len(percentStr)-1]
  230. }
  231. discount, err := strconv.ParseFloat(percentStr, 64)
  232. if err != nil {
  233. return 0.0, err
  234. }
  235. discount *= 0.01
  236. return discount, nil
  237. }
  238. func WrapData(data interface{}, err error) []byte {
  239. var resp []byte
  240. if err != nil {
  241. log.Errorf("Error returned to client: %s", err.Error())
  242. resp, _ = json.Marshal(&Response{
  243. Code: http.StatusInternalServerError,
  244. Status: "error",
  245. Message: err.Error(),
  246. Data: data,
  247. })
  248. } else {
  249. resp, _ = json.Marshal(&Response{
  250. Code: http.StatusOK,
  251. Status: "success",
  252. Data: data,
  253. })
  254. }
  255. return resp
  256. }
  257. func WrapDataWithMessage(data interface{}, err error, message string) []byte {
  258. var resp []byte
  259. if err != nil {
  260. log.Errorf("Error returned to client: %s", err.Error())
  261. resp, _ = json.Marshal(&Response{
  262. Code: http.StatusInternalServerError,
  263. Status: "error",
  264. Message: err.Error(),
  265. Data: data,
  266. })
  267. } else {
  268. resp, _ = json.Marshal(&Response{
  269. Code: http.StatusOK,
  270. Status: "success",
  271. Data: data,
  272. Message: message,
  273. })
  274. }
  275. return resp
  276. }
  277. func WrapDataWithWarning(data interface{}, err error, warning string) []byte {
  278. var resp []byte
  279. if err != nil {
  280. log.Errorf("Error returned to client: %s", err.Error())
  281. resp, _ = json.Marshal(&Response{
  282. Code: http.StatusInternalServerError,
  283. Status: "error",
  284. Message: err.Error(),
  285. Warning: warning,
  286. Data: data,
  287. })
  288. } else {
  289. resp, _ = json.Marshal(&Response{
  290. Code: http.StatusOK,
  291. Status: "success",
  292. Data: data,
  293. Warning: warning,
  294. })
  295. }
  296. return resp
  297. }
  298. func WrapDataWithMessageAndWarning(data interface{}, err error, message, warning string) []byte {
  299. var resp []byte
  300. if err != nil {
  301. log.Errorf("Error returned to client: %s", err.Error())
  302. resp, _ = json.Marshal(&Response{
  303. Code: http.StatusInternalServerError,
  304. Status: "error",
  305. Message: err.Error(),
  306. Warning: warning,
  307. Data: data,
  308. })
  309. } else {
  310. resp, _ = json.Marshal(&Response{
  311. Code: http.StatusOK,
  312. Status: "success",
  313. Data: data,
  314. Message: message,
  315. Warning: warning,
  316. })
  317. }
  318. return resp
  319. }
  320. // wrapAsObjectItems wraps a slice of items into an object containing a single items list
  321. // allows our k8s proxy methods to emulate a List() request to k8s API
  322. func wrapAsObjectItems(items interface{}) map[string]interface{} {
  323. return map[string]interface{}{
  324. "items": items,
  325. }
  326. }
  327. // RefreshPricingData needs to be called when a new node joins the fleet, since we cache the relevant subsets of pricing data to avoid storing the whole thing.
  328. func (a *Accesses) RefreshPricingData(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  329. w.Header().Set("Content-Type", "application/json")
  330. w.Header().Set("Access-Control-Allow-Origin", "*")
  331. err := a.CloudProvider.DownloadPricingData()
  332. if err != nil {
  333. log.Errorf("Error refreshing pricing data: %s", err.Error())
  334. }
  335. w.Write(WrapData(nil, err))
  336. }
  337. func (a *Accesses) CostDataModel(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  338. w.Header().Set("Content-Type", "application/json")
  339. w.Header().Set("Access-Control-Allow-Origin", "*")
  340. window := r.URL.Query().Get("timeWindow")
  341. offset := r.URL.Query().Get("offset")
  342. fields := r.URL.Query().Get("filterFields")
  343. namespace := r.URL.Query().Get("namespace")
  344. if offset != "" {
  345. offset = "offset " + offset
  346. }
  347. data, err := a.Model.ComputeCostData(a.PrometheusClient, a.CloudProvider, window, offset, namespace)
  348. if fields != "" {
  349. filteredData := filterFields(fields, data)
  350. w.Write(WrapData(filteredData, err))
  351. } else {
  352. w.Write(WrapData(data, err))
  353. }
  354. }
  355. func (a *Accesses) ClusterCosts(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  356. w.Header().Set("Content-Type", "application/json")
  357. w.Header().Set("Access-Control-Allow-Origin", "*")
  358. window := r.URL.Query().Get("window")
  359. offset := r.URL.Query().Get("offset")
  360. if window == "" {
  361. w.Write(WrapData(nil, fmt.Errorf("missing window argument")))
  362. return
  363. }
  364. windowDur, err := timeutil.ParseDuration(window)
  365. if err != nil {
  366. w.Write(WrapData(nil, fmt.Errorf("error parsing window (%s): %s", window, err)))
  367. return
  368. }
  369. // offset is not a required parameter
  370. var offsetDur time.Duration
  371. if offset != "" {
  372. offsetDur, err = timeutil.ParseDuration(offset)
  373. if err != nil {
  374. w.Write(WrapData(nil, fmt.Errorf("error parsing offset (%s): %s", offset, err)))
  375. return
  376. }
  377. }
  378. useThanos, _ := strconv.ParseBool(r.URL.Query().Get("multi"))
  379. if useThanos && !thanos.IsEnabled() {
  380. w.Write(WrapData(nil, fmt.Errorf("Multi=true while Thanos is not enabled.")))
  381. return
  382. }
  383. var client prometheus.Client
  384. if useThanos {
  385. client = a.ThanosClient
  386. offsetDur = thanos.OffsetDuration()
  387. } else {
  388. client = a.PrometheusClient
  389. }
  390. data, err := a.ComputeClusterCosts(client, a.CloudProvider, windowDur, offsetDur, true)
  391. w.Write(WrapData(data, err))
  392. }
  393. func (a *Accesses) ClusterCostsOverTime(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  394. w.Header().Set("Content-Type", "application/json")
  395. w.Header().Set("Access-Control-Allow-Origin", "*")
  396. start := r.URL.Query().Get("start")
  397. end := r.URL.Query().Get("end")
  398. window := r.URL.Query().Get("window")
  399. offset := r.URL.Query().Get("offset")
  400. if window == "" {
  401. w.Write(WrapData(nil, fmt.Errorf("missing window argument")))
  402. return
  403. }
  404. windowDur, err := timeutil.ParseDuration(window)
  405. if err != nil {
  406. w.Write(WrapData(nil, fmt.Errorf("error parsing window (%s): %s", window, err)))
  407. return
  408. }
  409. // offset is not a required parameter
  410. var offsetDur time.Duration
  411. if offset != "" {
  412. offsetDur, err = timeutil.ParseDuration(offset)
  413. if err != nil {
  414. w.Write(WrapData(nil, fmt.Errorf("error parsing offset (%s): %s", offset, err)))
  415. return
  416. }
  417. }
  418. data, err := ClusterCostsOverTime(a.PrometheusClient, a.CloudProvider, start, end, windowDur, offsetDur)
  419. w.Write(WrapData(data, err))
  420. }
  421. func (a *Accesses) CostDataModelRange(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  422. w.Header().Set("Content-Type", "application/json")
  423. w.Header().Set("Access-Control-Allow-Origin", "*")
  424. startStr := r.URL.Query().Get("start")
  425. endStr := r.URL.Query().Get("end")
  426. windowStr := r.URL.Query().Get("window")
  427. fields := r.URL.Query().Get("filterFields")
  428. namespace := r.URL.Query().Get("namespace")
  429. cluster := r.URL.Query().Get("cluster")
  430. remote := r.URL.Query().Get("remote")
  431. remoteEnabled := env.IsRemoteEnabled() && remote != "false"
  432. layout := "2006-01-02T15:04:05.000Z"
  433. start, err := time.Parse(layout, startStr)
  434. if err != nil {
  435. w.Write(WrapDataWithMessage(nil, fmt.Errorf("invalid start date: %s", startStr), fmt.Sprintf("invalid start date: %s", startStr)))
  436. return
  437. }
  438. end, err := time.Parse(layout, endStr)
  439. if err != nil {
  440. w.Write(WrapDataWithMessage(nil, fmt.Errorf("invalid end date: %s", endStr), fmt.Sprintf("invalid end date: %s", endStr)))
  441. return
  442. }
  443. window := kubecost.NewWindow(&start, &end)
  444. if window.IsOpen() || !window.HasDuration() || window.IsNegative() {
  445. w.Write(WrapDataWithMessage(nil, fmt.Errorf("invalid date range: %s", window), fmt.Sprintf("invalid date range: %s", window)))
  446. return
  447. }
  448. resolution := time.Hour
  449. if resDur, err := time.ParseDuration(windowStr); err == nil {
  450. resolution = resDur
  451. }
  452. // Use Thanos Client if it exists (enabled) and remote flag set
  453. var pClient prometheus.Client
  454. if remote != "false" && a.ThanosClient != nil {
  455. pClient = a.ThanosClient
  456. } else {
  457. pClient = a.PrometheusClient
  458. }
  459. data, err := a.Model.ComputeCostDataRange(pClient, a.CloudProvider, window, resolution, namespace, cluster, remoteEnabled)
  460. if err != nil {
  461. w.Write(WrapData(nil, err))
  462. }
  463. if fields != "" {
  464. filteredData := filterFields(fields, data)
  465. w.Write(WrapData(filteredData, err))
  466. } else {
  467. w.Write(WrapData(data, err))
  468. }
  469. }
  470. func parseAggregations(customAggregation, aggregator, filterType string) (string, []string, string) {
  471. var key string
  472. var filter string
  473. var val []string
  474. if customAggregation != "" {
  475. key = customAggregation
  476. filter = filterType
  477. val = strings.Split(customAggregation, ",")
  478. } else {
  479. aggregations := strings.Split(aggregator, ",")
  480. for i, agg := range aggregations {
  481. aggregations[i] = "kubernetes_" + agg
  482. }
  483. key = strings.Join(aggregations, ",")
  484. filter = "kubernetes_" + filterType
  485. val = aggregations
  486. }
  487. return key, val, filter
  488. }
  489. func (a *Accesses) GetAllNodePricing(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  490. w.Header().Set("Content-Type", "application/json")
  491. w.Header().Set("Access-Control-Allow-Origin", "*")
  492. data, err := a.CloudProvider.AllNodePricing()
  493. w.Write(WrapData(data, err))
  494. }
  495. func (a *Accesses) GetConfigs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  496. w.Header().Set("Content-Type", "application/json")
  497. w.Header().Set("Access-Control-Allow-Origin", "*")
  498. data, err := a.CloudProvider.GetConfig()
  499. w.Write(WrapData(data, err))
  500. }
  501. func (a *Accesses) UpdateSpotInfoConfigs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  502. w.Header().Set("Content-Type", "application/json")
  503. w.Header().Set("Access-Control-Allow-Origin", "*")
  504. data, err := a.CloudProvider.UpdateConfig(r.Body, cloud.SpotInfoUpdateType)
  505. if err != nil {
  506. w.Write(WrapData(data, err))
  507. return
  508. }
  509. w.Write(WrapData(data, err))
  510. err = a.CloudProvider.DownloadPricingData()
  511. if err != nil {
  512. log.Errorf("Error redownloading data on config update: %s", err.Error())
  513. }
  514. return
  515. }
  516. func (a *Accesses) UpdateAthenaInfoConfigs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  517. w.Header().Set("Content-Type", "application/json")
  518. w.Header().Set("Access-Control-Allow-Origin", "*")
  519. data, err := a.CloudProvider.UpdateConfig(r.Body, cloud.AthenaInfoUpdateType)
  520. if err != nil {
  521. w.Write(WrapData(data, err))
  522. return
  523. }
  524. w.Write(WrapData(data, err))
  525. return
  526. }
  527. func (a *Accesses) UpdateBigQueryInfoConfigs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  528. w.Header().Set("Content-Type", "application/json")
  529. w.Header().Set("Access-Control-Allow-Origin", "*")
  530. data, err := a.CloudProvider.UpdateConfig(r.Body, cloud.BigqueryUpdateType)
  531. if err != nil {
  532. w.Write(WrapData(data, err))
  533. return
  534. }
  535. w.Write(WrapData(data, err))
  536. return
  537. }
  538. func (a *Accesses) UpdateAzureStorageConfigs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  539. w.Header().Set("Content-Type", "application/json")
  540. w.Header().Set("Access-Control-Allow-Origin", "*")
  541. data, err := a.CloudProvider.UpdateConfig(r.Body, cloud.AzureStorageUpdateType)
  542. if err != nil {
  543. w.Write(WrapData(data, err))
  544. return
  545. }
  546. w.Write(WrapData(data, err))
  547. return
  548. }
  549. func (a *Accesses) UpdateConfigByKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  550. w.Header().Set("Content-Type", "application/json")
  551. w.Header().Set("Access-Control-Allow-Origin", "*")
  552. data, err := a.CloudProvider.UpdateConfig(r.Body, "")
  553. if err != nil {
  554. w.Write(WrapData(data, err))
  555. return
  556. }
  557. w.Write(WrapData(data, err))
  558. return
  559. }
  560. func (a *Accesses) ManagementPlatform(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  561. w.Header().Set("Content-Type", "application/json")
  562. w.Header().Set("Access-Control-Allow-Origin", "*")
  563. data, err := a.CloudProvider.GetManagementPlatform()
  564. if err != nil {
  565. w.Write(WrapData(data, err))
  566. return
  567. }
  568. w.Write(WrapData(data, err))
  569. return
  570. }
  571. func (a *Accesses) ClusterInfo(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  572. w.Header().Set("Content-Type", "application/json")
  573. w.Header().Set("Access-Control-Allow-Origin", "*")
  574. data := a.ClusterInfoProvider.GetClusterInfo()
  575. w.Write(WrapData(data, nil))
  576. }
  577. func (a *Accesses) GetClusterInfoMap(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  578. w.Header().Set("Content-Type", "application/json")
  579. w.Header().Set("Access-Control-Allow-Origin", "*")
  580. data := a.ClusterMap.AsMap()
  581. w.Write(WrapData(data, nil))
  582. }
  583. func (a *Accesses) GetServiceAccountStatus(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  584. w.Header().Set("Content-Type", "application/json")
  585. w.Header().Set("Access-Control-Allow-Origin", "*")
  586. w.Write(WrapData(a.CloudProvider.ServiceAccountStatus(), nil))
  587. }
  588. func (a *Accesses) GetPricingSourceStatus(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  589. w.Header().Set("Content-Type", "application/json")
  590. w.Header().Set("Access-Control-Allow-Origin", "*")
  591. w.Write(WrapData(a.CloudProvider.PricingSourceStatus(), nil))
  592. }
  593. func (a *Accesses) GetPricingSourceCounts(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  594. w.Header().Set("Content-Type", "application/json")
  595. w.Header().Set("Access-Control-Allow-Origin", "*")
  596. w.Write(WrapData(a.Model.GetPricingSourceCounts()))
  597. }
  598. func (a *Accesses) GetPricingSourceSummary(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
  599. w.Header().Set("Content-Type", "application/json")
  600. w.Header().Set("Access-Control-Allow-Origin", "*")
  601. data := a.CloudProvider.PricingSourceSummary()
  602. w.Write(WrapData(data, nil))
  603. }
  604. func (a *Accesses) GetPrometheusMetadata(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  605. w.Header().Set("Content-Type", "application/json")
  606. w.Header().Set("Access-Control-Allow-Origin", "*")
  607. w.Write(WrapData(prom.Validate(a.PrometheusClient)))
  608. }
  609. func (a *Accesses) PrometheusQuery(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  610. w.Header().Set("Content-Type", "application/json")
  611. w.Header().Set("Access-Control-Allow-Origin", "*")
  612. qp := httputil.NewQueryParams(r.URL.Query())
  613. query := qp.Get("query", "")
  614. if query == "" {
  615. w.Write(WrapData(nil, fmt.Errorf("Query Parameter 'query' is unset'")))
  616. return
  617. }
  618. // Attempt to parse time as either a unix timestamp or as an RFC3339 value
  619. var timeVal time.Time
  620. timeStr := qp.Get("time", "")
  621. if len(timeStr) > 0 {
  622. if t, err := strconv.ParseInt(timeStr, 10, 64); err == nil {
  623. timeVal = time.Unix(t, 0)
  624. } else if t, err := time.Parse(time.RFC3339, timeStr); err == nil {
  625. timeVal = t
  626. }
  627. // If time is given, but not parse-able, return an error
  628. if timeVal.IsZero() {
  629. http.Error(w, fmt.Sprintf("time must be a unix timestamp or RFC3339 value; illegal value given: %s", timeStr), http.StatusBadRequest)
  630. }
  631. }
  632. ctx := prom.NewNamedContext(a.PrometheusClient, prom.FrontendContextName)
  633. body, err := ctx.RawQuery(query, timeVal)
  634. if err != nil {
  635. w.Write(WrapData(nil, fmt.Errorf("Error running query %s. Error: %s", query, err)))
  636. return
  637. }
  638. w.Write(body)
  639. }
  640. func (a *Accesses) PrometheusQueryRange(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  641. w.Header().Set("Content-Type", "application/json")
  642. w.Header().Set("Access-Control-Allow-Origin", "*")
  643. qp := httputil.NewQueryParams(r.URL.Query())
  644. query := qp.Get("query", "")
  645. if query == "" {
  646. fmt.Fprintf(w, "Error parsing query from request parameters.")
  647. return
  648. }
  649. start, end, duration, err := toStartEndStep(qp)
  650. if err != nil {
  651. fmt.Fprintf(w, err.Error())
  652. return
  653. }
  654. ctx := prom.NewNamedContext(a.PrometheusClient, prom.FrontendContextName)
  655. body, err := ctx.RawQueryRange(query, start, end, duration)
  656. if err != nil {
  657. fmt.Fprintf(w, "Error running query %s. Error: %s", query, err)
  658. return
  659. }
  660. w.Write(body)
  661. }
  662. func (a *Accesses) ThanosQuery(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  663. w.Header().Set("Content-Type", "application/json")
  664. w.Header().Set("Access-Control-Allow-Origin", "*")
  665. if !thanos.IsEnabled() {
  666. w.Write(WrapData(nil, fmt.Errorf("ThanosDisabled")))
  667. return
  668. }
  669. qp := httputil.NewQueryParams(r.URL.Query())
  670. query := qp.Get("query", "")
  671. if query == "" {
  672. w.Write(WrapData(nil, fmt.Errorf("Query Parameter 'query' is unset'")))
  673. return
  674. }
  675. // Attempt to parse time as either a unix timestamp or as an RFC3339 value
  676. var timeVal time.Time
  677. timeStr := qp.Get("time", "")
  678. if len(timeStr) > 0 {
  679. if t, err := strconv.ParseInt(timeStr, 10, 64); err == nil {
  680. timeVal = time.Unix(t, 0)
  681. } else if t, err := time.Parse(time.RFC3339, timeStr); err == nil {
  682. timeVal = t
  683. }
  684. // If time is given, but not parse-able, return an error
  685. if timeVal.IsZero() {
  686. http.Error(w, fmt.Sprintf("time must be a unix timestamp or RFC3339 value; illegal value given: %s", timeStr), http.StatusBadRequest)
  687. }
  688. }
  689. ctx := prom.NewNamedContext(a.ThanosClient, prom.FrontendContextName)
  690. body, err := ctx.RawQuery(query, timeVal)
  691. if err != nil {
  692. w.Write(WrapData(nil, fmt.Errorf("Error running query %s. Error: %s", query, err)))
  693. return
  694. }
  695. w.Write(body)
  696. }
  697. func (a *Accesses) ThanosQueryRange(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  698. w.Header().Set("Content-Type", "application/json")
  699. w.Header().Set("Access-Control-Allow-Origin", "*")
  700. if !thanos.IsEnabled() {
  701. w.Write(WrapData(nil, fmt.Errorf("ThanosDisabled")))
  702. return
  703. }
  704. qp := httputil.NewQueryParams(r.URL.Query())
  705. query := qp.Get("query", "")
  706. if query == "" {
  707. fmt.Fprintf(w, "Error parsing query from request parameters.")
  708. return
  709. }
  710. start, end, duration, err := toStartEndStep(qp)
  711. if err != nil {
  712. fmt.Fprintf(w, err.Error())
  713. return
  714. }
  715. ctx := prom.NewNamedContext(a.ThanosClient, prom.FrontendContextName)
  716. body, err := ctx.RawQueryRange(query, start, end, duration)
  717. if err != nil {
  718. fmt.Fprintf(w, "Error running query %s. Error: %s", query, err)
  719. return
  720. }
  721. w.Write(body)
  722. }
  723. // helper for query range proxy requests
  724. func toStartEndStep(qp httputil.QueryParams) (start, end time.Time, step time.Duration, err error) {
  725. var e error
  726. ss := qp.Get("start", "")
  727. es := qp.Get("end", "")
  728. ds := qp.Get("duration", "")
  729. layout := "2006-01-02T15:04:05.000Z"
  730. start, e = time.Parse(layout, ss)
  731. if e != nil {
  732. err = fmt.Errorf("Error parsing time %s. Error: %s", ss, err)
  733. return
  734. }
  735. end, e = time.Parse(layout, es)
  736. if e != nil {
  737. err = fmt.Errorf("Error parsing time %s. Error: %s", es, err)
  738. return
  739. }
  740. step, e = time.ParseDuration(ds)
  741. if e != nil {
  742. err = fmt.Errorf("Error parsing duration %s. Error: %s", ds, err)
  743. return
  744. }
  745. err = nil
  746. return
  747. }
  748. func (a *Accesses) GetPrometheusQueueState(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  749. w.Header().Set("Content-Type", "application/json")
  750. w.Header().Set("Access-Control-Allow-Origin", "*")
  751. promQueueState, err := prom.GetPrometheusQueueState(a.PrometheusClient)
  752. if err != nil {
  753. w.Write(WrapData(nil, err))
  754. return
  755. }
  756. result := map[string]*prom.PrometheusQueueState{
  757. "prometheus": promQueueState,
  758. }
  759. if thanos.IsEnabled() {
  760. thanosQueueState, err := prom.GetPrometheusQueueState(a.ThanosClient)
  761. if err != nil {
  762. log.Warnf("Error getting Thanos queue state: %s", err)
  763. } else {
  764. result["thanos"] = thanosQueueState
  765. }
  766. }
  767. w.Write(WrapData(result, nil))
  768. }
  769. // GetPrometheusMetrics retrieves availability of Prometheus and Thanos metrics
  770. func (a *Accesses) GetPrometheusMetrics(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
  771. w.Header().Set("Content-Type", "application/json")
  772. w.Header().Set("Access-Control-Allow-Origin", "*")
  773. promMetrics := prom.GetPrometheusMetrics(a.PrometheusClient, "")
  774. result := map[string][]*prom.PrometheusDiagnostic{
  775. "prometheus": promMetrics,
  776. }
  777. if thanos.IsEnabled() {
  778. thanosMetrics := prom.GetPrometheusMetrics(a.ThanosClient, thanos.QueryOffset())
  779. result["thanos"] = thanosMetrics
  780. }
  781. w.Write(WrapData(result, nil))
  782. }
  783. func (a *Accesses) GetAllPersistentVolumes(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  784. w.Header().Set("Content-Type", "application/json")
  785. w.Header().Set("Access-Control-Allow-Origin", "*")
  786. pvList := a.ClusterCache.GetAllPersistentVolumes()
  787. body, err := json.Marshal(wrapAsObjectItems(pvList))
  788. if err != nil {
  789. fmt.Fprintf(w, "Error decoding persistent volumes: "+err.Error())
  790. } else {
  791. w.Write(body)
  792. }
  793. }
  794. func (a *Accesses) GetAllDeployments(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  795. w.Header().Set("Content-Type", "application/json")
  796. w.Header().Set("Access-Control-Allow-Origin", "*")
  797. qp := httputil.NewQueryParams(r.URL.Query())
  798. namespace := qp.Get("namespace", "")
  799. deploymentsList := a.ClusterCache.GetAllDeployments()
  800. // filter for provided namespace
  801. var deployments []*appsv1.Deployment
  802. if namespace == "" {
  803. deployments = deploymentsList
  804. } else {
  805. deployments = []*appsv1.Deployment{}
  806. for _, d := range deploymentsList {
  807. if d.Namespace == namespace {
  808. deployments = append(deployments, d)
  809. }
  810. }
  811. }
  812. body, err := json.Marshal(wrapAsObjectItems(deployments))
  813. if err != nil {
  814. fmt.Fprintf(w, "Error decoding deployment: "+err.Error())
  815. } else {
  816. w.Write(body)
  817. }
  818. }
  819. func (a *Accesses) GetAllStorageClasses(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  820. w.Header().Set("Content-Type", "application/json")
  821. w.Header().Set("Access-Control-Allow-Origin", "*")
  822. scList := a.ClusterCache.GetAllStorageClasses()
  823. body, err := json.Marshal(wrapAsObjectItems(scList))
  824. if err != nil {
  825. fmt.Fprintf(w, "Error decoding storageclasses: "+err.Error())
  826. } else {
  827. w.Write(body)
  828. }
  829. }
  830. func (a *Accesses) GetAllStatefulSets(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  831. w.Header().Set("Content-Type", "application/json")
  832. w.Header().Set("Access-Control-Allow-Origin", "*")
  833. qp := httputil.NewQueryParams(r.URL.Query())
  834. namespace := qp.Get("namespace", "")
  835. statefulSetsList := a.ClusterCache.GetAllStatefulSets()
  836. // filter for provided namespace
  837. var statefulSets []*appsv1.StatefulSet
  838. if namespace == "" {
  839. statefulSets = statefulSetsList
  840. } else {
  841. statefulSets = []*appsv1.StatefulSet{}
  842. for _, ss := range statefulSetsList {
  843. if ss.Namespace == namespace {
  844. statefulSets = append(statefulSets, ss)
  845. }
  846. }
  847. }
  848. body, err := json.Marshal(wrapAsObjectItems(statefulSets))
  849. if err != nil {
  850. fmt.Fprintf(w, "Error decoding deployment: "+err.Error())
  851. } else {
  852. w.Write(body)
  853. }
  854. }
  855. func (a *Accesses) GetAllNodes(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  856. w.Header().Set("Content-Type", "application/json")
  857. w.Header().Set("Access-Control-Allow-Origin", "*")
  858. nodeList := a.ClusterCache.GetAllNodes()
  859. body, err := json.Marshal(wrapAsObjectItems(nodeList))
  860. if err != nil {
  861. fmt.Fprintf(w, "Error decoding nodes: "+err.Error())
  862. } else {
  863. w.Write(body)
  864. }
  865. }
  866. func (a *Accesses) GetAllPods(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  867. w.Header().Set("Content-Type", "application/json")
  868. w.Header().Set("Access-Control-Allow-Origin", "*")
  869. podlist := a.ClusterCache.GetAllPods()
  870. body, err := json.Marshal(wrapAsObjectItems(podlist))
  871. if err != nil {
  872. fmt.Fprintf(w, "Error decoding pods: "+err.Error())
  873. } else {
  874. w.Write(body)
  875. }
  876. }
  877. func (a *Accesses) GetAllNamespaces(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  878. w.Header().Set("Content-Type", "application/json")
  879. w.Header().Set("Access-Control-Allow-Origin", "*")
  880. namespaces := a.ClusterCache.GetAllNamespaces()
  881. body, err := json.Marshal(wrapAsObjectItems(namespaces))
  882. if err != nil {
  883. fmt.Fprintf(w, "Error decoding deployment: "+err.Error())
  884. } else {
  885. w.Write(body)
  886. }
  887. }
  888. func (a *Accesses) GetAllDaemonSets(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  889. w.Header().Set("Content-Type", "application/json")
  890. w.Header().Set("Access-Control-Allow-Origin", "*")
  891. daemonSets := a.ClusterCache.GetAllDaemonSets()
  892. body, err := json.Marshal(wrapAsObjectItems(daemonSets))
  893. if err != nil {
  894. fmt.Fprintf(w, "Error decoding daemon set: "+err.Error())
  895. } else {
  896. w.Write(body)
  897. }
  898. }
  899. func (a *Accesses) GetPod(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  900. w.Header().Set("Content-Type", "application/json")
  901. w.Header().Set("Access-Control-Allow-Origin", "*")
  902. podName := ps.ByName("name")
  903. podNamespace := ps.ByName("namespace")
  904. // TODO: ClusterCache API could probably afford to have some better filtering
  905. allPods := a.ClusterCache.GetAllPods()
  906. for _, pod := range allPods {
  907. for _, container := range pod.Spec.Containers {
  908. container.Env = make([]v1.EnvVar, 0)
  909. }
  910. if pod.Namespace == podNamespace && pod.Name == podName {
  911. body, err := json.Marshal(pod)
  912. if err != nil {
  913. fmt.Fprintf(w, "Error decoding pod: "+err.Error())
  914. } else {
  915. w.Write(body)
  916. }
  917. return
  918. }
  919. }
  920. fmt.Fprintf(w, "Pod not found\n")
  921. }
  922. func (a *Accesses) PrometheusRecordingRules(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  923. w.Header().Set("Content-Type", "application/json")
  924. w.Header().Set("Access-Control-Allow-Origin", "*")
  925. u := a.PrometheusClient.URL(epRules, nil)
  926. req, err := http.NewRequest(http.MethodGet, u.String(), nil)
  927. if err != nil {
  928. fmt.Fprintf(w, "Error creating Prometheus rule request: "+err.Error())
  929. }
  930. _, body, err := a.PrometheusClient.Do(r.Context(), req)
  931. if err != nil {
  932. fmt.Fprintf(w, "Error making Prometheus rule request: "+err.Error())
  933. } else {
  934. w.Write(body)
  935. }
  936. }
  937. func (a *Accesses) PrometheusConfig(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  938. w.Header().Set("Content-Type", "application/json")
  939. w.Header().Set("Access-Control-Allow-Origin", "*")
  940. pConfig := map[string]string{
  941. "address": env.GetPrometheusServerEndpoint(),
  942. }
  943. body, err := json.Marshal(pConfig)
  944. if err != nil {
  945. fmt.Fprintf(w, "Error marshalling prometheus config")
  946. } else {
  947. w.Write(body)
  948. }
  949. }
  950. func (a *Accesses) PrometheusTargets(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  951. w.Header().Set("Content-Type", "application/json")
  952. w.Header().Set("Access-Control-Allow-Origin", "*")
  953. u := a.PrometheusClient.URL(epTargets, nil)
  954. req, err := http.NewRequest(http.MethodGet, u.String(), nil)
  955. if err != nil {
  956. fmt.Fprintf(w, "Error creating Prometheus rule request: "+err.Error())
  957. }
  958. _, body, err := a.PrometheusClient.Do(r.Context(), req)
  959. if err != nil {
  960. fmt.Fprintf(w, "Error making Prometheus rule request: "+err.Error())
  961. } else {
  962. w.Write(body)
  963. }
  964. }
  965. func (a *Accesses) GetOrphanedPods(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  966. w.Header().Set("Content-Type", "application/json")
  967. w.Header().Set("Access-Control-Allow-Origin", "*")
  968. podlist := a.ClusterCache.GetAllPods()
  969. var lonePods []*v1.Pod
  970. for _, pod := range podlist {
  971. if len(pod.OwnerReferences) == 0 {
  972. lonePods = append(lonePods, pod)
  973. }
  974. }
  975. body, err := json.Marshal(lonePods)
  976. if err != nil {
  977. fmt.Fprintf(w, "Error decoding pod: "+err.Error())
  978. } else {
  979. w.Write(body)
  980. }
  981. }
  982. func (a *Accesses) GetInstallNamespace(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  983. w.Header().Set("Content-Type", "application/json")
  984. w.Header().Set("Access-Control-Allow-Origin", "*")
  985. ns := env.GetKubecostNamespace()
  986. w.Write([]byte(ns))
  987. }
  988. type InstallInfo struct {
  989. Containers []ContainerInfo `json:"containers"`
  990. ClusterInfo map[string]string `json:"clusterInfo"`
  991. Version string `json:"version"`
  992. }
  993. type ContainerInfo struct {
  994. ContainerName string `json:"containerName"`
  995. Image string `json:"image"`
  996. ImageID string `json:"imageID"`
  997. StartTime string `json:"startTime"`
  998. Restarts int32 `json:"restarts"`
  999. }
  1000. func (a *Accesses) GetInstallInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  1001. w.Header().Set("Content-Type", "application/json")
  1002. w.Header().Set("Access-Control-Allow-Origin", "*")
  1003. pods, err := a.KubeClientSet.CoreV1().Pods(env.GetKubecostNamespace()).List(context.Background(), metav1.ListOptions{
  1004. LabelSelector: "app=cost-analyzer",
  1005. FieldSelector: "status.phase=Running",
  1006. Limit: 1,
  1007. })
  1008. if err != nil {
  1009. writeErrorResponse(w, 500, fmt.Sprintf("Unable to list pods: %s", err.Error()))
  1010. return
  1011. }
  1012. info := InstallInfo{
  1013. ClusterInfo: make(map[string]string),
  1014. Version: version.FriendlyVersion(),
  1015. }
  1016. // If we have zero pods either something is weird with the install since the app selector is not exposed in the helm
  1017. // chart or more likely we are running locally - in either case Images field will return as null
  1018. if len(pods.Items) > 0 {
  1019. for _, pod := range pods.Items {
  1020. for _, container := range pod.Status.ContainerStatuses {
  1021. c := ContainerInfo{
  1022. ContainerName: container.Name,
  1023. Image: container.Image,
  1024. ImageID: container.ImageID,
  1025. StartTime: pod.Status.StartTime.String(),
  1026. Restarts: container.RestartCount,
  1027. }
  1028. info.Containers = append(info.Containers, c)
  1029. }
  1030. }
  1031. }
  1032. nodes := a.ClusterCache.GetAllNodes()
  1033. cachePods := a.ClusterCache.GetAllPods()
  1034. info.ClusterInfo["nodeCount"] = strconv.Itoa(len(nodes))
  1035. info.ClusterInfo["podCount"] = strconv.Itoa(len(cachePods))
  1036. body, err := json.Marshal(info)
  1037. if err != nil {
  1038. writeErrorResponse(w, 500, fmt.Sprintf("Error decoding pod: %s", err.Error()))
  1039. return
  1040. }
  1041. w.Write(body)
  1042. }
  1043. // logsFor pulls the logs for a specific pod, namespace, and container
  1044. func logsFor(c kubernetes.Interface, namespace string, pod string, container string, dur time.Duration, ctx context.Context) (string, error) {
  1045. since := time.Now().UTC().Add(-dur)
  1046. logOpts := v1.PodLogOptions{
  1047. SinceTime: &metav1.Time{Time: since},
  1048. }
  1049. if container != "" {
  1050. logOpts.Container = container
  1051. }
  1052. req := c.CoreV1().Pods(namespace).GetLogs(pod, &logOpts)
  1053. reader, err := req.Stream(ctx)
  1054. if err != nil {
  1055. return "", err
  1056. }
  1057. podLogs, err := io.ReadAll(reader)
  1058. if err != nil {
  1059. return "", err
  1060. }
  1061. // If color is already disabled then we don't need to process the logs
  1062. // to drop ANSI colors
  1063. if !viper.GetBool("disable-log-color") {
  1064. podLogs = ANSIRegex.ReplaceAll(podLogs, []byte{})
  1065. }
  1066. return string(podLogs), nil
  1067. }
  1068. func (a *Accesses) GetPodLogs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  1069. w.Header().Set("Content-Type", "application/json")
  1070. w.Header().Set("Access-Control-Allow-Origin", "*")
  1071. qp := httputil.NewQueryParams(r.URL.Query())
  1072. ns := qp.Get("namespace", env.GetKubecostNamespace())
  1073. pod := qp.Get("pod", "")
  1074. selector := qp.Get("selector", "")
  1075. container := qp.Get("container", "")
  1076. since := qp.Get("since", "24h")
  1077. sinceDuration, err := time.ParseDuration(since)
  1078. if err != nil {
  1079. fmt.Fprintf(w, "Invalid Duration String: "+err.Error())
  1080. return
  1081. }
  1082. var logResult string
  1083. appendLog := func(ns string, pod string, container string, l string) {
  1084. if l == "" {
  1085. return
  1086. }
  1087. logResult += fmt.Sprintf("%s\n| %s:%s:%s\n%s\n%s\n\n", LogSeparator, ns, pod, container, LogSeparator, l)
  1088. }
  1089. if pod != "" {
  1090. pd, err := a.KubeClientSet.CoreV1().Pods(ns).Get(r.Context(), pod, metav1.GetOptions{})
  1091. if err != nil {
  1092. fmt.Fprintf(w, "Error Finding Pod: "+err.Error())
  1093. return
  1094. }
  1095. if container != "" {
  1096. var foundContainer bool
  1097. for _, cont := range pd.Spec.Containers {
  1098. if strings.EqualFold(cont.Name, container) {
  1099. foundContainer = true
  1100. break
  1101. }
  1102. }
  1103. if !foundContainer {
  1104. fmt.Fprintf(w, "Could not find container: "+container)
  1105. return
  1106. }
  1107. }
  1108. logs, err := logsFor(a.KubeClientSet, ns, pod, container, sinceDuration, r.Context())
  1109. if err != nil {
  1110. fmt.Fprintf(w, "Error Getting Logs: "+err.Error())
  1111. return
  1112. }
  1113. appendLog(ns, pod, container, logs)
  1114. w.Write([]byte(logResult))
  1115. return
  1116. }
  1117. if selector != "" {
  1118. pods, err := a.KubeClientSet.CoreV1().Pods(ns).List(r.Context(), metav1.ListOptions{LabelSelector: selector})
  1119. if err != nil {
  1120. fmt.Fprintf(w, "Error Finding Pod: "+err.Error())
  1121. return
  1122. }
  1123. for _, pd := range pods.Items {
  1124. for _, cont := range pd.Spec.Containers {
  1125. logs, err := logsFor(a.KubeClientSet, ns, pd.Name, cont.Name, sinceDuration, r.Context())
  1126. if err != nil {
  1127. continue
  1128. }
  1129. appendLog(ns, pd.Name, cont.Name, logs)
  1130. }
  1131. }
  1132. }
  1133. w.Write([]byte(logResult))
  1134. }
  1135. func (a *Accesses) AddServiceKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  1136. w.Header().Set("Content-Type", "application/json")
  1137. w.Header().Set("Access-Control-Allow-Origin", "*")
  1138. r.ParseForm()
  1139. key := r.PostForm.Get("key")
  1140. k := []byte(key)
  1141. err := os.WriteFile(path.Join(env.GetConfigPathWithDefault("/var/configs/"), "key.json"), k, 0644)
  1142. if err != nil {
  1143. fmt.Fprintf(w, "Error writing service key: "+err.Error())
  1144. }
  1145. w.WriteHeader(http.StatusOK)
  1146. }
  1147. func (a *Accesses) GetHelmValues(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  1148. w.Header().Set("Content-Type", "application/json")
  1149. w.Header().Set("Access-Control-Allow-Origin", "*")
  1150. encodedValues := env.Get("HELM_VALUES", "")
  1151. if encodedValues == "" {
  1152. fmt.Fprintf(w, "Values reporting disabled")
  1153. return
  1154. }
  1155. result, err := base64.StdEncoding.DecodeString(encodedValues)
  1156. if err != nil {
  1157. fmt.Fprintf(w, "Failed to decode encoded values: %s", err)
  1158. return
  1159. }
  1160. w.Write(result)
  1161. }
  1162. func (a *Accesses) Status(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  1163. w.Header().Set("Content-Type", "application/json")
  1164. w.Header().Set("Access-Control-Allow-Origin", "*")
  1165. promServer := env.GetPrometheusServerEndpoint()
  1166. api := prometheusAPI.NewAPI(a.PrometheusClient)
  1167. result, err := api.Config(r.Context())
  1168. if err != nil {
  1169. fmt.Fprintf(w, "Using Prometheus at "+promServer+". Error: "+err.Error())
  1170. } else {
  1171. fmt.Fprintf(w, "Using Prometheus at "+promServer+". PrometheusConfig: "+result.YAML)
  1172. }
  1173. }
  1174. type LogLevelRequestResponse struct {
  1175. Level string `json:"level"`
  1176. }
  1177. func (a *Accesses) GetLogLevel(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  1178. w.Header().Set("Content-Type", "application/json")
  1179. w.Header().Set("Access-Control-Allow-Origin", "*")
  1180. level := log.GetLogLevel()
  1181. llrr := LogLevelRequestResponse{
  1182. Level: level,
  1183. }
  1184. body, err := json.Marshal(llrr)
  1185. if err != nil {
  1186. http.Error(w, fmt.Sprintf("unable to retrive log level"), http.StatusInternalServerError)
  1187. return
  1188. }
  1189. _, err = w.Write(body)
  1190. if err != nil {
  1191. http.Error(w, fmt.Sprintf("unable to write response: %s", body), http.StatusInternalServerError)
  1192. return
  1193. }
  1194. }
  1195. func (a *Accesses) SetLogLevel(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  1196. params := LogLevelRequestResponse{}
  1197. err := json.NewDecoder(r.Body).Decode(&params)
  1198. if err != nil {
  1199. http.Error(w, fmt.Sprintf("unable to decode request body, error: %s", err), http.StatusBadRequest)
  1200. return
  1201. }
  1202. err = log.SetLogLevel(params.Level)
  1203. if err != nil {
  1204. http.Error(w, fmt.Sprintf("level must be a valid log level according to zerolog; level given: %s, error: %s", params.Level, err), http.StatusBadRequest)
  1205. return
  1206. }
  1207. w.WriteHeader(http.StatusOK)
  1208. }
  1209. // captures the panic event in sentry
  1210. func capturePanicEvent(err string, stack string) {
  1211. msg := fmt.Sprintf("Panic: %s\nStackTrace: %s\n", err, stack)
  1212. log.Infof(msg)
  1213. sentry.CurrentHub().CaptureEvent(&sentry.Event{
  1214. Level: sentry.LevelError,
  1215. Message: msg,
  1216. })
  1217. sentry.Flush(5 * time.Second)
  1218. }
  1219. // handle any panics reported by the errors package
  1220. func handlePanic(p errors.Panic) bool {
  1221. err := p.Error
  1222. if err != nil {
  1223. if err, ok := err.(error); ok {
  1224. capturePanicEvent(err.Error(), p.Stack)
  1225. }
  1226. if err, ok := err.(string); ok {
  1227. capturePanicEvent(err, p.Stack)
  1228. }
  1229. }
  1230. // Return true to recover iff the type is http, otherwise allow kubernetes
  1231. // to recover.
  1232. return p.Type == errors.PanicTypeHTTP
  1233. }
  1234. func Initialize(additionalConfigWatchers ...*watcher.ConfigMapWatcher) *Accesses {
  1235. configWatchers := watcher.NewConfigMapWatchers(additionalConfigWatchers...)
  1236. var err error
  1237. if errorReportingEnabled {
  1238. err = sentry.Init(sentry.ClientOptions{Release: env.GetAppVersion()})
  1239. if err != nil {
  1240. log.Infof("Failed to initialize sentry for error reporting")
  1241. } else {
  1242. err = errors.SetPanicHandler(handlePanic)
  1243. if err != nil {
  1244. log.Infof("Failed to set panic handler: %s", err)
  1245. }
  1246. }
  1247. }
  1248. address := env.GetPrometheusServerEndpoint()
  1249. if address == "" {
  1250. log.Fatalf("No address for prometheus set in $%s. Aborting.", env.PrometheusServerEndpointEnvVar)
  1251. }
  1252. queryConcurrency := env.GetMaxQueryConcurrency()
  1253. log.Infof("Prometheus/Thanos Client Max Concurrency set to %d", queryConcurrency)
  1254. timeout := 120 * time.Second
  1255. keepAlive := 120 * time.Second
  1256. tlsHandshakeTimeout := 10 * time.Second
  1257. scrapeInterval := time.Minute
  1258. var rateLimitRetryOpts *prom.RateLimitRetryOpts = nil
  1259. if env.IsPrometheusRetryOnRateLimitResponse() {
  1260. rateLimitRetryOpts = &prom.RateLimitRetryOpts{
  1261. MaxRetries: env.GetPrometheusRetryOnRateLimitMaxRetries(),
  1262. DefaultRetryWait: env.GetPrometheusRetryOnRateLimitDefaultWait(),
  1263. }
  1264. }
  1265. promCli, err := prom.NewPrometheusClient(address, &prom.PrometheusClientConfig{
  1266. Timeout: timeout,
  1267. KeepAlive: keepAlive,
  1268. TLSHandshakeTimeout: tlsHandshakeTimeout,
  1269. TLSInsecureSkipVerify: env.GetInsecureSkipVerify(),
  1270. RateLimitRetryOpts: rateLimitRetryOpts,
  1271. Auth: &prom.ClientAuth{
  1272. Username: env.GetDBBasicAuthUsername(),
  1273. Password: env.GetDBBasicAuthUserPassword(),
  1274. BearerToken: env.GetDBBearerToken(),
  1275. },
  1276. QueryConcurrency: queryConcurrency,
  1277. QueryLogFile: "",
  1278. })
  1279. if err != nil {
  1280. log.Fatalf("Failed to create prometheus client, Error: %v", err)
  1281. }
  1282. m, err := prom.Validate(promCli)
  1283. if err != nil || !m.Running {
  1284. if err != nil {
  1285. log.Errorf("Failed to query prometheus at %s. Error: %s . Troubleshooting help available at: %s", address, err.Error(), prom.PrometheusTroubleshootingURL)
  1286. } else if !m.Running {
  1287. log.Errorf("Prometheus at %s is not running. Troubleshooting help available at: %s", address, prom.PrometheusTroubleshootingURL)
  1288. }
  1289. } else {
  1290. log.Infof("Success: retrieved the 'up' query against prometheus at: " + address)
  1291. }
  1292. api := prometheusAPI.NewAPI(promCli)
  1293. _, err = api.Config(context.Background())
  1294. if err != nil {
  1295. log.Infof("No valid prometheus config file at %s. Error: %s . Troubleshooting help available at: %s. Ignore if using cortex/thanos here.", address, err.Error(), prom.PrometheusTroubleshootingURL)
  1296. } else {
  1297. log.Infof("Retrieved a prometheus config file from: %s", address)
  1298. }
  1299. // Lookup scrape interval for kubecost job, update if found
  1300. si, err := prom.ScrapeIntervalFor(promCli, env.GetKubecostJobName())
  1301. if err == nil {
  1302. scrapeInterval = si
  1303. }
  1304. log.Infof("Using scrape interval of %f", scrapeInterval.Seconds())
  1305. // Kubernetes API setup
  1306. kubeClientset, err := kubeconfig.LoadKubeClient("")
  1307. if err != nil {
  1308. log.Fatalf("Failed to build Kubernetes client: %s", err.Error())
  1309. }
  1310. // Create ConfigFileManager for synchronization of shared configuration
  1311. confManager := config.NewConfigFileManager(&config.ConfigFileManagerOpts{
  1312. BucketStoreConfig: env.GetKubecostConfigBucket(),
  1313. LocalConfigPath: "/",
  1314. })
  1315. configPrefix := env.GetConfigPathWithDefault("/var/configs/")
  1316. // Create Kubernetes Cluster Cache + Watchers
  1317. var k8sCache clustercache.ClusterCache
  1318. if env.IsClusterCacheFileEnabled() {
  1319. importLocation := confManager.ConfigFileAt(path.Join(configPrefix, "cluster-cache.json"))
  1320. k8sCache = clustercache.NewClusterImporter(importLocation)
  1321. } else {
  1322. k8sCache = clustercache.NewKubernetesClusterCache(kubeClientset)
  1323. }
  1324. k8sCache.Run()
  1325. cloudProviderKey := env.GetCloudProviderAPIKey()
  1326. cloudProvider, err := cloud.NewProvider(k8sCache, cloudProviderKey, confManager)
  1327. if err != nil {
  1328. panic(err.Error())
  1329. }
  1330. // Append the pricing config watcher
  1331. configWatchers.AddWatcher(cloud.ConfigWatcherFor(cloudProvider))
  1332. configWatchers.AddWatcher(metrics.GetMetricsConfigWatcher())
  1333. watchConfigFunc := configWatchers.ToWatchFunc()
  1334. watchedConfigs := configWatchers.GetWatchedConfigs()
  1335. kubecostNamespace := env.GetKubecostNamespace()
  1336. // We need an initial invocation because the init of the cache has happened before we had access to the provider.
  1337. for _, cw := range watchedConfigs {
  1338. configs, err := kubeClientset.CoreV1().ConfigMaps(kubecostNamespace).Get(context.Background(), cw, metav1.GetOptions{})
  1339. if err != nil {
  1340. log.Infof("No %s configmap found at install time, using existing configs: %s", cw, err.Error())
  1341. } else {
  1342. log.Infof("Found configmap %s, watching...", configs.Name)
  1343. watchConfigFunc(configs)
  1344. }
  1345. }
  1346. k8sCache.SetConfigMapUpdateFunc(watchConfigFunc)
  1347. remoteEnabled := env.IsRemoteEnabled()
  1348. if remoteEnabled {
  1349. info, err := cloudProvider.ClusterInfo()
  1350. log.Infof("Saving cluster with id:'%s', and name:'%s' to durable storage", info["id"], info["name"])
  1351. if err != nil {
  1352. log.Infof("Error saving cluster id %s", err.Error())
  1353. }
  1354. _, _, err = cloud.GetOrCreateClusterMeta(info["id"], info["name"])
  1355. if err != nil {
  1356. log.Infof("Unable to set cluster id '%s' for cluster '%s', %s", info["id"], info["name"], err.Error())
  1357. }
  1358. }
  1359. // Thanos Client
  1360. var thanosClient prometheus.Client
  1361. if thanos.IsEnabled() {
  1362. thanosAddress := thanos.QueryURL()
  1363. if thanosAddress != "" {
  1364. thanosCli, _ := thanos.NewThanosClient(thanosAddress, &prom.PrometheusClientConfig{
  1365. Timeout: timeout,
  1366. KeepAlive: keepAlive,
  1367. TLSHandshakeTimeout: tlsHandshakeTimeout,
  1368. TLSInsecureSkipVerify: env.GetInsecureSkipVerify(),
  1369. RateLimitRetryOpts: rateLimitRetryOpts,
  1370. Auth: &prom.ClientAuth{
  1371. Username: env.GetMultiClusterBasicAuthUsername(),
  1372. Password: env.GetMultiClusterBasicAuthPassword(),
  1373. BearerToken: env.GetMultiClusterBearerToken(),
  1374. },
  1375. QueryConcurrency: queryConcurrency,
  1376. QueryLogFile: env.GetQueryLoggingFile(),
  1377. })
  1378. _, err = prom.Validate(thanosCli)
  1379. if err != nil {
  1380. log.Warnf("Failed to query Thanos at %s. Error: %s.", thanosAddress, err.Error())
  1381. thanosClient = thanosCli
  1382. } else {
  1383. log.Infof("Success: retrieved the 'up' query against Thanos at: " + thanosAddress)
  1384. thanosClient = thanosCli
  1385. }
  1386. } else {
  1387. log.Infof("Error resolving environment variable: $%s", env.ThanosQueryUrlEnvVar)
  1388. }
  1389. }
  1390. // ClusterInfo Provider to provide the cluster map with local and remote cluster data
  1391. var clusterInfoProvider clusters.ClusterInfoProvider
  1392. if env.IsClusterInfoFileEnabled() {
  1393. clusterInfoFile := confManager.ConfigFileAt(path.Join(configPrefix, "cluster-info.json"))
  1394. clusterInfoProvider = NewConfiguredClusterInfoProvider(clusterInfoFile)
  1395. } else {
  1396. clusterInfoProvider = NewLocalClusterInfoProvider(kubeClientset, cloudProvider)
  1397. }
  1398. // Initialize ClusterMap for maintaining ClusterInfo by ClusterID
  1399. var clusterMap clusters.ClusterMap
  1400. if thanosClient != nil {
  1401. clusterMap = clusters.NewClusterMap(thanosClient, clusterInfoProvider, 10*time.Minute)
  1402. } else {
  1403. clusterMap = clusters.NewClusterMap(promCli, clusterInfoProvider, 5*time.Minute)
  1404. }
  1405. // cache responses from model and aggregation for a default of 10 minutes;
  1406. // clear expired responses every 20 minutes
  1407. aggregateCache := cache.New(time.Minute*10, time.Minute*20)
  1408. costDataCache := cache.New(time.Minute*10, time.Minute*20)
  1409. clusterCostsCache := cache.New(cache.NoExpiration, cache.NoExpiration)
  1410. outOfClusterCache := cache.New(time.Minute*5, time.Minute*10)
  1411. settingsCache := cache.New(cache.NoExpiration, cache.NoExpiration)
  1412. // query durations that should be cached longer should be registered here
  1413. // use relatively prime numbers to minimize likelihood of synchronized
  1414. // attempts at cache warming
  1415. day := 24 * time.Hour
  1416. cacheExpiration := map[time.Duration]time.Duration{
  1417. day: maxCacheMinutes1d * time.Minute,
  1418. 2 * day: maxCacheMinutes2d * time.Minute,
  1419. 7 * day: maxCacheMinutes7d * time.Minute,
  1420. 30 * day: maxCacheMinutes30d * time.Minute,
  1421. }
  1422. var pc prometheus.Client
  1423. if thanosClient != nil {
  1424. pc = thanosClient
  1425. } else {
  1426. pc = promCli
  1427. }
  1428. costModel := NewCostModel(pc, cloudProvider, k8sCache, clusterMap, scrapeInterval)
  1429. metricsEmitter := NewCostModelMetricsEmitter(promCli, k8sCache, cloudProvider, clusterInfoProvider, costModel)
  1430. a := &Accesses{
  1431. Router: httprouter.New(),
  1432. PrometheusClient: promCli,
  1433. ThanosClient: thanosClient,
  1434. KubeClientSet: kubeClientset,
  1435. ClusterCache: k8sCache,
  1436. ClusterMap: clusterMap,
  1437. CloudProvider: cloudProvider,
  1438. ConfigFileManager: confManager,
  1439. ClusterInfoProvider: clusterInfoProvider,
  1440. Model: costModel,
  1441. MetricsEmitter: metricsEmitter,
  1442. AggregateCache: aggregateCache,
  1443. CostDataCache: costDataCache,
  1444. ClusterCostsCache: clusterCostsCache,
  1445. OutOfClusterCache: outOfClusterCache,
  1446. SettingsCache: settingsCache,
  1447. CacheExpiration: cacheExpiration,
  1448. httpServices: services.NewCostModelServices(),
  1449. }
  1450. // Use the Accesses instance, itself, as the CostModelAggregator. This is
  1451. // confusing and unconventional, but necessary so that we can swap it
  1452. // out for the ETL-adapted version elsewhere.
  1453. // TODO clean this up once ETL is open-sourced.
  1454. a.AggAPI = a
  1455. // Initialize mechanism for subscribing to settings changes
  1456. a.InitializeSettingsPubSub()
  1457. err = a.CloudProvider.DownloadPricingData()
  1458. if err != nil {
  1459. log.Infof("Failed to download pricing data: " + err.Error())
  1460. }
  1461. // Warm the aggregate cache unless explicitly set to false
  1462. if env.IsCacheWarmingEnabled() {
  1463. log.Infof("Init: AggregateCostModel cache warming enabled")
  1464. a.warmAggregateCostModelCache()
  1465. } else {
  1466. log.Infof("Init: AggregateCostModel cache warming disabled")
  1467. }
  1468. if !env.IsKubecostMetricsPodEnabled() {
  1469. a.MetricsEmitter.Start()
  1470. }
  1471. a.Router.GET("/costDataModel", a.CostDataModel)
  1472. a.Router.GET("/costDataModelRange", a.CostDataModelRange)
  1473. a.Router.GET("/aggregatedCostModel", a.AggregateCostModelHandler)
  1474. a.Router.GET("/allocation/compute", a.ComputeAllocationHandler)
  1475. a.Router.GET("/allocation/compute/summary", a.ComputeAllocationHandlerSummary)
  1476. a.Router.GET("/allNodePricing", a.GetAllNodePricing)
  1477. a.Router.POST("/refreshPricing", a.RefreshPricingData)
  1478. a.Router.GET("/clusterCostsOverTime", a.ClusterCostsOverTime)
  1479. a.Router.GET("/clusterCosts", a.ClusterCosts)
  1480. a.Router.GET("/clusterCostsFromCache", a.ClusterCostsFromCacheHandler)
  1481. a.Router.GET("/validatePrometheus", a.GetPrometheusMetadata)
  1482. a.Router.GET("/managementPlatform", a.ManagementPlatform)
  1483. a.Router.GET("/clusterInfo", a.ClusterInfo)
  1484. a.Router.GET("/clusterInfoMap", a.GetClusterInfoMap)
  1485. a.Router.GET("/serviceAccountStatus", a.GetServiceAccountStatus)
  1486. a.Router.GET("/pricingSourceStatus", a.GetPricingSourceStatus)
  1487. a.Router.GET("/pricingSourceSummary", a.GetPricingSourceSummary)
  1488. a.Router.GET("/pricingSourceCounts", a.GetPricingSourceCounts)
  1489. // endpoints migrated from server
  1490. a.Router.GET("/allPersistentVolumes", a.GetAllPersistentVolumes)
  1491. a.Router.GET("/allDeployments", a.GetAllDeployments)
  1492. a.Router.GET("/allStorageClasses", a.GetAllStorageClasses)
  1493. a.Router.GET("/allStatefulSets", a.GetAllStatefulSets)
  1494. a.Router.GET("/allNodes", a.GetAllNodes)
  1495. a.Router.GET("/allPods", a.GetAllPods)
  1496. a.Router.GET("/allNamespaces", a.GetAllNamespaces)
  1497. a.Router.GET("/allDaemonSets", a.GetAllDaemonSets)
  1498. a.Router.GET("/pod/:namespace/:name", a.GetPod)
  1499. a.Router.GET("/prometheusRecordingRules", a.PrometheusRecordingRules)
  1500. a.Router.GET("/prometheusConfig", a.PrometheusConfig)
  1501. a.Router.GET("/prometheusTargets", a.PrometheusTargets)
  1502. a.Router.GET("/orphanedPods", a.GetOrphanedPods)
  1503. a.Router.GET("/installNamespace", a.GetInstallNamespace)
  1504. a.Router.GET("/installInfo", a.GetInstallInfo)
  1505. a.Router.GET("/podLogs", a.GetPodLogs)
  1506. a.Router.POST("/serviceKey", a.AddServiceKey)
  1507. a.Router.GET("/helmValues", a.GetHelmValues)
  1508. a.Router.GET("/status", a.Status)
  1509. // prom query proxies
  1510. a.Router.GET("/prometheusQuery", a.PrometheusQuery)
  1511. a.Router.GET("/prometheusQueryRange", a.PrometheusQueryRange)
  1512. a.Router.GET("/thanosQuery", a.ThanosQuery)
  1513. a.Router.GET("/thanosQueryRange", a.ThanosQueryRange)
  1514. // diagnostics
  1515. a.Router.GET("/diagnostics/requestQueue", a.GetPrometheusQueueState)
  1516. a.Router.GET("/diagnostics/prometheusMetrics", a.GetPrometheusMetrics)
  1517. a.Router.GET("/logs/level", a.GetLogLevel)
  1518. a.Router.POST("/logs/level", a.SetLogLevel)
  1519. a.httpServices.RegisterAll(a.Router)
  1520. return a
  1521. }
  1522. func writeErrorResponse(w http.ResponseWriter, code int, message string) {
  1523. out := map[string]string{
  1524. "message": message,
  1525. }
  1526. bytes, err := json.Marshal(out)
  1527. if err != nil {
  1528. w.Header().Set("Content-Type", "text/plain")
  1529. w.WriteHeader(500)
  1530. fmt.Fprint(w, "unable to marshall json for error")
  1531. log.Warnf("Failed to marshall JSON for error response: %s", err.Error())
  1532. return
  1533. }
  1534. w.WriteHeader(code)
  1535. fmt.Fprint(w, string(bytes))
  1536. }