From 4e511fd66c73f97f11da35c02929d214980cc77d Mon Sep 17 00:00:00 2001 From: Matthew Gilliard Date: Fri, 2 Jun 2017 16:20:25 +0100 Subject: [PATCH 01/11] Adds java-maven runtime --- fn/langs/base.go | 2 ++ fn/langs/java_maven.go | 59 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 fn/langs/java_maven.go diff --git a/fn/langs/base.go b/fn/langs/base.go index 1b5d66825..37b994568 100644 --- a/fn/langs/base.go +++ b/fn/langs/base.go @@ -31,6 +31,8 @@ func GetLangHelper(lang string) LangHelper { return &LambdaNodeHelper{} case "java": return &JavaLangHelper{} + case "java-maven": + return &JavaMavenLangHelper{} } return nil } diff --git a/fn/langs/java_maven.go b/fn/langs/java_maven.go new file mode 100644 index 000000000..bf1e9cb47 --- /dev/null +++ b/fn/langs/java_maven.go @@ -0,0 +1,59 @@ +package langs + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" +) + +// JavaMavenLangHelper provides a set of helper methods for the build lifecycle of Java Maven projects +type JavaMavenLangHelper struct { + BaseHelper +} + + +// Entrypoint returns the Java runtime Docker entrypoint that will be executed when the function is run +func (lh *JavaMavenLangHelper) Entrypoint() string { + return fmt.Sprintf("java -jar /function/target/function.jar com.example.faas.ExampleFunction::itsOn") +} + +// HasPreBuild returns whether the Java runtime has a pre-build step +func (lh *JavaMavenLangHelper) HasPreBuild() bool { + return true +} + +// PreBuild runs "mvn clean package" in the root of the project and by default expects a jar at `target/function.jar` as the +// output. We mount `$HOME/.m2` in order to get maven the proxy config specified in .m2/settings.xml, but it has the nice +// side effect of making the users .m2/repository available. Any new deps downloaded will be owned by root :( +func (lh *JavaMavenLangHelper) PreBuild() error { + wd, err := os.Getwd() + if err != nil { + return err + } + + if !exists(filepath.Join(wd, "pom.xml")) { + return fmt.Errorf("Could not find pom.xml - are you sure this is a maven project?") + } + + cmd := exec.Command( + "docker", "run", + "--rm", + "-v", wd+":/java", "-w", "/java", + "-v", os.Getenv("HOME")+"/.m2:/.m2", + "maven:3.5-jdk-8-alpine", + "/bin/sh", "-c", "mvn -gs /.m2/settings.xml clean package", + ) + + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + return dockerBuildError(err) + } + return nil +} + +// AfterBuild should remove the (root-owned) target dir. +func (lh *JavaMavenLangHelper) AfterBuild() error { + return nil +} From 1918edacff928f017aa215c925d1e454c43f6320 Mon Sep 17 00:00:00 2001 From: Mukhtar Haji Date: Mon, 5 Jun 2017 15:20:00 +0100 Subject: [PATCH 02/11] Build project/func on host machine for java-maven This change introduces a local build step as opposed to the prebuild step which happens inside a dev container. --- fn/langs/java_maven.go | 54 ++++++++++-------------------------------- 1 file changed, 13 insertions(+), 41 deletions(-) diff --git a/fn/langs/java_maven.go b/fn/langs/java_maven.go index bf1e9cb47..110adb14a 100644 --- a/fn/langs/java_maven.go +++ b/fn/langs/java_maven.go @@ -1,59 +1,31 @@ package langs -import ( - "fmt" - "os" - "os/exec" - "path/filepath" -) +import "fmt" // JavaMavenLangHelper provides a set of helper methods for the build lifecycle of Java Maven projects type JavaMavenLangHelper struct { BaseHelper } - // Entrypoint returns the Java runtime Docker entrypoint that will be executed when the function is run func (lh *JavaMavenLangHelper) Entrypoint() string { - return fmt.Sprintf("java -jar /function/target/function.jar com.example.faas.ExampleFunction::itsOn") + // TODO need mechanism to determine the java user function dynamically + userFunction := "com.example.faas.ExampleFunction::itsOn" + return fmt.Sprintf("java -jar /function/target/function.jar %s", userFunction) } -// HasPreBuild returns whether the Java runtime has a pre-build step -func (lh *JavaMavenLangHelper) HasPreBuild() bool { +func (lh *JavaMavenLangHelper) HasLocalBuildCmd() bool { return true } -// PreBuild runs "mvn clean package" in the root of the project and by default expects a jar at `target/function.jar` as the -// output. We mount `$HOME/.m2` in order to get maven the proxy config specified in .m2/settings.xml, but it has the nice -// side effect of making the users .m2/repository available. Any new deps downloaded will be owned by root :( -func (lh *JavaMavenLangHelper) PreBuild() error { - wd, err := os.Getwd() - if err != nil { - return err +func (lh *JavaMavenLangHelper) LocalBuildCmd() []string { + return []string{ + "mvn clean package", } - - if !exists(filepath.Join(wd, "pom.xml")) { - return fmt.Errorf("Could not find pom.xml - are you sure this is a maven project?") - } - - cmd := exec.Command( - "docker", "run", - "--rm", - "-v", wd+":/java", "-w", "/java", - "-v", os.Getenv("HOME")+"/.m2:/.m2", - "maven:3.5-jdk-8-alpine", - "/bin/sh", "-c", "mvn -gs /.m2/settings.xml clean package", - ) - - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout - if err := cmd.Run(); err != nil { - return dockerBuildError(err) - } - return nil } -// AfterBuild should remove the (root-owned) target dir. -func (lh *JavaMavenLangHelper) AfterBuild() error { - return nil -} +func (lh *JavaMavenLangHelper) HasPreBuild() bool { return false } + +func (lh *JavaMavenLangHelper) PreBuild() error { return nil } + +func (lh *JavaMavenLangHelper) AfterBuild() error { return nil } From 97bc009cce27e43c901d51ee3d00a2389f64d3d8 Mon Sep 17 00:00:00 2001 From: Mukhtar Haji Date: Tue, 6 Jun 2017 11:22:23 +0100 Subject: [PATCH 03/11] Revert "Build project/func on host machine for java-maven" This reverts commit 4ccb8fa33c8a5dc94e7262c3e5595ee4bced3d0b. --- fn/langs/base.go | 6 +++++ fn/langs/java_maven.go | 54 ++++++++++++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/fn/langs/base.go b/fn/langs/base.go index 37b994568..eaf26b782 100644 --- a/fn/langs/base.go +++ b/fn/langs/base.go @@ -66,6 +66,7 @@ type LangHelper interface { type BaseHelper struct { } +<<<<<<< 1918edacff928f017aa215c925d1e454c43f6320 func (h *BaseHelper) BuildFromImage() string { return "" } func (h *BaseHelper) RunFromImage() string { return h.BuildFromImage() } func (h *BaseHelper) IsMultiStage() bool { return true } @@ -78,6 +79,11 @@ func (h *BaseHelper) PreBuild() error { return nil } func (h *BaseHelper) AfterBuild() error { return nil } func (h *BaseHelper) HasBoilerplate() bool { return false } func (h *BaseHelper) GenerateBoilerplate() error { return nil } +======= +func (h *BaseHelper) Cmd() string { return "" } +func (h *BaseHelper) HasBoilerplate() bool { return false } +func (h *BaseHelper) GenerateBoilerplate() error { return nil } +>>>>>>> Revert "Build project/func on host machine for java-maven" // exists checks if a file exists func exists(name string) bool { diff --git a/fn/langs/java_maven.go b/fn/langs/java_maven.go index 110adb14a..bf1e9cb47 100644 --- a/fn/langs/java_maven.go +++ b/fn/langs/java_maven.go @@ -1,31 +1,59 @@ package langs -import "fmt" +import ( + "fmt" + "os" + "os/exec" + "path/filepath" +) // JavaMavenLangHelper provides a set of helper methods for the build lifecycle of Java Maven projects type JavaMavenLangHelper struct { BaseHelper } + // Entrypoint returns the Java runtime Docker entrypoint that will be executed when the function is run func (lh *JavaMavenLangHelper) Entrypoint() string { - // TODO need mechanism to determine the java user function dynamically - userFunction := "com.example.faas.ExampleFunction::itsOn" - return fmt.Sprintf("java -jar /function/target/function.jar %s", userFunction) + return fmt.Sprintf("java -jar /function/target/function.jar com.example.faas.ExampleFunction::itsOn") } -func (lh *JavaMavenLangHelper) HasLocalBuildCmd() bool { +// HasPreBuild returns whether the Java runtime has a pre-build step +func (lh *JavaMavenLangHelper) HasPreBuild() bool { return true } -func (lh *JavaMavenLangHelper) LocalBuildCmd() []string { - return []string{ - "mvn clean package", +// PreBuild runs "mvn clean package" in the root of the project and by default expects a jar at `target/function.jar` as the +// output. We mount `$HOME/.m2` in order to get maven the proxy config specified in .m2/settings.xml, but it has the nice +// side effect of making the users .m2/repository available. Any new deps downloaded will be owned by root :( +func (lh *JavaMavenLangHelper) PreBuild() error { + wd, err := os.Getwd() + if err != nil { + return err } + + if !exists(filepath.Join(wd, "pom.xml")) { + return fmt.Errorf("Could not find pom.xml - are you sure this is a maven project?") + } + + cmd := exec.Command( + "docker", "run", + "--rm", + "-v", wd+":/java", "-w", "/java", + "-v", os.Getenv("HOME")+"/.m2:/.m2", + "maven:3.5-jdk-8-alpine", + "/bin/sh", "-c", "mvn -gs /.m2/settings.xml clean package", + ) + + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + return dockerBuildError(err) + } + return nil } -func (lh *JavaMavenLangHelper) HasPreBuild() bool { return false } - -func (lh *JavaMavenLangHelper) PreBuild() error { return nil } - -func (lh *JavaMavenLangHelper) AfterBuild() error { return nil } +// AfterBuild should remove the (root-owned) target dir. +func (lh *JavaMavenLangHelper) AfterBuild() error { + return nil +} From 55c2aaf28bd1ec3bb4985cc57f02926d0588ede5 Mon Sep 17 00:00:00 2001 From: Mukhtar Haji Date: Tue, 6 Jun 2017 13:21:48 +0100 Subject: [PATCH 04/11] Create local .m2 dir to cache deps between builds Also deletes target directory as an AfterBuild step --- fn/langs/java_maven.go | 63 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/fn/langs/java_maven.go b/fn/langs/java_maven.go index bf1e9cb47..80a20e1e9 100644 --- a/fn/langs/java_maven.go +++ b/fn/langs/java_maven.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path/filepath" + "errors" ) // JavaMavenLangHelper provides a set of helper methods for the build lifecycle of Java Maven projects @@ -23,9 +24,9 @@ func (lh *JavaMavenLangHelper) HasPreBuild() bool { return true } -// PreBuild runs "mvn clean package" in the root of the project and by default expects a jar at `target/function.jar` as the -// output. We mount `$HOME/.m2` in order to get maven the proxy config specified in .m2/settings.xml, but it has the nice -// side effect of making the users .m2/repository available. Any new deps downloaded will be owned by root :( +// PreBuild runs "mvn package" in the root of the project. A local .m2 directory is created the first time this is run +// so that any pulled dependencies are cached in between builds. The local .m2 directory contains a hardlink to user's +// own settings.xml file and this is mounted into the build container. func (lh *JavaMavenLangHelper) PreBuild() error { wd, err := os.Getwd() if err != nil { @@ -33,16 +34,21 @@ func (lh *JavaMavenLangHelper) PreBuild() error { } if !exists(filepath.Join(wd, "pom.xml")) { - return fmt.Errorf("Could not find pom.xml - are you sure this is a maven project?") + return errors.New("Could not find pom.xml - are you sure this is a maven project?") + } + + err = createLocalM2Dir(wd) + if err != nil { + return err } cmd := exec.Command( "docker", "run", "--rm", "-v", wd+":/java", "-w", "/java", - "-v", os.Getenv("HOME")+"/.m2:/.m2", + "-v", wd+"/.m2:/root/.m2", "maven:3.5-jdk-8-alpine", - "/bin/sh", "-c", "mvn -gs /.m2/settings.xml clean package", + "/bin/sh", "-c", "mvn package", ) cmd.Stderr = os.Stderr @@ -53,7 +59,50 @@ func (lh *JavaMavenLangHelper) PreBuild() error { return nil } -// AfterBuild should remove the (root-owned) target dir. +// createLocalM2Dir creates a .m2 directory in the function's working directory and creates a hard link to the user's +// settings.xml file. +func createLocalM2Dir(wd string) error { + usersSettingsFile := filepath.Join(os.Getenv("HOME"), ".m2/settings.xml") + localSettingsFile := filepath.Join(wd, ".m2/settings.xml") + + if exists(localSettingsFile) { + return nil + } + + if !exists(usersSettingsFile) { + return fmt.Errorf("Unable to find user's settings.xml at %s", usersSettingsFile) + } + + if !exists(filepath.Dir(localSettingsFile)) { + if err := os.Mkdir(filepath.Dir(localSettingsFile), 0755); err != nil { + return fmt.Errorf("Unable to create a local .m2 directory: %s", err) + } + } + + return os.Link(usersSettingsFile, localSettingsFile) +} + +// AfterBuild removes the target directory by mounting the working directory into a container and removingit. This is +// done inside a container as the folder is owned by root. func (lh *JavaMavenLangHelper) AfterBuild() error { + wd, err := os.Getwd() + if err != nil { + return err + } + + cmd := exec.Command( + "docker", "run", + "--rm", + "-v", wd+":/root", + "maven:3.5-jdk-8-alpine", + "/bin/sh", "-c", "rm -r /root/target", + ) + + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + return fmt.Errorf("Error occured trying to delete the target directory: %s", err) + } + return nil } From 190af3fddd75b5737038f34084fc7e62d8afafe5 Mon Sep 17 00:00:00 2001 From: Mukhtar Haji Date: Mon, 26 Jun 2017 11:31:07 +0100 Subject: [PATCH 05/11] Rebase master onto java-maven-runtime --- fn/langs/base.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/fn/langs/base.go b/fn/langs/base.go index eaf26b782..37b994568 100644 --- a/fn/langs/base.go +++ b/fn/langs/base.go @@ -66,7 +66,6 @@ type LangHelper interface { type BaseHelper struct { } -<<<<<<< 1918edacff928f017aa215c925d1e454c43f6320 func (h *BaseHelper) BuildFromImage() string { return "" } func (h *BaseHelper) RunFromImage() string { return h.BuildFromImage() } func (h *BaseHelper) IsMultiStage() bool { return true } @@ -79,11 +78,6 @@ func (h *BaseHelper) PreBuild() error { return nil } func (h *BaseHelper) AfterBuild() error { return nil } func (h *BaseHelper) HasBoilerplate() bool { return false } func (h *BaseHelper) GenerateBoilerplate() error { return nil } -======= -func (h *BaseHelper) Cmd() string { return "" } -func (h *BaseHelper) HasBoilerplate() bool { return false } -func (h *BaseHelper) GenerateBoilerplate() error { return nil } ->>>>>>> Revert "Build project/func on host machine for java-maven" // exists checks if a file exists func exists(name string) bool { From 2b7e28b780fe9ca16c54763a78689e61075d801f Mon Sep 17 00:00:00 2001 From: Mukhtar Haji Date: Mon, 26 Jun 2017 17:44:57 +0100 Subject: [PATCH 06/11] Support multi-stage docker build for java-maven --- fn/langs/java_maven.go | 132 ++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 74 deletions(-) diff --git a/fn/langs/java_maven.go b/fn/langs/java_maven.go index 80a20e1e9..83c1fe96c 100644 --- a/fn/langs/java_maven.go +++ b/fn/langs/java_maven.go @@ -3,9 +3,11 @@ package langs import ( "fmt" "os" - "os/exec" "path/filepath" "errors" + "bytes" + "strings" + "net/url" ) // JavaMavenLangHelper provides a set of helper methods for the build lifecycle of Java Maven projects @@ -13,20 +15,64 @@ type JavaMavenLangHelper struct { BaseHelper } - -// Entrypoint returns the Java runtime Docker entrypoint that will be executed when the function is run -func (lh *JavaMavenLangHelper) Entrypoint() string { - return fmt.Sprintf("java -jar /function/target/function.jar com.example.faas.ExampleFunction::itsOn") +// BuildFromImage returns the Docker image used to compile the Maven function project +func (lh *JavaMavenLangHelper) BuildFromImage() string { + return "maven:3.5-jdk-8-alpine" } -// HasPreBuild returns whether the Java runtime has a pre-build step +// RunFromImage returns the Docker image used to run the Maven built function +func (lh *JavaMavenLangHelper) RunFromImage() string { + return "funcy/java" +} + + +// DockerfileBuildCmds returns the build stage steps to compile the Maven function project +func (lh *JavaMavenLangHelper) DockerfileBuildCmds() []string { + return []string{ + fmt.Sprintf("ENV MAVEN_OPTS %s", mavenOpts()), + "ADD pom.xml /function/pom.xml", + "RUN [\"mvn\", \"package\", \"dependency:go-offline\", \"-DstripVersion=true\", \"-Dmdep.prependGroupId=true\"," + + " \"dependency:copy-dependencies\"]", + "ADD src /function/src", + "RUN [\"mvn\", \"package\"]", + } +} + +func mavenOpts() string { + var opts bytes.Buffer + + if parsedURL, err := url.Parse(os.Getenv("http_proxy")); err == nil { + opts.WriteString(fmt.Sprintf("-Dhttp.proxyHost=%s ", parsedURL.Hostname())) + opts.WriteString(fmt.Sprintf("-Dhttp.proxyPort=%s ", parsedURL.Port())) + } + + if parsedURL, err := url.Parse(os.Getenv("https_proxy")); err == nil { + opts.WriteString(fmt.Sprintf("-Dhttps.proxyHost=%s ", parsedURL.Hostname())) + opts.WriteString(fmt.Sprintf("-Dhttps.proxyPort=%s ", parsedURL.Port())) + } + + nonProxyHost := os.Getenv("no_proxy") + opts.WriteString(fmt.Sprintf("-Dhttp.nonProxyHosts=%s ", strings.Replace(nonProxyHost, ",", "|", -1))) + + opts.WriteString("-Dmaven.repo.local=/usr/share/maven/ref/repository") + + return opts.String() +} + +// DockerfileCopyCmds returns the Docker COPY command to copy the compiled Java function classes +func (lh *JavaMavenLangHelper) DockerfileCopyCmds() []string { + return []string{ + "COPY --from=build-stage /function/target/*.jar /function/app/", + "COPY --from=build-stage /function/target/dependency/*.jar /function/lib/", + } +} + +// HasPreBuild returns whether the Java Maven runtime has a pre-build step func (lh *JavaMavenLangHelper) HasPreBuild() bool { return true } -// PreBuild runs "mvn package" in the root of the project. A local .m2 directory is created the first time this is run -// so that any pulled dependencies are cached in between builds. The local .m2 directory contains a hardlink to user's -// own settings.xml file and this is mounted into the build container. +// PreBuild ensures that the expected the function is based is a maven project func (lh *JavaMavenLangHelper) PreBuild() error { wd, err := os.Getwd() if err != nil { @@ -37,72 +83,10 @@ func (lh *JavaMavenLangHelper) PreBuild() error { return errors.New("Could not find pom.xml - are you sure this is a maven project?") } - err = createLocalM2Dir(wd) - if err != nil { - return err - } - - cmd := exec.Command( - "docker", "run", - "--rm", - "-v", wd+":/java", "-w", "/java", - "-v", wd+"/.m2:/root/.m2", - "maven:3.5-jdk-8-alpine", - "/bin/sh", "-c", "mvn package", - ) - - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout - if err := cmd.Run(); err != nil { - return dockerBuildError(err) - } return nil } -// createLocalM2Dir creates a .m2 directory in the function's working directory and creates a hard link to the user's -// settings.xml file. -func createLocalM2Dir(wd string) error { - usersSettingsFile := filepath.Join(os.Getenv("HOME"), ".m2/settings.xml") - localSettingsFile := filepath.Join(wd, ".m2/settings.xml") - - if exists(localSettingsFile) { - return nil - } - - if !exists(usersSettingsFile) { - return fmt.Errorf("Unable to find user's settings.xml at %s", usersSettingsFile) - } - - if !exists(filepath.Dir(localSettingsFile)) { - if err := os.Mkdir(filepath.Dir(localSettingsFile), 0755); err != nil { - return fmt.Errorf("Unable to create a local .m2 directory: %s", err) - } - } - - return os.Link(usersSettingsFile, localSettingsFile) -} - -// AfterBuild removes the target directory by mounting the working directory into a container and removingit. This is -// done inside a container as the folder is owned by root. -func (lh *JavaMavenLangHelper) AfterBuild() error { - wd, err := os.Getwd() - if err != nil { - return err - } - - cmd := exec.Command( - "docker", "run", - "--rm", - "-v", wd+":/root", - "maven:3.5-jdk-8-alpine", - "/bin/sh", "-c", "rm -r /root/target", - ) - - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout - if err := cmd.Run(); err != nil { - return fmt.Errorf("Error occured trying to delete the target directory: %s", err) - } - - return nil +// Entrypoint returns the Java runtime Docker entrypoint that will be executed when the function is run +func (lh *JavaMavenLangHelper) Entrypoint() string { + return "java -cp app/*:lib/* com.oracle.faas.runtime.EntryPoint com.example.faas.HelloFunction::handleRequest" } From c1e2d4b8169eeb0bda1de930c8d94d1332e9cf8b Mon Sep 17 00:00:00 2001 From: Mukhtar Haji Date: Tue, 27 Jun 2017 12:16:34 +0100 Subject: [PATCH 07/11] Manually generate boilerplate for Java Maven This will eventually be replaced with something like Maven archetypes. --- fn/langs/java_maven.go | 92 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/fn/langs/java_maven.go b/fn/langs/java_maven.go index 83c1fe96c..869e49853 100644 --- a/fn/langs/java_maven.go +++ b/fn/langs/java_maven.go @@ -8,6 +8,7 @@ import ( "bytes" "strings" "net/url" + "io/ioutil" ) // JavaMavenLangHelper provides a set of helper methods for the build lifecycle of Java Maven projects @@ -90,3 +91,94 @@ func (lh *JavaMavenLangHelper) PreBuild() error { func (lh *JavaMavenLangHelper) Entrypoint() string { return "java -cp app/*:lib/* com.oracle.faas.runtime.EntryPoint com.example.faas.HelloFunction::handleRequest" } + +// HasPreBuild returns whether the Java Maven runtime has boilerplate that can be generated. +func (lh *JavaMavenLangHelper) HasBoilerplate() bool { return true } + +// GenerateBoilerplate will generate function boilerplate for a Java Maven runtime +func (lh *JavaMavenLangHelper) GenerateBoilerplate() error { + wd, err := os.Getwd() + if err != nil { + return err + } + + pathToPomFile := filepath.Join(wd, "pom.xml") + if exists(pathToPomFile) { + return ErrBoilerplateExists + } + + if err := ioutil.WriteFile(pathToPomFile, []byte(pomFile), os.FileMode(0644)); err != nil { + return err + } + + helloJavaFunctionFileDir := filepath.Join(wd, "src/main/java/com/example/faas") + if err = os.MkdirAll(helloJavaFunctionFileDir, os.FileMode(0755)); err != nil { + os.Remove(pathToPomFile) + return err + } + + helloJavaFunctionFile := filepath.Join(helloJavaFunctionFileDir, "HelloFunction.java") + return ioutil.WriteFile(helloJavaFunctionFile, []byte(helloJavaFunctionBoilerplate), os.FileMode(0644)) +} + + +/* TODO temporarily generate maven project boilerplate from hardcoded values. + Will eventually move to using a maven archetype. +*/ + +const ( + pomFile = ` + + 4.0.0 + + UTF-8 + + com.example.faas + hello + 1.0.0-SNAPSHOT + + + + nexus-box + http://10.167.103.241:8081/repository/maven-snapshots/ + + + + + + com.oracle.faas + fdk + 1.0.0-SNAPSHOT + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + +` + + helloJavaFunctionBoilerplate = `package com.example.faas; + +public class HelloFunction { + + public String handleRequest(String input) { + String name = (input == null || input.isEmpty()) ? "world" : input; + + return "Hello, " + name + "!"; + } + +}` +) From 5c5f10d6e5efe23e6181533666a23eb1cf1a8b91 Mon Sep 17 00:00:00 2001 From: Mukhtar Haji Date: Mon, 3 Jul 2017 10:17:40 +0100 Subject: [PATCH 08/11] Make Java maven the default Java runtime This replaces the basic Java runtime with Java maven --- fn/langs/base.go | 2 - fn/langs/java.go | 232 +++++++++++++++++++++++++++-------------- fn/langs/java_maven.go | 184 -------------------------------- 3 files changed, 151 insertions(+), 267 deletions(-) delete mode 100644 fn/langs/java_maven.go diff --git a/fn/langs/base.go b/fn/langs/base.go index 37b994568..1b5d66825 100644 --- a/fn/langs/base.go +++ b/fn/langs/base.go @@ -31,8 +31,6 @@ func GetLangHelper(lang string) LangHelper { return &LambdaNodeHelper{} case "java": return &JavaLangHelper{} - case "java-maven": - return &JavaMavenLangHelper{} } return nil } diff --git a/fn/langs/java.go b/fn/langs/java.go index 3f40b6ad1..bc3a5549e 100644 --- a/fn/langs/java.go +++ b/fn/langs/java.go @@ -2,106 +2,176 @@ package langs import ( "fmt" - "io/ioutil" "os" "path/filepath" + "errors" + "bytes" + "strings" + "net/url" + "io/ioutil" ) -// JavaLangHelper provides a set of helper methods for the build lifecycle of the Java runtime +// JavaLangHelper provides a set of helper methods for the lifecycle of Java Maven projects type JavaLangHelper struct { BaseHelper } -const ( - mainClass = "Func" - mainClassFile = mainClass + ".java" -) +// BuildFromImage returns the Docker image used to compile the Maven function project +func (lh *JavaLangHelper) BuildFromImage() string { return "maven:3.5-jdk-8-alpine" } -// BuildFromImage returns the Docker image used to compile the Java function -func (lh *JavaLangHelper) BuildFromImage() string { - return "funcy/java:dev" -} - -// RunFromImage returns the Docker image used to run the Java function -func (lh *JavaLangHelper) RunFromImage() string { - return "funcy/java" -} - -// DockerfileBuildCmds returns the build stage steps to compile the Java function -func (lh *JavaLangHelper) DockerfileBuildCmds() []string { - return []string{ - fmt.Sprintf("ADD %s . /src/", mainClassFile), - fmt.Sprintf("RUN cd /src && javac %s", mainClassFile), - } -} - -// DockerfileCopyCmds returns the Docker COPY command to copy the compiled Java function classes -func (h *JavaLangHelper) DockerfileCopyCmds() []string { - return []string{ - "COPY --from=build-stage /src/ /function/", - } -} - -// Entrypoint returns the Java runtime Docker entrypoint that will be executed when the function is run -func (lh *JavaLangHelper) Entrypoint() string { - return fmt.Sprintf("java %s", mainClass) -} - -// HasPreBuild returns whether the Java runtime has a pre-build step -func (lh *JavaLangHelper) HasPreBuild() bool { - return true -} - -// PreBuild ensures that the expected Java source file is there before the build is executed. Returns an error if -// `mainClassFile` is not in the working directory -func (lh *JavaLangHelper) PreBuild() error { - wd, err := os.Getwd() - if err != nil { - return err - } - - if !exists(filepath.Join(wd, mainClassFile)) { - return fmt.Errorf("could not find function: for Java, class with main method must be "+ - "called %s", mainClassFile) - } - - return nil -} +// RunFromImage returns the Docker image used to run the Java function. +func (lh *JavaLangHelper) RunFromImage() string { return "funcy/java" } // HasPreBuild returns whether the Java runtime has boilerplate that can be generated. func (lh *JavaLangHelper) HasBoilerplate() bool { return true } -const javaFunctionBoilerplate = `import java.io.*; - -public class Func { - - /** - * This is the entrypoint to your function. Input will be via STDIN. - * Any output sent to STDOUT will be sent back as the function result. - */ - public static void main(String[] args) throws IOException { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); - - String name = bufferedReader.readLine(); - name = (name == null) ? "world" : name; - - System.out.println("Hello, " + name + "!"); - } - -} -` - -// GenerateBoilerplate will generate function boilerplate (Function.java) for java if it does not exist. -// Returns ErrBoilerplateExists if the function file already exists +// GenerateBoilerplate will generate function boilerplate for a Java runtime. The default boilerplate is for a Maven +// project. func (lh *JavaLangHelper) GenerateBoilerplate() error { wd, err := os.Getwd() if err != nil { return err } - pathToFunctionFile := filepath.Join(wd, mainClassFile) - if exists(filepath.Join(wd, mainClassFile)) { + pathToPomFile := filepath.Join(wd, "pom.xml") + if exists(pathToPomFile) { return ErrBoilerplateExists } - return ioutil.WriteFile(pathToFunctionFile, []byte(javaFunctionBoilerplate), os.FileMode(0644)) + + if err := ioutil.WriteFile(pathToPomFile, []byte(pomFile), os.FileMode(0644)); err != nil { + return err + } + + helloJavaFunctionFileDir := filepath.Join(wd, "src/main/java/com/example/faas") + if err = os.MkdirAll(helloJavaFunctionFileDir, os.FileMode(0755)); err != nil { + os.Remove(pathToPomFile) + return err + } + + helloJavaFunctionFile := filepath.Join(helloJavaFunctionFileDir, "HelloFunction.java") + return ioutil.WriteFile(helloJavaFunctionFile, []byte(helloJavaFunctionBoilerplate), os.FileMode(0644)) } + +// Entrypoint returns the Java runtime Docker entrypoint that will be executed when the function is executed. +func (lh *JavaLangHelper) Entrypoint() string { + return "java -cp app/*:lib/* com.oracle.faas.runtime.EntryPoint com.example.faas.HelloFunction::handleRequest" +} + +// DockerfileCopyCmds returns the Docker COPY command to copy the compiled Java function jar and dependencies. +func (lh *JavaLangHelper) DockerfileCopyCmds() []string { + return []string{ + "COPY --from=build-stage /function/target/*.jar /function/app/", + "COPY --from=build-stage /function/target/dependency/*.jar /function/lib/", + } +} + +// DockerfileBuildCmds returns the build stage steps to compile the Maven function project. +func (lh *JavaLangHelper) DockerfileBuildCmds() []string { + return []string{ + fmt.Sprintf("ENV MAVEN_OPTS %s", mavenOpts()), + "ADD pom.xml /function/pom.xml", + "RUN [\"mvn\", \"package\", \"dependency:go-offline\", \"-DstripVersion=true\", \"-Dmdep.prependGroupId=true\"," + + " \"dependency:copy-dependencies\"]", + "ADD src /function/src", + "RUN [\"mvn\", \"package\"]", + } +} + +// HasPreBuild returns whether the Java Maven runtime has a pre-build step. +func (lh *JavaLangHelper) HasPreBuild() bool { return true } + +// PreBuild ensures that the expected the function is based is a maven project. +func (lh *JavaLangHelper) PreBuild() error { + wd, err := os.Getwd() + if err != nil { + return err + } + + if !exists(filepath.Join(wd, "pom.xml")) { + return errors.New("Could not find pom.xml - are you sure this is a Maven project?") + } + + return nil +} + +func mavenOpts() string { + var opts bytes.Buffer + + if parsedURL, err := url.Parse(os.Getenv("http_proxy")); err == nil { + opts.WriteString(fmt.Sprintf("-Dhttp.proxyHost=%s ", parsedURL.Hostname())) + opts.WriteString(fmt.Sprintf("-Dhttp.proxyPort=%s ", parsedURL.Port())) + } + + if parsedURL, err := url.Parse(os.Getenv("https_proxy")); err == nil { + opts.WriteString(fmt.Sprintf("-Dhttps.proxyHost=%s ", parsedURL.Hostname())) + opts.WriteString(fmt.Sprintf("-Dhttps.proxyPort=%s ", parsedURL.Port())) + } + + nonProxyHost := os.Getenv("no_proxy") + opts.WriteString(fmt.Sprintf("-Dhttp.nonProxyHosts=%s ", strings.Replace(nonProxyHost, ",", "|", -1))) + + opts.WriteString("-Dmaven.repo.local=/usr/share/maven/ref/repository") + + return opts.String() +} + +/* TODO temporarily generate maven project boilerplate from hardcoded values. + Will eventually move to using a maven archetype. +*/ + +const ( + pomFile = ` + + 4.0.0 + + UTF-8 + + com.example.faas + hello + 1.0.0-SNAPSHOT + + + + nexus-box + http://10.167.103.241:8081/repository/maven-snapshots/ + + + + + + com.oracle.faas + fdk + 1.0.0-SNAPSHOT + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + +` + + helloJavaFunctionBoilerplate = `package com.example.faas; + +public class HelloFunction { + + public String handleRequest(String input) { + String name = (input == null || input.isEmpty()) ? "world" : input; + + return "Hello, " + name + "!"; + } + +}` +) diff --git a/fn/langs/java_maven.go b/fn/langs/java_maven.go deleted file mode 100644 index 869e49853..000000000 --- a/fn/langs/java_maven.go +++ /dev/null @@ -1,184 +0,0 @@ -package langs - -import ( - "fmt" - "os" - "path/filepath" - "errors" - "bytes" - "strings" - "net/url" - "io/ioutil" -) - -// JavaMavenLangHelper provides a set of helper methods for the build lifecycle of Java Maven projects -type JavaMavenLangHelper struct { - BaseHelper -} - -// BuildFromImage returns the Docker image used to compile the Maven function project -func (lh *JavaMavenLangHelper) BuildFromImage() string { - return "maven:3.5-jdk-8-alpine" -} - -// RunFromImage returns the Docker image used to run the Maven built function -func (lh *JavaMavenLangHelper) RunFromImage() string { - return "funcy/java" -} - - -// DockerfileBuildCmds returns the build stage steps to compile the Maven function project -func (lh *JavaMavenLangHelper) DockerfileBuildCmds() []string { - return []string{ - fmt.Sprintf("ENV MAVEN_OPTS %s", mavenOpts()), - "ADD pom.xml /function/pom.xml", - "RUN [\"mvn\", \"package\", \"dependency:go-offline\", \"-DstripVersion=true\", \"-Dmdep.prependGroupId=true\"," + - " \"dependency:copy-dependencies\"]", - "ADD src /function/src", - "RUN [\"mvn\", \"package\"]", - } -} - -func mavenOpts() string { - var opts bytes.Buffer - - if parsedURL, err := url.Parse(os.Getenv("http_proxy")); err == nil { - opts.WriteString(fmt.Sprintf("-Dhttp.proxyHost=%s ", parsedURL.Hostname())) - opts.WriteString(fmt.Sprintf("-Dhttp.proxyPort=%s ", parsedURL.Port())) - } - - if parsedURL, err := url.Parse(os.Getenv("https_proxy")); err == nil { - opts.WriteString(fmt.Sprintf("-Dhttps.proxyHost=%s ", parsedURL.Hostname())) - opts.WriteString(fmt.Sprintf("-Dhttps.proxyPort=%s ", parsedURL.Port())) - } - - nonProxyHost := os.Getenv("no_proxy") - opts.WriteString(fmt.Sprintf("-Dhttp.nonProxyHosts=%s ", strings.Replace(nonProxyHost, ",", "|", -1))) - - opts.WriteString("-Dmaven.repo.local=/usr/share/maven/ref/repository") - - return opts.String() -} - -// DockerfileCopyCmds returns the Docker COPY command to copy the compiled Java function classes -func (lh *JavaMavenLangHelper) DockerfileCopyCmds() []string { - return []string{ - "COPY --from=build-stage /function/target/*.jar /function/app/", - "COPY --from=build-stage /function/target/dependency/*.jar /function/lib/", - } -} - -// HasPreBuild returns whether the Java Maven runtime has a pre-build step -func (lh *JavaMavenLangHelper) HasPreBuild() bool { - return true -} - -// PreBuild ensures that the expected the function is based is a maven project -func (lh *JavaMavenLangHelper) PreBuild() error { - wd, err := os.Getwd() - if err != nil { - return err - } - - if !exists(filepath.Join(wd, "pom.xml")) { - return errors.New("Could not find pom.xml - are you sure this is a maven project?") - } - - return nil -} - -// Entrypoint returns the Java runtime Docker entrypoint that will be executed when the function is run -func (lh *JavaMavenLangHelper) Entrypoint() string { - return "java -cp app/*:lib/* com.oracle.faas.runtime.EntryPoint com.example.faas.HelloFunction::handleRequest" -} - -// HasPreBuild returns whether the Java Maven runtime has boilerplate that can be generated. -func (lh *JavaMavenLangHelper) HasBoilerplate() bool { return true } - -// GenerateBoilerplate will generate function boilerplate for a Java Maven runtime -func (lh *JavaMavenLangHelper) GenerateBoilerplate() error { - wd, err := os.Getwd() - if err != nil { - return err - } - - pathToPomFile := filepath.Join(wd, "pom.xml") - if exists(pathToPomFile) { - return ErrBoilerplateExists - } - - if err := ioutil.WriteFile(pathToPomFile, []byte(pomFile), os.FileMode(0644)); err != nil { - return err - } - - helloJavaFunctionFileDir := filepath.Join(wd, "src/main/java/com/example/faas") - if err = os.MkdirAll(helloJavaFunctionFileDir, os.FileMode(0755)); err != nil { - os.Remove(pathToPomFile) - return err - } - - helloJavaFunctionFile := filepath.Join(helloJavaFunctionFileDir, "HelloFunction.java") - return ioutil.WriteFile(helloJavaFunctionFile, []byte(helloJavaFunctionBoilerplate), os.FileMode(0644)) -} - - -/* TODO temporarily generate maven project boilerplate from hardcoded values. - Will eventually move to using a maven archetype. -*/ - -const ( - pomFile = ` - - 4.0.0 - - UTF-8 - - com.example.faas - hello - 1.0.0-SNAPSHOT - - - - nexus-box - http://10.167.103.241:8081/repository/maven-snapshots/ - - - - - - com.oracle.faas - fdk - 1.0.0-SNAPSHOT - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.3 - - 1.8 - 1.8 - - - - - -` - - helloJavaFunctionBoilerplate = `package com.example.faas; - -public class HelloFunction { - - public String handleRequest(String input) { - String name = (input == null || input.isEmpty()) ? "world" : input; - - return "Hello, " + name + "!"; - } - -}` -) From ce7ef8bb993c5142b87a42193892033fb81c3e3f Mon Sep 17 00:00:00 2001 From: Mukhtar Haji Date: Thu, 6 Jul 2017 10:41:25 +0100 Subject: [PATCH 09/11] Split the deps into api and runtime This is based on !34 in jfaas. The plan is to only depend on the api in the pom file and for the runtime to be embedded into the run image. Currently, there isn't a registry to push a Java image with the runtime embedded so for run, we'll still reference both the api and runtime in the pom. --- fn/langs/java.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fn/langs/java.go b/fn/langs/java.go index bc3a5549e..5f882a557 100644 --- a/fn/langs/java.go +++ b/fn/langs/java.go @@ -142,7 +142,12 @@ const ( com.oracle.faas - fdk + api + 1.0.0-SNAPSHOT + + + com.oracle.faas + runtime 1.0.0-SNAPSHOT From cdc8c03c8709bdee017ce53aa521fe32f0af89b7 Mon Sep 17 00:00:00 2001 From: Mukhtar Haji Date: Tue, 11 Jul 2017 11:43:47 +0100 Subject: [PATCH 10/11] Remove runtime as a dependency and add test lib This change also simplifies the entrypoint in the func.yaml, so only the user's function is referenced. All runtime related stuff is completely abstracted away from the user. --- fn/langs/java.go | 64 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/fn/langs/java.go b/fn/langs/java.go index 5f882a557..3fc4d44a6 100644 --- a/fn/langs/java.go +++ b/fn/langs/java.go @@ -20,7 +20,7 @@ type JavaLangHelper struct { func (lh *JavaLangHelper) BuildFromImage() string { return "maven:3.5-jdk-8-alpine" } // RunFromImage returns the Docker image used to run the Java function. -func (lh *JavaLangHelper) RunFromImage() string { return "funcy/java" } +func (lh *JavaLangHelper) RunFromImage() string { return "registry.oracledx.com/skeppare/jfaas-runtime:latest" } // HasPreBuild returns whether the Java runtime has boilerplate that can be generated. func (lh *JavaLangHelper) HasBoilerplate() bool { return true } @@ -42,19 +42,27 @@ func (lh *JavaLangHelper) GenerateBoilerplate() error { return err } - helloJavaFunctionFileDir := filepath.Join(wd, "src/main/java/com/example/faas") - if err = os.MkdirAll(helloJavaFunctionFileDir, os.FileMode(0755)); err != nil { - os.Remove(pathToPomFile) + mkDirAndWriteFile := func(dir, filename, content string) error { + fullPath := filepath.Join(wd, dir) + if err = os.MkdirAll(fullPath, os.FileMode(0755)); err != nil { + return err + } + + fullFilePath := filepath.Join(fullPath, filename) + return ioutil.WriteFile(fullFilePath, []byte(content), os.FileMode(0644)) + } + + err = mkDirAndWriteFile("src/main/java/com/example/faas", "HelloFunction.java", helloJavaSrcBoilerplate) + if err != nil { return err } - helloJavaFunctionFile := filepath.Join(helloJavaFunctionFileDir, "HelloFunction.java") - return ioutil.WriteFile(helloJavaFunctionFile, []byte(helloJavaFunctionBoilerplate), os.FileMode(0644)) + return mkDirAndWriteFile("src/test/java/com/example/faas", "HelloFunctionTest.java", helloJavaTestBoilerplate) } // Entrypoint returns the Java runtime Docker entrypoint that will be executed when the function is executed. -func (lh *JavaLangHelper) Entrypoint() string { - return "java -cp app/*:lib/* com.oracle.faas.runtime.EntryPoint com.example.faas.HelloFunction::handleRequest" +func (lh *JavaLangHelper) Cmd() string { + return "com.example.faas.HelloFunction::handleRequest" } // DockerfileCopyCmds returns the Docker COPY command to copy the compiled Java function jar and dependencies. @@ -70,8 +78,7 @@ func (lh *JavaLangHelper) DockerfileBuildCmds() []string { return []string{ fmt.Sprintf("ENV MAVEN_OPTS %s", mavenOpts()), "ADD pom.xml /function/pom.xml", - "RUN [\"mvn\", \"package\", \"dependency:go-offline\", \"-DstripVersion=true\", \"-Dmdep.prependGroupId=true\"," + - " \"dependency:copy-dependencies\"]", + "RUN [\"mvn\", \"package\", \"dependency:copy-dependencies\", \"-DincludeScope=runtime\", \"-DskipTests=true\", \"-Dmdep.prependGroupId=true\"]", "ADD src /function/src", "RUN [\"mvn\", \"package\"]", } @@ -130,7 +137,7 @@ const ( com.example.faas hello - 1.0.0-SNAPSHOT + 1.0.0 @@ -147,8 +154,15 @@ const ( com.oracle.faas - runtime + testing 1.0.0-SNAPSHOT + test + + + junit + junit + 4.12 + test @@ -168,7 +182,7 @@ const ( ` - helloJavaFunctionBoilerplate = `package com.example.faas; + helloJavaSrcBoilerplate = `package com.example.faas; public class HelloFunction { @@ -178,5 +192,29 @@ public class HelloFunction { return "Hello, " + name + "!"; } +}` + + helloJavaTestBoilerplate = `package com.example.faas; + +import com.oracle.faas.testing.FnTesting; +import org.junit.*; + +import static org.junit.Assert.*; +import static com.oracle.faas.testing.FnTesting.*; + +public class HelloFunctionTest { + + @Rule + public final FnTesting testing = FnTesting.createDefault(); + + @Test + public void shouldReturnGreeting() { + testing.givenEvent().enqueue(); + testing.thenRun(HelloFunction.class, "handleRequest"); + + FnResult result = testing.getOnlyResult(); + assertEquals("Hello, world!", result.getBodyAsString()); + } + }` ) From 73e81ec7364e7d165818336cd92d10b9216a53a1 Mon Sep 17 00:00:00 2001 From: Mukhtar Haji Date: Thu, 13 Jul 2017 10:47:59 +0100 Subject: [PATCH 11/11] Update test boilerplate to reflect API changes --- fn/langs/java.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fn/langs/java.go b/fn/langs/java.go index 3fc4d44a6..98d651920 100644 --- a/fn/langs/java.go +++ b/fn/langs/java.go @@ -196,11 +196,10 @@ public class HelloFunction { helloJavaTestBoilerplate = `package com.example.faas; -import com.oracle.faas.testing.FnTesting; +import com.oracle.faas.testing.*; import org.junit.*; import static org.junit.Assert.*; -import static com.oracle.faas.testing.FnTesting.*; public class HelloFunctionTest {