get_build.go 8.6 KB

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