Prechádzať zdrojové kódy

add support for rds provisioning

Alexander Belanger 4 rokov pred
rodič
commit
93112fe538

+ 145 - 1
api/server/handlers/infra/create.go

@@ -2,9 +2,11 @@ package infra
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net/http"
 
+	"github.com/mitchellh/mapstructure"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
@@ -12,6 +14,7 @@ import (
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/encryption"
 	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
 
 	"github.com/porter-dev/porter/provisioner/client"
 
@@ -42,6 +45,25 @@ func (c *InfraCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	var cluster *models.Cluster
+	var err error
+
+	if req.ClusterID != 0 {
+		cluster, err = c.Repo().Cluster().ReadCluster(proj.ID, req.ClusterID)
+
+		if err != nil {
+			if err == gorm.ErrRecordNotFound {
+				c.HandleAPIError(w, r, apierrors.NewErrForbidden(
+					fmt.Errorf("cluster with id %d not found in project %d", req.ClusterID, proj.ID),
+				))
+			} else {
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			}
+
+			return
+		}
+	}
+
 	suffix, err := encryption.GenerateRandomBytes(6)
 
 	if err != nil {
@@ -61,6 +83,7 @@ func (c *InfraCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		CreatedByUserID: user.ID,
 		SourceLink:      sourceLink,
 		SourceVersion:   sourceVersion,
+		ParentClusterID: req.ClusterID,
 	}
 
 	// verify the credentials
@@ -82,9 +105,27 @@ func (c *InfraCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	// call apply on the provisioner service
 	pClient := client.NewClient("http://localhost:8082/api/v1")
 
+	vals := req.Values
+
+	// if this is cluster-scoped and the kind is RDS, run the postrenderer
+	if req.ClusterID != 0 && req.Kind == "rds" {
+		var ok bool
+
+		pr := &InfraRDSPostrenderer{
+			config: c.Config(),
+		}
+
+		if vals, ok = pr.Run(w, r, &Opts{
+			Cluster: cluster,
+			Values:  req.Values,
+		}); !ok {
+			return
+		}
+	}
+
 	resp, err := pClient.Apply(context.Background(), proj.ID, infra.ID, &ptypes.ApplyBaseRequest{
 		Kind:          req.Kind,
-		Values:        req.Values,
+		Values:        vals,
 		OperationKind: "create",
 	})
 
@@ -162,3 +203,106 @@ func getSourceLinkAndVersion(kind types.InfraKind) (string, string) {
 
 	return "porter/test", "v0.1.0"
 }
+
+type InfraRDSPostrenderer struct {
+	config *config.Config
+}
+
+type Opts struct {
+	Cluster *models.Cluster
+	Values  map[string]interface{}
+}
+
+func (i *InfraRDSPostrenderer) Run(w http.ResponseWriter, r *http.Request, opts *Opts) (map[string]interface{}, bool) {
+	if opts.Cluster != nil {
+		proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+		values := opts.Values
+
+		// find the corresponding infra id
+		clusterInfra, err := i.config.Repo.Infra().ReadInfra(proj.ID, opts.Cluster.InfraID)
+
+		if err != nil {
+			apierrors.HandleAPIError(i.config.Logger, i.config.Alerter, w, r, apierrors.NewErrForbidden(fmt.Errorf("could not get cluster infra: %v", err)), true)
+			return nil, false
+		}
+
+		clusterInfraOperation, err := i.config.Repo.Infra().GetLatestOperation(clusterInfra)
+
+		pClient := client.NewClient("http://localhost:8082/api/v1")
+
+		// get the raw state for the cluster
+		rawState, err := pClient.GetRawState(context.Background(), models.GetWorkspaceID(clusterInfra, clusterInfraOperation))
+
+		if err != nil {
+			apierrors.HandleAPIError(i.config.Logger, i.config.Alerter, w, r, apierrors.NewErrInternal(err), true)
+			return nil, false
+		}
+
+		vpcID, subnetIDs, err := getVPCFromEKSTFState(rawState)
+
+		if err != nil {
+			return values, false
+		}
+
+		// if the length of the subnets is not 3, return an error
+		if len(subnetIDs) < 3 {
+			apierrors.HandleAPIError(i.config.Logger, i.config.Alerter, w, r, apierrors.NewErrInternal(fmt.Errorf("invalid number of subnet IDs in VPC configuration")), true)
+			return nil, false
+		}
+
+		values["porter_cluster_vpc"] = vpcID
+		values["porter_cluster_subnet_1"] = subnetIDs[0]
+		values["porter_cluster_subnet_2"] = subnetIDs[1]
+		values["porter_cluster_subnet_3"] = subnetIDs[2]
+
+		return values, true
+	}
+
+	return opts.Values, true
+}
+
+type AWSVPCConfig struct {
+	SubNetIDs []string `json:"subnet_ids" mapstructure:"subnet_ids"`
+	VPCID     string   `json:"vpc_id" mapstructure:"vpc_id"`
+}
+
+func getVPCFromEKSTFState(tfState *ptypes.ParseableRawTFState) (string, []string, error) {
+	for _, resource := range tfState.Resources {
+		if "aws_eks_cluster.cluster" == resource.Type+"."+resource.Name {
+			for _, instance := range resource.Instances {
+				vpcConfig, ok := instance.Attributes["vpc_config"]
+				if !ok {
+					return "", []string{}, errors.New("name not found for the requested resource name-type")
+				}
+
+				awsVPCConfigIface, ok := vpcConfig.([]interface{})
+				if !ok {
+					fmt.Printf("%#v\n", vpcConfig)
+					return "", []string{}, errors.New("cannot cast returned value to vpc config")
+				}
+
+				if len(awsVPCConfigIface) == 0 {
+					return "", []string{}, errors.New("empty vpc config")
+				}
+
+				awsVPCConfigMap, ok := awsVPCConfigIface[0].(map[string]interface{})
+				if !ok {
+					return "", []string{}, errors.New("cannot cast returned value to vpc config map")
+				}
+
+				var awsVPCConfig AWSVPCConfig
+
+				err := mapstructure.Decode(awsVPCConfigMap, &awsVPCConfig)
+				if err != nil {
+					return "", []string{}, errors.New("cannot cast returned value to vpc config")
+				}
+
+				return awsVPCConfig.VPCID, awsVPCConfig.SubNetIDs, nil
+			}
+
+			return "", []string{}, errors.New("name not found for the requested resource name-type")
+		}
+	}
+
+	return "", []string{}, errors.New("name not found for the requested resource name-type")
+}

+ 287 - 45
api/server/handlers/infra/forms.go

@@ -3,6 +3,7 @@ package infra
 const testForm = `name: Test
 hasSource: false
 includeHiddenFields: true
+isClusterScoped: true
 tabs:
 - name: main
   label: Configuration
@@ -17,51 +18,292 @@ tabs:
         default: hello
 `
 
-// note: region options for AWS, if needed
-// - type: select
-//       label: 📍 AWS Region
-//       variable: aws_region
-//       settings:
-//         default: us-east-2
-//         options:
-//         - label: US East (N. Virginia) us-east-1
-//           value: us-east-1
-//         - label: US East (Ohio) us-east-2
-//           value: us-east-2
-//         - label: US West (N. California) us-west-1
-//           value: us-west-1
-//         - label: US West (Oregon) us-west-2
-//           value: us-west-2
-//         - label: Africa (Cape Town) af-south-1
-//           value: af-south-1
-//         - label: Asia Pacific (Hong Kong) ap-east-1
-//           value: ap-east-1
-//         - label: Asia Pacific (Mumbai) ap-south-1
-//           value: ap-south-1
-//         - label: Asia Pacific (Seoul) ap-northeast-2
-//           value: ap-northeast-2
-//         - label: Asia Pacific (Singapore) ap-southeast-1
-//           value: ap-southeast-1
-//         - label: Asia Pacific (Sydney) ap-southeast-2
-//           value: ap-southeast-2
-//         - label: Asia Pacific (Tokyo) ap-northeast-1
-//           value: ap-northeast-1
-//         - label: Canada (Central) ca-central-1
-//           value: ca-central-1
-//         - label: Europe (Ireland) eu-west-1
-//           value: eu-west-1
-//         - label: Europe (London) eu-west-2
-//           value: eu-west-2
-//         - label: Europe (Milan) eu-south-1
-//           value: eu-south-1
-//         - label: Europe (Paris) eu-west-3
-//           value: eu-west-3
-//         - label: Europe (Stockholm) eu-north-1
-//           value: eu-north-1
-//         - label: Middle East (Bahrain) me-south-1
-//           value: me-south-1
-//         - label: South America (São Paulo) sa-east-1
-//           value: sa-east-1
+const rdsForm = `name: RDS
+hasSource: false
+includeHiddenFields: true
+isClusterScoped: true
+tabs:
+- name: main
+  label: Main
+  sections:
+  - name: heading
+    contents: 
+    - type: heading
+      label: Database Settings
+  - name: user
+    contents:
+    - type: string-input
+      label: Database Master User
+      required: true
+      placeholder: "admin"
+      variable: db_user
+  - name: password
+    contents:
+    - type: string-input
+      required: true
+      label: Database Master Password
+      variable: db_passwd
+  - name: name
+    contents:
+    - type: string-input
+      label: Database Name
+      required: true
+      placeholder: "rds-staging"
+      variable: db_name
+  - name: machine-type
+    contents:
+    - type: select
+      label: ⚙️ Database Machine Type
+      variable: machine_type
+      settings:
+        default: db.t3.medium
+        options:
+        - label: db.t2.medium
+          value: db.t2.medium
+        - label: db.t2.xlarge
+          value: db.t2.xlarge
+        - label: db.t2.2xlarge
+          value: db.t2.2xlarge
+        - label: db.t3.medium
+          value: db.t3.medium
+        - label: db.t3.xlarge
+          value: db.t3.xlarge
+        - label: db.t3.2xlarge
+          value: db.t3.2xlarge
+  - name: family-versions
+    contents:
+    - type: select
+      label:  Database Family Version
+      variable: db_family
+      settings:
+        default: postgres13
+        options:
+        - label: "Postgres 9"
+          value: postgres9
+        - label: "Postgres 10"
+          value: postgres10
+        - label: "Postgres 11"
+          value: postgres11
+        - label: "Postgres 12"
+          value: postgres12
+        - label: "Postgres 13"
+          value: postgres13
+  - name: pg-9-versions
+    show_if: 
+      is: "postgres9"
+      variable: db_family
+    contents:
+    - type: select
+      label:  Database Version
+      variable: db_engine_version
+      settings:
+        default: "9.6.23"
+        options:
+        - label: "v9.6.1"
+          value: "9.6.1"
+        - label: "v9.6.2"
+          value: "9.6.2"
+        - label: "v9.6.3"
+          value: "9.6.3"
+        - label: "v9.6.4"
+          value: "9.6.4"
+        - label: "v9.6.5"
+          value: "9.6.5"
+        - label: "v9.6.6"
+          value: "9.6.6"
+        - label: "v9.6.7"
+          value: "9.6.7"
+        - label: "v9.6.8"
+          value: "9.6.8"
+        - label: "v9.6.10"
+          value: "9.6.10"
+        - label: "v9.6.11"
+          value: "9.6.11"
+        - label: "v9.6.12"
+          value: "9.6.12"
+        - label: "v9.6.13"
+          value: "9.6.13"
+        - label: "v9.6.14"
+          value: "9.6.14"
+        - label: "v9.6.15"
+          value: "9.6.15"
+        - label: "v9.6.16"
+          value: "9.6.16"
+        - label: "v9.6.17"
+          value: "9.6.17"
+        - label: "v9.6.18"
+          value: "9.6.18"
+        - label: "v9.6.19"
+          value: "9.6.19"
+        - label: "v9.6.20"
+          value: "9.6.20"
+        - label: "v9.6.21"
+          value: "9.6.21"
+        - label: "v9.6.22"
+          value: "9.6.22"
+        - label: "v9.6.23"
+          value: "9.6.23"
+  - name: pg-10-versions
+    show_if: 
+      is: "postgres10"
+      variable: db_family
+    contents:
+    - type: select
+      label:  Database Version
+      variable: db_engine_version
+      settings:
+        default: "10.18"
+        options:
+        - label: "v10.1"
+          value: "10.1"
+        - label: "v10.2"
+          value: "10.2"
+        - label: "v10.3"
+          value: "10.3"
+        - label: "v10.4"
+          value: "10.4"
+        - label: "v10.5"
+          value: "10.5"
+        - label: "v10.6"
+          value: "10.6"
+        - label: "v10.7"
+          value: "10.7"
+        - label: "v10.8"
+          value: "10.8"
+        - label: "v10.9"
+          value: "10.9"
+        - label: "v10.10"
+          value: "10.10"
+        - label: "v10.11"
+          value: "10.11"
+        - label: "v10.12"
+          value: "10.12"
+        - label: "v10.13"
+          value: "10.13"
+        - label: "v10.14"
+          value: "10.14"
+        - label: "v10.15"
+          value: "10.15"
+        - label: "v10.16"
+          value: "10.16"
+        - label: "v10.17"
+          value: "10.17"
+        - label: "v10.18"
+          value: "10.18"
+  - name: pg-11-versions
+    show_if: 
+      is: "postgres11"
+      variable: db_family
+    contents:
+    - type: select
+      label:  Database Version
+      variable: db_engine_version
+      settings:
+        default: "11.13"
+        options:
+        - label: "v11.1"
+          value: "11.1"
+        - label: "v11.2"
+          value: "11.2"
+        - label: "v11.3"
+          value: "11.3"
+        - label: "v11.4"
+          value: "11.4"
+        - label: "v11.5"
+          value: "11.5"
+        - label: "v11.6"
+          value: "11.6"
+        - label: "v11.7"
+          value: "11.7"
+        - label: "v11.8"
+          value: "11.8"
+        - label: "v11.9"
+          value: "11.9"
+        - label: "v11.10"
+          value: "11.10"
+        - label: "v11.11"
+          value: "11.11"
+        - label: "v11.12"
+          value: "11.12"
+        - label: "v11.13"
+          value: "11.13"
+  - name: pg-12-versions
+    show_if: 
+      is: "postgres12"
+      variable: db_family
+    contents:
+    - type: select
+      label:  Database Version
+      variable: db_engine_version
+      settings:
+        default: "12.8"
+        options:
+        - label: "v12.2"
+          value: "12.2"
+        - label: "v12.3"
+          value: "12.3"
+        - label: "v12.4"
+          value: "12.4"
+        - label: "v12.5"
+          value: "12.5"
+        - label: "v12.6"
+          value: "12.6"
+        - label: "v12.7"
+          value: "12.7"
+        - label: "v12.8"
+          value: "12.8"
+  - name: pg-13-versions
+    show_if: 
+      is: "postgres13"
+      variable: db_family
+    contents:
+    - type: select
+      label:  Database Version
+      variable: db_engine_version
+      settings:
+        default: "13.4"
+        options:
+        - label: "v13.1"
+          value: "13.1"
+        - label: "v13.2"
+          value: "13.2"
+        - label: "v13.3"
+          value: "13.3"
+        - label: "v13.4"
+          value: "13.4"
+  - name: additional-settings
+    contents:
+    - type: heading
+      label: Additional Settings
+    - type: checkbox
+      variable: db_deletion_protection
+      label: Enable deletion protection for the database.
+      settings:
+        default: false
+- name: storage
+  label: Storage
+  sections:
+  - name: storage
+    contents:
+    - type: heading
+      label: Storage Settings
+    - type: number-input
+      label: Gigabytes
+      variable: db_allocated_storage
+      placeholder: "ex: 10"
+      settings:
+        default: 10
+    - type: number-input
+      label: Gigabytes
+      variable: db_max_allocated_storage
+      placeholder: "ex: 20"
+      settings:
+        default: 20
+    - type: checkbox
+      variable: db_storage_encrypted
+      label: Enable storage encryption for the database. 
+      settings:
+        default: false`
 
 const ecrForm = `name: ECR
 hasSource: false

+ 2 - 0
api/server/handlers/infra/get_template.go

@@ -65,6 +65,8 @@ func getFormBytesFromKind(kind string) []byte {
 	switch strings.ToLower(kind) {
 	case "ecr":
 		formBytes = []byte(ecrForm)
+	case "rds":
+		formBytes = []byte(rdsForm)
 	case "eks":
 		formBytes = []byte(eksForm)
 	case "gcr":

+ 8 - 0
api/server/handlers/infra/list_templates.go

@@ -49,6 +49,14 @@ var templateMap = map[string]*types.InfraTemplateMeta{
 		Kind:               "ecr",
 		RequiredCredential: "aws_integration_id",
 	},
+	"rds": {
+		Icon:               "",
+		Description:        "Create a Relational Database Service instance.",
+		Name:               "RDS",
+		Version:            "v0.1.0",
+		Kind:               "rds",
+		RequiredCredential: "aws_integration_id",
+	},
 	"eks": {
 		Icon:               "https://img.stackshare.io/service/7991/amazon-eks.png",
 		Description:        "Create an Elastic Kubernetes Service cluster.",

+ 41 - 2
api/server/handlers/infra/retry_create.go

@@ -3,6 +3,7 @@ package infra
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"net/http"
 
 	"github.com/porter-dev/porter/api/server/handlers"
@@ -13,6 +14,7 @@ import (
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/provisioner/client"
 	ptypes "github.com/porter-dev/porter/provisioner/types"
+	"gorm.io/gorm"
 )
 
 type InfraRetryCreateHandler struct {
@@ -35,8 +37,27 @@ func (c *InfraRetryCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 		return
 	}
 
+	var cluster *models.Cluster
+	var err error
+
+	if infra.ParentClusterID != 0 {
+		cluster, err = c.Repo().Cluster().ReadCluster(proj.ID, infra.ParentClusterID)
+
+		if err != nil {
+			if err == gorm.ErrRecordNotFound {
+				c.HandleAPIError(w, r, apierrors.NewErrForbidden(
+					fmt.Errorf("cluster with id %d not found in project %d", infra.ParentClusterID, proj.ID),
+				))
+			} else {
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			}
+
+			return
+		}
+	}
+
 	// verify the credentials
-	err := checkInfraCredentials(c.Config(), proj, infra, req.InfraCredentials)
+	err = checkInfraCredentials(c.Config(), proj, infra, req.InfraCredentials)
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
@@ -62,12 +83,30 @@ func (c *InfraRetryCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 		}
 	}
 
+	vals := req.Values
+
+	// if this is cluster-scoped and the kind is RDS, run the postrenderer
+	if infra.ParentClusterID != 0 && infra.Kind == "rds" {
+		var ok bool
+
+		pr := &InfraRDSPostrenderer{
+			config: c.Config(),
+		}
+
+		if vals, ok = pr.Run(w, r, &Opts{
+			Cluster: cluster,
+			Values:  vals,
+		}); !ok {
+			return
+		}
+	}
+
 	// call apply on the provisioner service
 	pClient := client.NewClient("http://localhost:8082/api/v1")
 
 	resp, err := pClient.Apply(context.Background(), proj.ID, infra.ID, &ptypes.ApplyBaseRequest{
 		Kind:          string(infra.Kind),
-		Values:        req.Values,
+		Values:        vals,
 		OperationKind: "retry_create",
 	})
 

+ 0 - 4
api/server/handlers/infra/stream_state.go

@@ -3,7 +3,6 @@ package infra
 import (
 	"context"
 	"errors"
-	"fmt"
 	"io"
 	"net/http"
 	"sync"
@@ -85,7 +84,6 @@ func (c *InfraStreamStateHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 		for {
 			if _, _, err := safeRW.ReadMessage(); err != nil {
 				errorchan <- nil
-				fmt.Println("STATE STREAMER closing websocket goroutine")
 				return
 			}
 		}
@@ -105,8 +103,6 @@ func (c *InfraStreamStateHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 					errorchan <- err
 				}
 
-				fmt.Println("STATE STREAMER closing grpc goroutine")
-
 				return
 			}
 

+ 41 - 2
api/server/handlers/infra/update.go

@@ -3,6 +3,7 @@ package infra
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"net/http"
 
 	"github.com/porter-dev/porter/api/server/handlers"
@@ -13,6 +14,7 @@ import (
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/provisioner/client"
 	ptypes "github.com/porter-dev/porter/provisioner/types"
+	"gorm.io/gorm"
 )
 
 type InfraUpdateHandler struct {
@@ -35,8 +37,27 @@ func (c *InfraUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	var cluster *models.Cluster
+	var err error
+
+	if infra.ParentClusterID != 0 {
+		cluster, err = c.Repo().Cluster().ReadCluster(proj.ID, infra.ParentClusterID)
+
+		if err != nil {
+			if err == gorm.ErrRecordNotFound {
+				c.HandleAPIError(w, r, apierrors.NewErrForbidden(
+					fmt.Errorf("cluster with id %d not found in project %d", infra.ParentClusterID, proj.ID),
+				))
+			} else {
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			}
+
+			return
+		}
+	}
+
 	// verify the credentials
-	err := checkInfraCredentials(c.Config(), proj, infra, req.InfraCredentials)
+	err = checkInfraCredentials(c.Config(), proj, infra, req.InfraCredentials)
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
@@ -62,12 +83,30 @@ func (c *InfraUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
+	vals := req.Values
+
+	// if this is cluster-scoped and the kind is RDS, run the postrenderer
+	if infra.ParentClusterID != 0 && infra.Kind == "rds" {
+		var ok bool
+
+		pr := &InfraRDSPostrenderer{
+			config: c.Config(),
+		}
+
+		if vals, ok = pr.Run(w, r, &Opts{
+			Cluster: cluster,
+			Values:  vals,
+		}); !ok {
+			return
+		}
+	}
+
 	// call apply on the provisioner service
 	pClient := client.NewClient("http://localhost:8082/api/v1")
 
 	resp, err := pClient.Apply(context.Background(), proj.ID, infra.ID, &ptypes.ApplyBaseRequest{
 		Kind:          string(infra.Kind),
-		Values:        req.Values,
+		Values:        vals,
 		OperationKind: "update",
 	})
 

+ 1 - 1
api/types/form.go

@@ -52,7 +52,7 @@ type FormYAML struct {
 	Icon                string     `yaml:"icon" json:"icon"`
 	HasSource           string     `yaml:"hasSource" json:"hasSource"`
 	IncludeHiddenFields string     `yaml:"includeHiddenFields,omitempty" json:"includeHiddenFields,omitempty"`
-	IsClusterScoped     bool       `yaml:"isClusterscoped" json:"isClusterScoped"`
+	IsClusterScoped     bool       `yaml:"isClusterScoped" json:"isClusterScoped"`
 	Description         string     `yaml:"description" json:"description"`
 	Tags                []string   `yaml:"tags" json:"tags"`
 	Tabs                []*FormTab `yaml:"tabs" json:"tabs,omitempty"`

+ 3 - 2
api/types/infra.go

@@ -78,8 +78,9 @@ type InfraCredentials struct {
 type CreateInfraRequest struct {
 	*InfraCredentials
 
-	Kind   string                 `json:"kind" form:"required"`
-	Values map[string]interface{} `json:"values" form:"required"`
+	ClusterID uint                   `json:"cluster_id"`
+	Kind      string                 `json:"kind" form:"required"`
+	Values    map[string]interface{} `json:"values" form:"required"`
 }
 
 type ListInfraRequest struct {

+ 123 - 183
api/types/provision.go

@@ -1,57 +1,13 @@
 package types
 
-import "strings"
-
-type CreateECRInfraRequest struct {
-	ECRName          string `json:"ecr_name" form:"required"`
-	ProjectID        uint   `json:"-" form:"required"`
-	AWSIntegrationID uint   `json:"aws_integration_id" form:"required"`
-}
-
-type CreateEKSInfraRequest struct {
-	EKSName          string `json:"eks_name" form:"required"`
-	MachineType      string `json:"machine_type"`
-	IssuerEmail      string `json:"issuer_email" form:"required"`
-	ProjectID        uint   `json:"-" form:"required"`
-	AWSIntegrationID uint   `json:"aws_integration_id" form:"required"`
-}
-
-type CreateGCRInfraRequest struct {
-	ProjectID        uint `json:"-" form:"required"`
-	GCPIntegrationID uint `json:"gcp_integration_id" form:"required"`
-}
-
-type CreateGKEInfraRequest struct {
-	GKEName          string `json:"gke_name" form:"required"`
-	GCPRegion        string `json:"gcp_region" form:"required"`
-	IssuerEmail      string `json:"issuer_email" form:"required"`
-	ProjectID        uint   `json:"-" form:"required"`
-	GCPIntegrationID uint   `json:"gcp_integration_id" form:"required"`
-}
-
-type CreateDOCRInfraRequest struct {
-	DOCRName             string `json:"docr_name" form:"required"`
-	DOCRSubscriptionTier string `json:"docr_subscription_tier" form:"required"`
-	ProjectID            uint   `json:"-" form:"required"`
-	DOIntegrationID      uint   `json:"do_integration_id" form:"required"`
-}
-
-type CreateDOKSInfraRequest struct {
-	DORegion        string `json:"do_region" form:"required"`
-	IssuerEmail     string `json:"issuer_email" form:"required"`
-	DOKSName        string `json:"doks_name" form:"required"`
-	ProjectID       uint   `json:"-" form:"required"`
-	DOIntegrationID uint   `json:"do_integration_id" form:"required"`
-}
-
 type CreateRDSInfraRequest struct {
+	Namespace string `json:"namespace"`
+
 	// version of the postgres engine
 	DBEngineVersion string `json:"db_engine_version"`
-	// db type - postgress / mysql
-	DBFamily string `json:"db_family"`
 
-	// Deprecated, use DBEngineVersion instead
-	// PGVersion string `json:"pg_version"`
+	// db type - postgres / mysql
+	DBFamily string `json:"db_family"`
 
 	// db instance credentials specifications
 	DBName   string `json:"db_name"`
@@ -64,138 +20,122 @@ type CreateRDSInfraRequest struct {
 	DBEncryption bool   `json:"db_storage_encrypted"`
 }
 
-type RDSInfraLastApplied struct {
-	*CreateRDSInfraRequest
-
-	ClusterID uint   `json:"cluster_id"`
-	Namespace string `json:"namespace"`
-
-	AWSRegion            string
-	DBMajorEngineVersion string
-	DBStorageEncrypted   string
-	DeletionProtection   string
-	VPCID                string
-	Subnet1              string
-	Subnet2              string
-	Subnet3              string
-}
-
-type Family string
-
-type EngineVersion string
-
-func (e EngineVersion) MajorVersion() string {
-	semver := strings.Split(string(e), ".")
-
-	return strings.Join(semver[:len(semver)-1], ".")
-}
-
-type EngineVersions []EngineVersion
-
-func (e EngineVersions) VersionExists(version EngineVersion) bool {
-	for _, v := range e {
-		if version == v {
-			return true
-		}
-	}
-
-	return false
-}
-
-const (
-	FamilyPG9   Family = "postgres9"
-	FamilyPG10  Family = "postgres10"
-	FamilyPG11  Family = "postgres11"
-	FamilyPG12  Family = "postgres12"
-	FamilyPG13  Family = "postgres13"
-	FamilyMysql Family = "mysql"
-)
-
-var availablePG9Versions EngineVersions = EngineVersions{
-	"9.6.1",
-	"9.6.2",
-	"9.6.3",
-	"9.6.4",
-	"9.6.5",
-	"9.6.6",
-	"9.6.7",
-	"9.6.8",
-	"9.6.9",
-	"9.6.10",
-	"9.6.11",
-	"9.6.12",
-	"9.6.13",
-	"9.6.14",
-	"9.6.15",
-	"9.6.16",
-	"9.6.17",
-	"9.6.18",
-	"9.6.19",
-	"9.6.20",
-	"9.6.21",
-	"9.6.22",
-	"9.6.23",
-}
-
-var availablePG10Versions EngineVersions = EngineVersions{
-	"10.1",
-	"10.2",
-	"10.3",
-	"10.4",
-	"10.5",
-	"10.6",
-	"10.7",
-	"10.8",
-	"10.9",
-	"10.10",
-	"10.11",
-	"10.12",
-	"10.13",
-	"10.14",
-	"10.15",
-	"10.16",
-	"10.17",
-	"10.18",
-}
-
-var availablePG11Versions EngineVersions = EngineVersions{
-	"11.1",
-	"11.2",
-	"11.3",
-	"11.4",
-	"11.5",
-	"11.6",
-	"11.7",
-	"11.8",
-	"11.9",
-	"11.10",
-	"11.11",
-	"11.12",
-	"11.13",
-}
-
-var availablePG12Versions EngineVersions = EngineVersions{
-	"12.2",
-	"12.3",
-	"12.4",
-	"12.5",
-	"12.6",
-	"12.7",
-	"12.8",
-}
-
-var availablePG13Versions EngineVersions = EngineVersions{
-	"13.1",
-	"13.2",
-	"13.3",
-	"13.4",
-}
-
-var DBVersionMapping = map[Family]EngineVersions{
-	FamilyPG9:   availablePG9Versions,
-	FamilyPG10:  availablePG10Versions,
-	FamilyPG11:  availablePG11Versions,
-	FamilyPG12:  availablePG12Versions,
-	FamilyPG13:  availablePG13Versions,
-	FamilyMysql: {},
-}
+// type Family string
+
+// type EngineVersion string
+
+// func (e EngineVersion) MajorVersion() string {
+// 	semver := strings.Split(string(e), ".")
+
+// 	return strings.Join(semver[:len(semver)-1], ".")
+// }
+
+// type EngineVersions []EngineVersion
+
+// func (e EngineVersions) VersionExists(version EngineVersion) bool {
+// 	for _, v := range e {
+// 		if version == v {
+// 			return true
+// 		}
+// 	}
+
+// 	return false
+// }
+
+// const (
+// 	FamilyPG9   Family = "postgres9"
+// 	FamilyPG10  Family = "postgres10"
+// 	FamilyPG11  Family = "postgres11"
+// 	FamilyPG12  Family = "postgres12"
+// 	FamilyPG13  Family = "postgres13"
+// 	FamilyMysql Family = "mysql"
+// )
+
+// var availablePG9Versions EngineVersions = EngineVersions{
+// 	"9.6.1",
+// 	"9.6.2",
+// 	"9.6.3",
+// 	"9.6.4",
+// 	"9.6.5",
+// 	"9.6.6",
+// 	"9.6.7",
+// 	"9.6.8",
+// 	"9.6.9",
+// 	"9.6.10",
+// 	"9.6.11",
+// 	"9.6.12",
+// 	"9.6.13",
+// 	"9.6.14",
+// 	"9.6.15",
+// 	"9.6.16",
+// 	"9.6.17",
+// 	"9.6.18",
+// 	"9.6.19",
+// 	"9.6.20",
+// 	"9.6.21",
+// 	"9.6.22",
+// 	"9.6.23",
+// }
+
+// var availablePG10Versions EngineVersions = EngineVersions{
+// 	"10.1",
+// 	"10.2",
+// 	"10.3",
+// 	"10.4",
+// 	"10.5",
+// 	"10.6",
+// 	"10.7",
+// 	"10.8",
+// 	"10.9",
+// 	"10.10",
+// 	"10.11",
+// 	"10.12",
+// 	"10.13",
+// 	"10.14",
+// 	"10.15",
+// 	"10.16",
+// 	"10.17",
+// 	"10.18",
+// }
+
+// var availablePG11Versions EngineVersions = EngineVersions{
+// 	"11.1",
+// 	"11.2",
+// 	"11.3",
+// 	"11.4",
+// 	"11.5",
+// 	"11.6",
+// 	"11.7",
+// 	"11.8",
+// 	"11.9",
+// 	"11.10",
+// 	"11.11",
+// 	"11.12",
+// 	"11.13",
+// }
+
+// var availablePG12Versions EngineVersions = EngineVersions{
+// 	"12.2",
+// 	"12.3",
+// 	"12.4",
+// 	"12.5",
+// 	"12.6",
+// 	"12.7",
+// 	"12.8",
+// }
+
+// var availablePG13Versions EngineVersions = EngineVersions{
+// 	"13.1",
+// 	"13.2",
+// 	"13.3",
+// 	"13.4",
+// }
+
+// var DBVersionMapping = map[Family]EngineVersions{
+// 	FamilyPG9:   availablePG9Versions,
+// 	FamilyPG10:  availablePG10Versions,
+// 	FamilyPG11:  availablePG11Versions,
+// 	FamilyPG12:  availablePG12Versions,
+// 	FamilyPG13:  availablePG13Versions,
+// 	FamilyMysql: {},
+// }

+ 13 - 1
dashboard/src/components/porter-form/PorterFormContextProvider.tsx

@@ -7,13 +7,20 @@ import {
   PorterFormValidationInfo,
   PorterFormVariableList,
 } from "./types";
-import { ShowIf, ShowIfAnd, ShowIfNot, ShowIfOr } from "../../shared/types";
+import {
+  ShowIf,
+  ShowIfAnd,
+  ShowIfIs,
+  ShowIfNot,
+  ShowIfOr,
+} from "../../shared/types";
 import { getFinalVariablesForStringInput } from "./field-components/Input";
 import { getFinalVariablesForKeyValueArray } from "./field-components/KeyValueArray";
 import { Context } from "../../shared/Context";
 import { getFinalVariablesForArrayInput } from "./field-components/ArrayInput";
 import { getFinalVariablesForCheckbox } from "./field-components/Checkbox";
 import { getFinalVariablesForSelect } from "./field-components/Select";
+import api from "shared/api";
 
 interface Props {
   rawFormData: PorterFormData;
@@ -200,6 +207,11 @@ export const PorterFormContextProvider: React.FC<Props> = (props) => {
     if (typeof vals == "string") {
       return !!variables[vals];
     }
+    if ((vals as ShowIfIs).is) {
+      vals = vals as ShowIfIs;
+      return vals.is == variables[vals.variable];
+    }
+
     if ((vals as ShowIfOr).or) {
       vals = vals as ShowIfOr;
       for (let i = 0; i < vals.or?.length; i++) {

+ 26 - 4
dashboard/src/main/home/infrastructure/components/ProvisionInfra.tsx

@@ -18,8 +18,11 @@ import {
   InfraTemplateMeta,
   InfraTemplate,
   InfraCredentials,
+  ClusterType,
 } from "shared/types";
 import Description from "components/Description";
+import Select from "components/porter-form/field-components/Select";
+import ClusterList from "./credentials/ClusterList";
 
 type Props = {};
 
@@ -27,6 +30,7 @@ const ProvisionInfra: React.FunctionComponent<Props> = () => {
   const { currentProject, setCurrentError } = useContext(Context);
   const [templates, setTemplates] = useState<InfraTemplateMeta[]>([]);
   const [currentTemplate, setCurrentTemplate] = useState<InfraTemplate>(null);
+  const [selectedClusterID, setSelectedClusterID] = useState<number>(null);
   const [currentCredential, setCurrentCredential] = useState<InfraCredentials>(
     null
   );
@@ -79,6 +83,7 @@ const ProvisionInfra: React.FunctionComponent<Props> = () => {
           aws_integration_id: currentCredential["aws_integration_id"],
           do_integration_id: currentCredential["do_integration_id"],
           gcp_integration_id: currentCredential["gcp_integration_id"],
+          cluster_id: selectedClusterID || null,
         },
         {
           project_id: currentProject.id,
@@ -163,6 +168,8 @@ const ProvisionInfra: React.FunctionComponent<Props> = () => {
   };
 
   const renderStepContents = () => {
+    const numSteps = 2 + currentTemplate?.form?.isClusterScoped;
+
     //   // if credentials need to be set and the list doesn't contain the necessary creds,
     //   // render a credentials form
     if (
@@ -172,7 +179,7 @@ const ProvisionInfra: React.FunctionComponent<Props> = () => {
       if (currentTemplate.required_credential == "aws_integration_id") {
         return (
           <ActionContainer>
-            <Heading>Step 1 of 2 - Link AWS Credentials</Heading>
+            <Heading>Step 1 of {numSteps} - Link AWS Credentials</Heading>
             <AWSCredentialsList
               selectCredential={(i) =>
                 setCurrentCredential({
@@ -185,7 +192,7 @@ const ProvisionInfra: React.FunctionComponent<Props> = () => {
       } else if (currentTemplate.required_credential == "gcp_integration_id") {
         return (
           <ActionContainer>
-            <Heading>Step 1 of 2 - Link GCP Credentials</Heading>
+            <Heading>Step 1 of {numSteps} - Link GCP Credentials</Heading>
             <GCPCredentialsList
               selectCredential={(i) =>
                 setCurrentCredential({
@@ -198,7 +205,7 @@ const ProvisionInfra: React.FunctionComponent<Props> = () => {
       } else if (currentTemplate.required_credential == "do_integration_id") {
         return (
           <ActionContainer>
-            <Heading>Step 1 of 2 - Link DO Credentials</Heading>
+            <Heading>Step 1 of {numSteps} - Link DO Credentials</Heading>
             <DOCredentialsList
               selectCredential={(i) =>
                 setCurrentCredential({
@@ -211,9 +218,24 @@ const ProvisionInfra: React.FunctionComponent<Props> = () => {
       }
     }
 
+    if (currentTemplate?.form?.isClusterScoped && !selectedClusterID) {
+      return (
+        <ActionContainer>
+          <Heading>Step 2 of {numSteps} - Select a Cluster</Heading>
+          <ClusterList
+            selectCluster={(cluster_id) => {
+              setSelectedClusterID(cluster_id);
+            }}
+          />
+        </ActionContainer>
+      );
+    }
+
     return (
       <ActionContainer>
-        <Heading>Step 2 of 2 - Configure Settings</Heading>
+        <Heading>
+          Step {numSteps} of {numSteps} - Configure Settings
+        </Heading>
         <FormContainer>
           <PorterFormWrapper
             showStateDebugger={false}

+ 101 - 0
dashboard/src/main/home/infrastructure/components/credentials/ClusterList.tsx

@@ -0,0 +1,101 @@
+import React, { useContext, useEffect, useState } from "react";
+import { Context } from "shared/Context";
+import api from "shared/api";
+import Loading from "components/Loading";
+import Placeholder from "components/Placeholder";
+import Description from "components/Description";
+import { ClusterType } from "shared/types";
+import SelectRow from "components/form-components/SelectRow";
+import SaveButton from "components/SaveButton";
+
+type Props = {
+  selectCluster: (cluster_id: number) => void;
+};
+
+const ClusterList: React.FunctionComponent<Props> = ({ selectCluster }) => {
+  const { currentProject, setCurrentError } = useContext(Context);
+  const [isLoading, setIsLoading] = useState(true);
+  const [clusters, setClusters] = useState<ClusterType[]>([]);
+  const [selectedClusterID, setSelectedClusterID] = useState<number>();
+  const [hasError, setHasError] = useState(false);
+
+  useEffect(() => {
+    api
+      .getClusters(
+        "<token>",
+        {},
+        {
+          id: currentProject.id,
+        }
+      )
+      .then(({ data }) => {
+        if (!Array.isArray(data)) {
+          throw Error("Data is not an array");
+        }
+
+        setClusters(data);
+        setSelectedClusterID(data[0]?.id);
+        setIsLoading(false);
+      })
+      .catch((err) => {
+        console.error(err);
+        setHasError(true);
+        setCurrentError(err.response?.data?.error);
+        setIsLoading(false);
+      });
+  }, [currentProject]);
+
+  if (hasError) {
+    return <Placeholder>Error</Placeholder>;
+  }
+
+  if (isLoading || !clusters) {
+    return (
+      <Placeholder>
+        <Loading />
+      </Placeholder>
+    );
+  }
+
+  if (clusters.length == 0) {
+    return (
+      <Placeholder>
+        At least one cluster must exist to create this resource
+      </Placeholder>
+    );
+  }
+
+  return (
+    <>
+      <Description>
+        Select your credentials from the list below, or create a new credential:
+      </Description>
+      <SelectRow
+        options={clusters.map((cluster, i) => {
+          return {
+            label: cluster.name,
+            value: "" + cluster.id,
+          };
+        })}
+        width="100%"
+        scrollBuffer={true}
+        value={"" + selectedClusterID}
+        dropdownMaxHeight="240px"
+        setActiveValue={(x: string) => {
+          setSelectedClusterID(parseInt(x));
+        }}
+        label="Cluster Options"
+      />
+      <SaveButton
+        text="Continue"
+        disabled={false}
+        onClick={() => selectCluster(selectedClusterID)}
+        makeFlush={true}
+        clearPosition={true}
+        statusPosition={"right"}
+      />
+    </>
+  );
+};
+
+export default ClusterList;

+ 14 - 2
dashboard/src/shared/api.tsx

@@ -672,12 +672,13 @@ const provisionInfra = baseApi<
     aws_integration_id?: number;
     gcp_integration_id?: number;
     do_integration_id?: number;
+    cluster_id?: number;
   },
   {
     project_id: number;
   }
->("POST", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/infras`;
+>("POST", ({ project_id }) => {
+  return `/api/projects/${project_id}/infras`;
 });
 
 const updateInfra = baseApi<
@@ -774,6 +775,16 @@ const getInfraState = baseApi<
   return `/api/projects/${pathParams.project_id}/infras/${pathParams.infra_id}/state`;
 });
 
+const getInfraRawState = baseApi<
+  {},
+  {
+    project_id: number;
+    infra_id: number;
+  }
+>("GET", (pathParams) => {
+  return `/api/projects/${pathParams.project_id}/infras/${pathParams.infra_id}/raw_state`;
+});
+
 const getInfraByID = baseApi<
   {},
   {
@@ -1610,6 +1621,7 @@ export default {
   retryCreateInfra,
   retryDeleteInfra,
   getInfraState,
+  getInfraRawState,
   getInfraByID,
   getInfraDesired,
   getInfraCurrent,

+ 6 - 1
dashboard/src/shared/types.tsx

@@ -180,7 +180,12 @@ export interface ShowIfNot {
   not: ShowIf;
 }
 
-export type ShowIf = string | ShowIfAnd | ShowIfOr | ShowIfNot;
+export interface ShowIfIs {
+  variable: string;
+  is: string;
+}
+
+export type ShowIf = string | ShowIfIs | ShowIfAnd | ShowIfOr | ShowIfNot;
 
 export interface Section {
   name?: string;

+ 4 - 78
internal/models/infra.go

@@ -39,6 +39,10 @@ type Infra struct {
 	// The ID of the user that created this infra
 	CreatedByUserID uint
 
+	// If this infra was created under a cluster scope (for example, for RDS), the parent cluster
+	// ID
+	ParentClusterID uint
+
 	// Status is the status of the infra
 	Status types.InfraStatus
 
@@ -131,85 +135,7 @@ func (i *Infra) ToInfraType() *types.Infra {
 		AWSIntegrationID: i.AWSIntegrationID,
 		DOIntegrationID:  i.DOIntegrationID,
 		GCPIntegrationID: i.GCPIntegrationID,
-		LastApplied:      i.SafelyGetLastApplied(),
-	}
-}
-
-// SafeGetLastApplied gets non-sensitive values for the last applied configuration
-func (i *Infra) SafelyGetLastApplied() map[string]string {
-	resp := make(map[string]string)
-
-	switch i.Kind {
-	case types.InfraECR:
-		lastApplied := &types.CreateECRInfraRequest{}
-
-		if err := json.Unmarshal(i.LastApplied, lastApplied); err != nil {
-			return resp
-		}
-
-		resp["ecr_name"] = lastApplied.ECRName
-
-		return resp
-	case types.InfraEKS:
-		lastApplied := &types.CreateEKSInfraRequest{}
-
-		if err := json.Unmarshal(i.LastApplied, lastApplied); err != nil {
-			return resp
-		}
-
-		resp["eks_name"] = lastApplied.EKSName
-		resp["machine_type"] = lastApplied.MachineType
-
-		return resp
-	case types.InfraGCR:
-		return resp
-	case types.InfraGKE:
-		lastApplied := &types.CreateGKEInfraRequest{}
-
-		if err := json.Unmarshal(i.LastApplied, lastApplied); err != nil {
-			return resp
-		}
-
-		resp["gke_name"] = lastApplied.GKEName
-
-		return resp
-	case types.InfraDOCR:
-		lastApplied := &types.CreateDOCRInfraRequest{}
-
-		if err := json.Unmarshal(i.LastApplied, lastApplied); err != nil {
-			return resp
-		}
-
-		resp["docr_name"] = lastApplied.DOCRName
-		resp["docr_subscription_tier"] = lastApplied.DOCRSubscriptionTier
-
-		return resp
-	case types.InfraDOKS:
-		lastApplied := &types.CreateDOKSInfraRequest{}
-
-		if err := json.Unmarshal(i.LastApplied, lastApplied); err != nil {
-			return resp
-		}
-
-		resp["cluster_name"] = lastApplied.DOKSName
-		resp["do_region"] = lastApplied.DORegion
-
-		return resp
-	case types.InfraRDS:
-		lastApplied := &types.RDSInfraLastApplied{}
-
-		if err := json.Unmarshal(i.LastApplied, lastApplied); err != nil {
-			return resp
-		}
-
-		resp["cluster_id"] = fmt.Sprintf("%d", lastApplied.ClusterID)
-		resp["aws_region"] = lastApplied.AWSRegion
-		resp["db_name"] = lastApplied.DBName
-
-		return resp
 	}
-
-	return resp
 }
 
 // GetID returns the unique id for this infra

+ 0 - 2
internal/templater/parser/parser.go

@@ -104,8 +104,6 @@ func FormYAMLFromBytes(def *ClientConfigDefault, bytes []byte, stateType, contex
 	// if the client config defaults are Helm-related, this is a cluster-scoped resource
 	if def.HelmAgent != nil || def.HelmChart != nil || def.HelmRelease != nil {
 		form.IsClusterScoped = true
-	} else {
-		form.IsClusterScoped = false
 	}
 
 	return form, nil

+ 27 - 0
provisioner/client/get_raw_state.go

@@ -0,0 +1,27 @@
+package client
+
+import (
+	"context"
+	"fmt"
+
+	ptypes "github.com/porter-dev/porter/provisioner/types"
+)
+
+// GetRawState gets the state stored for that infrastructure
+func (c *Client) GetRawState(
+	ctx context.Context,
+	workspaceID string,
+) (*ptypes.ParseableRawTFState, error) {
+	resp := &ptypes.ParseableRawTFState{}
+
+	err := c.getRequest(
+		fmt.Sprintf(
+			"/%s/tfstate",
+			workspaceID,
+		),
+		nil,
+		resp,
+	)
+
+	return resp, err
+}

+ 10 - 5
provisioner/integrations/provisioner/local/local_provisioner.go

@@ -4,6 +4,7 @@ import (
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
+	"os"
 	"os/exec"
 
 	"github.com/porter-dev/porter/internal/models"
@@ -29,22 +30,26 @@ func (l *LocalProvisioner) Provision(opts *provisioner.ProvisionOpts) error {
 	fmt.Println("running local provisioner with workspace id: ", models.GetWorkspaceID(opts.Infra, opts.Operation))
 
 	// TODO: allow cancellation -- this is just to simulate behavior
-	go func() error {
+	go func() {
 		cmdProv := exec.Command("porter-provisioner", string(opts.OperationKind))
-		// cmdProv.Stdout = os.Stdout
-		// cmdProv.Stderr = os.Stderr
+		cmdProv.Stdout = os.Stdout
+		cmdProv.Stderr = os.Stderr
 		env, err := l.getEnv(opts)
+		env = append(env, "PATH=/usr/local/bin:/usr/bin:/bin")
 
 		if err != nil {
-			return err
+			fmt.Println(err)
 		}
 
 		cmdProv.Env = env
 
-		return cmdProv.Run()
+		err = cmdProv.Run()
+
+		fmt.Println(err)
 	}()
 
 	return nil
+
 }
 
 func (l *LocalProvisioner) getEnv(opts *provisioner.ProvisionOpts) ([]string, error) {

+ 19 - 20
provisioner/server/handlers/state/create_resource.go

@@ -129,24 +129,16 @@ func createECRRegistry(config *config.Config, infra *models.Infra, operation *mo
 }
 
 func createRDSDatabase(config *config.Config, infra *models.Infra, operation *models.Operation, output map[string]interface{}) (*models.Database, error) {
-	// parse the last applied field to get the cluster id
-	rdsRequest := &types.RDSInfraLastApplied{}
-	err := json.Unmarshal(operation.LastApplied, rdsRequest)
-
-	if err != nil {
-		return nil, err
-	}
-
 	database := &models.Database{
 		ProjectID:        infra.ProjectID,
-		ClusterID:        rdsRequest.ClusterID,
+		ClusterID:        infra.ParentClusterID,
 		InfraID:          infra.ID,
 		InstanceID:       output["rds_instance_id"].(string),
 		InstanceEndpoint: output["rds_connection_endpoint"].(string),
 		InstanceName:     output["rds_instance_name"].(string),
 	}
 
-	database, err = config.Repo.Database().CreateDatabase(database)
+	database, err := config.Repo.Database().CreateDatabase(database)
 
 	if err != nil {
 		return nil, err
@@ -155,11 +147,18 @@ func createRDSDatabase(config *config.Config, infra *models.Infra, operation *mo
 	infra.DatabaseID = database.ID
 	infra, err = config.Repo.Infra().UpdateInfra(infra)
 
+	if err != nil {
+		return nil, err
+	}
+	lastApplied := make(map[string]interface{})
+
+	err = json.Unmarshal(operation.LastApplied, &lastApplied)
+
 	if err != nil {
 		return nil, err
 	}
 
-	err = createRDSEnvGroup(config, infra, database, rdsRequest)
+	err = createRDSEnvGroup(config, infra, database, lastApplied)
 
 	if err != nil {
 		return nil, err
@@ -282,8 +281,8 @@ func createGKECluster(config *config.Config, infra *models.Infra, operation *mod
 	return config.Repo.Cluster().CreateCluster(cluster)
 }
 
-func createRDSEnvGroup(config *config.Config, infra *models.Infra, database *models.Database, rdsConfig *types.RDSInfraLastApplied) error {
-	cluster, err := config.Repo.Cluster().ReadCluster(infra.ProjectID, rdsConfig.ClusterID)
+func createRDSEnvGroup(config *config.Config, infra *models.Infra, database *models.Database, lastApplied map[string]interface{}) error {
+	cluster, err := config.Repo.Cluster().ReadCluster(infra.ProjectID, infra.ParentClusterID)
 
 	if err != nil {
 		return err
@@ -311,14 +310,14 @@ func createRDSEnvGroup(config *config.Config, infra *models.Infra, database *mod
 	}
 
 	_, err = envgroup.CreateEnvGroup(agent, types.ConfigMapInput{
-		Name:      fmt.Sprintf("rds-credentials-%s", rdsConfig.DBName),
-		Namespace: rdsConfig.Namespace,
+		Name:      fmt.Sprintf("rds-credentials-%s", lastApplied["db_name"].(string)),
+		Namespace: "default",
 		Variables: map[string]string{},
 		SecretVariables: map[string]string{
 			"PGPORT":     port,
 			"PGHOST":     host,
-			"PGPASSWORD": rdsConfig.Password,
-			"PGUSER":     rdsConfig.Username,
+			"PGPASSWORD": lastApplied["db_passwd"].(string),
+			"PGUSER":     lastApplied["db_user"].(string),
 		},
 	})
 
@@ -329,8 +328,8 @@ func createRDSEnvGroup(config *config.Config, infra *models.Infra, database *mod
 	return nil
 }
 
-func deleteRDSEnvGroup(config *config.Config, infra *models.Infra, rdsConfig *types.RDSInfraLastApplied) error {
-	cluster, err := config.Repo.Cluster().ReadCluster(infra.ProjectID, rdsConfig.ClusterID)
+func deleteRDSEnvGroup(config *config.Config, infra *models.Infra, lastApplied map[string]interface{}) error {
+	cluster, err := config.Repo.Cluster().ReadCluster(infra.ProjectID, infra.ParentClusterID)
 
 	if err != nil {
 		return err
@@ -348,7 +347,7 @@ func deleteRDSEnvGroup(config *config.Config, infra *models.Infra, rdsConfig *ty
 		return fmt.Errorf("failed to get agent: %s", err.Error())
 	}
 
-	err = envgroup.DeleteEnvGroup(agent, fmt.Sprintf("rds-credentials-%s", rdsConfig.DBName), rdsConfig.Namespace)
+	err = envgroup.DeleteEnvGroup(agent, fmt.Sprintf("rds-credentials-%s", lastApplied["db_name"].(string)), "default")
 
 	if err != nil {
 		return fmt.Errorf("failed to create RDS env group: %s", err.Error())

+ 2 - 0
provisioner/server/handlers/state/delete_resource.go

@@ -117,5 +117,7 @@ func deleteDatabase(config *config.Config, infra *models.Infra, operation *model
 		return nil, err
 	}
 
+	// TODO: add delete env group here
+
 	return database, nil
 }

+ 22 - 0
provisioner/types/raw_state.go

@@ -10,3 +10,25 @@ type RawTFState struct {
 	Outputs          interface{} `json:"outputs"`
 	Resources        interface{} `json:"resources"`
 }
+
+type ParseableRawTFState struct {
+	Version          int                  `json:"version"`
+	TerraformVersion string               `json:"terraform_version"`
+	Serial           int                  `json:"serial"`
+	Lineage          string               `json:"lineage"`
+	Outputs          interface{}          `json:"outputs"`
+	Resources        []RawTFStateResource `json:"resources"`
+}
+
+type RawTFStateResource struct {
+	Instances []RawTFStateInstance `json:"instances"`
+	Mode      string               `json:"mode"`
+	Name      string               `json:"name"`
+	Provider  string               `json:"provider"`
+	Type      string               `json:"type"`
+}
+
+type RawTFStateInstance struct {
+	Attributes   map[string]interface{} `json:"attributes"`
+	Dependencies []string               `json:"dependencies"`
+}