create_subdomain.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. package porter_app
  2. import (
  3. "net/http"
  4. "github.com/porter-dev/porter/internal/telemetry"
  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/requestutils"
  11. "github.com/porter-dev/porter/api/types"
  12. "github.com/porter-dev/porter/internal/kubernetes/domain"
  13. "github.com/porter-dev/porter/internal/models"
  14. )
  15. // CreateSubdomainHandler handles requests to the /apps/{porter_app_name}/subdomain endpoint
  16. type CreateSubdomainHandler struct {
  17. handlers.PorterHandlerReadWriter
  18. authz.KubernetesAgentGetter
  19. }
  20. // NewCreateSubdomainHandler returns a new CreateSubdomainHandler
  21. func NewCreateSubdomainHandler(
  22. config *config.Config,
  23. decoderValidator shared.RequestDecoderValidator,
  24. writer shared.ResultWriter,
  25. ) *CreateSubdomainHandler {
  26. return &CreateSubdomainHandler{
  27. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  28. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  29. }
  30. }
  31. // CreateSubdomainRequest is the request object for the /apps/{porter_app_name}/subdomain endpoint
  32. type CreateSubdomainRequest struct {
  33. ServiceName string `schema:"service_name"`
  34. }
  35. // CreateSubdomainResponse is the response object for the /apps/{porter_app_name}/subdomain endpoint
  36. type CreateSubdomainResponse struct {
  37. // Subdomain is the url for the created subdomain
  38. Subdomain string `json:"subdomain"`
  39. }
  40. // ServeHTTP creates a subdomain for the provided service and returns it
  41. func (c *CreateSubdomainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  42. ctx, span := telemetry.NewSpan(r.Context(), "serve-create-subdomain")
  43. defer span.End()
  44. project, _ := ctx.Value(types.ProjectScope).(*models.Project)
  45. cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
  46. name, _ := requestutils.GetURLParamString(r, types.URLParamPorterAppName)
  47. telemetry.WithAttributes(span,
  48. telemetry.AttributeKV{Key: "project-id", Value: project.ID},
  49. telemetry.AttributeKV{Key: "cluster-id", Value: cluster.ID},
  50. telemetry.AttributeKV{Key: "app-name", Value: name},
  51. )
  52. request := &CreateSubdomainRequest{}
  53. if ok := c.DecodeAndValidate(w, r, request); !ok {
  54. err := telemetry.Error(ctx, span, nil, "error decoding request")
  55. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  56. return
  57. }
  58. if request.ServiceName == "" {
  59. err := telemetry.Error(ctx, span, nil, "service name cannot be empty")
  60. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  61. return
  62. }
  63. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "service-name", Value: request.ServiceName})
  64. k8sAgent, err := c.GetAgent(r, cluster, "")
  65. if err != nil {
  66. err := telemetry.Error(ctx, span, nil, "error getting agent")
  67. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  68. return
  69. }
  70. if k8sAgent == nil {
  71. err := telemetry.Error(ctx, span, nil, "agent is nil")
  72. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  73. return
  74. }
  75. endpoint, found, err := domain.GetNGINXIngressServiceIP(k8sAgent.Clientset)
  76. if err != nil {
  77. err := telemetry.Error(ctx, span, nil, "error getting nginx ingress service ip")
  78. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  79. return
  80. }
  81. if !found {
  82. err := telemetry.Error(ctx, span, nil, "nginx ingress service ip not found")
  83. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  84. return
  85. }
  86. if endpoint == "" {
  87. err := telemetry.Error(ctx, span, nil, "nginx ingress service ip is empty")
  88. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  89. return
  90. }
  91. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "nginx-ingress-ip", Value: endpoint})
  92. createDomain := domain.CreateDNSRecordConfig{
  93. ReleaseName: request.ServiceName,
  94. RootDomain: c.Config().ServerConf.AppRootDomain,
  95. Endpoint: endpoint,
  96. }
  97. record := createDomain.NewDNSRecordForEndpoint()
  98. if record == nil {
  99. err := telemetry.Error(ctx, span, nil, "dns record is nil")
  100. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  101. return
  102. }
  103. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "host-name", Value: record.Hostname})
  104. record, err = c.Repo().DNSRecord().CreateDNSRecord(record)
  105. if err != nil {
  106. err := telemetry.Error(ctx, span, nil, "error creating dns record")
  107. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  108. return
  109. }
  110. if record == nil {
  111. err := telemetry.Error(ctx, span, nil, "dns record is nil")
  112. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  113. return
  114. }
  115. _record := domain.DNSRecord(*record)
  116. if c.Config().DNSClient == nil {
  117. err := telemetry.Error(ctx, span, nil, "dns client is nil")
  118. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  119. return
  120. }
  121. err = _record.CreateDomain(c.Config().DNSClient)
  122. if err != nil {
  123. err := telemetry.Error(ctx, span, nil, "error creating domain")
  124. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  125. return
  126. }
  127. resp := &CreateSubdomainResponse{
  128. Subdomain: _record.Hostname,
  129. }
  130. c.WriteResult(w, r, resp)
  131. }