cluster_handler.go 11 KB

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