Просмотр исходного кода

add environment + deployment create endpoints

Alexander Belanger 4 лет назад
Родитель
Сommit
3eb14e35f0

+ 54 - 0
api/server/handlers/environment/create.go

@@ -0,0 +1,54 @@
+package environment
+
+import (
+	"net/http"
+
+	"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"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/models/integrations"
+)
+
+type CreateEnvironmentHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewCreateEnvironmentHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *CreateEnvironmentHandler {
+	return &CreateEnvironmentHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	ga, _ := r.Context().Value(types.GitInstallationScope).(*integrations.GithubAppInstallation)
+	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	// create the environment
+	request := &types.CreateEnvironmentRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	env, err := c.Repo().Environment().CreateEnvironment(&models.Environment{
+		ProjectID:         project.ID,
+		ClusterID:         cluster.ID,
+		GitInstallationID: uint(ga.InstallationID),
+		Name:              request.Name,
+	})
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	c.WriteResult(w, r, env.ToEnvironmentType())
+}

+ 61 - 0
api/server/handlers/environment/create_deployment.go

@@ -0,0 +1,61 @@
+package environment
+
+import (
+	"net/http"
+
+	"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"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/models/integrations"
+)
+
+type CreateDeploymentHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewCreateDeploymentHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *CreateDeploymentHandler {
+	return &CreateDeploymentHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *CreateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	ga, _ := r.Context().Value(types.GitInstallationScope).(*integrations.GithubAppInstallation)
+	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	request := &types.CreateDeploymentRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	// read the environment to get the environment id
+	env, err := c.Repo().Environment().ReadEnvironment(project.ID, cluster.ID, uint(ga.InstallationID))
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	// create the deployment
+	depl, err := c.Repo().Environment().CreateDeployment(&models.Deployment{
+		EnvironmentID: env.ID,
+		Namespace:     request.Namespace,
+		Status:        "creating",
+	})
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	c.WriteResult(w, r, depl.ToDeploymentType())
+}

+ 70 - 0
api/server/handlers/environment/finalize_deployment.go

@@ -0,0 +1,70 @@
+package environment
+
+import (
+	"net/http"
+
+	"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"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/models/integrations"
+)
+
+type FinalizeDeploymentHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewFinalizeDeploymentHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *FinalizeDeploymentHandler {
+	return &FinalizeDeploymentHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *FinalizeDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	ga, _ := r.Context().Value(types.GitInstallationScope).(*integrations.GithubAppInstallation)
+	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	request := &types.FinalizeDeploymentRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	// read the environment to get the environment id
+	env, err := c.Repo().Environment().ReadEnvironment(project.ID, cluster.ID, uint(ga.InstallationID))
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	// read the deployment
+	depl, err := c.Repo().Environment().ReadDeployment(env.ID, request.Namespace)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	depl.Subdomain = request.Subdomain
+	depl.Status = "created"
+
+	// update the deployment
+	depl, err = c.Repo().Environment().UpdateDeployment(depl)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	// TODO: call Github client here using the env.GitInstallationID which is the installation ID
+
+	c.WriteResult(w, r, depl.ToDeploymentType())
+}

+ 94 - 0
api/server/router/git_installation.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/go-chi/chi"
+	"github.com/porter-dev/porter/api/server/handlers/environment"
 	"github.com/porter-dev/porter/api/server/handlers/gitinstallation"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/config"
@@ -82,6 +83,99 @@ func getGitInstallationRoutes(
 		Router:   r,
 	})
 
