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