get.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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. } else if err != gorm.ErrRecordNotFound {
  54. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  55. return
  56. } else {
  57. res.PorterRelease = &types.PorterRelease{}
  58. }
  59. // detect if Porter application chart and attempt to get the latest version
  60. // from chart repo
  61. cache := c.Config().URLCache
  62. chartRepoURL, foundFirst := cache.GetURL(helmRelease.Chart.Metadata.Name)
  63. if !foundFirst {
  64. cache.Update()
  65. chartRepoURL, _ = cache.GetURL(helmRelease.Chart.Metadata.Name)
  66. }
  67. if chartRepoURL != "" {
  68. repoIndex, err := loader.LoadRepoIndexPublic(chartRepoURL)
  69. if err == nil {
  70. porterChart := loader.FindPorterChartInIndexList(repoIndex, res.Chart.Metadata.Name)
  71. res.LatestVersion = res.Chart.Metadata.Version
  72. // set latest version to the greater of porterChart.Versions and res.Chart.Metadata.Version
  73. porterChartVersion, porterChartErr := semver.NewVersion(porterChart.Versions[0])
  74. currChartVersion, currChartErr := semver.NewVersion(res.Chart.Metadata.Version)
  75. if currChartErr == nil && porterChartErr == nil && porterChartVersion.GreaterThan(currChartVersion) {
  76. res.LatestVersion = porterChart.Versions[0]
  77. }
  78. }
  79. }
  80. // look for the form using the dynamic client
  81. dynClient, err := c.GetDynamicClient(r, cluster)
  82. if err != nil {
  83. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  84. return
  85. }
  86. parserDef := &parser.ClientConfigDefault{
  87. DynamicClient: dynClient,
  88. HelmChart: helmRelease.Chart,
  89. HelmRelease: helmRelease,
  90. }
  91. form, err := parser.GetFormFromRelease(parserDef, helmRelease)
  92. if err != nil {
  93. c.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(err))
  94. } else {
  95. res.Form = form
  96. }
  97. // if form not populated, detect common charts
  98. if res.Form == nil {
  99. // for now just case by name
  100. if res.Release.Chart.Name() == "cert-manager" {
  101. formYAML, err := parser.FormYAMLFromBytes(parserDef, []byte(certManagerForm), "", "")
  102. if err == nil {
  103. res.Form = formYAML
  104. }
  105. } else if res.Release.Chart.Name() == "velero" {
  106. formYAML, err := parser.FormYAMLFromBytes(parserDef, []byte(veleroForm), "", "")
  107. if err == nil {
  108. res.Form = formYAML
  109. }
  110. }
  111. }
  112. c.WriteResult(w, r, res)
  113. }
  114. const certManagerForm string = `tags:
  115. - hello
  116. tabs:
  117. - name: main
  118. context:
  119. type: cluster
  120. config:
  121. group: cert-manager.io
  122. version: v1
  123. resource: certificates
  124. label: Certificates
  125. sections:
  126. - name: section_one
  127. contents:
  128. - type: heading
  129. label: Certificates
  130. - type: resource-list
  131. settings:
  132. options:
  133. resource-button:
  134. name: "Renew Certificate"
  135. description: "This will delete the existing certificate resource, triggering a new certificate request."
  136. actions:
  137. - delete:
  138. scope: namespace
  139. relative_uri: /crd
  140. context:
  141. type: cluster
  142. config:
  143. group: cert-manager.io
  144. version: v1
  145. resource: certificates
  146. value: |
  147. .items[] | {
  148. metadata: .metadata,
  149. name: "\(.spec.dnsNames | join(","))",
  150. label: "\(.metadata.namespace)/\(.metadata.name)",
  151. status: (
  152. ([.status.conditions[].type] | index("Ready")) as $index | (
  153. if $index then (
  154. if .status.conditions[$index].status == "True" then "Ready" else "Not Ready" end
  155. ) else (
  156. "Not Ready"
  157. ) end
  158. )
  159. ),
  160. timestamp: .status.conditions[0].lastTransitionTime,
  161. message: [.status.conditions[].message] | unique | join(","),
  162. data: {}
  163. }`
  164. const veleroForm string = `tags:
  165. - hello
  166. tabs:
  167. - name: main
  168. context:
  169. type: cluster
  170. config:
  171. group: velero.io
  172. version: v1
  173. resource: backups
  174. label: Backups
  175. sections:
  176. - name: section_one
  177. contents:
  178. - type: heading
  179. label: 💾 Velero Backups
  180. - type: resource-list
  181. value: |
  182. .items[] | {
  183. name: .metadata.name,
  184. label: .metadata.namespace,
  185. status: .status.phase,
  186. timestamp: .status.completionTimestamp,
  187. message: [
  188. (if .status.volumeSnapshotsAttempted then "\(.status.volumeSnapshotsAttempted) volume snapshots attempted, \(.status.volumeSnapshotsCompleted) completed." else null end),
  189. "Finished \(.status.completionTimestamp).",
  190. "Backup expires on \(.status.expiration)."
  191. ]|join(" "),
  192. data: {
  193. "Included Namespaces": (if .spec.includedNamespaces then .spec.includedNamespaces|join(",") else "* (all)" end),
  194. "Included Resources": (if .spec.includedResources then .spec.includedResources|join(",") else "* (all)" end),
  195. "Storage Location": .spec.storageLocation
  196. }
  197. }`