create.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. package infra
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "github.com/mitchellh/mapstructure"
  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/encryption"
  14. "github.com/porter-dev/porter/internal/models"
  15. "gorm.io/gorm"
  16. ptypes "github.com/porter-dev/porter/provisioner/types"
  17. )
  18. type InfraCreateHandler struct {
  19. handlers.PorterHandlerReadWriter
  20. }
  21. func NewInfraCreateHandler(
  22. config *config.Config,
  23. decoderValidator shared.RequestDecoderValidator,
  24. writer shared.ResultWriter,
  25. ) *InfraCreateHandler {
  26. return &InfraCreateHandler{
  27. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  28. }
  29. }
  30. func (c *InfraCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  31. user, _ := r.Context().Value(types.UserScope).(*models.User)
  32. proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
  33. req := &types.CreateInfraRequest{}
  34. if ok := c.DecodeAndValidate(w, r, req); !ok {
  35. return
  36. }
  37. var cluster *models.Cluster
  38. var err error
  39. if req.ClusterID != 0 {
  40. cluster, err = c.Repo().Cluster().ReadCluster(proj.ID, req.ClusterID)
  41. if err != nil {
  42. if err == gorm.ErrRecordNotFound {
  43. c.HandleAPIError(w, r, apierrors.NewErrForbidden(
  44. fmt.Errorf("cluster with id %d not found in project %d", req.ClusterID, proj.ID),
  45. ))
  46. } else {
  47. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  48. }
  49. return
  50. }
  51. }
  52. suffix, err := encryption.GenerateRandomBytes(6)
  53. if err != nil {
  54. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  55. return
  56. }
  57. sourceLink, sourceVersion := getSourceLinkAndVersion(types.InfraKind(req.Kind))
  58. // create the infra object
  59. infra := &models.Infra{
  60. Kind: types.InfraKind(req.Kind),
  61. APIVersion: "v2",
  62. ProjectID: proj.ID,
  63. Suffix: suffix,
  64. Status: types.StatusCreating,
  65. CreatedByUserID: user.ID,
  66. SourceLink: sourceLink,
  67. SourceVersion: sourceVersion,
  68. ParentClusterID: req.ClusterID,
  69. }
  70. // verify the credentials
  71. err = checkInfraCredentials(c.Config(), proj, infra, req.InfraCredentials)
  72. if err != nil {
  73. c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
  74. return
  75. }
  76. // call apply on the provisioner service
  77. vals := req.Values
  78. // if this is cluster-scoped and the kind is RDS, run the postrenderer
  79. if req.ClusterID != 0 && req.Kind == "rds" {
  80. var ok bool
  81. pr := &InfraRDSPostrenderer{
  82. config: c.Config(),
  83. }
  84. if vals, ok = pr.Run(w, r, &Opts{
  85. Cluster: cluster,
  86. Values: req.Values,
  87. }); !ok {
  88. return
  89. }
  90. }
  91. // handle write to the database
  92. infra, err = c.Repo().Infra().CreateInfra(infra)
  93. if err != nil {
  94. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  95. return
  96. }
  97. resp, err := c.Config().ProvisionerClient.Apply(context.Background(), proj.ID, infra.ID, &ptypes.ApplyBaseRequest{
  98. Kind: req.Kind,
  99. Values: vals,
  100. OperationKind: "create",
  101. })
  102. if err != nil {
  103. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  104. return
  105. }
  106. c.WriteResult(w, r, resp)
  107. }
  108. func checkInfraCredentials(config *config.Config, proj *models.Project, infra *models.Infra, req *types.InfraCredentials) error {
  109. if req == nil {
  110. return nil
  111. }
  112. if req.DOIntegrationID != 0 {
  113. _, err := config.Repo.OAuthIntegration().ReadOAuthIntegration(proj.ID, req.DOIntegrationID)
  114. if err != nil {
  115. return fmt.Errorf("do integration id %d not found in project %d", req.DOIntegrationID, proj.ID)
  116. }
  117. infra.DOIntegrationID = req.DOIntegrationID
  118. infra.AWSIntegrationID = 0
  119. infra.GCPIntegrationID = 0
  120. infra.AzureIntegrationID = 0
  121. } else if req.AWSIntegrationID != 0 {
  122. _, err := config.Repo.AWSIntegration().ReadAWSIntegration(proj.ID, req.AWSIntegrationID)
  123. if err != nil {
  124. return fmt.Errorf("aws integration id %d not found in project %d", req.AWSIntegrationID, proj.ID)
  125. }
  126. infra.DOIntegrationID = 0
  127. infra.AWSIntegrationID = req.AWSIntegrationID
  128. infra.GCPIntegrationID = 0
  129. infra.AzureIntegrationID = 0
  130. } else if req.GCPIntegrationID != 0 {
  131. _, err := config.Repo.GCPIntegration().ReadGCPIntegration(proj.ID, req.GCPIntegrationID)
  132. if err != nil {
  133. return fmt.Errorf("gcp integration id %d not found in project %d", req.GCPIntegrationID, proj.ID)
  134. }
  135. infra.DOIntegrationID = 0
  136. infra.AWSIntegrationID = 0
  137. infra.GCPIntegrationID = req.GCPIntegrationID
  138. infra.AzureIntegrationID = 0
  139. } else if req.AzureIntegrationID != 0 {
  140. _, err := config.Repo.AzureIntegration().ReadAzureIntegration(proj.ID, req.AzureIntegrationID)
  141. if err != nil {
  142. return fmt.Errorf("azure integration id %d not found in project %d", req.AzureIntegrationID, proj.ID)
  143. }
  144. infra.DOIntegrationID = 0
  145. infra.AWSIntegrationID = 0
  146. infra.GCPIntegrationID = 0
  147. infra.AzureIntegrationID = req.AzureIntegrationID
  148. }
  149. if infra.DOIntegrationID == 0 && infra.AWSIntegrationID == 0 && infra.GCPIntegrationID == 0 && infra.AzureIntegrationID == 0 {
  150. return fmt.Errorf("at least one integration id must be set")
  151. }
  152. return nil
  153. }
  154. // getSourceLinkAndVersion returns the source link and version for the infrastructure. For now,
  155. // this is hardcoded
  156. func getSourceLinkAndVersion(kind types.InfraKind) (string, string) {
  157. switch kind {
  158. case types.InfraECR:
  159. return "porter/aws/ecr", "v0.1.0"
  160. case types.InfraEKS:
  161. return "porter/aws/eks", "v0.1.0"
  162. case types.InfraRDS:
  163. return "porter/aws/rds", "v0.1.0"
  164. case types.InfraGCR:
  165. return "porter/gcp/gcr", "v0.1.0"
  166. case types.InfraGKE:
  167. return "porter/gcp/gke", "v0.1.0"
  168. case types.InfraDOCR:
  169. return "porter/do/docr", "v0.1.0"
  170. case types.InfraDOKS:
  171. return "porter/do/doks", "v0.1.0"
  172. case types.InfraAKS:
  173. return "porter/azure/aks", "v0.1.0"
  174. case types.InfraACR:
  175. return "porter/azure/acr", "v0.1.0"
  176. }
  177. return "porter/test", "v0.1.0"
  178. }
  179. type InfraRDSPostrenderer struct {
  180. config *config.Config
  181. }
  182. type Opts struct {
  183. Cluster *models.Cluster
  184. Values map[string]interface{}
  185. }
  186. func (i *InfraRDSPostrenderer) Run(w http.ResponseWriter, r *http.Request, opts *Opts) (map[string]interface{}, bool) {
  187. if opts.Cluster != nil {
  188. proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
  189. values := opts.Values
  190. // find the corresponding infra id
  191. clusterInfra, err := i.config.Repo.Infra().ReadInfra(proj.ID, opts.Cluster.InfraID)
  192. if err != nil {
  193. apierrors.HandleAPIError(i.config.Logger, i.config.Alerter, w, r, apierrors.NewErrForbidden(fmt.Errorf("could not get cluster infra: %v", err)), true)
  194. return nil, false
  195. }
  196. clusterInfraOperation, err := i.config.Repo.Infra().GetLatestOperation(clusterInfra)
  197. // get the raw state for the cluster
  198. rawState, err := i.config.ProvisionerClient.GetRawState(context.Background(), models.GetWorkspaceID(clusterInfra, clusterInfraOperation))
  199. if err != nil {
  200. apierrors.HandleAPIError(i.config.Logger, i.config.Alerter, w, r, apierrors.NewErrInternal(err), true)
  201. return nil, false
  202. }
  203. vpcID, subnetIDs, err := getVPCFromEKSTFState(rawState)
  204. if err != nil {
  205. return values, false
  206. }
  207. // if the length of the subnets is not 3, return an error
  208. if len(subnetIDs) < 3 {
  209. apierrors.HandleAPIError(i.config.Logger, i.config.Alerter, w, r, apierrors.NewErrInternal(fmt.Errorf("invalid number of subnet IDs in VPC configuration")), true)
  210. return nil, false
  211. }
  212. values["porter_cluster_vpc"] = vpcID
  213. values["porter_cluster_subnet_1"] = subnetIDs[0]
  214. values["porter_cluster_subnet_2"] = subnetIDs[1]
  215. values["porter_cluster_subnet_3"] = subnetIDs[2]
  216. return values, true
  217. }
  218. return opts.Values, true
  219. }
  220. type AWSVPCConfig struct {
  221. SubNetIDs []string `json:"subnet_ids" mapstructure:"subnet_ids"`
  222. VPCID string `json:"vpc_id" mapstructure:"vpc_id"`
  223. }
  224. func getVPCFromEKSTFState(tfState *ptypes.ParseableRawTFState) (string, []string, error) {
  225. for _, resource := range tfState.Resources {
  226. if "aws_eks_cluster.cluster" == resource.Type+"."+resource.Name {
  227. for _, instance := range resource.Instances {
  228. vpcConfig, ok := instance.Attributes["vpc_config"]
  229. if !ok {
  230. return "", []string{}, errors.New("name not found for the requested resource name-type")
  231. }
  232. awsVPCConfigIface, ok := vpcConfig.([]interface{})
  233. if !ok {
  234. fmt.Printf("%#v\n", vpcConfig)
  235. return "", []string{}, errors.New("cannot cast returned value to vpc config")
  236. }
  237. if len(awsVPCConfigIface) == 0 {
  238. return "", []string{}, errors.New("empty vpc config")
  239. }
  240. awsVPCConfigMap, ok := awsVPCConfigIface[0].(map[string]interface{})
  241. if !ok {
  242. return "", []string{}, errors.New("cannot cast returned value to vpc config map")
  243. }
  244. var awsVPCConfig AWSVPCConfig
  245. err := mapstructure.Decode(awsVPCConfigMap, &awsVPCConfig)
  246. if err != nil {
  247. return "", []string{}, errors.New("cannot cast returned value to vpc config")
  248. }
  249. return awsVPCConfig.VPCID, awsVPCConfig.SubNetIDs, nil
  250. }
  251. return "", []string{}, errors.New("name not found for the requested resource name-type")
  252. }
  253. }
  254. return "", []string{}, errors.New("name not found for the requested resource name-type")
  255. }