diff --git a/examples/tutorial/hello/java/.gitignore b/examples/tutorial/hello/java/.gitignore new file mode 100644 index 000000000..735b5aea6 --- /dev/null +++ b/examples/tutorial/hello/java/.gitignore @@ -0,0 +1 @@ +func.yaml \ No newline at end of file diff --git a/examples/tutorial/hello/java/Func.java b/examples/tutorial/hello/java/Func.java new file mode 100644 index 000000000..b4c94285d --- /dev/null +++ b/examples/tutorial/hello/java/Func.java @@ -0,0 +1,13 @@ +import java.io.*; + +public class Func { + 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 + "!"); + } + +} diff --git a/examples/tutorial/hello/java/README.md b/examples/tutorial/hello/java/README.md new file mode 100644 index 000000000..f5a5fbd31 --- /dev/null +++ b/examples/tutorial/hello/java/README.md @@ -0,0 +1,43 @@ +# Oracle Functions: Java +This is a hello world example of a Oracle Function using the Java runtime. + +Firstly, we initialize our function by creating a `func.yaml` using `fn init`. This command can optionally take a `--runtime` flag to explicitly specify the target function runtime. In this example, the target runtime is implied to be Java because there is a `Func.java` file in the working directory. + +```sh +$ fn init /hello-java +``` + +This is what our `func.yaml` looks like now. + + +``` +name: mhaji/hello-java +version: 0.0.1 +runtime: java +entrypoint: java Func +path: /hello-java +max_concurrency: 1 +``` + +Next, we build and run our function using `fn run`. + + +```sh +$ fn run +Hello, world! +``` + +You can also pipe input via `stdin` into to the function as follows: + +```sh +$ echo "Michael FassBender" | fn run +Hello Michael FassBender! +``` + +To execute your function via a HTTP trigger: + +```sh +fn apps create myapp +fn routes create myapp /hello +curl -H "Content-Type: text/plain" -X POST -d "Michael FassBender" http://localhost:8080/r/myapp/hello +``` \ No newline at end of file diff --git a/fn/init.go b/fn/init.go index a5f36721f..a0736ae8e 100644 --- a/fn/init.go +++ b/fn/init.go @@ -31,6 +31,7 @@ var ( ".rs": "rust", ".cs": "dotnet", ".fs": "dotnet", + ".java": "java", } fnInitRuntimes []string @@ -104,11 +105,20 @@ func (a *initFnCmd) init(c *cli.Context) error { } } + runtimeSpecified := a.runtime != "" + err := a.buildFuncFile(c) if err != nil { return err } + if runtimeSpecified { + err := a.generateBoilerplate() + if err != nil { + return err + } + } + var ffmt *string if a.format != "" { ffmt = &a.format @@ -135,6 +145,20 @@ func (a *initFnCmd) init(c *cli.Context) error { return nil } +func (a *initFnCmd) generateBoilerplate() error { + helper := langs.GetLangHelper(a.runtime) + if helper != nil && helper.HasBoilerplate() { + if err := helper.GenerateBoilerplate(); err != nil { + if err == langs.ErrBoilerplateExists { + return nil + } + return err + } + fmt.Println("function boilerplate generated.") + } + return nil +} + func (a *initFnCmd) buildFuncFile(c *cli.Context) error { pwd, err := os.Getwd() if err != nil { @@ -190,9 +214,14 @@ func (a *initFnCmd) buildFuncFile(c *cli.Context) error { func detectRuntime(path string) (runtime string, err error) { for ext, runtime := range fileExtToRuntime { - fn := filepath.Join(path, fmt.Sprintf("func%s", ext)) - if exists(fn) { - return runtime, nil + filenames := []string { + filepath.Join(path, fmt.Sprintf("func%s", ext)), + filepath.Join(path, fmt.Sprintf("Func%s", ext)), + } + for _, filename := range filenames { + if exists(filename) { + return runtime, nil + } } } return "", fmt.Errorf("no supported files found to guess runtime, please set runtime explicitly with --runtime flag.") diff --git a/fn/langs/base.go b/fn/langs/base.go index 1319ca014..1449dce81 100644 --- a/fn/langs/base.go +++ b/fn/langs/base.go @@ -1,5 +1,15 @@ package langs +import ( + "errors" + "os" + "fmt" +) + +var ( + ErrBoilerplateExists = errors.New("Function boilerplate already exists") +) + // GetLangHelper returns a LangHelper for the passed in language func GetLangHelper(lang string) LangHelper { switch lang { @@ -19,6 +29,8 @@ func GetLangHelper(lang string) LangHelper { return &DotNetLangHelper{} case "lambda-nodejs4.3": return &LambdaNodeHelper{} + case "java": + return &JavaLangHelper{} } return nil } @@ -31,6 +43,11 @@ type LangHelper interface { HasPreBuild() bool PreBuild() error AfterBuild() error + // HasBoilerplate indicates whether a language has support for generating function boilerplate. + HasBoilerplate() bool + // GenerateBoilerplate generates basic function boilerplate. Returns ErrBoilerplateExists if the function file + // already exists. + GenerateBoilerplate() error } // BaseHelper is empty implementation of LangHelper for embedding in implementations. @@ -38,3 +55,18 @@ type BaseHelper struct { } func (h *BaseHelper) Cmd() string { return "" } +func (h *BaseHelper) HasBoilerplate() bool { return false } +func (h *BaseHelper) GenerateBoilerplate() error { return nil } + +func exists(name string) bool { + if _, err := os.Stat(name); err != nil { + if os.IsNotExist(err) { + return false + } + } + return true +} + +func dockerBuildError(err error) error { + return fmt.Errorf("error running docker build: %v", err) +} \ No newline at end of file diff --git a/fn/langs/dotnet.go b/fn/langs/dotnet.go index 1ecf21482..92757e85e 100644 --- a/fn/langs/dotnet.go +++ b/fn/langs/dotnet.go @@ -1,7 +1,6 @@ package langs import ( - "fmt" "os" "os/exec" ) @@ -34,7 +33,7 @@ func (lh *DotNetLangHelper) PreBuild() error { cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout if err := cmd.Run(); err != nil { - return fmt.Errorf("error running docker build: %v", err) + return dockerBuildError(err) } return nil } diff --git a/fn/langs/go.go b/fn/langs/go.go index c3f373054..d06f112d4 100644 --- a/fn/langs/go.go +++ b/fn/langs/go.go @@ -36,7 +36,7 @@ func (lh *GoLangHelper) PreBuild() error { cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout if err := cmd.Run(); err != nil { - return fmt.Errorf("error running docker build: %v", err) + return dockerBuildError(err) } return nil } diff --git a/fn/langs/java.go b/fn/langs/java.go new file mode 100644 index 000000000..879bea137 --- /dev/null +++ b/fn/langs/java.go @@ -0,0 +1,118 @@ +package langs + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" +) + +// JavaLangHelper provides a set of helper methods for the build lifecycle of the Java runtime +type JavaLangHelper struct { + BaseHelper +} + +const ( + mainClass = "Func" + mainClassFile = mainClass + ".java" +) + +// 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 executes the pre-build step for the Java runtime which involves compiling the relevant classes. It expects +// the entrypoint to the function, in other words or the class with the main method (not to be confused with the Docker +// entrypoint from Entrypoint()) to be Function.java +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) + } + + cmd := exec.Command( + "docker", "run", + "--rm", "-v", wd+":/java", "-w", "/java", + "funcy/java:dev", + "/bin/sh", "-c", "javac "+mainClassFile, + ) + + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + return dockerBuildError(err) + } + return nil +} + +// AfterBuild removes all compiled class files from the host machine +func (lh *JavaLangHelper) AfterBuild() error { + wd, err := os.Getwd() + if err != nil { + return err + } + + files, err := filepath.Glob(filepath.Join(wd, "*.class")) + if err != nil { + return err + } + + for _, file := range files { + err = os.Remove(file) + if err != nil { + return err + } + } + + return nil +} + +// 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 +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)) { + return ErrBoilerplateExists + } + return ioutil.WriteFile(pathToFunctionFile, []byte(javaFunctionBoilerplate), os.FileMode(0644)) +} diff --git a/fn/langs/php.go b/fn/langs/php.go index e97dd5d6e..dfd5df109 100644 --- a/fn/langs/php.go +++ b/fn/langs/php.go @@ -39,7 +39,7 @@ func (lh *PhpLangHelper) PreBuild() error { cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout if err := cmd.Run(); err != nil { - return fmt.Errorf("error running docker build: %v", err) + return dockerBuildError(err) } return nil } diff --git a/fn/langs/python.go b/fn/langs/python.go index fb7cb5da2..f8370eb26 100644 --- a/fn/langs/python.go +++ b/fn/langs/python.go @@ -35,7 +35,7 @@ func (lh *PythonHelper) PreBuild() error { cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout if err := cmd.Run(); err != nil { - return fmt.Errorf("error running docker build: %v", err) + return dockerBuildError(err) } return nil } diff --git a/fn/langs/ruby.go b/fn/langs/ruby.go index a87672349..a5164f79f 100644 --- a/fn/langs/ruby.go +++ b/fn/langs/ruby.go @@ -39,20 +39,11 @@ func (lh *RubyLangHelper) PreBuild() error { cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout if err := cmd.Run(); err != nil { - return fmt.Errorf("error running docker build: %v", err) + return dockerBuildError(err) } return nil } func (lh *RubyLangHelper) AfterBuild() error { return nil -} - -func exists(name string) bool { - if _, err := os.Stat(name); err != nil { - if os.IsNotExist(err) { - return false - } - } - return true -} +} \ No newline at end of file diff --git a/fn/langs/rust.go b/fn/langs/rust.go index 95c33cacd..f4fbeb123 100644 --- a/fn/langs/rust.go +++ b/fn/langs/rust.go @@ -1,7 +1,6 @@ package langs import ( - "fmt" "os" "os/exec" ) @@ -34,7 +33,7 @@ func (lh *RustLangHelper) PreBuild() error { cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout if err := cmd.Run(); err != nil { - return fmt.Errorf("error running docker build: %v", err) + return dockerBuildError(err) } return nil }