rollback.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package porter_app
  2. import (
  3. "net/http"
  4. "strings"
  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/server/shared/features"
  11. "github.com/porter-dev/porter/api/server/shared/requestutils"
  12. "github.com/porter-dev/porter/api/types"
  13. utils "github.com/porter-dev/porter/api/utils/porter_app"
  14. "github.com/porter-dev/porter/internal/helm"
  15. "github.com/porter-dev/porter/internal/models"
  16. "github.com/porter-dev/porter/internal/telemetry"
  17. "gopkg.in/yaml.v2"
  18. )
  19. type RollbackPorterAppHandler struct {
  20. handlers.PorterHandlerReadWriter
  21. authz.KubernetesAgentGetter
  22. }
  23. func NewRollbackPorterAppHandler(
  24. config *config.Config,
  25. decoderValidator shared.RequestDecoderValidator,
  26. writer shared.ResultWriter,
  27. ) *RollbackPorterAppHandler {
  28. return &RollbackPorterAppHandler{
  29. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  30. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  31. }
  32. }
  33. func (c *RollbackPorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  34. ctx, span := telemetry.NewSpan(r.Context(), "serve-rollback-porter-app")
  35. defer span.End()
  36. cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
  37. user, _ := ctx.Value(types.UserScope).(*models.User)
  38. request := &types.RollbackPorterAppRequest{}
  39. if ok := c.DecodeAndValidate(w, r, request); !ok {
  40. err := telemetry.Error(ctx, span, nil, "error decoding request")
  41. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  42. return
  43. }
  44. appName, reqErr := requestutils.GetURLParamString(r, types.URLParamPorterAppName)
  45. if reqErr != nil {
  46. err := telemetry.Error(ctx, span, reqErr, "error getting stack name from url")
  47. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  48. return
  49. }
  50. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "stack-name", Value: appName})
  51. namespace := utils.NamespaceFromPorterAppName(appName)
  52. helmAgent, err := c.GetHelmAgent(ctx, r, cluster, namespace)
  53. if err != nil {
  54. err = telemetry.Error(ctx, span, err, "error getting helm agent")
  55. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  56. return
  57. }
  58. k8sAgent, err := c.GetAgent(r, cluster, namespace)
  59. if err != nil {
  60. err = telemetry.Error(ctx, span, err, "error getting k8s agent")
  61. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  62. return
  63. }
  64. helmReleaseFromRequestedRevision, err := helmAgent.GetRelease(ctx, appName, request.Revision, false)
  65. if err != nil {
  66. err = telemetry.Error(ctx, span, err, "error getting helm release for requested revision")
  67. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  68. return
  69. }
  70. latestHelmRelease, err := helmAgent.GetRelease(ctx, appName, 0, false)
  71. if err != nil {
  72. err = telemetry.Error(ctx, span, err, "error getting latest helm release")
  73. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  74. return
  75. }
  76. valuesYaml, err := yaml.Marshal(helmReleaseFromRequestedRevision.Config)
  77. if err != nil {
  78. err = telemetry.Error(ctx, span, err, "error marshalling helm release config to yaml")
  79. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  80. return
  81. }
  82. imageInfo := attemptToGetImageInfoFromRelease(helmReleaseFromRequestedRevision.Config)
  83. if imageInfo.Tag == "" {
  84. imageInfo.Tag = "latest"
  85. }
  86. porterApp, err := c.Repo().PorterApp().ReadPorterAppByName(cluster.ID, appName)
  87. if err != nil {
  88. err = telemetry.Error(ctx, span, err, "error getting porter app")
  89. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  90. return
  91. }
  92. injectLauncher := strings.Contains(porterApp.Builder, "heroku") ||
  93. strings.Contains(porterApp.Builder, "paketo")
  94. registries, err := c.Repo().Registry().ListRegistriesByProjectID(cluster.ProjectID)
  95. if err != nil {
  96. err = telemetry.Error(ctx, span, err, "error listing registries")
  97. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  98. return
  99. }
  100. chart, values, _, err := parse(
  101. ctx,
  102. ParseConf{
  103. PorterAppName: appName,
  104. ImageInfo: imageInfo,
  105. ServerConfig: c.Config(),
  106. ProjectID: cluster.ProjectID,
  107. Namespace: namespace,
  108. SubdomainCreateOpts: SubdomainCreateOpts{
  109. k8sAgent: k8sAgent,
  110. dnsRepo: c.Repo().DNSRecord(),
  111. powerDnsClient: c.Config().PowerDNSClient,
  112. appRootDomain: c.Config().ServerConf.AppRootDomain,
  113. stackName: appName,
  114. },
  115. InjectLauncherToStartCommand: injectLauncher,
  116. FullHelmValues: string(valuesYaml),
  117. },
  118. )
  119. if err != nil {
  120. err = telemetry.Error(ctx, span, err, "error parsing helm chart")
  121. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  122. return
  123. }
  124. conf := &helm.InstallChartConfig{
  125. Chart: chart,
  126. Name: appName,
  127. Namespace: namespace,
  128. Values: values,
  129. Cluster: cluster,
  130. Repo: c.Repo(),
  131. Registries: registries,
  132. }
  133. _, err = helmAgent.UpgradeInstallChart(ctx, conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
  134. if err != nil {
  135. err = telemetry.Error(ctx, span, err, "error upgrading application")
  136. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  137. return
  138. }
  139. if features.AreAgentDeployEventsEnabled(user.Email, k8sAgent) {
  140. serviceDeploymentStatusMap := getServiceDeploymentMetadataFromValues(values, types.PorterAppEventStatus_Progressing)
  141. _, err = createNewPorterAppDeployEvent(ctx, serviceDeploymentStatusMap, types.PorterAppEventStatus_Progressing, porterApp.ID, latestHelmRelease.Version+1, imageInfo.Tag, c.Repo().PorterAppEvent())
  142. } else {
  143. _, err = createOldPorterAppDeployEvent(ctx, types.PorterAppEventStatus_Success, porterApp.ID, latestHelmRelease.Version+1, imageInfo.Tag, c.Repo().PorterAppEvent())
  144. }
  145. if err != nil {
  146. err = telemetry.Error(ctx, span, err, "error creating porter app event")
  147. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  148. return
  149. }
  150. }