Răsfoiți Sursa

New API runtime for Go

Mohammed Nafees 4 ani în urmă
părinte
comite
77e00957c3

+ 19 - 10
api/server/handlers/gitinstallation/get_buildpack.go

@@ -3,6 +3,7 @@ package gitinstallation
 import (
 	"context"
 	"net/http"
+	"sync"
 
 	"github.com/google/go-github/github"
 	"github.com/porter-dev/porter/api/server/authz"
@@ -72,18 +73,26 @@ func (c *GithubGetBuildpackHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 		return
 	}
 
-	res := &types.GetBuildpackResponse{}
+	var wg sync.WaitGroup
+	wg.Add(len(buildpacks.APIRuntimes))
+	detectResults := make(chan *buildpacks.RuntimeResponse, len(buildpacks.APIRuntimes))
+	for i := range buildpacks.APIRuntimes {
+		go func(idx int) {
+			detectResults <- buildpacks.APIRuntimes[idx].Detect(
+				client, directoryContents, owner, name, request.Dir, repoContentOptions,
+			)
+			wg.Done()
+		}(i)
+	}
+	wg.Wait()
+	close(detectResults)
 
-	nodeRuntime := buildpacks.NewAPINodeRuntime(client)
-	config := nodeRuntime.Detect(directoryContents, owner, name, request.Dir, repoContentOptions)
-	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"]}
+	var matches []*buildpacks.RuntimeResponse
+	for detectRes := range detectResults {
+		if detectRes != nil {
+			matches = append(matches, detectRes)
 		}
 	}
 
-	c.WriteResult(w, r, res)
+	c.WriteResult(w, r, matches)
 }

+ 0 - 9
api/types/git_installation.go

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

+ 1 - 0
go.mod

