run_command.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package porter_app
  2. import (
  3. "net/http"
  4. "strings"
  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. utils "github.com/porter-dev/porter/api/utils/porter_app"
  13. "github.com/porter-dev/porter/internal/kubernetes/porter_app"
  14. "github.com/porter-dev/porter/internal/models"
  15. "github.com/porter-dev/porter/internal/telemetry"
  16. )
  17. // RunPorterAppCommandHandler runs a command on a porter app
  18. type RunPorterAppCommandHandler struct {
  19. handlers.PorterHandlerReadWriter
  20. authz.KubernetesAgentGetter
  21. }
  22. // NewRunPorterAppCommandHandler returns a new RunPorterAppCommandHandler
  23. func NewRunPorterAppCommandHandler(
  24. config *config.Config,
  25. decoderValidator shared.RequestDecoderValidator,
  26. writer shared.ResultWriter,
  27. ) *RunPorterAppCommandHandler {
  28. return &RunPorterAppCommandHandler{
  29. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  30. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  31. }
  32. }
  33. func (c *RunPorterAppCommandHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  34. ctx := r.Context()
  35. cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
  36. ctx, span := telemetry.NewSpan(r.Context(), "serve-run-porter-app-command")
  37. defer span.End()
  38. request := &types.RunPorterAppCommandRequest{}
  39. if ok := c.DecodeAndValidate(w, r, request); !ok {
  40. err := telemetry.Error(ctx, span, nil, "error decoding request")
  41. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  42. return
  43. }
  44. appName, reqErr := requestutils.GetURLParamString(r, types.URLParamPorterAppName)
  45. if reqErr != nil {
  46. err := telemetry.Error(ctx, span, reqErr, "error getting app name from url")
  47. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  48. return
  49. }
  50. namespace := utils.NamespaceFromPorterAppName(appName)
  51. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "application-name", Value: appName})
  52. app, err := c.Repo().PorterApp().ReadPorterAppByName(cluster.ID, appName)
  53. if err != nil {
  54. err = telemetry.Error(ctx, span, err, "error reading app from DB")
  55. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  56. return
  57. }
  58. if app == nil {
  59. err = telemetry.Error(ctx, span, nil, "app with name does not exist in project")
  60. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusForbidden))
  61. return
  62. }
  63. k8sAgent, err := c.GetAgent(r, cluster, namespace)
  64. if err != nil {
  65. err = telemetry.Error(ctx, span, err, "error getting k8s agent")
  66. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  67. return
  68. }
  69. podList, err := k8sAgent.GetPodsByLabel(porter_app.LabelKey_PorterApplication, namespace)
  70. if err != nil {
  71. err = telemetry.Error(ctx, span, err, "error getting pods by label")
  72. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  73. return
  74. }
  75. if len(podList.Items) == 0 {
  76. err = telemetry.Error(ctx, span, err, "no pods found to run command on")
  77. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  78. return
  79. }
  80. selectedPod := podList.Items[0]
  81. execArgs := strings.Split(request.Command, " ")
  82. if app.Builder != "" &&
  83. (strings.Contains(app.Builder, "heroku") ||
  84. strings.Contains(app.Builder, "paketo")) &&
  85. execArgs[0] != "/cnb/lifecycle/launcher" &&
  86. execArgs[0] != "launcher" {
  87. // this is a buildpacks release using a heroku builder, so we prepend commands with launcher command
  88. execArgs = append([]string{"/cnb/lifecycle/launcher"}, execArgs...)
  89. }
  90. err = k8sAgent.RunCommandOnPod(ctx, &selectedPod, execArgs)
  91. if err != nil {
  92. err = telemetry.Error(ctx, span, err, "error running command on pod")
  93. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  94. return
  95. }
  96. }