├── terraform ├── data.tf ├── provider.tf ├── templates │ └── body_mapping.template ├── variables.tf ├── s3.tf ├── terraform.tfvars ├── lambda.tf └── api_gateway.tf ├── swiftcommand ├── .gitignore ├── Resources │ ├── intentSchema.json │ └── SampleUtterances.txt ├── Tests │ ├── LinuxMain.swift │ └── swiftcommandTests │ │ └── swiftcommandTests.swift ├── Package.swift ├── TestData │ ├── expectedOutput.json │ └── testInput.json └── Sources │ ├── main.swift │ ├── ReadTransformPrint.swift │ ├── greeter.swift │ └── AlexaMessages.swift ├── .gitignore ├── notes ├── static-library-analysis │ ├── static-library-analysis-note.txt │ ├── neededLibraries02.txt │ ├── neededLibraries01.txt │ └── neededLibraries03.txt ├── validAlexaRequestResponse.json └── spartashim │ └── index.js ├── shim └── index.js ├── README.md └── Makefile /terraform/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | -------------------------------------------------------------------------------- /swiftcommand/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /terraform/provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "${var.region}" 3 | } 4 | -------------------------------------------------------------------------------- /swiftcommand/Resources/intentSchema.json: -------------------------------------------------------------------------------- 1 | { 2 | "intents": [ 3 | { 4 | "intent": "HelloWorldIntent" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /swiftcommand/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import swiftcommandTests 3 | 4 | XCTMain([ 5 | testCase(greeterTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /swiftcommand/Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "swiftcommand", 5 | dependencies:[ 6 | ], 7 | exclude: ["LinuxLibraries","TestData","Resources"] 8 | ) 9 | 10 | 11 | -------------------------------------------------------------------------------- /swiftcommand/Resources/SampleUtterances.txt: -------------------------------------------------------------------------------- 1 | HelloWorldIntent say hello 2 | HelloWorldIntent say hello world 3 | HelloWorldIntent hello 4 | HelloWorldIntent say hi 5 | HelloWorldIntent say hi world 6 | HelloWorldIntent hi 7 | HelloWorldIntent how are you 8 | -------------------------------------------------------------------------------- /swiftcommand/TestData/expectedOutput.json: -------------------------------------------------------------------------------- 1 | // known-good expected output as of 2016-10-20T1215 2 | { 3 | "version": "1.0", 4 | "response": { 5 | "outputSpeech": { 6 | "type": "PlainText", 7 | "text": "Hello from Swift" 8 | }, 9 | "shouldEndSession": true 10 | }, 11 | "sessionAttributes": {} 12 | } 13 | -------------------------------------------------------------------------------- /terraform/templates/body_mapping.template: -------------------------------------------------------------------------------- 1 | { 2 | "body" : $input.json('$'), 3 | "headers": { 4 | #foreach($param in $input.params().header.keySet()) 5 | "$param": "$util.escapeJavaScript($input.params().header.get($param))" #if($foreach.hasNext),#end 6 | 7 | #end 8 | }, 9 | "stage" : "$context.stage" 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # build products for the deployment package 4 | /build 5 | 6 | # a Swift Package Manger directory, plus a LinuxLibraries 7 | /swiftcommand/LinuxLibraries/ 8 | /swiftcommand/*.xcodeproj 9 | /swiftcommand/Packages 10 | /swiftcommand/.build 11 | 12 | # Terraform state 13 | /**/*.tfstate 14 | /**/*.tfstate.backup 15 | -------------------------------------------------------------------------------- /terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = "string" 3 | description = "AWS region e.g eu-west-1" 4 | } 5 | 6 | variable "environment" { 7 | type = "string" 8 | default = "Dev" 9 | } 10 | 11 | variable "s3_bucket" { 12 | type = "string" 13 | } 14 | 15 | # path to the lambda function deployment package zip file 16 | variable "lambdazip" { 17 | type = "string" 18 | } 19 | 20 | variable "lambda_function_name" { 21 | type = "string" 22 | } 23 | -------------------------------------------------------------------------------- /terraform/s3.tf: -------------------------------------------------------------------------------- 1 | resource "aws_s3_bucket" "s3Bucket" { 2 | bucket = "${var.s3_bucket}" 3 | acl = "private" 4 | 5 | tags { 6 | Name = "${var.s3_bucket}" 7 | Environment = "${var.environment}" 8 | } 9 | } 10 | 11 | resource "aws_s3_bucket_object" "lambdaPackage" { 12 | bucket = "${aws_s3_bucket.s3Bucket.bucket}" 13 | key = "new_object_key" 14 | source = "${path.module}/${var.lambdazip}" 15 | etag = "${md5(file("${path.module}/${var.lambdazip}"))}" 16 | } 17 | -------------------------------------------------------------------------------- /notes/static-library-analysis/static-library-analysis-note.txt: -------------------------------------------------------------------------------- 1 | I've found more libraries via the following method: 2 | 3 | run ldd swiftcommand in Linux to find all referenced libraries 4 | run swiftcommand, having coded it to import Foundation 5 | see errors about missing libraries 6 | add the missing libraries one by one until that error disappears. 7 | I first tried adding all missing libraries but when you do that it segfaults. I don't know why. 8 | 9 | I'm worried we're playing fast & loose with the linker here, since I presume it is not safe to assume that static libraries from ibmcom/kitura-swift are compatible with those from ubuntu:latest. Or am I wrong about that? 10 | -------------------------------------------------------------------------------- /terraform/terraform.tfvars: -------------------------------------------------------------------------------- 1 | # 2 | # MUST be updated for use 3 | # 4 | 5 | # update the below to a DNS-prefixed bucket name to ensure global uniqueness 6 | s3_bucket = "com.alexisgallagher.swiftlambda2" 7 | 8 | # 9 | # MAY be adjusted independently 10 | # 11 | 12 | # (you must use us-east-1 if you're developing an Alexa Custom Skill.) 13 | region = "us-east-1" 14 | 15 | environment = "Dev" 16 | lambda_function_name = "lambdaTester" 17 | 18 | # 19 | # MUST be adjusted in tandem with the Makefile 20 | # 21 | 22 | # the builddir must be equal to BUILDDIR in the Makefile 23 | # the filename must be aligned with LAMBDA_DEPLOYMENT_PACKAGE_NAME in Makefile 24 | lambdazip = "../build/lambda_deployment_package.zip" 25 | 26 | -------------------------------------------------------------------------------- /swiftcommand/TestData/testInput.json: -------------------------------------------------------------------------------- 1 | // known-good test input as 2016-10-20T1215 2 | { 3 | "session": { 4 | "sessionId": "SessionId.87f621dd-3232-428c-8d35-f27dd3500985", 5 | "application": { 6 | "applicationId": "amzn1.ask.skill.221dba25-e4b4-40bc-952c-5b25ec81bd4d" 7 | }, 8 | "attributes": {}, 9 | "user": { 10 | "userId": "amzn1.ask.account.AECQY5WZK7XRIQQEEBOAWBPVDR7663OASF5BYYCXLLMZHSNDYHPJJZWVFVS7U5CGSCPOFANKW73MGG2ORDVCN37IUMYKBH4I4US4ZBGL7KK3PNCQEZYAAJHWSVW3E44ZQT4OLODNBVJ24QMS3G5GANPUDFVHCAM4WLOAS5UXZQIVZ4FZE5Q43PHMVR3QWWNTQTHOC6E76WZCSTY" 11 | }, 12 | "new": true 13 | }, 14 | "request": { 15 | "type": "IntentRequest", 16 | "requestId": "EdwRequestId.b3f41621-13f6-4714-826b-0d64db9c9d26", 17 | "locale": "en-US", 18 | "timestamp": "2016-10-20T19:15:06Z", 19 | "intent": { 20 | "name": "HelloWorldIntent", 21 | "slots": {} 22 | } 23 | }, 24 | "version": "1.0" 25 | } 26 | -------------------------------------------------------------------------------- /swiftcommand/Sources/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // print("Hello, world, from Swift! Echoing stdin. CTRL-D to stop") 4 | 5 | /* 6 | This function just cleanly echoes its input. 7 | */ 8 | 9 | fileprivate 10 | func echo(string:String) -> String { 11 | return string 12 | } 13 | 14 | /* 15 | 16 | `Greeter.service` implements a simple Alexa Custom Skill that says 17 | "Hello from Swift" 18 | 19 | */ 20 | let g = Greeter() 21 | func greetResponse(string:String) -> String { 22 | return g.service(string:string) 23 | } 24 | 25 | /* 26 | 27 | USER: if you want to define your own Lambda function in swift, just 28 | define a function `f:(String)->String` and pass it in as the argument 29 | to `readTransformPrint`. 30 | 31 | Be sure your function `f` expects a String containing JSON and returns 32 | a String containing JSON. 33 | 34 | */ 35 | 36 | // echo: reads a string and returns it exactly 37 | readTransformPrint(transform:echo) 38 | 39 | // ALEXA: reads an Alexa Request envelope and returns a response 40 | //readTransformPrint(transform:greetResponse) 41 | -------------------------------------------------------------------------------- /terraform/lambda.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role_policy" "logs_policy" { 2 | name = "logs_policy" 3 | role = "${aws_iam_role.iam_for_lambda.name}" 4 | policy = <) -> AnyIterator 20 | { 21 | return AnyIterator { () -> String? in 22 | var line:UnsafeMutablePointer? = nil 23 | var linecap:Int = 0 24 | defer { free(line) } 25 | let ret = getline(&line, &linecap, file) 26 | 27 | if ret > 0 { 28 | guard let line = line else { return nil } 29 | return String(validatingUTF8: line) 30 | } 31 | else { 32 | return nil 33 | } 34 | } 35 | } 36 | 37 | 38 | /** 39 | Reads all of stdin in a String buffer. Transforms the string. Prints result to stdout. 40 | 41 | */ 42 | func readTransformPrint(transform:(String)->String) 43 | { 44 | var input:String = "" 45 | for line in lineGenerator(file: stdin) { 46 | input += line 47 | } 48 | let result = transform(input) 49 | 50 | print(result, separator: "", terminator: "") 51 | } 52 | -------------------------------------------------------------------------------- /shim/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env['PATH'] = process.env['PATH'] + ':' + process.env['LAMBDA_TASK_ROOT'] 4 | 5 | const spawnSync = require('child_process').spawnSync; 6 | 7 | /** 8 | Defines a handler function called "index.handler" to Lambda. 9 | 10 | This function just transparently wraps the `swiftcommand` executable. 11 | 12 | It takes a JSON object `event` as input, serializes it to a 13 | string, invokes the `swiftcommand` executable, passing the string 14 | into stdin and reads the stdout, deserializes the stdout into 15 | JSON, and then calls back with the reponse JSON object. 16 | 17 | If the executable errors, the handler calls back with the error 18 | object intead. 19 | 20 | In all cases, the executable's stdout is logged to the console as 21 | a log message, and its stderr is logged as error messages. 22 | 23 | */ 24 | exports.handler = (event, context, callback) => { 25 | 26 | const options = { 27 | cwd: 'native/', 28 | env: { 29 | LD_LIBRARY_PATH: 'LinuxLibraries' 30 | }, 31 | input:JSON.stringify(event) 32 | }; 33 | 34 | 35 | var command = ''; 36 | 37 | if (event.command) 38 | { 39 | command = event.command; 40 | } else { 41 | command = './swiftcommand'; 42 | } 43 | 44 | const childObject = spawnSync(command, [], options) 45 | 46 | var error = childObject.error; 47 | var stdout = childObject.stdout.toString('utf8'); 48 | var stderr = childObject.stderr.toString('utf8'); 49 | 50 | // Log process stdout and stderr 51 | console.log(stdout); 52 | console.error(stderr); 53 | 54 | if (error) { 55 | callback(error,null); 56 | } 57 | else { 58 | // executable's raw stdout is the Lambda output 59 | var response = JSON.parse(stdout); 60 | callback(null,response); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /notes/validAlexaRequestResponse.json: -------------------------------------------------------------------------------- 1 | /// launch request session 2 | 3 | { 4 | "session": { 5 | "new": true, 6 | "sessionId": "session1234", 7 | "attributes": {}, 8 | "user": { 9 | "userId": null 10 | }, 11 | "application": { 12 | "applicationId": "amzn1.ask.skill.221dba25-e4b4-40bc-952c-5b25ec81bd4d" 13 | } 14 | }, 15 | "version": "1.0", 16 | "request": { 17 | "type": "LaunchRequest", 18 | "requestId": "request5678" 19 | } 20 | } 21 | 22 | /// response 23 | 24 | { 25 | "version": "1.0", 26 | "response": { 27 | "outputSpeech": { 28 | "type": "PlainText", 29 | "text": "Hello World!" 30 | }, 31 | "shouldEndSession": true 32 | }, 33 | "sessionAttributes": {} 34 | } 35 | 36 | 37 | //// 38 | //// intent request 39 | /// 40 | 41 | { 42 | "session": { 43 | "sessionId": "SessionId.94e9d2b9-4385-44b9-876d-32ad8166bd17", 44 | "application": { 45 | "applicationId": "amzn1.ask.skill.f41b7f6b-3eaf-4c69-9fb2-9862833d644f" 46 | }, 47 | "attributes": {}, 48 | "user": { 49 | "userId": "amzn1.ask.account.AHMKJEVGCTMRKKSXRU6YZJV7362ELC2CKJ7ERNUHSHNNBOVEW6RYIZNCBWYX2XIPLLCCEVMKHOD2GTVJPDFK5NPM3N6PSC2MXFK6MFIO2IMEUVPTXWZ2ZGKNIDJW72PQUPAYH7KV4HOCAW7CIXWYBS7WYUR6MKZHLGE2RXZHCOZHOJLDMD7PTBSEFHC7QCTMFKQRTAAH3GRY4PA" 50 | }, 51 | "new": true 52 | }, 53 | "request": { 54 | "type": "IntentRequest", 55 | "requestId": "EdwRequestId.11528d24-9be4-4f9b-ac7d-f516a492fc3c", 56 | "locale": "en-US", 57 | "timestamp": "2016-10-20T03:10:32Z", 58 | "intent": { 59 | "name": "HelloWorldIntent", 60 | "slots": {} 61 | } 62 | }, 63 | "version": "1.0" 64 | } 65 | 66 | // intent response 67 | 68 | { 69 | "version": "1.0", 70 | "response": { 71 | "outputSpeech": { 72 | "type": "PlainText", 73 | "text": "Hello World!" 74 | }, 75 | "shouldEndSession": true 76 | }, 77 | "sessionAttributes": {} 78 | } 79 | -------------------------------------------------------------------------------- /notes/static-library-analysis/neededLibraries02.txt: -------------------------------------------------------------------------------- 1 | # needed by Swift stdlib, and present on ubuntu 2 | /lib64/ld-linux-x86-64.so.2 (0x00005605ab87d000) 3 | 4 | # used by Swift stdlib 5 | /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libFoundation.so 6 | /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libdispatch.so 7 | /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libswiftCore.so 8 | /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libswiftGlibc.so 9 | /usr/lib/x86_64-linux-gnu/libicudata.so.52 10 | /usr/lib/x86_64-linux-gnu/libicui18n.so.52 11 | /usr/lib/x86_64-linux-gnu/libicuuc.so.52 12 | 13 | # used by Foundation 14 | /lib/x86_64-linux-gnu/libbsd.so.0 15 | /lib/x86_64-linux-gnu/libc.so.6 16 | /lib/x86_64-linux-gnu/libcom_err.so.2 17 | /lib/x86_64-linux-gnu/libcrypt.so.1 18 | /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 19 | /lib/x86_64-linux-gnu/libdl.so.2 20 | /lib/x86_64-linux-gnu/libgcc_s.so.1 21 | /lib/x86_64-linux-gnu/libgcrypt.so.11 22 | /lib/x86_64-linux-gnu/libgpg-error.so.0 23 | /lib/x86_64-linux-gnu/libkeyutils.so.1 24 | /lib/x86_64-linux-gnu/liblzma.so.5 25 | /lib/x86_64-linux-gnu/libm.so.6 26 | /lib/x86_64-linux-gnu/libpthread.so.0 27 | /lib/x86_64-linux-gnu/libresolv.so.2 28 | /lib/x86_64-linux-gnu/librt.so.1 29 | /lib/x86_64-linux-gnu/libssl.so.1.0.0 30 | /lib/x86_64-linux-gnu/libutil.so.1 31 | /lib/x86_64-linux-gnu/libz.so.1 32 | /usr/lib/x86_64-linux-gnu/libasn1.so.8 33 | /usr/lib/x86_64-linux-gnu/libcurl.so.4 34 | /usr/lib/x86_64-linux-gnu/libffi.so.6 35 | /usr/lib/x86_64-linux-gnu/libgnutls.so.26 36 | /usr/lib/x86_64-linux-gnu/libgssapi.so.3 37 | /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2 38 | /usr/lib/x86_64-linux-gnu/libhcrypto.so.4 39 | /usr/lib/x86_64-linux-gnu/libheimbase.so.1 40 | /usr/lib/x86_64-linux-gnu/libheimntlm.so.0 41 | /usr/lib/x86_64-linux-gnu/libhx509.so.5 42 | /usr/lib/x86_64-linux-gnu/libidn.so.11 43 | /usr/lib/x86_64-linux-gnu/libk5crypto.so.3 44 | /usr/lib/x86_64-linux-gnu/libkrb5.so.26 45 | /usr/lib/x86_64-linux-gnu/libkrb5.so.3 46 | /usr/lib/x86_64-linux-gnu/libkrb5support.so.0 47 | /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2 48 | /usr/lib/x86_64-linux-gnu/libldap_r-2.4.so.2 49 | /usr/lib/x86_64-linux-gnu/libp11-kit.so.0 50 | /usr/lib/x86_64-linux-gnu/libroken.so.18 51 | /usr/lib/x86_64-linux-gnu/librtmp.so.0 52 | /usr/lib/x86_64-linux-gnu/libsasl2.so.2 53 | /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 54 | /usr/lib/x86_64-linux-gnu/libstdc++.so.6 55 | /usr/lib/x86_64-linux-gnu/libtasn1.so.6 56 | /usr/lib/x86_64-linux-gnu/libwind.so.0 57 | /usr/lib/x86_64-linux-gnu/libxml2.so.2 58 | -------------------------------------------------------------------------------- /swiftcommand/Tests/swiftcommandTests/swiftcommandTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | import XCTest 4 | @testable import swiftcommand 5 | 6 | class greeterTests: XCTestCase 7 | { 8 | func testExample() 9 | { 10 | // This is an example of a functional test case. 11 | // Use XCTAssert and related functions to verify your tests produce the correct results. 12 | let g = Greeter() 13 | 14 | /* 15 | { 16 | "session": { 17 | "sessionId": "SessionId.0acdeb7e-1d2e-41a9-91a0-abd690624c94", 18 | "application": { 19 | "applicationId": "amzn1.ask.skill.221dba25-e4b4-40bc-952c-5b25ec81bd4d" 20 | }, 21 | "attributes": {}, 22 | "user": { 23 | "userId": "amzn1.ask.account.AECQY5WZK7XRIQQEEBOAWBPVDR7663OASF5BYYCXLLMZHSNDYHPJJZWVFVS7U5CGSCPOFANKW73MGG2ORDVCN37IUMYKBH4I4US4ZBGL7KK3PNCQEZYAAJHWSVW3E44ZQT4OLODNBVJ24QMS3G5GANPUDFVHCAM4WLOAS5UXZQIVZ4FZE5Q43PHMVR3QWWNTQTHOC6E76WZCSTY" 24 | }, 25 | "new": true 26 | }, 27 | "request": { 28 | "type": "IntentRequest", 29 | "requestId": "EdwRequestId.fad8cbfd-a9f2-4645-97dc-58986a127813", 30 | "locale": "en-US", 31 | "timestamp": "2016-10-19T00:40:06Z", 32 | "intent": { 33 | "name": "HelloWorldIntent", 34 | "slots": {} 35 | } 36 | }, 37 | "version": "1.0" 38 | } 39 | */ 40 | 41 | let intentRequest:String = "{\n \"session\": {\n \"sessionId\": \"SessionId.0acdeb7e-1d2e-41a9-91a0-abd690624c94\",\n \"application\": {\n \"applicationId\": \"amzn1.ask.skill.221dba25-e4b4-40bc-952c-5b25ec81bd4d\"\n },\n \"attributes\": {},\n \"user\": {\n \"userId\": \"amzn1.ask.account.AECQY5WZK7XRIQQEEBOAWBPVDR7663OASF5BYYCXLLMZHSNDYHPJJZWVFVS7U5CGSCPOFANKW73MGG2ORDVCN37IUMYKBH4I4US4ZBGL7KK3PNCQEZYAAJHWSVW3E44ZQT4OLODNBVJ24QMS3G5GANPUDFVHCAM4WLOAS5UXZQIVZ4FZE5Q43PHMVR3QWWNTQTHOC6E76WZCSTY\"\n },\n \"new\": true\n },\n \"request\": {\n \"type\": \"IntentRequest\",\n \"requestId\": \"EdwRequestId.fad8cbfd-a9f2-4645-97dc-58986a127813\",\n \"locale\": \"en-US\",\n \"timestamp\": \"2016-10-19T00:40:06Z\",\n \"intent\": {\n \"name\": \"HelloWorldIntent\",\n \"slots\": {}\n }\n },\n \"version\": \"1.0\"\n}\n" 42 | 43 | 44 | // check result is at least JSON 45 | let resultString = g.service(string: intentRequest) 46 | let resultJSON = try? JSONSerialization.jsonObject(with: resultString.data(using: String.Encoding.utf8)!, 47 | options: []) 48 | XCTAssert(resultJSON != nil) 49 | 50 | } 51 | 52 | 53 | static var allTests : [(String, (greeterTests) -> () throws -> Void)] { 54 | return [ 55 | ("testExample", testExample), 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /notes/static-library-analysis/neededLibraries01.txt: -------------------------------------------------------------------------------- 1 | $ ldd swiftcommand # => 2 | 3 | linux-vdso.so.1 => (0x00007ffd973db000) 4 | libswiftCore.so => /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libswiftCore.so (0x00007f7fb1790000) 5 | libFoundation.so => /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libFoundation.so (0x00007f7fb0cd0000) 6 | libswiftGlibc.so => /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libswiftGlibc.so (0x00007f7fb0ccb000) 7 | libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f7fb0aa5000) 8 | libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007f7fb08a1000) 9 | libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f7fb069d000) 10 | libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f7fb0399000) 11 | libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f7fb0092000) 12 | libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f7fafe7c000) 13 | libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7fafab7000) 14 | libicuuc.so.52 => /usr/lib/x86_64-linux-gnu/libicuuc.so.52 (0x00007f7faf73d000) 15 | libicui18n.so.52 => /usr/lib/x86_64-linux-gnu/libicui18n.so.52 (0x00007f7faf336000) 16 | /lib64/ld-linux-x86-64.so.2 (0x00005605ab87d000) 17 | libicudata.so.52 => /usr/lib/x86_64-linux-gnu/libicudata.so.52 (0x00007f7fadac9000) 18 | libxml2.so.2 => /usr/lib/x86_64-linux-gnu/libxml2.so.2 (0x00007f7fad761000) 19 | libcurl.so.4 => /usr/lib/x86_64-linux-gnu/libcurl.so.4 (0x00007f7fad4fa000) 20 | libdispatch.so => /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libdispatch.so (0x00007f7fad444000) 21 | libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f7fad22a000) 22 | liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007f7fad008000) 23 | libidn.so.11 => /usr/lib/x86_64-linux-gnu/libidn.so.11 (0x00007f7facdd4000) 24 | librtmp.so.0 => /usr/lib/x86_64-linux-gnu/librtmp.so.0 (0x00007f7facbba000) 25 | libssl.so.1.0.0 => /lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007f7fac95b000) 26 | libcrypto.so.1.0.0 => /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 (0x00007f7fac57e000) 27 | libgssapi_krb5.so.2 => /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2 (0x00007f7fac337000) 28 | liblber-2.4.so.2 => /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2 (0x00007f7fac128000) 29 | libldap_r-2.4.so.2 => /usr/lib/x86_64-linux-gnu/libldap_r-2.4.so.2 (0x00007f7fabed6000) 30 | librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f7fabcce000) 31 | libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007f7fababf000) 32 | libgnutls.so.26 => /usr/lib/x86_64-linux-gnu/libgnutls.so.26 (0x00007f7fab800000) 33 | libgcrypt.so.11 => /lib/x86_64-linux-gnu/libgcrypt.so.11 (0x00007f7fab580000) 34 | libkrb5.so.3 => /usr/lib/x86_64-linux-gnu/libkrb5.so.3 (0x00007f7fab2b4000) 35 | libk5crypto.so.3 => /usr/lib/x86_64-linux-gnu/libk5crypto.so.3 (0x00007f7fab085000) 36 | libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 (0x00007f7faae81000) 37 | libkrb5support.so.0 => /usr/lib/x86_64-linux-gnu/libkrb5support.so.0 (0x00007f7faac75000) 38 | libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007f7faaa5a000) 39 | libsasl2.so.2 => /usr/lib/x86_64-linux-gnu/libsasl2.so.2 (0x00007f7faa83f000) 40 | libgssapi.so.3 => /usr/lib/x86_64-linux-gnu/libgssapi.so.3 (0x00007f7faa600000) 41 | libtasn1.so.6 => /usr/lib/x86_64-linux-gnu/libtasn1.so.6 (0x00007f7faa3ec000) 42 | libp11-kit.so.0 => /usr/lib/x86_64-linux-gnu/libp11-kit.so.0 (0x00007f7faa1aa000) 43 | libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0 (0x00007f7fa9fa4000) 44 | libkeyutils.so.1 => /lib/x86_64-linux-gnu/libkeyutils.so.1 (0x00007f7fa9da0000) 45 | libheimntlm.so.0 => /usr/lib/x86_64-linux-gnu/libheimntlm.so.0 (0x00007f7fa9b96000) 46 | libkrb5.so.26 => /usr/lib/x86_64-linux-gnu/libkrb5.so.26 (0x00007f7fa990e000) 47 | libasn1.so.8 => /usr/lib/x86_64-linux-gnu/libasn1.so.8 (0x00007f7fa966d000) 48 | libhcrypto.so.4 => /usr/lib/x86_64-linux-gnu/libhcrypto.so.4 (0x00007f7fa9439000) 49 | libroken.so.18 => /usr/lib/x86_64-linux-gnu/libroken.so.18 (0x00007f7fa9224000) 50 | libffi.so.6 => /usr/lib/x86_64-linux-gnu/libffi.so.6 (0x00007f7fa901c000) 51 | libwind.so.0 => /usr/lib/x86_64-linux-gnu/libwind.so.0 (0x00007f7fa8df2000) 52 | libheimbase.so.1 => /usr/lib/x86_64-linux-gnu/libheimbase.so.1 (0x00007f7fa8be4000) 53 | libhx509.so.5 => /usr/lib/x86_64-linux-gnu/libhx509.so.5 (0x00007f7fa899b000) 54 | libsqlite3.so.0 => /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 (0x00007f7fa86e1000) 55 | libcrypt.so.1 => /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007f7fa84a8000) 56 | -------------------------------------------------------------------------------- /terraform/api_gateway.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "iam_for_api_gateway" { 2 | name = "iam_for_api_gateway" 3 | assume_role_policy = < Void 20 | 21 | let SPEECH_OUTPUT = "Hello World!" 22 | 23 | 24 | func helloResponseFunction(intent:AlexaIntent,session:AlexaSession,response:AlexaResponse) { 25 | response.tell(SPEECH_OUTPUT) 26 | } 27 | 28 | class GreeterService : AlexaSkill { 29 | 30 | // TODO: use APP_ID on initialization 31 | 32 | func onLaunch(intent:AlexaIntent,session:AlexaSession,response:AlexaResponse) { 33 | helloResponseFunction(intent:intent,session:session,response:response) 34 | } 35 | 36 | /// maps intent names to intent handlers 37 | var intentHandlers:[String:AlexaIntentHandler] { 38 | return ["HelloWorldIntent":helloResponseFunction] 39 | } 40 | } 41 | 42 | // 43 | // MARK: AlexaSkills API 44 | // 45 | 46 | protocol AlexaSkill { 47 | func onLaunch(intent:AlexaIntent,session:AlexaSession,response:AlexaResponse) 48 | var intentHandlers:[String:AlexaIntentHandler] 49 | } 50 | 51 | 52 | typealias AmazonEvent = [String:Any] 53 | typealias AmazonContext = [String:Any] 54 | 55 | func serviceHandler(event:AmazonEvent,context:AmazonContext) { 56 | let greeterService = GreeterService() 57 | greeterService.execute(event:event,context:context) 58 | } 59 | 60 | */ 61 | 62 | /** 63 | Parses the top-level JSON value passed in as part of the 64 | Alexa JSON interface 65 | 66 | */ 67 | func serviceJson(jsonInput:JSONDictionary) -> AlexaResponseEnvelope? 68 | { 69 | // parse the service request 70 | guard let serviceRequest = AlexaRequestEnvelope(JSON:jsonInput) else 71 | { 72 | print("ERROR: unable to parse alexa request message") 73 | 74 | return nil 75 | } 76 | 77 | // parse the embedded Alexa request 78 | let alexaRequestUnparsed = serviceRequest.request 79 | 80 | if let _ = LaunchRequest(JSON: alexaRequestUnparsed) { 81 | // print("found launch request") 82 | } 83 | else if let _ = IntentRequest(JSON:alexaRequestUnparsed) { 84 | // print("found intentRequest") 85 | } 86 | else { 87 | print("did not find launch request or intent request") 88 | let msg:String = "ERROR: did not detect a launch request" 89 | print(msg) 90 | return nil 91 | } 92 | 93 | // LATER: here we would process details in a launch request or intent request object 94 | 95 | // generate output speech 96 | let outputSpeech = AlexaOutputSpeech(text: "Hello from Swift") 97 | // build an AlexaResponse 98 | let alexaResponse:AlexaResponse = AlexaResponse(outputSpeech: outputSpeech, card: nil, reprompt: nil, directives: nil, shouldEndSession: true) 99 | 100 | // build a service response 101 | let serviceResponse:AlexaResponseEnvelope = AlexaResponseEnvelope(version: "1.0", sessionAttributes: nil, response: alexaResponse) 102 | 103 | return serviceResponse 104 | } 105 | 106 | public class Greeter 107 | { 108 | public init() { } 109 | 110 | /** 111 | String->String wrapper for the JSON->JSON service above. 112 | 113 | */ 114 | public func service(string s:String) -> String { 115 | guard let data = s.data(using: String.Encoding.utf8) else { return "error serialized string to data" } 116 | guard let jsonInput = (try? JSONSerialization.jsonObject(with: data, options: [])) as? JSONDictionary else { 117 | return "error parsing JSON from string" 118 | } 119 | 120 | guard let response:AlexaResponseEnvelope = serviceJson(jsonInput:jsonInput) 121 | else { 122 | let msg = "did not generate response object" 123 | print(msg) 124 | return msg 125 | } 126 | 127 | // print(" will try to encode outputJSON=\(response.asJSON)") 128 | let JSONString = response.asJSONString 129 | // print(" self-encoded to JSON=\(JSONString)") 130 | return JSONString 131 | } 132 | } 133 | /* 134 | Questions: 135 | 136 | - does AMZ check timestamp security before passing to lambda handlers? or is that the rsponsibility of my lambda func when it processes the request? Is this checked for certification? 137 | 138 | - AMZ published any schema of all these types? Protocol buffers schema? 139 | 140 | - AMZ published nginx config for auth anywhere? 141 | 142 | 143 | */ 144 | 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift On Lambda 2 | 3 | ## Update in June 2020 4 | 5 | This repo is now mostsly irrelevant. Four years later, Apple now supports this directly: https://developer.apple.com/videos/play/wwdc2020/10644/ 6 | 7 | ## What's here 8 | 9 | This repo contains an example of two things: using Swift to define an Amazon Lambda function, and in particular using it to define an Amazon Lambda function which implements a simple "Hello world" Alexa Custom Skill. (This was work on the way to building an Swift chatbot, as [described in ths Swift Summit talk](https://www.skilled.io/u/swiftsummit/talking-to-swift).) 10 | 11 | Amazon Lambda only officially supports NodeJS, Java, and Python but it also supports including arbitrary Linux executables. So the trick to using Swift on Lambda is just to compile your Swift function to a Linux executable and then build a Lambda function which runs a shim in a supported language that transparently wraps that executable. (There's actually a mature version of this for the Go language, the [Sparta](http://gosparta.io) project.) To do all this, this repo uses [Docker](https://www.docker.com) to help with building the Linux executable and grabbing needed libraries, and it uses [terraform](https://www.docker.com) to help with deploying to Lambda. 12 | 13 | Right now, by default, the repo will deploy a simple Lambda function that just performs an echo command, returning as output whatever was the input. If you want to define your own Lambda function, have a look at `main.swift` and just change the argument from `echo` to a function of your choice. 14 | 15 | This repo also contains code to define a simple "Hello from Swift" Alexa Custom Skill. (Why use Lambda for this? Because although you can host an Alexa Custom Skill on an ordinary web server, the HTTPS authentication requirements are quite messy, and you get those for free with Lambda.) If you want to experiment with this, then go to `main.swift` and use `greetResponse` instead of `echo`. You will also need to manually configure the skill's intent schema and sample utterances on the Alexa developer website, since Amazon does not provide an API for automated deployment yet. You can find a sample intent schema and sample utterances in the `Resources/` directory. 16 | 17 | The best place to look for a clear explanation of developing for Alexa, by the way, is the Big Nerd Ranch [videos](https://www.youtube.com/watch?v=QxgdPI1B7rg) and [sample code](https://github.com/bignerdranch/developing-alexa-skills-solutions) that Amazon commissioned. 18 | 19 | ## How to build, provision, and run a Lambda function 20 | 21 | Prerequisites: 22 | 23 | - if you want, update `main.swift` so it calls your own function which takes a (JSON) `String` to a (JSON) `String`. (By default, it will just echo.) 24 | 25 | - install docker and start the docker daemon. 26 | 27 | This is used to compile for Linux and grab Linux libraries 28 | 29 | - install terraform 30 | 31 | This is used to drive AWS. 32 | 33 | - install AWS credentials in ~/.aws/credentials or in environmental variables, for terraform to use 34 | 35 | - modify `terraform.tfvars` so that `s3_bucket` is a unique name you own, such as "com.yourdomainnamehere.swiftlambda" 36 | 37 | - build with `make build` 38 | 39 | - provision with `make provision` 40 | 41 | - to run the function, use Amazon's web GUI to go find the function and test it. 42 | 43 | ### Provisioning 44 | 45 | Swift Lambda uses terraform to provision your lambda function. You can install terrafrom either using brew or get the official package from here: [https://www.terraform.io/downloads.html](https://www.terraform.io/downloads.html). 46 | 47 | The terraform plan configures the following: 48 | 49 | - the Lambda function itself (a set of configurations on AWS) 50 | 51 | - an S3 bucket, holding the deployment package defining the lambda function's contents (this is just a zip file) 52 | 53 | - an IAM role, defining permissions which your Lamba function has while executing 54 | 55 | - an API gateway configuration, so AWS presents an HTTPS interface to the function, in case you want that (tho it is not needed for Alexa) 56 | 57 | The file `/terraform/terraform.tfvars` contains the default configuerations such as the lambda function name and the S3 bucket name. 58 | 59 | To provision your lambda, ensure you have your AWS credentials either in the ~/.aws/credentials file or you have the environment variables set. 60 | 61 | Run: 62 | ``` 63 | make provision 64 | ``` 65 | 66 | #### what this Makefile does 67 | 68 | An Amazon Lambda function is defined by AWS configurations and by a _deployment package_. The Makefile builds that package. 69 | 70 | The package must use one of the supported languages, Java, NodeJS, or Python. We use JS and that JS file is contained in `shim/`. A package may also contain a native binary executable, and this is where we drop in our compiled Swift code. 71 | 72 | One catch is that the binary must be compiled on an appropriate Linux distribution. The Makefile's `build_swift` target uses docker to do this. In case you are new to the exciting world of docker, here's a breakdown of key points in part of the build process: 73 | 74 | The first command is roughly like this: 75 | 76 | ```sh 77 | $ docker run # create a container and run it 78 | --rm # rm the container when it exits 79 | --volume "$(shell pwd)/src:/src" # bind the local host directory src/ to in-the-container /src 80 | --workdir /src # set /src as the working directory in the container 81 | ibmcom/kitura-ubuntu # use IBM's Kitura docker image to define the container 82 | swift build -v # in the container, run only the command "swift build -v" 83 | ``` 84 | 85 | In other words, this command runs a virtualized ubuntu containing IBM's Swift Kitura web framework, and uses the copy of the swift compiler within that container to compile the Swift package located in the container's /src directory. Because the container's /src is mapped to the host's src/, compiling this package in the container _also places the build outputs in the host's src/ directory_. 86 | 87 | The next docker commands look like this (with command line arguments in short form): 88 | 89 | ```sh 90 | $ docker run --rm -v "$(shell pwd)/src:/src" -w /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/*.so /src/libs' 91 | $ docker run --rm -v "$(shell pwd)/src:/src" -w /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libicudata.so.52 /src/libs' 92 | $ ... 93 | 94 | ``` 95 | 96 | These essentially copy files from inside the IBM Kitura Swift docker container into the container's /src directory, which is bound to the host's file system. In other words, we are essentially just extracting useful Linux static libraries from inside the container. (Thanks, IBM!) 97 | 98 | Finally, the run command is a bit different: 99 | 100 | ```sh 101 | $ docker run --interactive # let allow stdin/out into the container 102 | --tty # "allocate a pseudo-TTY" 103 | --rm # rm the container when it exits 104 | --volume "$(shell pwd):/src" # map our src/ to its /src 105 | --workdir /src # set its workdir as its /src 106 | ubuntu # build container from the image "ubuntu" 107 | /bin/bash -c 'LD_LIBRARY_PATH=/src/src/libs /src/src/.build/debug/src' # set env var and run executable src 108 | ``` 109 | 110 | This runs a container based on the plain `ubuntu` image, mapping to the host src/ directory with the extracted static libraries, and then runs a single shell command in the container which sets an environmental variable pointing the linker to those libraries and executes the executable `src`. Why are we running this based on a plain ubuntu image? Because we want to verify that our executable and libraries will run fine on Amazon's Linux, which is closer to plain ubuntu than to IBM's Kitura container. Even better would be to use here a docker image based on Amazon's Linux AMIs. 111 | 112 | It's worth emphasizing that while some of these docker command build a container from the same image, none of these commands operate on the same container since the container is removed each time a command exits. Despite this, we are still accumulating a directory of all the outputs because every container's /src directory is mapped to our host's src/ directory, and that directory's contents persist across the different containers' lifetimes like the host itself. 113 | 114 | Does it seem like docker is a handy tool to support cross-platform Swift development? I [sure think so](https://github.com/algal/swiftecho). 115 | -------------------------------------------------------------------------------- /notes/static-library-analysis/neededLibraries03.txt: -------------------------------------------------------------------------------- 1 | # seemingly needed for Swift 2 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libFoundation.so /src/LinuxLibraries' 3 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libdispatch.so /src/LinuxLibraries' 4 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libswiftCore.so /src/LinuxLibraries' 5 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libswiftGlibc.so /src/LinuxLibraries' 6 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libicudata.so.52 /src/LinuxLibraries' 7 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libicui18n.so.52 /src/LinuxLibraries' 8 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libicuuc.so.52 /src/LinuxLibraries' 9 | # seemingly needed for Foundation 10 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libbsd.so.0 /src/LinuxLibraries' 11 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libc.so.6 /src/LinuxLibraries' 12 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libcom_err.so.2 /src/LinuxLibraries' 13 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libcrypt.so.1 /src/LinuxLibraries' 14 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 /src/LinuxLibraries' 15 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libdl.so.2 /src/LinuxLibraries' 16 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libgcc_s.so.1 /src/LinuxLibraries' 17 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libgcrypt.so.11 /src/LinuxLibraries' 18 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libgpg-error.so.0 /src/LinuxLibraries' 19 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libkeyutils.so.1 /src/LinuxLibraries' 20 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/liblzma.so.5 /src/LinuxLibraries' 21 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libm.so.6 /src/LinuxLibraries' 22 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libpthread.so.0 /src/LinuxLibraries' 23 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libresolv.so.2 /src/LinuxLibraries' 24 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/librt.so.1 /src/LinuxLibraries' 25 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libssl.so.1.0.0 /src/LinuxLibraries' 26 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libutil.so.1 /src/LinuxLibraries' 27 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libz.so.1 /src/LinuxLibraries' 28 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libasn1.so.8 /src/LinuxLibraries' 29 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libcurl.so.4 /src/LinuxLibraries' 30 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libffi.so.6 /src/LinuxLibraries' 31 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libgnutls.so.26 /src/LinuxLibraries' 32 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libgssapi.so.3 /src/LinuxLibraries' 33 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2 /src/LinuxLibraries' 34 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libhcrypto.so.4 /src/LinuxLibraries' 35 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libheimbase.so.1 /src/LinuxLibraries' 36 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libheimntlm.so.0 /src/LinuxLibraries' 37 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libhx509.so.5 /src/LinuxLibraries' 38 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libidn.so.11 /src/LinuxLibraries' 39 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libk5crypto.so.3 /src/LinuxLibraries' 40 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libkrb5.so.26 /src/LinuxLibraries' 41 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libkrb5.so.3 /src/LinuxLibraries' 42 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libkrb5support.so.0 /src/LinuxLibraries' 43 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2 /src/LinuxLibraries' 44 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libldap_r-2.4.so.2 /src/LinuxLibraries' 45 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libp11-kit.so.0 /src/LinuxLibraries' 46 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libroken.so.18 /src/LinuxLibraries' 47 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/librtmp.so.0 /src/LinuxLibraries' 48 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libsasl2.so.2 /src/LinuxLibraries' 49 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 /src/LinuxLibraries' 50 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libstdc++.so.6 /src/LinuxLibraries' 51 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libtasn1.so.6 /src/LinuxLibraries' 52 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libwind.so.0 /src/LinuxLibraries' 53 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libxml2.so.2 /src/LinuxLibraries' 54 | -------------------------------------------------------------------------------- /notes/spartashim/index.js: -------------------------------------------------------------------------------- 1 | var util = require('util') 2 | var fs = require('fs') 3 | var http = require('http') 4 | var path = require('path') 5 | var os = require('os') 6 | var process = require('process') 7 | var childProcess = require('child_process') 8 | var spartaUtils = require('./sparta_utils') 9 | var AWS = require('aws-sdk') 10 | var awsConfig = new AWS.Config({}) 11 | var GOLANG_CONSTANTS = require('./golang-constants.json') 12 | 13 | // TODO: See if https://forums.aws.amazon.com/message.jspa?messageID=633802 14 | // has been updated with new information 15 | process.env.PATH = process.env.PATH + ':/var/task' 16 | 17 | // These two names will be dynamically reassigned during archive creation 18 | var SPARTA_BINARY_NAME = 'Sparta.lambda.amd64' 19 | var SPARTA_SERVICE_NAME = 'SpartaService' 20 | // End dynamic reassignment 21 | 22 | // This is where the binary will be extracted 23 | var SPARTA_BINARY_PATH = path.join('/tmp', SPARTA_BINARY_NAME) 24 | var MAXIMUM_RESPAWN_COUNT = 5 25 | 26 | // Handle to the active golang process. 27 | var golangProcess = null 28 | var failCount = 0 29 | 30 | function makeRequest (path, startRemainingCountMillis, event, context, lambdaCallback) { 31 | // http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html 32 | context.callbackWaitsForEmptyEventLoop = false 33 | 34 | // Let's track the request lifecycle 35 | var requestTime = process.hrtime() 36 | var lambdaBodyLength = 0 37 | var socketDuration = null 38 | var writeCompleteDuration = null 39 | var responseEndDuration = null 40 | 41 | var requestBody = { 42 | event: event, 43 | context: context 44 | } 45 | // If there is a request.event.body element, try and parse it to make 46 | // interacting with API Gateway a bit simpler. The .body property 47 | // corresponds to the data shape set by the *.vtl templates 48 | if (requestBody && requestBody.event && requestBody.event.body) { 49 | try { 50 | requestBody.event.body = JSON.parse(requestBody.event.body) 51 | } catch (e) {} 52 | } 53 | var stringified = JSON.stringify(requestBody) 54 | var contentLength = Buffer.byteLength(stringified, 'utf-8') 55 | var options = { 56 | host: 'localhost', 57 | port: 9999, 58 | path: path, 59 | method: 'POST', 60 | headers: { 61 | 'Content-Type': 'application/json', 62 | 'Content-Length': contentLength 63 | } 64 | } 65 | 66 | var onProxyComplete = function (err, response) { 67 | try { 68 | responseEndDuration = process.hrtime(requestTime) 69 | postRequestMetrics(path, 70 | startRemainingCountMillis, 71 | socketDuration, 72 | lambdaBodyLength, 73 | writeCompleteDuration, 74 | responseEndDuration) 75 | 76 | context.done(err, response) 77 | } catch (e) { 78 | context.done(e, null) 79 | } 80 | } 81 | 82 | var req = http.request(options, function (res) { 83 | res.setEncoding('utf8') 84 | var body = '' 85 | res.on('data', function (chunk) { 86 | body += chunk 87 | }) 88 | res.on('end', function () { 89 | // Bridge the NodeJS and golang worlds by including the golang 90 | // HTTP status text in the error response if appropriate. This enables 91 | // the API Gateway integration response to use standard golang StatusText regexp 92 | // matches to manage HTTP status codes. 93 | var responseData = {} 94 | var handlerError = (res.statusCode >= 400) ? new Error(body) : undefined 95 | if (handlerError) { 96 | responseData.code = res.statusCode 97 | responseData.status = GOLANG_CONSTANTS.HTTP_STATUS_TEXT[res.statusCode.toString()] 98 | responseData.headers = res.headers 99 | responseData.error = handlerError.toString() 100 | } else { 101 | responseData = body 102 | lambdaBodyLength = Buffer.byteLength(responseData, 'utf8') 103 | if (res.headers['content-type'] === 'application/json') { 104 | try { 105 | responseData = JSON.parse(body) 106 | } catch (e) {} 107 | } 108 | } 109 | var err = handlerError ? new Error(JSON.stringify(responseData)) : null 110 | var resp = handlerError ? null : responseData 111 | onProxyComplete(err, resp) 112 | }) 113 | }) 114 | req.once('socket', function (res) { 115 | socketDuration = process.hrtime(requestTime) 116 | }) 117 | req.once('finish', function () { 118 | writeCompleteDuration = process.hrtime(requestTime) 119 | }) 120 | req.once('error', function (e) { 121 | onProxyComplete(e, null) 122 | }) 123 | req.write(stringified) 124 | req.end() 125 | } 126 | 127 | // Move the file to /tmp to temporarily work around 128 | // https://forums.aws.amazon.com/message.jspa?messageID=583910 129 | var ensureGoLangBinary = function (callback) { 130 | try { 131 | fs.statSync(SPARTA_BINARY_PATH) 132 | setImmediate(callback, null) 133 | } catch (e) { 134 | var command = util.format('cp ./%s %s; chmod +x %s', 135 | SPARTA_BINARY_NAME, 136 | SPARTA_BINARY_PATH, 137 | SPARTA_BINARY_PATH) 138 | childProcess.exec(command, function (err, stdout) { 139 | if (err) { 140 | console.error(err) 141 | process.exit(1) 142 | } else { 143 | var stdoutMsg = stdout.toString('utf-8') 144 | if (stdoutMsg.length !== 0) { 145 | spartaUtils.log(stdoutMsg) 146 | } 147 | // Post the 148 | } 149 | callback(err, stdout) 150 | }) 151 | } 152 | } 153 | 154 | var createForwarder = function (path) { 155 | var forwardToGolangProcess = function (event, context, callback, metricName, startRemainingCountMillisParam) { 156 | var startRemainingCountMillis = startRemainingCountMillisParam || context.getRemainingTimeInMillis() 157 | if (!golangProcess) { 158 | ensureGoLangBinary(function () { 159 | spartaUtils.log(util.format('Launching %s with args: execute --signal %d', SPARTA_BINARY_PATH, process.pid)) 160 | golangProcess = childProcess.spawn(SPARTA_BINARY_PATH, ['execute', '--signal', process.pid], {}) 161 | 162 | golangProcess.stdout.on('data', function (buf) { 163 | buf.toString('utf-8').split('\n').forEach(function (eachLine) { 164 | spartaUtils.log(eachLine) 165 | }) 166 | }) 167 | golangProcess.stderr.on('data', function (buf) { 168 | buf.toString('utf-8').split('\n').forEach(function (eachLine) { 169 | spartaUtils.log(eachLine) 170 | }) 171 | }) 172 | 173 | var terminationHandler = function (eventName) { 174 | return function (value) { 175 | var onPosted = function () { 176 | console.error(util.format('Sparta %s: %s\n', eventName.toUpperCase(), JSON.stringify(value))) 177 | failCount += 1 178 | if (failCount > MAXIMUM_RESPAWN_COUNT) { 179 | process.exit(1) 180 | } 181 | golangProcess = null 182 | forwardToGolangProcess(null, 183 | null, 184 | callback, 185 | METRIC_NAMES.TERMINATED, 186 | startRemainingCountMillis) 187 | } 188 | postMetricCounter(METRIC_NAMES.TERMINATED, onPosted) 189 | } 190 | } 191 | golangProcess.on('error', terminationHandler('error')) 192 | golangProcess.on('exit', terminationHandler('exit')) 193 | process.on('exit', function () { 194 | spartaUtils.log('Go process exited') 195 | if (golangProcess) { 196 | golangProcess.kill() 197 | } 198 | }) 199 | var golangProcessReadyHandler = function () { 200 | spartaUtils.log('SIGUSR2 signal received') 201 | process.removeListener('SIGUSR2', golangProcessReadyHandler) 202 | forwardToGolangProcess(event, 203 | context, 204 | callback, 205 | METRIC_NAMES.CREATED, 206 | startRemainingCountMillis) 207 | } 208 | spartaUtils.log('Waiting for SIGUSR2 signal') 209 | process.on('SIGUSR2', golangProcessReadyHandler) 210 | }) 211 | } 212 | else if (event && context) { 213 | postMetricCounter(metricName || METRIC_NAMES.REUSED) 214 | makeRequest(path, startRemainingCountMillis, event, context, callback) 215 | } 216 | } 217 | return forwardToGolangProcess 218 | } 219 | 220 | // Log the outputs 221 | var envSettings = { 222 | aws_sdk: AWS.VERSION, 223 | node_js: process.version, 224 | os: { 225 | platform: os.platform(), 226 | release: os.release(), 227 | type: os.type(), 228 | uptime: os.uptime(), 229 | cpus: os.cpus(), 230 | totalmem: os.totalmem() 231 | } 232 | } 233 | spartaUtils.log(envSettings) 234 | 235 | exports.main = createForwarder('/') 236 | 237 | // Additional golang handlers to be dynamically appended below 238 | 239 | // DO NOT EDIT - CONTENT UNTIL EOF IS AUTOMATICALLY GENERATED 240 | exports["main_helloWorld"] = createForwarder("/main.helloWorld"); 241 | exports["Custom_goAWS_SESLambdaEventSourceResource"] = createForwarder("/Custom::goAWS::SESLambdaEventSourceResource"); 242 | exports["Custom_goAWS_S3LambdaEventSourceResource"] = createForwarder("/Custom::goAWS::S3LambdaEventSourceResource"); 243 | exports["Custom_goAWS_SNSLambdaEventSourceResource"] = createForwarder("/Custom::goAWS::SNSLambdaEventSourceResource"); 244 | exports["Custom_goAWS_CloudWatchLogsLambdaEventSourceResource"] = createForwarder("/Custom::goAWS::CloudWatchLogsLambdaEventSourceResource"); 245 | exports["Custom_goAWS_ZipToS3BucketResource"] = createForwarder("/Custom::goAWS::ZipToS3BucketResource"); 246 | 247 | SPARTA_BINARY_NAME='MyHelloWorldStack.lambda.amd64'; 248 | SPARTA_SERVICE_NAME='MyHelloWorldStack'; 249 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # this variable must be aligned with lambdazip in terraform.tfvars 3 | BUILDDIR = build 4 | 5 | # this variable must be aligned with lambdazip in terraform.tfvars 6 | LAMBDA_DEPLOYMENT_PACKAGE_NAME = lambda_deployment_package.zip 7 | 8 | build: build_lambda_package 9 | echo "Everything built" 10 | 11 | clean: clean_lambda_package clean_swift 12 | echo "Everything cleaned" 13 | 14 | build_lambda_package: build_swift gather_dependencies 15 | echo "Assembling the Lambda function deployment package" 16 | mkdir -p $(BUILDDIR)/native 17 | cp shim/index.js $(BUILDDIR)/ 18 | cp swiftcommand/.build/release/swiftcommand $(BUILDDIR)/native 19 | cp -r swiftcommand/LinuxLibraries $(BUILDDIR)/native 20 | cd $(BUILDDIR) && zip -r $(LAMBDA_DEPLOYMENT_PACKAGE_NAME) * 21 | 22 | clean_lambda_package: 23 | echo "Cleaning deployment package" 24 | rm -r $(BUILDDIR) $(LAMBDA_DEPLOYMENT_PACKAGE_NAME) || true 25 | 26 | build_swift: 27 | echo "Building swiftcommand Linux executable" 28 | docker run \ 29 | --rm \ 30 | --volume "$(shell pwd)/swiftcommand:/src" \ 31 | --workdir /src \ 32 | ibmcom/kitura-ubuntu \ 33 | swift build -c release -v 34 | 35 | gather_dependencies: 36 | echo "Copying executable's Linux dependencies from Kitura-Swift" 37 | mkdir -p swiftcommand/LinuxLibraries 38 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/*.so /usr/lib/x86_64-linux-gnu/libicudata.so.52 /usr/lib/x86_64-linux-gnu/libicui18n.so.52 /usr/lib/x86_64-linux-gnu/libicuuc.so.52 /usr/lib/x86_64-linux-gnu/libbsd.so /usr/lib/x86_64-linux-gnu/libxml2.so.2 /usr/lib/x86_64-linux-gnu/libxml2.so.2.9.1 /usr/lib/x86_64-linux-gnu/libcurl.so.4 /usr/lib/x86_64-linux-gnu/libidn.so.11 /usr/lib/x86_64-linux-gnu/librtmp.so.0 /lib/x86_64-linux-gnu/libssl.so.1.0.0 /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2 /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2 /usr/lib/x86_64-linux-gnu/libldap_r-2.4.so.2 /lib/x86_64-linux-gnu/libbsd.so.0 /usr/lib/x86_64-linux-gnu/libgnutls.so.26 /lib/x86_64-linux-gnu/libgcrypt.so.11 /usr/lib/x86_64-linux-gnu/libkrb5.so.3 /usr/lib/x86_64-linux-gnu/libk5crypto.so.3 /usr/lib/x86_64-linux-gnu/libkrb5support.so.0 /usr/lib/x86_64-linux-gnu/libsasl2.so.2 /usr/lib/x86_64-linux-gnu/libgssapi.so.3 /usr/lib/x86_64-linux-gnu/libtasn1.so.6 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0 /lib/x86_64-linux-gnu/libkeyutils.so.1 /usr/lib/x86_64-linux-gnu/libheimntlm.so.0 /usr/lib/x86_64-linux-gnu/libkrb5.so.26 /src/LinuxLibraries' 39 | # needed for Swift's stdlib 40 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libFoundation.so /src/LinuxLibraries' 41 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libdispatch.so /src/LinuxLibraries' 42 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libswiftCore.so /src/LinuxLibraries' 43 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /root/swift-3.0-RELEASE-ubuntu14.04/usr/lib/swift/linux/libswiftGlibc.so /src/LinuxLibraries' 44 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libicudata.so.52 /src/LinuxLibraries' 45 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libicui18n.so.52 /src/LinuxLibraries' 46 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libicuuc.so.52 /src/LinuxLibraries' 47 | # needed for Foundation (excluding ones that empirically are not needed and/or cause a seg fault) 48 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libbsd.so.0 /src/LinuxLibraries' 49 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libc.so.6 /src/LinuxLibraries' 50 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libcom_err.so.2 /src/LinuxLibraries' 51 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libcrypt.so.1 /src/LinuxLibraries' 52 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 /src/LinuxLibraries' 53 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libdl.so.2 /src/LinuxLibraries' 54 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libgcc_s.so.1 /src/LinuxLibraries' 55 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libgcrypt.so.11 /src/LinuxLibraries' 56 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libgpg-error.so.0 /src/LinuxLibraries' 57 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libkeyutils.so.1 /src/LinuxLibraries' 58 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/liblzma.so.5 /src/LinuxLibraries' 59 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libm.so.6 /src/LinuxLibraries' 60 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libpthread.so.0 /src/LinuxLibraries' 61 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libresolv.so.2 /src/LinuxLibraries' 62 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/librt.so.1 /src/LinuxLibraries' 63 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libssl.so.1.0.0 /src/LinuxLibraries' 64 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libutil.so.1 /src/LinuxLibraries' 65 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /lib/x86_64-linux-gnu/libz.so.1 /src/LinuxLibraries' 66 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libasn1.so.8 /src/LinuxLibraries' 67 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libcurl.so.4 /src/LinuxLibraries' 68 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libffi.so.6 /src/LinuxLibraries' 69 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libgnutls.so.26 /src/LinuxLibraries' 70 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libgssapi.so.3 /src/LinuxLibraries' 71 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2 /src/LinuxLibraries' 72 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libhcrypto.so.4 /src/LinuxLibraries' 73 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libheimbase.so.1 /src/LinuxLibraries' 74 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libheimntlm.so.0 /src/LinuxLibraries' 75 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libhx509.so.5 /src/LinuxLibraries' 76 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libidn.so.11 /src/LinuxLibraries' 77 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libk5crypto.so.3 /src/LinuxLibraries' 78 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libkrb5.so.26 /src/LinuxLibraries' 79 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libkrb5.so.3 /src/LinuxLibraries' 80 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libkrb5support.so.0 /src/LinuxLibraries' 81 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2 /src/LinuxLibraries' 82 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libldap_r-2.4.so.2 /src/LinuxLibraries' 83 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libp11-kit.so.0 /src/LinuxLibraries' 84 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libroken.so.18 /src/LinuxLibraries' 85 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/librtmp.so.0 /src/LinuxLibraries' 86 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libsasl2.so.2 /src/LinuxLibraries' 87 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 /src/LinuxLibraries' 88 | # docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libstdc++.so.6 /src/LinuxLibraries' 89 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libtasn1.so.6 /src/LinuxLibraries' 90 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libwind.so.0 /src/LinuxLibraries' 91 | docker run --rm --volume "$(shell pwd)/swiftcommand:/src" --workdir /src ibmcom/kitura-ubuntu /bin/bash -c 'cp /usr/lib/x86_64-linux-gnu/libxml2.so.2 /src/LinuxLibraries' 92 | 93 | clean_swift: 94 | echo "Cleaning Swift build products" 95 | docker run \ 96 | --rm \ 97 | --volume "$(shell pwd)/swiftcommand:/src" \ 98 | --workdir /src \ 99 | ibmcom/kitura-ubuntu \ 100 | swift build --clean 101 | echo "Cleaning Swift Linux dependencies" 102 | rm swiftcommand/LinuxLibraries/* || true 103 | 104 | run: 105 | echo "Running executable on Linux" 106 | docker run -it --rm -v "$(shell pwd)/swiftcommand:/src" -w /src ubuntu /bin/bash -c 'LD_LIBRARY_PATH=/src/LinuxLibraries /src/.build/release/swiftcommand' 107 | 108 | run_test: 109 | echo "Running executable on Linux" 110 | docker run --rm -v "$(shell pwd)/swiftcommand:/src" -w /src ubuntu /bin/bash -c 'LD_LIBRARY_PATH=/src/LinuxLibraries /src/.build/release/swiftcommand < /src/TestData/testInput.json' 111 | 112 | test: 113 | echo "Running tests on Linux" 114 | docker run \ 115 | --rm \ 116 | --volume "$(shell pwd)/swiftcommand:/src" \ 117 | --workdir /src \ 118 | ibmcom/kitura-ubuntu \ 119 | swift build 120 | docker run --rm -v "$(shell pwd)/swiftcommand:/src" -w /src ibmcom/kitura-ubuntu swift test 121 | 122 | provision: 123 | cd terraform && terraform apply 124 | 125 | destroy: 126 | cd terraform && terraform destroy 127 | -------------------------------------------------------------------------------- /swiftcommand/Sources/AlexaMessages.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlexaMessages.swift 3 | // Listener 4 | // 5 | // Created by Alexis Gallagher on 2016-10-10. 6 | // Copyright © 2016 Bloom Filter. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | 13 | 14 | 15 | At present, this is a mechanical translation of the 16 | types implicit in the Amazon docs for the JSON API, as 17 | described here: 18 | 19 | https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference 20 | 21 | This does not, for example, cleverly represent implicit 22 | variant types as enums with associated values, etc.. 23 | 24 | The JSON parsing is an enormous pile of uninteresting boilerplate. 25 | 26 | Maybe explore generating it with protocol buffers or something? 27 | 28 | */ 29 | 30 | 31 | /* 32 | ALEXA REQUEST DOCS: 33 | 34 | HTTP Header 35 | 36 | POST / HTTP/1.1 37 | Content-Type : application/json;charset=UTF-8 38 | Host : your.application.endpoint 39 | Content-Length : 40 | Accept : application/json 41 | Accept-Charset : utf-8 42 | Signature: 43 | SignatureCertChainUrl: https://s3.amazonaws.com/echo.api/echo-api-cert.pem 44 | Request Body Syntax 45 | 46 | 47 | { 48 | "version": "string", 49 | "session": { 50 | "new": true, 51 | "sessionId": "string", 52 | "application": { 53 | "applicationId": "string" 54 | }, 55 | "attributes": { 56 | "string": {} 57 | }, 58 | "user": { 59 | "userId": "string", 60 | "accessToken": "string" 61 | } 62 | }, 63 | "context": { 64 | "System": { 65 | "application": { 66 | "applicationId": "string" 67 | }, 68 | "user": { 69 | "userId": "string", 70 | "accessToken": "string" 71 | }, 72 | "device": { 73 | "supportedInterfaces": { 74 | "AudioPlayer": {} 75 | } 76 | } 77 | }, 78 | "AudioPlayer": { 79 | "token": "string", 80 | "offsetInMilliseconds": 0, 81 | "playerActivity": "string" 82 | } 83 | }, 84 | "request": {} 85 | } 86 | */ 87 | 88 | /// a frequently needed alias 89 | typealias JSONDictionary = [String:Any] 90 | 91 | struct AlexaRequestEnvelope { 92 | var version:String 93 | var session:AlexaSession? 94 | var context:AlexaContext? 95 | var request:AlexaRequest 96 | } 97 | 98 | extension AlexaRequestEnvelope 99 | { 100 | init?(JSON JSONValue:Any) { 101 | guard 102 | let v = JSONValue as? JSONDictionary, 103 | let theVersion = v["version"] as? String, 104 | let theRequest = v["request"] as? AlexaRequest 105 | else { return nil } 106 | 107 | version = theVersion 108 | request = theRequest 109 | 110 | if let theSessionDict = v["session"] as? JSONDictionary, 111 | let theSession = AlexaSession(JSON:theSessionDict) { 112 | session = theSession 113 | } 114 | else { 115 | session = nil 116 | } 117 | 118 | if let theContextDict = v["context"] as? JSONDictionary, 119 | let theContext = AlexaContext(JSON:theContextDict) { 120 | context = theContext 121 | } 122 | else { 123 | context = nil 124 | } 125 | 126 | 127 | } 128 | } 129 | 130 | /** 131 | Must be LaunchRequest, Intentrequest, or SessionEndRequest 132 | */ 133 | typealias AlexaRequest = JSONDictionary 134 | 135 | struct LaunchRequest 136 | { 137 | var type:String 138 | var requestId:String 139 | var timestamp:String? 140 | var locale:String? 141 | } 142 | 143 | extension LaunchRequest { 144 | init?(JSON JSONValue:JSONDictionary) { 145 | guard 146 | let theType = JSONValue["type"] as? String, 147 | let theRequestId = JSONValue["requestId"] as? String, 148 | theType == "LaunchRequest" 149 | else { return nil } 150 | 151 | if let theTimestamp = JSONValue["timestamp"] as? String { 152 | timestamp = theTimestamp 153 | } 154 | else { 155 | timestamp = nil 156 | } 157 | 158 | if let theLocale = JSONValue["locale"] as? String { 159 | locale = theLocale 160 | } else { 161 | locale = nil 162 | } 163 | 164 | type = theType 165 | requestId = theRequestId 166 | } 167 | } 168 | 169 | /* 170 | { 171 | "type": "SessionEndedRequest", 172 | "requestId": "string", 173 | "timestamp": "string", 174 | "reason": "string", 175 | "locale": "string", 176 | "error": { 177 | "type": "string", 178 | "message": "string" 179 | } 180 | } 181 | */ 182 | 183 | struct SessionEndRequest 184 | { 185 | var type:String 186 | var requestId:String 187 | var reason:String 188 | var timestamp:String? 189 | var locale:String? 190 | var error:JSONDictionary? 191 | } 192 | 193 | extension SessionEndRequest { 194 | init?(JSON JSONValue:JSONDictionary) { 195 | guard 196 | let theType = JSONValue["type"] as? String, 197 | let theRequestId = JSONValue["requestId"] as? String, 198 | let theReason = JSONValue["reason"] as? String, 199 | theType == "SessionEndRequest" 200 | else { return nil } 201 | 202 | if let theTimestamp = JSONValue["timestamp"] as? String { 203 | timestamp = theTimestamp 204 | } 205 | else { 206 | timestamp = nil 207 | } 208 | 209 | if let theLocale = JSONValue["locale"] as? String { 210 | locale = theLocale 211 | } else { 212 | locale = nil 213 | } 214 | 215 | if let theError = JSONValue["error"] as? JSONDictionary { 216 | error = theError 217 | } else { 218 | error = nil 219 | } 220 | 221 | type = theType 222 | requestId = theRequestId 223 | reason = theReason 224 | } 225 | } 226 | 227 | 228 | // TODO: implement AlexaIntent 229 | typealias AlexaIntent = JSONDictionary 230 | struct IntentRequest 231 | { 232 | var type:String 233 | var requestId:String 234 | var timestamp:String? 235 | var locale:String? 236 | var intent:AlexaIntent 237 | } 238 | 239 | extension IntentRequest { 240 | init?(JSON JSONValue:JSONDictionary) { 241 | guard let theType = JSONValue["type"] as? String, 242 | let theRequestId = JSONValue["requestId"] as? String, 243 | let theIntent = JSONValue["intent"] as? JSONDictionary, 244 | theType == "IntentRequest" 245 | else { return nil } 246 | 247 | 248 | if let theTimestamp = JSONValue["timestamp"] as? String { 249 | timestamp = theTimestamp 250 | } else { 251 | timestamp = nil 252 | } 253 | if let theLocale = JSONValue["locale"] as? String { 254 | locale = theLocale 255 | } else { 256 | locale = nil 257 | } 258 | 259 | type = theType 260 | requestId = theRequestId 261 | intent = theIntent 262 | } 263 | } 264 | 265 | 266 | /* 267 | 268 | // actual sample request: 269 | 270 | // example 1: 271 | { 272 | "session": { 273 | "new": true, 274 | "sessionId": "session1234", 275 | "attributes": {}, 276 | "user": { 277 | "userId": null 278 | }, 279 | "application": { 280 | "applicationId": "amzn1.ask.skill.221dba25-e4b4-40bc-952c-5b25ec81bd4d" 281 | } 282 | }, 283 | "version": "1.0", 284 | "request": { 285 | "type": "LaunchRequest", 286 | "requestId": "request5678" 287 | } 288 | } 289 | 290 | // example 2: 291 | 292 | */ 293 | struct AlexaSession { 294 | var new:Bool 295 | var sessionId:String 296 | var application:AlexaApplication 297 | var attributes:AlexaSessionAttributes 298 | var user:AlexaUser 299 | } 300 | 301 | extension AlexaSession 302 | { 303 | init?(JSON JSONValue:Any) { 304 | guard 305 | let v = JSONValue as? JSONDictionary, 306 | let theNew = v["new"] as? Bool, 307 | let theSessionId = v["sessionId"] as? String, 308 | let theApplicationDict = v["application"] as? [String:String], 309 | let theApplication = AlexaApplication(JSON: theApplicationDict), 310 | let theAttributes = v["attributes"] as? [String:Any], 311 | let theUserDict = v["user"] as? JSONDictionary, 312 | let theUser = AlexaUser(JSON:theUserDict) 313 | else { return nil } 314 | new = theNew 315 | sessionId = theSessionId 316 | application = theApplication 317 | attributes = theAttributes 318 | user = theUser 319 | } 320 | } 321 | 322 | /* 323 | { 324 | "applicationId": "string" 325 | } 326 | */ 327 | struct AlexaApplication { 328 | var applicationId:String 329 | } 330 | 331 | extension AlexaApplication { 332 | init?(JSON JSONValue:JSONDictionary) { 333 | guard 334 | let d = JSONValue as? [String:String], 335 | let s = d["applicationId"] 336 | else { return nil } 337 | applicationId = s 338 | } 339 | } 340 | 341 | typealias AlexaSessionAttributes = [String:Any] 342 | 343 | /* 344 | { 345 | "userId": "string", 346 | "accessToken": "string" 347 | } 348 | */ 349 | struct AlexaUser { 350 | var userId:String 351 | var accessToken:String 352 | } 353 | 354 | extension AlexaUser { 355 | init?(JSON JSONValue:Any) { 356 | guard 357 | let v = JSONValue as? JSONDictionary, 358 | let theUserId = v["userId"] as? String, 359 | let theAccessToken = v["accessToken"] as? String 360 | else { return nil } 361 | userId = theUserId 362 | accessToken = theAccessToken 363 | } 364 | } 365 | 366 | /* 367 | { 368 | "System": { 369 | "application": { 370 | "applicationId": "string" 371 | }, 372 | "user": { 373 | "userId": "string", 374 | "accessToken": "string" 375 | }, 376 | "device": { 377 | "supportedInterfaces": { 378 | "AudioPlayer": {} 379 | } 380 | } 381 | }, 382 | "AudioPlayer": { 383 | "token": "string", 384 | "offsetInMilliseconds": 0, 385 | "playerActivity": "string" 386 | } 387 | } 388 | */ 389 | 390 | struct AlexaContext { 391 | var System:AlexaSystem 392 | var AudioPlayer:AlexaAudioPlayer 393 | } 394 | 395 | extension AlexaContext { 396 | init?(JSON JSONValue:Any) { 397 | guard 398 | let v = JSONValue as? JSONDictionary, 399 | let theSystemDict = v["System"] as? JSONDictionary, 400 | let theSystem = AlexaSystem(JSON:theSystemDict), 401 | let theAudioPlayerDict = v["AudioPlayer"] as? JSONDictionary, 402 | let theAudioPlayer = AlexaAudioPlayer(JSON:theAudioPlayerDict) 403 | else { return nil } 404 | 405 | System = theSystem 406 | AudioPlayer = theAudioPlayer 407 | } 408 | } 409 | 410 | /* 411 | { 412 | "application": { 413 | "applicationId": "string" 414 | }, 415 | "user": { 416 | "userId": "string", 417 | "accessToken": "string" 418 | }, 419 | "device": { 420 | "supportedInterfaces": { 421 | "AudioPlayer": {} 422 | } 423 | } 424 | } 425 | */ 426 | struct AlexaSystem { 427 | var application:AlexaApplication 428 | var user:AlexaUser 429 | var device:AlexaDevice 430 | } 431 | 432 | extension AlexaSystem { 433 | init?(JSON JSONValue:Any) { 434 | guard 435 | let v = JSONValue as? JSONDictionary, 436 | let theApplicationD = v["application"] as? JSONDictionary, 437 | let theApplication = AlexaApplication(JSON:theApplicationD), 438 | let theUserD = v["user"] as? JSONDictionary, 439 | let theUser = AlexaUser(JSON:theUserD), 440 | let theDeviceD = v["device"] as? JSONDictionary, 441 | let theDevice = AlexaDevice(JSON:theDeviceD) 442 | else { return nil } 443 | application = theApplication 444 | user = theUser 445 | device = theDevice 446 | } 447 | } 448 | 449 | /* 450 | { 451 | "supportedInterfaces": { 452 | "AudioPlayer": {} 453 | } 454 | } 455 | */ 456 | struct AlexaDevice { 457 | var supportedInterfaces:[String:AnyObject] 458 | } 459 | 460 | extension AlexaDevice { 461 | init?(JSON JSONValue:Any) { 462 | guard 463 | let v = JSONValue as? [String:AnyObject] 464 | else { return nil } 465 | supportedInterfaces = v 466 | } 467 | } 468 | 469 | /* 470 | "AudioPlayer": { 471 | "token": "string", 472 | "offsetInMilliseconds": 0, 473 | "playerActivity": "string" 474 | } 475 | */ 476 | struct AlexaAudioPlayer { 477 | var token:String 478 | var offsetInMilliseconds:Double 479 | var playerActivity:String 480 | } 481 | 482 | extension AlexaAudioPlayer { 483 | init?(JSON JSONValue:Any) { 484 | guard 485 | let d = JSONValue as? JSONDictionary, 486 | let theToken = d["token"] as? String, 487 | let theOffsetInMillseconds = d["offsetInMilliseconds"] as? Double, 488 | let thePlayerActivity = d["playerActivity"] as? String 489 | else { return nil } 490 | token = theToken 491 | offsetInMilliseconds = theOffsetInMillseconds 492 | playerActivity = thePlayerActivity 493 | } 494 | } 495 | 496 | // MARK: - response 497 | 498 | /* 499 | 500 | { 501 | "version": "string", 502 | "sessionAttributes": { 503 | "string": object 504 | }, 505 | "response": { 506 | "outputSpeech": { 507 | "type": "string", 508 | "text": "string", 509 | "ssml": "string" 510 | }, 511 | "card": { 512 | "type": "string", 513 | "title": "string", 514 | "content": "string", 515 | "text": "string", 516 | "image": { 517 | "smallImageUrl": "string", 518 | "largeImageUrl": "string" 519 | } 520 | }, 521 | "reprompt": { 522 | "outputSpeech": { 523 | "type": "string", 524 | "text": "string", 525 | "ssml": "string" 526 | } 527 | }, 528 | "directives": [ 529 | { 530 | "type": "string", 531 | "playBehavior": "string", 532 | "audioItem": { 533 | "stream": { 534 | "token": "string", 535 | "url": "string", 536 | "offsetInMilliseconds": 0 537 | } 538 | } 539 | } 540 | ], 541 | "shouldEndSession": boolean 542 | } 543 | } 544 | 545 | */ 546 | 547 | struct AlexaResponseEnvelope { 548 | var version:String 549 | var sessionAttributes:AlexaSessionAttributes? 550 | var response:AlexaResponse 551 | } 552 | 553 | extension AlexaResponseEnvelope { 554 | var asJSON:JSONDictionary { 555 | let emptyDictionary:[String:String] = [:] 556 | var d:JSONDictionary = [:] 557 | d["version"] = self.version 558 | d["sessionAttributes"] = self.sessionAttributes ?? emptyDictionary 559 | d["response"] = self.response.asJSON 560 | return d 561 | } 562 | 563 | /// hack to workaround JSONSerialization bug with Bools: https://bugs.swift.org/browse/SR-3013 564 | var asJSONString:String 565 | { 566 | var kvStrings:[String] = [] 567 | 568 | kvStrings.append( "\"version\" : \"\(self.version)\"" ) 569 | 570 | if 571 | let speechJSON = self.sessionAttributes, 572 | let speechData = try? JSONSerialization.data(withJSONObject: speechJSON, options: []), 573 | let speechString = String(data:speechData,encoding:.utf8) { 574 | 575 | let key = "sessionAttributes" 576 | kvStrings.append( " \"\(key)\" : \(speechString) " ) 577 | } 578 | else { 579 | let key = "sessionAttributes" 580 | kvStrings.append( "\"\(key)\" : {}" ) 581 | } 582 | 583 | let responseKey = "response" 584 | let responseValueString = self.response.asJSONString 585 | kvStrings.append( "\"\(responseKey)\" : \(responseValueString)" ) 586 | 587 | let s = "{\n" + kvStrings.joined(separator: ",\n") + "\n}" 588 | 589 | return s 590 | } 591 | } 592 | 593 | struct AlexaResponse 594 | { 595 | var outputSpeech:AlexaOutputSpeech? 596 | var card:AlexaCard? 597 | var reprompt:AlexaReprompt? 598 | var directives:[AlexaDirective]? 599 | var shouldEndSession:Bool? 600 | } 601 | extension AlexaResponse 602 | { 603 | var asJSON:JSONDictionary { 604 | var d:JSONDictionary = [:] 605 | d["outputSpeech"] = self.outputSpeech?.asJSON 606 | d["card"] = self.card?.asJSON 607 | d["reprompt"] = self.reprompt?.asJSON 608 | 609 | if let directives = self.directives 610 | { 611 | d["directives"] = directives.map({ 612 | (directive:AlexaDirective) -> JSONDictionary in 613 | return directive.asJSON 614 | }) 615 | } 616 | else { 617 | d["directives"] = nil 618 | } 619 | 620 | if let shouldEndSession = self.shouldEndSession { 621 | d["shouldEndSession"] = shouldEndSession 622 | } else { 623 | d["shouldEndSession"] = nil 624 | } 625 | 626 | return d 627 | } 628 | 629 | /// hack to workaround JSONSerialization bug with Bools: https://bugs.swift.org/browse/SR-3013 630 | var asJSONString:String 631 | { 632 | var kvStrings:[String] = [] 633 | 634 | if 635 | let speechJSON = self.outputSpeech?.asJSON, 636 | let speechData = try? JSONSerialization.data(withJSONObject: speechJSON, options: []), 637 | let speechString = String(data:speechData,encoding:.utf8) { 638 | 639 | let key = "outputSpeech" 640 | 641 | kvStrings.append( "\"\(key)\" : \(speechString)" ) 642 | } 643 | 644 | if 645 | let speechJSON = self.card?.asJSON, 646 | let speechData = try? JSONSerialization.data(withJSONObject: speechJSON, options: []), 647 | let speechString = String(data:speechData,encoding:.utf8) { 648 | 649 | let key = "card" 650 | 651 | kvStrings.append( "\"\(key)\" : \(speechString)" ) 652 | } 653 | 654 | if 655 | let speechJSON = self.reprompt?.asJSON, 656 | let speechData = try? JSONSerialization.data(withJSONObject: speechJSON, options: []), 657 | let speechString = String(data:speechData,encoding:.utf8) { 658 | 659 | let key = "reprompt" 660 | kvStrings.append( "\"\(key)\" = \(speechString)" ) 661 | } 662 | 663 | if 664 | let directivesJSON = self.directives?.map({ 665 | (directive:AlexaDirective) -> JSONDictionary in 666 | return directive.asJSON 667 | }), 668 | let directivesData = try? JSONSerialization.data(withJSONObject: directivesJSON, options: []), 669 | let directivesString = String(data:directivesData,encoding:.utf8) 670 | { 671 | let key = "directives" 672 | kvStrings.append( "\"\(key)\" : \(directivesString)" ) 673 | } 674 | 675 | 676 | if let shouldEndSession = self.shouldEndSession { 677 | let v:String = shouldEndSession ? "true" : "false" 678 | let kvShouldEndSession = "\"shouldEndSession\" : \(v)" 679 | kvStrings.append( kvShouldEndSession ) 680 | } 681 | 682 | let s = "{\n" + kvStrings.joined(separator: ",\n") + "\n}" 683 | 684 | return s 685 | } 686 | } 687 | 688 | 689 | struct AlexaReprompt { 690 | var outputSpeech:AlexaOutputSpeech? 691 | } 692 | extension AlexaReprompt 693 | { 694 | var asJSON:JSONDictionary { 695 | var d:JSONDictionary = [:] 696 | d["outputSpeech"] = outputSpeech 697 | return d 698 | } 699 | } 700 | 701 | // poor mans variant type 702 | struct AlexaOutputSpeech { 703 | // valid values: "PlainText" "SSML" 704 | var type:String 705 | var text:String? 706 | var ssml:String? 707 | 708 | init(text t:String) { 709 | type = "PlainText" 710 | text = t 711 | ssml = nil 712 | } 713 | } 714 | 715 | extension AlexaOutputSpeech { 716 | var asJSON:JSONDictionary { 717 | var d:JSONDictionary = [:] 718 | d["type"] = type 719 | d["text"] = text 720 | d["ssml"] = ssml 721 | return d 722 | } 723 | } 724 | 725 | struct AlexaCard { 726 | /// valid values: Simple, Standard, LinkAccount 727 | var type:String 728 | var title:String? 729 | var content:String? 730 | var text:String? 731 | var image:AlexaImage? 732 | } 733 | 734 | extension AlexaCard { 735 | var asJSON:JSONDictionary { 736 | var d:JSONDictionary = [:] 737 | d["type"] = self.type 738 | d["title"] = self.title 739 | d["content"] = self.content 740 | d["text"] = self.text 741 | d["image"] = self.image?.asJSON 742 | return d 743 | } 744 | } 745 | 746 | struct AlexaImage { 747 | var smallImageURL:URL 748 | var largeImageURL:URL 749 | } 750 | extension AlexaImage { 751 | var asJSON:JSONDictionary { 752 | var d:JSONDictionary = [:] 753 | d["smallImageURL"] = smallImageURL.absoluteString 754 | d["largeImageURL"] = largeImageURL.absoluteString 755 | return d 756 | } 757 | } 758 | 759 | struct AlexaDirective { 760 | var type:String 761 | var playBehavior:String 762 | var audioItem:AlexaAudioItem 763 | } 764 | extension AlexaDirective { 765 | var asJSON:JSONDictionary { 766 | var d:JSONDictionary = [:] 767 | d["type"] = type 768 | d["playBehavior"] = playBehavior 769 | d["audioItem"] = audioItem.asJSON 770 | return d 771 | } 772 | } 773 | 774 | 775 | struct AlexaAudioItem { 776 | var stream:AlexaAudioStream 777 | } 778 | extension AlexaAudioItem { 779 | var asJSON:JSONDictionary { 780 | var d:JSONDictionary = [:] 781 | d["stream"] = stream.asJSON 782 | return d 783 | } 784 | } 785 | 786 | struct AlexaAudioStream { 787 | var token:String 788 | var url:URL 789 | var offsetInMilliseconds:Double 790 | } 791 | 792 | extension AlexaAudioStream { 793 | var asJSON:JSONDictionary { 794 | var d:JSONDictionary = [:] 795 | d["token"] = token 796 | d["url"] = url.absoluteString 797 | d["offsetInMilliseconds"] = offsetInMilliseconds 798 | return d 799 | } 800 | } 801 | 802 | 803 | --------------------------------------------------------------------------------