@naanlang/naan 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -1
- package/README.md +1 -1
- package/bin/index.js +5 -2
- package/dist/env_web.js +11 -1
- package/dist/naan.min.js +4 -4
- package/frameworks/browser/https_request.nlg +2 -0
- package/frameworks/browser/sworker.js +23 -23
- package/frameworks/browser/terminals.nlg +123 -21
- package/frameworks/browser/workers.nlg +4 -1
- package/frameworks/browser/ws_client.nlg +5 -3
- package/frameworks/client/apiclient.nlg +8 -6
- package/frameworks/client/relaycon.nlg +3 -1
- package/frameworks/node/apiserver.nlg +190 -4
- package/frameworks/node/worker.nlg +112 -10
- package/frameworks/node/ws_client.nlg +5 -3
- package/frameworks/project/build.nlg +3 -1
- package/frameworks/project/projects.nlg +1 -1
- package/frameworks/running/executors.nlg +3 -1
- package/frameworks/running/taskexec.nlg +5 -1
- package/lib/browser/env_web.js +17 -7
- package/lib/browser/env_webworker.js +22 -3
- package/lib/core/naanlib.js +4 -4
- package/lib/env_node.js +15 -4
- package/lib/env_nodeworker.js +21 -1
- package/package.json +8 -5
- package/plugins/serviceAws/aws/aws-sdk-node.min.js +2 -2
- package/plugins/serviceAws/aws/aws-sdk.min.js +3 -2
- package/plugins/serviceAws/aws_dynamo.nlg +19 -19
- package/plugins/serviceYC/generic_api.yaml +36 -0
- package/plugins/serviceYC/naanide-relay-index.js +148 -0
- package/plugins/serviceYC/serviceYC.nlg +60 -0
- package/plugins/serviceYC/yc_apigateway.nlg +205 -0
- package/plugins/serviceYC/yc_function.nlg +163 -0
- package/plugins/serviceYC/yc_vm.nlg +207 -0
- package/test/test_07_lingo.nlg +1 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* yc_function.nlg
|
|
3
|
+
* serviceYC
|
|
4
|
+
*
|
|
5
|
+
* Access to serverless function management services for YC.
|
|
6
|
+
*
|
|
7
|
+
* column positioning: // // !
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2022 by Richard C. Zulch
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* YCFunction
|
|
16
|
+
*
|
|
17
|
+
* YCFunction manipulation functions.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
closure YCFunction(local ycfun) {
|
|
22
|
+
ycfun = new(object, this)
|
|
23
|
+
|
|
24
|
+
//
|
|
25
|
+
// login
|
|
26
|
+
//
|
|
27
|
+
ycfun.login = closure login(creds) {
|
|
28
|
+
ycfun.creds = creds
|
|
29
|
+
list(false, { ok: true })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//
|
|
33
|
+
// getFunction
|
|
34
|
+
//
|
|
35
|
+
// Get basic function info like:
|
|
36
|
+
// {
|
|
37
|
+
// id : "d4er5sq67r5j47nno6nb",
|
|
38
|
+
// folderId : "b1gjjahqm5u9tjolom9l",
|
|
39
|
+
// createdAt : "2022-08-30T00:57:18.024Z",
|
|
40
|
+
// name : "hello-world",
|
|
41
|
+
// description : "A quick first function.",
|
|
42
|
+
// logGroupId : "ckgbmh6df6dt92pphc6b",
|
|
43
|
+
// httpInvokeUrl: "https://functions.yandexcloud.net/d4er5sq67r5j47nno6nb",
|
|
44
|
+
// status : "ACTIVE"
|
|
45
|
+
// }
|
|
46
|
+
//
|
|
47
|
+
ycfun.getFunction = closure getFunction(funcID, local url, error, data) {
|
|
48
|
+
debuglog("YCFunction.getFunction", funcID)
|
|
49
|
+
url = "https://serverless-functions.api.cloud.yandex.net/functions/v1/functions/".concat(funcID)
|
|
50
|
+
`(error, data) = YcRequest(url, ycfun.creds, {
|
|
51
|
+
query: { x: 1 }
|
|
52
|
+
})
|
|
53
|
+
if error
|
|
54
|
+
debuglog("YCFunction.getFunction failed:", ErrorString(error))
|
|
55
|
+
list(error, data)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//
|
|
59
|
+
// getVersionByTag
|
|
60
|
+
//
|
|
61
|
+
// Get info about a specific function version using its tag, e.g. $latest:
|
|
62
|
+
// {
|
|
63
|
+
// resources : { memory: "134217728" },
|
|
64
|
+
// tags : ["$latest"],
|
|
65
|
+
// id : "d4evm4lsi91hqkv78ane",
|
|
66
|
+
// functionId : "d4er5sq67r5j47nno6nb",
|
|
67
|
+
// createdAt : "2022-08-31T03:32:59.577Z",
|
|
68
|
+
// runtime : "nodejs16",
|
|
69
|
+
// entrypoint : "index.handler",
|
|
70
|
+
// executionTimeout: "3s",
|
|
71
|
+
// serviceAccountId: "aje9s3df60gofsddio0c",
|
|
72
|
+
// imageSize : "4096",
|
|
73
|
+
// status : "ACTIVE",
|
|
74
|
+
// logGroupId : "ckgbmh6df6dt92pphc6b" }
|
|
75
|
+
// }
|
|
76
|
+
//
|
|
77
|
+
ycfun.getVersionByTag = closure getVersionByTag(funcID, tag, local url, error, data) {
|
|
78
|
+
url = "https://serverless-functions.api.cloud.yandex.net/functions/v1/versions:byTag".concat(
|
|
79
|
+
EncodeQuery("?", {
|
|
80
|
+
functionId: funcID
|
|
81
|
+
tag: tag
|
|
82
|
+
}))
|
|
83
|
+
`(error, data) = YcRequest(url, ycfun.creds, {
|
|
84
|
+
query: { x: 1 }
|
|
85
|
+
})
|
|
86
|
+
if error
|
|
87
|
+
debuglog("YCFunction.getVersionByTag failed:", ErrorString(error))
|
|
88
|
+
list(error, data)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//
|
|
92
|
+
// invokeFunction
|
|
93
|
+
//
|
|
94
|
+
// Invoke a public cloud function. It is impossible to invoke a private cloud function from
|
|
95
|
+
// browsers because the function cannot respond to a CORS authorization request using the OPTIONS
|
|
96
|
+
// method without an authorization header that cannot be added to the preflight.
|
|
97
|
+
//
|
|
98
|
+
ycfun.invokeFunction = closure invokeFunction(funcID, local url, error, data) {
|
|
99
|
+
url = "https://functions.yandexcloud.net/".concat(funcID)
|
|
100
|
+
`(error, data) = https.HttpsApiRequest(url, {
|
|
101
|
+
query: { x: 1 }
|
|
102
|
+
})
|
|
103
|
+
if error
|
|
104
|
+
debuglog("YCFunction.invokeFunction failed:", ErrorString(error))
|
|
105
|
+
list(error, data)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
//
|
|
109
|
+
// updateFunctionCode
|
|
110
|
+
//
|
|
111
|
+
//
|
|
112
|
+
ycfun.updateFunctionCode = closure updateFunctionCode(info, bucket, key, sha256,
|
|
113
|
+
local url, params, error, data) {
|
|
114
|
+
params = {
|
|
115
|
+
function_id: info.functionId
|
|
116
|
+
functionID: info.functionId
|
|
117
|
+
runtime: info.runtime
|
|
118
|
+
description: "Updated: ".concat(Date())
|
|
119
|
+
entrypoint: info.entrypoint
|
|
120
|
+
resources: info.resources
|
|
121
|
+
executionTimeout: info.executionTimeout
|
|
122
|
+
serviceAccountId: info.serviceAccountId
|
|
123
|
+
package: {
|
|
124
|
+
bucketName: bucket
|
|
125
|
+
objectName: key
|
|
126
|
+
sha256: sha256
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if info.environment
|
|
130
|
+
params.environment = info.environment
|
|
131
|
+
if info.logOptions
|
|
132
|
+
params.logOptions = info.logOptions
|
|
133
|
+
url = "https://serverless-functions.api.cloud.yandex.net/functions/v1/versions"
|
|
134
|
+
`(error, data) = YcRequest(url, ycfun.creds, {
|
|
135
|
+
putdata: params
|
|
136
|
+
})
|
|
137
|
+
if error
|
|
138
|
+
debuglog("YCFunction.updateFunctionCode failed:", ErrorString(error))
|
|
139
|
+
list(error, data)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// finis
|
|
143
|
+
ycfun
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
/*
|
|
148
|
+
* ycFunctionInit
|
|
149
|
+
*
|
|
150
|
+
* Initialize the module.
|
|
151
|
+
*
|
|
152
|
+
*/
|
|
153
|
+
|
|
154
|
+
function ycFunctionInit(local manifest) {
|
|
155
|
+
manifest = `(YCFunction, ycFunctionInit)
|
|
156
|
+
|
|
157
|
+
Naan.module.build(module.id, "yc_function", function(modobj, compobj) {
|
|
158
|
+
require("./serviceYC.nlg")
|
|
159
|
+
compobj.manifest = manifest
|
|
160
|
+
modobj.exports.YCFunction = YCFunction
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
} ();
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* yc_vm.nlg
|
|
3
|
+
* serviceYC
|
|
4
|
+
*
|
|
5
|
+
* YC VM inquiry and control.
|
|
6
|
+
*
|
|
7
|
+
* column positioning: // // !
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2024 by Richard C. Zulch
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
vmState = false;
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* VmControl
|
|
17
|
+
*
|
|
18
|
+
* When run on a YC VM instance, this class can acquire useful VM information and perform selected
|
|
19
|
+
* operations. At the present time it can obtain the identity document with instanceID, auth token,
|
|
20
|
+
* and shut down the VM.
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
closure VmControl(local ycvm) {
|
|
25
|
+
global(https, nodeHttp, vmState)
|
|
26
|
+
if vmState.ycvm
|
|
27
|
+
return (vmState.ycvm) // singleton
|
|
28
|
+
ycvm = new(object, this)
|
|
29
|
+
vmState = new(nonce) // don't save with state
|
|
30
|
+
vmState.ycvm = ycvm
|
|
31
|
+
|
|
32
|
+
// httpLocalRequest
|
|
33
|
+
//
|
|
34
|
+
// Make a simple GET request for the Docker's 169.254.169.254 metadata interface. This is not
|
|
35
|
+
// supported by the standard NaaN framework HttpsApiRequest because it is HTTP and returns JSON
|
|
36
|
+
// without bothering to set a contentType advising this.
|
|
37
|
+
//
|
|
38
|
+
|
|
39
|
+
closure httpLocalRequest(url, params, local pending, chunks, request) {
|
|
40
|
+
pending = new(nonce)
|
|
41
|
+
chunks = []
|
|
42
|
+
request = nodeHttp.request(url, params, closure (response) {
|
|
43
|
+
response.on("data", function (chunk) {
|
|
44
|
+
chunks.push(chunk)
|
|
45
|
+
})
|
|
46
|
+
response.on("end", function (local content, error) {
|
|
47
|
+
content = js.g.Buffer.concat(chunks)
|
|
48
|
+
if response.statusCode < 200 || response.statusCode >= 299 { // 299 is "cancelled"
|
|
49
|
+
debuglog("httpRequest status:", url, response.statusCode, "message:", content)
|
|
50
|
+
pending.signal(list(Error("httpRequest statusCode:", url, response.statusCode, {
|
|
51
|
+
status: response.statusCode
|
|
52
|
+
}), false, { status: response.statusCode }))
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
if response.complete {
|
|
56
|
+
try {
|
|
57
|
+
content = js.g.JSON.parse(content.toString())
|
|
58
|
+
} catch {
|
|
59
|
+
error = Error("httpRequest decode:", url, exception)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else
|
|
63
|
+
error = Error("httpRequest terminated prematurely:", url, response.statusCode)
|
|
64
|
+
pending.signal(list(error, content, {
|
|
65
|
+
headers: response.headers
|
|
66
|
+
status: response.statusCode
|
|
67
|
+
contentType: response.headers["content-type"]
|
|
68
|
+
}))
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
request.on("error", function hre(error) {
|
|
72
|
+
debuglog("httpRequest error:", url, "error:", error)
|
|
73
|
+
error = Error("httpRequest failed:", url, error)
|
|
74
|
+
pending.signal(list(error))
|
|
75
|
+
})
|
|
76
|
+
request.end()
|
|
77
|
+
pending.wait()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// getIdentityDoc
|
|
81
|
+
//
|
|
82
|
+
// Ask the VM for its identity document. This information does not change.
|
|
83
|
+
//
|
|
84
|
+
// How to get instanceID from "identity document":
|
|
85
|
+
// curl -H Metadata-Flavor:Google 169.254.169.254/computeMetadata/v1/instance/vendor/identity/document
|
|
86
|
+
// {"instanceId":"epd15j0nbbp0mgs9p9ra","productCodes":null,"imageId":"fd8ru2jkl1laurb1emo7", \
|
|
87
|
+
// "productIds":["f2eo49853qme9iril0tp"],"createdAt":"2024-06-15T18:12:50Z","version":"2023-03-01"}
|
|
88
|
+
//
|
|
89
|
+
closure getIdentityDoc(local error, idDoc) {
|
|
90
|
+
if ycvm.idDoc
|
|
91
|
+
return (list(false, ycvm.idDoc))
|
|
92
|
+
`(error, idDoc) = httpLocalRequest("http://169.254.169.254/computeMetadata/v1/instance/vendor/identity/document", {
|
|
93
|
+
headers: {
|
|
94
|
+
"Metadata-Flavor": "Google"
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
if error
|
|
98
|
+
list(error)
|
|
99
|
+
else
|
|
100
|
+
list(false, ycvm.idDoc = idDoc)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// getAuthDoc
|
|
104
|
+
//
|
|
105
|
+
// Ask the VM for an auth token so we can call YC APIs using the VM's permissions. This expires
|
|
106
|
+
// so we keep track of when to call it again.
|
|
107
|
+
//
|
|
108
|
+
// How to get auth token:
|
|
109
|
+
// curl -H Metadata-Flavor:Google 169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token
|
|
110
|
+
// {"access_token":"t1.9euelZqVjY2Tk8rIlYyQnpGPjZbJmO3rnpWazpqcmJ6dnJKLzoqKlJeOmpHl9PdVKkhM-e8lZC_l3fT \
|
|
111
|
+
// 3FVlFTPnvJWQv5c3n9euelZqLlJaSnZaUypaYzc_Pnc2Mme_8xeuelZqLlJaSnZaUypaYzc_Pnc2Mmb3rnpWanZ2WnpaPysfNzc \
|
|
112
|
+
// eUy8zOmI-13oac0ZyQko-Ki5rRi5nSnJCSj4qLmtKSmouem56LntKMng.-wi4CzEJbPX5ume3Ma_P7nZG-g0YRCQ2P4l2koIRYV \
|
|
113
|
+
// drsy9QGHR5JEx2zD--xtvXGBBCbZNo-I41GdjEb_IPCg","expires_in":41998,"token_type":"Bearer"}
|
|
114
|
+
//
|
|
115
|
+
closure getAuthDoc(local now, error, authDoc, expireTime) {
|
|
116
|
+
now = milliseconds()
|
|
117
|
+
if ycvm.authDoc && ycvm.authDocExpire > milliseconds()
|
|
118
|
+
return (list(false, ycvm.authDoc)) // use cached credential
|
|
119
|
+
`(error, authDoc) = httpLocalRequest("http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token", {
|
|
120
|
+
headers: {
|
|
121
|
+
"Metadata-Flavor": "Google"
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
if authDoc {
|
|
125
|
+
expireTime = now + (authDoc.expires_in - 3600) * 1000 // give us 1 hour of margin
|
|
126
|
+
if !expireTime || expireTime < now + 3600000 { // if not at least one hour from now
|
|
127
|
+
debuglog("YcVmControl.getAuthDoc: invalid credential expires_in", authDoc.expires_in)
|
|
128
|
+
expireTime = now + 3600000
|
|
129
|
+
}
|
|
130
|
+
ycvm.authDocExpire = expireTime
|
|
131
|
+
ycvm.authDoc = authDoc
|
|
132
|
+
}
|
|
133
|
+
if error
|
|
134
|
+
list(error)
|
|
135
|
+
else
|
|
136
|
+
list(false, authDoc)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// authHeader
|
|
140
|
+
//
|
|
141
|
+
// Return authorization header for the VM to be used in API requests to YC, as a result tuple.
|
|
142
|
+
// The returned header is a list: (`Authorization, "Bearer <token>")
|
|
143
|
+
//
|
|
144
|
+
ycvm.authHeader = closure authHeader(error, authDoc) {
|
|
145
|
+
`(error, authDoc) = getAuthDoc()
|
|
146
|
+
if error
|
|
147
|
+
list(error)
|
|
148
|
+
else
|
|
149
|
+
list(false, list(`Authorization, "${authDoc.token_type} ${authDoc.access_token}"))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// shutdownVM
|
|
153
|
+
//
|
|
154
|
+
// Tell YC to shutdown our VM.
|
|
155
|
+
//
|
|
156
|
+
// curl -X POST -H "Authorization:Bearer <token>" \
|
|
157
|
+
// https://compute.api.cloud.yandex.net/compute/v1/instances/epd15j0nbbp0mgs9p9ra:stop
|
|
158
|
+
// {
|
|
159
|
+
// "done": false,
|
|
160
|
+
// "metadata": {
|
|
161
|
+
// "@type": "type.googleapis.com/yandex.cloud.compute.v1.StopInstanceMetadata",
|
|
162
|
+
// "instanceId": "epd15j0nbbp0mgs9p9ra"
|
|
163
|
+
// },
|
|
164
|
+
// "id": "epdnbllb1cgtmukpt1aq",
|
|
165
|
+
// "description": "Stop instance",
|
|
166
|
+
// "createdAt": "2024-06-15T19:47:40.315889611Z",
|
|
167
|
+
// "createdBy": "aje1ecgabcmt1uukhqen",
|
|
168
|
+
// "modifiedAt": "2024-06-15T19:47:40.315889611Z"
|
|
169
|
+
// }
|
|
170
|
+
//
|
|
171
|
+
ycvm.shutdownVM = closure shutdownVM(local error, idDoc, header, data) {
|
|
172
|
+
`(error, idDoc) = getIdentityDoc()
|
|
173
|
+
if !error
|
|
174
|
+
`(error, header) = authHeader()
|
|
175
|
+
if !error
|
|
176
|
+
`(error, data) = https.HttpsApiRequest("https://compute.api.cloud.yandex.net/compute/v1/instances/${idDoc.instanceId}:stop", {
|
|
177
|
+
method: "POST"
|
|
178
|
+
headers: [ header ]
|
|
179
|
+
})
|
|
180
|
+
if error
|
|
181
|
+
list(error)
|
|
182
|
+
else
|
|
183
|
+
list(false, data)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// finis
|
|
187
|
+
|
|
188
|
+
ycvm
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
/*
|
|
193
|
+
* ycvmInit
|
|
194
|
+
*
|
|
195
|
+
* Initialize the module.
|
|
196
|
+
*
|
|
197
|
+
*/
|
|
198
|
+
|
|
199
|
+
function ycvmInit(local manifest) {
|
|
200
|
+
manifest = `(vmState, VmControl, ycvmInit)
|
|
201
|
+
|
|
202
|
+
Naan.module.build(module.id, "yc_vm", function(modobj, compobj) {
|
|
203
|
+
require("./serviceYC.nlg")
|
|
204
|
+
compobj.manifest = manifest
|
|
205
|
+
module.exports.VmControl = VmControl
|
|
206
|
+
})
|
|
207
|
+
} ();
|
package/test/test_07_lingo.nlg
CHANGED
|
@@ -192,7 +192,7 @@ RegisterTestCategory("Lingo", [
|
|
|
192
192
|
[ "macro(x){x.c=5}({a:3,b:4});", { errors: "can't modify NaanConst::Dictionary{2} from Test-Lingo" } ],
|
|
193
193
|
[ "macro(x){x[1]=6}([3,4,5]);", { errors: "can't modify NaanConst::Array[3] from Test-Lingo" } ],
|
|
194
194
|
|
|
195
|
-
[ "js.*;", '("n", "o", "i", "d", "g", "r", "t")' ],
|
|
195
|
+
[ "js.*;", '("n", "o", "i", "d", "g", "r", "t", "k")' ],
|
|
196
196
|
|
|
197
197
|
[ "[1,2,3].concat(4,5,6);", "[1, 2, 3, 4, 5, 6]" ],
|
|
198
198
|
[ "[1,2,3].concat([4,5,6]);", "[1, 2, 3, 4, 5, 6]" ],
|