Quellcode durchsuchen

Merge branch 'beta.3.gcp-provisioning-integration' of https://github.com/porter-dev/porter into beta.3.do-provisioning-integration

mergin w gcp
Alexander Belanger vor 5 Jahren
Ursprung
Commit
475a56d2ab

+ 1 - 1
cmd/migrate/main.go

@@ -36,7 +36,7 @@ func main() {
 		&models.Cluster{},
 		&models.ClusterCandidate{},
 		&models.ClusterResolver{},
-		&models.AWSInfra{},
+		&models.Infra{},
 		&ints.KubeIntegration{},
 		&ints.BasicIntegration{},
 		&ints.OIDCIntegration{},

+ 51 - 8
internal/forms/infra.go

@@ -17,10 +17,10 @@ type CreateECRInfra struct {
 	AWSIntegrationID uint   `json:"aws_integration_id" form:"required"`
 }
 
-// ToAWSInfra converts the form to a gorm aws infra model
-func (ce *CreateECRInfra) ToAWSInfra() (*models.AWSInfra, error) {
-	return &models.AWSInfra{
-		Kind:             models.AWSInfraECR,
+// ToInfra converts the form to a gorm aws infra model
+func (ce *CreateECRInfra) ToInfra() (*models.Infra, error) {
+	return &models.Infra{
+		Kind:             models.InfraECR,
 		ProjectID:        ce.ProjectID,
 		Suffix:           stringWithCharset(6, randCharset),
 		Status:           models.StatusCreating,
@@ -36,10 +36,10 @@ type CreateEKSInfra struct {
 	AWSIntegrationID uint   `json:"aws_integration_id" form:"required"`
 }
 
-// ToAWSInfra converts the form to a gorm aws infra model
-func (ce *CreateEKSInfra) ToAWSInfra() (*models.AWSInfra, error) {
-	return &models.AWSInfra{
-		Kind:             models.AWSInfraEKS,
+// ToInfra converts the form to a gorm aws infra model
+func (ce *CreateEKSInfra) ToInfra() (*models.Infra, error) {
+	return &models.Infra{
+		Kind:             models.InfraEKS,
 		ProjectID:        ce.ProjectID,
 		Suffix:           stringWithCharset(6, randCharset),
 		Status:           models.StatusCreating,
@@ -47,6 +47,43 @@ func (ce *CreateEKSInfra) ToAWSInfra() (*models.AWSInfra, error) {
 	}, nil
 }
 
+// CreateGCRInfra represents the accepted values for creating an
+// GCR infra via the provisioning container
+type CreateGCRInfra struct {
+	ProjectID        uint `json:"project_id" form:"required"`
+	GCPIntegrationID uint `json:"gcp_integration_id" form:"required"`
+}
+
+// ToInfra converts the form to a gorm aws infra model
+func (ce *CreateGCRInfra) ToInfra() (*models.Infra, error) {
+	return &models.Infra{
+		Kind:             models.InfraGCR,
+		ProjectID:        ce.ProjectID,
+		Suffix:           stringWithCharset(6, randCharset),
+		Status:           models.StatusCreating,
+		GCPIntegrationID: ce.GCPIntegrationID,
+	}, nil
+}
+
+// CreateGKEInfra represents the accepted values for creating a
+// GKE infra via the provisioning container
+type CreateGKEInfra struct {
+	GKEName          string `json:"gke_name" form:"required"`
+	ProjectID        uint   `json:"project_id" form:"required"`
+	GCPIntegrationID uint   `json:"gcp_integration_id" form:"required"`
+}
+
+// ToInfra converts the form to a gorm aws infra model
+func (ce *CreateGKEInfra) ToInfra() (*models.Infra, error) {
+	return &models.Infra{
+		Kind:             models.InfraGKE,
+		ProjectID:        ce.ProjectID,
+		Suffix:           stringWithCharset(6, randCharset),
+		Status:           models.StatusCreating,
+		GCPIntegrationID: ce.GCPIntegrationID,
+	}, nil
+}
+
 // DestroyECRInfra represents the accepted values for destroying an
 // ECR infra via the provisioning container
 type DestroyECRInfra struct {
@@ -59,6 +96,12 @@ type DestroyEKSInfra struct {
 	EKSName string `json:"eks_name" form:"required"`
 }
 
+// DestroyGKEInfra represents the accepted values for destroying an
+// GKE infra via the provisioning container
+type DestroyGKEInfra struct {
+	GKEName string `json:"gke_name" form:"required"`
+}
+
 // helpers for random string
 var seededRand *rand.Rand = rand.New(
 	rand.NewSource(time.Now().UnixNano()))

+ 64 - 4
internal/kubernetes/agent.go

@@ -12,6 +12,8 @@ import (
 	"github.com/porter-dev/porter/internal/kubernetes/provisioner/aws"
 	"github.com/porter-dev/porter/internal/kubernetes/provisioner/aws/ecr"
 	"github.com/porter-dev/porter/internal/kubernetes/provisioner/aws/eks"
+	"github.com/porter-dev/porter/internal/kubernetes/provisioner/gcp"
+	"github.com/porter-dev/porter/internal/kubernetes/provisioner/gcp/gke"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/models/integrations"
 	"github.com/porter-dev/porter/internal/registry"
@@ -244,12 +246,12 @@ func (a *Agent) ProvisionECR(
 	projectID uint,
 	awsConf *integrations.AWSIntegration,
 	ecrName string,
-	awsInfra *models.AWSInfra,
+	infra *models.Infra,
 	operation provisioner.ProvisionerOperation,
 	pgConf *config.DBConf,
 	redisConf *config.RedisConf,
 ) (*batchv1.Job, error) {
-	id := awsInfra.GetID()
+	id := infra.GetID()
 	prov := &provisioner.Conf{
 		ID:        id,
 		Name:      fmt.Sprintf("prov-%s-%s", id, string(operation)),
@@ -275,12 +277,12 @@ func (a *Agent) ProvisionEKS(
 	projectID uint,
 	awsConf *integrations.AWSIntegration,
 	eksName string,
-	awsInfra *models.AWSInfra,
+	infra *models.Infra,
 	operation provisioner.ProvisionerOperation,
 	pgConf *config.DBConf,
 	redisConf *config.RedisConf,
 ) (*batchv1.Job, error) {
-	id := awsInfra.GetID()
+	id := infra.GetID()
 	prov := &provisioner.Conf{
 		ID:        id,
 		Name:      fmt.Sprintf("prov-%s-%s", id, string(operation)),
@@ -301,6 +303,64 @@ func (a *Agent) ProvisionEKS(
 	return a.provision(prov)
 }
 
+// ProvisionGCR spawns a new provisioning pod that creates a GCR instance
+func (a *Agent) ProvisionGCR(
+	projectID uint,
+	gcpConf *integrations.GCPIntegration,
+	infra *models.Infra,
+	operation provisioner.ProvisionerOperation,
+	pgConf *config.DBConf,
+	redisConf *config.RedisConf,
+) (*batchv1.Job, error) {
+	id := infra.GetID()
+	prov := &provisioner.Conf{
+		ID:        id,
+		Name:      fmt.Sprintf("prov-%s-%s", id, string(operation)),
+		Kind:      provisioner.GCR,
+		Operation: operation,
+		Redis:     redisConf,
+		Postgres:  pgConf,
+		GCP: &gcp.Conf{
+			GCPRegion:    gcpConf.GCPRegion,
+			GCPProjectID: gcpConf.GCPProjectID,
+			GCPKeyData:   string(gcpConf.GCPKeyData),
+		},
+	}
+
+	return a.provision(prov)
+}
+
+// ProvisionGKE spawns a new provisioning pod that creates a GKE instance
+func (a *Agent) ProvisionGKE(
+	projectID uint,
+	gcpConf *integrations.GCPIntegration,
+	gkeName string,
+	infra *models.Infra,
+	operation provisioner.ProvisionerOperation,
+	pgConf *config.DBConf,
+	redisConf *config.RedisConf,
+) (*batchv1.Job, error) {
+	id := infra.GetID()
+	prov := &provisioner.Conf{
+		ID:        id,
+		Name:      fmt.Sprintf("prov-%s-%s", id, string(operation)),
+		Kind:      provisioner.GKE,
+		Operation: operation,
+		Redis:     redisConf,
+		Postgres:  pgConf,
+		GCP: &gcp.Conf{
+			GCPRegion:    gcpConf.GCPRegion,
+			GCPProjectID: gcpConf.GCPProjectID,
+			GCPKeyData:   string(gcpConf.GCPKeyData),
+		},
+		GKE: &gke.Conf{
+			ClusterName: gkeName,
+		},
+	}
+
+	return a.provision(prov)
+}
+
 // ProvisionTest spawns a new provisioning pod that tests provisioning
 func (a *Agent) ProvisionTest(
 	projectID uint,

+ 18 - 0
internal/kubernetes/provisioner/gcp/gke/gke.go

@@ -0,0 +1,18 @@
+package gke
+
+import v1 "k8s.io/api/core/v1"
+
+// Conf is the GKE cluster config required for the provisioner
+type Conf struct {
+	ClusterName string
+}
+
+// AttachGKEEnv adds the relevant GKE env for the provisioner
+func (conf *Conf) AttachGKEEnv(env []v1.EnvVar) []v1.EnvVar {
+	env = append(env, v1.EnvVar{
+		Name:  "GKE_CLUSTER_NAME",
+		Value: conf.ClusterName,
+	})
+
+	return env
+}

+ 62 - 8
internal/kubernetes/provisioner/global_stream.go

@@ -114,7 +114,7 @@ func GlobalStreamListener(
 			kind, projID, infraID, err := models.ParseWorkspaceID(fmt.Sprintf("%v", msg.Values["id"]))
 
 			if fmt.Sprintf("%v", msg.Values["status"]) == "created" {
-				infra, err := repo.AWSInfra.ReadAWSInfra(infraID)
+				infra, err := repo.Infra.ReadInfra(infraID)
 
 				if err != nil {
 					continue
@@ -122,14 +122,14 @@ func GlobalStreamListener(
 
 				infra.Status = models.StatusCreated
 
-				infra, err = repo.AWSInfra.UpdateAWSInfra(infra)
+				infra, err = repo.Infra.UpdateInfra(infra)
 
 				if err != nil {
 					continue
 				}
 
 				// create ECR/EKS
-				if kind == string(models.AWSInfraECR) {
+				if kind == string(models.InfraECR) {
 					reg := &models.Registry{
 						ProjectID:        projID,
 						AWSIntegrationID: infra.AWSIntegrationID,
@@ -170,7 +170,7 @@ func GlobalStreamListener(
 					if err != nil {
 						continue
 					}
-				} else if kind == string(models.AWSInfraEKS) {
+				} else if kind == string(models.InfraEKS) {
 					cluster := &models.Cluster{
 						AuthMechanism:    models.AWS,
 						ProjectID:        projID,
@@ -201,12 +201,66 @@ func GlobalStreamListener(
 
 					cluster, err := repo.Cluster.CreateCluster(cluster)
 
+					if err != nil {
+						continue
+					}
+				} else if kind == string(models.InfraGCR) {
+					reg := &models.Registry{
+						ProjectID:        projID,
+						GCPIntegrationID: infra.GCPIntegrationID,
+						InfraID:          infra.ID,
+						Name:             "gcr-registry",
+					}
+
+					// parse raw data into ECR type
+					dataString, ok := msg.Values["data"].(string)
+
+					if ok {
+						json.Unmarshal([]byte(dataString), reg)
+					}
+
+					reg, err = repo.Registry.CreateRegistry(reg)
+
+					if err != nil {
+						continue
+					}
+				} else if kind == string(models.InfraGKE) {
+					cluster := &models.Cluster{
+						AuthMechanism:    models.GCP,
+						ProjectID:        projID,
+						GCPIntegrationID: infra.GCPIntegrationID,
+						InfraID:          infra.ID,
+					}
+
+					// parse raw data into GKE type
+					dataString, ok := msg.Values["data"].(string)
+
+					if ok {
+						json.Unmarshal([]byte(dataString), cluster)
+					}
+
+					re := regexp.MustCompile(`^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$`)
+
+					// if it matches the base64 regex, decode it
+					caData := string(cluster.CertificateAuthorityData)
+					if re.MatchString(caData) {
+						decoded, err := base64.StdEncoding.DecodeString(caData)
+
+						if err != nil {
+							continue
+						}
+
+						cluster.CertificateAuthorityData = []byte(decoded)
+					}
+
+					cluster, err := repo.Cluster.CreateCluster(cluster)
+
 					if err != nil {
 						continue
 					}
 				}
 			} else if fmt.Sprintf("%v", msg.Values["status"]) == "error" {
-				infra, err := repo.AWSInfra.ReadAWSInfra(infraID)
+				infra, err := repo.Infra.ReadInfra(infraID)
 
 				if err != nil {
 					continue
@@ -214,13 +268,13 @@ func GlobalStreamListener(
 
 				infra.Status = models.StatusError
 
-				infra, err = repo.AWSInfra.UpdateAWSInfra(infra)
+				infra, err = repo.Infra.UpdateInfra(infra)
 
 				if err != nil {
 					continue
 				}
 			} else if fmt.Sprintf("%v", msg.Values["status"]) == "destroyed" {
-				infra, err := repo.AWSInfra.ReadAWSInfra(infraID)
+				infra, err := repo.Infra.ReadInfra(infraID)
 
 				if err != nil {
 					continue
@@ -228,7 +282,7 @@ func GlobalStreamListener(
 
 				infra.Status = models.StatusDestroyed
 
-				infra, err = repo.AWSInfra.UpdateAWSInfra(infra)
+				infra, err = repo.Infra.UpdateInfra(infra)
 
 				if err != nil {
 					continue

+ 10 - 0
internal/kubernetes/provisioner/provisioner.go

@@ -12,6 +12,7 @@ import (
 	"github.com/porter-dev/porter/internal/kubernetes/provisioner/aws/eks"
 
 	"github.com/porter-dev/porter/internal/kubernetes/provisioner/gcp"
+	"github.com/porter-dev/porter/internal/kubernetes/provisioner/gcp/gke"
 
 	"github.com/porter-dev/porter/internal/config"
 )
@@ -25,6 +26,7 @@ const (
 	ECR  InfraOption = "ecr"
 	EKS  InfraOption = "eks"
 	GCR  InfraOption = "gcr"
+	GKE  InfraOption = "gke"
 )
 
 // Conf is the config required to start a provisioner container
@@ -46,6 +48,7 @@ type Conf struct {
 
 	// GKE
 	GCP *gcp.Conf
+	GKE *gke.Conf
 }
 
 type ProvisionerOperation string
@@ -92,6 +95,13 @@ func (conf *Conf) GetProvisionerJobTemplate() (*batchv1.Job, error) {
 		args = []string{operation, "eks"}
 		env = conf.AWS.AttachAWSEnv(env)
 		env = conf.EKS.AttachEKSEnv(env)
+	} else if conf.Kind == GCR {
+		args = []string{operation, "gcr"}
+		env = conf.GCP.AttachGCPEnv(env)
+	} else if conf.Kind == GKE {
+		args = []string{operation, "gke"}
+		env = conf.GCP.AttachGCPEnv(env)
+		env = conf.GKE.AttachGKEEnv(env)
 	}
 
 	return &batchv1.Job{

+ 26 - 21
internal/models/infra.go

@@ -20,22 +20,24 @@ const (
 	StatusDestroyed  InfraStatus = "destroyed"
 )
 
-// AWSInfraKind is the kind that aws infra can be
-type AWSInfraKind string
+// InfraKind is the kind that infra can be
+type InfraKind string
 
-// The supported AWS infra kinds
+// The supported infra kinds
 const (
-	AWSInfraECR AWSInfraKind = "ecr"
-	AWSInfraEKS AWSInfraKind = "eks"
+	InfraECR InfraKind = "ecr"
+	InfraEKS InfraKind = "eks"
+	InfraGCR InfraKind = "gcr"
+	InfraGKE InfraKind = "gke"
 )
 
-// AWSInfra represents the metadata for an infrastructure type provisioned on
-// AWS
-type AWSInfra struct {
+// Infra represents the metadata for an infrastructure type provisioned on
+// Porter
+type Infra struct {
 	gorm.Model
 
 	// The type of infra that was provisioned
-	Kind AWSInfraKind `json:"kind"`
+	Kind InfraKind `json:"kind"`
 
 	// A random 6-byte suffix to ensure workspace/stream ids are unique
 	Suffix string
@@ -48,35 +50,38 @@ type AWSInfra struct {
 
 	// The AWS integration that was used to create the infra
 	AWSIntegrationID uint
+
+	// The GCP integration that was used to create the infra
+	GCPIntegrationID uint
 }
 
-// AWSInfraExternal is an external AWSInfra to be shared over REST
-type AWSInfraExternal struct {
+// InfraExternal is an external Infra to be shared over REST
+type InfraExternal struct {
 	ID uint `json:"id"`
 
 	// The project that this integration belongs to
 	ProjectID uint `json:"project_id"`
 
 	// The type of infra that was provisioned
-	Kind AWSInfraKind `json:"kind"`
+	Kind InfraKind `json:"kind"`
 
 	// Status is the status of the infra
 	Status InfraStatus `json:"status"`
 }
 
-// Externalize generates an external AWSInfra to be shared over REST
-func (ai *AWSInfra) Externalize() *AWSInfraExternal {
-	return &AWSInfraExternal{
-		ID:        ai.ID,
-		ProjectID: ai.ProjectID,
-		Kind:      ai.Kind,
-		Status:    ai.Status,
+// Externalize generates an external Infra to be shared over REST
+func (i *Infra) Externalize() *InfraExternal {
+	return &InfraExternal{
+		ID:        i.ID,
+		ProjectID: i.ProjectID,
+		Kind:      i.Kind,
+		Status:    i.Status,
 	}
 }
 
 // GetID returns the unique id for this infra
-func (ai *AWSInfra) GetID() string {
-	return fmt.Sprintf("%s-%d-%d-%s", ai.Kind, ai.ProjectID, ai.ID, ai.Suffix)
+func (i *Infra) GetID() string {
+	return fmt.Sprintf("%s-%d-%d-%s", i.Kind, i.ProjectID, i.ID, i.Suffix)
 }
 
 // ParseWorkspaceID returns the (kind, projectID, infraID)

+ 1 - 1
internal/models/project.go

@@ -27,7 +27,7 @@ type Project struct {
 	HelmRepos []HelmRepo `json:"helm_repos"`
 
 	// provisioned aws infra
-	AWSInfras []AWSInfra `json:"aws_infras"`
+	Infras []Infra `json:"infras"`
 
 	// auth mechanisms
 	KubeIntegrations  []ints.KubeIntegration  `json:"kube_integrations"`

+ 23 - 23
internal/repository/gorm/helpers_test.go

@@ -13,23 +13,23 @@ import (
 )
 
 type tester struct {
-	repo          *repository.Repository
-	key           *[32]byte
-	dbFileName    string
-	initUsers     []*models.User
-	initProjects  []*models.Project
-	initGRs       []*models.GitRepo
-	initRegs      []*models.Registry
-	initClusters  []*models.Cluster
-	initHRs       []*models.HelmRepo
-	initAWSInfras []*models.AWSInfra
-	initCCs       []*models.ClusterCandidate
-	initKIs       []*ints.KubeIntegration
-	initBasics    []*ints.BasicIntegration
-	initOIDCs     []*ints.OIDCIntegration
-	initOAuths    []*ints.OAuthIntegration
-	initGCPs      []*ints.GCPIntegration
-	initAWSs      []*ints.AWSIntegration
+	repo         *repository.Repository
+	key          *[32]byte
+	dbFileName   string
+	initUsers    []*models.User
+	initProjects []*models.Project
+	initGRs      []*models.GitRepo
+	initRegs     []*models.Registry
+	initClusters []*models.Cluster
+	initHRs      []*models.HelmRepo
+	initInfras   []*models.Infra
+	initCCs      []*models.ClusterCandidate
+	initKIs      []*ints.KubeIntegration
+	initBasics   []*ints.BasicIntegration
+	initOIDCs    []*ints.OIDCIntegration
+	initOAuths   []*ints.OAuthIntegration
+	initGCPs     []*ints.GCPIntegration
+	initAWSs     []*ints.AWSIntegration
 }
 
 func setupTestEnv(tester *tester, t *testing.T) {
@@ -57,7 +57,7 @@ func setupTestEnv(tester *tester, t *testing.T) {
 		&models.Cluster{},
 		&models.ClusterCandidate{},
 		&models.ClusterResolver{},
-		&models.AWSInfra{},
+		&models.Infra{},
 		&ints.KubeIntegration{},
 		&ints.BasicIntegration{},
 		&ints.OIDCIntegration{},
@@ -436,24 +436,24 @@ func initHelmRepo(tester *tester, t *testing.T) {
 	tester.initHRs = append(tester.initHRs, hr)
 }
 
-func initAWSInfra(tester *tester, t *testing.T) {
+func initInfra(tester *tester, t *testing.T) {
 	t.Helper()
 
 	if len(tester.initProjects) == 0 {
 		initProject(tester, t)
 	}
 
-	infra := &models.AWSInfra{
-		Kind:      models.AWSInfraECR,
+	infra := &models.Infra{
+		Kind:      models.InfraECR,
 		ProjectID: tester.initProjects[0].Model.ID,
 		Status:    models.StatusCreated,
 	}
 
-	infra, err := tester.repo.AWSInfra.CreateAWSInfra(infra)
+	infra, err := tester.repo.Infra.CreateInfra(infra)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
 
-	tester.initAWSInfras = append(tester.initAWSInfras, infra)
+	tester.initInfras = append(tester.initInfras, infra)
 }

+ 19 - 19
internal/repository/gorm/infra.go

@@ -6,26 +6,26 @@ import (
 	"gorm.io/gorm"
 )
 
-// AWSInfraRepository uses gorm.DB for querying the database
-type AWSInfraRepository struct {
+// InfraRepository uses gorm.DB for querying the database
+type InfraRepository struct {
 	db *gorm.DB
 }
 
-// NewAWSInfraRepository returns a AWSInfraRepository which uses
+// NewInfraRepository returns a InfraRepository which uses
 // gorm.DB for querying the database
-func NewAWSInfraRepository(db *gorm.DB) repository.AWSInfraRepository {
-	return &AWSInfraRepository{db}
+func NewInfraRepository(db *gorm.DB) repository.InfraRepository {
+	return &InfraRepository{db}
 }
 
-// CreateAWSInfra creates a new aws infra
-func (repo *AWSInfraRepository) CreateAWSInfra(infra *models.AWSInfra) (*models.AWSInfra, error) {
+// CreateInfra creates a new aws infra
+func (repo *InfraRepository) CreateInfra(infra *models.Infra) (*models.Infra, error) {
 	project := &models.Project{}
 
 	if err := repo.db.Where("id = ?", infra.ProjectID).First(&project).Error; err != nil {
 		return nil, err
 	}
 
-	assoc := repo.db.Model(&project).Association("AWSInfras")
+	assoc := repo.db.Model(&project).Association("Infras")
 
 	if assoc.Error != nil {
 		return nil, assoc.Error
@@ -38,9 +38,9 @@ func (repo *AWSInfraRepository) CreateAWSInfra(infra *models.AWSInfra) (*models.
 	return infra, nil
 }
 
-// ReadAWSInfra gets a aws infra specified by a unique id
-func (repo *AWSInfraRepository) ReadAWSInfra(id uint) (*models.AWSInfra, error) {
-	infra := &models.AWSInfra{}
+// ReadInfra gets a aws infra specified by a unique id
+func (repo *InfraRepository) ReadInfra(id uint) (*models.Infra, error) {
+	infra := &models.Infra{}
 
 	if err := repo.db.Where("id = ?", id).First(&infra).Error; err != nil {
 		return nil, err
@@ -49,12 +49,12 @@ func (repo *AWSInfraRepository) ReadAWSInfra(id uint) (*models.AWSInfra, error)
 	return infra, nil
 }
 
-// ListAWSInfrasByProjectID finds all aws infras
+// ListInfrasByProjectID finds all aws infras
 // for a given project id
-func (repo *AWSInfraRepository) ListAWSInfrasByProjectID(
+func (repo *InfraRepository) ListInfrasByProjectID(
 	projectID uint,
-) ([]*models.AWSInfra, error) {
-	infras := []*models.AWSInfra{}
+) ([]*models.Infra, error) {
+	infras := []*models.Infra{}
 
 	if err := repo.db.Where("project_id = ?", projectID).Find(&infras).Error; err != nil {
 		return nil, err
@@ -63,10 +63,10 @@ func (repo *AWSInfraRepository) ListAWSInfrasByProjectID(
 	return infras, nil
 }
 
-// UpdateAWSInfra modifies an existing AWSInfra in the database
-func (repo *AWSInfraRepository) UpdateAWSInfra(
-	ai *models.AWSInfra,
-) (*models.AWSInfra, error) {
+// UpdateInfra modifies an existing Infra in the database
+func (repo *InfraRepository) UpdateInfra(
+	ai *models.Infra,
+) (*models.Infra, error) {
 	if err := repo.db.Save(ai).Error; err != nil {
 		return nil, err
 	}

+ 12 - 12
internal/repository/gorm/infra_test.go

@@ -8,7 +8,7 @@ import (
 	"gorm.io/gorm"
 )
 
-func TestCreateAWSInfra(t *testing.T) {
+func TestCreateInfra(t *testing.T) {
 	tester := &tester{
 		dbFileName: "./porter_create_aws_infra.db",
 	}
@@ -17,19 +17,19 @@ func TestCreateAWSInfra(t *testing.T) {
 	initProject(tester, t)
 	defer cleanup(tester, t)
 
-	infra := &models.AWSInfra{
-		Kind:      models.AWSInfraECR,
+	infra := &models.Infra{
+		Kind:      models.InfraECR,
 		ProjectID: tester.initProjects[0].Model.ID,
 		Status:    models.StatusCreated,
 	}
 
-	infra, err := tester.repo.AWSInfra.CreateAWSInfra(infra)
+	infra, err := tester.repo.Infra.CreateInfra(infra)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
 
-	infra, err = tester.repo.AWSInfra.ReadAWSInfra(infra.Model.ID)
+	infra, err = tester.repo.Infra.ReadInfra(infra.Model.ID)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -40,8 +40,8 @@ func TestCreateAWSInfra(t *testing.T) {
 		t.Errorf("incorrect registry ID: expected %d, got %d\n", 1, infra.Model.ID)
 	}
 
-	if infra.Kind != models.AWSInfraECR {
-		t.Errorf("incorrect aws infra kind: expected %s, got %s\n", models.AWSInfraECR, infra.Kind)
+	if infra.Kind != models.InfraECR {
+		t.Errorf("incorrect aws infra kind: expected %s, got %s\n", models.InfraECR, infra.Kind)
 	}
 
 	if infra.Status != models.StatusCreated {
@@ -49,17 +49,17 @@ func TestCreateAWSInfra(t *testing.T) {
 	}
 }
 
-func TestListAWSInfrasByProjectID(t *testing.T) {
+func TestListInfrasByProjectID(t *testing.T) {
 	tester := &tester{
 		dbFileName: "./porter_list_aws_infras.db",
 	}
 
 	setupTestEnv(tester, t)
 	initProject(tester, t)
-	initAWSInfra(tester, t)
+	initInfra(tester, t)
 	defer cleanup(tester, t)
 
-	infras, err := tester.repo.AWSInfra.ListAWSInfrasByProjectID(
+	infras, err := tester.repo.Infra.ListInfrasByProjectID(
 		tester.initProjects[0].Model.ID,
 	)
 
@@ -72,7 +72,7 @@ func TestListAWSInfrasByProjectID(t *testing.T) {
 	}
 
 	// make sure data is correct
-	expAWSInfra := models.AWSInfra{
+	expInfra := models.Infra{
 		Kind:      "ecr",
 		ProjectID: tester.initProjects[0].Model.ID,
 		Status:    models.StatusCreated,
@@ -83,7 +83,7 @@ func TestListAWSInfrasByProjectID(t *testing.T) {
 	// reset fields for reflect.DeepEqual
 	infra.Model = gorm.Model{}
 
-	if diff := deep.Equal(expAWSInfra, *infra); diff != nil {
+	if diff := deep.Equal(expInfra, *infra); diff != nil {
 		t.Errorf("incorrect aws infra")
 		t.Error(diff)
 	}

+ 1 - 1
internal/repository/gorm/repository.go

@@ -17,7 +17,7 @@ func NewRepository(db *gorm.DB, key *[32]byte) *repository.Repository {
 		Cluster:          NewClusterRepository(db, key),
 		HelmRepo:         NewHelmRepoRepository(db, key),
 		Registry:         NewRegistryRepository(db, key),
-		AWSInfra:         NewAWSInfraRepository(db),
+		Infra:         NewInfraRepository(db),
 		KubeIntegration:  NewKubeIntegrationRepository(db, key),
 		BasicIntegration: NewBasicIntegrationRepository(db, key),
 		OIDCIntegration:  NewOIDCIntegrationRepository(db, key),

+ 6 - 6
internal/repository/infra.go

@@ -4,10 +4,10 @@ import (
 	"github.com/porter-dev/porter/internal/models"
 )
 
-// AWSInfraRepository represents the set of queries on the AWSInfra model
-type AWSInfraRepository interface {
-	CreateAWSInfra(repo *models.AWSInfra) (*models.AWSInfra, error)
-	ReadAWSInfra(id uint) (*models.AWSInfra, error)
-	ListAWSInfrasByProjectID(projectID uint) ([]*models.AWSInfra, error)
-	UpdateAWSInfra(repo *models.AWSInfra) (*models.AWSInfra, error)
+// InfraRepository represents the set of queries on the Infra model
+type InfraRepository interface {
+	CreateInfra(repo *models.Infra) (*models.Infra, error)
+	ReadInfra(id uint) (*models.Infra, error)
+	ListInfrasByProjectID(projectID uint) ([]*models.Infra, error)
+	UpdateInfra(repo *models.Infra) (*models.Infra, error)
 }

+ 29 - 29
internal/repository/memory/infra.go

@@ -8,62 +8,62 @@ import (
 	"gorm.io/gorm"
 )
 
-// AWSInfraRepository implements repository.AWSInfraRepository
-type AWSInfraRepository struct {
+// InfraRepository implements repository.InfraRepository
+type InfraRepository struct {
 	canQuery  bool
-	awsInfras []*models.AWSInfra
+	infras []*models.Infra
 }
 
-// NewAWSInfraRepository will return errors if canQuery is false
-func NewAWSInfraRepository(canQuery bool) repository.AWSInfraRepository {
-	return &AWSInfraRepository{
+// NewInfraRepository will return errors if canQuery is false
+func NewInfraRepository(canQuery bool) repository.InfraRepository {
+	return &InfraRepository{
 		canQuery,
-		[]*models.AWSInfra{},
+		[]*models.Infra{},
 	}
 }
 
-// CreateAWSInfra creates a new aws infra
-func (repo *AWSInfraRepository) CreateAWSInfra(
-	infra *models.AWSInfra,
-) (*models.AWSInfra, error) {
+// CreateInfra creates a new aws infra
+func (repo *InfraRepository) CreateInfra(
+	infra *models.Infra,
+) (*models.Infra, error) {
 	if !repo.canQuery {
 		return nil, errors.New("Cannot write database")
 	}
 
-	repo.awsInfras = append(repo.awsInfras, infra)
-	infra.ID = uint(len(repo.awsInfras))
+	repo.infras = append(repo.infras, infra)
+	infra.ID = uint(len(repo.infras))
 
 	return infra, nil
 }
 
-// ReadAWSInfra finds a aws infra by id
-func (repo *AWSInfraRepository) ReadAWSInfra(
+// ReadInfra finds a aws infra by id
+func (repo *InfraRepository) ReadInfra(
 	id uint,
-) (*models.AWSInfra, error) {
+) (*models.Infra, error) {
 	if !repo.canQuery {
 		return nil, errors.New("Cannot read from database")
 	}
 
-	if int(id-1) >= len(repo.awsInfras) || repo.awsInfras[id-1] == nil {
+	if int(id-1) >= len(repo.infras) || repo.infras[id-1] == nil {
 		return nil, gorm.ErrRecordNotFound
 	}
 
 	index := int(id - 1)
-	return repo.awsInfras[index], nil
+	return repo.infras[index], nil
 }
 
-// ListAWSInfrasByProjectID finds all aws infras
+// ListInfrasByProjectID finds all aws infras
 // for a given project id
-func (repo *AWSInfraRepository) ListAWSInfrasByProjectID(
+func (repo *InfraRepository) ListInfrasByProjectID(
 	projectID uint,
-) ([]*models.AWSInfra, error) {
+) ([]*models.Infra, error) {
 	if !repo.canQuery {
 		return nil, errors.New("Cannot read from database")
 	}
 
-	res := make([]*models.AWSInfra, 0)
+	res := make([]*models.Infra, 0)
 
-	for _, infra := range repo.awsInfras {
+	for _, infra := range repo.infras {
 		if infra != nil && infra.ProjectID == projectID {
 			res = append(res, infra)
 		}
@@ -72,20 +72,20 @@ func (repo *AWSInfraRepository) ListAWSInfrasByProjectID(
 	return res, nil
 }
 
-// UpdateAWSInfra modifies an existing AWSInfra in the database
-func (repo *AWSInfraRepository) UpdateAWSInfra(
-	ai *models.AWSInfra,
-) (*models.AWSInfra, error) {
+// UpdateInfra modifies an existing Infra in the database
+func (repo *InfraRepository) UpdateInfra(
+	ai *models.Infra,
+) (*models.Infra, error) {
 	if !repo.canQuery {
 		return nil, errors.New("Cannot write database")
 	}
 
-	if int(ai.ID-1) >= len(repo.awsInfras) || repo.awsInfras[ai.ID-1] == nil {
+	if int(ai.ID-1) >= len(repo.infras) || repo.infras[ai.ID-1] == nil {
 		return nil, gorm.ErrRecordNotFound
 	}
 
 	index := int(ai.ID - 1)
-	repo.awsInfras[index] = ai
+	repo.infras[index] = ai
 
 	return ai, nil
 }

+ 1 - 1
internal/repository/repository.go

@@ -10,7 +10,7 @@ type Repository struct {
 	Cluster          ClusterRepository
 	HelmRepo         HelmRepoRepository
 	Registry         RegistryRepository
-	AWSInfra         AWSInfraRepository
+	Infra         InfraRepository
 	KubeIntegration  KubeIntegrationRepository
 	BasicIntegration BasicIntegrationRepository
 	OIDCIntegration  OIDCIntegrationRepository

+ 2 - 2
server/api/infra_handler.go

@@ -18,14 +18,14 @@ func (app *App) HandleListProjectInfra(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	infras, err := app.Repo.AWSInfra.ListAWSInfrasByProjectID(uint(projID))
+	infras, err := app.Repo.Infra.ListInfrasByProjectID(uint(projID))
 
 	if err != nil {
 		app.handleErrorRead(err, ErrProjectDataRead, w)
 		return
 	}
 
-	extInfras := make([]*models.AWSInfraExternal, 0)
+	extInfras := make([]*models.InfraExternal, 0)
 
 	for _, infra := range infras {
 		extInfras = append(extInfras, infra.Externalize())

+ 284 - 21
server/api/provision_handler.go

@@ -74,7 +74,7 @@ func (app *App) HandleProvisionAWSECRInfra(w http.ResponseWriter, r *http.Reques
 	}
 
 	// convert the form to an aws infra instance
-	infra, err := form.ToAWSInfra()
+	infra, err := form.ToInfra()
 
 	if err != nil {
 		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
@@ -82,7 +82,7 @@ func (app *App) HandleProvisionAWSECRInfra(w http.ResponseWriter, r *http.Reques
 	}
 
 	// handle write to the database
-	infra, err = app.Repo.AWSInfra.CreateAWSInfra(infra)
+	infra, err = app.Repo.Infra.CreateInfra(infra)
 
 	if err != nil {
 		app.handleErrorDataWrite(err, w)
@@ -93,7 +93,7 @@ func (app *App) HandleProvisionAWSECRInfra(w http.ResponseWriter, r *http.Reques
 
 	if err != nil {
 		infra.Status = models.StatusError
-		infra, _ = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
 
 		app.handleErrorDataRead(err, w)
 		return
@@ -104,7 +104,7 @@ func (app *App) HandleProvisionAWSECRInfra(w http.ResponseWriter, r *http.Reques
 
 	if err != nil {
 		infra.Status = models.StatusError
-		infra, _ = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
 
 		app.handleErrorDataRead(err, w)
 		return
@@ -122,7 +122,7 @@ func (app *App) HandleProvisionAWSECRInfra(w http.ResponseWriter, r *http.Reques
 
 	if err != nil {
 		infra.Status = models.StatusError
-		infra, _ = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
 
 		app.handleErrorInternal(err, w)
 		return
@@ -151,7 +151,7 @@ func (app *App) HandleDestroyAWSECRInfra(w http.ResponseWriter, r *http.Request)
 	}
 
 	// read infra to get id
-	infra, err := app.Repo.AWSInfra.ReadAWSInfra(uint(infraID))
+	infra, err := app.Repo.Infra.ReadInfra(uint(infraID))
 
 	if err != nil {
 		app.handleErrorDataRead(err, w)
@@ -165,7 +165,7 @@ func (app *App) HandleDestroyAWSECRInfra(w http.ResponseWriter, r *http.Request)
 	// decode from JSON to form value
 	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
 		infra.Status = models.StatusError
-		infra, _ = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
 
 		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
 		return
@@ -174,7 +174,7 @@ func (app *App) HandleDestroyAWSECRInfra(w http.ResponseWriter, r *http.Request)
 	// validate the form
 	if err := app.validator.Struct(form); err != nil {
 		infra.Status = models.StatusError
-		infra, _ = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
 
 		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
 		return
@@ -185,7 +185,7 @@ func (app *App) HandleDestroyAWSECRInfra(w http.ResponseWriter, r *http.Request)
 
 	if err != nil {
 		infra.Status = models.StatusError
-		infra, _ = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
 
 		app.handleErrorDataRead(err, w)
 		return
@@ -193,7 +193,7 @@ func (app *App) HandleDestroyAWSECRInfra(w http.ResponseWriter, r *http.Request)
 
 	// mark infra for deletion
 	infra.Status = models.StatusDestroying
-	infra, err = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+	infra, err = app.Repo.Infra.UpdateInfra(infra)
 
 	if err != nil {
 		app.handleErrorDataWrite(err, w)
@@ -246,7 +246,7 @@ func (app *App) HandleProvisionAWSEKSInfra(w http.ResponseWriter, r *http.Reques
 	}
 
 	// convert the form to an aws infra instance
-	infra, err := form.ToAWSInfra()
+	infra, err := form.ToInfra()
 
 	if err != nil {
 		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
@@ -254,7 +254,7 @@ func (app *App) HandleProvisionAWSEKSInfra(w http.ResponseWriter, r *http.Reques
 	}
 
 	// handle write to the database
-	infra, err = app.Repo.AWSInfra.CreateAWSInfra(infra)
+	infra, err = app.Repo.Infra.CreateInfra(infra)
 
 	if err != nil {
 		app.handleErrorDataWrite(err, w)
@@ -265,7 +265,7 @@ func (app *App) HandleProvisionAWSEKSInfra(w http.ResponseWriter, r *http.Reques
 
 	if err != nil {
 		infra.Status = models.StatusError
-		infra, _ = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
 
 		app.handleErrorDataRead(err, w)
 		return
@@ -276,7 +276,7 @@ func (app *App) HandleProvisionAWSEKSInfra(w http.ResponseWriter, r *http.Reques
 
 	if err != nil {
 		infra.Status = models.StatusError
-		infra, _ = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
 
 		app.handleErrorDataRead(err, w)
 		return
@@ -294,7 +294,7 @@ func (app *App) HandleProvisionAWSEKSInfra(w http.ResponseWriter, r *http.Reques
 
 	if err != nil {
 		infra.Status = models.StatusError
-		infra, _ = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
 
 		app.handleErrorInternal(err, w)
 		return
@@ -323,7 +323,7 @@ func (app *App) HandleDestroyAWSEKSInfra(w http.ResponseWriter, r *http.Request)
 	}
 
 	// read infra to get id
-	infra, err := app.Repo.AWSInfra.ReadAWSInfra(uint(infraID))
+	infra, err := app.Repo.Infra.ReadInfra(uint(infraID))
 
 	if err != nil {
 		app.handleErrorDataRead(err, w)
@@ -337,7 +337,7 @@ func (app *App) HandleDestroyAWSEKSInfra(w http.ResponseWriter, r *http.Request)
 	// decode from JSON to form value
 	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
 		infra.Status = models.StatusError
-		infra, _ = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
 
 		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
 		return
@@ -346,7 +346,7 @@ func (app *App) HandleDestroyAWSEKSInfra(w http.ResponseWriter, r *http.Request)
 	// validate the form
 	if err := app.validator.Struct(form); err != nil {
 		infra.Status = models.StatusError
-		infra, _ = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
 
 		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
 		return
@@ -357,7 +357,7 @@ func (app *App) HandleDestroyAWSEKSInfra(w http.ResponseWriter, r *http.Request)
 
 	if err != nil {
 		infra.Status = models.StatusError
-		infra, _ = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
 
 		app.handleErrorDataRead(err, w)
 		return
@@ -365,7 +365,7 @@ func (app *App) HandleDestroyAWSEKSInfra(w http.ResponseWriter, r *http.Request)
 
 	// mark infra for deletion
 	infra.Status = models.StatusDestroying
-	infra, err = app.Repo.AWSInfra.UpdateAWSInfra(infra)
+	infra, err = app.Repo.Infra.UpdateInfra(infra)
 
 	if err != nil {
 		app.handleErrorDataWrite(err, w)
@@ -392,6 +392,269 @@ func (app *App) HandleDestroyAWSEKSInfra(w http.ResponseWriter, r *http.Request)
 	w.WriteHeader(http.StatusOK)
 }
 
+// HandleProvisionGCPGCRInfra enables GCR for a project
+func (app *App) HandleProvisionGCPGCRInfra(w http.ResponseWriter, r *http.Request) {
+	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
+
+	if err != nil || projID == 0 {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	form := &forms.CreateGCRInfra{
+		ProjectID: uint(projID),
+	}
+
+	// decode from JSON to form value
+	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	// validate the form
+	if err := app.validator.Struct(form); err != nil {
+		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
+		return
+	}
+
+	// convert the form to an aws infra instance
+	infra, err := form.ToInfra()
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	// handle write to the database
+	infra, err = app.Repo.Infra.CreateInfra(infra)
+
+	if err != nil {
+		app.handleErrorDataWrite(err, w)
+		return
+	}
+
+	gcpInt, err := app.Repo.GCPIntegration.ReadGCPIntegration(infra.GCPIntegrationID)
+
+	if err != nil {
+		infra.Status = models.StatusError
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
+
+		app.handleErrorDataRead(err, w)
+		return
+	}
+
+	// launch provisioning pod
+	agent, err := kubernetes.GetAgentInClusterConfig()
+
+	if err != nil {
+		infra.Status = models.StatusError
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
+
+		app.handleErrorDataRead(err, w)
+		return
+	}
+
+	_, err = agent.ProvisionGCR(
+		uint(projID),
+		gcpInt,
+		infra,
+		provisioner.Apply,
+		&app.DBConf,
+		app.RedisConf,
+	)
+
+	if err != nil {
+		infra.Status = models.StatusError
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
+
+		app.handleErrorInternal(err, w)
+		return
+	}
+
+	app.Logger.Info().Msgf("New gcp gcr infra created: %d", infra.ID)
+
+	w.WriteHeader(http.StatusCreated)
+
+	infraExt := infra.Externalize()
+
+	if err := json.NewEncoder(w).Encode(infraExt); err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+}
+
+// HandleProvisionGCPGKEInfra provisions a new GKE instance for a project
+func (app *App) HandleProvisionGCPGKEInfra(w http.ResponseWriter, r *http.Request) {
+	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
+
+	if err != nil || projID == 0 {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	form := &forms.CreateGKEInfra{
+		ProjectID: uint(projID),
+	}
+
+	// decode from JSON to form value
+	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	// validate the form
+	if err := app.validator.Struct(form); err != nil {
+		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
+		return
+	}
+
+	// convert the form to an aws infra instance
+	infra, err := form.ToInfra()
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	// handle write to the database
+	infra, err = app.Repo.Infra.CreateInfra(infra)
+
+	if err != nil {
+		app.handleErrorDataWrite(err, w)
+		return
+	}
+
+	gcpInt, err := app.Repo.GCPIntegration.ReadGCPIntegration(infra.GCPIntegrationID)
+
+	if err != nil {
+		infra.Status = models.StatusError
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
+
+		app.handleErrorDataRead(err, w)
+		return
+	}
+
+	// launch provisioning pod
+	agent, err := kubernetes.GetAgentInClusterConfig()
+
+	if err != nil {
+		infra.Status = models.StatusError
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
+
+		app.handleErrorDataRead(err, w)
+		return
+	}
+
+	_, err = agent.ProvisionGKE(
+		uint(projID),
+		gcpInt,
+		form.GKEName,
+		infra,
+		provisioner.Apply,
+		&app.DBConf,
+		app.RedisConf,
+	)
+
+	if err != nil {
+		infra.Status = models.StatusError
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
+
+		app.handleErrorInternal(err, w)
+		return
+	}
+
+	app.Logger.Info().Msgf("New gcp gke infra created: %d", infra.ID)
+
+	w.WriteHeader(http.StatusCreated)
+
+	infraExt := infra.Externalize()
+
+	if err := json.NewEncoder(w).Encode(infraExt); err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+}
+
+// HandleDestroyGCPGKEInfra destroys gke infra
+func (app *App) HandleDestroyGCPGKEInfra(w http.ResponseWriter, r *http.Request) {
+	// get path parameters
+	infraID, err := strconv.ParseUint(chi.URLParam(r, "infra_id"), 10, 64)
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	// read infra to get id
+	infra, err := app.Repo.Infra.ReadInfra(uint(infraID))
+
+	if err != nil {
+		app.handleErrorDataRead(err, w)
+		return
+	}
+
+	gcpInt, err := app.Repo.GCPIntegration.ReadGCPIntegration(infra.GCPIntegrationID)
+
+	form := &forms.DestroyGKEInfra{}
+
+	// decode from JSON to form value
+	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
+		infra.Status = models.StatusError
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
+
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	// validate the form
+	if err := app.validator.Struct(form); err != nil {
+		infra.Status = models.StatusError
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
+
+		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
+		return
+	}
+
+	// launch provisioning destruction pod
+	agent, err := kubernetes.GetAgentInClusterConfig()
+
+	if err != nil {
+		infra.Status = models.StatusError
+		infra, _ = app.Repo.Infra.UpdateInfra(infra)
+
+		app.handleErrorDataRead(err, w)
+		return
+	}
+
+	// mark infra for deletion
+	infra.Status = models.StatusDestroying
+	infra, err = app.Repo.Infra.UpdateInfra(infra)
+
+	if err != nil {
+		app.handleErrorDataWrite(err, w)
+		return
+	}
+
+	_, err = agent.ProvisionGKE(
+		infra.ProjectID,
+		gcpInt,
+		form.GKEName,
+		infra,
+		provisioner.Destroy,
+		&app.DBConf,
+		app.RedisConf,
+	)
+
+	if err != nil {
+		app.handleErrorInternal(err, w)
+		return
+	}
+
+	app.Logger.Info().Msgf("GCP GKE infra marked for destruction: %d", infra.ID)
+
+	w.WriteHeader(http.StatusOK)
+}
+
 // HandleGetProvisioningLogs returns real-time logs of the provisioning process via websockets
 func (app *App) HandleGetProvisioningLogs(w http.ResponseWriter, r *http.Request) {
 	// get path parameters
@@ -403,7 +666,7 @@ func (app *App) HandleGetProvisioningLogs(w http.ResponseWriter, r *http.Request
 	}
 
 	// read infra to get id
-	infra, err := app.Repo.AWSInfra.ReadAWSInfra(uint(infraID))
+	infra, err := app.Repo.Infra.ReadInfra(uint(infraID))
 
 	if err != nil {
 		app.handleErrorDataRead(err, w)

+ 1 - 1
server/router/middleware/auth.go

@@ -349,7 +349,7 @@ func (auth *Auth) DoesUserHaveInfraAccess(
 			return
 		}
 
-		infras, err := auth.repo.AWSInfra.ListAWSInfrasByProjectID(uint(projID))
+		infras, err := auth.repo.Infra.ListInfrasByProjectID(uint(projID))
 
 		if err != nil {
 			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)

+ 44 - 0
server/router/router.go

@@ -245,6 +245,36 @@ func New(a *api.App) *chi.Mux {
 			),
 		)
 
+		r.Method(
+			"POST",
+			"/projects/{project_id}/provision/gcr",
+			auth.DoesUserHaveProjectAccess(
+				auth.DoesUserHaveGCPIntegrationAccess(
+					requestlog.NewHandler(a.HandleProvisionGCPGCRInfra, l),
+					mw.URLParam,
+					mw.BodyParam,
+					true,
+				),
+				mw.URLParam,
+				mw.ReadAccess,
+			),
+		)
+
+		r.Method(
+			"POST",
+			"/projects/{project_id}/provision/gke",
+			auth.DoesUserHaveProjectAccess(
+				auth.DoesUserHaveGCPIntegrationAccess(
+					requestlog.NewHandler(a.HandleProvisionGCPGKEInfra, l),
+					mw.URLParam,
+					mw.BodyParam,
+					true,
+				),
+				mw.URLParam,
+				mw.ReadAccess,
+			),
+		)
+
 		r.Method(
 			"GET",
 			"/projects/{project_id}/provision/{kind}/{infra_id}/logs",
@@ -301,6 +331,20 @@ func New(a *api.App) *chi.Mux {
 			),
 		)
 
+		r.Method(
+			"POST",
+			"/projects/{project_id}/infra/{infra_id}/gke/destroy",
+			auth.DoesUserHaveProjectAccess(
+				auth.DoesUserHaveInfraAccess(
+					requestlog.NewHandler(a.HandleDestroyGCPGKEInfra, l),
+					mw.URLParam,
+					mw.URLParam,
+				),
+				mw.URLParam,
+				mw.ReadAccess,
+			),
+		)
+
 		// /api/projects/{project_id}/clusters routes
 		r.Method(
 			"GET",