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  )