+	// POST /api/projects/{project_id}/gitrepos/{git_installation_id}/clusters/{cluster_id} ->
+	// environment.NewCreateEnvironmentHandler
+	createEnvironmentEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbCreate,
+			Method: types.HTTPVerbPost,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/clusters/{cluster_id}/environment",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.GitInstallationScope,
+				types.ClusterScope,
+			},
+		},
+	)
+
+	createEnvironmentHandler := environment.NewCreateEnvironmentHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: createEnvironmentEndpoint,
+		Handler:  createEnvironmentHandler,
+		Router:   r,
+	})
+
+	// POST /api/projects/{project_id}/gitrepos/{git_installation_id}/clusters/{cluster_id}/deployment ->
+	// environment.NewCreateDeploymentHandler
+	createDeploymentEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbCreate,
+			Method: types.HTTPVerbPost,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/clusters/{cluster_id}/deployment",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.GitInstallationScope,
+				types.ClusterScope,
+			},
+		},
+	)
+
+	createDeploymentHandler := environment.NewCreateDeploymentHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: createDeploymentEndpoint,
+		Handler:  createDeploymentHandler,
+		Router:   r,
+	})
+
+	// POST /api/projects/{project_id}/gitrepos/{git_installation_id}/clusters/{cluster_id}/deployment/finalize ->
+	// environment.NewFinalizeDeploymentHandler
+	finalizeDeploymentEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbCreate,
+			Method: types.HTTPVerbPost,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/clusters/{cluster_id}/deployment/finalize",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.GitInstallationScope,
+				types.ClusterScope,
+			},
+		},
+	)
+
+	finalizeDeploymentHandler := environment.NewFinalizeDeploymentHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: finalizeDeploymentEndpoint,
+		Handler:  finalizeDeploymentHandler,
+		Router:   r,
+	})
+
 	// GET /api/projects/{project_id}/gitrepos/{git_installation_id}/repos ->
 	// gitinstallation.GithubListReposHandler
 	listReposEndpoint := factory.NewAPIEndpoint(

+ 31 - 0
api/types/environment.go

@@ -0,0 +1,31 @@
+package types
+
+type Environment struct {
+	ID                uint `json:"id"`
+	ProjectID         uint `json:"project_id"`
+	ClusterID         uint `json:"cluster_id"`
+	GitInstallationID uint `json:"git_installation_id"`
+
+	Name string `json:"name"`
+}
+
+type CreateEnvironmentRequest struct {
+	Name string `json:"name" form:"required"`
+}
+
+type Deployment struct {
+	ID            uint   `json:"id"`
+	EnvironmentID uint   `json:"environment_id"`
+	Namespace     string `json:"namespace"`
+	Status        string `json:"status"`
+	Subdomain     string `json:"subdomain"`
+}
+
+type CreateDeploymentRequest struct {
+	Namespace string `json:"namespace" form:"required"`
+}
+
+type FinalizeDeploymentRequest struct {
+	Namespace string `json:"namespace" form:"required"`
+	Subdomain string `json:"subdomain"`
+}

+ 45 - 0
internal/models/deployment.go

@@ -0,0 +1,45 @@
+package models
+
+import (
+	"github.com/porter-dev/porter/api/types"
+	"gorm.io/gorm"
+)
+
+type Environment struct {
+	gorm.Model
+
+	ProjectID         uint
+	ClusterID         uint
+	GitInstallationID uint
+
+	Name string
+}
+
+func (e *Environment) ToEnvironmentType() *types.Environment {
+	return &types.Environment{
+		ID:                e.Model.ID,
+		ProjectID:         e.ProjectID,
+		ClusterID:         e.ClusterID,
+		GitInstallationID: e.GitInstallationID,
+		Name:              e.Name,
+	}
+}
+
+type Deployment struct {
+	gorm.Model
+
+	EnvironmentID uint
+	Namespace     string
+	Status        string
+	Subdomain     string
+}
+
+func (d *Deployment) ToDeploymentType() *types.Deployment {
+	return &types.Deployment{
+		ID:            d.Model.ID,
+		EnvironmentID: d.EnvironmentID,
+		Namespace:     d.Namespace,
+		Status:        d.Status,
+		Subdomain:     d.Subdomain,
+	}
+}

+ 13 - 0
internal/repository/deployment.go

@@ -0,0 +1,13 @@
+package repository
+
+import "github.com/porter-dev/porter/internal/models"
+
+type EnvironmentRepository interface {
+	CreateEnvironment(env *models.Environment) (*models.Environment, error)
+	ReadEnvironment(projectID, clusterID, gitInstallationID uint) (*models.Environment, error)
+	DeleteEnvironment(env *models.Environment) (*models.Environment, error)
+	CreateDeployment(deployment *models.Deployment) (*models.Deployment, error)
+	ReadDeployment(environmentID uint, namespace string) (*models.Deployment, error)
+	UpdateDeployment(deployment *models.Deployment) (*models.Deployment, error)
+	DeleteDeployment(deployment *models.Deployment) (*models.Deployment, error)
+}

+ 70 - 0
internal/repository/gorm/environment.go

@@ -0,0 +1,70 @@
+package gorm
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// EnvironmentRepository uses gorm.DB for querying the database
+type EnvironmentRepository struct {
+	db *gorm.DB
+}
+
+// NewEnvironmentRepository returns a DefaultEnvironmentRepository which uses
+// gorm.DB for querying the database
+func NewEnvironmentRepository(db *gorm.DB) repository.EnvironmentRepository {
+	return &EnvironmentRepository{db}
+}
+
+func (repo *EnvironmentRepository) CreateEnvironment(env *models.Environment) (*models.Environment, error) {
+	if err := repo.db.Create(env).Error; err != nil {
+		return nil, err
+	}
+	return env, nil
+}
+
+func (repo *EnvironmentRepository) ReadEnvironment(projectID, clusterID, gitInstallationID uint) (*models.Environment, error) {
+	env := &models.Environment{}
+	if err := repo.db.Order("id desc").Where("project_id = ? AND cluster_id = ? AND git_installation_id = ?", projectID, clusterID, gitInstallationID).First(&env).Error; err != nil {
+		return nil, err
+	}
+	return env, nil
+}
+
+func (repo *EnvironmentRepository) DeleteEnvironment(env *models.Environment) (*models.Environment, error) {
+	if err := repo.db.Delete(&env).Error; err != nil {
+		return nil, err
+	}
+	return env, nil
+}
+
+func (repo *EnvironmentRepository) CreateDeployment(deployment *models.Deployment) (*models.Deployment, error) {
+	if err := repo.db.Create(deployment).Error; err != nil {
+		return nil, err
+	}
+	return deployment, nil
+}
+
+func (repo *EnvironmentRepository) UpdateDeployment(deployment *models.Deployment) (*models.Deployment, error) {
+	if err := repo.db.Save(deployment).Error; err != nil {
+		return nil, err
+	}
+
+	return deployment, nil
+}
+
+func (repo *EnvironmentRepository) ReadDeployment(environmentID uint, namespace string) (*models.Deployment, error) {
+	depl := &models.Deployment{}
+	if err := repo.db.Order("id desc").Where("environment_id = ? AND namespace = ?", environmentID).First(&depl).Error; err != nil {
+		return nil, err
+	}
+	return depl, nil
+}
+
+func (repo *EnvironmentRepository) DeleteDeployment(deployment *models.Deployment) (*models.Deployment, error) {
+	if err := repo.db.Delete(deployment).Error; err != nil {
+		return nil, err
+	}
+	return deployment, nil
+}

+ 6 - 0
internal/repository/gorm/repository.go

@@ -17,6 +17,7 @@ type GormRepository struct {
 	gitActionConfig           repository.GitActionConfigRepository
 	invite                    repository.InviteRepository
 	release                   repository.ReleaseRepository
+	environment               repository.EnvironmentRepository
 	authCode                  repository.AuthCodeRepository
 	dnsRecord                 repository.DNSRecordRepository
 	pwResetToken              repository.PWResetTokenRepository
@@ -79,6 +80,10 @@ func (t *GormRepository) Release() repository.ReleaseRepository {
 	return t.release
 }
 
+func (t *GormRepository) Environment() repository.EnvironmentRepository {
+	return t.environment
+}
+
 func (t *GormRepository) AuthCode() repository.AuthCodeRepository {
 	return t.authCode
 }
@@ -173,6 +178,7 @@ func NewRepository(db *gorm.DB, key *[32]byte, storageBackend credentials.Creden
 		gitActionConfig:           NewGitActionConfigRepository(db),
 		invite:                    NewInviteRepository(db),
 		release:                   NewReleaseRepository(db),
+		environment:               NewEnvironmentRepository(db),
 		authCode:                  NewAuthCodeRepository(db),
 		dnsRecord:                 NewDNSRecordRepository(db),
 		pwResetToken:              NewPWResetTokenRepository(db),

+ 1 - 0
internal/repository/repository.go

@@ -4,6 +4,7 @@ type Repository interface {
 	User() UserRepository
 	Project() ProjectRepository
 	Release() ReleaseRepository
+	Environment() EnvironmentRepository
 	Session() SessionRepository
 	GitRepo() GitRepoRepository
 	Cluster() ClusterRepository

+ 44 - 0
internal/repository/test/environment.go

@@ -0,0 +1,44 @@
+package test
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// EnvironmentRepository uses gorm.DB for querying the database
+type EnvironmentRepository struct {
+}
+
+// NewEnvironmentRepository returns a DefaultEnvironmentRepository which uses
+// gorm.DB for querying the database
+func NewEnvironmentRepository() repository.EnvironmentRepository {
+	return &EnvironmentRepository{}
+}
+
+func (repo *EnvironmentRepository) CreateEnvironment(env *models.Environment) (*models.Environment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ReadEnvironment(projectID, clusterID, gitInstallationID uint) (*models.Environment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) DeleteEnvironment(env *models.Environment) (*models.Environment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) CreateDeployment(deployment *models.Deployment) (*models.Deployment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) UpdateDeployment(deployment *models.Deployment) (*models.Deployment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) ReadDeployment(environmentID uint, namespace string) (*models.Deployment, error) {
+	panic("unimplemented")
+}
+
+func (repo *EnvironmentRepository) DeleteDeployment(deployment *models.Deployment) (*models.Deployment, error) {
+	panic("unimplemented")
+}

+ 6 - 0
internal/repository/test/repository.go

@@ -15,6 +15,7 @@ type TestRepository struct {
 	gitActionConfig           repository.GitActionConfigRepository
 	invite                    repository.InviteRepository
 	release                   repository.ReleaseRepository
+	environment               repository.EnvironmentRepository
 	authCode                  repository.AuthCodeRepository
 	dnsRecord                 repository.DNSRecordRepository
 	pwResetToken              repository.PWResetTokenRepository
@@ -77,6 +78,10 @@ func (t *TestRepository) Release() repository.ReleaseRepository {
 	return t.release
 }
 
+func (t *TestRepository) Environment() repository.EnvironmentRepository {
+	return t.environment
+}
+
 func (t *TestRepository) AuthCode() repository.AuthCodeRepository {
 	return t.authCode
 }
@@ -171,6 +176,7 @@ func NewRepository(canQuery bool, failingMethods ...string) repository.Repositor
 		gitActionConfig:           NewGitActionConfigRepository(canQuery),
 		invite:                    NewInviteRepository(canQuery),
 		release:                   NewReleaseRepository(canQuery),
+		environment:               NewEnvironmentRepository(),
 		authCode:                  NewAuthCodeRepository(canQuery),
 		dnsRecord:                 NewDNSRecordRepository(canQuery),
 		pwResetToken:              NewPWResetTokenRepository(canQuery),

+ 34 - 0
services/porter_cli_container/dev.Dockerfile

@@ -0,0 +1,34 @@
+# syntax=docker/dockerfile:1.1.7-experimental
+
+# Base Go environment
+# -------------------
+FROM golang:1.16 as base
+WORKDIR /porter
+
+RUN apt-get update && apt-get install -y gcc musl-dev git
+
+COPY go.mod go.sum Makefile ./
+COPY /cli ./cli
+COPY /internal ./internal
+COPY /api ./api
+COPY /ee ./ee
+
+RUN --mount=type=cache,target=$GOPATH/pkg/mod \
+    go mod download
+
+# Go build environment
+# --------------------
+FROM base AS build-go
+
+ARG version=dev
+
+RUN make build-cli-dev
+
+# Deployment environment
+# ----------------------
+FROM ubuntu:latest
+RUN apt-get update && apt-get install -y ca-certificates git
+
+COPY --from=build-go /porter/bin/porter /bin/porter
+
+ENTRYPOINT ["porter"]