k8s_handler.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  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/websocket"
  9. "github.com/porter-dev/porter/internal/forms"
  10. "github.com/porter-dev/porter/internal/kubernetes"
  11. "github.com/porter-dev/porter/internal/kubernetes/prometheus"
  12. v1 "k8s.io/api/core/v1"
  13. )
  14. // Enumeration of k8s API error codes, represented as int64
  15. const (
  16. ErrK8sDecode ErrorCode = iota + 600
  17. ErrK8sValidate
  18. )
  19. var upgrader = websocket.Upgrader{
  20. ReadBufferSize: 1024,
  21. WriteBufferSize: 1024,
  22. }
  23. // HandleListNamespaces retrieves a list of namespaces
  24. func (app *App) HandleListNamespaces(w http.ResponseWriter, r *http.Request) {
  25. vals, err := url.ParseQuery(r.URL.RawQuery)
  26. if err != nil {
  27. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  28. return
  29. }
  30. // get the filter options
  31. form := &forms.K8sForm{
  32. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  33. Repo: app.Repo,
  34. DigitalOceanOAuth: app.DOConf,
  35. },
  36. }
  37. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  38. // validate the form
  39. if err := app.validator.Struct(form); err != nil {
  40. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  41. return
  42. }
  43. // create a new agent
  44. var agent *kubernetes.Agent
  45. if app.ServerConf.IsTesting {
  46. agent = app.TestAgents.K8sAgent
  47. } else {
  48. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  49. }
  50. namespaces, err := agent.ListNamespaces()
  51. if err != nil {
  52. app.handleErrorDataRead(err, w)
  53. return
  54. }
  55. if err := json.NewEncoder(w).Encode(namespaces); err != nil {
  56. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  57. return
  58. }
  59. }
  60. // HandleGetPodLogs returns real-time logs of the pod via websockets
  61. // TODO: Refactor repeated calls.
  62. func (app *App) HandleGetPodLogs(w http.ResponseWriter, r *http.Request) {
  63. // get session to retrieve correct kubeconfig
  64. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  65. // get path parameters
  66. namespace := chi.URLParam(r, "namespace")
  67. podName := chi.URLParam(r, "name")
  68. if err != nil {
  69. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  70. return
  71. }
  72. vals, err := url.ParseQuery(r.URL.RawQuery)
  73. if err != nil {
  74. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  75. return
  76. }
  77. // get the filter options
  78. form := &forms.K8sForm{
  79. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  80. Repo: app.Repo,
  81. DigitalOceanOAuth: app.DOConf,
  82. },
  83. }
  84. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  85. // validate the form
  86. if err := app.validator.Struct(form); err != nil {
  87. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  88. return
  89. }
  90. // create a new agent
  91. var agent *kubernetes.Agent
  92. if app.ServerConf.IsTesting {
  93. agent = app.TestAgents.K8sAgent
  94. } else {
  95. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  96. }
  97. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  98. // upgrade to websocket.
  99. conn, err := upgrader.Upgrade(w, r, nil)
  100. if err != nil {
  101. app.handleErrorUpgradeWebsocket(err, w)
  102. }
  103. err = agent.GetPodLogs(namespace, podName, conn)
  104. if err != nil {
  105. app.handleErrorWebsocketWrite(err, w)
  106. return
  107. }
  108. }
  109. // HandleGetIngress returns the ingress object given the name and namespace.
  110. func (app *App) HandleGetIngress(w http.ResponseWriter, r *http.Request) {
  111. // get session to retrieve correct kubeconfig
  112. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  113. // get path parameters
  114. namespace := chi.URLParam(r, "namespace")
  115. name := chi.URLParam(r, "name")
  116. if err != nil {
  117. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  118. return
  119. }
  120. vals, err := url.ParseQuery(r.URL.RawQuery)
  121. if err != nil {
  122. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  123. return
  124. }
  125. // get the filter options
  126. form := &forms.K8sForm{
  127. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  128. Repo: app.Repo,
  129. DigitalOceanOAuth: app.DOConf,
  130. },
  131. }
  132. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  133. // validate the form
  134. if err := app.validator.Struct(form); err != nil {
  135. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  136. return
  137. }
  138. // create a new agent
  139. var agent *kubernetes.Agent
  140. if app.ServerConf.IsTesting {
  141. agent = app.TestAgents.K8sAgent
  142. } else {
  143. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  144. }
  145. ingress, err := agent.GetIngress(namespace, name)
  146. if err != nil {
  147. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  148. return
  149. }
  150. if err := json.NewEncoder(w).Encode(ingress); err != nil {
  151. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  152. return
  153. }
  154. }
  155. // HandleListPods returns all pods that match the given selectors
  156. // TODO: Refactor repeated calls.
  157. func (app *App) HandleListPods(w http.ResponseWriter, r *http.Request) {
  158. // get session to retrieve correct kubeconfig
  159. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  160. if err != nil {
  161. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  162. return
  163. }
  164. vals, err := url.ParseQuery(r.URL.RawQuery)
  165. if err != nil {
  166. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  167. return
  168. }
  169. // get the filter options
  170. form := &forms.K8sForm{
  171. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  172. Repo: app.Repo,
  173. DigitalOceanOAuth: app.DOConf,
  174. },
  175. }
  176. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  177. // validate the form
  178. if err := app.validator.Struct(form); err != nil {
  179. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  180. return
  181. }
  182. // create a new agent
  183. var agent *kubernetes.Agent
  184. if app.ServerConf.IsTesting {
  185. agent = app.TestAgents.K8sAgent
  186. } else {
  187. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  188. }
  189. pods := []v1.Pod{}
  190. for _, selector := range vals["selectors"] {
  191. podsList, err := agent.GetPodsByLabel(selector)
  192. if err != nil {
  193. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  194. return
  195. }
  196. for _, pod := range podsList.Items {
  197. pods = append(pods, pod)
  198. }
  199. }
  200. if err := json.NewEncoder(w).Encode(pods); err != nil {
  201. app.handleErrorFormDecoding(err, ErrK8sDecode, w)
  202. return
  203. }
  204. }
  205. // HandleStreamControllerStatus test calls
  206. // TODO: Refactor repeated calls.
  207. func (app *App) HandleStreamControllerStatus(w http.ResponseWriter, r *http.Request) {
  208. // get session to retrieve correct kubeconfig
  209. _, err := app.Store.Get(r, app.ServerConf.CookieName)
  210. if err != nil {
  211. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  212. return
  213. }
  214. vals, err := url.ParseQuery(r.URL.RawQuery)
  215. if err != nil {
  216. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  217. return
  218. }
  219. // get the filter options
  220. form := &forms.K8sForm{
  221. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  222. Repo: app.Repo,
  223. DigitalOceanOAuth: app.DOConf,
  224. },
  225. }
  226. form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  227. // validate the form
  228. if err := app.validator.Struct(form); err != nil {
  229. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  230. return
  231. }
  232. // create a new agent
  233. var agent *kubernetes.Agent
  234. if app.ServerConf.IsTesting {
  235. agent = app.TestAgents.K8sAgent
  236. } else {
  237. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  238. }
  239. upgrader.CheckOrigin = func(r *http.Request) bool { return true }
  240. // upgrade to websocket.
  241. conn, err := upgrader.Upgrade(w, r, nil)
  242. if err != nil {
  243. app.handleErrorUpgradeWebsocket(err, w)
  244. }
  245. // get path parameters
  246. kind := chi.URLParam(r, "kind")
  247. err = agent.StreamControllerStatus(conn, kind)
  248. if err != nil {
  249. app.handleErrorWebsocketWrite(err, w)
  250. return
  251. }
  252. }
  253. func (app *App) HandleGetPodMetrics(w http.ResponseWriter, r *http.Request) {
  254. vals, err := url.ParseQuery(r.URL.RawQuery)
  255. if err != nil {
  256. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  257. return
  258. }
  259. // get the filter options
  260. form := &forms.MetricsQueryForm{
  261. K8sForm: &forms.K8sForm{
  262. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  263. Repo: app.Repo,
  264. DigitalOceanOAuth: app.DOConf,
  265. },
  266. },
  267. QueryOpts: &prometheus.QueryOpts{},
  268. }
  269. form.K8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  270. // decode from JSON to form value
  271. if err := json.NewDecoder(r.Body).Decode(form.QueryOpts); err != nil {
  272. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  273. return
  274. }
  275. // validate the form
  276. if err := app.validator.Struct(form); err != nil {
  277. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  278. return
  279. }
  280. // create a new agent
  281. var agent *kubernetes.Agent
  282. if app.ServerConf.IsTesting {
  283. agent = app.TestAgents.K8sAgent
  284. } else {
  285. agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  286. }
  287. // get prometheus service
  288. promSvc, found, err := prometheus.GetPrometheusService(agent.Clientset)
  289. if err != nil {
  290. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  291. return
  292. }
  293. if !found {
  294. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  295. return
  296. }
  297. rawQuery, err := prometheus.QueryPrometheus(agent.Clientset, promSvc, form.QueryOpts)
  298. if err != nil {
  299. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  300. return
  301. }
  302. fmt.Fprint(w, string(rawQuery))
  303. }