upgrade_webhook.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. package release
  2. import (
  3. "fmt"
  4. "net/http"
  5. "net/url"
  6. "github.com/porter-dev/porter/api/server/authz"
  7. "github.com/porter-dev/porter/api/server/handlers"
  8. "github.com/porter-dev/porter/api/server/shared"
  9. "github.com/porter-dev/porter/api/server/shared/apierrors"
  10. "github.com/porter-dev/porter/api/server/shared/config"
  11. "github.com/porter-dev/porter/api/server/shared/requestutils"
  12. "github.com/porter-dev/porter/api/types"
  13. "github.com/porter-dev/porter/internal/analytics"
  14. "github.com/porter-dev/porter/internal/helm"
  15. "github.com/porter-dev/porter/internal/notifier"
  16. "github.com/porter-dev/porter/internal/notifier/slack"
  17. "gorm.io/gorm"
  18. )
  19. type WebhookHandler struct {
  20. handlers.PorterHandlerReadWriter
  21. authz.KubernetesAgentGetter
  22. }
  23. func NewWebhookHandler(
  24. config *config.Config,
  25. decoderValidator shared.RequestDecoderValidator,
  26. writer shared.ResultWriter,
  27. ) *WebhookHandler {
  28. return &WebhookHandler{
  29. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  30. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  31. }
  32. }
  33. func (c *WebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  34. token, _ := requestutils.GetURLParamString(r, types.URLParamToken)
  35. // retrieve release by token
  36. release, err := c.Repo().Release().ReadReleaseByWebhookToken(token)
  37. if err != nil {
  38. if err == gorm.ErrRecordNotFound {
  39. // throw forbidden error, since we don't want a way to verify if webhooks exist
  40. c.HandleAPIError(w, r, apierrors.NewErrForbidden(
  41. fmt.Errorf("release not found with given webhook"),
  42. ))
  43. return
  44. }
  45. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  46. return
  47. }
  48. cluster, err := c.Repo().Cluster().ReadCluster(release.ProjectID, release.ClusterID)
  49. if err != nil {
  50. if err == gorm.ErrRecordNotFound {
  51. // throw forbidden error, since we don't want a way to verify if the cluster and project
  52. // still exist for a cluster that's been deleted
  53. c.HandleAPIError(w, r, apierrors.NewErrForbidden(
  54. fmt.Errorf("cluster %d in project %d not found for upgrade webhook", release.ClusterID, release.ProjectID),
  55. ))
  56. return
  57. }
  58. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  59. return
  60. }
  61. // in this case, we retrieve the agent by passing in the namespace field directly, since
  62. // it cannot be detected from the URL
  63. helmAgent, err := c.GetHelmAgent(r, cluster, release.Namespace)
  64. if err != nil {
  65. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  66. return
  67. }
  68. request := &types.WebhookRequest{}
  69. if ok := c.DecodeAndValidate(w, r, request); !ok {
  70. return
  71. }
  72. rel, err := helmAgent.GetRelease(release.Name, 0, true)
  73. if err != nil {
  74. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  75. return
  76. }
  77. // repository is set to current repository by default
  78. repository := rel.Config["image"].(map[string]interface{})["repository"]
  79. currTag := rel.Config["image"].(map[string]interface{})["tag"]
  80. gitAction := release.GitActionConfig
  81. if gitAction != nil && gitAction.ID != 0 && (repository == "porterdev/hello-porter" || repository == "public.ecr.aws/o1j4x7p4/hello-porter") {
  82. repository = gitAction.ImageRepoURI
  83. } else if gitAction != nil && gitAction.ID != 0 && (repository == "porterdev/hello-porter-job" || repository == "public.ecr.aws/o1j4x7p4/hello-porter-job") {
  84. repository = gitAction.ImageRepoURI
  85. }
  86. image := map[string]interface{}{}
  87. image["repository"] = repository
  88. image["tag"] = request.Commit
  89. if request.Commit == "" {
  90. image["tag"] = currTag
  91. }
  92. rel.Config["image"] = image
  93. if rel.Config["auto_deploy"] == false {
  94. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  95. fmt.Errorf("Deploy webhook is disabled for this deployment."),
  96. http.StatusBadRequest,
  97. ))
  98. return
  99. }
  100. registries, err := c.Repo().Registry().ListRegistriesByProjectID(release.ProjectID)
  101. if err != nil {
  102. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  103. return
  104. }
  105. conf := &helm.UpgradeReleaseConfig{
  106. Name: release.Name,
  107. Cluster: cluster,
  108. Repo: c.Repo(),
  109. Registries: registries,
  110. Values: rel.Config,
  111. }
  112. slackInts, _ := c.Repo().SlackIntegration().ListSlackIntegrationsByProjectID(release.ProjectID)
  113. var notifConf *types.NotificationConfig
  114. notifConf = nil
  115. if release != nil && release.NotificationConfig != 0 {
  116. conf, err := c.Repo().NotificationConfig().ReadNotificationConfig(release.NotificationConfig)
  117. if err != nil {
  118. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  119. return
  120. }
  121. notifConf = conf.ToNotificationConfigType()
  122. }
  123. deplNotifier := slack.NewDeploymentNotifier(notifConf, slackInts...)
  124. notifyOpts := &notifier.NotifyOpts{
  125. ProjectID: release.ProjectID,
  126. ClusterID: cluster.ID,
  127. ClusterName: cluster.Name,
  128. Name: rel.Name,
  129. Namespace: rel.Namespace,
  130. URL: fmt.Sprintf(
  131. "%s/applications/%s/%s/%s?project_id=%d",
  132. c.Config().ServerConf.ServerURL,
  133. url.PathEscape(cluster.Name),
  134. release.Namespace,
  135. rel.Name,
  136. cluster.ProjectID,
  137. ),
  138. }
  139. rel, err = helmAgent.UpgradeReleaseByValues(conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
  140. if err != nil {
  141. notifyOpts.Status = notifier.StatusHelmFailed
  142. notifyOpts.Info = err.Error()
  143. if !cluster.NotificationsDisabled {
  144. deplNotifier.Notify(notifyOpts)
  145. }
  146. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  147. err,
  148. http.StatusBadRequest,
  149. ))
  150. return
  151. }
  152. if rel.Chart != nil && rel.Chart.Metadata.Name != "job" {
  153. notifyOpts.Status = notifier.StatusHelmDeployed
  154. notifyOpts.Version = rel.Version
  155. if !cluster.NotificationsDisabled {
  156. deplNotifier.Notify(notifyOpts)
  157. }
  158. }
  159. c.Config().AnalyticsClient.Track(analytics.ApplicationDeploymentWebhookTrack(&analytics.ApplicationDeploymentWebhookTrackOpts{
  160. ImageURI: fmt.Sprintf("%v", repository),
  161. ApplicationScopedTrackOpts: analytics.GetApplicationScopedTrackOpts(
  162. 0,
  163. release.ProjectID,
  164. release.ClusterID,
  165. release.Name,
  166. release.Namespace,
  167. rel.Chart.Metadata.Name,
  168. ),
  169. }))
  170. c.WriteResult(w, r, nil)
  171. err = postUpgrade(c.Config(), cluster.ProjectID, cluster.ID, rel)
  172. if err != nil {
  173. c.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(err))
  174. return
  175. }
  176. }