Weather Function Sample (#1746)
This commit is contained in:
committed by
Nikolay Igotti
parent
7b730544ea
commit
ba30936b9d
@@ -0,0 +1,16 @@
|
|||||||
|
# Additional files/dirs to unignore.
|
||||||
|
|
||||||
|
# Additional files/dirs to ignore.
|
||||||
|
c_interop/
|
||||||
|
*.kexe
|
||||||
|
*.bak
|
||||||
|
openweathermap_key.txt
|
||||||
|
temp.txt
|
||||||
|
temp.json
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
weather
|
||||||
|
.konan/
|
||||||
|
gradle/
|
||||||
|
gradlew
|
||||||
|
template/
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
FROM openjdk:8u171-jdk-stretch as builder
|
||||||
|
WORKDIR /opt
|
||||||
|
ENV kotlin_native_ver "0.7.1"
|
||||||
|
ENV kotlin_native_home "/opt/kotlin-native-linux-$kotlin_native_ver"
|
||||||
|
RUN wget "https://github.com/JetBrains/kotlin-native/releases/download/v$kotlin_native_ver/kotlin-native-linux-$kotlin_native_ver.tar.gz" \
|
||||||
|
&& tar -xzf kotlin-native-linux-$kotlin_native_ver.tar.gz && rm kotlin-native-linux-$kotlin_native_ver.tar.gz \
|
||||||
|
&& apt-get -qy update && apt-get install libcurl4-openssl-dev \
|
||||||
|
&& mkdir -p /app/src/main/kotlin/org/example/weather_func \
|
||||||
|
&& mkdir -p /app/lib/cJSON-1.7.7/include/cjson && mkdir -p /app/lib/cJSON-1.7.7/lib/x86_64-linux-gnu \
|
||||||
|
&& mkdir -p /app/gradle/wrapper
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY gradle/wrapper/gradle-wrapper.properties gradle/wrapper/gradle-wrapper.jar /app/gradle/wrapper/
|
||||||
|
COPY gradlew openweathermap_key.txt /app/
|
||||||
|
COPY .konan/ /root/.konan
|
||||||
|
COPY *.def *.kts /app/
|
||||||
|
COPY src/ /app/src
|
||||||
|
COPY lib/cJSON-1.7.7/include/cjson/cJSON.h /app/lib/cJSON-1.7.7/include/cJSON.h
|
||||||
|
COPY lib/cJSON-1.7.7/lib/x86_64-linux-gnu/libcjson.a /app/lib/cJSON-1.7.7/lib/libcjson.a
|
||||||
|
RUN ./gradlew build && cp build/konan/bin/linux_x64/weather.kexe weather
|
||||||
|
|
||||||
|
FROM debian:stretch
|
||||||
|
ADD https://github.com/openfaas/faas/releases/download/0.8.2/fwatchdog /usr/bin
|
||||||
|
RUN apt-get update && apt-get -qy install libcurl4-openssl-dev \
|
||||||
|
&& chmod +x /usr/bin/fwatchdog && mkdir -p /app
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/openweathermap_key.txt .
|
||||||
|
COPY --from=builder /app/weather .
|
||||||
|
RUN chmod +x weather
|
||||||
|
ENV fprocess "./weather"
|
||||||
|
HEALTHCHECK --interval=2s CMD [ -e /tmp/.lock ] || exit 1
|
||||||
|
CMD ["fwatchdog"]
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
group = "org.example"
|
||||||
|
version = "0.1-SNAPSHOT"
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("konan")
|
||||||
|
}
|
||||||
|
|
||||||
|
konanArtifacts {
|
||||||
|
interop("curl") {
|
||||||
|
defFile("curl.def")
|
||||||
|
}
|
||||||
|
|
||||||
|
interop("cjson") {
|
||||||
|
defFile("cjson.def")
|
||||||
|
}
|
||||||
|
|
||||||
|
program("weather") {
|
||||||
|
entryPoint("org.example.weather_func.main")
|
||||||
|
libraries {
|
||||||
|
artifact("cjson")
|
||||||
|
artifact("curl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package = cjson
|
||||||
|
headers = cJSON.h
|
||||||
|
compilerOpts = -std=c89
|
||||||
|
compilerOpts.linux = -I/app/lib/cJSON-1.7.7/include
|
||||||
|
staticLibraries = libcjson.a
|
||||||
|
libraryPaths = /app/lib/cJSON-1.7.7/lib
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package = curl
|
||||||
|
headers = curl.h
|
||||||
|
linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lcurl
|
||||||
|
compilerOpts = -std=c89
|
||||||
|
compilerOpts.linux = -I/usr/include/x86_64-linux-gnu/curl
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
rootProject.name = "weather-function"
|
||||||
|
|
||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
jcenter()
|
||||||
|
maven("https://dl.bintray.com/jetbrains/kotlin-native-dependencies")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This way we can map plugin to standard maven artifact
|
||||||
|
// for maven repositories without plugin descriptor
|
||||||
|
resolutionStrategy {
|
||||||
|
eachPlugin {
|
||||||
|
if (requested.id.id == "konan") {
|
||||||
|
val kotlinNativeVer = "0.7.1"
|
||||||
|
useModule("org.jetbrains.kotlin:kotlin-native-gradle-plugin:$kotlinNativeVer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.example.weather_func
|
||||||
|
|
||||||
|
/** Models the current weather for a location. */
|
||||||
|
internal data class Weather(
|
||||||
|
val placeName: String,
|
||||||
|
val countryCode: String,
|
||||||
|
val windSpeed: Double = 0.0,
|
||||||
|
val windDegrees: Int = 0,
|
||||||
|
val temp: Int,
|
||||||
|
val minTemp: Int,
|
||||||
|
val maxTemp: Int,
|
||||||
|
val humidity: Int,
|
||||||
|
val conditions: Array<Pair<String, String>>
|
||||||
|
)
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package org.example.weather_func
|
||||||
|
|
||||||
|
import org.example.weather_func.Event
|
||||||
|
import platform.posix.size_t
|
||||||
|
|
||||||
|
import curl.curl_easy_setopt
|
||||||
|
import curl.CURLOPT_URL
|
||||||
|
import curl.CURLOPT_HEADERFUNCTION
|
||||||
|
import curl.CURLOPT_HEADERDATA
|
||||||
|
import curl.CURLOPT_WRITEFUNCTION
|
||||||
|
import curl.CURLOPT_WRITEDATA
|
||||||
|
import curl.curl_easy_cleanup
|
||||||
|
import curl.curl_easy_init
|
||||||
|
import curl.CURLE_OK
|
||||||
|
import curl.curl_easy_strerror
|
||||||
|
import curl.curl_easy_perform
|
||||||
|
|
||||||
|
import kotlinx.cinterop.staticCFunction
|
||||||
|
import kotlinx.cinterop.COpaquePointer
|
||||||
|
import kotlinx.cinterop.CPointer
|
||||||
|
import kotlinx.cinterop.StableRef
|
||||||
|
import kotlinx.cinterop.asStableRef
|
||||||
|
import kotlinx.cinterop.ByteVar
|
||||||
|
import kotlinx.cinterop.readBytes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides basic HTTP client functionality through the libCurl library.
|
||||||
|
*/
|
||||||
|
internal class CUrl(val url: String) {
|
||||||
|
private val stableRef = StableRef.create(this)
|
||||||
|
private val curlObj = curl_easy_init()
|
||||||
|
val header = Event<String>()
|
||||||
|
val body = Event<String>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
val header = staticCFunction(::headerCallback)
|
||||||
|
val writeData = staticCFunction(::writeCallback)
|
||||||
|
|
||||||
|
// Setup Curl.
|
||||||
|
curl_easy_setopt(curlObj, CURLOPT_URL, url)
|
||||||
|
curl_easy_setopt(curlObj, CURLOPT_HEADERFUNCTION, header)
|
||||||
|
curl_easy_setopt(curlObj, CURLOPT_HEADERDATA, stableRef.asCPointer())
|
||||||
|
curl_easy_setopt(curlObj, CURLOPT_WRITEFUNCTION, writeData)
|
||||||
|
curl_easy_setopt(curlObj, CURLOPT_WRITEDATA, stableRef.asCPointer())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetch() {
|
||||||
|
// Have Curl do a HTTP/HTTPS request and store the response (status).
|
||||||
|
val response = curl_easy_perform(curlObj)
|
||||||
|
val maxChars = 50
|
||||||
|
// utfCharSize in bytes.
|
||||||
|
val utfCharSize = 4
|
||||||
|
// length in bytes.
|
||||||
|
val length = utfCharSize * maxChars
|
||||||
|
// Print the error message if the Curl status code isn't OK (CURLE_OK).
|
||||||
|
if (response != CURLE_OK) println("Curl HTTP/S request failed: ${curl_easy_strerror(response)?.toKString(length)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun close() {
|
||||||
|
curl_easy_cleanup(curlObj)
|
||||||
|
stableRef.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun headerCallback(buffer: CPointer<ByteVar>?, size: size_t, totalItems: size_t, userData: COpaquePointer?): size_t {
|
||||||
|
var responseSize = 0L
|
||||||
|
if (buffer != null && userData != null) {
|
||||||
|
val header = buffer.toKString((size * totalItems).toInt()).trim()
|
||||||
|
val curlRef = userData.asStableRef<CUrl>().get()
|
||||||
|
|
||||||
|
curlRef.header(header)
|
||||||
|
responseSize = size * totalItems
|
||||||
|
}
|
||||||
|
return responseSize
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeCallback(buffer: CPointer<ByteVar>?, size: size_t, totalItems: size_t, userData: COpaquePointer?): size_t {
|
||||||
|
var responseSize = 0L
|
||||||
|
if (buffer != null && userData != null) {
|
||||||
|
val data = buffer.toKString((size * totalItems).toInt()).trim()
|
||||||
|
val curlRef = userData.asStableRef<CUrl>().get()
|
||||||
|
|
||||||
|
curlRef.body(data)
|
||||||
|
responseSize = size * totalItems
|
||||||
|
}
|
||||||
|
return responseSize
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CPointer<ByteVar>.toKString(length: Int) = readBytes(length).stringFromUtf8()
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package org.example.weather_func
|
||||||
|
|
||||||
|
internal typealias EventHandler<T> = (T) -> Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Covers event handling for the program.
|
||||||
|
*/
|
||||||
|
internal class Event<T : Any?> {
|
||||||
|
private var handlers = emptyList<EventHandler<T>>()
|
||||||
|
|
||||||
|
fun subscribe(handler: EventHandler<T>) {
|
||||||
|
handlers += handler
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unsubscribe(handler: EventHandler<T>) {
|
||||||
|
handlers -= handler
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun plusAssign(handler: EventHandler<T>) = subscribe(handler)
|
||||||
|
operator fun minusAssign(handler: EventHandler<T>) = unsubscribe(handler)
|
||||||
|
|
||||||
|
operator fun invoke(value: T) {
|
||||||
|
var exception: Throwable? = null
|
||||||
|
for (handler in handlers) {
|
||||||
|
try { handler(value) }
|
||||||
|
catch (ex: Throwable) { exception = ex }
|
||||||
|
}
|
||||||
|
// If the exception isn't null then throw it.
|
||||||
|
exception?.let { throw it }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package org.example.weather_func
|
||||||
|
|
||||||
|
import cjson.cJSON_CreateObject as createJsonObject
|
||||||
|
import cjson.cJSON_AddStringToObject as addStringToObject
|
||||||
|
import cjson.cJSON_AddNumberToObject as addNumberToObject
|
||||||
|
import cjson.cJSON_AddArrayToObject as addArrayToObject
|
||||||
|
import cjson.cJSON_AddItemToArray as addItemToArray
|
||||||
|
import cjson.cJSON_AddItemToObjectCS as addItemToObject
|
||||||
|
|
||||||
|
import cjson.cJSON_Delete as deleteObject
|
||||||
|
import cjson.cJSON_Print as jsonString
|
||||||
|
import cjson.cJSON_Parse as parseJson
|
||||||
|
import cjson.cJSON_GetObjectItemCaseSensitive as jsonObjectItem
|
||||||
|
import cjson.cJSON_IsString as jsonValueIsString
|
||||||
|
|
||||||
|
import cjson.cJSON_IsNumber as jsonValueIsNumber
|
||||||
|
import cjson.cJSON_GetErrorPtr as getErrorPointer
|
||||||
|
import cjson.cJSON_GetStringValue as jsonStringValue
|
||||||
|
import cjson.cJSON_GetArraySize as jsonArraySize
|
||||||
|
import cjson.cJSON_GetArrayItem as jsonArrayItem
|
||||||
|
import cjson.cJSON
|
||||||
|
|
||||||
|
import kotlinx.cinterop.CPointer
|
||||||
|
import kotlinx.cinterop.toKString
|
||||||
|
import kotlinx.cinterop.pointed
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
private val trueValue = 1
|
||||||
|
|
||||||
|
internal fun createWeatherFromJson(jsonStr: String): Weather {
|
||||||
|
val rootObj = parseJson(jsonStr)
|
||||||
|
checkForError()
|
||||||
|
val result = createWeather(
|
||||||
|
placeName = jsonObjectItem(rootObj, "name").stringValue(),
|
||||||
|
countryCode = extractCountryCode(rootObj),
|
||||||
|
windInfo = extractWindInfo(rootObj),
|
||||||
|
conditions = extractConditions(rootObj),
|
||||||
|
tempInfo = extractTempInfo(rootObj),
|
||||||
|
humidity = extractHumidity(rootObj)
|
||||||
|
)
|
||||||
|
if (rootObj != null) deleteObject(rootObj)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Weather object.
|
||||||
|
* @param placeName Name of the place.
|
||||||
|
* @param countryCode Unique country code.
|
||||||
|
* @param windInfo A Pair with wind speed as 1st item, and wind degrees as 2nd item.
|
||||||
|
* @param conditions A Array of Pair. Each Pair has name as 1st item, and desc as 2nd item.
|
||||||
|
* @param humidity General humidity of the atmosphere.
|
||||||
|
* @param tempInfo A Triple with temperature as 1st item, min temperature as 2nd item, and max temperature as 3rd item.
|
||||||
|
* @return A instance of Weather.
|
||||||
|
*/
|
||||||
|
private fun createWeather(
|
||||||
|
placeName: String,
|
||||||
|
countryCode: String,
|
||||||
|
windInfo: Pair<Double, Int>,
|
||||||
|
conditions: Array<Pair<String, String>>,
|
||||||
|
humidity: Int,
|
||||||
|
tempInfo: Triple<Int, Int, Int>
|
||||||
|
) = Weather(
|
||||||
|
placeName = placeName,
|
||||||
|
countryCode = countryCode,
|
||||||
|
conditions = conditions,
|
||||||
|
humidity = humidity,
|
||||||
|
windSpeed = windInfo.first,
|
||||||
|
windDegrees = windInfo.second,
|
||||||
|
temp = tempInfo.first,
|
||||||
|
minTemp = tempInfo.second,
|
||||||
|
maxTemp = tempInfo.third
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun extractTempInfo(rootObj: CPointer<cJSON>?): Triple<Int, Int, Int> {
|
||||||
|
val mainObj = jsonObjectItem(rootObj, "main")
|
||||||
|
val temp = jsonObjectItem(mainObj, "temp").intValue()
|
||||||
|
val minTemp = jsonObjectItem(mainObj, "temp_min").intValue()
|
||||||
|
val maxTemp = jsonObjectItem(mainObj, "temp_max").intValue()
|
||||||
|
|
||||||
|
return Triple(temp, minTemp, maxTemp)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractHumidity(rootObj: CPointer<cJSON>?): Int {
|
||||||
|
val mainObj = jsonObjectItem(rootObj, "main")
|
||||||
|
|
||||||
|
return jsonObjectItem(mainObj, "humidity").intValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractConditions(rootObj: CPointer<cJSON>?): Array<Pair<String, String>> {
|
||||||
|
val tmp = mutableListOf<Pair<String, String>>()
|
||||||
|
val weatherObj = jsonObjectItem(rootObj, "weather")
|
||||||
|
|
||||||
|
for (pos in 0..(jsonArraySize(weatherObj) - 1)) {
|
||||||
|
val item = jsonArrayItem(weatherObj, pos)
|
||||||
|
val name = jsonObjectItem(item, "main").stringValue()
|
||||||
|
val desc = jsonObjectItem(item, "description").stringValue()
|
||||||
|
|
||||||
|
tmp += (name to desc)
|
||||||
|
}
|
||||||
|
return tmp.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractWindInfo(rootObj: CPointer<cJSON>?): Pair<Double, Int> {
|
||||||
|
val windObj = jsonObjectItem(rootObj, "wind")
|
||||||
|
val windSpeed = jsonObjectItem(windObj, "speed").doubleValue()
|
||||||
|
val windDegrees = jsonObjectItem(windObj, "deg").intValue()
|
||||||
|
|
||||||
|
return (windSpeed to windDegrees)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractCountryCode(rootObj: CPointer<cJSON>?): String {
|
||||||
|
val sys = jsonObjectItem(rootObj, "sys")
|
||||||
|
|
||||||
|
return jsonObjectItem(sys, "country").stringValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun weatherToJsonString(weather: Weather) : String {
|
||||||
|
val rootObj = createJsonObject()
|
||||||
|
addStringToObject(rootObj, "placeName", weather.placeName)
|
||||||
|
addStringToObject(rootObj, "countryCode", weather.countryCode)
|
||||||
|
addNumberToObject(rootObj, "humidity", weather.humidity.toDouble())
|
||||||
|
addTempInfoArray(rootObj, temp = weather.temp, minTemp = weather.minTemp, maxTemp = weather.maxTemp)
|
||||||
|
addConditionsArray(rootObj, weather.conditions)
|
||||||
|
val result = jsonString(rootObj)?.toKString() ?: ""
|
||||||
|
deleteObject(rootObj)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addTempInfoArray(rootObj: CPointer<cJSON>?, temp: Int, minTemp: Int, maxTemp: Int) {
|
||||||
|
val tmpObj = createJsonObject()
|
||||||
|
|
||||||
|
addNumberToObject(tmpObj, "temp", temp.toDouble())
|
||||||
|
addNumberToObject(tmpObj, "minTemp", minTemp.toDouble())
|
||||||
|
addNumberToObject(tmpObj, "maxTemp", maxTemp.toDouble())
|
||||||
|
addItemToObject(rootObj, "tempInfo", tmpObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addConditionsArray(rootObj: CPointer<cJSON>?, conditions: Array<Pair<String, String>>) {
|
||||||
|
val tmpArr = addArrayToObject(rootObj, "conditions")
|
||||||
|
|
||||||
|
conditions.forEach { (name, desc) ->
|
||||||
|
val tmpObj = createJsonObject()
|
||||||
|
addStringToObject(tmpObj, "name", name)
|
||||||
|
addStringToObject(tmpObj, "desc", desc)
|
||||||
|
addItemToArray(tmpArr, tmpObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkForError() {
|
||||||
|
val errorMsg = getErrorPointer()?.toKString() ?: ""
|
||||||
|
|
||||||
|
if (errorMsg.isNotEmpty()) {
|
||||||
|
println("JSON error before: $errorMsg\n")
|
||||||
|
exitProcess(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CPointer<cJSON>?.intValue() = this?.pointed?.valueint ?: 0
|
||||||
|
|
||||||
|
private fun CPointer<cJSON>?.doubleValue() = this?.pointed?.valuedouble ?: 0.0
|
||||||
|
|
||||||
|
private fun CPointer<cJSON>?.stringValue() = jsonStringValue(this)?.toKString() ?: ""
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package org.example.weather_func
|
||||||
|
|
||||||
|
import platform.posix.*
|
||||||
|
import kotlinx.cinterop.allocArray
|
||||||
|
import kotlinx.cinterop.ByteVar
|
||||||
|
import kotlinx.cinterop.memScoped
|
||||||
|
import kotlinx.cinterop.toKString
|
||||||
|
import kotlinx.cinterop.refTo
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
private val API_KEY by lazy { fetchApiKey() }
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val input = readLine()
|
||||||
|
val location = fetchLocationArg(input)
|
||||||
|
val jsonFile = fetchJsonFileArg(input)
|
||||||
|
|
||||||
|
if (location.isNotEmpty()) {
|
||||||
|
printFromWeatherService(location)
|
||||||
|
} else if (jsonFile.isNotEmpty()) {
|
||||||
|
checkFile(jsonFile)
|
||||||
|
printJsonFile(jsonFile)
|
||||||
|
} else {
|
||||||
|
handleEmptyInput()
|
||||||
|
}
|
||||||
|
println("Exiting...")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleEmptyInput() {
|
||||||
|
println(
|
||||||
|
"""
|
||||||
|
| Weather - A OpenFaaS Serverless Function that outputs weather information.
|
||||||
|
| Usage examples (pass one of the following as input to the function):
|
||||||
|
| * Location (uses the Open Weather Map service), eg:
|
||||||
|
| -l="christchurch,nz"
|
||||||
|
| * JSON File, eg: -f="weather.json"
|
||||||
|
""".trimMargin()
|
||||||
|
)
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkFile(file: String) {
|
||||||
|
val error = -1
|
||||||
|
if (access(file, F_OK) == error) {
|
||||||
|
println("File $file doesn't exist!")
|
||||||
|
exitProcess(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchJson(jsonFile: String): String {
|
||||||
|
var result = ""
|
||||||
|
// Open the file using the fopen function and store the file handle.
|
||||||
|
val file = fopen(jsonFile, "r")
|
||||||
|
fseek(file, 0, SEEK_END)
|
||||||
|
val fileSize = ftell(file)
|
||||||
|
fseek(file, 0, SEEK_SET)
|
||||||
|
|
||||||
|
memScoped {
|
||||||
|
val buffer = allocArray<ByteVar>(fileSize)
|
||||||
|
// Read the entire file and store the contents into the buffer.
|
||||||
|
fread(buffer, fileSize, 1, file)
|
||||||
|
result = buffer.toKString()
|
||||||
|
}
|
||||||
|
// Close the file.
|
||||||
|
fclose(file)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printJsonFile(jsonFile: String) {
|
||||||
|
println("Printing from JSON file ($jsonFile)...")
|
||||||
|
val weather = createWeatherFromJson(fetchJson(jsonFile))
|
||||||
|
println("Weather Object:\n$weather")
|
||||||
|
println("Weather JSON:\n${weatherToJsonString(weather)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printFromWeatherService(location: String) {
|
||||||
|
println("Fetching weather information (for $location)...")
|
||||||
|
val curl = CUrl(createUrl(location)).apply {
|
||||||
|
header += { if(it.startsWith("HTTP")) println("Response Status: $it") }
|
||||||
|
body += { data ->
|
||||||
|
val weather = createWeatherFromJson(data)
|
||||||
|
println("Weather information:\n${weatherToJsonString(weather)}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curl.fetch()
|
||||||
|
curl.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchJsonFileArg(input: String?): String {
|
||||||
|
val flag = "-f"
|
||||||
|
return if (input != null && input.startsWith("$flag=")) input.replace("$flag=", "").replace("\"", "")
|
||||||
|
else ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchLocationArg(input: String?): String {
|
||||||
|
val flag = "-l"
|
||||||
|
return if (input != null && input.startsWith("$flag=")) input.replace("$flag=", "").replace("\"", "")
|
||||||
|
else ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createUrl(location: String): String {
|
||||||
|
val baseUrl = "http://api.openweathermap.org/data/2.5/weather"
|
||||||
|
return "$baseUrl?q=$location&units=metric&appid=$API_KEY"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchApiKey(): String {
|
||||||
|
var result = ""
|
||||||
|
val maxChars = 50
|
||||||
|
// utfCharSize in bytes.
|
||||||
|
val utfCharSize = 4
|
||||||
|
// Open the file using the fopen function and store the file handle.
|
||||||
|
val file = fopen("openweathermap_key.txt", "r")
|
||||||
|
val buffer = ByteArray(utfCharSize * maxChars)
|
||||||
|
if (file != null) result = fgets(buffer.refTo(0), buffer.size, file)?.toKString()?.trim() ?: ""
|
||||||
|
// Close the file.
|
||||||
|
fclose(file)
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Weather Function Sample
|
||||||
|
|
||||||
|
This is a Serverless Function that fetches the current weather information from the Open Weather Map [service](https://openweathermap.org/current) via HTTP. Part of the Function's output includes HTTP response headers, and the HTTP response body. This sample is designed to be deployed on OpenFaaS via a Docker image, in conjunction with Docker Swarm.
|
||||||
|
|
||||||
|
|
||||||
|
# Initial Setup
|
||||||
|
|
||||||
|
If the **~/.konan/cache** directory doesn't exist then you will need to compile a basic Linux amd64 program with konan before proceeding.
|
||||||
|
|
||||||
|
1. Clone the Kotlin Native Git repository: `git clone https://github.com/JetBrains/kotlin-native ~/repos/kotlin-native`
|
||||||
|
2. Change working directory to the sample: `cd ~/repos/kotlin-native/samples/weather_function`
|
||||||
|
3. Copy the Konan cache to the sample: `mkdir -p function/.konan || cp -R ~/.konan/cache function/.konan`
|
||||||
|
4. Copy the Gradle Wrapper to the sample: `cp ../gradle function`
|
||||||
|
5. Copy the **gradlew** file to the sample: `cp ../gradlew function`
|
||||||
|
6. Create the **openweathermap_key.txt** file in the **function** directory: `touch function/openweathermap_key.txt`
|
||||||
|
7. Append your [Open Weather Map API key](https://openweathermap.org/appid) to **function/openweathermap_key.txt**
|
||||||
|
|
||||||
|
|
||||||
|
# Building Docker Image
|
||||||
|
|
||||||
|
It will be assumed that [Docker](https://store.docker.com/search?type=edition&offering=community), and [OpenFaaS](https://docs.openfaas.com/deployment/) is already installed along with the [OpenFaaS CLI](https://github.com/openfaas/faas-cli). Build the *weather* Docker image using the **fass-cli** tool: `faas-cli build -f weather.yml`
|
||||||
|
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
Make sure that you have completed the *Building Docker Image* section before proceeding.
|
||||||
|
|
||||||
|
1. Start OpenFaaS
|
||||||
|
2. Deploy the **weather** image using the **faas-cli** tool: `faas-cli deploy --image weather --name weather`
|
||||||
|
3. Invoke the **weather** function (including passing through the location argument) by running the following: `echo '-l="christchurch,nz"' | faas-cli invoke weather`
|
||||||
|
|
||||||
|
**Note:** The program (weather) can print weather information from a JSON file, eg: `./weather -f="current_weather.json"`. This functionality isn't available in the Serverless Function unless the file is generated in the **~/repos/kotlin-native/samples/weather_function/function** directory, the Dockerfile is updated to include the file in the Docker image, and the image is built (refer to the *Building Docker Image* section) before deploying the image (refer to the *Usage* section).
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
provider:
|
||||||
|
name: faas
|
||||||
|
gateway: http://127.0.0.1:8080
|
||||||
|
|
||||||
|
functions:
|
||||||
|
function:
|
||||||
|
lang: dockerfile
|
||||||
|
handler: function
|
||||||
|
image: weather
|
||||||
Reference in New Issue
Block a user