k8s_handler.go 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  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. namespace := vals.Get("namespace")
  480. pods := []v1.Pod{}
  481. for _, selector := range vals["selectors"] {
  482. podsList, err := agent.GetPodsByLabel(selector, namespace)
  483. if err != nil {
  484. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  485. return
  486. }
  487. for _, pod := range podsList.Items {
  488. pods = append(pods, pod)
  489. }
  490. }
  491. if err := json.NewEncoder(w).Encode(pods); err != nil {
  492. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  493. return
  494. }
  495. }
  496. // HandleListJobsByChart lists all jobs belonging to a specific Helm chart
  497. func (app *App) HandleListJobsByChart(w http.ResponseWriter, r *http.Request) {
  498. // get path parameters
  499. namespace := chi.URLParam(r, "namespace")
  500. chart := chi.URLParam(r, "chart")
  501. releaseName := chi.URLParam(r, "release_name")
  502. vals, err := url.ParseQuery(r.URL.RawQuery)
  503. if err != nil {
  504. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  505. return
  506. }
  507. // get the filter options
  508. form := &forms.K8sForm{
  509. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  510. Repo: app.Repo,
  511. DigitalOceanOAuth: app.DOConf,
  512. },
  513. }
  514. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  515. // validate the form
  516. if err := app.validator.Struct(form); err != nil {
  517. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  518. return
  519. }
  520. // create a new agent
  521. var agent *kubernetes.Agent
  522. if app.ServerConf.IsTesting {
  523. agent = app.TestAgents.K8sAgent
  524. } else {
  525. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  526. }
  527. jobs, err := agent.ListJobsByLabel(namespace, kubernetes.Label{
  528. Key: "helm.sh/chart",
  529. Val: chart,
  530. }, kubernetes.Label{
  531. Key: "meta.helm.sh/release-name",
  532. Val: releaseName,
  533. })
  534. if err != nil {
  535. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  536. return
  537. }
  538. if err := json.NewEncoder(w).Encode(jobs); err != nil {
  539. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  540. return
  541. }
  542. }
  543. // HandleStopJob stops a running job
  544. func (app *App) HandleStopJob(w http.ResponseWriter, r *http.Request) {
  545. // get path parameters
  546. namespace := chi.URLParam(r, "namespace")
  547. name := chi.URLParam(r, "name")
  548. vals, err := url.ParseQuery(r.URL.RawQuery)
  549. if err != nil {
  550. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  551. return
  552. }
  553. // get the filter options
  554. form := &forms.K8sForm{
  555. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  556. Repo: app.Repo,
  557. DigitalOceanOAuth: app.DOConf,
  558. },
  559. }
  560. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  561. // validate the form
  562. if err := app.validator.Struct(form); err != nil {
  563. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  564. return
  565. }
  566. // create a new agent
  567. var agent *kubernetes.Agent
  568. if app.ServerConf.IsTesting {
  569. agent = app.TestAgents.K8sAgent
  570. } else {
  571. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  572. }
  573. err = agent.StopJobWithJobSidecar(namespace, name)
  574. if err != nil {
  575. app.handleErrorInternal(err, w)
  576. return
  577. }
  578. w.WriteHeader(http.StatusOK)
  579. return
  580. }
  581. // HandleListJobPods lists all pods belonging to a specific job
  582. func (app *App) HandleListJobPods(w http.ResponseWriter, r *http.Request) {
  583. // get path parameters
  584. namespace := chi.URLParam(r, "namespace")
  585. name := chi.URLParam(r, "name")
  586. vals, err := url.ParseQuery(r.URL.RawQuery)
  587. if err != nil {
  588. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  589. return
  590. }
  591. // get the filter options
  592. form := &forms.K8sForm{
  593. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  594. Repo: app.Repo,
  595. DigitalOceanOAuth: app.DOConf,
  596. },
  597. }
  598. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  599. // validate the form
  600. if err := app.validator.Struct(form); err != nil {
  601. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  602. return
  603. }
  604. // create a new agent
  605. var agent *kubernetes.Agent
  606. if app.ServerConf.IsTesting {
  607. agent = app.TestAgents.K8sAgent
  608. } else {
  609. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  610. }
  611. pods, err := agent.GetJobPods(namespace, name)
  612. if err != nil {
  613. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  614. return
  615. }
  616. if err := json.NewEncoder(w).Encode(pods); err != nil {
  617. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  618. return
  619. }
  620. }
  621. // HandleStreamControllerStatus test calls
  622. // TODO: Refactor repeated calls.
  623. func (app *App) HandleStreamControllerStatus(w http.ResponseWriter, r *http.Request) {
  624. // get session to retrieve correct kubeconfig
  625. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  626. if err != nil {
  627. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  628. return
  629. }
  630. vals, err := url.ParseQuery(r.URL.RawQuery)
  631. if err != nil {
  632. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  633. return
  634. }
  635. // get the filter options
  636. form := &forms.K8sForm{
  637. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  638. Repo: app.Repo,
  639. DigitalOceanOAuth: app.DOConf,
  640. },
  641. }
  642. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  643. // validate the form
  644. if err := app.validator.Struct(form); err != nil {
  645. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  646. return
  647. }
  648. // create a new agent
  649. var agent *kubernetes.Agent
  650. if app.ServerConf.IsTesting {
  651. agent = app.TestAgents.K8sAgent
  652. } else {
  653. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  654. }
  655. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  656. // upgrade to websocket.
  657. conn, err := upgrader.Upgrade(w, r, nil)
  658. if err != nil {
  659. app.handleErrorUpgradeWebsocket(err, w)
  660. }
  661. // get path parameters
  662. kind := chi.URLParam(r, "kind")
  663. err = agent.StreamControllerStatus(conn, kind)
  664. if err != nil {
  665. app.handleErrorWebsocketWrite(err, w)
  666. return
  667. }
  668. }
  669. // HandleDetectPrometheusInstalled detects a prometheus installation in the target cluster
  670. func (app *App) HandleDetectPrometheusInstalled(w http.ResponseWriter, r *http.Request) {
  671. vals, err := url.ParseQuery(r.URL.RawQuery)
  672. if err != nil {
  673. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  674. return
  675. }
  676. // get the filter options
  677. form := &forms.K8sForm{
  678. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  679. Repo: app.Repo,
  680. DigitalOceanOAuth: app.DOConf,
  681. },
  682. }
  683. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  684. // validate the form
  685. if err := app.validator.Struct(form); err != nil {
  686. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  687. return
  688. }
  689. // create a new agent
  690. var agent *kubernetes.Agent
  691. if app.ServerConf.IsTesting {
  692. agent = app.TestAgents.K8sAgent
  693. } else {
  694. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  695. }
  696. // detect prometheus service
  697. _, found, err := prometheus.GetPrometheusService(agent.Clientset)
  698. if !found {
  699. http.NotFound(w, r)
  700. return
  701. }
  702. w.WriteHeader(http.StatusOK)
  703. return
  704. }
  705. // HandleListNGINXIngresses lists all NGINX ingresses in a target cluster
  706. func (app *App) HandleListNGINXIngresses(w http.ResponseWriter, r *http.Request) {
  707. vals, err := url.ParseQuery(r.URL.RawQuery)
  708. if err != nil {
  709. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  710. return
  711. }
  712. // get the filter options
  713. form := &forms.K8sForm{
  714. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  715. Repo: app.Repo,
  716. DigitalOceanOAuth: app.DOConf,
  717. },
  718. }
  719. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  720. // validate the form
  721. if err := app.validator.Struct(form); err != nil {
  722. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  723. return
  724. }
  725. // create a new agent
  726. var agent *kubernetes.Agent
  727. if app.ServerConf.IsTesting {
  728. agent = app.TestAgents.K8sAgent
  729. } else {
  730. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  731. }
  732. res, err := prometheus.GetIngressesWithNGINXAnnotation(agent.Clientset)
  733. if err != nil {
  734. app.handleErrorInternal(err, w)
  735. return
  736. }
  737. w.WriteHeader(http.StatusOK)
  738. if err := json.NewEncoder(w).Encode(res); err != nil {
  739. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  740. return
  741. }
  742. }
  743. func (app *App) HandleGetPodMetrics(w http.ResponseWriter, r *http.Request) {
  744. vals, err := url.ParseQuery(r.URL.RawQuery)
  745. if err != nil {
  746. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  747. return
  748. }
  749. // get the filter options
  750. form := &forms.MetricsQueryForm{
  751. K8sForm: &forms.K8sForm{
  752. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  753. Repo: app.Repo,
  754. DigitalOceanOAuth: app.DOConf,
  755. },
  756. },
  757. QueryOpts: &prometheus.QueryOpts{},
  758. }
  759. form.K8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  760. // decode from JSON to form value
  761. decoder := schema.NewDecoder()
  762. decoder.IgnoreUnknownKeys(true)
  763. if err := decoder.Decode(form.QueryOpts, vals); err != nil {
  764. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  765. return
  766. }
  767. // validate the form
  768. if err := app.validator.Struct(form); err != nil {
  769. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  770. return
  771. }
  772. // create a new agent
  773. var agent *kubernetes.Agent
  774. if app.ServerConf.IsTesting {
  775. agent = app.TestAgents.K8sAgent
  776. } else {
  777. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  778. }
  779. // get prometheus service
  780. promSvc, found, err := prometheus.GetPrometheusService(agent.Clientset)
  781. if err != nil {
  782. app.handleErrorInternal(err, w)
  783. return
  784. }
  785. if !found {
  786. app.handleErrorInternal(err, w)
  787. return
  788. }
  789. rawQuery, err := prometheus.QueryPrometheus(agent.Clientset, promSvc, form.QueryOpts)
  790. if err != nil {
  791. app.handleErrorInternal(err, w)
  792. return
  793. }
  794. fmt.Fprint(w, string(rawQuery))
  795. }
  796. type KubeconfigResponse struct {
  797. Kubeconfig []byte `json:"kubeconfig"`
  798. }
  799. func (app *App) HandleGetTemporaryKubeconfig(w http.ResponseWriter, r *http.Request) {
  800. vals, err := url.ParseQuery(r.URL.RawQuery)
  801. if err != nil {
  802. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  803. return
  804. }
  805. // get the filter options
  806. form := &forms.K8sForm{
  807. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  808. Repo: app.Repo,
  809. DigitalOceanOAuth: app.DOConf,
  810. },
  811. }
  812. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  813. // get the API config
  814. apiConf, err := form.OutOfClusterConfig.CreateRawConfigFromCluster()
  815. if err != nil {
  816. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  817. return
  818. }
  819. bytes, err := clientcmd.Write(*apiConf)
  820. res := &KubeconfigResponse{
  821. Kubeconfig: bytes,
  822. }
  823. if err := json.NewEncoder(w).Encode(res); err != nil {
  824. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  825. return
  826. }
  827. }