diff --git a/actions/project.go b/actions/project.go index 6a7788be..c8a62e1e 100644 --- a/actions/project.go +++ b/actions/project.go @@ -19,6 +19,7 @@ import ( "path" "regexp" + "github.com/eclipse/codewind-installer/apiroutes" "github.com/eclipse/codewind-installer/errors" "github.com/eclipse/codewind-installer/utils" "github.com/urfave/cli" @@ -52,9 +53,9 @@ func DownloadTemplate(c *cli.Context) { // Remove invalid characters from the string we will use // as the project name in the template. - r := regexp.MustCompile("[^a-zA-Z0-9._-]"); - projectName := r.ReplaceAllString(projectDir, ""); - if (len(projectName) == 0){ + r := regexp.MustCompile("[^a-zA-Z0-9._-]") + projectName := r.ReplaceAllString(projectDir, "") + if len(projectName) == 0 { projectName = "PROJ_NAME_PLACEHOLDER" } @@ -70,6 +71,37 @@ func DownloadTemplate(c *cli.Context) { } } +// checkIsExtension checks if a project is an extension project and run associated commands as necessary +func checkIsExtension(projectPath string) (string, error) { + + extensions, err := apiroutes.GetExtensions() + if err != nil { + log.Println("There was a problem retrieving extensions data") + return "unknown", err + } + + for _, extension := range extensions { + + // check if project contains the detection file an extension defines + if extension.Detection != "" && utils.PathExists(path.Join(projectPath, extension.Detection)) { + + var cmdErr error + + // check if there are any commands to run + for _, command := range extension.Commands { + if command.Name == "postProjectValidate" { + cmdErr = utils.RunCommand(projectPath, command) + break + } + } + + return extension.ProjectType, cmdErr + } + } + + return "", nil +} + // ValidateProject returns the language and buildType for a project at given filesystem path, // and writes a default .cw-settings file to that project func ValidateProject(c *cli.Context) { @@ -78,14 +110,19 @@ func ValidateProject(c *cli.Context) { validationStatus := "success" // result could be ProjectType or string, so define as an interface var validationResult interface{} - language, buildType, isAppsody := utils.DetermineProjectInfo(projectPath) + language, buildType := utils.DetermineProjectInfo(projectPath) validationResult = ProjectType{ - Language: language, + Language: language, BuildType: buildType, } - if isAppsody { - validated, err := utils.SuccessfullyCallAppsodyInit(projectPath) - if !validated { + extensionType, err := checkIsExtension(projectPath) + if extensionType != "" { + if err == nil { + validationResult = ProjectType{ + Language: language, + BuildType: extensionType, + } + } else { validationStatus = "failed" validationResult = err.Error() } diff --git a/apiroutes/extensions.go b/apiroutes/extensions.go new file mode 100644 index 00000000..eae7a820 --- /dev/null +++ b/apiroutes/extensions.go @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2019 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package apiroutes + +import ( + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/eclipse/codewind-installer/config" + "github.com/eclipse/codewind-installer/utils" +) + +type ( + // Extension represents a project extension defined by codewind.yaml + Extension struct { + ProjectType string `json:"projectType"` + Detection string `json:"detection"` + Commands []utils.ExtensionCommand `json:"commands"` + } +) + +// GetExtensions gets project extensions from PFE's REST API. +func GetExtensions() ([]Extension, error) { + resp, err := http.Get(config.PFEApiRoute() + "extensions") + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + byteArray, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var extensions []Extension + json.Unmarshal(byteArray, &extensions) + + return extensions, nil +} diff --git a/utils/appsody.go b/utils/extensions.go similarity index 55% rename from utils/appsody.go rename to utils/extensions.go index 6327406e..8b9380b3 100644 --- a/utils/appsody.go +++ b/utils/extensions.go @@ -9,43 +9,46 @@ * IBM Corporation - initial API and implementation *******************************************************************************/ - package utils +package utils - import ( +import ( "bytes" + "log" "os" "os/exec" - "log" - "runtime" "path/filepath" ) - - // SuccessfullyCallAppsodyInit calls Appsody Init to initialise Appsody projects and returns a boolean to indicate success - func SuccessfullyCallAppsodyInit(projectPath string) (bool, error) { + +type ( + // ExtensionCommand represents a command defined by a project extension + ExtensionCommand struct { + Name string `json:"name"` + Command string `json:"command"` + Args []string `json:"args"` + } +) + +// RunCommand runs a command defined by an extension +func RunCommand(projectPath string, command ExtensionCommand) error { cwd, err := os.Executable() if err != nil { - log.Println("There was a problem with locating appsody binary") - return false, err + log.Println("There was a problem with locating the command directory") + return err } - const GOOS string = runtime.GOOS installerPath := filepath.Dir(cwd) - appsodyBinPath := "/appsody" - if GOOS == "windows" { - appsodyBinPath = "/appsody.exe" - } - appsodyBin := installerPath + appsodyBinPath - cmd := exec.Command(appsodyBin, "init") + commandName := filepath.Base(command.Command) // prevent path traversal + commandBin := filepath.Join(installerPath, commandName) + cmd := exec.Command(commandBin, command.Args...) cmd.Dir = projectPath output := new(bytes.Buffer) cmd.Stdout = output cmd.Stderr = output if err := cmd.Start(); err != nil { // after 'Start' the program is continued and script is executing in background - log.Println("There was a problem initializing the Appsody project: ", err, ". Project was not initialized.") - return false, err + log.Println("There was a problem running the command:", commandName) + return err } - log.Printf("Please wait while the Appsody project is initialized... %s \n", output.String()) + log.Printf("Please wait while the project is initialized... %s", output.String()) cmd.Wait() log.Println(output.String()) // Wait to finish execution, so we can read all output - return true, nil - } - \ No newline at end of file + return nil +} diff --git a/utils/project.go b/utils/project.go index 35a0308e..ee95aab2 100644 --- a/utils/project.go +++ b/utils/project.go @@ -34,9 +34,9 @@ type CWSettings struct { MavenProperties []string `json:"mavenProperties,omitempty"` } -// DetermineProjectInfo returns the language and build-type of a project, as well as if it's an Appsody project -func DetermineProjectInfo(projectPath string) (string, string, bool) { - language, buildType, isAppsody := "unknown", "docker", false +// DetermineProjectInfo returns the language and build-type of a project +func DetermineProjectInfo(projectPath string) (string, string) { + language, buildType := "unknown", "docker" if PathExists(path.Join(projectPath, "pom.xml")) { language = "java" buildType = determineJavaBuildType(projectPath) @@ -49,13 +49,11 @@ func DetermineProjectInfo(projectPath string) (string, string, bool) { language = "swift" buildType = "swift" } - if PathExists(path.Join(projectPath, "stack.yaml")) { - isAppsody = true + if PathExists(path.Join(projectPath, "Pipfile")) { + language = "python" + buildType = "docker" } - if PathExists(path.Join(projectPath, ".appsody-config.yaml")) { - isAppsody = true - } - return language, buildType, isAppsody + return language, buildType } // CheckProjectPath will stop the process and return an error if path does not diff --git a/utils/project_test.go b/utils/project_test.go index 48d2f330..648caccc 100644 --- a/utils/project_test.go +++ b/utils/project_test.go @@ -27,41 +27,35 @@ func TestDetermineProjectInfo(t *testing.T) { in string wantLanguage string wantBuildType string - wantIsAppsody bool wantedErr error }{ "success case: liberty project": { in: path.Join("..", "resources", "test", "liberty-project"), wantLanguage: "java", wantBuildType: "liberty", - wantIsAppsody: false, }, "success case: spring project": { in: path.Join("..", "resources", "test", "spring-project"), wantLanguage: "java", wantBuildType: "spring", - wantIsAppsody: false, }, "success case: node.js project": { in: path.Join("..", "resources", "test", "node-project"), wantLanguage: "nodejs", wantBuildType: "nodejs", - wantIsAppsody: false, }, "success case: swift project": { in: path.Join("..", "resources", "test", "swift-project"), wantLanguage: "swift", wantBuildType: "swift", - wantIsAppsody: false, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - gotLanguage, gotBuildType, gotIsAppsody := DetermineProjectInfo(test.in) + gotLanguage, gotBuildType := DetermineProjectInfo(test.in) assert.Equal(t, test.wantLanguage, gotLanguage) assert.Equal(t, test.wantBuildType, gotBuildType) - assert.Equal(t, test.wantIsAppsody, gotIsAppsody) }) } }