release_handler.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. package api
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "net/url"
  6. "strconv"
  7. "github.com/go-chi/chi"
  8. "github.com/porter-dev/porter/internal/forms"
  9. "github.com/porter-dev/porter/internal/helm"
  10. "github.com/porter-dev/porter/internal/helm/grapher"
  11. "github.com/porter-dev/porter/internal/repository"
  12. )
  13. // Enumeration of release API error codes, represented as int64
  14. const (
  15. ErrReleaseDecode ErrorCode = iota + 600
  16. ErrReleaseValidateFields
  17. ErrReleaseReadData
  18. ErrReleaseDeploy
  19. )
  20. // HandleListReleases retrieves a list of releases for a cluster
  21. // with various filter options
  22. func (app *App) HandleListReleases(w http.ResponseWriter, r *http.Request) {
  23. form := &forms.ListReleaseForm{
  24. ReleaseForm: &forms.ReleaseForm{
  25. Form: &helm.Form{
  26. UpdateTokenCache: app.updateTokenCache,
  27. },
  28. },
  29. ListFilter: &helm.ListFilter{},
  30. }
  31. agent, err := app.getAgentFromQueryParams(
  32. w,
  33. r,
  34. form.ReleaseForm,
  35. form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
  36. form.PopulateListFromQueryParams,
  37. )
  38. // errors are handled in app.getAgentFromQueryParams
  39. if err != nil {
  40. return
  41. }
  42. releases, err := agent.ListReleases(form.Namespace, form.ListFilter)
  43. if err != nil {
  44. app.handleErrorRead(err, ErrReleaseReadData, w)
  45. return
  46. }
  47. if err := json.NewEncoder(w).Encode(releases); err != nil {
  48. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  49. return
  50. }
  51. }
  52. // HandleGetRelease retrieves a single release based on a name and revision
  53. func (app *App) HandleGetRelease(w http.ResponseWriter, r *http.Request) {
  54. name := chi.URLParam(r, "name")
  55. revision, err := strconv.ParseUint(chi.URLParam(r, "revision"), 0, 64)
  56. form := &forms.GetReleaseForm{
  57. ReleaseForm: &forms.ReleaseForm{
  58. Form: &helm.Form{
  59. UpdateTokenCache: app.updateTokenCache,
  60. },
  61. },
  62. Name: name,
  63. Revision: int(revision),
  64. }
  65. agent, err := app.getAgentFromQueryParams(
  66. w,
  67. r,
  68. form.ReleaseForm,
  69. form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
  70. )
  71. // errors are handled in app.getAgentFromQueryParams
  72. if err != nil {
  73. return
  74. }
  75. release, err := agent.GetRelease(form.Name, form.Revision)
  76. if err != nil {
  77. app.sendExternalError(err, http.StatusNotFound, HTTPError{
  78. Code: ErrReleaseReadData,
  79. Errors: []string{"release not found"},
  80. }, w)
  81. return
  82. }
  83. if err := json.NewEncoder(w).Encode(release); err != nil {
  84. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  85. return
  86. }
  87. }
  88. // HandleGetReleaseComponents retrieves a single release based on a name and revision
  89. func (app *App) HandleGetReleaseComponents(w http.ResponseWriter, r *http.Request) {
  90. name := chi.URLParam(r, "name")
  91. revision, err := strconv.ParseUint(chi.URLParam(r, "revision"), 0, 64)
  92. form := &forms.GetReleaseForm{
  93. ReleaseForm: &forms.ReleaseForm{
  94. Form: &helm.Form{
  95. UpdateTokenCache: app.updateTokenCache,
  96. },
  97. },
  98. Name: name,
  99. Revision: int(revision),
  100. }
  101. agent, err := app.getAgentFromQueryParams(
  102. w,
  103. r,
  104. form.ReleaseForm,
  105. form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
  106. )
  107. // errors are handled in app.getAgentFromQueryParams
  108. if err != nil {
  109. return
  110. }
  111. release, err := agent.GetRelease(form.Name, form.Revision)
  112. if err != nil {
  113. app.sendExternalError(err, http.StatusNotFound, HTTPError{
  114. Code: ErrReleaseReadData,
  115. Errors: []string{"release not found"},
  116. }, w)
  117. return
  118. }
  119. yamlArr := grapher.ImportMultiDocYAML([]byte(release.Manifest))
  120. objects := grapher.ParseObjs(yamlArr)
  121. parsed := grapher.ParsedObjs{
  122. Objects: objects,
  123. }
  124. parsed.GetControlRel()
  125. parsed.GetLabelRel()
  126. parsed.GetSpecRel()
  127. if err := json.NewEncoder(w).Encode(parsed.Objects); err != nil {
  128. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  129. return
  130. }
  131. }
  132. // HandleListReleaseHistory retrieves a history of releases based on a release name
  133. func (app *App) HandleListReleaseHistory(w http.ResponseWriter, r *http.Request) {
  134. name := chi.URLParam(r, "name")
  135. form := &forms.ListReleaseHistoryForm{
  136. ReleaseForm: &forms.ReleaseForm{
  137. Form: &helm.Form{
  138. UpdateTokenCache: app.updateTokenCache,
  139. },
  140. },
  141. Name: name,
  142. }
  143. agent, err := app.getAgentFromQueryParams(
  144. w,
  145. r,
  146. form.ReleaseForm,
  147. form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
  148. )
  149. // errors are handled in app.getAgentFromQueryParams
  150. if err != nil {
  151. return
  152. }
  153. release, err := agent.GetReleaseHistory(form.Name)
  154. if err != nil {
  155. app.sendExternalError(err, http.StatusNotFound, HTTPError{
  156. Code: ErrReleaseReadData,
  157. Errors: []string{"release not found"},
  158. }, w)
  159. return
  160. }
  161. if err := json.NewEncoder(w).Encode(release); err != nil {
  162. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  163. return
  164. }
  165. }
  166. // HandleUpgradeRelease upgrades a release with new values.yaml
  167. func (app *App) HandleUpgradeRelease(w http.ResponseWriter, r *http.Request) {
  168. name := chi.URLParam(r, "name")
  169. vals, err := url.ParseQuery(r.URL.RawQuery)
  170. if err != nil {
  171. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  172. return
  173. }
  174. form := &forms.UpgradeReleaseForm{
  175. ReleaseForm: &forms.ReleaseForm{
  176. Form: &helm.Form{
  177. UpdateTokenCache: app.updateTokenCache,
  178. },
  179. },
  180. Name: name,
  181. }
  182. form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
  183. vals,
  184. app.repo.ServiceAccount,
  185. )
  186. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  187. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  188. return
  189. }
  190. agent, err := app.getAgentFromReleaseForm(
  191. w,
  192. r,
  193. form.ReleaseForm,
  194. )
  195. // errors are handled in app.getAgentFromBodyParams
  196. if err != nil {
  197. return
  198. }
  199. _, err = agent.UpgradeRelease(form.Name, form.Values)
  200. if err != nil {
  201. app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
  202. Code: ErrReleaseDeploy,
  203. Errors: []string{"error upgrading release " + err.Error()},
  204. }, w)
  205. return
  206. }
  207. w.WriteHeader(http.StatusOK)
  208. }
  209. // HandleRollbackRelease rolls a release back to a specified revision
  210. func (app *App) HandleRollbackRelease(w http.ResponseWriter, r *http.Request) {
  211. name := chi.URLParam(r, "name")
  212. vals, err := url.ParseQuery(r.URL.RawQuery)
  213. if err != nil {
  214. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  215. return
  216. }
  217. form := &forms.RollbackReleaseForm{
  218. ReleaseForm: &forms.ReleaseForm{
  219. Form: &helm.Form{
  220. UpdateTokenCache: app.updateTokenCache,
  221. },
  222. },
  223. Name: name,
  224. }
  225. form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
  226. vals,
  227. app.repo.ServiceAccount,
  228. )
  229. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  230. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  231. return
  232. }
  233. agent, err := app.getAgentFromReleaseForm(
  234. w,
  235. r,
  236. form.ReleaseForm,
  237. )
  238. // errors are handled in app.getAgentFromBodyParams
  239. if err != nil {
  240. return
  241. }
  242. err = agent.RollbackRelease(form.Name, form.Revision)
  243. if err != nil {
  244. app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
  245. Code: ErrReleaseDeploy,
  246. Errors: []string{"error rolling back release " + err.Error()},
  247. }, w)
  248. return
  249. }
  250. w.WriteHeader(http.StatusOK)
  251. }
  252. // ------------------------ Release handler helper functions ------------------------ //
  253. // getAgentFromQueryParams uses the query params to populate a form, and then
  254. // passes that form to the underlying app.getAgentFromReleaseForm to create a new
  255. // Helm agent.
  256. func (app *App) getAgentFromQueryParams(
  257. w http.ResponseWriter,
  258. r *http.Request,
  259. form *forms.ReleaseForm,
  260. // populate uses the query params to populate a form
  261. populate ...func(vals url.Values, repo repository.ServiceAccountRepository) error,
  262. ) (*helm.Agent, error) {
  263. vals, err := url.ParseQuery(r.URL.RawQuery)
  264. if err != nil {
  265. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  266. return nil, err
  267. }
  268. for _, f := range populate {
  269. err := f(vals, app.repo.ServiceAccount)
  270. if err != nil {
  271. return nil, err
  272. }
  273. }
  274. return app.getAgentFromReleaseForm(w, r, form)
  275. }
  276. // getAgentFromReleaseForm uses a non-validated form to construct a new Helm agent based on
  277. // the userID found in the session and the options required by the Helm agent.
  278. func (app *App) getAgentFromReleaseForm(
  279. w http.ResponseWriter,
  280. r *http.Request,
  281. form *forms.ReleaseForm,
  282. ) (*helm.Agent, error) {
  283. var err error
  284. // validate the form
  285. if err := app.validator.Struct(form); err != nil {
  286. app.handleErrorFormValidation(err, ErrReleaseValidateFields, w)
  287. return nil, err
  288. }
  289. // create a new agent
  290. var agent *helm.Agent
  291. if app.testing {
  292. agent = app.TestAgents.HelmAgent
  293. } else {
  294. agent, err = helm.GetAgentOutOfClusterConfig(form.Form, app.logger)
  295. }
  296. return agent, err
  297. }