create.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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. } else if req.AWSIntegrationID != 0 {
  121. _, err := config.Repo.AWSIntegration().ReadAWSIntegration(proj.ID, req.AWSIntegrationID)
  122. if err != nil {
  123. return fmt.Errorf("aws integration id %d not found in project %d", req.AWSIntegrationID, proj.ID)
  124. }
  125. infra.DOIntegrationID = 0
  126. infra.AWSIntegrationID = req.AWSIntegrationID
  127. infra.GCPIntegrationID = 0
  128. } else if req.GCPIntegrationID != 0 {
  129. _, err := config.Repo.GCPIntegration().ReadGCPIntegration(proj.ID, req.GCPIntegrationID)
  130. if err != nil {
  131. return fmt.Errorf("gcp integration id %d not found in project %d", req.GCPIntegrationID, proj.ID)
  132. }
  133. infra.DOIntegrationID = 0
  134. infra.AWSIntegrationID = 0
  135. infra.GCPIntegrationID = req.GCPIntegrationID
  136. }
  137. if infra.DOIntegrationID == 0 && infra.AWSIntegrationID == 0 && infra.GCPIntegrationID == 0 {
  138. return fmt.Errorf("at least one integration id must be set")
  139. }
  140. return nil
  141. }
  142. // getSourceLinkAndVersion returns the source link and version for the infrastructure. For now,
  143. // this is hardcoded
  144. func getSourceLinkAndVersion(kind types.InfraKind) (string, string) {
  145. switch kind {
  146. case types.InfraECR:
  147. return "porter/aws/ecr", "v0.1.0"
  148. case types.InfraEKS:
  149. return "porter/aws/eks", "v0.1.0"
  150. case types.InfraRDS:
  151. return "porter/aws/rds", "v0.1.0"
  152. case types.InfraGCR:
  153. return "porter/gcp/gcr", "v0.1.0"
  154. case types.InfraGKE:
  155. return "porter/gcp/gke", "v0.1.0"
  156. case types.InfraDOCR:
  157. return "porter/do/docr", "v0.1.0"
  158. case types.InfraDOKS:
  159. return "porter/do/doks", "v0.1.0"
  160. }
  161. return "porter/test", "v0.1.0"
  162. }
  163. type InfraRDSPostrenderer struct {
  164. config *config.Config
  165. }
  166. type Opts struct {
  167. Cluster *models.Cluster
  168. Values map[string]interface{}
  169. }
  170. func (i *InfraRDSPostrenderer) Run(w http.ResponseWriter, r *http.Request, opts *Opts) (map[string]interface{}, bool) {
  171. if opts.Cluster != nil {
  172. proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
  173. values := opts.Values
  174. // find the corresponding infra id
  175. clusterInfra, err := i.config.Repo.Infra().ReadInfra(proj.ID, opts.Cluster.InfraID)
  176. if err != nil {
  177. apierrors.HandleAPIError(i.config.Logger, i.config.Alerter, w, r, apierrors.NewErrForbidden(fmt.Errorf("could not get cluster infra: %v", err)), true)
  178. return nil, false
  179. }
  180. clusterInfraOperation, err := i.config.Repo.Infra().GetLatestOperation(clusterInfra)
  181. // get the raw state for the cluster
  182. rawState, err := i.config.ProvisionerClient.GetRawState(context.Background(), models.GetWorkspaceID(clusterInfra, clusterInfraOperation))
  183. if err != nil {
  184. apierrors.HandleAPIError(i.config.Logger, i.config.Alerter, w, r, apierrors.NewErrInternal(err), true)
  185. return nil, false
  186. }
  187. vpcID, subnetIDs, err := getVPCFromEKSTFState(rawState)
  188. if err != nil {
  189. return values, false
  190. }
  191. // if the length of the subnets is not 3, return an error
  192. if len(subnetIDs) < 3 {
  193. apierrors.HandleAPIError(i.config.Logger, i.config.Alerter, w, r, apierrors.NewErrInternal(fmt.Errorf("invalid number of subnet IDs in VPC configuration")), true)
  194. return nil, false
  195. }
  196. values["porter_cluster_vpc"] = vpcID
  197. values["porter_cluster_subnet_1"] = subnetIDs[0]
  198. values["porter_cluster_subnet_2"] = subnetIDs[1]
  199. values["porter_cluster_subnet_3"] = subnetIDs[2]
  200. return values, true
  201. }
  202. return opts.Values, true
  203. }
  204. type AWSVPCConfig struct {
  205. SubNetIDs []string `json:"subnet_ids" mapstructure:"subnet_ids"`
  206. VPCID string `json:"vpc_id" mapstructure:"vpc_id"`
  207. }
  208. func getVPCFromEKSTFState(tfState *ptypes.ParseableRawTFState) (string, []string, error) {
  209. for _, resource := range tfState.Resources {
  210. if "aws_eks_cluster.cluster" == resource.Type+"."+resource.Name {
  211. for _, instance := range resource.Instances {
  212. vpcConfig, ok := instance.Attributes["vpc_config"]
  213. if !ok {
  214. return "", []string{}, errors.New("name not found for the requested resource name-type")
  215. }
  216. awsVPCConfigIface, ok := vpcConfig.([]interface{})
  217. if !ok {
  218. fmt.Printf("%#v\n", vpcConfig)
  219. return "", []string{}, errors.New("cannot cast returned value to vpc config")
  220. }
  221. if len(awsVPCConfigIface) == 0 {
  222. return "", []string{}, errors.New("empty vpc config")
  223. }
  224. awsVPCConfigMap, ok := awsVPCConfigIface[0].(map[string]interface{})
  225. if !ok {
  226. return "", []string{}, errors.New("cannot cast returned value to vpc config map")
  227. }
  228. var awsVPCConfig AWSVPCConfig
  229. err := mapstructure.Decode(awsVPCConfigMap, &awsVPCConfig)
  230. if err != nil {
  231. return "", []string{}, errors.New("cannot cast returned value to vpc config")
  232. }
  233. return awsVPCConfig.VPCID, awsVPCConfig.SubNetIDs, nil
  234. }
  235. return "", []string{}, errors.New("name not found for the requested resource name-type")
  236. }
  237. }
  238. return "", []string{}, errors.New("name not found for the requested resource name-type")
  239. }