github.com/devcamcar/cli@v0.0.0-20181107134215-706a05759d18/langs/java.go (about)

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