get_build.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. package porter_app
  2. import (
  3. "encoding/base64"
  4. "net/http"
  5. "github.com/google/uuid"
  6. "github.com/porter-dev/api-contracts/generated/go/helpers"
  7. porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
  8. "github.com/porter-dev/porter/api/server/authz"
  9. "github.com/porter-dev/porter/api/server/handlers"
  10. "github.com/porter-dev/porter/api/server/shared"
  11. "github.com/porter-dev/porter/api/server/shared/apierrors"
  12. "github.com/porter-dev/porter/api/server/shared/config"
  13. "github.com/porter-dev/porter/api/server/shared/requestutils"
  14. "github.com/porter-dev/porter/api/types"
  15. "github.com/porter-dev/porter/internal/deployment_target"
  16. "github.com/porter-dev/porter/internal/models"
  17. "github.com/porter-dev/porter/internal/porter_app"
  18. "github.com/porter-dev/porter/internal/telemetry"
  19. )
  20. // GetBuildFromRevisionHandler is the handler for the /apps/{porter_app_name}/revisions/{app_revision_id}/build endpoint
  21. type GetBuildFromRevisionHandler struct {
  22. handlers.PorterHandlerReadWriter
  23. authz.KubernetesAgentGetter
  24. }
  25. // NewGetBuildFromRevisionHandler handles GET requests to the /apps/{porter_app_name}/revisions/{app_revision_id}/build endpoint
  26. func NewGetBuildFromRevisionHandler(
  27. config *config.Config,
  28. decoderValidator shared.RequestDecoderValidator,
  29. writer shared.ResultWriter,
  30. ) *GetBuildFromRevisionHandler {
  31. return &GetBuildFromRevisionHandler{
  32. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  33. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  34. }
  35. }
  36. // Image is the image used by an app with a docker registry source
  37. type Image struct {
  38. Repository string `json:"repository"`
  39. Tag string `json:"tag"`
  40. }
  41. // BuildSettings is the set of fields for a revision's build
  42. type BuildSettings struct {
  43. Method string `json:"method"`
  44. Context string `json:"context"`
  45. Builder string `json:"builder"`
  46. Buildpacks []string `json:"buildpacks"`
  47. Dockerfile string `json:"dockerfile"`
  48. CommitSHA string `json:"commit_sha"`
  49. }
  50. // GetBuildFromRevisionResponse is the response object for the /apps/{porter_app_name}/revisions/{app_revision_id}/build endpoint
  51. type GetBuildFromRevisionResponse struct {
  52. BuildEnvVariables map[string]string `json:"build_env_variables"`
  53. Build BuildSettings `json:"build"`
  54. Image Image `json:"image"`
  55. }
  56. // ServeHTTP translates the request into a GetBuildFromRevisionRequest request, uses the proto to query the revision for the build settings, and returns the response
  57. func (c *GetBuildFromRevisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  58. ctx, span := telemetry.NewSpan(r.Context(), "serve-get-build")
  59. defer span.End()
  60. project, _ := ctx.Value(types.ProjectScope).(*models.Project)
  61. cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
  62. telemetry.WithAttributes(span,
  63. telemetry.AttributeKV{Key: "project-id", Value: project.ID},
  64. telemetry.AttributeKV{Key: "cluster-id", Value: cluster.ID},
  65. )
  66. if !project.GetFeatureFlag(models.ValidateApplyV2, c.Config().LaunchDarklyClient) {
  67. err := telemetry.Error(ctx, span, nil, "project does not have validate apply v2 enabled")
  68. c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
  69. return
  70. }
  71. revisionID, reqErr := requestutils.GetURLParamString(r, types.URLParamAppRevisionID)
  72. if reqErr != nil {
  73. err := telemetry.Error(ctx, span, nil, "error parsing app revision id")
  74. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  75. return
  76. }
  77. appRevisionUuid, err := uuid.Parse(revisionID)
  78. if err != nil {
  79. err := telemetry.Error(ctx, span, err, "error parsing app revision id")
  80. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  81. return
  82. }
  83. if appRevisionUuid == uuid.Nil {
  84. err := telemetry.Error(ctx, span, nil, "app revision id is nil")
  85. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  86. return
  87. }
  88. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-revision-id", Value: appRevisionUuid.String()})
  89. revision, err := porter_app.GetAppRevision(ctx, porter_app.GetAppRevisionInput{
  90. AppRevisionID: appRevisionUuid,
  91. ProjectID: project.ID,
  92. CCPClient: c.Config().ClusterControlPlaneClient,
  93. })
  94. if err != nil {
  95. err := telemetry.Error(ctx, span, err, "error getting app revision")
  96. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  97. return
  98. }
  99. decoded, err := base64.StdEncoding.DecodeString(revision.B64AppProto)
  100. if err != nil {
  101. err := telemetry.Error(ctx, span, err, "error decoding base proto")
  102. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  103. return
  104. }
  105. resp := &GetBuildFromRevisionResponse{}
  106. appProto := &porterv1.PorterApp{}
  107. err = helpers.UnmarshalContractObject(decoded, appProto)
  108. if err != nil {
  109. err := telemetry.Error(ctx, span, err, "error unmarshalling app proto")
  110. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  111. return
  112. }
  113. if appProto.Image == nil {
  114. err := telemetry.Error(ctx, span, nil, "app proto does not have image settings. Tag is unknown")
  115. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  116. return
  117. }
  118. resp.Image = Image{
  119. Repository: appProto.Image.Repository,
  120. Tag: appProto.Image.Tag,
  121. }
  122. if appProto.Build == nil {
  123. c.WriteResult(w, r, resp)
  124. return
  125. }
  126. resp.Build = BuildSettings{
  127. Method: appProto.Build.Method,
  128. Context: appProto.Build.Context,
  129. Builder: appProto.Build.Builder,
  130. Buildpacks: appProto.Build.Buildpacks,
  131. Dockerfile: appProto.Build.Dockerfile,
  132. CommitSHA: appProto.Build.CommitSha,
  133. }
  134. agent, err := c.GetAgent(r, cluster, "")
  135. if err != nil {
  136. err := telemetry.Error(ctx, span, err, "error getting agent")
  137. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  138. return
  139. }
  140. deploymentTarget, err := deployment_target.DeploymentTargetDetails(ctx, deployment_target.DeploymentTargetDetailsInput{
  141. ProjectID: int64(project.ID),
  142. ClusterID: int64(cluster.ID),
  143. DeploymentTargetID: revision.DeploymentTarget.ID,
  144. CCPClient: c.Config().ClusterControlPlaneClient,
  145. })
  146. if err != nil {
  147. err := telemetry.Error(ctx, span, err, "error getting deployment target details")
  148. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  149. return
  150. }
  151. envFromProtoInp := porter_app.AppEnvironmentFromProtoInput{
  152. ProjectID: project.ID,
  153. ClusterID: int(cluster.ID),
  154. DeploymentTarget: deploymentTarget,
  155. App: appProto,
  156. K8SAgent: agent,
  157. }
  158. envGroups, err := porter_app.AppEnvironmentFromProto(ctx, envFromProtoInp)
  159. if err != nil {
  160. err := telemetry.Error(ctx, span, err, "error getting app environment from revision")
  161. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  162. return
  163. }
  164. buildEnvVariables := make(map[string]string)
  165. for _, envGroup := range envGroups {
  166. for key, val := range envGroup.Variables {
  167. buildEnvVariables[key] = val
  168. }
  169. }
  170. resp.BuildEnvVariables = buildEnvVariables
  171. c.WriteResult(w, r, resp)
  172. }