create.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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. // If the cluster ID was passed in, we store the parent cluster ID in the infra
  69. // so it can be referenced later
  70. ParentClusterID: req.ClusterID,
  71. }
  72. // verify the credentials
  73. err = checkInfraCredentials(c.Config(), proj, infra, req.InfraCredentials)
  74. if err != nil {
  75. c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
  76. return
  77. }
  78. // call apply on the provisioner service
  79. vals := req.Values
  80. // if this is cluster-scoped and the kind is RDS, run the postrenderer
  81. if req.ClusterID != 0 && req.Kind == "rds" {
  82. var ok bool
  83. pr := &InfraRDSPostrenderer{
  84. config: c.Config(),
  85. }
  86. if vals, ok = pr.Run(w, r, &Opts{
  87. Cluster: cluster,
  88. Values: req.Values,
  89. }); !ok {
  90. return
  91. }
  92. }
  93. // handle write to the database
  94. infra, err = c.Repo().Infra().CreateInfra(infra)
  95. if err != nil {
  96. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  97. return
  98. }
  99. resp, err := c.Config().ProvisionerClient.Apply(context.Background(), proj.ID, infra.ID, &ptypes.ApplyBaseRequest{
  100. Kind: req.Kind,
  101. Values: vals,
  102. OperationKind: "create",
  103. })
  104. if err != nil {
  105. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  106. return
  107. }
  108. c.WriteResult(w, r, resp)
  109. }
  110. func checkInfraCredentials(config *config.Config, proj *models.Project, infra *models.Infra, req *types.InfraCredentials) error {
  111. if req == nil {
  112. return nil
  113. }
  114. if req.DOIntegrationID != 0 {
  115. _, err := config.Repo.OAuthIntegration().ReadOAuthIntegration(proj.ID, req.DOIntegrationID)
  116. if err != nil {
  117. return fmt.Errorf("do integration id %d not found in project %d", req.DOIntegrationID, proj.ID)
  118. }
  119. infra.DOIntegrationID = req.DOIntegrationID
  120. infra.AWSIntegrationID = 0
  121. infra.GCPIntegrationID = 0
  122. infra.AzureIntegrationID = 0
  123. } else if req.AWSIntegrationID != 0 {
  124. _, err := config.Repo.AWSIntegration().ReadAWSIntegration(proj.ID, req.AWSIntegrationID)
  125. if err != nil {
  126. return fmt.Errorf("aws integration id %d not found in project %d", req.AWSIntegrationID, proj.ID)
  127. }
  128. infra.DOIntegrationID = 0
  129. infra.AWSIntegrationID = req.AWSIntegrationID
  130. infra.GCPIntegrationID = 0
  131. infra.AzureIntegrationID = 0
  132. } else if req.GCPIntegrationID != 0 {
  133. _, err := config.Repo.GCPIntegration().ReadGCPIntegration(proj.ID, req.GCPIntegrationID)
  134. if err != nil {
  135. return fmt.Errorf("gcp integration id %d not found in project %d", req.GCPIntegrationID, proj.ID)
  136. }
  137. infra.DOIntegrationID = 0
  138. infra.AWSIntegrationID = 0
  139. infra.GCPIntegrationID = req.GCPIntegrationID
  140. infra.AzureIntegrationID = 0
  141. } else if req.AzureIntegrationID != 0 {
  142. _, err := config.Repo.AzureIntegration().ReadAzureIntegration(proj.ID, req.AzureIntegrationID)
  143. if err != nil {
  144. return fmt.Errorf("azure integration id %d not found in project %d", req.AzureIntegrationID, proj.ID)
  145. }
  146. infra.DOIntegrationID = 0
  147. infra.AWSIntegrationID = 0
  148. infra.GCPIntegrationID = 0
  149. infra.AzureIntegrationID = req.AzureIntegrationID
  150. }
  151. if infra.DOIntegrationID == 0 && infra.AWSIntegrationID == 0 && infra.GCPIntegrationID == 0 && infra.AzureIntegrationID == 0 {
  152. return fmt.Errorf("at least one integration id must be set")
  153. }
  154. return nil
  155. }
  156. // getSourceLinkAndVersion returns the source link and version for the infrastructure. For now,
  157. // this is hardcoded
  158. func getSourceLinkAndVersion(kind types.InfraKind) (string, string) {
  159. switch kind {
  160. case types.InfraECR:
  161. return "porter/aws/ecr", "v0.1.0"
  162. case types.InfraEKS:
  163. return "porter/aws/eks", "v0.1.0"
  164. case types.InfraRDS:
  165. return "porter/aws/rds", "v0.1.0"
  166. case types.InfraS3:
  167. return "porter/aws/s3", "v0.1.0"
  168. case types.InfraGCR:
  169. return "porter/gcp/gcr", "v0.1.0"
  170. case types.InfraGAR:
  171. return "porter/gcp/gar", "v0.1.0"
  172. case types.InfraGKE:
  173. return "porter/gcp/gke", "v0.1.0"
  174. case types.InfraDOCR:
  175. return "porter/do/docr", "v0.1.0"
  176. case types.InfraDOKS:
  177. return "porter/do/doks", "v0.1.0"
  178. case types.InfraAKS:
  179. return "porter/azure/aks", "v0.1.0"
  180. case types.InfraACR:
  181. return "porter/azure/acr", "v0.1.0"
  182. }
  183. return "porter/test", "v0.1.0"
  184. }
  185. type InfraRDSPostrenderer struct {
  186. config *config.Config
  187. }
  188. type Opts struct {
  189. Cluster *models.Cluster
  190. Values map[string]interface{}
  191. }
  192. func (i *InfraRDSPostrenderer) Run(w http.ResponseWriter, r *http.Request, opts *Opts) (map[string]interface{}, bool) {
  193. if opts.Cluster != nil {
  194. proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
  195. values := opts.Values
  196. // find the corresponding infra id
  197. clusterInfra, err := i.config.Repo.Infra().ReadInfra(proj.ID, opts.Cluster.InfraID)
  198. if err != nil {
  199. apierrors.HandleAPIError(i.config.Logger, i.config.Alerter, w, r, apierrors.NewErrForbidden(fmt.Errorf("could not get cluster infra: %v", err)), true)
  200. return nil, false
  201. }
  202. clusterInfraOperation, err := i.config.Repo.Infra().GetLatestOperation(clusterInfra)
  203. // get the raw state for the cluster
  204. rawState, err := i.config.ProvisionerClient.GetRawState(context.Background(), models.GetWorkspaceID(clusterInfra, clusterInfraOperation))
  205. if err != nil {
  206. apierrors.HandleAPIError(i.config.Logger, i.config.Alerter, w, r, apierrors.NewErrInternal(err), true)
  207. return nil, false
  208. }
  209. vpcID, subnetIDs, err := getVPCFromEKSTFState(rawState)
  210. if err != nil {
  211. return values, false
  212. }
  213. // if the length of the subnets is not 3, return an error
  214. if len(subnetIDs) < 3 {
  215. apierrors.HandleAPIError(i.config.Logger, i.config.Alerter, w, r, apierrors.NewErrInternal(fmt.Errorf("invalid number of subnet IDs in VPC configuration")), true)
  216. return nil, false
  217. }
  218. values["porter_cluster_vpc"] = vpcID
  219. values["porter_cluster_subnet_1"] = subnetIDs[0]
  220. values["porter_cluster_subnet_2"] = subnetIDs[1]
  221. values["porter_cluster_subnet_3"] = subnetIDs[2]
  222. return values, true
  223. }
  224. return opts.Values, true
  225. }
  226. type AWSVPCConfig struct {
  227. SubNetIDs []string `json:"subnet_ids" mapstructure:"subnet_ids"`
  228. VPCID string `json:"vpc_id" mapstructure:"vpc_id"`
  229. }
  230. func getVPCFromEKSTFState(tfState *ptypes.ParseableRawTFState) (string, []string, error) {
  231. for _, resource := range tfState.Resources {
  232. if "aws_eks_cluster.cluster" == resource.Type+"."+resource.Name {
  233. for _, instance := range resource.Instances {
  234. vpcConfig, ok := instance.Attributes["vpc_config"]
  235. if !ok {
  236. return "", []string{}, errors.New("name not found for the requested resource name-type")
  237. }
  238. awsVPCConfigIface, ok := vpcConfig.([]interface{})
  239. if !ok {
  240. fmt.Printf("%#v\n", vpcConfig)
  241. return "", []string{}, errors.New("cannot cast returned value to vpc config")
  242. }
  243. if len(awsVPCConfigIface) == 0 {
  244. return "", []string{}, errors.New("empty vpc config")
  245. }
  246. awsVPCConfigMap, ok := awsVPCConfigIface[0].(map[string]interface{})
  247. if !ok {
  248. return "", []string{}, errors.New("cannot cast returned value to vpc config map")
  249. }
  250. var awsVPCConfig AWSVPCConfig
  251. err := mapstructure.Decode(awsVPCConfigMap, &awsVPCConfig)
  252. if err != nil {
  253. return "", []string{}, errors.New("cannot cast returned value to vpc config")
  254. }
  255. return awsVPCConfig.VPCID, awsVPCConfig.SubNetIDs, nil
  256. }
  257. return "", []string{}, errors.New("name not found for the requested resource name-type")
  258. }
  259. }
  260. return "", []string{}, errors.New("name not found for the requested resource name-type")
  261. }