get.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. package datastore
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "net/http"
  6. "connectrpc.com/connect"
  7. "github.com/aws/aws-sdk-go/aws/arn"
  8. "github.com/google/uuid"
  9. "github.com/porter-dev/api-contracts/generated/go/helpers"
  10. porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
  11. "github.com/porter-dev/porter/api/server/authz"
  12. "github.com/porter-dev/porter/api/server/handlers"
  13. "github.com/porter-dev/porter/api/server/handlers/cloud_provider"
  14. "github.com/porter-dev/porter/api/server/shared"
  15. "github.com/porter-dev/porter/api/server/shared/apierrors"
  16. "github.com/porter-dev/porter/api/server/shared/config"
  17. "github.com/porter-dev/porter/api/server/shared/requestutils"
  18. "github.com/porter-dev/porter/api/types"
  19. "github.com/porter-dev/porter/internal/datastore"
  20. "github.com/porter-dev/porter/internal/models"
  21. "github.com/porter-dev/porter/internal/telemetry"
  22. )
  23. // GetDatastoreResponse describes the list datastores response body
  24. type GetDatastoreResponse struct {
  25. // Datastore is the datastore that has been retrieved
  26. Datastore datastore.Datastore `json:"datastore"`
  27. }
  28. // GetDatastoreHandler is a struct for retrieving a datastore
  29. type GetDatastoreHandler struct {
  30. handlers.PorterHandlerReadWriter
  31. authz.KubernetesAgentGetter
  32. }
  33. // NewGetDatastoreHandler returns a GetDatastoreHandler
  34. func NewGetDatastoreHandler(
  35. config *config.Config,
  36. decoderValidator shared.RequestDecoderValidator,
  37. writer shared.ResultWriter,
  38. ) *GetDatastoreHandler {
  39. return &GetDatastoreHandler{
  40. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  41. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  42. }
  43. }
  44. const (
  45. // SupportedDatastoreCloudProvider_AWS is the AWS cloud provider
  46. SupportedDatastoreCloudProvider_AWS string = "AWS"
  47. )
  48. // ServeHTTP retrieves the datastore in the given project
  49. func (c *GetDatastoreHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  50. ctx, span := telemetry.NewSpan(r.Context(), "serve-get-datastore")
  51. defer span.End()
  52. project, _ := ctx.Value(types.ProjectScope).(*models.Project)
  53. resp := GetDatastoreResponse{}
  54. datastoreName, reqErr := requestutils.GetURLParamString(r, types.URLParamDatastoreName)
  55. if reqErr != nil {
  56. err := telemetry.Error(ctx, span, nil, "error parsing datastore name")
  57. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  58. return
  59. }
  60. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "datastore-name", Value: datastoreName})
  61. datastoreRecord, err := c.Repo().Datastore().GetByProjectIDAndName(ctx, project.ID, datastoreName)
  62. if err != nil {
  63. err = telemetry.Error(ctx, span, err, "datastore record not found")
  64. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  65. return
  66. }
  67. if datastoreRecord == nil || datastoreRecord.ID == uuid.Nil {
  68. err = telemetry.Error(ctx, span, nil, "datastore record does not exist")
  69. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusNotFound))
  70. return
  71. }
  72. // TODO: delete this branch once all datastores are on the management cluster
  73. if !datastoreRecord.OnManagementCluster {
  74. awsArn, err := arn.Parse(datastoreRecord.CloudProviderCredentialIdentifier)
  75. if err != nil {
  76. err = telemetry.Error(ctx, span, err, "error parsing aws account id")
  77. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  78. return
  79. }
  80. datastore, err := c.LEGACY_handleGetDatastore(ctx, project.ID, awsArn.AccountID, datastoreName)
  81. if err != nil {
  82. err = telemetry.Error(ctx, span, err, "error retrieving datastore")
  83. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  84. return
  85. }
  86. resp.Datastore = datastore
  87. c.WriteResult(w, r, resp)
  88. return
  89. }
  90. req := connect.NewRequest(&porterv1.ReadCloudContractRequest{
  91. ProjectId: int64(project.ID),
  92. })
  93. ccpResp, err := c.Config().ClusterControlPlaneClient.ReadCloudContract(ctx, req)
  94. if err != nil {
  95. err = telemetry.Error(ctx, span, err, "error getting cloud contract")
  96. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  97. return
  98. }
  99. if ccpResp.Msg == nil {
  100. err = telemetry.Error(ctx, span, nil, "cloud contract not found")
  101. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusNotFound))
  102. return
  103. }
  104. cloudContract := ccpResp.Msg.CloudContract
  105. if cloudContract == nil {
  106. err = telemetry.Error(ctx, span, nil, "cloud contract is empty")
  107. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusNotFound))
  108. return
  109. }
  110. datastores := cloudContract.Datastores
  111. if datastores == nil {
  112. err = telemetry.Error(ctx, span, nil, "datastores is empty")
  113. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusNotFound))
  114. return
  115. }
  116. var matchingDatastore *porterv1.ManagedDatastore
  117. for _, ds := range datastores {
  118. if ds.Id == datastoreRecord.ID.String() {
  119. matchingDatastore = ds
  120. break
  121. }
  122. }
  123. if matchingDatastore == nil {
  124. err = telemetry.Error(ctx, span, nil, "datastore not found")
  125. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusNotFound))
  126. return
  127. }
  128. encoded, err := helpers.MarshalContractObject(ctx, matchingDatastore)
  129. if err != nil {
  130. err = telemetry.Error(ctx, span, err, "error marshaling datastore")
  131. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  132. return
  133. }
  134. b64 := base64.StdEncoding.EncodeToString(encoded)
  135. datastore := datastore.Datastore{
  136. Name: datastoreRecord.Name,
  137. Type: datastoreRecord.Type,
  138. Engine: datastoreRecord.Engine,
  139. CreatedAtUTC: datastoreRecord.CreatedAt,
  140. Status: string(datastoreRecord.Status),
  141. CloudProvider: SupportedDatastoreCloudProvider_AWS,
  142. CloudProviderCredentialIdentifier: datastoreRecord.CloudProviderCredentialIdentifier,
  143. B64Proto: b64,
  144. }
  145. resp.Datastore = datastore
  146. c.WriteResult(w, r, resp)
  147. }
  148. // LEGACY_handleGetDatastore retrieves the datastore in the given project for datastores that are on the customer clusters rather than the management cluster
  149. func (c *GetDatastoreHandler) LEGACY_handleGetDatastore(ctx context.Context, projectId uint, accountId string, datastoreName string) (datastore.Datastore, error) {
  150. ctx, span := telemetry.NewSpan(ctx, "legacy-handle-get-datastore")
  151. defer span.End()
  152. var datastore datastore.Datastore
  153. datastores, err := Datastores(ctx, DatastoresInput{
  154. ProjectID: projectId,
  155. CloudProvider: cloud_provider.CloudProvider{
  156. AccountID: accountId,
  157. Type: porterv1.EnumCloudProvider_ENUM_CLOUD_PROVIDER_AWS,
  158. },
  159. Name: datastoreName,
  160. IncludeEnvGroup: true,
  161. IncludeMetadata: true,
  162. CCPClient: c.Config().ClusterControlPlaneClient,
  163. DatastoreRepository: c.Repo().Datastore(),
  164. })
  165. if err != nil {
  166. return datastore, err
  167. }
  168. if len(datastores) != 1 {
  169. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "datastore-count", Value: len(datastores)})
  170. if len(datastores) == 0 {
  171. return datastore, telemetry.Error(ctx, span, nil, "datastore not found")
  172. }
  173. return datastore, telemetry.Error(ctx, span, nil, "unexpected number of datastores found matching filters")
  174. }
  175. return datastores[0], nil
  176. }