get.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. package release
  2. import (
  3. "net/http"
  4. semver "github.com/Masterminds/semver/v3"
  5. "github.com/porter-dev/porter/api/server/authz"
  6. "github.com/porter-dev/porter/api/server/handlers"
  7. "github.com/porter-dev/porter/api/server/shared"
  8. "github.com/porter-dev/porter/api/server/shared/apierrors"
  9. "github.com/porter-dev/porter/api/server/shared/config"
  10. "github.com/porter-dev/porter/api/types"
  11. "github.com/porter-dev/porter/internal/helm/loader"
  12. "github.com/porter-dev/porter/internal/models"
  13. "github.com/porter-dev/porter/internal/templater/parser"
  14. "gorm.io/gorm"
  15. "helm.sh/helm/v3/pkg/release"
  16. )
  17. type ReleaseGetHandler struct {
  18. handlers.PorterHandlerWriter
  19. authz.KubernetesAgentGetter
  20. }
  21. func NewReleaseGetHandler(
  22. config *config.Config,
  23. writer shared.ResultWriter,
  24. ) *ReleaseGetHandler {
  25. return &ReleaseGetHandler{
  26. PorterHandlerWriter: handlers.NewDefaultPorterHandler(config, nil, writer),
  27. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  28. }
  29. }
  30. func (c *ReleaseGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  31. helmRelease, _ := r.Context().Value(types.ReleaseScope).(*release.Release)
  32. res := &types.Release{
  33. Release: helmRelease,
  34. }
  35. // look up the release in the database; if not found, do not populate Porter fields
  36. cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
  37. release, err := c.Repo().Release().ReadRelease(cluster.ID, helmRelease.Name, helmRelease.Namespace)
  38. if err == nil {
  39. res.PorterRelease = release.ToReleaseType()
  40. res.ID = release.ID
  41. res.WebhookToken = release.WebhookToken
  42. if release.GitActionConfig != nil {
  43. res.GitActionConfig = release.GitActionConfig.ToGitActionConfigType()
  44. }
  45. if release.BuildConfig != 0 {
  46. bc, err := c.Repo().BuildConfig().GetBuildConfig(release.BuildConfig)
  47. if err != nil {
  48. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  49. return
  50. }
  51. res.BuildConfig = bc.ToBuildConfigType()
  52. }
  53. if release.StackResourceID != 0 {
  54. stackResource, err := c.Repo().Stack().ReadStackResource(release.StackResourceID)
  55. if err != nil {
  56. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  57. return
  58. }
  59. stackRevision, err := c.Repo().Stack().ReadStackRevision(stackResource.StackRevisionID)
  60. if err != nil {
  61. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  62. return
  63. }
  64. stack, err := c.Repo().Stack().ReadStackByID(cluster.ProjectID, stackRevision.StackID)
  65. if err != nil {
  66. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  67. return
  68. }
  69. res.StackID = stack.UID
  70. }
  71. } else if err != gorm.ErrRecordNotFound {
  72. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  73. return
  74. } else {
  75. res.PorterRelease = &types.PorterRelease{}
  76. }
  77. // detect if Porter application chart and attempt to get the latest version
  78. // from chart repo
  79. cache := c.Config().URLCache
  80. chartRepoURL, foundFirst := cache.GetURL(helmRelease.Chart.Metadata.Name)
  81. if !foundFirst {
  82. cache.Update()
  83. chartRepoURL, _ = cache.GetURL(helmRelease.Chart.Metadata.Name)
  84. }
  85. if chartRepoURL != "" {
  86. repoIndex, err := loader.LoadRepoIndexPublic(chartRepoURL)
  87. if err == nil {
  88. porterChart := loader.FindPorterChartInIndexList(repoIndex, res.Chart.Metadata.Name)
  89. res.LatestVersion = res.Chart.Metadata.Version
  90. // set latest version to the greater of porterChart.Versions and res.Chart.Metadata.Version
  91. porterChartVersion, porterChartErr := semver.NewVersion(porterChart.Versions[0])
  92. currChartVersion, currChartErr := semver.NewVersion(res.Chart.Metadata.Version)
  93. if currChartErr == nil && porterChartErr == nil && porterChartVersion.GreaterThan(currChartVersion) {
  94. res.LatestVersion = porterChart.Versions[0]
  95. }
  96. }
  97. }
  98. // look for the form using the dynamic client
  99. dynClient, err := c.GetDynamicClient(r, cluster)
  100. if err != nil {
  101. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  102. return
  103. }
  104. parserDef := &parser.ClientConfigDefault{
  105. DynamicClient: dynClient,
  106. HelmChart: helmRelease.Chart,
  107. HelmRelease: helmRelease,
  108. }
  109. form, err := parser.GetFormFromRelease(parserDef, helmRelease)
  110. if err != nil {
  111. c.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(err))
  112. } else {
  113. res.Form = form
  114. }
  115. // if form not populated, detect common charts
  116. if res.Form == nil {
  117. // for now just case by name
  118. if res.Release.Chart.Name() == "cert-manager" {
  119. formYAML, err := parser.FormYAMLFromBytes(parserDef, []byte(certManagerForm), "", "")
  120. if err == nil {
  121. res.Form = formYAML
  122. }
  123. } else if res.Release.Chart.Name() == "velero" {
  124. formYAML, err := parser.FormYAMLFromBytes(parserDef, []byte(veleroForm), "", "")
  125. if err == nil {
  126. res.Form = formYAML
  127. }
  128. }
  129. }
  130. c.WriteResult(w, r, res)
  131. }
  132. const certManagerForm string = `tags:
  133. - hello
  134. tabs:
  135. - name: main
  136. context:
  137. type: cluster
  138. config:
  139. group: cert-manager.io
  140. version: v1
  141. resource: certificates
  142. label: Certificates
  143. sections:
  144. - name: section_one
  145. contents:
  146. - type: heading
  147. label: Certificates
  148. - type: resource-list
  149. settings:
  150. options:
  151. resource-button:
  152. name: "Renew Certificate"
  153. description: "This will delete the existing certificate resource, triggering a new certificate request."
  154. actions:
  155. - delete:
  156. scope: namespace
  157. relative_uri: /crd
  158. context:
  159. type: cluster
  160. config:
  161. group: cert-manager.io
  162. version: v1
  163. resource: certificates
  164. value: |
  165. .items[] | {
  166. metadata: .metadata,
  167. name: "\(.spec.dnsNames | join(","))",
  168. label: "\(.metadata.namespace)/\(.metadata.name)",
  169. status: (
  170. ([.status.conditions[].type] | index("Ready")) as $index | (
  171. if $index then (
  172. if .status.conditions[$index].status == "True" then "Ready" else "Not Ready" end
  173. ) else (
  174. "Not Ready"
  175. ) end
  176. )
  177. ),
  178. timestamp: .status.conditions[0].lastTransitionTime,
  179. message: [.status.conditions[].message] | unique | join(","),
  180. data: {}
  181. }`
  182. const veleroForm string = `tags:
  183. - hello
  184. tabs:
  185. - name: main
  186. context:
  187. type: cluster
  188. config:
  189. group: velero.io
  190. version: v1
  191. resource: backups
  192. label: Backups
  193. sections:
  194. - name: section_one
  195. contents:
  196. - type: heading
  197. label: 💾 Velero Backups
  198. - type: resource-list
  199. value: |
  200. .items[] | {
  201. name: .metadata.name,
  202. label: .metadata.namespace,
  203. status: .status.phase,
  204. timestamp: .status.completionTimestamp,
  205. message: [
  206. (if .status.volumeSnapshotsAttempted then "\(.status.volumeSnapshotsAttempted) volume snapshots attempted, \(.status.volumeSnapshotsCompleted) completed." else null end),
  207. "Finished \(.status.completionTimestamp).",
  208. "Backup expires on \(.status.expiration)."
  209. ]|join(" "),
  210. data: {
  211. "Included Namespaces": (if .spec.includedNamespaces then .spec.includedNamespaces|join(",") else "* (all)" end),
  212. "Included Resources": (if .spec.includedResources then .spec.includedResources|join(",") else "* (all)" end),
  213. "Storage Location": .spec.storageLocation
  214. }
  215. }`