@@ -42,6 +42,7 @@ require (
 	github.com/paketo-buildpacks/conda-env-update v0.2.2
 	github.com/paketo-buildpacks/dep-ensure v0.1.1
 	github.com/paketo-buildpacks/go-mod-vendor v0.3.1
+	github.com/paketo-buildpacks/node-engine v0.10.0
 	github.com/paketo-buildpacks/node-run-script v0.2.0
 	github.com/paketo-buildpacks/node-start v0.5.0
 	github.com/paketo-buildpacks/npm-install v0.5.0

+ 2 - 0
go.sum

@@ -1058,6 +1058,8 @@ github.com/paketo-buildpacks/dep-ensure v0.1.1 h1:/vPTihH/2Z7paIHzwrWkMeuOGtXxyv
 github.com/paketo-buildpacks/dep-ensure v0.1.1/go.mod h1:r0bjiaauIgEdXKU+FDgvseqTAjWXYuOGXi2omjRJkYc=
 github.com/paketo-buildpacks/go-mod-vendor v0.3.1 h1:4ltB7mmMg2dGRNDLxwPMLoY98rePUhny/6L0QUn8aYU=
 github.com/paketo-buildpacks/go-mod-vendor v0.3.1/go.mod h1:ycd4yAggQShyoQg+bXZyaxazx/Nms98f1SH9NkG/I3k=
+github.com/paketo-buildpacks/node-engine v0.10.0 h1:mY1bbd0QIQRUAS0NE9V8ktc3JGnq+eSO9CL+C1Rd+bw=
+github.com/paketo-buildpacks/node-engine v0.10.0/go.mod h1:CUppwO2je0YQsN8VX+DgJwG0cyUNkbjVoOWFoeJ0A5M=
 github.com/paketo-buildpacks/node-run-script v0.2.0 h1:IAmnpavIuBM49RaKDIK/OdRbo12TfdUCzJ88CvYe4Kk=
 github.com/paketo-buildpacks/node-run-script v0.2.0/go.mod h1:5SexxOVrn+IfafiZthaFNUo/DM0ugOA83kWAffkPbVE=
 github.com/paketo-buildpacks/node-start v0.5.0 h1:R9FXIiXYaTig6PlG335GDoCR2v9z9M6d3eLdiI/T5LU=

+ 97 - 7
internal/integrations/buildpacks/api_go.go

@@ -1,27 +1,117 @@
 package buildpacks
 
 import (
+	"fmt"
 	"sync"
 
 	"github.com/google/go-github/github"
 )
 
 type apiGoRuntime struct {
-	ghClient *github.Client
-	wg       sync.WaitGroup
+	wg sync.WaitGroup
 }
 
-func NewAPIGoRuntime(client *github.Client) *apiGoRuntime {
-	return &apiGoRuntime{
-		ghClient: client,
+func NewAPIGoRuntime() APIRuntime {
+	return &apiGoRuntime{}
+}
+
+func (runtime *apiGoRuntime) detectMod(results chan struct {
+	string
+	bool
+}, directoryContent []*github.RepositoryContent) {
+	goModFound := false
+	for i := 0; i < len(directoryContent); i++ {
+		name := directoryContent[i].GetName()
+		if name == "go.mod" {
+			goModFound = true
+		}
+	}
+	if goModFound {
+		results <- struct {
+			string
+			bool
+		}{mod, true}
+	} else {
+		results <- struct {
+			string
+			bool
+		}{mod, false}
+	}
+}
+
+func (runtime *apiGoRuntime) detectDep(results chan struct {
+	string
+	bool
+}, directoryContent []*github.RepositoryContent) {
+	gopkgFound := false
+	vendorFound := false
+	for i := 0; i < len(directoryContent); i++ {
+		name := directoryContent[i].GetName()
+		if name == "Gopkg.toml" {
+			gopkgFound = true
+		} else if name == "vendor" && directoryContent[i].GetType() == "dir" {
+			vendorFound = true
+		}
+		if gopkgFound && vendorFound {
+			break
+		}
+	}
+	if gopkgFound && vendorFound {
+		results <- struct {
+			string
+			bool
+		}{dep, true}
+	} else {
+		results <- struct {
+			string
+			bool
+		}{dep, false}
 	}
 }
 
 func (runtime *apiGoRuntime) Detect(
+	client *github.Client,
 	directoryContent []*github.RepositoryContent,
-	owner string, name string,
+	owner, name, path string,
 	repoContentOptions github.RepositoryContentGetOptions,
-) map[string]interface{} {
+) *RuntimeResponse {
+	results := make(chan struct {
+		string
+		bool
+	}, 2)
+
+	fmt.Printf("Starting detection for a Go runtime for %s/%s\n", owner, name)
+	runtime.wg.Add(2)
+	fmt.Println("Checking for go-mod")
+	go runtime.detectMod(results, directoryContent)
+	fmt.Println("Checking for dep")
+	go runtime.detectDep(results, directoryContent)
+	runtime.wg.Wait()
+	close(results)
+
+	atLeastOne := false
+	detected := make(map[string]bool)
+	for result := range results {
+		if result.bool {
+			atLeastOne = true
+		}
+		detected[result.string] = result.bool
+	}
+
+	if atLeastOne {
+		// TODO: how to access config values for Go projects
+		if detected[mod] {
+			return &RuntimeResponse{
+				Name:    "Go",
+				Runtime: mod,
+			}
+		} else if detected[dep] {
+			return &RuntimeResponse{
+				Name:    "Go",
+				Runtime: dep,
+			}
+		}
+	}
 
 	return nil
 }

+ 36 - 25
internal/integrations/buildpacks/api_nodejs.go

@@ -23,12 +23,16 @@ var (
 )
 
 type apiNodeRuntime struct {
-	ghClient *github.Client
-	wg       sync.WaitGroup
-	packs    map[string]*BuildpackInfo
+	wg    sync.WaitGroup
+	packs map[string]*BuildpackInfo
 }
 
-func NewAPINodeRuntime(client *github.Client) *apiNodeRuntime {
+func NewAPINodeRuntime() APIRuntime {
+	return &apiNodeRuntime{}
+}
+
+// FIXME: should be called once at the top-level somewhere in the backend
+func populatePacks(client *github.Client) map[string]*BuildpackInfo {
 	packs := make(map[string]*BuildpackInfo)
 
 	repoRelease, _, err := client.Repositories.GetLatestRelease(context.Background(), "paketo-buildpacks", "nodejs")
@@ -118,10 +122,7 @@ func NewAPINodeRuntime(client *github.Client) *apiNodeRuntime {
 	packs[standalone].addEnvVar("BP_LAUNCHPOINT", "")
 	packs[standalone].addEnvVar("BP_LIVE_RELOAD_ENABLED", "")
 
-	return &apiNodeRuntime{
-		ghClient: client,
-		packs:    packs,
-	}
+	return packs
 }
 
 func (runtime *apiNodeRuntime) detectYarn(results chan struct {
@@ -269,10 +270,13 @@ func validateNodeVersion(content string) (string, error) {
 }
 
 func (runtime *apiNodeRuntime) Detect(
+	client *github.Client,
 	directoryContent []*github.RepositoryContent,
 	owner, name, path string,
 	repoContentOptions github.RepositoryContentGetOptions,
-) map[string]interface{} {
+) *RuntimeResponse {
+	runtime.packs = populatePacks(client)
+
 	results := make(chan struct {
 		string
 		bool
@@ -302,7 +306,7 @@ func (runtime *apiNodeRuntime) Detect(
 		if detected[yarn] || detected[npm] {
 			// it is safe to assume that the project contains a package.json
 			fmt.Println("package.json file detected")
-			fileContent, _, _, err := runtime.ghClient.Repositories.GetContents(
+			fileContent, _, _, err := client.Repositories.GetContents(
 				context.Background(),
 				owner,
 				name,
@@ -346,7 +350,7 @@ func (runtime *apiNodeRuntime) Detect(
 
 				if nvmrcFound {
 					// copy exact behavior of https://github.com/paketo-buildpacks/node-engine/blob/main/nvmrc_parser.go
-					fileContent, _, _, err = runtime.ghClient.Repositories.GetContents(
+					fileContent, _, _, err = client.Repositories.GetContents(
 						context.Background(),
 						owner,
 						name,
@@ -376,7 +380,7 @@ func (runtime *apiNodeRuntime) Detect(
 
 				if packageJSON.Engines.Node == "" && nodeVersionFound {
 					// copy exact behavior of https://github.com/paketo-buildpacks/node-engine/blob/main/node_version_parser.go
-					fileContent, _, _, err = runtime.ghClient.Repositories.GetContents(
+					fileContent, _, _, err = client.Repositories.GetContents(
 						context.Background(),
 						owner,
 						name,
@@ -410,27 +414,34 @@ func (runtime *apiNodeRuntime) Detect(
 
 			if detected[yarn] {
 				fmt.Printf("NodeJS yarn runtime detected for %s/%s\n", owner, name)
-				return map[string]interface{}{
-					"buildpacks":  runtime.packs[yarn],
-					"runtime":     yarn,
-					"scripts":     packageJSON.Scripts,
-					"node_engine": packageJSON.Engines.Node,
+				return &RuntimeResponse{
+					Name:       "Node.js",
+					Buildpacks: runtime.packs[yarn],
+					Runtime:    yarn,
+					Config: map[string]interface{}{
+						"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{}{
-					"buildpacks":  runtime.packs[npm],
-					"runtime":     npm,
-					"scripts":     packageJSON.Scripts,
-					"node_engine": packageJSON.Engines.Node,
+				return &RuntimeResponse{
+					Name:       "Node.js",
+					Buildpacks: runtime.packs[npm],
+					Runtime:    npm,
+					Config: map[string]interface{}{
+						"scripts":     packageJSON.Scripts,
+						"node_engine": packageJSON.Engines.Node,
+					},
 				}
 			}
 		}
 
 		fmt.Printf("NodeJS standalone runtime detected for %s/%s\n", owner, name)
-		return map[string]interface{}{
-			"buildpacks": runtime.packs[standalone],
-			"runtime":    "node-standalone",
+		return &RuntimeResponse{
+			Name:       "Node.js",
+			Buildpacks: runtime.packs[standalone],
+			Runtime:    standalone,
 		}
 	}
 

+ 1 - 0
internal/integrations/buildpacks/api_python.go

@@ -0,0 +1 @@
+package buildpacks

+ 6 - 24
internal/integrations/buildpacks/cli_go.go

@@ -9,25 +9,15 @@ import (
 )
 
 type cliGoRuntime struct {
-	packs map[string]*BuildpackInfo
-	wg    sync.WaitGroup
+	wg sync.WaitGroup
 }
 
-func NewCLIGoRuntime() *cliGoRuntime {
-	packs := make(map[string]*BuildpackInfo)
+func NewCLIGoRuntime() CLIRuntime {
+	// adding packs to the Go runtime does not make sense
+	// since we will be using a Packaeto builder that
+	// already comes with the Go buildpack
 
-	// mod
-	packs[mod] = newBuildpackInfo()
-
-	// dep
-	packs[dep] = newBuildpackInfo()
-
-	// go build
-	packs[standalone] = newBuildpackInfo()
-
-	return &cliGoRuntime{
-		packs: packs,
-	}
+	return &cliGoRuntime{}
 }
 
 func (runtime *cliGoRuntime) detectMod(results chan struct {
@@ -75,13 +65,6 @@ func (runtime *cliGoRuntime) detectDep(results chan struct {
 	runtime.wg.Done()
 }
 
-func (runtime *cliGoRuntime) detectStandalone(results chan struct {
-	string
-	bool
-}, workingDir string) {
-	runtime.wg.Done()
-}
-
 func (runtime *cliGoRuntime) Detect(workingDir string) (BuildpackInfo, map[string]interface{}) {
 	results := make(chan struct {
 		string
@@ -91,7 +74,6 @@ func (runtime *cliGoRuntime) Detect(workingDir string) (BuildpackInfo, map[strin
 	runtime.wg.Add(3)
 	go runtime.detectMod(results, workingDir)
 	go runtime.detectDep(results, workingDir)
-	go runtime.detectStandalone(results, workingDir)
 	runtime.wg.Wait()
 	close(results)
 

+ 25 - 3
internal/integrations/buildpacks/cli_nodejs.go

@@ -6,6 +6,7 @@ import (
 	"path/filepath"
 	"sync"
 
+	nodeengine "github.com/paketo-buildpacks/node-engine"
 	noderunscript "github.com/paketo-buildpacks/node-run-script"
 	nodestart "github.com/paketo-buildpacks/node-start"
 	npminstall "github.com/paketo-buildpacks/npm-install"
@@ -21,7 +22,7 @@ type cliNodeRuntime struct {
 	wg    sync.WaitGroup
 }
 
-func NewCLINodeRuntime() *cliNodeRuntime {
+func NewCLINodeRuntime() CLIRuntime {
 	packs := make(map[string]*BuildpackInfo)
 
 	buildpackToml, err := toml.LoadFile(filepath.Join(getExecPath(), nodejsTomlFile))
@@ -191,19 +192,40 @@ func (runtime *cliNodeRuntime) Detect(workingDir string) (BuildpackInfo, map[str
 		if detected[yarn] || detected[npm] {
 			// it is safe to assume that the project contains a package.json
 			packageJSONPath := filepath.Join(workingDir, "package.json")
+			nvmrcPath := filepath.Join(workingDir, ".nvmrc")
+			nodeVersionPath := filepath.Join(workingDir, ".node-version")
 
 			scriptManager := noderunscript.NewScriptManager()
 			scripts, err := scriptManager.GetPackageScripts(workingDir)
 			if err != nil {
 				fmt.Printf("Error reading %s: %v\n", packageJSONPath, err)
-				os.Exit(1)
 			}
 
 			packageJSONParser := npminstall.NewPackageJSONParser()
 			engineVersion, err := packageJSONParser.ParseVersion(packageJSONPath)
 			if err != nil {
 				fmt.Printf("Error reading %s: %v\n", packageJSONPath, err)
-				os.Exit(1)
+			}
+
+			if engineVersion == "" {
+				nvmrcParser := nodeengine.NewNvmrcParser()
+				engineVersion, err = nvmrcParser.ParseVersion(nvmrcPath)
+				if err != nil {
+					fmt.Printf("Error reading %s: %v\n", nvmrcPath, err)
+				}
+			}
+
+			if engineVersion == "" {
+				versionParser := nodeengine.NewNodeVersionParser()
+				engineVersion, err = versionParser.ParseVersion(nodeVersionPath)
+				if err != nil {
+					fmt.Printf("Error reading %s: %v\n", nodeVersionPath, err)
+				}
+			}
+
+			if engineVersion == "" {
+				// taken from https://github.com/paketo-buildpacks/node-engine/blob/main/buildpack.toml
+				engineVersion = "16.*.*"
 			}
 
 			if detected[yarn] {

+ 1 - 0
internal/integrations/buildpacks/cli_python.go

@@ -0,0 +1 @@
+package buildpacks

+ 21 - 1
internal/integrations/buildpacks/common.go → internal/integrations/buildpacks/shared.go

@@ -50,10 +50,30 @@ func getExecPath() string {
 	return filepath.Dir(ex)
 }
 
+type RuntimeResponse struct {
+	Name       string                 `json:"name"`
+	Buildpacks *BuildpackInfo         `json:"buildpacks"`
+	Runtime    string                 `json:"runtime"`
+	Config     map[string]interface{} `json:"config"`
+}
+
 type CLIRuntime interface {
 	Detect(string) (BuildpackInfo, map[string]interface{})
 }
 
 type APIRuntime interface {
-	Detect([]*github.RepositoryContent, string, string, string, github.RepositoryContentGetOptions) map[string]interface{}
+	Detect(
+		*github.Client,
+		[]*github.RepositoryContent,
+		string,
+		string,
+		string,
+		github.RepositoryContentGetOptions,
+	) *RuntimeResponse
+}
+
+// APIRuntimes is a list of all API runtimes
+var APIRuntimes = []APIRuntime{
+	NewAPIGoRuntime(),
+	NewAPINodeRuntime(),
 }