create_app.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package porter_app
  2. import (
  3. "errors"
  4. "net/http"
  5. "connectrpc.com/connect"
  6. porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
  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/models"
  13. "github.com/porter-dev/porter/internal/porter_app"
  14. "github.com/porter-dev/porter/internal/repository"
  15. "github.com/porter-dev/porter/internal/telemetry"
  16. )
  17. // ErrMissingSourceType is returned when the source type is not specified
  18. var ErrMissingSourceType = errors.New("missing source type")
  19. // CreateAppHandler is the handler for the /apps/create endpoint
  20. type CreateAppHandler struct {
  21. handlers.PorterHandlerReadWriter
  22. }
  23. // NewCreateAppHandler handles POST requests to the endpoint /apps/create
  24. func NewCreateAppHandler(
  25. config *config.Config,
  26. decoderValidator shared.RequestDecoderValidator,
  27. writer shared.ResultWriter,
  28. ) *CreateAppHandler {
  29. return &CreateAppHandler{
  30. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  31. }
  32. }
  33. // GitSource is the github metadata for an app
  34. type GitSource struct {
  35. GitBranch string `json:"git_branch"`
  36. GitRepoName string `json:"git_repo_name"`
  37. GitRepoID uint `json:"git_repo_id"`
  38. }
  39. // CreateAppRequest is the request object for the /apps/create endpoint
  40. type CreateAppRequest struct {
  41. GitSource `json:",inline"`
  42. SourceType porter_app.SourceType `json:"type"`
  43. PorterYamlPath string `json:"porter_yaml_path"`
  44. Image *porter_app.Image `json:"image,omitempty"`
  45. Name string `json:"name"`
  46. DeploymentTargetName string `json:"deployment_target_name,omitempty"`
  47. DeploymentTargetID string `json:"deployment_target_id,omitempty"`
  48. }
  49. // CreateGithubAppInput is the input for creating an app with a github source
  50. type CreateGithubAppInput struct {
  51. ProjectID uint
  52. ClusterID uint
  53. Name string
  54. GitBranch string
  55. GitRepoName string
  56. PorterYamlPath string
  57. GitRepoID uint
  58. PorterAppRepository repository.PorterAppRepository
  59. }
  60. // CreateDockerRegistryAppInput is the input for creating an app with a docker registry source
  61. type CreateDockerRegistryAppInput struct {
  62. ProjectID uint
  63. ClusterID uint
  64. Name string
  65. Repository string
  66. Tag string
  67. PorterAppRepository repository.PorterAppRepository
  68. }
  69. // CreateLocalAppInput is the input for creating an app that is built locally via the cli
  70. type CreateLocalAppInput struct {
  71. ProjectID uint
  72. ClusterID uint
  73. Name string
  74. PorterAppRepository repository.PorterAppRepository
  75. }
  76. func (c *CreateAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  77. ctx, span := telemetry.NewSpan(r.Context(), "serve-create-app")
  78. defer span.End()
  79. project, _ := ctx.Value(types.ProjectScope).(*models.Project)
  80. cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
  81. if !project.GetFeatureFlag(models.ValidateApplyV2, c.Config().LaunchDarklyClient) {
  82. err := telemetry.Error(ctx, span, nil, "project does not have validate apply v2 enabled")
  83. c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
  84. return
  85. }
  86. request := &CreateAppRequest{}
  87. if ok := c.DecodeAndValidate(w, r, request); !ok {
  88. err := telemetry.Error(ctx, span, nil, "error decoding request")
  89. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  90. return
  91. }
  92. if request.Name == "" {
  93. err := telemetry.Error(ctx, span, nil, "name is required")
  94. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  95. return
  96. }
  97. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-name", Value: request.Name})
  98. porterApp, err := porter_app.CreateOrGetAppRecord(ctx, porter_app.CreateOrGetAppRecordInput{
  99. ClusterID: cluster.ID,
  100. ProjectID: project.ID,
  101. Name: request.Name,
  102. SourceType: request.SourceType,
  103. GitBranch: request.GitBranch,
  104. GitRepoName: request.GitRepoName,
  105. GitRepoID: request.GitRepoID,
  106. PorterYamlPath: request.PorterYamlPath,
  107. Image: request.Image,
  108. PorterAppRepository: c.Repo().PorterApp(),
  109. })
  110. if err != nil {
  111. err := telemetry.Error(ctx, span, err, "error creating porter app")
  112. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  113. return
  114. }
  115. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-id", Value: porterApp.ID})
  116. telemetry.WithAttributes(span,
  117. telemetry.AttributeKV{Key: "deployment-target-name", Value: request.DeploymentTargetName},
  118. telemetry.AttributeKV{Key: "deployment-target-id", Value: request.DeploymentTargetID},
  119. )
  120. if request.DeploymentTargetName != "" || request.DeploymentTargetID != "" {
  121. createAppInstanceReq := connect.NewRequest(&porterv1.CreateAppInstanceRequest{
  122. ProjectId: int64(project.ID),
  123. AppName: request.Name,
  124. DeploymentTargetIdentifier: &porterv1.DeploymentTargetIdentifier{
  125. Id: request.DeploymentTargetID,
  126. Name: request.DeploymentTargetName,
  127. },
  128. PorterAppId: int64(porterApp.ID),
  129. })
  130. createAppInstanceResp, err := c.Config().ClusterControlPlaneClient.CreateAppInstance(ctx, createAppInstanceReq)
  131. if err != nil {
  132. // ignore error until app instances are fully supported: POR-2056
  133. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "create-app-instance-error", Value: err.Error()})
  134. }
  135. if createAppInstanceResp == nil || createAppInstanceResp.Msg == nil {
  136. // ignore error until app instances are fully supported: POR-2056
  137. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "create-app-instance-nil", Value: true})
  138. } else {
  139. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-instance-id", Value: createAppInstanceResp.Msg.AppInstanceId})
  140. }
  141. }
  142. c.WriteResult(w, r, porterApp)
  143. }