k8s_handler.go 23 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010
  1. package api
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "github.com/go-chi/chi"
  8. "github.com/gorilla/schema"
  9. "github.com/gorilla/websocket"
  10. "github.com/porter-dev/porter/internal/forms"
  11. "github.com/porter-dev/porter/internal/kubernetes"
  12. "github.com/porter-dev/porter/internal/kubernetes/prometheus"
  13. v1 "k8s.io/api/core/v1"
  14. "k8s.io/client-go/tools/clientcmd"
  15. )
  16. // Enumeration of k8s API error codes, represented as int64
  17. const (
  18. ErrK8sDecode ErrorCode = iota + 600
  19. ErrK8sValidate
  20. ErrEnvDecode
  21. )
  22. var upgrader = websocket.Upgrader{
  23. ReadBufferSize: 1024,
  24. WriteBufferSize: 1024,
  25. }
  26. // HandleListNamespaces retrieves a list of namespaces
  27. func (app *App) HandleListNamespaces(w http.ResponseWriter, r *http.Request) {
  28. vals, err := url.ParseQuery(r.URL.RawQuery)
  29. if err != nil {
  30. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  31. return
  32. }
  33. // get the filter options
  34. form := &forms.K8sForm{
  35. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  36. Repo: app.Repo,
  37. DigitalOceanOAuth: app.DOConf,
  38. },
  39. }
  40. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  41. // validate the form
  42. if err := app.validator.Struct(form); err != nil {
  43. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  44. return
  45. }
  46. // create a new agent
  47. var agent *kubernetes.Agent
  48. if app.ServerConf.IsTesting {
  49. agent = app.TestAgents.K8sAgent
  50. } else {
  51. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  52. }
  53. namespaces, err := agent.ListNamespaces()
  54. if err != nil {
  55. app.handleErrorDataRead(err, w)
  56. return
  57. }
  58. if err := json.NewEncoder(w).Encode(namespaces); err != nil {
  59. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  60. return
  61. }
  62. }
  63. // HandleCreateConfigMap deletes the pod given the name and namespace.
  64. func (app *App) HandleCreateConfigMap(w http.ResponseWriter, r *http.Request) {
  65. vals, err := url.ParseQuery(r.URL.RawQuery)
  66. if err != nil {
  67. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  68. return
  69. }
  70. // get the filter options
  71. form := &forms.K8sForm{
  72. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  73. Repo: app.Repo,
  74. DigitalOceanOAuth: app.DOConf,
  75. },
  76. }
  77. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  78. // validate the form
  79. if err := app.validator.Struct(form); err != nil {
  80. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  81. return
  82. }
  83. // create a new agent
  84. var agent *kubernetes.Agent
  85. if app.ServerConf.IsTesting {
  86. agent = app.TestAgents.K8sAgent
  87. } else {
  88. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  89. }
  90. configMap := &forms.ConfigMapForm{}
  91. if err := json.NewDecoder(r.Body).Decode(configMap); err != nil {
  92. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  93. return
  94. }
  95. secretData := make(map[string][]byte)
  96. for key, rawValue := range configMap.SecretEnvVariables {
  97. // encodedValue := base64.StdEncoding.EncodeToString([]byte(rawValue))
  98. // if err != nil {
  99. // app.handleErrorInternal(err, w)
  100. // return
  101. // }
  102. secretData[key] = []byte(rawValue)
  103. }
  104. // create secret first
  105. _, err = agent.CreateLinkedSecret(configMap.Name, configMap.Namespace, configMap.Name, secretData)
  106. if err != nil {
  107. app.handleErrorInternal(err, w)
  108. return
  109. }
  110. // add all secret env variables to configmap with value PORTERSECRET_${configmap_name}
  111. for key, _ := range configMap.SecretEnvVariables {
  112. configMap.EnvVariables[key] = fmt.Sprintf("PORTERSECRET_%s", configMap.Name)
  113. }
  114. _, err = agent.CreateConfigMap(configMap.Name, configMap.Namespace, configMap.EnvVariables)
  115. if err != nil {
  116. app.handleErrorInternal(err, w)
  117. return
  118. }
  119. if err := json.NewEncoder(w).Encode(configMap); err != nil {
  120. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  121. return
  122. }
  123. w.WriteHeader(http.StatusOK)
  124. return
  125. }
  126. // HandleListConfigMaps lists all configmaps in a namespace.
  127. func (app *App) HandleListConfigMaps(w http.ResponseWriter, r *http.Request) {
  128. vals, err := url.ParseQuery(r.URL.RawQuery)
  129. if err != nil {
  130. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  131. return
  132. }
  133. // get the filter options
  134. form := &forms.K8sForm{
  135. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  136. Repo: app.Repo,
  137. DigitalOceanOAuth: app.DOConf,
  138. },
  139. }
  140. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  141. // validate the form
  142. if err := app.validator.Struct(form); err != nil {
  143. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  144. return
  145. }
  146. // create a new agent
  147. var agent *kubernetes.Agent
  148. if app.ServerConf.IsTesting {
  149. agent = app.TestAgents.K8sAgent
  150. } else {
  151. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  152. }
  153. configMaps, err := agent.ListConfigMaps(vals["namespace"][0])
  154. if err != nil {
  155. app.handleErrorInternal(err, w)
  156. return
  157. }
  158. if err := json.NewEncoder(w).Encode(configMaps); err != nil {
  159. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  160. return
  161. }
  162. w.WriteHeader(http.StatusOK)
  163. return
  164. }
  165. // HandleGetConfigMap retreives the configmap given the name and namespace.
  166. func (app *App) HandleGetConfigMap(w http.ResponseWriter, r *http.Request) {
  167. vals, err := url.ParseQuery(r.URL.RawQuery)
  168. if err != nil {
  169. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  170. return
  171. }
  172. // get the filter options
  173. form := &forms.K8sForm{
  174. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  175. Repo: app.Repo,
  176. DigitalOceanOAuth: app.DOConf,
  177. },
  178. }
  179. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  180. // validate the form
  181. if err := app.validator.Struct(form); err != nil {
  182. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  183. return
  184. }
  185. // create a new agent
  186. var agent *kubernetes.Agent
  187. if app.ServerConf.IsTesting {
  188. agent = app.TestAgents.K8sAgent
  189. } else {
  190. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  191. }
  192. configMap, err := agent.GetConfigMap(vals["name"][0], vals["namespace"][0])
  193. if err != nil {
  194. app.handleErrorInternal(err, w)
  195. return
  196. }
  197. if err := json.NewEncoder(w).Encode(configMap); err != nil {
  198. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  199. return
  200. }
  201. w.WriteHeader(http.StatusOK)
  202. return
  203. }
  204. // HandleDeleteConfigMap deletes the pod given the name and namespace.
  205. func (app *App) HandleDeleteConfigMap(w http.ResponseWriter, r *http.Request) {
  206. vals, err := url.ParseQuery(r.URL.RawQuery)
  207. if err != nil {
  208. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  209. return
  210. }
  211. // get the filter options
  212. form := &forms.K8sForm{
  213. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  214. Repo: app.Repo,
  215. DigitalOceanOAuth: app.DOConf,
  216. },
  217. }
  218. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  219. // validate the form
  220. if err := app.validator.Struct(form); err != nil {
  221. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  222. return
  223. }
  224. // create a new agent
  225. var agent *kubernetes.Agent
  226. if app.ServerConf.IsTesting {
  227. agent = app.TestAgents.K8sAgent
  228. } else {
  229. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  230. }
  231. err = agent.DeleteLinkedSecret(vals["name"][0], vals["namespace"][0])
  232. if err != nil {
  233. app.handleErrorInternal(err, w)
  234. return
  235. }
  236. err = agent.DeleteConfigMap(vals["name"][0], vals["namespace"][0])
  237. if err != nil {
  238. app.handleErrorInternal(err, w)
  239. return
  240. }
  241. w.WriteHeader(http.StatusOK)
  242. return
  243. }
  244. // HandleUpdateConfigMap deletes the pod given the name and namespace.
  245. func (app *App) HandleUpdateConfigMap(w http.ResponseWriter, r *http.Request) {
  246. vals, err := url.ParseQuery(r.URL.RawQuery)
  247. if err != nil {
  248. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  249. return
  250. }
  251. // get the filter options
  252. form := &forms.K8sForm{
  253. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  254. Repo: app.Repo,
  255. DigitalOceanOAuth: app.DOConf,
  256. },
  257. }
  258. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  259. // validate the form
  260. if err := app.validator.Struct(form); err != nil {
  261. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  262. return
  263. }
  264. // create a new agent
  265. var agent *kubernetes.Agent
  266. if app.ServerConf.IsTesting {
  267. agent = app.TestAgents.K8sAgent
  268. } else {
  269. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  270. }
  271. configMap := &forms.ConfigMapForm{}
  272. if err := json.NewDecoder(r.Body).Decode(configMap); err != nil {
  273. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  274. return
  275. }
  276. secretData := make(map[string][]byte)
  277. for key, rawValue := range configMap.SecretEnvVariables {
  278. // encodedValue, err := base64.StdEncoding.DecodeString(rawValue)
  279. // if err != nil {
  280. // app.handleErrorInternal(err, w)
  281. // return
  282. // }
  283. secretData[key] = []byte(rawValue)
  284. }
  285. // create secret first
  286. err = agent.UpdateLinkedSecret(configMap.Name, configMap.Namespace, configMap.Name, secretData)
  287. if err != nil {
  288. app.handleErrorInternal(err, w)
  289. return
  290. }
  291. // add all secret env variables to configmap with value PORTERSECRET_${configmap_name}
  292. for key, val := range configMap.SecretEnvVariables {
  293. configMap.EnvVariables[key] = fmt.Sprintf("PORTERSECRET_%s", configMap.Name)
  294. // if val is empty, set to empty
  295. if val == "" {
  296. configMap.EnvVariables[key] = ""
  297. }
  298. }
  299. err = agent.UpdateConfigMap(configMap.Name, configMap.Namespace, configMap.EnvVariables)
  300. if err != nil {
  301. app.handleErrorInternal(err, w)
  302. return
  303. }
  304. if err := json.NewEncoder(w).Encode(configMap); err != nil {
  305. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  306. return
  307. }
  308. w.WriteHeader(http.StatusOK)
  309. return
  310. }
  311. // HandleGetPodLogs returns real-time logs of the pod via websockets
  312. // TODO: Refactor repeated calls.
  313. func (app *App) HandleGetPodLogs(w http.ResponseWriter, r *http.Request) {
  314. // get session to retrieve correct kubeconfig
  315. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  316. // get path parameters
  317. namespace := chi.URLParam(r, "namespace")
  318. podName := chi.URLParam(r, "name")
  319. if err != nil {
  320. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  321. return
  322. }
  323. vals, err := url.ParseQuery(r.URL.RawQuery)
  324. if err != nil {
  325. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  326. return
  327. }
  328. // get the filter options
  329. form := &forms.K8sForm{
  330. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  331. Repo: app.Repo,
  332. DigitalOceanOAuth: app.DOConf,
  333. },
  334. }
  335. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  336. // validate the form
  337. if err := app.validator.Struct(form); err != nil {
  338. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  339. return
  340. }
  341. // create a new agent
  342. var agent *kubernetes.Agent
  343. if app.ServerConf.IsTesting {
  344. agent = app.TestAgents.K8sAgent
  345. } else {
  346. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  347. }
  348. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  349. // upgrade to websocket.
  350. conn, err := upgrader.Upgrade(w, r, nil)
  351. if err != nil {
  352. app.handleErrorUpgradeWebsocket(err, w)
  353. }
  354. err = agent.GetPodLogs(namespace, podName, conn)
  355. if err != nil {
  356. app.handleErrorWebsocketWrite(err, w)
  357. return
  358. }
  359. }
  360. // HandleDeletePod deletes the pod given the name and namespace.
  361. func (app *App) HandleDeletePod(w http.ResponseWriter, r *http.Request) {
  362. // get path parameters
  363. namespace := chi.URLParam(r, "namespace")
  364. name := chi.URLParam(r, "name")
  365. vals, err := url.ParseQuery(r.URL.RawQuery)
  366. if err != nil {
  367. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  368. return
  369. }
  370. // get the filter options
  371. form := &forms.K8sForm{
  372. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  373. Repo: app.Repo,
  374. DigitalOceanOAuth: app.DOConf,
  375. },
  376. }
  377. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  378. // validate the form
  379. if err := app.validator.Struct(form); err != nil {
  380. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  381. return
  382. }
  383. // create a new agent
  384. var agent *kubernetes.Agent
  385. if app.ServerConf.IsTesting {
  386. agent = app.TestAgents.K8sAgent
  387. } else {
  388. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  389. }
  390. err = agent.DeletePod(namespace, name)
  391. if err != nil {
  392. app.handleErrorInternal(err, w)
  393. return
  394. }
  395. w.WriteHeader(http.StatusOK)
  396. return
  397. }
  398. // HandleGetIngress returns the ingress object given the name and namespace.
  399. func (app *App) HandleGetIngress(w http.ResponseWriter, r *http.Request) {
  400. // get session to retrieve correct kubeconfig
  401. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  402. // get path parameters
  403. namespace := chi.URLParam(r, "namespace")
  404. name := chi.URLParam(r, "name")
  405. if err != nil {
  406. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  407. return
  408. }
  409. vals, err := url.ParseQuery(r.URL.RawQuery)
  410. if err != nil {
  411. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  412. return
  413. }
  414. // get the filter options
  415. form := &forms.K8sForm{
  416. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  417. Repo: app.Repo,
  418. DigitalOceanOAuth: app.DOConf,
  419. },
  420. }
  421. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  422. // validate the form
  423. if err := app.validator.Struct(form); err != nil {
  424. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  425. return
  426. }
  427. // create a new agent
  428. var agent *kubernetes.Agent
  429. if app.ServerConf.IsTesting {
  430. agent = app.TestAgents.K8sAgent
  431. } else {
  432. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  433. }
  434. ingress, err := agent.GetIngress(namespace, name)
  435. if err != nil {
  436. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  437. return
  438. }
  439. if err := json.NewEncoder(w).Encode(ingress); err != nil {
  440. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  441. return
  442. }
  443. }
  444. // HandleListPods returns all pods that match the given selectors
  445. // TODO: Refactor repeated calls.
  446. func (app *App) HandleListPods(w http.ResponseWriter, r *http.Request) {
  447. // get session to retrieve correct kubeconfig
  448. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  449. if err != nil {
  450. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  451. return
  452. }
  453. vals, err := url.ParseQuery(r.URL.RawQuery)
  454. if err != nil {
  455. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  456. return
  457. }
  458. // get the filter options
  459. form := &forms.K8sForm{
  460. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  461. Repo: app.Repo,
  462. DigitalOceanOAuth: app.DOConf,
  463. },
  464. }
  465. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  466. // validate the form
  467. if err := app.validator.Struct(form); err != nil {
  468. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  469. return
  470. }
  471. // create a new agent
  472. var agent *kubernetes.Agent
  473. if app.ServerConf.IsTesting {
  474. agent = app.TestAgents.K8sAgent
  475. } else {
  476. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  477. }
  478. pods := []v1.Pod{}
  479. for _, selector := range vals["selectors"] {
  480. podsList, err := agent.GetPodsByLabel(selector)
  481. if err != nil {
  482. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  483. return
  484. }
  485. for _, pod := range podsList.Items {
  486. pods = append(pods, pod)
  487. }
  488. }
  489. if err := json.NewEncoder(w).Encode(pods); err != nil {
  490. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  491. return
  492. }
  493. }
  494. // HandleListJobsByChart lists all jobs belonging to a specific Helm chart
  495. func (app *App) HandleListJobsByChart(w http.ResponseWriter, r *http.Request) {
  496. // get path parameters
  497. namespace := chi.URLParam(r, "namespace")
  498. chart := chi.URLParam(r, "chart")
  499. releaseName := chi.URLParam(r, "release_name")
  500. vals, err := url.ParseQuery(r.URL.RawQuery)
  501. if err != nil {
  502. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  503. return
  504. }
  505. // get the filter options
  506. form := &forms.K8sForm{
  507. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  508. Repo: app.Repo,
  509. DigitalOceanOAuth: app.DOConf,
  510. },
  511. }
  512. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  513. // validate the form
  514. if err := app.validator.Struct(form); err != nil {
  515. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  516. return
  517. }
  518. // create a new agent
  519. var agent *kubernetes.Agent
  520. if app.ServerConf.IsTesting {
  521. agent = app.TestAgents.K8sAgent
  522. } else {
  523. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  524. }
  525. jobs, err := agent.ListJobsByLabel(namespace, kubernetes.Label{
  526. Key: "helm.sh/chart",
  527. Val: chart,
  528. }, kubernetes.Label{
  529. Key: "meta.helm.sh/release-name",
  530. Val: releaseName,
  531. })
  532. if err != nil {
  533. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  534. return
  535. }
  536. if err := json.NewEncoder(w).Encode(jobs); err != nil {
  537. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  538. return
  539. }
  540. }
  541. // HandleListJobPods lists all pods belonging to a specific job
  542. func (app *App) HandleListJobPods(w http.ResponseWriter, r *http.Request) {
  543. // get path parameters
  544. namespace := chi.URLParam(r, "namespace")
  545. name := chi.URLParam(r, "name")
  546. vals, err := url.ParseQuery(r.URL.RawQuery)
  547. if err != nil {
  548. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  549. return
  550. }
  551. // get the filter options
  552. form := &forms.K8sForm{
  553. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  554. Repo: app.Repo,
  555. DigitalOceanOAuth: app.DOConf,
  556. },
  557. }
  558. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  559. // validate the form
  560. if err := app.validator.Struct(form); err != nil {
  561. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  562. return
  563. }
  564. // create a new agent
  565. var agent *kubernetes.Agent
  566. if app.ServerConf.IsTesting {
  567. agent = app.TestAgents.K8sAgent
  568. } else {
  569. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  570. }
  571. pods, err := agent.GetJobPods(namespace, name)
  572. if err != nil {
  573. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  574. return
  575. }
  576. if err := json.NewEncoder(w).Encode(pods); err != nil {
  577. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  578. return
  579. }
  580. }
  581. // HandleStreamControllerStatus test calls
  582. // TODO: Refactor repeated calls.
  583. func (app *App) HandleStreamControllerStatus(w http.ResponseWriter, r *http.Request) {
  584. // get session to retrieve correct kubeconfig
  585. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  586. if err != nil {
  587. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  588. return
  589. }
  590. vals, err := url.ParseQuery(r.URL.RawQuery)
  591. if err != nil {
  592. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  593. return
  594. }
  595. // get the filter options
  596. form := &forms.K8sForm{
  597. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  598. Repo: app.Repo,
  599. DigitalOceanOAuth: app.DOConf,
  600. },
  601. }
  602. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  603. // validate the form
  604. if err := app.validator.Struct(form); err != nil {
  605. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  606. return
  607. }
  608. // create a new agent
  609. var agent *kubernetes.Agent
  610. if app.ServerConf.IsTesting {
  611. agent = app.TestAgents.K8sAgent
  612. } else {
  613. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  614. }
  615. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  616. // upgrade to websocket.
  617. conn, err := upgrader.Upgrade(w, r, nil)
  618. if err != nil {
  619. app.handleErrorUpgradeWebsocket(err, w)
  620. }
  621. // get path parameters
  622. kind := chi.URLParam(r, "kind")
  623. err = agent.StreamControllerStatus(conn, kind)
  624. if err != nil {
  625. app.handleErrorWebsocketWrite(err, w)
  626. return
  627. }
  628. }
  629. // HandleDetectPrometheusInstalled detects a prometheus installation in the target cluster
  630. func (app *App) HandleDetectPrometheusInstalled(w http.ResponseWriter, r *http.Request) {
  631. vals, err := url.ParseQuery(r.URL.RawQuery)
  632. if err != nil {
  633. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  634. return
  635. }
  636. // get the filter options
  637. form := &forms.K8sForm{
  638. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  639. Repo: app.Repo,
  640. DigitalOceanOAuth: app.DOConf,
  641. },
  642. }
  643. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  644. // validate the form
  645. if err := app.validator.Struct(form); err != nil {
  646. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  647. return
  648. }
  649. // create a new agent
  650. var agent *kubernetes.Agent
  651. if app.ServerConf.IsTesting {
  652. agent = app.TestAgents.K8sAgent
  653. } else {
  654. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  655. }
  656. // detect prometheus service
  657. _, found, err := prometheus.GetPrometheusService(agent.Clientset)
  658. if !found {
  659. http.NotFound(w, r)
  660. return
  661. }
  662. w.WriteHeader(http.StatusOK)
  663. return
  664. }
  665. // HandleListNGINXIngresses lists all NGINX ingresses in a target cluster
  666. func (app *App) HandleListNGINXIngresses(w http.ResponseWriter, r *http.Request) {
  667. vals, err := url.ParseQuery(r.URL.RawQuery)
  668. if err != nil {
  669. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  670. return
  671. }
  672. // get the filter options
  673. form := &forms.K8sForm{
  674. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  675. Repo: app.Repo,
  676. DigitalOceanOAuth: app.DOConf,
  677. },
  678. }
  679. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  680. // validate the form
  681. if err := app.validator.Struct(form); err != nil {
  682. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  683. return
  684. }
  685. // create a new agent
  686. var agent *kubernetes.Agent
  687. if app.ServerConf.IsTesting {
  688. agent = app.TestAgents.K8sAgent
  689. } else {
  690. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  691. }
  692. res, err := prometheus.GetIngressesWithNGINXAnnotation(agent.Clientset)
  693. if err != nil {
  694. app.handleErrorInternal(err, w)
  695. return
  696. }
  697. w.WriteHeader(http.StatusOK)
  698. if err := json.NewEncoder(w).Encode(res); err != nil {
  699. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  700. return
  701. }
  702. }
  703. func (app *App) HandleGetPodMetrics(w http.ResponseWriter, r *http.Request) {
  704. vals, err := url.ParseQuery(r.URL.RawQuery)
  705. if err != nil {
  706. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  707. return
  708. }
  709. // get the filter options
  710. form := &forms.MetricsQueryForm{
  711. K8sForm: &forms.K8sForm{
  712. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  713. Repo: app.Repo,
  714. DigitalOceanOAuth: app.DOConf,
  715. },
  716. },
  717. QueryOpts: &prometheus.QueryOpts{},
  718. }
  719. form.K8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  720. // decode from JSON to form value
  721. decoder := schema.NewDecoder()
  722. decoder.IgnoreUnknownKeys(true)
  723. if err := decoder.Decode(form.QueryOpts, vals); err != nil {
  724. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  725. return
  726. }
  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. // get prometheus service
  740. promSvc, found, err := prometheus.GetPrometheusService(agent.Clientset)
  741. if err != nil {
  742. app.handleErrorInternal(err, w)
  743. return
  744. }
  745. if !found {
  746. app.handleErrorInternal(err, w)
  747. return
  748. }
  749. rawQuery, err := prometheus.QueryPrometheus(agent.Clientset, promSvc, form.QueryOpts)
  750. if err != nil {
  751. app.handleErrorInternal(err, w)
  752. return
  753. }
  754. fmt.Fprint(w, string(rawQuery))
  755. }
  756. type KubeconfigResponse struct {
  757. Kubeconfig []byte `json:"kubeconfig"`
  758. }
  759. func (app *App) HandleGetTemporaryKubeconfig(w http.ResponseWriter, r *http.Request) {
  760. vals, err := url.ParseQuery(r.URL.RawQuery)
  761. if err != nil {
  762. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  763. return
  764. }
  765. // get the filter options
  766. form := &forms.K8sForm{
  767. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  768. Repo: app.Repo,
  769. DigitalOceanOAuth: app.DOConf,
  770. },
  771. }
  772. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  773. // get the API config
  774. apiConf, err := form.OutOfClusterConfig.CreateRawConfigFromCluster()
  775. if err != nil {
  776. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  777. return
  778. }
  779. bytes, err := clientcmd.Write(*apiConf)
  780. res := &KubeconfigResponse{
  781. Kubeconfig: bytes,
  782. }
  783. if err := json.NewEncoder(w).Encode(res); err != nil {
  784. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  785. return
  786. }
  787. }