github.com/aneshas/cli@v0.0.0-20180104210444-aec958fa47db/langs/java.go (about)

     1  package langs
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/url"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  )
    15  
    16  // JavaLangHelper provides a set of helper methods for the lifecycle of Java Maven projects
    17  type JavaLangHelper struct {
    18  	BaseHelper
    19  	version          string
    20  	latestFdkVersion string
    21  }
    22  
    23  func (h *JavaLangHelper) Handles(lang string) bool {
    24  	for _, s := range h.LangStrings() {
    25  		if lang == s {
    26  			return true
    27  		}
    28  	}
    29  	return false
    30  }
    31  func (h *JavaLangHelper) Runtime() string {
    32  	return h.LangStrings()[0]
    33  }
    34  
    35  // TOOD: same as python, I think we should just have version tags on the single runtime, eg: `java:8` or `java:9`
    36  func (lh *JavaLangHelper) LangStrings() []string {
    37  	if lh.version == "1.8" {
    38  		return []string{"java8"}
    39  	}
    40  	return []string{"java9", "java"}
    41  
    42  }
    43  func (lh *JavaLangHelper) Extensions() []string {
    44  	return []string{".java"}
    45  }
    46  
    47  // BuildFromImage returns the Docker image used to compile the Maven function project
    48  func (lh *JavaLangHelper) BuildFromImage() (string, error) {
    49  
    50  	fdkVersion, err := lh.getFDKAPIVersion()
    51  	if err != nil {
    52  		return "", err
    53  	}
    54  
    55  	if lh.version == "1.8" {
    56  		return fmt.Sprintf("fnproject/fn-java-fdk-build:%s", fdkVersion), nil
    57  	} else if lh.version == "9" {
    58  		return fmt.Sprintf("fnproject/fn-java-fdk-build:jdk9-%s", fdkVersion), nil
    59  	} else {
    60  		return "", fmt.Errorf("unsupported java version %s", lh.version)
    61  	}
    62  }
    63  
    64  // RunFromImage returns the Docker image used to run the Java function.
    65  func (lh *JavaLangHelper) RunFromImage() (string, error) {
    66  	fdkVersion, err := lh.getFDKAPIVersion()
    67  	if err != nil {
    68  		return "", err
    69  	}
    70  	if lh.version == "1.8" {
    71  		return fmt.Sprintf("fnproject/fn-java-fdk:%s", fdkVersion), nil
    72  	} else if lh.version == "9" {
    73  		return fmt.Sprintf("fnproject/fn-java-fdk:jdk9-%s", fdkVersion), nil
    74  	} else {
    75  		return "", fmt.Errorf("unsupported java version %s", lh.version)
    76  	}
    77  }
    78  
    79  // HasBoilerplate returns whether the Java runtime has boilerplate that can be generated.
    80  func (lh *JavaLangHelper) HasBoilerplate() bool { return true }
    81  
    82  // Java defaults to http
    83  func (lh *JavaLangHelper) DefaultFormat() string { return "http" }
    84  
    85  // GenerateBoilerplate will generate function boilerplate for a Java runtime. The default boilerplate is for a Maven
    86  // project.
    87  func (lh *JavaLangHelper) GenerateBoilerplate() error {
    88  	wd, err := os.Getwd()
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	pathToPomFile := filepath.Join(wd, "pom.xml")
    94  	if exists(pathToPomFile) {
    95  		return ErrBoilerplateExists
    96  	}
    97  
    98  	apiVersion, err := lh.getFDKAPIVersion()
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	if err := ioutil.WriteFile(pathToPomFile, []byte(pomFileContent(apiVersion, lh.version)), os.FileMode(0644)); err != nil {
   104  		return err
   105  	}
   106  
   107  	mkDirAndWriteFile := func(dir, filename, content string) error {
   108  		fullPath := filepath.Join(wd, dir)
   109  		if err = os.MkdirAll(fullPath, os.FileMode(0755)); err != nil {
   110  			return err
   111  		}
   112  
   113  		fullFilePath := filepath.Join(fullPath, filename)
   114  		return ioutil.WriteFile(fullFilePath, []byte(content), os.FileMode(0644))
   115  	}
   116  
   117  	err = mkDirAndWriteFile("src/main/java/com/example/fn", "HelloFunction.java", helloJavaSrcBoilerplate)
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	return mkDirAndWriteFile("src/test/java/com/example/fn", "HelloFunctionTest.java", helloJavaTestBoilerplate)
   123  }
   124  
   125  // Cmd returns the Java runtime Docker entrypoint that will be executed when the function is executed.
   126  func (lh *JavaLangHelper) Cmd() (string, error) {
   127  	return "com.example.fn.HelloFunction::handleRequest", nil
   128  }
   129  
   130  // DockerfileCopyCmds returns the Docker COPY command to copy the compiled Java function jar and dependencies.
   131  func (lh *JavaLangHelper) DockerfileCopyCmds() []string {
   132  	return []string{
   133  		"COPY --from=build-stage /function/target/*.jar /function/app/",
   134  	}
   135  }
   136  
   137  // DockerfileBuildCmds returns the build stage steps to compile the Maven function project.
   138  func (lh *JavaLangHelper) DockerfileBuildCmds() []string {
   139  	return []string{
   140  		fmt.Sprintf("ENV MAVEN_OPTS %s", mavenOpts()),
   141  		"ADD pom.xml /function/pom.xml",
   142  		"RUN [\"mvn\", \"package\", \"dependency:copy-dependencies\", \"-DincludeScope=runtime\", " +
   143  			"\"-DskipTests=true\", \"-Dmdep.prependGroupId=true\", \"-DoutputDirectory=target\", \"--fail-never\"]",
   144  		"ADD src /function/src",
   145  		"RUN [\"mvn\", \"package\"]",
   146  	}
   147  }
   148  
   149  // HasPreBuild returns whether the Java Maven runtime has a pre-build step.
   150  func (lh *JavaLangHelper) HasPreBuild() bool { return true }
   151  
   152  // PreBuild ensures that the expected the function is based is a maven project.
   153  func (lh *JavaLangHelper) PreBuild() error {
   154  	wd, err := os.Getwd()
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	if !exists(filepath.Join(wd, "pom.xml")) {
   160  		return errors.New("Could not find pom.xml - are you sure this is a Maven project?")
   161  	}
   162  
   163  	return nil
   164  }
   165  
   166  func mavenOpts() string {
   167  	var opts bytes.Buffer
   168  
   169  	if parsedURL, err := url.Parse(os.Getenv("http_proxy")); err == nil {
   170  		opts.WriteString(fmt.Sprintf("-Dhttp.proxyHost=%s ", parsedURL.Hostname()))
   171  		opts.WriteString(fmt.Sprintf("-Dhttp.proxyPort=%s ", parsedURL.Port()))
   172  	}
   173  
   174  	if parsedURL, err := url.Parse(os.Getenv("https_proxy")); err == nil {
   175  		opts.WriteString(fmt.Sprintf("-Dhttps.proxyHost=%s ", parsedURL.Hostname()))
   176  		opts.WriteString(fmt.Sprintf("-Dhttps.proxyPort=%s ", parsedURL.Port()))
   177  	}
   178  
   179  	nonProxyHost := os.Getenv("no_proxy")
   180  	opts.WriteString(fmt.Sprintf("-Dhttp.nonProxyHosts=%s ", strings.Replace(nonProxyHost, ",", "|", -1)))
   181  
   182  	opts.WriteString("-Dmaven.repo.local=/usr/share/maven/ref/repository")
   183  
   184  	return opts.String()
   185  }
   186  
   187  /*    TODO temporarily generate maven project boilerplate from hardcoded values.
   188  Will eventually move to using a maven archetype.
   189  */
   190  func pomFileContent(APIversion, javaVersion string) string {
   191  	return fmt.Sprintf(pomFile, APIversion, javaVersion, javaVersion)
   192  }
   193  
   194  func (lh *JavaLangHelper) getFDKAPIVersion() (string, error) {
   195  
   196  	if lh.latestFdkVersion != "" {
   197  		return lh.latestFdkVersion, nil
   198  	}
   199  
   200  	const versionURL = "https://api.bintray.com/search/packages/maven?repo=fnproject&g=com.fnproject.fn&a=fdk"
   201  	const versionEnv = "FN_JAVA_FDK_VERSION"
   202  	fetchError := fmt.Errorf("Failed to fetch latest Java FDK javaVersion from %v. Check your network settings or manually override the javaVersion by setting %s", versionURL, versionEnv)
   203  
   204  	type parsedResponse struct {
   205  		Version string `json:"latest_version"`
   206  	}
   207  	version := os.Getenv(versionEnv)
   208  	if version != "" {
   209  		return version, nil
   210  	}
   211  	resp, err := http.Get(versionURL)
   212  	if err != nil || resp.StatusCode != 200 {
   213  		return "", fetchError
   214  	}
   215  
   216  	buf := bytes.Buffer{}
   217  	_, err = buf.ReadFrom(resp.Body)
   218  	if err != nil {
   219  		return "", fetchError
   220  	}
   221  
   222  	parsedResp := make([]parsedResponse, 1)
   223  	err = json.Unmarshal(buf.Bytes(), &parsedResp)
   224  	if err != nil {
   225  		return "", fetchError
   226  	}
   227  
   228  	version = parsedResp[0].Version
   229  	lh.latestFdkVersion = version
   230  	return version, nil
   231  }
   232  
   233  func (lh *JavaLangHelper) FixImagesOnInit() bool {
   234  	return true
   235  }
   236  
   237  const (
   238  	pomFile = `<?xml version="1.0" encoding="UTF-8"?>
   239  <project xmlns="http://maven.apache.org/POM/4.0.0"
   240           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   241           xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   242      <modelVersion>4.0.0</modelVersion>
   243      <properties>
   244          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   245          <fdk.version>%s</fdk.version>
   246      </properties>
   247      <groupId>com.example.fn</groupId>
   248      <artifactId>hello</artifactId>
   249      <version>1.0.0</version>
   250  
   251      <repositories>
   252          <repository>
   253              <id>fn-release-repo</id>
   254              <url>https://dl.bintray.com/fnproject/fnproject</url>
   255              <releases>
   256                  <enabled>true</enabled>
   257              </releases>
   258              <snapshots>
   259                  <enabled>false</enabled>
   260              </snapshots>
   261          </repository>
   262      </repositories>
   263  
   264      <dependencies>
   265          <dependency>
   266              <groupId>com.fnproject.fn</groupId>
   267              <artifactId>api</artifactId>
   268              <version>${fdk.version}</version>
   269          </dependency>
   270          <dependency>
   271              <groupId>com.fnproject.fn</groupId>
   272              <artifactId>testing</artifactId>
   273              <version>${fdk.version}</version>
   274              <scope>test</scope>
   275          </dependency>
   276          <dependency>
   277              <groupId>junit</groupId>
   278              <artifactId>junit</artifactId>
   279              <version>4.12</version>
   280              <scope>test</scope>
   281          </dependency>
   282      </dependencies>
   283  
   284      <build>
   285          <plugins>
   286              <plugin>
   287                  <groupId>org.apache.maven.plugins</groupId>
   288                  <artifactId>maven-compiler-plugin</artifactId>
   289                  <version>3.3</version>
   290                  <configuration>
   291                      <source>%s</source>
   292                      <target>%s</target>
   293                  </configuration>
   294              </plugin>
   295          </plugins>
   296      </build>
   297  </project>
   298  `
   299  
   300  	helloJavaSrcBoilerplate = `package com.example.fn;
   301  
   302  public class HelloFunction {
   303  
   304      public String handleRequest(String input) {
   305          String name = (input == null || input.isEmpty()) ? "world"  : input;
   306  
   307          return "Hello, " + name + "!";
   308      }
   309  
   310  }`
   311  
   312  	helloJavaTestBoilerplate = `package com.example.fn;
   313  
   314  import com.fnproject.fn.testing.*;
   315  import org.junit.*;
   316  
   317  import static org.junit.Assert.*;
   318  
   319  public class HelloFunctionTest {
   320  
   321      @Rule
   322      public final FnTestingRule testing = FnTestingRule.createDefault();
   323  
   324      @Test
   325      public void shouldReturnGreeting() {
   326          testing.givenEvent().enqueue();
   327          testing.thenRun(HelloFunction.class, "handleRequest");
   328  
   329          FnResult result = testing.getOnlyResult();
   330          assertEquals("Hello, world!", result.getBodyAsString());
   331      }
   332  
   333  }`
   334  )