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 )