k8s_handler.go 24 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. // if val is empty and key does not exist in configmap already, set to empty
  294. if _, found := configMap.EnvVariables[key]; val == "" && !found {
  295. configMap.EnvVariables[key] = ""
  296. } else if val != "" {
  297. configMap.EnvVariables[key] = fmt.Sprintf("PORTERSECRET_%s", configMap.Name)
  298. }
  299. }
  300. err = agent.UpdateConfigMap(configMap.Name, configMap.Namespace, configMap.EnvVariables)
  301. if err != nil {
  302. app.handleErrorInternal(err, w)
  303. return
  304. }
  305. if err := json.NewEncoder(w).Encode(configMap); err != nil {
  306. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  307. return
  308. }
  309. w.WriteHeader(http.StatusOK)
  310. return
  311. }
  312. // HandleGetPodLogs returns real-time logs of the pod via websockets
  313. // TODO: Refactor repeated calls.
  314. func (app *App) HandleGetPodLogs(w http.ResponseWriter, r *http.Request) {
  315. // get session to retrieve correct kubeconfig
  316. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  317. // get path parameters
  318. namespace := chi.URLParam(r, "namespace")
  319. podName := chi.URLParam(r, "name")
  320. if err != nil {
  321. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  322. return
  323. }
  324. vals, err := url.ParseQuery(r.URL.RawQuery)
  325. if err != nil {
  326. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  327. return
  328. }
  329. // get the filter options
  330. form := &forms.K8sForm{
  331. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  332. Repo: app.Repo,
  333. DigitalOceanOAuth: app.DOConf,
  334. },
  335. }
  336. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  337. // validate the form
  338. if err := app.validator.Struct(form); err != nil {
  339. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  340. return
  341. }
  342. // create a new agent
  343. var agent *kubernetes.Agent
  344. if app.ServerConf.IsTesting {
  345. agent = app.TestAgents.K8sAgent
  346. } else {
  347. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  348. }
  349. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  350. // upgrade to websocket.
  351. conn, err := upgrader.Upgrade(w, r, nil)
  352. if err != nil {
  353. app.handleErrorUpgradeWebsocket(err, w)
  354. }
  355. err = agent.GetPodLogs(namespace, podName, conn)
  356. if err != nil {
  357. app.handleErrorWebsocketWrite(err, w)
  358. return
  359. }
  360. }
  361. // HandleDeletePod deletes the pod given the name and namespace.
  362. func (app *App) HandleDeletePod(w http.ResponseWriter, r *http.Request) {
  363. // get path parameters
  364. namespace := chi.URLParam(r, "namespace")
  365. name := chi.URLParam(r, "name")
  366. vals, err := url.ParseQuery(r.URL.RawQuery)
  367. if err != nil {
  368. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  369. return
  370. }
  371. // get the filter options
  372. form := &forms.K8sForm{
  373. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  374. Repo: app.Repo,
  375. DigitalOceanOAuth: app.DOConf,
  376. },
  377. }
  378. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  379. // validate the form
  380. if err := app.validator.Struct(form); err != nil {
  381. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  382. return
  383. }
  384. // create a new agent
  385. var agent *kubernetes.Agent
  386. if app.ServerConf.IsTesting {
  387. agent = app.TestAgents.K8sAgent
  388. } else {
  389. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  390. }
  391. err = agent.DeletePod(namespace, name)
  392. if err != nil {
  393. app.handleErrorInternal(err, w)
  394. return
  395. }
  396. w.WriteHeader(http.StatusOK)
  397. return
  398. }
  399. // HandleGetIngress returns the ingress object given the name and namespace.
  400. func (app *App) HandleGetIngress(w http.ResponseWriter, r *http.Request) {
  401. // get session to retrieve correct kubeconfig
  402. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  403. // get path parameters
  404. namespace := chi.URLParam(r, "namespace")
  405. name := chi.URLParam(r, "name")
  406. if err != nil {
  407. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  408. return
  409. }
  410. vals, err := url.ParseQuery(r.URL.RawQuery)
  411. if err != nil {
  412. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  413. return
  414. }
  415. // get the filter options
  416. form := &forms.K8sForm{
  417. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  418. Repo: app.Repo,
  419. DigitalOceanOAuth: app.DOConf,
  420. },
  421. }
  422. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  423. // validate the form
  424. if err := app.validator.Struct(form); err != nil {
  425. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  426. return
  427. }
  428. // create a new agent
  429. var agent *kubernetes.Agent
  430. if app.ServerConf.IsTesting {
  431. agent = app.TestAgents.K8sAgent
  432. } else {
  433. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  434. }
  435. ingress, err := agent.GetIngress(namespace, name)
  436. if err != nil {
  437. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  438. return
  439. }
  440. if err := json.NewEncoder(w).Encode(ingress); err != nil {
  441. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  442. return
  443. }
  444. }
  445. // HandleListPods returns all pods that match the given selectors
  446. // TODO: Refactor repeated calls.
  447. func (app *App) HandleListPods(w http.ResponseWriter, r *http.Request) {
  448. // get session to retrieve correct kubeconfig
  449. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  450. if err != nil {
  451. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  452. return
  453. }
  454. vals, err := url.ParseQuery(r.URL.RawQuery)
  455. if err != nil {
  456. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  457. return
  458. }
  459. // get the filter options
  460. form := &forms.K8sForm{
  461. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  462. Repo: app.Repo,
  463. DigitalOceanOAuth: app.DOConf,
  464. },
  465. }
  466. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  467. // validate the form
  468. if err := app.validator.Struct(form); err != nil {
  469. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  470. return
  471. }
  472. // create a new agent
  473. var agent *kubernetes.Agent
  474. if app.ServerConf.IsTesting {
  475. agent = app.TestAgents.K8sAgent
  476. } else {
  477. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  478. }
  479. pods := []v1.Pod{}
  480. for _, selector := range vals["selectors"] {
  481. podsList, err := agent.GetPodsByLabel(selector)
  482. if err != nil {
  483. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  484. return
  485. }
  486. for _, pod := range podsList.Items {
  487. pods = append(pods, pod)
  488. }
  489. }
  490. if err := json.NewEncoder(w).Encode(pods); err != nil {
  491. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  492. return
  493. }
  494. }
  495. // HandleListJobsByChart lists all jobs belonging to a specific Helm chart
  496. func (app *App) HandleListJobsByChart(w http.ResponseWriter, r *http.Request) {
  497. // get path parameters
  498. namespace := chi.URLParam(r, "namespace")
  499. chart := chi.URLParam(r, "chart")
  500. releaseName := chi.URLParam(r, "release_name")
  501. vals, err := url.ParseQuery(r.URL.RawQuery)
  502. if err != nil {
  503. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  504. return
  505. }
  506. // get the filter options
  507. form := &forms.K8sForm{
  508. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  509. Repo: app.Repo,
  510. DigitalOceanOAuth: app.DOConf,
  511. },
  512. }
  513. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  514. // validate the form
  515. if err := app.validator.Struct(form); err != nil {
  516. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  517. return
  518. }
  519. // create a new agent
  520. var agent *kubernetes.Agent
  521. if app.ServerConf.IsTesting {
  522. agent = app.TestAgents.K8sAgent
  523. } else {
  524. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  525. }
  526. jobs, err := agent.ListJobsByLabel(namespace, kubernetes.Label{
  527. Key: "helm.sh/chart",
  528. Val: chart,
  529. }, kubernetes.Label{
  530. Key: "meta.helm.sh/release-name",
  531. Val: releaseName,
  532. })
  533. if err != nil {
  534. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  535. return
  536. }
  537. if err := json.NewEncoder(w).Encode(jobs); err != nil {
  538. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  539. return
  540. }
  541. }
  542. // HandleListJobPods lists all pods belonging to a specific job
  543. func (app *App) HandleListJobPods(w http.ResponseWriter, r *http.Request) {
  544. // get path parameters
  545. namespace := chi.URLParam(r, "namespace")
  546. name := chi.URLParam(r, "name")
  547. vals, err := url.ParseQuery(r.URL.RawQuery)
  548. if err != nil {
  549. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  550. return
  551. }
  552. // get the filter options
  553. form := &forms.K8sForm{
  554. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  555. Repo: app.Repo,
  556. DigitalOceanOAuth: app.DOConf,
  557. },
  558. }
  559. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  560. // validate the form
  561. if err := app.validator.Struct(form); err != nil {
  562. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  563. return
  564. }
  565. // create a new agent
  566. var agent *kubernetes.Agent
  567. if app.ServerConf.IsTesting {
  568. agent = app.TestAgents.K8sAgent
  569. } else {
  570. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  571. }
  572. pods, err := agent.GetJobPods(namespace, name)
  573. if err != nil {
  574. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  575. return
  576. }
  577. if err := json.NewEncoder(w).Encode(pods); err != nil {
  578. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  579. return
  580. }
  581. }
  582. // HandleStreamControllerStatus test calls
  583. // TODO: Refactor repeated calls.
  584. func (app *App) HandleStreamControllerStatus(w http.ResponseWriter, r *http.Request) {
  585. // get session to retrieve correct kubeconfig
  586. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  587. if err != nil {
  588. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  589. return
  590. }
  591. vals, err := url.ParseQuery(r.URL.RawQuery)
  592. if err != nil {
  593. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  594. return
  595. }
  596. // get the filter options
  597. form := &forms.K8sForm{
  598. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  599. Repo: app.Repo,
  600. DigitalOceanOAuth: app.DOConf,
  601. },
  602. }
  603. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  604. // validate the form
  605. if err := app.validator.Struct(form); err != nil {
  606. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  607. return
  608. }
  609. // create a new agent
  610. var agent *kubernetes.Agent
  611. if app.ServerConf.IsTesting {
  612. agent = app.TestAgents.K8sAgent
  613. } else {
  614. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  615. }
  616. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  617. // upgrade to websocket.
  618. conn, err := upgrader.Upgrade(w, r, nil)
  619. if err != nil {
  620. app.handleErrorUpgradeWebsocket(err, w)
  621. }
  622. // get path parameters
  623. kind := chi.URLParam(r, "kind")
  624. err = agent.StreamControllerStatus(conn, kind)
  625. if err != nil {
  626. app.handleErrorWebsocketWrite(err, w)
  627. return
  628. }
  629. }
  630. // HandleDetectPrometheusInstalled detects a prometheus installation in the target cluster
  631. func (app *App) HandleDetectPrometheusInstalled(w http.ResponseWriter, r *http.Request) {
  632. vals, err := url.ParseQuery(r.URL.RawQuery)
  633. if err != nil {
  634. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  635. return
  636. }
  637. // get the filter options
  638. form := &forms.K8sForm{
  639. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  640. Repo: app.Repo,
  641. DigitalOceanOAuth: app.DOConf,
  642. },
  643. }
  644. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  645. // validate the form
  646. if err := app.validator.Struct(form); err != nil {
  647. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  648. return
  649. }
  650. // create a new agent
  651. var agent *kubernetes.Agent
  652. if app.ServerConf.IsTesting {
  653. agent = app.TestAgents.K8sAgent
  654. } else {
  655. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  656. }
  657. // detect prometheus service
  658. _, found, err := prometheus.GetPrometheusService(agent.Clientset)
  659. if !found {
  660. http.NotFound(w, r)
  661. return
  662. }
  663. w.WriteHeader(http.StatusOK)
  664. return
  665. }
  666. // HandleListNGINXIngresses lists all NGINX ingresses in a target cluster
  667. func (app *App) HandleListNGINXIngresses(w http.ResponseWriter, r *http.Request) {
  668. vals, err := url.ParseQuery(r.URL.RawQuery)
  669. if err != nil {
  670. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  671. return
  672. }
  673. // get the filter options
  674. form := &forms.K8sForm{
  675. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  676. Repo: app.Repo,
  677. DigitalOceanOAuth: app.DOConf,
  678. },
  679. }
  680. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  681. // validate the form
  682. if err := app.validator.Struct(form); err != nil {
  683. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  684. return
  685. }
  686. // create a new agent
  687. var agent *kubernetes.Agent
  688. if app.ServerConf.IsTesting {
  689. agent = app.TestAgents.K8sAgent
  690. } else {
  691. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  692. }
  693. res, err := prometheus.GetIngressesWithNGINXAnnotation(agent.Clientset)
  694. if err != nil {
  695. app.handleErrorInternal(err, w)
  696. return
  697. }
  698. w.WriteHeader(http.StatusOK)
  699. if err := json.NewEncoder(w).Encode(res); err != nil {
  700. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  701. return
  702. }
  703. }
  704. func (app *App) HandleGetPodMetrics(w http.ResponseWriter, r *http.Request) {
  705. vals, err := url.ParseQuery(r.URL.RawQuery)
  706. if err != nil {
  707. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  708. return
  709. }
  710. // get the filter options
  711. form := &forms.MetricsQueryForm{
  712. K8sForm: &forms.K8sForm{
  713. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  714. Repo: app.Repo,
  715. DigitalOceanOAuth: app.DOConf,
  716. },
  717. },
  718. QueryOpts: &prometheus.QueryOpts{},
  719. }
  720. form.K8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  721. // decode from JSON to form value
  722. decoder := schema.NewDecoder()
  723. decoder.IgnoreUnknownKeys(true)
  724. if err := decoder.Decode(form.QueryOpts, vals); err != nil {
  725. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  726. return
  727. }
  728. // validate the form
  729. if err := app.validator.Struct(form); err != nil {
  730. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  731. return
  732. }
  733. // create a new agent
  734. var agent *kubernetes.Agent
  735. if app.ServerConf.IsTesting {
  736. agent = app.TestAgents.K8sAgent
  737. } else {
  738. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  739. }
  740. // get prometheus service
  741. promSvc, found, err := prometheus.GetPrometheusService(agent.Clientset)
  742. if err != nil {
  743. app.handleErrorInternal(err, w)
  744. return
  745. }
  746. if !found {
  747. app.handleErrorInternal(err, w)
  748. return
  749. }
  750. rawQuery, err := prometheus.QueryPrometheus(agent.Clientset, promSvc, form.QueryOpts)
  751. if err != nil {
  752. app.handleErrorInternal(err, w)
  753. return
  754. }
  755. fmt.Fprint(w, string(rawQuery))
  756. }
  757. type KubeconfigResponse struct {
  758. Kubeconfig []byte `json:"kubeconfig"`
  759. }
  760. func (app *App) HandleGetTemporaryKubeconfig(w http.ResponseWriter, r *http.Request) {
  761. vals, err := url.ParseQuery(r.URL.RawQuery)
  762. if err != nil {
  763. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  764. return
  765. }
  766. // get the filter options
  767. form := &forms.K8sForm{
  768. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  769. Repo: app.Repo,
  770. DigitalOceanOAuth: app.DOConf,
  771. },
  772. }
  773. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  774. // get the API config
  775. apiConf, err := form.OutOfClusterConfig.CreateRawConfigFromCluster()
  776. if err != nil {
  777. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  778. return
  779. }
  780. bytes, err := clientcmd.Write(*apiConf)
  781. res := &KubeconfigResponse{
  782. Kubeconfig: bytes,
  783. }
  784. if err := json.NewEncoder(w).Encode(res); err != nil {
  785. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  786. return
  787. }
  788. }