cluster_handler.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. package api
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "strconv"
  6. "github.com/go-chi/chi"
  7. "github.com/porter-dev/porter/internal/forms"
  8. "github.com/porter-dev/porter/internal/kubernetes"
  9. "github.com/porter-dev/porter/internal/kubernetes/domain"
  10. "github.com/porter-dev/porter/internal/models"
  11. )
  12. // HandleCreateProjectCluster creates a new cluster
  13. func (app *App) HandleCreateProjectCluster(w http.ResponseWriter, r *http.Request) {
  14. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  15. if err != nil || projID == 0 {
  16. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  17. return
  18. }
  19. form := &forms.CreateClusterForm{
  20. ProjectID: uint(projID),
  21. }
  22. // decode from JSON to form value
  23. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  24. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  25. return
  26. }
  27. // validate the form
  28. if err := app.validator.Struct(form); err != nil {
  29. app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
  30. return
  31. }
  32. // convert the form to a registry
  33. cluster, err := form.ToCluster()
  34. if err != nil {
  35. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  36. return
  37. }
  38. // handle write to the database
  39. cluster, err = app.Repo.Cluster.CreateCluster(cluster)
  40. if err != nil {
  41. app.handleErrorDataWrite(err, w)
  42. return
  43. }
  44. app.Logger.Info().Msgf("New cluster created: %d", cluster.ID)
  45. w.WriteHeader(http.StatusCreated)
  46. clusterExt := cluster.Externalize()
  47. if err := json.NewEncoder(w).Encode(clusterExt); err != nil {
  48. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  49. return
  50. }
  51. }
  52. // HandleReadProjectCluster reads a cluster by id
  53. func (app *App) HandleReadProjectCluster(w http.ResponseWriter, r *http.Request) {
  54. id, err := strconv.ParseUint(chi.URLParam(r, "cluster_id"), 0, 64)
  55. if err != nil || id == 0 {
  56. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  57. return
  58. }
  59. cluster, err := app.Repo.Cluster.ReadCluster(uint(id))
  60. if err != nil {
  61. app.handleErrorRead(err, ErrProjectDataRead, w)
  62. return
  63. }
  64. clusterExt := cluster.DetailedExternalize()
  65. form := &forms.K8sForm{
  66. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  67. Repo: app.Repo,
  68. DigitalOceanOAuth: app.DOConf,
  69. Cluster: cluster,
  70. },
  71. }
  72. var agent *kubernetes.Agent
  73. if app.ServerConf.IsTesting {
  74. agent = app.TestAgents.K8sAgent
  75. } else {
  76. agent, _ = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
  77. }
  78. endpoint, found, ingressErr := domain.GetNGINXIngressServiceIP(agent.Clientset)
  79. if found {
  80. clusterExt.IngressIP = endpoint
  81. }
  82. if !found && ingressErr != nil {
  83. clusterExt.IngressError = kubernetes.CatchK8sConnectionError(ingressErr).Externalize()
  84. }
  85. w.WriteHeader(http.StatusOK)
  86. if err := json.NewEncoder(w).Encode(clusterExt); err != nil {
  87. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  88. return
  89. }
  90. }
  91. // HandleListProjectClusters returns a list of clusters that have linked Integrations.
  92. func (app *App) HandleListProjectClusters(w http.ResponseWriter, r *http.Request) {
  93. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  94. if err != nil || projID == 0 {
  95. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  96. return
  97. }
  98. clusters, err := app.Repo.Cluster.ListClustersByProjectID(uint(projID))
  99. if err != nil {
  100. app.handleErrorRead(err, ErrProjectDataRead, w)
  101. return
  102. }
  103. extClusters := make([]*models.ClusterExternal, 0)
  104. for _, cluster := range clusters {
  105. extClusters = append(extClusters, cluster.Externalize())
  106. }
  107. w.WriteHeader(http.StatusOK)
  108. if err := json.NewEncoder(w).Encode(extClusters); err != nil {
  109. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  110. return
  111. }
  112. }
  113. // HandleUpdateProjectCluster updates a project's cluster
  114. func (app *App) HandleUpdateProjectCluster(w http.ResponseWriter, r *http.Request) {
  115. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  116. if err != nil || projID == 0 {
  117. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  118. return
  119. }
  120. clusterID, err := strconv.ParseUint(chi.URLParam(r, "cluster_id"), 0, 64)
  121. if err != nil || clusterID == 0 {
  122. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  123. return
  124. }
  125. form := &forms.UpdateClusterForm{
  126. ID: uint(clusterID),
  127. }
  128. // decode from JSON to form value
  129. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  130. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  131. return
  132. }
  133. // validate the form
  134. if err := app.validator.Struct(form); err != nil {
  135. app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
  136. return
  137. }
  138. // convert the form to a registry
  139. cluster, err := form.ToCluster(app.Repo.Cluster)
  140. if err != nil {
  141. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  142. return
  143. }
  144. // handle write to the database
  145. cluster, err = app.Repo.Cluster.UpdateCluster(cluster)
  146. if err != nil {
  147. app.handleErrorDataWrite(err, w)
  148. return
  149. }
  150. w.WriteHeader(http.StatusOK)
  151. clusterExt := cluster.Externalize()
  152. if err := json.NewEncoder(w).Encode(clusterExt); err != nil {
  153. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  154. return
  155. }
  156. }
  157. // HandleDeleteProjectCluster handles the deletion of a Cluster via the cluster ID
  158. func (app *App) HandleDeleteProjectCluster(w http.ResponseWriter, r *http.Request) {
  159. id, err := strconv.ParseUint(chi.URLParam(r, "cluster_id"), 0, 64)
  160. if err != nil || id == 0 {
  161. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  162. return
  163. }
  164. cluster, err := app.Repo.Cluster.ReadCluster(uint(id))
  165. if err != nil {
  166. app.handleErrorRead(err, ErrProjectDataRead, w)
  167. return
  168. }
  169. err = app.Repo.Cluster.DeleteCluster(cluster)
  170. if err != nil {
  171. app.handleErrorRead(err, ErrProjectDataRead, w)
  172. return
  173. }
  174. w.WriteHeader(http.StatusOK)
  175. }
  176. // HandleCreateProjectClusterCandidates handles the creation of ClusterCandidates using
  177. // a kubeconfig and a project id
  178. func (app *App) HandleCreateProjectClusterCandidates(w http.ResponseWriter, r *http.Request) {
  179. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  180. if err != nil || projID == 0 {
  181. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  182. return
  183. }
  184. form := &forms.CreateClusterCandidatesForm{
  185. ProjectID: uint(projID),
  186. }
  187. // decode from JSON to form value
  188. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  189. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  190. return
  191. }
  192. // validate the form
  193. if err := app.validator.Struct(form); err != nil {
  194. app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
  195. return
  196. }
  197. // convert the form to a ClusterCandidate
  198. ccs, err := form.ToClusterCandidates(app.ServerConf.IsLocal)
  199. if err != nil {
  200. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  201. return
  202. }
  203. extClusters := make([]*models.ClusterCandidateExternal, 0)
  204. session, err := app.Store.Get(r, app.ServerConf.CookieName)
  205. if err != nil {
  206. http.Error(w, err.Error(), http.StatusInternalServerError)
  207. return
  208. }
  209. userID, _ := session.Values["user_id"].(uint)
  210. for _, cc := range ccs {
  211. // handle write to the database
  212. cc, err = app.Repo.Cluster.CreateClusterCandidate(cc)
  213. if err != nil {
  214. app.handleErrorDataWrite(err, w)
  215. return
  216. }
  217. app.Logger.Info().Msgf("New cluster candidate created: %d", cc.ID)
  218. // if the ClusterCandidate does not have any actions to perform, create the Cluster
  219. // automatically
  220. if len(cc.Resolvers) == 0 {
  221. // we query the repo again to get the decrypted version of the cluster candidate
  222. cc, err = app.Repo.Cluster.ReadClusterCandidate(cc.ID)
  223. if err != nil {
  224. app.handleErrorDataRead(err, w)
  225. return
  226. }
  227. clusterForm := &forms.ResolveClusterForm{
  228. Resolver: &models.ClusterResolverAll{},
  229. ClusterCandidateID: cc.ID,
  230. ProjectID: uint(projID),
  231. UserID: userID,
  232. }
  233. err := clusterForm.ResolveIntegration(*app.Repo)
  234. if err != nil {
  235. app.handleErrorDataWrite(err, w)
  236. return
  237. }
  238. cluster, err := clusterForm.ResolveCluster(*app.Repo)
  239. if err != nil {
  240. app.handleErrorDataWrite(err, w)
  241. return
  242. }
  243. cc, err = app.Repo.Cluster.UpdateClusterCandidateCreatedClusterID(cc.ID, cluster.ID)
  244. if err != nil {
  245. app.handleErrorDataWrite(err, w)
  246. return
  247. }
  248. app.Logger.Info().Msgf("New cluster created: %d", cluster.ID)
  249. }
  250. extClusters = append(extClusters, cc.Externalize())
  251. }
  252. w.WriteHeader(http.StatusCreated)
  253. if err := json.NewEncoder(w).Encode(extClusters); err != nil {
  254. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  255. return
  256. }
  257. }
  258. // HandleListProjectClusterCandidates returns a list of externalized ClusterCandidates
  259. // ([]models.ClusterCandidateExternal) based on a project ID
  260. func (app *App) HandleListProjectClusterCandidates(w http.ResponseWriter, r *http.Request) {
  261. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  262. if err != nil || projID == 0 {
  263. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  264. return
  265. }
  266. ccs, err := app.Repo.Cluster.ListClusterCandidatesByProjectID(uint(projID))
  267. if err != nil {
  268. app.handleErrorRead(err, ErrProjectDataRead, w)
  269. return
  270. }
  271. extCCs := make([]*models.ClusterCandidateExternal, 0)
  272. for _, cc := range ccs {
  273. extCCs = append(extCCs, cc.Externalize())
  274. }
  275. w.WriteHeader(http.StatusOK)
  276. if err := json.NewEncoder(w).Encode(extCCs); err != nil {
  277. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  278. return
  279. }
  280. }
  281. // HandleResolveClusterCandidate accepts a list of resolving objects (ClusterResolver)
  282. // for a given ClusterCandidate, which "resolves" that ClusterCandidate and creates a
  283. // Cluster for a specific project
  284. func (app *App) HandleResolveClusterCandidate(w http.ResponseWriter, r *http.Request) {
  285. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  286. if err != nil || projID == 0 {
  287. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  288. return
  289. }
  290. candID, err := strconv.ParseUint(chi.URLParam(r, "candidate_id"), 0, 64)
  291. if err != nil || projID == 0 {
  292. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  293. return
  294. }
  295. session, err := app.Store.Get(r, app.ServerConf.CookieName)
  296. if err != nil {
  297. http.Error(w, err.Error(), http.StatusInternalServerError)
  298. return
  299. }
  300. userID, _ := session.Values["user_id"].(uint)
  301. // decode actions from request
  302. resolver := &models.ClusterResolverAll{}
  303. if err := json.NewDecoder(r.Body).Decode(resolver); err != nil {
  304. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  305. return
  306. }
  307. clusterResolver := &forms.ResolveClusterForm{
  308. Resolver: resolver,
  309. ClusterCandidateID: uint(candID),
  310. ProjectID: uint(projID),
  311. UserID: userID,
  312. }
  313. err = clusterResolver.ResolveIntegration(*app.Repo)
  314. if err != nil {
  315. app.handleErrorDataWrite(err, w)
  316. return
  317. }
  318. cluster, err := clusterResolver.ResolveCluster(*app.Repo)
  319. if err != nil {
  320. app.handleErrorDataWrite(err, w)
  321. return
  322. }
  323. _, err = app.Repo.Cluster.UpdateClusterCandidateCreatedClusterID(uint(candID), cluster.ID)
  324. if err != nil {
  325. app.handleErrorDataWrite(err, w)
  326. return
  327. }
  328. app.Logger.Info().Msgf("New cluster created: %d", cluster.ID)
  329. clusterExt := cluster.Externalize()
  330. w.WriteHeader(http.StatusCreated)
  331. if err := json.NewEncoder(w).Encode(clusterExt); err != nil {
  332. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  333. return
  334. }
  335. }