cluster_handler.go 11 KB

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