create_addon.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package release
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  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/types"
  12. "github.com/porter-dev/porter/internal/analytics"
  13. "github.com/porter-dev/porter/internal/helm"
  14. "github.com/porter-dev/porter/internal/helm/loader"
  15. "github.com/porter-dev/porter/internal/models"
  16. "github.com/porter-dev/porter/internal/oauth"
  17. "github.com/porter-dev/porter/internal/telemetry"
  18. "github.com/stefanmcshane/helm/pkg/chart"
  19. )
  20. // Namespace_EnvironmentGroups is the base namespace for storing all environment groups.
  21. const Namespace_EnvironmentGroups = "porter-env-group"
  22. // Namespace_ACKSystem is the base namespace for interacting with ack chart controllers
  23. const Namespace_ACKSystem = "ack-system"
  24. type CreateAddonHandler struct {
  25. handlers.PorterHandlerReadWriter
  26. authz.KubernetesAgentGetter
  27. }
  28. func NewCreateAddonHandler(
  29. config *config.Config,
  30. decoderValidator shared.RequestDecoderValidator,
  31. writer shared.ResultWriter,
  32. ) *CreateAddonHandler {
  33. return &CreateAddonHandler{
  34. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  35. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  36. }
  37. }
  38. func (c *CreateAddonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  39. ctx, span := telemetry.NewSpan(r.Context(), "serve-create-addon")
  40. defer span.End()
  41. user, _ := ctx.Value(types.UserScope).(*models.User)
  42. proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
  43. cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
  44. namespace := ctx.Value(types.NamespaceScope).(string)
  45. operationID := oauth.CreateRandomState()
  46. c.Config().AnalyticsClient.Track(analytics.ApplicationLaunchStartTrack(
  47. &analytics.ApplicationLaunchStartTrackOpts{
  48. ClusterScopedTrackOpts: analytics.GetClusterScopedTrackOpts(user.ID, cluster.ProjectID, cluster.ID),
  49. FlowID: operationID,
  50. },
  51. ))
  52. helmAgent, err := c.GetHelmAgent(ctx, r, cluster, "")
  53. if err != nil {
  54. err = telemetry.Error(ctx, span, nil, "error creating helm agent")
  55. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  56. return
  57. }
  58. request := &types.CreateAddonRequest{}
  59. if ok := c.DecodeAndValidate(w, r, request); !ok {
  60. err := telemetry.Error(ctx, span, nil, "error decoding request")
  61. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  62. return
  63. }
  64. if request.TemplateVersion == "latest" {
  65. request.TemplateVersion = ""
  66. }
  67. telemetry.WithAttributes(span,
  68. telemetry.AttributeKV{Key: "repo-url", Value: request.RepoURL},
  69. telemetry.AttributeKV{Key: "template-name", Value: request.TemplateName},
  70. telemetry.AttributeKV{Key: "template-version", Value: request.TemplateVersion},
  71. )
  72. chart, err := LoadChart(ctx, c.Config(), &LoadAddonChartOpts{
  73. ProjectID: proj.ID,
  74. RepoURL: request.RepoURL,
  75. TemplateName: request.TemplateName,
  76. TemplateVersion: request.TemplateVersion,
  77. })
  78. if err != nil {
  79. err = telemetry.Error(ctx, span, nil, "error loading chart")
  80. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  81. return
  82. }
  83. registries, err := c.Repo().Registry().ListRegistriesByProjectID(cluster.ProjectID)
  84. if err != nil {
  85. err = telemetry.Error(ctx, span, err, "error retrieving project registry")
  86. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  87. return
  88. }
  89. conf := &helm.InstallChartConfig{
  90. Chart: chart,
  91. Name: request.Name,
  92. Namespace: namespace,
  93. Values: request.Values,
  94. Cluster: cluster,
  95. Repo: c.Repo(),
  96. Registries: registries,
  97. }
  98. helmRelease, err := helmAgent.InstallChart(ctx, conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
  99. if err != nil {
  100. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  101. telemetry.Error(ctx, span, nil, fmt.Sprintf("error installing a new chart: %s", err.Error())),
  102. http.StatusBadRequest,
  103. ))
  104. return
  105. }
  106. c.Config().AnalyticsClient.Track(analytics.ApplicationLaunchSuccessTrack(
  107. &analytics.ApplicationLaunchSuccessTrackOpts{
  108. ApplicationScopedTrackOpts: analytics.GetApplicationScopedTrackOpts(
  109. user.ID,
  110. cluster.ProjectID,
  111. cluster.ID,
  112. helmRelease.Name,
  113. helmRelease.Namespace,
  114. chart.Metadata.Name,
  115. ),
  116. FlowID: operationID,
  117. },
  118. ))
  119. }
  120. type LoadAddonChartOpts struct {
  121. ProjectID uint
  122. RepoURL, TemplateName, TemplateVersion string
  123. }
  124. // LoadChart fetches a chart from a remote repo
  125. func LoadChart(ctx context.Context, config *config.Config, opts *LoadAddonChartOpts) (*chart.Chart, error) {
  126. // if the chart repo url is one of the specified application/addon charts, just load public
  127. if opts.RepoURL == config.ServerConf.DefaultAddonHelmRepoURL || opts.RepoURL == config.ServerConf.DefaultApplicationHelmRepoURL {
  128. return loader.LoadChartPublic(ctx, opts.RepoURL, opts.TemplateName, opts.TemplateVersion)
  129. } else {
  130. // load the helm repos in the project
  131. hrs, err := config.Repo.HelmRepo().ListHelmReposByProjectID(opts.ProjectID)
  132. if err != nil {
  133. return nil, err
  134. }
  135. for _, hr := range hrs {
  136. if hr.RepoURL == opts.RepoURL {
  137. if hr.BasicAuthIntegrationID != 0 {
  138. // read the basic integration id
  139. basic, err := config.Repo.BasicIntegration().ReadBasicIntegration(opts.ProjectID, hr.BasicAuthIntegrationID)
  140. if err != nil {
  141. return nil, err
  142. }
  143. return loader.LoadChart(ctx,
  144. &loader.BasicAuthClient{
  145. Username: string(basic.Username),
  146. Password: string(basic.Password),
  147. }, hr.RepoURL, opts.TemplateName, opts.TemplateVersion)
  148. } else {
  149. return loader.LoadChartPublic(ctx, hr.RepoURL, opts.TemplateName, opts.TemplateVersion)
  150. }
  151. }
  152. }
  153. }
  154. return nil, fmt.Errorf("chart repo not found")
  155. }