ソースを参照

add gke provisioning

Alexander Belanger 5 年 前
コミット
f05181523e

+ 19 - 0
internal/forms/infra.go

@@ -65,6 +65,25 @@ func (ce *CreateGCRInfra) ToInfra() (*models.Infra, error) {
 	}, 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 {

+ 32 - 0
internal/kubernetes/agent.go

@@ -12,6 +12,7 @@ import (
 	"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"
 
@@ -324,6 +325,37 @@ func (a *Agent) ProvisionGCR(
 	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
+}

+ 34 - 0
internal/kubernetes/provisioner/global_stream.go

@@ -221,6 +221,40 @@ func GlobalStreamListener(
 
 					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
 					}

+ 7 - 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
@@ -90,6 +93,10 @@ func (conf *Conf) GetProvisionerJobTemplate() (*batchv1.Job, error) {
 	} 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{

+ 1 - 0
internal/models/infra.go

@@ -28,6 +28,7 @@ const (
 	InfraECR InfraKind = "ecr"
 	InfraEKS InfraKind = "eks"
 	InfraGCR InfraKind = "gcr"
+	InfraGKE InfraKind = "gke"
 )
 
 // Infra represents the metadata for an infrastructure type provisioned on

+ 92 - 0
server/api/provision_handler.go

@@ -483,6 +483,98 @@ func (app *App) HandleProvisionGCPGCRInfra(w http.ResponseWriter, r *http.Reques
 	}
 }
 
+// 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
+	}
+}
+
 // HandleGetProvisioningLogs returns real-time logs of the provisioning process via websockets
 func (app *App) HandleGetProvisioningLogs(w http.ResponseWriter, r *http.Request) {
 	// get path parameters

+ 15 - 0
server/router/router.go

@@ -244,6 +244,21 @@ func New(a *api.App) *chi.Mux {
 			),
 		)
 
+		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",