k8s_handler.go 31 KB

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