k8s_handler.go 26 KB

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