k8s_handler.go 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430
  1. package api
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "strconv"
  8. "github.com/go-chi/chi"
  9. "github.com/gorilla/schema"
  10. "github.com/gorilla/websocket"
  11. "github.com/porter-dev/porter/internal/forms"
  12. "github.com/porter-dev/porter/internal/kubernetes"
  13. "github.com/porter-dev/porter/internal/kubernetes/nodes"
  14. "github.com/porter-dev/porter/internal/kubernetes/prometheus"
  15. v1 "k8s.io/api/core/v1"
  16. "k8s.io/client-go/tools/clientcmd"
  17. )
  18. // Enumeration of k8s API error codes, represented as int64
  19. const (
  20. ErrK8sDecode ErrorCode = iota + 600
  21. ErrK8sValidate
  22. ErrEnvDecode
  23. )
  24. var upgrader = websocket.Upgrader{
  25. ReadBufferSize: 1024,
  26. WriteBufferSize: 1024,
  27. }
  28. // HandleListNamespaces retrieves a list of namespaces
  29. func (app *App) HandleListNamespaces(w http.ResponseWriter, r *http.Request) {
  30. vals, err := url.ParseQuery(r.URL.RawQuery)
  31. if err != nil {
  32. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  33. return
  34. }
  35. // get the filter options
  36. form := &forms.K8sForm{
  37. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  38. Repo: app.Repo,
  39. DigitalOceanOAuth: app.DOConf,
  40. },
  41. }
  42. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  43. // validate the form
  44. if err := app.validator.Struct(form); err != nil {
  45. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  46. return
  47. }
  48. // create a new agent
  49. var agent *kubernetes.Agent
  50. if app.ServerConf.IsTesting {
  51. agent = app.TestAgents.K8sAgent
  52. } else {
  53. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  54. }
  55. namespaces, err := agent.ListNamespaces()
  56. if err != nil {
  57. app.handleErrorDataRead(err, w)
  58. return
  59. }
  60. if err := json.NewEncoder(w).Encode(namespaces); err != nil {
  61. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  62. return
  63. }
  64. }
  65. // HandleCreateNamespace creates a new namespace given the name.
  66. func (app *App) HandleCreateNamespace(w http.ResponseWriter, r *http.Request) {
  67. vals, err := url.ParseQuery(r.URL.RawQuery)
  68. if err != nil {
  69. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  70. return
  71. }
  72. // get the filter options
  73. form := &forms.K8sForm{
  74. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  75. Repo: app.Repo,
  76. DigitalOceanOAuth: app.DOConf,
  77. },
  78. }
  79. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  80. // validate the form
  81. if err := app.validator.Struct(form); err != nil {
  82. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  83. return
  84. }
  85. // create a new agent
  86. var agent *kubernetes.Agent
  87. if app.ServerConf.IsTesting {
  88. agent = app.TestAgents.K8sAgent
  89. } else {
  90. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  91. }
  92. ns := &forms.NamespaceForm{}
  93. if err := json.NewDecoder(r.Body).Decode(ns); err != nil {
  94. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  95. return
  96. }
  97. namespace, err := agent.CreateNamespace(ns.Name)
  98. if err != nil {
  99. app.handleErrorInternal(err, w)
  100. return
  101. }
  102. if err := json.NewEncoder(w).Encode(namespace); err != nil {
  103. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  104. return
  105. }
  106. w.WriteHeader(http.StatusOK)
  107. return
  108. }
  109. // HandleDeleteNamespace deletes a namespace given the name.
  110. func (app *App) HandleDeleteNamespace(w http.ResponseWriter, r *http.Request) {
  111. vals, err := url.ParseQuery(r.URL.RawQuery)
  112. if err != nil {
  113. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  114. return
  115. }
  116. // get the filter options
  117. form := &forms.K8sForm{
  118. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  119. Repo: app.Repo,
  120. DigitalOceanOAuth: app.DOConf,
  121. },
  122. }
  123. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  124. // validate the form
  125. if err := app.validator.Struct(form); err != nil {
  126. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  127. return
  128. }
  129. // create a new agent
  130. var agent *kubernetes.Agent
  131. if app.ServerConf.IsTesting {
  132. agent = app.TestAgents.K8sAgent
  133. } else {
  134. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  135. }
  136. if err != nil {
  137. app.handleErrorInternal(err, w)
  138. return
  139. }
  140. namespace := &forms.NamespaceForm{
  141. Name: vals.Get("name"),
  142. }
  143. err = agent.DeleteNamespace(namespace.Name)
  144. if err != nil {
  145. app.handleErrorInternal(err, w)
  146. return
  147. }
  148. w.WriteHeader(http.StatusOK)
  149. return
  150. }
  151. // HandleListPodEvents retrieves all events tied to a pod.
  152. func (app *App) HandleListPodEvents(w http.ResponseWriter, r *http.Request) {
  153. vals, err := url.ParseQuery(r.URL.RawQuery)
  154. // get path parameters
  155. namespace := chi.URLParam(r, "namespace")
  156. name := chi.URLParam(r, "name")
  157. if err != nil {
  158. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  159. return
  160. }
  161. // get the filter options
  162. form := &forms.K8sForm{
  163. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  164. Repo: app.Repo,
  165. DigitalOceanOAuth: app.DOConf,
  166. },
  167. }
  168. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  169. // validate the form
  170. if err := app.validator.Struct(form); err != nil {
  171. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  172. return
  173. }
  174. // create a new agent
  175. var agent *kubernetes.Agent
  176. if app.ServerConf.IsTesting {
  177. agent = app.TestAgents.K8sAgent
  178. } else {
  179. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  180. }
  181. events, err := agent.ListEvents(name, namespace)
  182. if err != nil {
  183. app.handleErrorDataRead(err, w)
  184. return
  185. }
  186. if err := json.NewEncoder(w).Encode(events); err != nil {
  187. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  188. return
  189. }
  190. }
  191. // HandleCreateConfigMap deletes the pod given the name and namespace.
  192. func (app *App) HandleCreateConfigMap(w http.ResponseWriter, r *http.Request) {
  193. vals, err := url.ParseQuery(r.URL.RawQuery)
  194. if err != nil {
  195. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  196. return
  197. }
  198. // get the filter options
  199. form := &forms.K8sForm{
  200. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  201. Repo: app.Repo,
  202. DigitalOceanOAuth: app.DOConf,
  203. },
  204. }
  205. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  206. // validate the form
  207. if err := app.validator.Struct(form); err != nil {
  208. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  209. return
  210. }
  211. // create a new agent
  212. var agent *kubernetes.Agent
  213. if app.ServerConf.IsTesting {
  214. agent = app.TestAgents.K8sAgent
  215. } else {
  216. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  217. }
  218. configMap := &forms.ConfigMapForm{}
  219. if err := json.NewDecoder(r.Body).Decode(configMap); err != nil {
  220. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  221. return
  222. }
  223. secretData := make(map[string][]byte)
  224. for key, rawValue := range configMap.SecretEnvVariables {
  225. // encodedValue := base64.StdEncoding.EncodeToString([]byte(rawValue))
  226. // if err != nil {
  227. // app.handleErrorInternal(err, w)
  228. // return
  229. // }
  230. secretData[key] = []byte(rawValue)
  231. }
  232. // create secret first
  233. _, err = agent.CreateLinkedSecret(configMap.Name, configMap.Namespace, configMap.Name, secretData)
  234. if err != nil {
  235. app.handleErrorInternal(err, w)
  236. return
  237. }
  238. // add all secret env variables to configmap with value PORTERSECRET_${configmap_name}
  239. for key, _ := range configMap.SecretEnvVariables {
  240. configMap.EnvVariables[key] = fmt.Sprintf("PORTERSECRET_%s", configMap.Name)
  241. }
  242. _, err = agent.CreateConfigMap(configMap.Name, configMap.Namespace, configMap.EnvVariables)
  243. if err != nil {
  244. app.handleErrorInternal(err, w)
  245. return
  246. }
  247. if err := json.NewEncoder(w).Encode(configMap); err != nil {
  248. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  249. return
  250. }
  251. w.WriteHeader(http.StatusOK)
  252. return
  253. }
  254. // HandleListConfigMaps lists all configmaps in a namespace.
  255. func (app *App) HandleListConfigMaps(w http.ResponseWriter, r *http.Request) {
  256. vals, err := url.ParseQuery(r.URL.RawQuery)
  257. if err != nil {
  258. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  259. return
  260. }
  261. // get the filter options
  262. form := &forms.K8sForm{
  263. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  264. Repo: app.Repo,
  265. DigitalOceanOAuth: app.DOConf,
  266. },
  267. }
  268. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  269. // validate the form
  270. if err := app.validator.Struct(form); err != nil {
  271. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  272. return
  273. }
  274. // create a new agent
  275. var agent *kubernetes.Agent
  276. if app.ServerConf.IsTesting {
  277. agent = app.TestAgents.K8sAgent
  278. } else {
  279. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  280. }
  281. configMaps, err := agent.ListConfigMaps(vals["namespace"][0])
  282. if err != nil {
  283. app.handleErrorInternal(err, w)
  284. return
  285. }
  286. if err := json.NewEncoder(w).Encode(configMaps); err != nil {
  287. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  288. return
  289. }
  290. w.WriteHeader(http.StatusOK)
  291. return
  292. }
  293. // HandleGetConfigMap retreives the configmap given the name and namespace.
  294. func (app *App) HandleGetConfigMap(w http.ResponseWriter, r *http.Request) {
  295. vals, err := url.ParseQuery(r.URL.RawQuery)
  296. if err != nil {
  297. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  298. return
  299. }
  300. // get the filter options
  301. form := &forms.K8sForm{
  302. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  303. Repo: app.Repo,
  304. DigitalOceanOAuth: app.DOConf,
  305. },
  306. }
  307. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  308. // validate the form
  309. if err := app.validator.Struct(form); err != nil {
  310. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  311. return
  312. }
  313. // create a new agent
  314. var agent *kubernetes.Agent
  315. if app.ServerConf.IsTesting {
  316. agent = app.TestAgents.K8sAgent
  317. } else {
  318. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  319. }
  320. configMap, err := agent.GetConfigMap(vals["name"][0], vals["namespace"][0])
  321. if err != nil {
  322. app.handleErrorInternal(err, w)
  323. return
  324. }
  325. if err := json.NewEncoder(w).Encode(configMap); err != nil {
  326. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  327. return
  328. }
  329. w.WriteHeader(http.StatusOK)
  330. return
  331. }
  332. // HandleDeleteConfigMap deletes the pod given the name and namespace.
  333. func (app *App) HandleDeleteConfigMap(w http.ResponseWriter, r *http.Request) {
  334. vals, err := url.ParseQuery(r.URL.RawQuery)
  335. if err != nil {
  336. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  337. return
  338. }
  339. // get the filter options
  340. form := &forms.K8sForm{
  341. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  342. Repo: app.Repo,
  343. DigitalOceanOAuth: app.DOConf,
  344. },
  345. }
  346. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  347. // validate the form
  348. if err := app.validator.Struct(form); err != nil {
  349. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  350. return
  351. }
  352. // create a new agent
  353. var agent *kubernetes.Agent
  354. if app.ServerConf.IsTesting {
  355. agent = app.TestAgents.K8sAgent
  356. } else {
  357. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  358. }
  359. err = agent.DeleteLinkedSecret(vals["name"][0], vals["namespace"][0])
  360. if err != nil {
  361. app.handleErrorInternal(err, w)
  362. return
  363. }
  364. err = agent.DeleteConfigMap(vals["name"][0], vals["namespace"][0])
  365. if err != nil {
  366. app.handleErrorInternal(err, w)
  367. return
  368. }
  369. w.WriteHeader(http.StatusOK)
  370. return
  371. }
  372. // HandleUpdateConfigMap deletes the pod given the name and namespace.
  373. func (app *App) HandleUpdateConfigMap(w http.ResponseWriter, r *http.Request) {
  374. vals, err := url.ParseQuery(r.URL.RawQuery)
  375. if err != nil {
  376. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  377. return
  378. }
  379. // get the filter options
  380. form := &forms.K8sForm{
  381. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  382. Repo: app.Repo,
  383. DigitalOceanOAuth: app.DOConf,
  384. },
  385. }
  386. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  387. // validate the form
  388. if err := app.validator.Struct(form); err != nil {
  389. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  390. return
  391. }
  392. // create a new agent
  393. var agent *kubernetes.Agent
  394. if app.ServerConf.IsTesting {
  395. agent = app.TestAgents.K8sAgent
  396. } else {
  397. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  398. }
  399. configMap := &forms.ConfigMapForm{}
  400. if err := json.NewDecoder(r.Body).Decode(configMap); err != nil {
  401. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  402. return
  403. }
  404. secretData := make(map[string][]byte)
  405. for key, rawValue := range configMap.SecretEnvVariables {
  406. // encodedValue, err := base64.StdEncoding.DecodeString(rawValue)
  407. // if err != nil {
  408. // app.handleErrorInternal(err, w)
  409. // return
  410. // }
  411. secretData[key] = []byte(rawValue)
  412. }
  413. // create secret first
  414. err = agent.UpdateLinkedSecret(configMap.Name, configMap.Namespace, configMap.Name, secretData)
  415. if err != nil {
  416. app.handleErrorInternal(err, w)
  417. return
  418. }
  419. // add all secret env variables to configmap with value PORTERSECRET_${configmap_name}
  420. for key, val := range configMap.SecretEnvVariables {
  421. // if val is empty and key does not exist in configmap already, set to empty
  422. if _, found := configMap.EnvVariables[key]; val == "" && !found {
  423. configMap.EnvVariables[key] = ""
  424. } else if val != "" {
  425. configMap.EnvVariables[key] = fmt.Sprintf("PORTERSECRET_%s", configMap.Name)
  426. }
  427. }
  428. err = agent.UpdateConfigMap(configMap.Name, configMap.Namespace, configMap.EnvVariables)
  429. if err != nil {
  430. app.handleErrorInternal(err, w)
  431. return
  432. }
  433. if err := json.NewEncoder(w).Encode(configMap); err != nil {
  434. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  435. return
  436. }
  437. w.WriteHeader(http.StatusOK)
  438. return
  439. }
  440. // HandleGetPodLogs returns real-time logs of the pod via websockets
  441. // TODO: Refactor repeated calls.
  442. func (app *App) HandleGetPodLogs(w http.ResponseWriter, r *http.Request) {
  443. // get session to retrieve correct kubeconfig
  444. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  445. // get path parameters
  446. namespace := chi.URLParam(r, "namespace")
  447. podName := chi.URLParam(r, "name")
  448. if err != nil {
  449. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  450. return
  451. }
  452. vals, err := url.ParseQuery(r.URL.RawQuery)
  453. if err != nil {
  454. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  455. return
  456. }
  457. // get the filter options
  458. form := &forms.K8sForm{
  459. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  460. Repo: app.Repo,
  461. DigitalOceanOAuth: app.DOConf,
  462. },
  463. }
  464. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  465. // validate the form
  466. if err := app.validator.Struct(form); err != nil {
  467. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  468. return
  469. }
  470. // create a new agent
  471. var agent *kubernetes.Agent
  472. if app.ServerConf.IsTesting {
  473. agent = app.TestAgents.K8sAgent
  474. } else {
  475. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  476. }
  477. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  478. // upgrade to websocket.
  479. conn, err := upgrader.Upgrade(w, r, nil)
  480. if err != nil {
  481. app.handleErrorUpgradeWebsocket(err, w)
  482. }
  483. err = agent.GetPodLogs(namespace, podName, conn)
  484. if err != nil {
  485. app.handleErrorWebsocketWrite(err, w)
  486. return
  487. }
  488. }
  489. // HandleDeletePod deletes the pod given the name and namespace.
  490. func (app *App) HandleDeletePod(w http.ResponseWriter, r *http.Request) {
  491. // get path parameters
  492. namespace := chi.URLParam(r, "namespace")
  493. name := chi.URLParam(r, "name")
  494. vals, err := url.ParseQuery(r.URL.RawQuery)
  495. if err != nil {
  496. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  497. return
  498. }
  499. // get the filter options
  500. form := &forms.K8sForm{
  501. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  502. Repo: app.Repo,
  503. DigitalOceanOAuth: app.DOConf,
  504. },
  505. }
  506. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  507. // validate the form
  508. if err := app.validator.Struct(form); err != nil {
  509. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  510. return
  511. }
  512. // create a new agent
  513. var agent *kubernetes.Agent
  514. if app.ServerConf.IsTesting {
  515. agent = app.TestAgents.K8sAgent
  516. } else {
  517. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  518. }
  519. err = agent.DeletePod(namespace, name)
  520. if err != nil {
  521. app.handleErrorInternal(err, w)
  522. return
  523. }
  524. w.WriteHeader(http.StatusOK)
  525. return
  526. }
  527. // HandleGetIngress returns the ingress object given the name and namespace.
  528. func (app *App) HandleGetIngress(w http.ResponseWriter, r *http.Request) {
  529. // get session to retrieve correct kubeconfig
  530. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  531. // get path parameters
  532. namespace := chi.URLParam(r, "namespace")
  533. name := chi.URLParam(r, "name")
  534. if err != nil {
  535. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  536. return
  537. }
  538. vals, err := url.ParseQuery(r.URL.RawQuery)
  539. if err != nil {
  540. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  541. return
  542. }
  543. // get the filter options
  544. form := &forms.K8sForm{
  545. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  546. Repo: app.Repo,
  547. DigitalOceanOAuth: app.DOConf,
  548. },
  549. }
  550. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  551. // validate the form
  552. if err := app.validator.Struct(form); err != nil {
  553. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  554. return
  555. }
  556. // create a new agent
  557. var agent *kubernetes.Agent
  558. if app.ServerConf.IsTesting {
  559. agent = app.TestAgents.K8sAgent
  560. } else {
  561. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  562. }
  563. ingress, err := agent.GetIngress(namespace, name)
  564. if err != nil {
  565. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  566. return
  567. }
  568. if err := json.NewEncoder(w).Encode(ingress); err != nil {
  569. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  570. return
  571. }
  572. }
  573. // HandleListPods returns all pods that match the given selectors
  574. // TODO: Refactor repeated calls.
  575. func (app *App) HandleListPods(w http.ResponseWriter, r *http.Request) {
  576. // get session to retrieve correct kubeconfig
  577. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  578. if err != nil {
  579. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  580. return
  581. }
  582. vals, err := url.ParseQuery(r.URL.RawQuery)
  583. if err != nil {
  584. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  585. return
  586. }
  587. // get the filter options
  588. form := &forms.K8sForm{
  589. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  590. Repo: app.Repo,
  591. DigitalOceanOAuth: app.DOConf,
  592. },
  593. }
  594. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  595. // validate the form
  596. if err := app.validator.Struct(form); err != nil {
  597. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  598. return
  599. }
  600. // create a new agent
  601. var agent *kubernetes.Agent
  602. if app.ServerConf.IsTesting {
  603. agent = app.TestAgents.K8sAgent
  604. } else {
  605. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  606. }
  607. namespace := vals.Get("namespace")
  608. pods := []v1.Pod{}
  609. for _, selector := range vals["selectors"] {
  610. podsList, err := agent.GetPodsByLabel(selector, namespace)
  611. if err != nil {
  612. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  613. return
  614. }
  615. for _, pod := range podsList.Items {
  616. pods = append(pods, pod)
  617. }
  618. }
  619. if err := json.NewEncoder(w).Encode(pods); err != nil {
  620. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  621. return
  622. }
  623. }
  624. // HandleListJobsByChart lists all jobs belonging to a specific Helm chart
  625. func (app *App) HandleListJobsByChart(w http.ResponseWriter, r *http.Request) {
  626. // get path parameters
  627. namespace := chi.URLParam(r, "namespace")
  628. chart := chi.URLParam(r, "chart")
  629. releaseName := chi.URLParam(r, "release_name")
  630. vals, err := url.ParseQuery(r.URL.RawQuery)
  631. if err != nil {
  632. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  633. return
  634. }
  635. // get the filter options
  636. form := &forms.K8sForm{
  637. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  638. Repo: app.Repo,
  639. DigitalOceanOAuth: app.DOConf,
  640. },
  641. }
  642. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  643. // validate the form
  644. if err := app.validator.Struct(form); err != nil {
  645. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  646. return
  647. }
  648. // create a new agent
  649. var agent *kubernetes.Agent
  650. if app.ServerConf.IsTesting {
  651. agent = app.TestAgents.K8sAgent
  652. } else {
  653. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  654. }
  655. jobs, err := agent.ListJobsByLabel(namespace, kubernetes.Label{
  656. Key: "helm.sh/chart",
  657. Val: chart,
  658. }, kubernetes.Label{
  659. Key: "meta.helm.sh/release-name",
  660. Val: releaseName,
  661. })
  662. if err != nil {
  663. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  664. return
  665. }
  666. if err := json.NewEncoder(w).Encode(jobs); err != nil {
  667. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  668. return
  669. }
  670. }
  671. // HandleDeleteConfigMap deletes the pod given the name and namespace.
  672. func (app *App) HandleDeleteJob(w http.ResponseWriter, r *http.Request) {
  673. // get path parameters
  674. namespace := chi.URLParam(r, "namespace")
  675. name := chi.URLParam(r, "name")
  676. vals, err := url.ParseQuery(r.URL.RawQuery)
  677. if err != nil {
  678. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  679. return
  680. }
  681. // get the filter options
  682. form := &forms.K8sForm{
  683. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  684. Repo: app.Repo,
  685. DigitalOceanOAuth: app.DOConf,
  686. },
  687. }
  688. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  689. // validate the form
  690. if err := app.validator.Struct(form); err != nil {
  691. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  692. return
  693. }
  694. // create a new agent
  695. var agent *kubernetes.Agent
  696. if app.ServerConf.IsTesting {
  697. agent = app.TestAgents.K8sAgent
  698. } else {
  699. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  700. }
  701. err = agent.DeleteJob(name, namespace)
  702. if err != nil {
  703. app.handleErrorInternal(err, w)
  704. return
  705. }
  706. w.WriteHeader(http.StatusOK)
  707. return
  708. }
  709. // HandleStopJob stops a running job
  710. func (app *App) HandleStopJob(w http.ResponseWriter, r *http.Request) {
  711. // get path parameters
  712. namespace := chi.URLParam(r, "namespace")
  713. name := chi.URLParam(r, "name")
  714. vals, err := url.ParseQuery(r.URL.RawQuery)
  715. if err != nil {
  716. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  717. return
  718. }
  719. // get the filter options
  720. form := &forms.K8sForm{
  721. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  722. Repo: app.Repo,
  723. DigitalOceanOAuth: app.DOConf,
  724. },
  725. }
  726. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  727. // validate the form
  728. if err := app.validator.Struct(form); err != nil {
  729. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  730. return
  731. }
  732. // create a new agent
  733. var agent *kubernetes.Agent
  734. if app.ServerConf.IsTesting {
  735. agent = app.TestAgents.K8sAgent
  736. } else {
  737. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  738. }
  739. err = agent.StopJobWithJobSidecar(namespace, name)
  740. if err != nil {
  741. app.sendExternalError(err, 500, HTTPError{
  742. Code: 500,
  743. Errors: []string{err.Error()},
  744. }, w)
  745. return
  746. }
  747. w.WriteHeader(http.StatusOK)
  748. return
  749. }
  750. // HandleListJobPods lists all pods belonging to a specific job
  751. func (app *App) HandleListJobPods(w http.ResponseWriter, r *http.Request) {
  752. // get path parameters
  753. namespace := chi.URLParam(r, "namespace")
  754. name := chi.URLParam(r, "name")
  755. vals, err := url.ParseQuery(r.URL.RawQuery)
  756. if err != nil {
  757. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  758. return
  759. }
  760. // get the filter options
  761. form := &forms.K8sForm{
  762. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  763. Repo: app.Repo,
  764. DigitalOceanOAuth: app.DOConf,
  765. },
  766. }
  767. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  768. // validate the form
  769. if err := app.validator.Struct(form); err != nil {
  770. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  771. return
  772. }
  773. // create a new agent
  774. var agent *kubernetes.Agent
  775. if app.ServerConf.IsTesting {
  776. agent = app.TestAgents.K8sAgent
  777. } else {
  778. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  779. }
  780. pods, err := agent.GetJobPods(namespace, name)
  781. if err != nil {
  782. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  783. return
  784. }
  785. if err := json.NewEncoder(w).Encode(pods); err != nil {
  786. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  787. return
  788. }
  789. }
  790. // HandleStreamControllerStatus test calls
  791. // TODO: Refactor repeated calls.
  792. func (app *App) HandleStreamControllerStatus(w http.ResponseWriter, r *http.Request) {
  793. vals, err := url.ParseQuery(r.URL.RawQuery)
  794. if err != nil {
  795. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  796. return
  797. }
  798. // get session to retrieve correct kubeconfig
  799. _, err = app.Store.Get(r, app.ServerConf.CookieName)
  800. if err != nil {
  801. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  802. return
  803. }
  804. // get the filter options
  805. form := &forms.K8sForm{
  806. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  807. Repo: app.Repo,
  808. DigitalOceanOAuth: app.DOConf,
  809. },
  810. }
  811. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  812. // validate the form
  813. if err := app.validator.Struct(form); err != nil {
  814. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  815. return
  816. }
  817. // create a new agent
  818. var agent *kubernetes.Agent
  819. if app.ServerConf.IsTesting {
  820. agent = app.TestAgents.K8sAgent
  821. } else {
  822. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  823. }
  824. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  825. // upgrade to websocket.
  826. conn, err := upgrader.Upgrade(w, r, nil)
  827. if err != nil {
  828. app.handleErrorUpgradeWebsocket(err, w)
  829. }
  830. // get path parameters
  831. kind := chi.URLParam(r, "kind")
  832. selectors := ""
  833. if vals["selectors"] != nil {
  834. selectors = vals["selectors"][0]
  835. }
  836. err = agent.StreamControllerStatus(conn, kind, selectors)
  837. if err != nil {
  838. app.handleErrorWebsocketWrite(err, w)
  839. return
  840. }
  841. }
  842. func (app *App) HandleStreamHelmReleases(w http.ResponseWriter, r *http.Request) {
  843. vals, err := url.ParseQuery(r.URL.RawQuery)
  844. if err != nil {
  845. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  846. return
  847. }
  848. // get session to retrieve correct kubeconfig
  849. _, err = app.Store.Get(r, app.ServerConf.CookieName)
  850. if err != nil {
  851. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  852. return
  853. }
  854. // get the filter options
  855. form := &forms.K8sForm{
  856. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  857. Repo: app.Repo,
  858. DigitalOceanOAuth: app.DOConf,
  859. },
  860. }
  861. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  862. // validate the form
  863. if err := app.validator.Struct(form); err != nil {
  864. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  865. return
  866. }
  867. // create a new agent
  868. var agent *kubernetes.Agent
  869. if app.ServerConf.IsTesting {
  870. agent = app.TestAgents.K8sAgent
  871. } else {
  872. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  873. }
  874. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  875. // upgrade to websocket.
  876. conn, err := upgrader.Upgrade(w, r, nil)
  877. if err != nil {
  878. app.handleErrorUpgradeWebsocket(err, w)
  879. }
  880. selectors := ""
  881. if vals["selectors"] != nil {
  882. selectors = vals["selectors"][0]
  883. }
  884. var chartList []string
  885. if vals["charts"] != nil {
  886. chartList = vals["charts"]
  887. }
  888. err = agent.StreamHelmReleases(conn, chartList, selectors)
  889. if err != nil {
  890. app.handleErrorWebsocketWrite(err, w)
  891. return
  892. }
  893. }
  894. // HandleDetectPrometheusInstalled detects a prometheus installation in the target cluster
  895. func (app *App) HandleDetectPrometheusInstalled(w http.ResponseWriter, r *http.Request) {
  896. vals, err := url.ParseQuery(r.URL.RawQuery)
  897. if err != nil {
  898. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  899. return
  900. }
  901. // get the filter options
  902. form := &forms.K8sForm{
  903. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  904. Repo: app.Repo,
  905. DigitalOceanOAuth: app.DOConf,
  906. },
  907. }
  908. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  909. // validate the form
  910. if err := app.validator.Struct(form); err != nil {
  911. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  912. return
  913. }
  914. // create a new agent
  915. var agent *kubernetes.Agent
  916. if app.ServerConf.IsTesting {
  917. agent = app.TestAgents.K8sAgent
  918. } else {
  919. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  920. }
  921. // detect prometheus service
  922. _, found, err := prometheus.GetPrometheusService(agent.Clientset)
  923. if !found {
  924. http.NotFound(w, r)
  925. return
  926. }
  927. w.WriteHeader(http.StatusOK)
  928. return
  929. }
  930. // HandleListNGINXIngresses lists all NGINX ingresses in a target cluster
  931. func (app *App) HandleListNGINXIngresses(w http.ResponseWriter, r *http.Request) {
  932. vals, err := url.ParseQuery(r.URL.RawQuery)
  933. if err != nil {
  934. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  935. return
  936. }
  937. // get the filter options
  938. form := &forms.K8sForm{
  939. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  940. Repo: app.Repo,
  941. DigitalOceanOAuth: app.DOConf,
  942. },
  943. }
  944. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  945. // validate the form
  946. if err := app.validator.Struct(form); err != nil {
  947. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  948. return
  949. }
  950. // create a new agent
  951. var agent *kubernetes.Agent
  952. if app.ServerConf.IsTesting {
  953. agent = app.TestAgents.K8sAgent
  954. } else {
  955. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  956. }
  957. res, err := prometheus.GetIngressesWithNGINXAnnotation(agent.Clientset)
  958. if err != nil {
  959. app.handleErrorInternal(err, w)
  960. return
  961. }
  962. w.WriteHeader(http.StatusOK)
  963. if err := json.NewEncoder(w).Encode(res); err != nil {
  964. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  965. return
  966. }
  967. }
  968. func (app *App) HandleGetPodMetrics(w http.ResponseWriter, r *http.Request) {
  969. vals, err := url.ParseQuery(r.URL.RawQuery)
  970. if err != nil {
  971. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  972. return
  973. }
  974. // get the filter options
  975. form := &forms.MetricsQueryForm{
  976. K8sForm: &forms.K8sForm{
  977. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  978. Repo: app.Repo,
  979. DigitalOceanOAuth: app.DOConf,
  980. },
  981. },
  982. QueryOpts: &prometheus.QueryOpts{},
  983. }
  984. form.K8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  985. // decode from JSON to form value
  986. decoder := schema.NewDecoder()
  987. decoder.IgnoreUnknownKeys(true)
  988. if err := decoder.Decode(form.QueryOpts, vals); err != nil {
  989. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  990. return
  991. }
  992. // validate the form
  993. if err := app.validator.Struct(form); err != nil {
  994. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  995. return
  996. }
  997. // create a new agent
  998. var agent *kubernetes.Agent
  999. if app.ServerConf.IsTesting {
  1000. agent = app.TestAgents.K8sAgent
  1001. } else {
  1002. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  1003. }
  1004. // get prometheus service
  1005. promSvc, found, err := prometheus.GetPrometheusService(agent.Clientset)
  1006. if err != nil {
  1007. app.handleErrorInternal(err, w)
  1008. return
  1009. }
  1010. if !found {
  1011. app.handleErrorInternal(err, w)
  1012. return
  1013. }
  1014. rawQuery, err := prometheus.QueryPrometheus(agent.Clientset, promSvc, form.QueryOpts)
  1015. if err != nil {
  1016. app.handleErrorInternal(err, w)
  1017. return
  1018. }
  1019. fmt.Fprint(w, string(rawQuery))
  1020. }
  1021. type KubeconfigResponse struct {
  1022. Kubeconfig []byte `json:"kubeconfig"`
  1023. }
  1024. func (app *App) HandleGetTemporaryKubeconfig(w http.ResponseWriter, r *http.Request) {
  1025. vals, err := url.ParseQuery(r.URL.RawQuery)
  1026. if err != nil {
  1027. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  1028. return
  1029. }
  1030. // get the filter options
  1031. form := &forms.K8sForm{
  1032. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  1033. Repo: app.Repo,
  1034. DigitalOceanOAuth: app.DOConf,
  1035. },
  1036. }
  1037. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  1038. // get the API config
  1039. apiConf, err := form.OutOfClusterConfig.CreateRawConfigFromCluster()
  1040. if err != nil {
  1041. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  1042. return
  1043. }
  1044. bytes, err := clientcmd.Write(*apiConf)
  1045. res := &KubeconfigResponse{
  1046. Kubeconfig: bytes,
  1047. }
  1048. if err := json.NewEncoder(w).Encode(res); err != nil {
  1049. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  1050. return
  1051. }
  1052. }
  1053. func (app *App) HandleListNodes(w http.ResponseWriter, r *http.Request) {
  1054. id, err := strconv.ParseUint(chi.URLParam(r, "cluster_id"), 0, 64)
  1055. if err != nil || id == 0 {
  1056. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  1057. return
  1058. }
  1059. cluster, err := app.Repo.Cluster.ReadCluster(uint(id))
  1060. if err != nil {
  1061. app.handleErrorRead(err, ErrProjectDataRead, w)
  1062. return
  1063. }
  1064. form := &forms.K8sForm{
  1065. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  1066. Repo: app.Repo,
  1067. DigitalOceanOAuth: app.DOConf,
  1068. Cluster: cluster,
  1069. },
  1070. }
  1071. var agent *kubernetes.Agent
  1072. if app.ServerConf.IsTesting {
  1073. agent = app.TestAgents.K8sAgent
  1074. } else {
  1075. agent, _ = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  1076. }
  1077. nodeWithUsageList := nodes.GetNodesUsage(agent.Clientset)
  1078. w.WriteHeader(http.StatusOK)
  1079. if err := json.NewEncoder(w).Encode(nodeWithUsageList); err != nil {
  1080. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  1081. return
  1082. }
  1083. }
  1084. func (app *App) HandleGetNode(w http.ResponseWriter, r *http.Request) {
  1085. cluster_id, err := strconv.ParseUint(chi.URLParam(r, "cluster_id"), 0, 64)
  1086. node_name := chi.URLParam(r, "node_name")
  1087. if err != nil || cluster_id == 0 {
  1088. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  1089. return
  1090. }
  1091. cluster, err := app.Repo.Cluster.ReadCluster(uint(cluster_id))
  1092. if err != nil {
  1093. app.handleErrorRead(err, ErrProjectDataRead, w)
  1094. return
  1095. }
  1096. form := &forms.K8sForm{
  1097. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  1098. Repo: app.Repo,
  1099. DigitalOceanOAuth: app.DOConf,
  1100. Cluster: cluster,
  1101. },
  1102. }
  1103. var agent *kubernetes.Agent
  1104. if app.ServerConf.IsTesting {
  1105. agent = app.TestAgents.K8sAgent
  1106. } else {
  1107. agent, _ = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  1108. }
  1109. nodeWithUsageData := nodes.DescribeNode(agent.Clientset, node_name)
  1110. w.WriteHeader(http.StatusOK)
  1111. if err := json.NewEncoder(w).Encode(nodeWithUsageData); err != nil {
  1112. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  1113. return
  1114. }
  1115. }