k8s_handler.go 26 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111
  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. // HandleListPodEvents retrieves all events tied to a pod.
  64. func (app *App) HandleListPodEvents(w http.ResponseWriter, r *http.Request) {
  65. vals, err := url.ParseQuery(r.URL.RawQuery)
  66. // get path parameters
  67. namespace := chi.URLParam(r, "namespace")
  68. name := chi.URLParam(r, "name")
  69. if err != nil {
  70. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  71. return
  72. }
  73. // get the filter options
  74. form := &forms.K8sForm{
  75. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  76. Repo: app.Repo,
  77. DigitalOceanOAuth: app.DOConf,
  78. },
  79. }
  80. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  81. // validate the form
  82. if err := app.validator.Struct(form); err != nil {
  83. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  84. return
  85. }
  86. // create a new agent
  87. var agent *kubernetes.Agent
  88. if app.ServerConf.IsTesting {
  89. agent = app.TestAgents.K8sAgent
  90. } else {
  91. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  92. }
  93. events, err := agent.ListEvents(name, namespace)
  94. if err != nil {
  95. app.handleErrorDataRead(err, w)
  96. return
  97. }
  98. if err := json.NewEncoder(w).Encode(events); err != nil {
  99. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  100. return
  101. }
  102. }
  103. // HandleCreateConfigMap deletes the pod given the name and namespace.
  104. func (app *App) HandleCreateConfigMap(w http.ResponseWriter, r *http.Request) {
  105. vals, err := url.ParseQuery(r.URL.RawQuery)
  106. if err != nil {
  107. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  108. return
  109. }
  110. // get the filter options
  111. form := &forms.K8sForm{
  112. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  113. Repo: app.Repo,
  114. DigitalOceanOAuth: app.DOConf,
  115. },
  116. }
  117. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  118. // validate the form
  119. if err := app.validator.Struct(form); err != nil {
  120. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  121. return
  122. }
  123. // create a new agent
  124. var agent *kubernetes.Agent
  125. if app.ServerConf.IsTesting {
  126. agent = app.TestAgents.K8sAgent
  127. } else {
  128. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  129. }
  130. configMap := &forms.ConfigMapForm{}
  131. if err := json.NewDecoder(r.Body).Decode(configMap); err != nil {
  132. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  133. return
  134. }
  135. secretData := make(map[string][]byte)
  136. for key, rawValue := range configMap.SecretEnvVariables {
  137. // encodedValue := base64.StdEncoding.EncodeToString([]byte(rawValue))
  138. // if err != nil {
  139. // app.handleErrorInternal(err, w)
  140. // return
  141. // }
  142. secretData[key] = []byte(rawValue)
  143. }
  144. // create secret first
  145. _, err = agent.CreateLinkedSecret(configMap.Name, configMap.Namespace, configMap.Name, secretData)
  146. if err != nil {
  147. app.handleErrorInternal(err, w)
  148. return
  149. }
  150. // add all secret env variables to configmap with value PORTERSECRET_${configmap_name}
  151. for key := range configMap.SecretEnvVariables {
  152. configMap.EnvVariables[key] = fmt.Sprintf("PORTERSECRET_%s", configMap.Name)
  153. }
  154. _, err = agent.CreateConfigMap(configMap.Name, configMap.Namespace, configMap.EnvVariables)
  155. if err != nil {
  156. app.handleErrorInternal(err, w)
  157. return
  158. }
  159. if err := json.NewEncoder(w).Encode(configMap); err != nil {
  160. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  161. return
  162. }
  163. w.WriteHeader(http.StatusOK)
  164. return
  165. }
  166. // HandleListConfigMaps lists all configmaps in a namespace.
  167. func (app *App) HandleListConfigMaps(w http.ResponseWriter, r *http.Request) {
  168. vals, err := url.ParseQuery(r.URL.RawQuery)
  169. if err != nil {
  170. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  171. return
  172. }
  173. // get the filter options
  174. form := &forms.K8sForm{
  175. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  176. Repo: app.Repo,
  177. DigitalOceanOAuth: app.DOConf,
  178. },
  179. }
  180. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  181. // validate the form
  182. if err := app.validator.Struct(form); err != nil {
  183. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  184. return
  185. }
  186. // create a new agent
  187. var agent *kubernetes.Agent
  188. if app.ServerConf.IsTesting {
  189. agent = app.TestAgents.K8sAgent
  190. } else {
  191. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  192. }
  193. configMaps, err := agent.ListConfigMaps(vals["namespace"][0])
  194. if err != nil {
  195. app.handleErrorInternal(err, w)
  196. return
  197. }
  198. if err := json.NewEncoder(w).Encode(configMaps); err != nil {
  199. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  200. return
  201. }
  202. w.WriteHeader(http.StatusOK)
  203. return
  204. }
  205. // HandleGetConfigMap retreives the configmap given the name and namespace.
  206. func (app *App) HandleGetConfigMap(w http.ResponseWriter, r *http.Request) {
  207. vals, err := url.ParseQuery(r.URL.RawQuery)
  208. if err != nil {
  209. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  210. return
  211. }
  212. // get the filter options
  213. form := &forms.K8sForm{
  214. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  215. Repo: app.Repo,
  216. DigitalOceanOAuth: app.DOConf,
  217. },
  218. }
  219. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  220. // validate the form
  221. if err := app.validator.Struct(form); err != nil {
  222. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  223. return
  224. }
  225. // create a new agent
  226. var agent *kubernetes.Agent
  227. if app.ServerConf.IsTesting {
  228. agent = app.TestAgents.K8sAgent
  229. } else {
  230. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  231. }
  232. configMap, err := agent.GetConfigMap(vals["name"][0], vals["namespace"][0])
  233. if err != nil {
  234. app.handleErrorInternal(err, w)
  235. return
  236. }
  237. if err := json.NewEncoder(w).Encode(configMap); err != nil {
  238. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  239. return
  240. }
  241. w.WriteHeader(http.StatusOK)
  242. return
  243. }
  244. // HandleDeleteConfigMap deletes the pod given the name and namespace.
  245. func (app *App) HandleDeleteConfigMap(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. err = agent.DeleteLinkedSecret(vals["name"][0], vals["namespace"][0])
  272. if err != nil {
  273. app.handleErrorInternal(err, w)
  274. return
  275. }
  276. err = agent.DeleteConfigMap(vals["name"][0], vals["namespace"][0])
  277. if err != nil {
  278. app.handleErrorInternal(err, w)
  279. return
  280. }
  281. w.WriteHeader(http.StatusOK)
  282. return
  283. }
  284. // HandleUpdateConfigMap deletes the pod given the name and namespace.
  285. func (app *App) HandleUpdateConfigMap(w http.ResponseWriter, r *http.Request) {
  286. vals, err := url.ParseQuery(r.URL.RawQuery)
  287. if err != nil {
  288. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  289. return
  290. }
  291. // get the filter options
  292. form := &forms.K8sForm{
  293. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  294. Repo: app.Repo,
  295. DigitalOceanOAuth: app.DOConf,
  296. },
  297. }
  298. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  299. // validate the form
  300. if err := app.validator.Struct(form); err != nil {
  301. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  302. return
  303. }
  304. // create a new agent
  305. var agent *kubernetes.Agent
  306. if app.ServerConf.IsTesting {
  307. agent = app.TestAgents.K8sAgent
  308. } else {
  309. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  310. }
  311. configMap := &forms.ConfigMapForm{}
  312. if err := json.NewDecoder(r.Body).Decode(configMap); err != nil {
  313. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  314. return
  315. }
  316. secretData := make(map[string][]byte)
  317. for key, rawValue := range configMap.SecretEnvVariables {
  318. // encodedValue, err := base64.StdEncoding.DecodeString(rawValue)
  319. // if err != nil {
  320. // app.handleErrorInternal(err, w)
  321. // return
  322. // }
  323. secretData[key] = []byte(rawValue)
  324. }
  325. // create secret first
  326. err = agent.UpdateLinkedSecret(configMap.Name, configMap.Namespace, configMap.Name, secretData)
  327. if err != nil {
  328. app.handleErrorInternal(err, w)
  329. return
  330. }
  331. // add all secret env variables to configmap with value PORTERSECRET_${configmap_name}
  332. for key, val := range configMap.SecretEnvVariables {
  333. // if val is empty and key does not exist in configmap already, set to empty
  334. if _, found := configMap.EnvVariables[key]; val == "" && !found {
  335. configMap.EnvVariables[key] = ""
  336. } else if val != "" {
  337. configMap.EnvVariables[key] = fmt.Sprintf("PORTERSECRET_%s", configMap.Name)
  338. }
  339. }
  340. err = agent.UpdateConfigMap(configMap.Name, configMap.Namespace, configMap.EnvVariables)
  341. if err != nil {
  342. app.handleErrorInternal(err, w)
  343. return
  344. }
  345. if err := json.NewEncoder(w).Encode(configMap); err != nil {
  346. app.handleErrorFormDecoding(err, ErrEnvDecode, w)
  347. return
  348. }
  349. w.WriteHeader(http.StatusOK)
  350. return
  351. }
  352. // HandleGetPodLogs returns real-time logs of the pod via websockets
  353. // TODO: Refactor repeated calls.
  354. func (app *App) HandleGetPodLogs(w http.ResponseWriter, r *http.Request) {
  355. // get session to retrieve correct kubeconfig
  356. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  357. // get path parameters
  358. namespace := chi.URLParam(r, "namespace")
  359. podName := chi.URLParam(r, "name")
  360. if err != nil {
  361. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  362. return
  363. }
  364. vals, err := url.ParseQuery(r.URL.RawQuery)
  365. if err != nil {
  366. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  367. return
  368. }
  369. // get the filter options
  370. form := &forms.K8sForm{
  371. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  372. Repo: app.Repo,
  373. DigitalOceanOAuth: app.DOConf,
  374. },
  375. }
  376. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  377. // validate the form
  378. if err := app.validator.Struct(form); err != nil {
  379. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  380. return
  381. }
  382. // create a new agent
  383. var agent *kubernetes.Agent
  384. if app.ServerConf.IsTesting {
  385. agent = app.TestAgents.K8sAgent
  386. } else {
  387. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  388. }
  389. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  390. // upgrade to websocket.
  391. conn, err := upgrader.Upgrade(w, r, nil)
  392. if err != nil {
  393. app.handleErrorUpgradeWebsocket(err, w)
  394. }
  395. err = agent.GetPodLogs(namespace, podName, conn)
  396. if err != nil {
  397. app.handleErrorWebsocketWrite(err, w)
  398. return
  399. }
  400. }
  401. // HandleDeletePod deletes the pod given the name and namespace.
  402. func (app *App) HandleDeletePod(w http.ResponseWriter, r *http.Request) {
  403. // get path parameters
  404. namespace := chi.URLParam(r, "namespace")
  405. name := chi.URLParam(r, "name")
  406. vals, err := url.ParseQuery(r.URL.RawQuery)
  407. if err != nil {
  408. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  409. return
  410. }
  411. // get the filter options
  412. form := &forms.K8sForm{
  413. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  414. Repo: app.Repo,
  415. DigitalOceanOAuth: app.DOConf,
  416. },
  417. }
  418. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  419. // validate the form
  420. if err := app.validator.Struct(form); err != nil {
  421. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  422. return
  423. }
  424. // create a new agent
  425. var agent *kubernetes.Agent
  426. if app.ServerConf.IsTesting {
  427. agent = app.TestAgents.K8sAgent
  428. } else {
  429. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  430. }
  431. err = agent.DeletePod(namespace, name)
  432. if err != nil {
  433. app.handleErrorInternal(err, w)
  434. return
  435. }
  436. w.WriteHeader(http.StatusOK)
  437. return
  438. }
  439. // HandleGetIngress returns the ingress object given the name and namespace.
  440. func (app *App) HandleGetIngress(w http.ResponseWriter, r *http.Request) {
  441. // get session to retrieve correct kubeconfig
  442. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  443. // get path parameters
  444. namespace := chi.URLParam(r, "namespace")
  445. name := chi.URLParam(r, "name")
  446. if err != nil {
  447. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  448. return
  449. }
  450. vals, err := url.ParseQuery(r.URL.RawQuery)
  451. if err != nil {
  452. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  453. return
  454. }
  455. // get the filter options
  456. form := &forms.K8sForm{
  457. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  458. Repo: app.Repo,
  459. DigitalOceanOAuth: app.DOConf,
  460. },
  461. }
  462. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  463. // validate the form
  464. if err := app.validator.Struct(form); err != nil {
  465. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  466. return
  467. }
  468. // create a new agent
  469. var agent *kubernetes.Agent
  470. if app.ServerConf.IsTesting {
  471. agent = app.TestAgents.K8sAgent
  472. } else {
  473. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  474. }
  475. ingress, err := agent.GetIngress(namespace, name)
  476. if err != nil {
  477. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  478. return
  479. }
  480. if err := json.NewEncoder(w).Encode(ingress); err != nil {
  481. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  482. return
  483. }
  484. }
  485. // HandleListPods returns all pods that match the given selectors
  486. // TODO: Refactor repeated calls.
  487. func (app *App) HandleListPods(w http.ResponseWriter, r *http.Request) {
  488. // get session to retrieve correct kubeconfig
  489. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  490. if err != nil {
  491. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  492. return
  493. }
  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. namespace := vals.Get("namespace")
  520. pods := []v1.Pod{}
  521. for _, selector := range vals["selectors"] {
  522. podsList, err := agent.GetPodsByLabel(selector, namespace)
  523. if err != nil {
  524. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  525. return
  526. }
  527. for _, pod := range podsList.Items {
  528. pods = append(pods, pod)
  529. }
  530. }
  531. if err := json.NewEncoder(w).Encode(pods); err != nil {
  532. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  533. return
  534. }
  535. }
  536. // HandleListJobsByChart lists all jobs belonging to a specific Helm chart
  537. func (app *App) HandleListJobsByChart(w http.ResponseWriter, r *http.Request) {
  538. // get path parameters
  539. namespace := chi.URLParam(r, "namespace")
  540. chart := chi.URLParam(r, "chart")
  541. releaseName := chi.URLParam(r, "release_name")
  542. vals, err := url.ParseQuery(r.URL.RawQuery)
  543. if err != nil {
  544. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  545. return
  546. }
  547. // get the filter options
  548. form := &forms.K8sForm{
  549. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  550. Repo: app.Repo,
  551. DigitalOceanOAuth: app.DOConf,
  552. },
  553. }
  554. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  555. // validate the form
  556. if err := app.validator.Struct(form); err != nil {
  557. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  558. return
  559. }
  560. // create a new agent
  561. var agent *kubernetes.Agent
  562. if app.ServerConf.IsTesting {
  563. agent = app.TestAgents.K8sAgent
  564. } else {
  565. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  566. }
  567. jobs, err := agent.ListJobsByLabel(namespace, kubernetes.Label{
  568. Key: "helm.sh/chart",
  569. Val: chart,
  570. }, kubernetes.Label{
  571. Key: "meta.helm.sh/release-name",
  572. Val: releaseName,
  573. })
  574. if err != nil {
  575. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  576. return
  577. }
  578. if err := json.NewEncoder(w).Encode(jobs); err != nil {
  579. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  580. return
  581. }
  582. }
  583. // HandleStopJob stops a running job
  584. func (app *App) HandleStopJob(w http.ResponseWriter, r *http.Request) {
  585. // get path parameters
  586. namespace := chi.URLParam(r, "namespace")
  587. name := chi.URLParam(r, "name")
  588. vals, err := url.ParseQuery(r.URL.RawQuery)
  589. if err != nil {
  590. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  591. return
  592. }
  593. // get the filter options
  594. form := &forms.K8sForm{
  595. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  596. Repo: app.Repo,
  597. DigitalOceanOAuth: app.DOConf,
  598. },
  599. }
  600. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  601. // validate the form
  602. if err := app.validator.Struct(form); err != nil {
  603. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  604. return
  605. }
  606. // create a new agent
  607. var agent *kubernetes.Agent
  608. if app.ServerConf.IsTesting {
  609. agent = app.TestAgents.K8sAgent
  610. } else {
  611. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  612. }
  613. err = agent.StopJobWithJobSidecar(namespace, name)
  614. if err != nil {
  615. app.handleErrorInternal(err, w)
  616. return
  617. }
  618. w.WriteHeader(http.StatusOK)
  619. return
  620. }
  621. // HandleListJobPods lists all pods belonging to a specific job
  622. func (app *App) HandleListJobPods(w http.ResponseWriter, r *http.Request) {
  623. // get path parameters
  624. namespace := chi.URLParam(r, "namespace")
  625. name := chi.URLParam(r, "name")
  626. vals, err := url.ParseQuery(r.URL.RawQuery)
  627. if err != nil {
  628. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  629. return
  630. }
  631. // get the filter options
  632. form := &forms.K8sForm{
  633. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  634. Repo: app.Repo,
  635. DigitalOceanOAuth: app.DOConf,
  636. },
  637. }
  638. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  639. // validate the form
  640. if err := app.validator.Struct(form); err != nil {
  641. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  642. return
  643. }
  644. // create a new agent
  645. var agent *kubernetes.Agent
  646. if app.ServerConf.IsTesting {
  647. agent = app.TestAgents.K8sAgent
  648. } else {
  649. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  650. }
  651. pods, err := agent.GetJobPods(namespace, name)
  652. if err != nil {
  653. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  654. return
  655. }
  656. if err := json.NewEncoder(w).Encode(pods); err != nil {
  657. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  658. return
  659. }
  660. }
  661. // HandleStreamControllerStatus test calls
  662. // TODO: Refactor repeated calls.
  663. func (app *App) HandleStreamControllerStatus(w http.ResponseWriter, r *http.Request) {
  664. // get session to retrieve correct kubeconfig
  665. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  666. if err != nil {
  667. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  668. return
  669. }
  670. vals, err := url.ParseQuery(r.URL.RawQuery)
  671. if err != nil {
  672. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  673. return
  674. }
  675. // get the filter options
  676. form := &forms.K8sForm{
  677. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  678. Repo: app.Repo,
  679. DigitalOceanOAuth: app.DOConf,
  680. },
  681. }
  682. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  683. // validate the form
  684. if err := app.validator.Struct(form); err != nil {
  685. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  686. return
  687. }
  688. // create a new agent
  689. var agent *kubernetes.Agent
  690. if app.ServerConf.IsTesting {
  691. agent = app.TestAgents.K8sAgent
  692. } else {
  693. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  694. }
  695. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  696. // upgrade to websocket.
  697. conn, err := upgrader.Upgrade(w, r, nil)
  698. if err != nil {
  699. app.handleErrorUpgradeWebsocket(err, w)
  700. }
  701. // get path parameters
  702. kind := chi.URLParam(r, "kind")
  703. err = agent.StreamControllerStatus(conn, kind)
  704. if err != nil {
  705. app.handleErrorWebsocketWrite(err, w)
  706. return
  707. }
  708. }
  709. // HandleDetectPrometheusInstalled detects a prometheus installation in the target cluster
  710. func (app *App) HandleDetectPrometheusInstalled(w http.ResponseWriter, r *http.Request) {
  711. vals, err := url.ParseQuery(r.URL.RawQuery)
  712. if err != nil {
  713. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  714. return
  715. }
  716. // get the filter options
  717. form := &forms.K8sForm{
  718. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  719. Repo: app.Repo,
  720. DigitalOceanOAuth: app.DOConf,
  721. },
  722. }
  723. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  724. // validate the form
  725. if err := app.validator.Struct(form); err != nil {
  726. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  727. return
  728. }
  729. // create a new agent
  730. var agent *kubernetes.Agent
  731. if app.ServerConf.IsTesting {
  732. agent = app.TestAgents.K8sAgent
  733. } else {
  734. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  735. }
  736. // detect prometheus service
  737. _, found, err := prometheus.GetPrometheusService(agent.Clientset)
  738. if !found {
  739. http.NotFound(w, r)
  740. return
  741. }
  742. w.WriteHeader(http.StatusOK)
  743. return
  744. }
  745. // HandleListNGINXIngresses lists all NGINX ingresses in a target cluster
  746. func (app *App) HandleListNGINXIngresses(w http.ResponseWriter, r *http.Request) {
  747. vals, err := url.ParseQuery(r.URL.RawQuery)
  748. if err != nil {
  749. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  750. return
  751. }
  752. // get the filter options
  753. form := &forms.K8sForm{
  754. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  755. Repo: app.Repo,
  756. DigitalOceanOAuth: app.DOConf,
  757. },
  758. }
  759. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  760. // validate the form
  761. if err := app.validator.Struct(form); err != nil {
  762. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  763. return
  764. }
  765. // create a new agent
  766. var agent *kubernetes.Agent
  767. if app.ServerConf.IsTesting {
  768. agent = app.TestAgents.K8sAgent
  769. } else {
  770. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  771. }
  772. res, err := prometheus.GetIngressesWithNGINXAnnotation(agent.Clientset)
  773. if err != nil {
  774. app.handleErrorInternal(err, w)
  775. return
  776. }
  777. w.WriteHeader(http.StatusOK)
  778. if err := json.NewEncoder(w).Encode(res); err != nil {
  779. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  780. return
  781. }
  782. }
  783. func (app *App) HandleGetPodMetrics(w http.ResponseWriter, r *http.Request) {
  784. vals, err := url.ParseQuery(r.URL.RawQuery)
  785. if err != nil {
  786. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  787. return
  788. }
  789. // get the filter options
  790. form := &forms.MetricsQueryForm{
  791. K8sForm: &forms.K8sForm{
  792. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  793. Repo: app.Repo,
  794. DigitalOceanOAuth: app.DOConf,
  795. },
  796. },
  797. QueryOpts: &prometheus.QueryOpts{},
  798. }
  799. form.K8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  800. // decode from JSON to form value
  801. decoder := schema.NewDecoder()
  802. decoder.IgnoreUnknownKeys(true)
  803. if err := decoder.Decode(form.QueryOpts, vals); err != nil {
  804. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  805. return
  806. }
  807. // validate the form
  808. if err := app.validator.Struct(form); err != nil {
  809. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  810. return
  811. }
  812. // create a new agent
  813. var agent *kubernetes.Agent
  814. if app.ServerConf.IsTesting {
  815. agent = app.TestAgents.K8sAgent
  816. } else {
  817. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  818. }
  819. // get prometheus service
  820. promSvc, found, err := prometheus.GetPrometheusService(agent.Clientset)
  821. if err != nil {
  822. app.handleErrorInternal(err, w)
  823. return
  824. }
  825. if !found {
  826. app.handleErrorInternal(err, w)
  827. return
  828. }
  829. rawQuery, err := prometheus.QueryPrometheus(agent.Clientset, promSvc, form.QueryOpts)
  830. if err != nil {
  831. app.handleErrorInternal(err, w)
  832. return
  833. }
  834. fmt.Fprint(w, string(rawQuery))
  835. }
  836. type KubeconfigResponse struct {
  837. Kubeconfig []byte `json:"kubeconfig"`
  838. }
  839. func (app *App) HandleGetTemporaryKubeconfig(w http.ResponseWriter, r *http.Request) {
  840. vals, err := url.ParseQuery(r.URL.RawQuery)
  841. if err != nil {
  842. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  843. return
  844. }
  845. // get the filter options
  846. form := &forms.K8sForm{
  847. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  848. Repo: app.Repo,
  849. DigitalOceanOAuth: app.DOConf,
  850. },
  851. }
  852. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster())
  853. // get the API config
  854. apiConf, err := form.OutOfClusterConfig.CreateRawConfigFromCluster()
  855. if err != nil {
  856. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  857. return
  858. }
  859. bytes, err := clientcmd.Write(*apiConf)
  860. res := &KubeconfigResponse{
  861. Kubeconfig: bytes,
  862. }
  863. if err := json.NewEncoder(w).Encode(res); err != nil {
  864. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  865. return
  866. }
  867. }