create_cluster.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package project
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "github.com/nats-io/nats.go"
  7. porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
  8. "github.com/porter-dev/porter/api/server/handlers"
  9. "github.com/porter-dev/porter/api/server/shared"
  10. "github.com/porter-dev/porter/api/server/shared/apierrors"
  11. "github.com/porter-dev/porter/api/server/shared/config"
  12. "github.com/porter-dev/porter/api/types"
  13. "github.com/porter-dev/porter/internal/models"
  14. "google.golang.org/protobuf/proto"
  15. )
  16. type CreateClusterHandler struct {
  17. handlers.PorterHandlerReadWriter
  18. }
  19. func NewProvisionClusterHandler(
  20. config *config.Config,
  21. decoderValidator shared.RequestDecoderValidator,
  22. writer shared.ResultWriter,
  23. ) *CreateClusterHandler {
  24. return &CreateClusterHandler{
  25. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  26. }
  27. }
  28. // ServeHTTP creates a CAPI cluster by adding the configuration to a NATS stream
  29. // This inserts a row into the cluster table in order to create an ID for this cluster, as well as stores the raw request JSON for updating later
  30. func (c *CreateClusterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  31. var capiClusterReq types.CAPIClusterRequest
  32. ctx := r.Context()
  33. if ok := c.DecodeAndValidate(w, r, &capiClusterReq); !ok {
  34. return
  35. }
  36. if capiClusterReq.ClusterID == 0 {
  37. dbCluster := models.Cluster{
  38. ProjectID: uint(capiClusterReq.ProjectID),
  39. Status: types.UpdatingUnavailable,
  40. ProvisionedBy: "CAPI",
  41. CloudProvider: "AWS",
  42. CloudProviderCredentialIdentifier: capiClusterReq.CloudProviderCredentialsID,
  43. }
  44. cl, err := c.Config().Repo.Cluster().CreateCluster(&dbCluster)
  45. if err != nil {
  46. e := fmt.Errorf("error creating new cluster: %w", err)
  47. c.HandleAPIError(w, r, apierrors.NewErrInternal(e))
  48. return
  49. }
  50. capiClusterReq.ClusterID = int64(cl.ID)
  51. }
  52. by, err := json.Marshal(capiClusterReq)
  53. if err != nil {
  54. e := fmt.Errorf("error marshalling capi config: %w", err)
  55. c.HandleAPIError(w, r, apierrors.NewErrInternal(e))
  56. return
  57. }
  58. capiConfig := models.CAPIConfig{
  59. ClusterID: int(capiClusterReq.ClusterID),
  60. ProjectID: int(capiClusterReq.ProjectID),
  61. Base64RequestJSON: string(by),
  62. }
  63. _, err = c.Config().Repo.CAPIConfigRepository().Insert(ctx, capiConfig)
  64. if err != nil {
  65. e := fmt.Errorf("error creating new capi config: %w", err)
  66. c.HandleAPIError(w, r, apierrors.NewErrInternal(e))
  67. return
  68. }
  69. capiCluster := porterv1.Kubernetes{
  70. ProjectId: int32(capiClusterReq.ProjectID),
  71. ClusterId: int32(capiClusterReq.ClusterID),
  72. }
  73. if capiClusterReq.CloudProvider == "aws" {
  74. capiCluster.CloudProvider = porterv1.EnumCloudProvider_ENUM_CLOUD_PROVIDER_AWS
  75. capiCluster.Kind = porterv1.EnumKubernetesKind_ENUM_KUBERNETES_KIND_EKS
  76. capiCluster.CloudProviderCredentialsId = capiClusterReq.CloudProviderCredentialsID
  77. var capiNodeGroups []*porterv1.EKSNodeGroup
  78. for _, ng := range capiClusterReq.ClusterSettings.NodeGroups {
  79. cng := porterv1.EKSNodeGroup{
  80. InstanceType: ng.InstanceType,
  81. MinInstances: uint32(ng.MinInstances),
  82. MaxInstances: uint32(ng.MaxInstances),
  83. NodeGroupType: protoNodeGroupTypeLookup(ng.NodeGroupType),
  84. }
  85. capiNodeGroups = append(capiNodeGroups, &cng)
  86. }
  87. capiCluster.KindValues = &porterv1.Kubernetes_EksKind{
  88. EksKind: &porterv1.EKS{
  89. ClusterName: capiClusterReq.ClusterSettings.ClusterName,
  90. CidrRange: capiClusterReq.ClusterSettings.CIDRRange,
  91. ClusterVersion: capiClusterReq.ClusterSettings.ClusterVersion,
  92. Region: capiClusterReq.ClusterSettings.Region,
  93. NodeGroups: capiNodeGroups,
  94. },
  95. }
  96. }
  97. // This gates the cluster actually being provisioned by CAPI
  98. // This can be removed whenever we are able to run NATS and CCP locally, easier
  99. if !c.Config().DisableCAPIProvisioner {
  100. kubeBy, err := proto.Marshal(&capiCluster)
  101. if err != nil {
  102. e := fmt.Errorf("error marshalling proto: %w", err)
  103. c.HandleAPIError(w, r, apierrors.NewErrInternal(e))
  104. return
  105. }
  106. subject := "porter.system.infrastructure.update"
  107. _, err = c.Config().NATS.JetStream.Publish(subject, kubeBy, nats.Context(ctx))
  108. if err != nil {
  109. e := fmt.Errorf("error publishing cluster for creation: %w", err)
  110. c.HandleAPIError(w, r, apierrors.NewErrInternal(e))
  111. return
  112. }
  113. }
  114. w.WriteHeader(http.StatusCreated)
  115. c.WriteResult(w, r, types.Cluster{
  116. ID: uint(capiClusterReq.ClusterID),
  117. })
  118. }
  119. var (
  120. apiNodeGroupToProtoNodeGroup = map[string]porterv1.NodeGroupType{
  121. "SYSTEM": porterv1.NodeGroupType_NODE_GROUP_TYPE_SYSTEM,
  122. "MONITORING": porterv1.NodeGroupType_NODE_GROUP_TYPE_MONITORING,
  123. "APPLICATION": porterv1.NodeGroupType_NODE_GROUP_TYPE_APPLICATION,
  124. "CUSTOM": porterv1.NodeGroupType_NODE_GROUP_TYPE_CUSTOM,
  125. }
  126. )
  127. // protoNodeGroupTypeLookup is a helper function for finding a nodegroup, and returning a default if its not found
  128. func protoNodeGroupTypeLookup(apiNodeGroup string) porterv1.NodeGroupType {
  129. if ngt, ok := apiNodeGroupToProtoNodeGroup[apiNodeGroup]; ok {
  130. return ngt
  131. }
  132. return porterv1.NodeGroupType_NODE_GROUP_TYPE_CUSTOM
  133. }