release_handler.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. package api
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "strconv"
  8. "strings"
  9. "github.com/porter-dev/porter/internal/models"
  10. "github.com/porter-dev/porter/internal/templater/parser"
  11. "helm.sh/helm/v3/pkg/release"
  12. "github.com/go-chi/chi"
  13. "github.com/porter-dev/porter/internal/forms"
  14. "github.com/porter-dev/porter/internal/helm"
  15. "github.com/porter-dev/porter/internal/helm/grapher"
  16. "github.com/porter-dev/porter/internal/kubernetes"
  17. "github.com/porter-dev/porter/internal/repository"
  18. )
  19. // Enumeration of release API error codes, represented as int64
  20. const (
  21. ErrReleaseDecode ErrorCode = iota + 600
  22. ErrReleaseValidateFields
  23. ErrReleaseReadData
  24. ErrReleaseDeploy
  25. )
  26. // HandleListReleases retrieves a list of releases for a cluster
  27. // with various filter options
  28. func (app *App) HandleListReleases(w http.ResponseWriter, r *http.Request) {
  29. form := &forms.ListReleaseForm{
  30. ReleaseForm: &forms.ReleaseForm{
  31. Form: &helm.Form{
  32. Repo: app.Repo,
  33. },
  34. },
  35. ListFilter: &helm.ListFilter{},
  36. }
  37. agent, err := app.getAgentFromQueryParams(
  38. w,
  39. r,
  40. form.ReleaseForm,
  41. form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
  42. form.PopulateListFromQueryParams,
  43. )
  44. // errors are handled in app.getAgentFromQueryParams
  45. if err != nil {
  46. return
  47. }
  48. releases, err := agent.ListReleases(form.Namespace, form.ListFilter)
  49. if err != nil {
  50. app.handleErrorRead(err, ErrReleaseReadData, w)
  51. return
  52. }
  53. if err := json.NewEncoder(w).Encode(releases); err != nil {
  54. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  55. return
  56. }
  57. }
  58. // PorterRelease is a helm release with a form attached
  59. type PorterRelease struct {
  60. *release.Release
  61. Form *models.FormYAML `json:"form"`
  62. }
  63. // HandleGetRelease retrieves a single release based on a name and revision
  64. func (app *App) HandleGetRelease(w http.ResponseWriter, r *http.Request) {
  65. name := chi.URLParam(r, "name")
  66. revision, err := strconv.ParseUint(chi.URLParam(r, "revision"), 0, 64)
  67. form := &forms.GetReleaseForm{
  68. ReleaseForm: &forms.ReleaseForm{
  69. Form: &helm.Form{
  70. Repo: app.Repo,
  71. },
  72. },
  73. Name: name,
  74. Revision: int(revision),
  75. }
  76. agent, err := app.getAgentFromQueryParams(
  77. w,
  78. r,
  79. form.ReleaseForm,
  80. form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
  81. )
  82. // errors are handled in app.getAgentFromQueryParams
  83. if err != nil {
  84. return
  85. }
  86. release, err := agent.GetRelease(form.Name, form.Revision)
  87. if err != nil {
  88. app.sendExternalError(err, http.StatusNotFound, HTTPError{
  89. Code: ErrReleaseReadData,
  90. Errors: []string{"release not found"},
  91. }, w)
  92. return
  93. }
  94. // get the filter options
  95. k8sForm := &forms.K8sForm{
  96. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  97. Repo: app.Repo,
  98. },
  99. }
  100. vals, err := url.ParseQuery(r.URL.RawQuery)
  101. if err != nil {
  102. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  103. return
  104. }
  105. k8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  106. // validate the form
  107. if err := app.validator.Struct(k8sForm); err != nil {
  108. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  109. return
  110. }
  111. // create a new dynamic client
  112. dynClient, err := kubernetes.GetDynamicClientOutOfClusterConfig(k8sForm.OutOfClusterConfig)
  113. if err != nil {
  114. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  115. return
  116. }
  117. parserDef := &parser.ClientConfigDefault{
  118. DynamicClient: dynClient,
  119. HelmChart: release.Chart,
  120. HelmRelease: release,
  121. }
  122. res := &PorterRelease{release, nil}
  123. for _, file := range release.Chart.Files {
  124. if strings.Contains(file.Name, "form.yaml") {
  125. formYAML, err := parser.FormYAMLFromBytes(parserDef, file.Data, "")
  126. if err != nil {
  127. break
  128. }
  129. res.Form = formYAML
  130. break
  131. }
  132. }
  133. // if form not populated, detect common charts
  134. if res.Form == nil {
  135. // for now just case by name
  136. if res.Release.Chart.Name() == "velero" {
  137. formYAML, err := parser.FormYAMLFromBytes(parserDef, []byte(veleroForm), "")
  138. if err == nil {
  139. res.Form = formYAML
  140. }
  141. }
  142. }
  143. if err := json.NewEncoder(w).Encode(res); err != nil {
  144. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  145. return
  146. }
  147. }
  148. // HandleGetReleaseComponents retrieves kubernetes objects listed in a release identified by name and revision
  149. func (app *App) HandleGetReleaseComponents(w http.ResponseWriter, r *http.Request) {
  150. name := chi.URLParam(r, "name")
  151. revision, err := strconv.ParseUint(chi.URLParam(r, "revision"), 0, 64)
  152. form := &forms.GetReleaseForm{
  153. ReleaseForm: &forms.ReleaseForm{
  154. Form: &helm.Form{
  155. Repo: app.Repo,
  156. },
  157. },
  158. Name: name,
  159. Revision: int(revision),
  160. }
  161. agent, err := app.getAgentFromQueryParams(
  162. w,
  163. r,
  164. form.ReleaseForm,
  165. form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
  166. )
  167. // errors are handled in app.getAgentFromQueryParams
  168. if err != nil {
  169. return
  170. }
  171. release, err := agent.GetRelease(form.Name, form.Revision)
  172. if err != nil {
  173. app.sendExternalError(err, http.StatusNotFound, HTTPError{
  174. Code: ErrReleaseReadData,
  175. Errors: []string{"release not found"},
  176. }, w)
  177. return
  178. }
  179. yamlArr := grapher.ImportMultiDocYAML([]byte(release.Manifest))
  180. objects := grapher.ParseObjs(yamlArr)
  181. parsed := grapher.ParsedObjs{
  182. Objects: objects,
  183. }
  184. parsed.GetControlRel()
  185. parsed.GetLabelRel()
  186. parsed.GetSpecRel()
  187. if err := json.NewEncoder(w).Encode(parsed); err != nil {
  188. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  189. return
  190. }
  191. }
  192. // HandleGetReleaseControllers retrieves controllers that belong to a release.
  193. // Used to display status of charts.
  194. func (app *App) HandleGetReleaseControllers(w http.ResponseWriter, r *http.Request) {
  195. name := chi.URLParam(r, "name")
  196. revision, err := strconv.ParseUint(chi.URLParam(r, "revision"), 0, 64)
  197. form := &forms.GetReleaseForm{
  198. ReleaseForm: &forms.ReleaseForm{
  199. Form: &helm.Form{
  200. Repo: app.Repo,
  201. },
  202. },
  203. Name: name,
  204. Revision: int(revision),
  205. }
  206. agent, err := app.getAgentFromQueryParams(
  207. w,
  208. r,
  209. form.ReleaseForm,
  210. form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
  211. )
  212. // errors are handled in app.getAgentFromQueryParams
  213. if err != nil {
  214. return
  215. }
  216. release, err := agent.GetRelease(form.Name, form.Revision)
  217. if err != nil {
  218. app.sendExternalError(err, http.StatusNotFound, HTTPError{
  219. Code: ErrReleaseReadData,
  220. Errors: []string{"release not found"},
  221. }, w)
  222. return
  223. }
  224. vals, err := url.ParseQuery(r.URL.RawQuery)
  225. if err != nil {
  226. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  227. return
  228. }
  229. // get the filter options
  230. k8sForm := &forms.K8sForm{
  231. OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
  232. Repo: app.Repo,
  233. },
  234. }
  235. k8sForm.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
  236. // validate the form
  237. if err := app.validator.Struct(k8sForm); err != nil {
  238. app.handleErrorFormValidation(err, ErrK8sValidate, w)
  239. return
  240. }
  241. // create a new kubernetes agent
  242. var k8sAgent *kubernetes.Agent
  243. if app.ServerConf.IsTesting {
  244. k8sAgent = app.TestAgents.K8sAgent
  245. } else {
  246. k8sAgent, err = kubernetes.GetAgentOutOfClusterConfig(k8sForm.OutOfClusterConfig)
  247. }
  248. yamlArr := grapher.ImportMultiDocYAML([]byte(release.Manifest))
  249. controllers := grapher.ParseControllers(yamlArr)
  250. retrievedControllers := []interface{}{}
  251. // get current status of each controller
  252. // TODO: refactor with type assertion
  253. for _, c := range controllers {
  254. c.Namespace = form.ReleaseForm.Form.Namespace
  255. switch c.Kind {
  256. case "Deployment":
  257. rc, err := k8sAgent.GetDeployment(c)
  258. if err != nil {
  259. app.handleErrorDataRead(err, w)
  260. return
  261. }
  262. rc.Kind = c.Kind
  263. retrievedControllers = append(retrievedControllers, rc)
  264. case "StatefulSet":
  265. rc, err := k8sAgent.GetStatefulSet(c)
  266. if err != nil {
  267. app.handleErrorDataRead(err, w)
  268. return
  269. }
  270. rc.Kind = c.Kind
  271. retrievedControllers = append(retrievedControllers, rc)
  272. case "DaemonSet":
  273. rc, err := k8sAgent.GetDaemonSet(c)
  274. if err != nil {
  275. app.handleErrorDataRead(err, w)
  276. return
  277. }
  278. rc.Kind = c.Kind
  279. retrievedControllers = append(retrievedControllers, rc)
  280. case "ReplicaSet":
  281. rc, err := k8sAgent.GetReplicaSet(c)
  282. if err != nil {
  283. app.handleErrorDataRead(err, w)
  284. return
  285. }
  286. rc.Kind = c.Kind
  287. retrievedControllers = append(retrievedControllers, rc)
  288. }
  289. }
  290. if err := json.NewEncoder(w).Encode(retrievedControllers); err != nil {
  291. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  292. return
  293. }
  294. }
  295. // HandleListReleaseHistory retrieves a history of releases based on a release name
  296. func (app *App) HandleListReleaseHistory(w http.ResponseWriter, r *http.Request) {
  297. name := chi.URLParam(r, "name")
  298. form := &forms.ListReleaseHistoryForm{
  299. ReleaseForm: &forms.ReleaseForm{
  300. Form: &helm.Form{
  301. Repo: app.Repo,
  302. },
  303. },
  304. Name: name,
  305. }
  306. agent, err := app.getAgentFromQueryParams(
  307. w,
  308. r,
  309. form.ReleaseForm,
  310. form.ReleaseForm.PopulateHelmOptionsFromQueryParams,
  311. )
  312. // errors are handled in app.getAgentFromQueryParams
  313. if err != nil {
  314. return
  315. }
  316. release, err := agent.GetReleaseHistory(form.Name)
  317. if err != nil {
  318. app.sendExternalError(err, http.StatusNotFound, HTTPError{
  319. Code: ErrReleaseReadData,
  320. Errors: []string{"release not found"},
  321. }, w)
  322. return
  323. }
  324. if err := json.NewEncoder(w).Encode(release); err != nil {
  325. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  326. return
  327. }
  328. }
  329. // HandleGetReleaseToken retrieves the webhook token of a specific release.
  330. func (app *App) HandleGetReleaseToken(w http.ResponseWriter, r *http.Request) {
  331. name := chi.URLParam(r, "name")
  332. vals, err := url.ParseQuery(r.URL.RawQuery)
  333. namespace := vals["namespace"][0]
  334. release, err := app.Repo.Release.ReadRelease(name, namespace)
  335. if err != nil {
  336. app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
  337. Code: ErrReleaseReadData,
  338. Errors: []string{"release not found"},
  339. }, w)
  340. }
  341. releaseExt := release.Externalize()
  342. if err := json.NewEncoder(w).Encode(releaseExt); err != nil {
  343. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  344. return
  345. }
  346. }
  347. // HandleUpgradeRelease upgrades a release with new values.yaml
  348. func (app *App) HandleUpgradeRelease(w http.ResponseWriter, r *http.Request) {
  349. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  350. if err != nil || projID == 0 {
  351. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  352. return
  353. }
  354. name := chi.URLParam(r, "name")
  355. vals, err := url.ParseQuery(r.URL.RawQuery)
  356. if err != nil {
  357. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  358. return
  359. }
  360. form := &forms.UpgradeReleaseForm{
  361. ReleaseForm: &forms.ReleaseForm{
  362. Form: &helm.Form{
  363. Repo: app.Repo,
  364. },
  365. },
  366. Name: name,
  367. }
  368. form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
  369. vals,
  370. app.Repo.Cluster,
  371. )
  372. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  373. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  374. return
  375. }
  376. agent, err := app.getAgentFromReleaseForm(
  377. w,
  378. r,
  379. form.ReleaseForm,
  380. )
  381. // errors are handled in app.getAgentFromBodyParams
  382. if err != nil {
  383. return
  384. }
  385. registries, err := app.Repo.Registry.ListRegistriesByProjectID(uint(projID))
  386. if err != nil {
  387. app.handleErrorDataRead(err, w)
  388. return
  389. }
  390. conf := &helm.UpgradeReleaseConfig{
  391. Name: form.Name,
  392. Cluster: form.ReleaseForm.Cluster,
  393. Repo: *app.Repo,
  394. Registries: registries,
  395. }
  396. _, err = agent.UpgradeRelease(conf, form.Values)
  397. if err != nil {
  398. app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
  399. Code: ErrReleaseDeploy,
  400. Errors: []string{"error upgrading release " + err.Error()},
  401. }, w)
  402. return
  403. }
  404. w.WriteHeader(http.StatusOK)
  405. }
  406. // HandleReleaseDeployHook upgrades a release with new image commit
  407. func (app *App) HandleReleaseDeployHook(w http.ResponseWriter, r *http.Request) {
  408. name := chi.URLParam(r, "name")
  409. vals, err := url.ParseQuery(r.URL.RawQuery)
  410. commit := vals["commit"][0]
  411. repository := vals["repository"][0]
  412. if err != nil {
  413. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  414. return
  415. }
  416. form := &forms.UpgradeReleaseForm{
  417. ReleaseForm: &forms.ReleaseForm{
  418. Form: &helm.Form{
  419. Repo: app.Repo,
  420. },
  421. },
  422. Name: name,
  423. }
  424. form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
  425. vals,
  426. app.Repo.Cluster,
  427. )
  428. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  429. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  430. return
  431. }
  432. agent, err := app.getAgentFromReleaseForm(
  433. w,
  434. r,
  435. form.ReleaseForm,
  436. )
  437. // errors are handled in app.getAgentFromBodyParams
  438. if err != nil {
  439. return
  440. }
  441. image := map[string]interface{}{}
  442. image["repository"] = repository
  443. image["tag"] = commit
  444. newval := map[string]interface{}{}
  445. newval["image"] = image
  446. registries, err := app.Repo.Registry.ListRegistriesByProjectID(uint(form.ReleaseForm.Cluster.ProjectID))
  447. if err != nil {
  448. app.handleErrorDataRead(err, w)
  449. return
  450. }
  451. conf := &helm.UpgradeReleaseConfig{
  452. Name: form.Name,
  453. Cluster: form.ReleaseForm.Cluster,
  454. Repo: *app.Repo,
  455. Registries: registries,
  456. Values: newval,
  457. }
  458. _, err = agent.UpgradeReleaseByValues(conf)
  459. if err != nil {
  460. app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
  461. Code: ErrReleaseDeploy,
  462. Errors: []string{"error upgrading release " + err.Error()},
  463. }, w)
  464. return
  465. }
  466. w.WriteHeader(http.StatusOK)
  467. }
  468. // HandleReleaseDeployWebhook upgrades a release when a chart specific webhook is called.
  469. func (app *App) HandleReleaseDeployWebhook(w http.ResponseWriter, r *http.Request) {
  470. token := chi.URLParam(r, "token")
  471. // retrieve release by token
  472. release, err := app.Repo.Release.ReadReleaseByWebhookToken(token)
  473. if err != nil {
  474. app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
  475. Code: ErrReleaseReadData,
  476. Errors: []string{"release not found with given webhook"},
  477. }, w)
  478. return
  479. }
  480. params := map[string][]string{}
  481. params["cluster_id"] = []string{fmt.Sprint(release.ClusterID)}
  482. params["storage"] = []string{"secret"}
  483. params["namespace"] = []string{release.Namespace}
  484. vals, err := url.ParseQuery(r.URL.RawQuery)
  485. commit := vals["commit"][0]
  486. repository := vals["repository"][0]
  487. if err != nil {
  488. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  489. return
  490. }
  491. form := &forms.UpgradeReleaseForm{
  492. ReleaseForm: &forms.ReleaseForm{
  493. Form: &helm.Form{
  494. Repo: app.Repo,
  495. },
  496. },
  497. Name: release.Name,
  498. }
  499. form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
  500. params,
  501. app.Repo.Cluster,
  502. )
  503. agent, err := app.getAgentFromReleaseForm(
  504. w,
  505. r,
  506. form.ReleaseForm,
  507. )
  508. // errors are handled in app.getAgentFromBodyParams
  509. if err != nil {
  510. return
  511. }
  512. image := map[string]interface{}{}
  513. image["repository"] = repository
  514. image["tag"] = commit
  515. newval := map[string]interface{}{}
  516. newval["image"] = image
  517. registries, err := app.Repo.Registry.ListRegistriesByProjectID(uint(form.ReleaseForm.Cluster.ProjectID))
  518. if err != nil {
  519. app.handleErrorDataRead(err, w)
  520. return
  521. }
  522. conf := &helm.UpgradeReleaseConfig{
  523. Name: form.Name,
  524. Cluster: form.ReleaseForm.Cluster,
  525. Repo: *app.Repo,
  526. Registries: registries,
  527. Values: newval,
  528. }
  529. _, err = agent.UpgradeReleaseByValues(conf)
  530. if err != nil {
  531. app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
  532. Code: ErrReleaseDeploy,
  533. Errors: []string{"error upgrading release " + err.Error()},
  534. }, w)
  535. return
  536. }
  537. w.WriteHeader(http.StatusOK)
  538. }
  539. // HandleRollbackRelease rolls a release back to a specified revision
  540. func (app *App) HandleRollbackRelease(w http.ResponseWriter, r *http.Request) {
  541. name := chi.URLParam(r, "name")
  542. vals, err := url.ParseQuery(r.URL.RawQuery)
  543. if err != nil {
  544. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  545. return
  546. }
  547. form := &forms.RollbackReleaseForm{
  548. ReleaseForm: &forms.ReleaseForm{
  549. Form: &helm.Form{
  550. Repo: app.Repo,
  551. },
  552. },
  553. Name: name,
  554. }
  555. form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
  556. vals,
  557. app.Repo.Cluster,
  558. )
  559. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  560. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  561. return
  562. }
  563. agent, err := app.getAgentFromReleaseForm(
  564. w,
  565. r,
  566. form.ReleaseForm,
  567. )
  568. // errors are handled in app.getAgentFromBodyParams
  569. if err != nil {
  570. return
  571. }
  572. err = agent.RollbackRelease(form.Name, form.Revision)
  573. if err != nil {
  574. app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
  575. Code: ErrReleaseDeploy,
  576. Errors: []string{"error rolling back release " + err.Error()},
  577. }, w)
  578. return
  579. }
  580. w.WriteHeader(http.StatusOK)
  581. }
  582. // ------------------------ Release handler helper functions ------------------------ //
  583. // getAgentFromQueryParams uses the query params to populate a form, and then
  584. // passes that form to the underlying app.getAgentFromReleaseForm to create a new
  585. // Helm agent.
  586. func (app *App) getAgentFromQueryParams(
  587. w http.ResponseWriter,
  588. r *http.Request,
  589. form *forms.ReleaseForm,
  590. // populate uses the query params to populate a form
  591. populate ...func(vals url.Values, repo repository.ClusterRepository) error,
  592. ) (*helm.Agent, error) {
  593. vals, err := url.ParseQuery(r.URL.RawQuery)
  594. if err != nil {
  595. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  596. return nil, err
  597. }
  598. for _, f := range populate {
  599. err := f(vals, app.Repo.Cluster)
  600. if err != nil {
  601. return nil, err
  602. }
  603. }
  604. return app.getAgentFromReleaseForm(w, r, form)
  605. }
  606. // getAgentFromReleaseForm uses a non-validated form to construct a new Helm agent based on
  607. // the userID found in the session and the options required by the Helm agent.
  608. func (app *App) getAgentFromReleaseForm(
  609. w http.ResponseWriter,
  610. r *http.Request,
  611. form *forms.ReleaseForm,
  612. ) (*helm.Agent, error) {
  613. var err error
  614. // validate the form
  615. if err := app.validator.Struct(form); err != nil {
  616. app.handleErrorFormValidation(err, ErrReleaseValidateFields, w)
  617. return nil, err
  618. }
  619. // create a new agent
  620. var agent *helm.Agent
  621. if app.ServerConf.IsTesting {
  622. agent = app.TestAgents.HelmAgent
  623. } else {
  624. agent, err = helm.GetAgentOutOfClusterConfig(form.Form, app.Logger)
  625. }
  626. return agent, err
  627. }
  628. const veleroForm string = `tags:
  629. - hello
  630. tabs:
  631. - name: main
  632. context:
  633. type: cluster
  634. config:
  635. group: velero.io
  636. version: v1
  637. resource: backups
  638. label: Backups
  639. sections:
  640. - name: section_one
  641. contents:
  642. - type: heading
  643. label: 💾 Velero Backups
  644. - type: resource-list
  645. value: |
  646. .items[] | {
  647. name: .metadata.name,
  648. label: .metadata.namespace,
  649. status: .status.phase,
  650. timestamp: .status.completionTimestamp,
  651. message: [
  652. (if .status.volumeSnapshotsAttempted then "\(.status.volumeSnapshotsAttempted) volume snapshots attempted, \(.status.volumeSnapshotsCompleted) completed." else null end),
  653. "Finished \(.status.completionTimestamp).",
  654. "Backup expires on \(.status.expiration)."
  655. ]|join(" "),
  656. data: {
  657. "Included Namespaces": (if .spec.includedNamespaces then .spec.includedNamespaces|join(",") else "* (all)" end),
  658. "Included Resources": (if .spec.includedResources then .spec.includedResources|join(",") else "* (all)" end),
  659. "Storage Location": .spec.storageLocation
  660. }
  661. }`