Sfoglia il codice sorgente

Add BuildConfig models, types and DB operations for the API

Mohammed Nafees 4 anni fa
parent
commit
00306a482d

+ 1 - 0
api/server/handlers/gitinstallation/get_buildpack.go

@@ -79,6 +79,7 @@ func (c *GithubGetBuildpackHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	if config != nil {
 		res.Name = "Node.js"
 		res.Runtime = config["runtime"].(string)
+		res.Buildpacks = config["buildpacks"].([]*buildpacks.BuildpackInfo)
 		if res.Runtime != "node-standalone" {
 			res.Config = map[string]interface{}{"scripts": config["scripts"], "node_engine": config["node_engine"]}
 		}

+ 44 - 0
api/server/handlers/release/create.go

@@ -1,6 +1,7 @@
 package release
 
 import (
+	"encoding/json"
 	"fmt"
 	"net/http"
 	"strings"
@@ -134,6 +135,13 @@ func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		}
 	}
 
+	_, err = createBuildConfig(c.Config(), release, request.BuildConfig)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
 	c.Config().AnalyticsClient.Track(analytics.ApplicationLaunchSuccessTrack(
 		&analytics.ApplicationLaunchSuccessTrackOpts{
 			ApplicationScopedTrackOpts: analytics.GetApplicationScopedTrackOpts(
@@ -306,6 +314,42 @@ func createGitAction(
 	return ga.ToGitActionConfigType(), workflowYAML, nil
 }
 
+func createBuildConfig(
+	config *config.Config,
+	release *models.Release,
+	bcRequest *types.CreateBuildConfigRequest,
+) (*types.BuildConfig, error) {
+	data, err := json.Marshal(bcRequest.Config)
+	if err != nil {
+		return nil, err
+	}
+
+	buildpacks, err := json.Marshal(bcRequest.Buildpacks)
+	if err != nil {
+		return nil, err
+	}
+
+	// handle write to the database
+	bc, err := config.Repo.BuildConfig().CreateBuildConfig(&models.BuildConfig{
+		Name:       bcRequest.Name,
+		Runtime:    bcRequest.Runtime,
+		Buildpacks: buildpacks,
+		Config:     data,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	release.BuildConfig = bc.ID
+
+	_, err = config.Repo.Release().UpdateRelease(release)
+	if err != nil {
+		return nil, err
+	}
+
+	return bc.ToBuildConfigType(), nil
+}
+
 type containerEnvConfig struct {
 	Container struct {
 		Env struct {

+ 77 - 0
api/server/handlers/release/update_build_config.go

@@ -0,0 +1,77 @@
+package release
+
+import (
+	"encoding/json"
+	"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/server/shared/requestutils"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
+)
+
+type UpdateBuildConfigHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewUpdateBuildConfigHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *UpdateBuildConfigHandler {
+	return &UpdateBuildConfigHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *UpdateBuildConfigHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	name, _ := requestutils.GetURLParamString(r, types.URLParamReleaseName)
+	namespace := r.Context().Value(types.NamespaceScope).(string)
+
+	request := &types.UpdateBuildConfigRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	release, err := c.Repo().Release().ReadRelease(cluster.ID, name, namespace)
+
+	if err != nil {
+		if err == gorm.ErrRecordNotFound {
+			w.WriteHeader(http.StatusNotFound)
+			return
+		}
+
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+	}
+
+	config, err := json.Marshal(request.Config)
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	buildpacks, err := json.Marshal(request.Buildpacks)
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	buildConfig := &models.BuildConfig{
+		Buildpacks: buildpacks,
+		Config:     config,
+	}
+
+	buildConfig.ID = release.BuildConfig
+	buildConfig, err = c.Repo().BuildConfig().UpdateBuildConfig(buildConfig)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+}

+ 30 - 0
api/server/router/release.go

@@ -261,6 +261,36 @@ func getReleaseRoutes(
 		Router:   r,
 	})
 
+	// POST /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases/{name}/buildconfig -> release.NewUpdateBuildConfigHandler
+	updateBuildConfigEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbUpdate,
+			Method: types.HTTPVerbPost,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: "/releases/{name}/buildconfig",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+				types.NamespaceScope,
+			},
+		},
+	)
+
+	updateBuildConfigHandler := release.NewUpdateBuildConfigHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: updateBuildConfigEndpoint,
+		Handler:  updateBuildConfigHandler,
+		Router:   r,
+	})
+
 	// GET /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases/{name}/webhook -> release.NewGetWebhookHandler
 	getWebhookEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

+ 23 - 0
api/types/build_config.go

@@ -0,0 +1,23 @@
+package types
+
+import "github.com/porter-dev/porter/internal/integrations/buildpacks"
+
+// BuildConfig
+type BuildConfig struct {
+	Name       string `json:"name"`
+	Runtime    string `json:"runtime"`
+	Buildpacks []byte `json:"buildpacks"` // FIXME: should be a []string
+	Config     []byte `json:"data"`
+}
+
+type CreateBuildConfigRequest struct {
+	Name       string                      `json:"name" form:"required"`
+	Runtime    string                      `json:"runtime" form:"required"`
+	Buildpacks []*buildpacks.BuildpackInfo `json:"buildpacks"`
+	Config     map[string]interface{}      `json:"config,omitempty"`
+}
+
+type UpdateBuildConfigRequest struct {
+	Buildpacks []*buildpacks.BuildpackInfo `json:"buildpacks"`
+	Config     map[string]interface{}      `json:"config,omitempty"`
+}

+ 6 - 3
api/types/git_installation.go

@@ -1,5 +1,7 @@
 package types
 
+import "github.com/porter-dev/porter/internal/integrations/buildpacks"
+
 type GitInstallation struct {
 	ID uint `json:"id"`
 
@@ -40,9 +42,10 @@ type GetBuildpackRequest struct {
 }
 
 type GetBuildpackResponse struct {
-	Name    string                 `json:"name"`
-	Runtime string                 `json:"runtime"`
-	Config  map[string]interface{} `json:"config"`
+	Name       string                      `json:"name"`
+	Runtime    string                      `json:"runtime"`
+	Buildpacks []*buildpacks.BuildpackInfo `json:"buildpacks"`
+	Config     map[string]interface{}      `json:"config"`
 }
 
 type GetContentsRequest struct {

+ 2 - 0
api/types/release.go

@@ -20,6 +20,7 @@ type PorterRelease struct {
 	LatestVersion   string           `json:"latest_version"`
 	GitActionConfig *GitActionConfig `json:"git_action_config,omitempty"`
 	ImageRepoURI    string           `json:"image_repo_uri"`
+	BuildConfig     *BuildConfig     `json:"build_config"`
 }
 
 type GetReleaseResponse Release
@@ -45,6 +46,7 @@ type CreateReleaseRequest struct {
 
 	ImageURL           string                        `json:"image_url" form:"required"`
 	GithubActionConfig *CreateGitActionConfigRequest `json:"github_action_config,omitempty"`
+	BuildConfig        *CreateBuildConfigRequest     `json:"build_config" form:"required"`
 }
 
 type CreateAddonRequest struct {

+ 105 - 3
internal/integrations/buildpacks/api_nodejs.go

@@ -4,11 +4,13 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"os"
 	"strings"
 	"sync"
 
 	"github.com/Masterminds/semver/v3"
 	"github.com/google/go-github/github"
+	"github.com/pelletier/go-toml"
 )
 
 var (
@@ -23,11 +25,102 @@ var (
 type apiNodeRuntime struct {
 	ghClient *github.Client
 	wg       sync.WaitGroup
+	packs    map[string]*BuildpackInfo
 }
 
 func NewAPINodeRuntime(client *github.Client) *apiNodeRuntime {
+	packs := make(map[string]*BuildpackInfo)
+
+	repoRelease, _, err := client.Repositories.GetLatestRelease(context.Background(), "paketo-buildpacks", "nodejs")
+	if err != nil {
+		fmt.Printf("Error fetching latest release for packeto-buildpacks/nodejs: %v\n", err)
+		return nil
+	}
+	fileContent, _, _, err := client.Repositories.GetContents(
+		context.Background(), "packeto-buildpacks", "nodejs", "buildpack.toml",
+		&github.RepositoryContentGetOptions{
+			Ref: *repoRelease.TagName,
+		},
+	)
+	if err != nil {
+		fmt.Printf("Error fetching contents of buildpack.toml for packeto-buildpacks/node: %v\n", err)
+		return nil
+	}
+
+	data, err := fileContent.GetContent()
+	if err != nil {
+		fmt.Printf("Error calling GetContent() on buildpack.toml for packeto-buildpacks/node: %v\n", err)
+		return nil
+	}
+
+	buildpackToml, err := toml.Load(data)
+	if err != nil {
+		fmt.Printf("Error while reading %s: %v\n", nodejsTomlFile, err)
+		os.Exit(1)
+	}
+	order := buildpackToml.Get("order").([]*toml.Tree)
+
+	// yarn
+	packs[yarn] = newBuildpackInfo()
+	yarnGroup := order[0].GetArray("group").([]*toml.Tree)
+	for i := 0; i < len(yarnGroup); i++ {
+		packs[yarn].addPack(
+			buildpackOrderGroupInfo{
+				ID:       yarnGroup[i].Get("id").(string),
+				Optional: yarnGroup[i].GetDefault("optional", false).(bool),
+				Version:  yarnGroup[i].Get("version").(string),
+			},
+		)
+	}
+	packs[yarn].addEnvVar("SSL_CERT_DIR", "")
+	packs[yarn].addEnvVar("SSL_CERT_FILE", "")
+	packs[yarn].addEnvVar("BP_NODE_OPTIMIZE_MEMORY", "")
+	packs[yarn].addEnvVar("BP_NODE_PROJECT_PATH", "")
+	packs[yarn].addEnvVar("BP_NODE_VERSION", "")
+	packs[yarn].addEnvVar("BP_NODE_RUN_SCRIPTS", "")
+
+	// npm
+	packs[npm] = newBuildpackInfo()
+	npmGroup := order[1].GetArray("group").([]*toml.Tree)
+	for i := 0; i < len(npmGroup); i++ {
+		packs[npm].addPack(
+			buildpackOrderGroupInfo{
+				ID:       npmGroup[i].Get("id").(string),
+				Optional: npmGroup[i].GetDefault("optional", false).(bool),
+				Version:  npmGroup[i].Get("version").(string),
+			},
+		)
+	}
+	packs[npm].addEnvVar("SSL_CERT_DIR", "")
+	packs[npm].addEnvVar("SSL_CERT_FILE", "")
+	packs[npm].addEnvVar("BP_NODE_OPTIMIZE_MEMORY", "")
+	packs[npm].addEnvVar("BP_NODE_PROJECT_PATH", "")
+	packs[npm].addEnvVar("BP_NODE_VERSION", "")
+	packs[npm].addEnvVar("BP_NODE_RUN_SCRIPTS", "")
+
+	// no package manager
+	packs[standalone] = newBuildpackInfo()
+	standaloneGroup := order[2].GetArray("group").([]*toml.Tree)
+	for i := 0; i < len(standaloneGroup); i++ {
+		packs[standalone].addPack(
+			buildpackOrderGroupInfo{
+				ID:       standaloneGroup[i].Get("id").(string),
+				Optional: standaloneGroup[i].GetDefault("optional", false).(bool),
+				Version:  standaloneGroup[i].Get("version").(string),
+			},
+		)
+	}
+	packs[standalone].addEnvVar("SSL_CERT_DIR", "")
+	packs[standalone].addEnvVar("SSL_CERT_FILE", "")
+	packs[standalone].addEnvVar("BP_NODE_OPTIMIZE_MEMORY", "")
+	packs[standalone].addEnvVar("BP_NODE_PROJECT_PATH", "")
+	packs[standalone].addEnvVar("BP_NODE_VERSION", "")
+	packs[standalone].addEnvVar("BP_LAUNCHPOINT", "")
+	packs[standalone].addEnvVar("BP_LIVE_RELOAD_ENABLED", "")
+
 	return &apiNodeRuntime{
 		ghClient: client,
+		packs:    packs,
 	}
 }
 
@@ -318,18 +411,27 @@ func (runtime *apiNodeRuntime) Detect(
 			if detected[yarn] {
 				fmt.Printf("NodeJS yarn runtime detected for %s/%s\n", owner, name)
 				return map[string]interface{}{
-					"runtime": yarn, "scripts": packageJSON.Scripts, "node_engine": packageJSON.Engines.Node,
+					"buildpacks":  runtime.packs[yarn],
+					"runtime":     yarn,
+					"scripts":     packageJSON.Scripts,
+					"node_engine": packageJSON.Engines.Node,
 				}
 			} else {
 				fmt.Printf("NodeJS npm runtime detected for %s/%s\n", owner, name)
 				return map[string]interface{}{
-					"runtime": npm, "scripts": packageJSON.Scripts, "node_engine": packageJSON.Engines.Node,
+					"buildpacks":  runtime.packs[npm],
+					"runtime":     npm,
+					"scripts":     packageJSON.Scripts,
+					"node_engine": packageJSON.Engines.Node,
 				}
 			}
 		}
 
 		fmt.Printf("NodeJS standalone runtime detected for %s/%s\n", owner, name)
-		return map[string]interface{}{"runtime": "node-standalone"}
+		return map[string]interface{}{
+			"buildpacks": runtime.packs[standalone],
+			"runtime":    "node-standalone",
+		}
 	}
 
 	fmt.Printf("No NodeJS runtime detected for %s/%s\n", owner, name)

+ 5 - 5
internal/integrations/buildpacks/common.go

@@ -16,16 +16,16 @@ const (
 )
 
 type buildpackOrderGroupInfo struct {
-	ID       string
-	Optional bool
-	Version  string
+	ID       string `json:"id"`
+	Optional bool   `json:"optional"`
+	Version  string `json:"version"`
 }
 
 type BuildpackInfo struct {
-	Packs []buildpackOrderGroupInfo
+	Packs []buildpackOrderGroupInfo `json:"packs"`
 	// FIXME: env vars for https://github.com/paketo-buildpacks/environment-variables
 	//        and for https://github.com/paketo-buildpacks/image-labels
-	EnvVars map[string]string
+	EnvVars map[string]string `json:"env_vars"`
 }
 
 func newBuildpackInfo() *BuildpackInfo {

+ 24 - 0
internal/models/build_config.go

@@ -0,0 +1,24 @@
+package models
+
+import (
+	"github.com/porter-dev/porter/api/types"
+	"gorm.io/gorm"
+)
+
+type BuildConfig struct {
+	gorm.Model
+
+	Name       string `json:"name"`
+	Runtime    string `json:"runtime"`
+	Buildpacks []byte `json:"buildpacks"` // FIXME: should be a []string
+	Config     []byte `json:"config"`
+}
+
+func (conf *BuildConfig) ToBuildConfigType() *types.BuildConfig {
+	return &types.BuildConfig{
+		Name:       conf.Name,
+		Runtime:    conf.Runtime,
+		Buildpacks: conf.Buildpacks,
+		Config:     conf.Config,
+	}
+}

+ 1 - 0
internal/models/release.go

@@ -24,6 +24,7 @@ type Release struct {
 	GitActionConfig    *GitActionConfig `json:"git_action_config"`
 	EventContainer     uint
 	NotificationConfig uint
+	BuildConfig        uint
 }
 
 func (r *Release) ToReleaseType() *types.PorterRelease {

+ 9 - 0
internal/repository/build_config.go

@@ -0,0 +1,9 @@
+package repository
+
+import "github.com/porter-dev/porter/internal/models"
+
+// BuildConfigRepository represents the set of queries on the BuildConfig model
+type BuildConfigRepository interface {
+	CreateBuildConfig(*models.BuildConfig) (*models.BuildConfig, error)
+	UpdateBuildConfig(*models.BuildConfig) (*models.BuildConfig, error)
+}

+ 37 - 0
internal/repository/gorm/build_config.go

@@ -0,0 +1,37 @@
+package gorm
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// BuildConfigRepository uses gorm.DB for querying the database
+type BuildConfigRepository struct {
+	db *gorm.DB
+}
+
+// NewBuildConfigRepository returns a BuildConfigRepository which uses
+// gorm.DB for querying the database. It accepts an encryption key to encrypt
+// sensitive data
+func NewBuildConfigRepository(db *gorm.DB) repository.BuildConfigRepository {
+	return &BuildConfigRepository{db}
+}
+
+// CreateBuildConfig creates a new build config for a release
+func (repo *BuildConfigRepository) CreateBuildConfig(bc *models.BuildConfig) (*models.BuildConfig, error) {
+	if err := repo.db.Create(bc).Error; err != nil {
+		return nil, err
+	}
+
+	return bc, nil
+}
+
+// UpdateBuildConfig updates a build config
+func (repo *BuildConfigRepository) UpdateBuildConfig(bc *models.BuildConfig) (*models.BuildConfig, error) {
+	if err := repo.db.Save(bc).Error; err != nil {
+		return nil, err
+	}
+
+	return bc, nil
+}

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

@@ -33,6 +33,7 @@ func AutoMigrate(db *gorm.DB) error {
 		&models.ProjectUsageCache{},
 		&models.Onboarding{},
 		&models.CredentialsExchangeToken{},
+		&models.BuildConfig{},
 		&ints.KubeIntegration{},
 		&ints.BasicIntegration{},
 		&ints.OIDCIntegration{},

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

@@ -35,6 +35,7 @@ type GormRepository struct {
 	projectUsage              repository.ProjectUsageRepository
 	onboarding                repository.ProjectOnboardingRepository
 	ceToken                   repository.CredentialsExchangeTokenRepository
+	buildConfig               repository.BuildConfigRepository
 }
 
 func (t *GormRepository) User() repository.UserRepository {
@@ -149,6 +150,10 @@ func (t *GormRepository) CredentialsExchangeToken() repository.CredentialsExchan
 	return t.ceToken
 }
 
+func (t *GormRepository) BuildConfig() repository.BuildConfigRepository {
+	return t.buildConfig
+}
+
 // NewRepository returns a Repository which persists users in memory
 // and accepts a parameter that can trigger read/write errors
 func NewRepository(db *gorm.DB, key *[32]byte, storageBackend credentials.CredentialStorage) repository.Repository {
@@ -181,5 +186,6 @@ func NewRepository(db *gorm.DB, key *[32]byte, storageBackend credentials.Creden
 		projectUsage:              NewProjectUsageRepository(db),
 		onboarding:                NewProjectOnboardingRepository(db),
 		ceToken:                   NewCredentialsExchangeTokenRepository(db),
+		buildConfig:               NewBuildConfigRepository(db),
 	}
 }

+ 1 - 0
internal/repository/repository.go

@@ -29,4 +29,5 @@ type Repository interface {
 	ProjectUsage() ProjectUsageRepository
 	Onboarding() ProjectOnboardingRepository
 	CredentialsExchangeToken() CredentialsExchangeTokenRepository
+	BuildConfig() BuildConfigRepository
 }

+ 35 - 0
internal/repository/test/build_config.go

@@ -0,0 +1,35 @@
+package test
+
+import (
+	"errors"
+
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+type BuildConfigRepository struct {
+	canQuery     bool
+	buildConfigs []*models.BuildConfig
+}
+
+func NewBuildConfigRepository(canQuery bool) repository.BuildConfigRepository {
+	return &BuildConfigRepository{canQuery, []*models.BuildConfig{}}
+}
+
+func (repo *BuildConfigRepository) CreateBuildConfig(
+	a *models.BuildConfig,
+) (*models.BuildConfig, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	repo.buildConfigs = append(repo.buildConfigs, a)
+	a.ID = uint(len(repo.buildConfigs))
+
+	return a, nil
+}
+
+func (repo *BuildConfigRepository) UpdateBuildConfig(bc *models.BuildConfig) (*models.BuildConfig, error) {
+	// TODO
+	return bc, nil
+}

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

@@ -33,6 +33,7 @@ type TestRepository struct {
 	projectUsage              repository.ProjectUsageRepository
 	onboarding                repository.ProjectOnboardingRepository
 	ceToken                   repository.CredentialsExchangeTokenRepository
+	buildConfig               repository.BuildConfigRepository
 }
 
 func (t *TestRepository) User() repository.UserRepository {
@@ -147,6 +148,10 @@ func (t *TestRepository) CredentialsExchangeToken() repository.CredentialsExchan
 	return t.ceToken
 }
 
+func (t *TestRepository) BuildConfig() repository.BuildConfigRepository {
+	return t.buildConfig
+}
+
 // NewRepository returns a Repository which persists users in memory
 // and accepts a parameter that can trigger read/write errors
 func NewRepository(canQuery bool, failingMethods ...string) repository.Repository {
@@ -179,5 +184,6 @@ func NewRepository(canQuery bool, failingMethods ...string) repository.Repositor
 		projectUsage:              NewProjectUsageRepository(canQuery),
 		onboarding:                NewProjectOnboardingRepository(canQuery),
 		ceToken:                   NewCredentialsExchangeTokenRepository(canQuery),
+		buildConfig:               NewBuildConfigRepository(canQuery),
 	}
 }