preflight.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. package api_contract
  2. import (
  3. "net/http"
  4. "connectrpc.com/connect"
  5. "github.com/porter-dev/api-contracts/generated/go/helpers"
  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/internal/telemetry"
  12. )
  13. // PreflightCheckHandler runs preflight checks on a cluster contract
  14. type PreflightCheckHandler struct {
  15. handlers.PorterHandlerReadWriter
  16. }
  17. // NewPreflightCheckHandler returns a new PreflightCheckHandler
  18. func NewPreflightCheckHandler(
  19. config *config.Config,
  20. decoderValidator shared.RequestDecoderValidator,
  21. writer shared.ResultWriter,
  22. ) *PreflightCheckHandler {
  23. return &PreflightCheckHandler{
  24. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  25. }
  26. }
  27. // PorterError is the error response for the preflight check endpoint
  28. type PorterError struct {
  29. Code string `json:"code"`
  30. Message string `json:"message"`
  31. Metadata map[string]string `json:"metadata,omitempty"`
  32. }
  33. // PreflightCheckError is the error response for the preflight check endpoint
  34. type PreflightCheckError struct {
  35. Name string `json:"name"`
  36. Error PorterError `json:"error"`
  37. }
  38. // PreflightCheckResponse is the response to the preflight check endpoint
  39. type PreflightCheckResponse struct {
  40. Errors []PreflightCheckError `json:"errors"`
  41. }
  42. var recognizedPreflightCheckTypes = []string{
  43. "eip",
  44. "vcpu",
  45. "vpc",
  46. "natGateway",
  47. "apiEnabled",
  48. "cidrAvailability",
  49. "iamPermissions",
  50. "authz",
  51. }
  52. func (p *PreflightCheckHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  53. ctx, span := telemetry.NewSpan(r.Context(), "serve-preflight-checks")
  54. defer span.End()
  55. var apiContract porterv1.Contract
  56. err := helpers.UnmarshalContractObjectFromReader(r.Body, &apiContract)
  57. if err != nil {
  58. e := telemetry.Error(ctx, span, err, "error parsing api contract")
  59. p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(e, http.StatusBadRequest))
  60. return
  61. }
  62. var resp PreflightCheckResponse
  63. req := porterv1.CloudContractPreflightCheckRequest{
  64. Contract: &apiContract,
  65. }
  66. checkResp, err := p.Config().ClusterControlPlaneClient.CloudContractPreflightCheck(ctx, connect.NewRequest(&req))
  67. if err != nil {
  68. err = telemetry.Error(ctx, span, err, "error calling preflight checks")
  69. p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  70. return
  71. }
  72. if checkResp.Msg == nil {
  73. err = telemetry.Error(ctx, span, nil, "no message received from preflight checks")
  74. p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  75. return
  76. }
  77. errors := []PreflightCheckError{}
  78. for _, check := range checkResp.Msg.FailingPreflightChecks {
  79. if check.Message == "" || !contains(recognizedPreflightCheckTypes, check.Type) {
  80. continue
  81. }
  82. errors = append(errors, PreflightCheckError{
  83. Name: check.Type,
  84. Error: PorterError{
  85. Message: check.Message,
  86. Metadata: check.Metadata,
  87. },
  88. })
  89. }
  90. resp.Errors = errors
  91. p.WriteResult(w, r, resp)
  92. }
  93. func contains(slice []string, elem string) bool {
  94. for _, item := range slice {
  95. if item == elem {
  96. return true
  97. }
  98. }
  99. return false
  100. }