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

API runtimes for Go and Python

Mohammed Nafees 4 лет назад
Родитель
Сommit
173194ad56

+ 13 - 16
internal/integrations/buildpacks/api_go.go

@@ -89,29 +89,26 @@ func (runtime *apiGoRuntime) Detect(
 	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,
-			}
+	// TODO: how to access config values for Go projects
+	if detected[mod] {
+		fmt.Printf("Go mod runtime detected for %s/%s\n", owner, name)
+		return &RuntimeResponse{
+			Name:    "Go",
+			Runtime: mod,
+		}
+	} else if detected[dep] {
+		fmt.Printf("Go dep runtime detected for %s/%s\n", owner, name)
+		return &RuntimeResponse{
+			Name:    "Go",
+			Runtime: dep,
 		}
 	}
 
+	fmt.Printf("No Go runtime detected for %s/%s\n", owner, name)
 	return nil
 }

+ 124 - 130
internal/integrations/buildpacks/api_nodejs.go

@@ -32,7 +32,7 @@ func NewAPINodeRuntime() APIRuntime {
 }
 
 // FIXME: should be called once at the top-level somewhere in the backend
-func populatePacks(client *github.Client) map[string]*BuildpackInfo {
+func populateNodePacks(client *github.Client) map[string]*BuildpackInfo {
 	packs := make(map[string]*BuildpackInfo)
 
 	repoRelease, _, err := client.Repositories.GetLatestRelease(context.Background(), "paketo-buildpacks", "nodejs")
@@ -47,19 +47,19 @@ func populatePacks(client *github.Client) map[string]*BuildpackInfo {
 		},
 	)
 	if err != nil {
-		fmt.Printf("Error fetching contents of buildpack.toml for packeto-buildpacks/node: %v\n", err)
+		fmt.Printf("Error fetching contents of buildpack.toml for packeto-buildpacks/nodejs: %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)
+		fmt.Printf("Error calling GetContent() on buildpack.toml for packeto-buildpacks/nodejs: %v\n", err)
 		return nil
 	}
 
 	buildpackToml, err := toml.Load(data)
 	if err != nil {
-		fmt.Printf("Error while reading %s: %v\n", nodejsTomlFile, err)
+		fmt.Printf("Error while reading buildpack.toml from packeto-buildpacks/nodejs: %v\n", err)
 		os.Exit(1)
 	}
 	order := buildpackToml.Get("order").([]*toml.Tree)
@@ -275,7 +275,7 @@ func (runtime *apiNodeRuntime) Detect(
 	owner, name, path string,
 	repoContentOptions github.RepositoryContentGetOptions,
 ) *RuntimeResponse {
-	runtime.packs = populatePacks(client)
+	runtime.packs = populateNodePacks(client)
 
 	results := make(chan struct {
 		string
@@ -293,150 +293,144 @@ func (runtime *apiNodeRuntime) Detect(
 	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 {
-		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 := client.Repositories.GetContents(
-				context.Background(),
-				owner,
-				name,
-				fmt.Sprintf("%s/package.json", path),
-				&repoContentOptions,
-			)
-			if err != nil {
-				fmt.Printf("Error fetching contents of package.json: %v\n", err)
-				return nil
-			}
-			var packageJSON struct {
-				Scripts map[string]string `json:"scripts"`
-				Engines struct {
-					Node string `json:"node"`
-				} `json:"engines"`
-			}
+	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 := client.Repositories.GetContents(
+			context.Background(),
+			owner,
+			name,
+			fmt.Sprintf("%s/package.json", path),
+			&repoContentOptions,
+		)
+		if err != nil {
+			fmt.Printf("Error fetching contents of package.json: %v\n", err)
+			return nil
+		}
+		var packageJSON struct {
+			Scripts map[string]string `json:"scripts"`
+			Engines struct {
+				Node string `json:"node"`
+			} `json:"engines"`
+		}
 
-			data, err := fileContent.GetContent()
-			if err != nil {
-				fmt.Printf("Error calling GetContent() on package.json: %v\n", err)
-				return nil
-			}
-			err = json.NewDecoder(strings.NewReader(data)).Decode(&packageJSON)
-			if err != nil {
-				fmt.Printf("Error decoding package.json contents to struct: %v\n", err)
-				return nil
-			}
+		data, err := fileContent.GetContent()
+		if err != nil {
+			fmt.Printf("Error calling GetContent() on package.json: %v\n", err)
+			return nil
+		}
+		err = json.NewDecoder(strings.NewReader(data)).Decode(&packageJSON)
+		if err != nil {
+			fmt.Printf("Error decoding package.json contents to struct: %v\n", err)
+			return nil
+		}
 
-			if packageJSON.Engines.Node == "" {
-				// we should now check for the node engine version in .nvmrc and then .node-version
-				nvmrcFound := false
-				nodeVersionFound := false
-				for i := 0; i < len(directoryContent); i++ {
-					name := directoryContent[i].GetName()
-					if name == ".nvmrc" {
-						nvmrcFound = true
-					} else if name == ".node-version" {
-						nodeVersionFound = true
-					}
+		if packageJSON.Engines.Node == "" {
+			// we should now check for the node engine version in .nvmrc and then .node-version
+			nvmrcFound := false
+			nodeVersionFound := false
+			for i := 0; i < len(directoryContent); i++ {
+				name := directoryContent[i].GetName()
+				if name == ".nvmrc" {
+					nvmrcFound = true
+				} else if name == ".node-version" {
+					nodeVersionFound = true
 				}
+			}
 
-				if nvmrcFound {
-					// copy exact behavior of https://github.com/paketo-buildpacks/node-engine/blob/main/nvmrc_parser.go
-					fileContent, _, _, err = client.Repositories.GetContents(
-						context.Background(),
-						owner,
-						name,
-						fmt.Sprintf("%s/.nvmrc", path),
-						&repoContentOptions,
-					)
-					if err != nil {
-						fmt.Printf("Error fetching contents of .nvmrc: %v\n", err)
-						return nil
-					}
-					data, err = fileContent.GetContent()
-					if err != nil {
-						fmt.Printf("Error calling GetContent() on .nvmrc: %v\n", err)
-						return nil
-					}
-					nvmrcVersion, err := validateNvmrc(data)
-					if err != nil {
-						fmt.Printf("Error validating .nvmrc: %v\n", err)
-						return nil
-					}
-					nvmrcVersion = formatNvmrcContent(nvmrcVersion)
-
-					if nvmrcVersion != "*" {
-						packageJSON.Engines.Node = data
-					}
+			if nvmrcFound {
+				// copy exact behavior of https://github.com/paketo-buildpacks/node-engine/blob/main/nvmrc_parser.go
+				fileContent, _, _, err = client.Repositories.GetContents(
+					context.Background(),
+					owner,
+					name,
+					fmt.Sprintf("%s/.nvmrc", path),
+					&repoContentOptions,
+				)
+				if err != nil {
+					fmt.Printf("Error fetching contents of .nvmrc: %v\n", err)
+					return nil
 				}
-
-				if packageJSON.Engines.Node == "" && nodeVersionFound {
-					// copy exact behavior of https://github.com/paketo-buildpacks/node-engine/blob/main/node_version_parser.go
-					fileContent, _, _, err = client.Repositories.GetContents(
-						context.Background(),
-						owner,
-						name,
-						fmt.Sprintf("%s/.node-version", path),
-						&repoContentOptions,
-					)
-					if err != nil {
-						fmt.Printf("Error fetching contents of .node-version: %v\n", err)
-						return nil
-					}
-					data, err = fileContent.GetContent()
-					if err != nil {
-						fmt.Printf("Error calling GetContent() on .node-version: %v\n", err)
-						return nil
-					}
-					nodeVersion, err := validateNodeVersion(data)
-					if err != nil {
-						fmt.Printf("Error validating .node-version: %v\n", err)
-						return nil
-					}
-					if nodeVersion != "" {
-						packageJSON.Engines.Node = nodeVersion
-					}
+				data, err = fileContent.GetContent()
+				if err != nil {
+					fmt.Printf("Error calling GetContent() on .nvmrc: %v\n", err)
+					return nil
 				}
-			}
+				nvmrcVersion, err := validateNvmrc(data)
+				if err != nil {
+					fmt.Printf("Error validating .nvmrc: %v\n", err)
+					return nil
+				}
+				nvmrcVersion = formatNvmrcContent(nvmrcVersion)
 
-			if packageJSON.Engines.Node == "" {
-				// use the default node engine version from https://github.com/paketo-buildpacks/node-engine/blob/main/buildpack.toml
-				packageJSON.Engines.Node = "16.*.*"
+				if nvmrcVersion != "*" {
+					packageJSON.Engines.Node = data
+				}
 			}
 
-			if detected[yarn] {
-				fmt.Printf("NodeJS yarn runtime detected for %s/%s\n", owner, name)
-				return &RuntimeResponse{
-					Name:       "Node.js",
-					Buildpacks: runtime.packs[yarn],
-					Runtime:    yarn,
-					Config: map[string]interface{}{
-						"scripts":     packageJSON.Scripts,
-						"node_engine": packageJSON.Engines.Node,
-					},
+			if packageJSON.Engines.Node == "" && nodeVersionFound {
+				// copy exact behavior of https://github.com/paketo-buildpacks/node-engine/blob/main/node_version_parser.go
+				fileContent, _, _, err = client.Repositories.GetContents(
+					context.Background(),
+					owner,
+					name,
+					fmt.Sprintf("%s/.node-version", path),
+					&repoContentOptions,
+				)
+				if err != nil {
+					fmt.Printf("Error fetching contents of .node-version: %v\n", err)
+					return nil
+				}
+				data, err = fileContent.GetContent()
+				if err != nil {
+					fmt.Printf("Error calling GetContent() on .node-version: %v\n", err)
+					return nil
+				}
+				nodeVersion, err := validateNodeVersion(data)
+				if err != nil {
+					fmt.Printf("Error validating .node-version: %v\n", err)
+					return nil
 				}
-			} else {
-				fmt.Printf("NodeJS npm runtime detected for %s/%s\n", owner, name)
-				return &RuntimeResponse{
-					Name:       "Node.js",
-					Buildpacks: runtime.packs[npm],
-					Runtime:    npm,
-					Config: map[string]interface{}{
-						"scripts":     packageJSON.Scripts,
-						"node_engine": packageJSON.Engines.Node,
-					},
+				if nodeVersion != "" {
+					packageJSON.Engines.Node = nodeVersion
 				}
 			}
 		}
 
+		if packageJSON.Engines.Node == "" {
+			// use the default node engine version from https://github.com/paketo-buildpacks/node-engine/blob/main/buildpack.toml
+			packageJSON.Engines.Node = "16.*.*"
+		}
+
+		if detected[yarn] {
+			fmt.Printf("NodeJS yarn runtime detected for %s/%s\n", owner, name)
+			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 &RuntimeResponse{
+				Name:       "Node.js",
+				Buildpacks: runtime.packs[npm],
+				Runtime:    npm,
+				Config: map[string]interface{}{
+					"scripts":     packageJSON.Scripts,
+					"node_engine": packageJSON.Engines.Node,
+				},
+			}
+		}
+	} else if detected[standalone] {
 		fmt.Printf("NodeJS standalone runtime detected for %s/%s\n", owner, name)
 		return &RuntimeResponse{
 			Name:       "Node.js",

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

@@ -1 +1,283 @@
 package buildpacks
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/google/go-github/github"
+	"github.com/pelletier/go-toml"
+)
+
+type apiPythonRuntime struct {
+	wg    sync.WaitGroup
+	packs map[string]*BuildpackInfo
+}
+
+// FIXME: should be called once at the top-level somewhere in the backend
+func populatePythonPacks(client *github.Client) map[string]*BuildpackInfo {
+	packs := make(map[string]*BuildpackInfo)
+
+	repoRelease, _, err := client.Repositories.GetLatestRelease(context.Background(), "paketo-buildpacks", "python")
+	if err != nil {
+		fmt.Printf("Error fetching latest release for packeto-buildpacks/python: %v\n", err)
+		return nil
+	}
+	fileContent, _, _, err := client.Repositories.GetContents(
+		context.Background(), "packeto-buildpacks", "python", "buildpack.toml",
+		&github.RepositoryContentGetOptions{
+			Ref: *repoRelease.TagName,
+		},
+	)
+	if err != nil {
+		fmt.Printf("Error fetching contents of buildpack.toml for packeto-buildpacks/python: %v\n", err)
+		return nil
+	}
+
+	data, err := fileContent.GetContent()
+	if err != nil {
+		fmt.Printf("Error calling GetContent() on buildpack.toml for packeto-buildpacks/python: %v\n", err)
+		return nil
+	}
+
+	buildpackToml, err := toml.Load(data)
+	if err != nil {
+		fmt.Printf("Error while reading buildpack.toml from packeto-buildpacks/python: %v\n", err)
+		os.Exit(1)
+	}
+	order := buildpackToml.Get("order").([]*toml.Tree)
+
+	// pipenv
+	packs[pipenv] = newBuildpackInfo()
+	pipenvGroup := order[0].GetArray("group").([]*toml.Tree)
+	for i := 0; i < len(pipenvGroup); i++ {
+		packs[pipenv].addPack(
+			buildpackOrderGroupInfo{
+				ID:       pipenvGroup[i].Get("id").(string),
+				Optional: pipenvGroup[i].GetDefault("optional", false).(bool),
+				Version:  pipenvGroup[i].Get("version").(string),
+			},
+		)
+	}
+
+	// pip
+	packs[pip] = newBuildpackInfo()
+	pipGroup := order[1].GetArray("group").([]*toml.Tree)
+	for i := 0; i < len(pipGroup); i++ {
+		packs[pip].addPack(
+			buildpackOrderGroupInfo{
+				ID:       pipGroup[i].Get("id").(string),
+				Optional: pipGroup[i].GetDefault("optional", false).(bool),
+				Version:  pipGroup[i].Get("version").(string),
+			},
+		)
+	}
+
+	// conda
+	packs[conda] = newBuildpackInfo()
+	condaGroup := order[2].GetArray("group").([]*toml.Tree)
+	for i := 0; i < len(condaGroup); i++ {
+		packs[pip].addPack(
+			buildpackOrderGroupInfo{
+				ID:       condaGroup[i].Get("id").(string),
+				Optional: condaGroup[i].GetDefault("optional", false).(bool),
+				Version:  condaGroup[i].Get("version").(string),
+			},
+		)
+	}
+
+	// no package manager
+	packs[standalone] = newBuildpackInfo()
+	standaloneGroup := order[3].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),
+			},
+		)
+	}
+
+	return packs
+}
+
+func NewAPIPythonRuntime() APIRuntime {
+	return &apiPythonRuntime{}
+}
+
+func (runtime *apiPythonRuntime) detectPipenv(results chan struct {
+	string
+	bool
+}, directoryContent []*github.RepositoryContent) {
+	pipfileFound := false
+	pipfileLockFound := false
+	for i := 0; i < len(directoryContent); i++ {
+		name := directoryContent[i].GetName()
+		if name == "Pipfile" {
+			pipfileFound = true
+		} else if name == "Pipfile.lock" {
+			pipfileLockFound = true
+		}
+		if pipfileFound && pipfileLockFound {
+			break
+		}
+	}
+	if pipfileFound && pipfileLockFound {
+		results <- struct {
+			string
+			bool
+		}{pipenv, true}
+	} else {
+		results <- struct {
+			string
+			bool
+		}{pipenv, false}
+	}
+	runtime.wg.Done()
+}
+
+func (runtime *apiPythonRuntime) detectPip(results chan struct {
+	string
+	bool
+}, directoryContent []*github.RepositoryContent) {
+	requirementsTxtFound := false
+	for i := 0; i < len(directoryContent); i++ {
+		name := directoryContent[i].GetName()
+		if name == "requirements.txt" {
+			requirementsTxtFound = true
+		}
+	}
+	if requirementsTxtFound {
+		results <- struct {
+			string
+			bool
+		}{pip, true}
+	} else {
+		results <- struct {
+			string
+			bool
+		}{pip, false}
+	}
+	runtime.wg.Done()
+}
+
+func (runtime *apiPythonRuntime) detectConda(results chan struct {
+	string
+	bool
+}, directoryContent []*github.RepositoryContent) {
+	environmentFound := false
+	packageListFound := false
+	for i := 0; i < len(directoryContent); i++ {
+		name := directoryContent[i].GetName()
+		if name == "environment.yml" {
+			environmentFound = true
+			break
+		} else if name == "package-list.txt" {
+			packageListFound = true
+			break
+		}
+	}
+	if environmentFound || packageListFound {
+		results <- struct {
+			string
+			bool
+		}{conda, true}
+	} else {
+		results <- struct {
+			string
+			bool
+		}{conda, false}
+	}
+	runtime.wg.Done()
+}
+
+func (runtime *apiPythonRuntime) detectStandalone(results chan struct {
+	string
+	bool
+}, directoryContent []*github.RepositoryContent) {
+	pyFound := false
+	for i := 0; i < len(directoryContent); i++ {
+		name := directoryContent[i].GetName()
+		if strings.HasSuffix(name, ".py") {
+			pyFound = true
+			break
+		}
+	}
+	if pyFound {
+		results <- struct {
+			string
+			bool
+		}{standalone, true}
+	} else {
+		results <- struct {
+			string
+			bool
+		}{standalone, false}
+	}
+	runtime.wg.Done()
+}
+
+func (runtime *apiPythonRuntime) Detect(
+	client *github.Client,
+	directoryContent []*github.RepositoryContent,
+	owner, name, path string,
+	repoContentOptions github.RepositoryContentGetOptions,
+) *RuntimeResponse {
+	runtime.packs = populatePythonPacks(client)
+
+	results := make(chan struct {
+		string
+		bool
+	}, 4)
+
+	fmt.Printf("Starting detection for a Python runtime for %s/%s\n", owner, name)
+	runtime.wg.Add(4)
+	fmt.Println("Checking for pipenv")
+	go runtime.detectPipenv(results, directoryContent)
+	fmt.Println("Checking for pip")
+	go runtime.detectPip(results, directoryContent)
+	fmt.Println("Checking for conda")
+	go runtime.detectConda(results, directoryContent)
+	fmt.Println("Checking for Python standalone")
+	go runtime.detectStandalone(results, directoryContent)
+	runtime.wg.Wait()
+	close(results)
+
+	detected := make(map[string]bool)
+	for result := range results {
+		detected[result.string] = result.bool
+	}
+
+	// TODO: how to access config values for Python projects
+	if detected[pipenv] {
+		fmt.Printf("Python pipenv runtime detected for %s/%s\n", owner, name)
+		return &RuntimeResponse{
+			Name:    "Python",
+			Runtime: pipenv,
+		}
+	} else if detected[pip] {
+		fmt.Printf("Python pip runtime detected for %s/%s\n", owner, name)
+		return &RuntimeResponse{
+			Name:    "Python",
+			Runtime: pip,
+		}
+	} else if detected[conda] {
+		fmt.Printf("Python conda runtime detected for %s/%s\n", owner, name)
+		return &RuntimeResponse{
+			Name:    "Python",
+			Runtime: conda,
+		}
+	} else if detected[standalone] {
+		fmt.Printf("Python standalone runtime detected for %s/%s\n", owner, name)
+		return &RuntimeResponse{
+			Name:    "Python",
+			Runtime: standalone,
+		}
+	}
+
+	fmt.Printf("No Python runtime detected for %s/%s\n", owner, name)
+	return nil
+}

+ 4 - 0
internal/integrations/buildpacks/shared.go

@@ -12,6 +12,9 @@ const (
 	npm        = "npm"
 	mod        = "mod"
 	dep        = "dep"
+	pipenv     = "pipenv"
+	pip        = "pip"
+	conda      = "conda"
 	standalone = "standalone"
 )
 
@@ -76,4 +79,5 @@ type APIRuntime interface {
 var APIRuntimes = []APIRuntime{
 	NewAPIGoRuntime(),
 	NewAPINodeRuntime(),
+	NewAPIPythonRuntime(),
 }