k8s_handler.go 24 KB

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