k8s_handler.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059
  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. // HandleStopJob stops a running job
  543. func (app *App) HandleStopJob(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. err = agent.StopJobWithJobSidecar(namespace, name)
  573. if err != nil {
  574. app.handleErrorInternal(err, w)
  575. return
  576. }
  577. w.WriteHeader(http.StatusOK)
  578. return
  579. }
  580. // HandleListJobPods lists all pods belonging to a specific job
  581. func (app *App) HandleListJobPods(w http.ResponseWriter, r *http.Request) {
  582. // get path parameters
  583. namespace := chi.URLParam(r, "namespace")
  584. name := chi.URLParam(r, "name")
  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. pods, err := agent.GetJobPods(namespace, name)
  611. if err != nil {
  612. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  613. return
  614. }
  615. if err := json.NewEncoder(w).Encode(pods); err != nil {
  616. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  617. return
  618. }
  619. }
  620. // HandleStreamControllerStatus test calls
  621. // TODO: Refactor repeated calls.
  622. func (app *App) HandleStreamControllerStatus(w http.ResponseWriter, r *http.Request) {
  623. // get session to retrieve correct kubeconfig
  624. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  625. if err != nil {
  626. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  627. return
  628. }
  629. vals, err := url.ParseQuery(r.URL.RawQuery)
  630. if err != nil {
  631. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  632. return
  633. }
  634. // get the filter options
  635. form := &forms.K8sForm{
  636. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  637. Repo: app.Repo,
  638. DigitalOceanOAuth: app.DOConf,
  639. },
  640. }
  641. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  642. // validate the form
  643. if err := app.validator.Struct(form); err != nil {
  644. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  645. return
  646. }
  647. // create a new agent
  648. var agent *kubernetes.Agent
  649. if app.ServerConf.IsTesting {
  650. agent = app.TestAgents.K8sAgent
  651. } else {
  652. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  653. }
  654. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  655. // upgrade to websocket.
  656. conn, err := upgrader.Upgrade(w, r, nil)
  657. if err != nil {
  658. app.handleErrorUpgradeWebsocket(err, w)
  659. }
  660. // get path parameters
  661. kind := chi.URLParam(r, "kind")
  662. err = agent.StreamControllerStatus(conn, kind)
  663. if err != nil {
  664. app.handleErrorWebsocketWrite(err, w)
  665. return
  666. }
  667. }
  668. // HandleDetectPrometheusInstalled detects a prometheus installation in the target cluster
  669. func (app *App) HandleDetectPrometheusInstalled(w http.ResponseWriter, r *http.Request) {
  670. vals, err := url.ParseQuery(r.URL.RawQuery)
  671. if err != nil {
  672. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  673. return
  674. }
  675. // get the filter options
  676. form := &forms.K8sForm{
  677. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  678. Repo: app.Repo,
  679. DigitalOceanOAuth: app.DOConf,
  680. },
  681. }
  682. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  683. // validate the form
  684. if err := app.validator.Struct(form); err != nil {
  685. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  686. return
  687. }
  688. // create a new agent
  689. var agent *kubernetes.Agent
  690. if app.ServerConf.IsTesting {
  691. agent = app.TestAgents.K8sAgent
  692. } else {
  693. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  694. }
  695. // detect prometheus service
  696. _, found, err := prometheus.GetPrometheusService(agent.Clientset)
  697. if !found {
  698. http.NotFound(w, r)
  699. return
  700. }
  701. w.WriteHeader(http.StatusOK)
  702. return
  703. }
  704. // HandleListNGINXIngresses lists all NGINX ingresses in a target cluster
  705. func (app *App) HandleListNGINXIngresses(w http.ResponseWriter, r *http.Request) {
  706. vals, err := url.ParseQuery(r.URL.RawQuery)
  707. if err != nil {
  708. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  709. return
  710. }
  711. // get the filter options
  712. form := &forms.K8sForm{
  713. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  714. Repo: app.Repo,
  715. DigitalOceanOAuth: app.DOConf,
  716. },
  717. }
  718. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  719. // validate the form
  720. if err := app.validator.Struct(form); err != nil {
  721. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  722. return
  723. }
  724. // create a new agent
  725. var agent *kubernetes.Agent
  726. if app.ServerConf.IsTesting {
  727. agent = app.TestAgents.K8sAgent
  728. } else {
  729. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  730. }
  731. res, err := prometheus.GetIngressesWithNGINXAnnotation(agent.Clientset)
  732. if err != nil {
  733. app.handleErrorInternal(err, w)
  734. return
  735. }
  736. w.WriteHeader(http.StatusOK)
  737. if err := json.NewEncoder(w).Encode(res); err != nil {
  738. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  739. return
  740. }
  741. }
  742. func (app *App) HandleGetPodMetrics(w http.ResponseWriter, r *http.Request) {
  743. vals, err := url.ParseQuery(r.URL.RawQuery)
  744. if err != nil {
  745. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  746. return
  747. }
  748. // get the filter options
  749. form := &forms.MetricsQueryForm{
  750. K8sForm: &forms.K8sForm{
  751. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  752. Repo: app.Repo,
  753. DigitalOceanOAuth: app.DOConf,
  754. },
  755. },
  756. QueryOpts: &prometheus.QueryOpts{},
  757. }
  758. form.K8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  759. // decode from JSON to form value
  760. decoder := schema.NewDecoder()
  761. decoder.IgnoreUnknownKeys(true)
  762. if err := decoder.Decode(form.QueryOpts, vals); err != nil {
  763. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  764. return
  765. }
  766. // validate the form
  767. if err := app.validator.Struct(form); err != nil {
  768. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  769. return
  770. }
  771. // create a new agent
  772. var agent *kubernetes.Agent
  773. if app.ServerConf.IsTesting {
  774. agent = app.TestAgents.K8sAgent
  775. } else {
  776. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  777. }
  778. // get prometheus service
  779. promSvc, found, err := prometheus.GetPrometheusService(agent.Clientset)
  780. if err != nil {
  781. app.handleErrorInternal(err, w)
  782. return
  783. }
  784. if !found {
  785. app.handleErrorInternal(err, w)
  786. return
  787. }
  788. rawQuery, err := prometheus.QueryPrometheus(agent.Clientset, promSvc, form.QueryOpts)
  789. if err != nil {
  790. app.handleErrorInternal(err, w)
  791. return
  792. }
  793. fmt.Fprint(w, string(rawQuery))
  794. }
  795. type KubeconfigResponse struct {
  796. Kubeconfig []byte `json:"kubeconfig"`
  797. }
  798. func (app *App) HandleGetTemporaryKubeconfig(w http.ResponseWriter, r *http.Request) {
  799. vals, err := url.ParseQuery(r.URL.RawQuery)
  800. if err != nil {
  801. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  802. return
  803. }
  804. // get the filter options
  805. form := &forms.K8sForm{
  806. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  807. Repo: app.Repo,
  808. DigitalOceanOAuth: app.DOConf,
  809. },
  810. }
  811. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  812. // get the API config
  813. apiConf, err := form.OutOfClusterConfig.CreateRawConfigFromCluster()
  814. if err != nil {
  815. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  816. return
  817. }
  818. bytes, err := clientcmd.Write(*apiConf)
  819. res := &KubeconfigResponse{
  820. Kubeconfig: bytes,
  821. }
  822. if err := json.NewEncoder(w).Encode(res); err != nil {
  823. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  824. return
  825. }
  826. }