-
Notifications
You must be signed in to change notification settings - Fork 0
/
anycloud.ln
161 lines (145 loc) · 5.15 KB
/
anycloud.ln
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
from @std/app import start, print, eprint, exit
from @std/avmdaemon import controlPort, getClusterSecret, Connection, body, send, status
from @std/cmd import exec
from @std/datastore import namespace, getOr, set, has, del
from @std/http import fetch, Request, Response
from @std/tcpserver import tunnel
type ModeAndKey {
mode: string,
key: string,
}
type ServiceRequestBody {
errorCode: int64,
alanVersion: string,
level: string,
message: string,
}
on start {
print('Starting Anycloud...');
const alanVersionExec = exec('alan --version | sed "s/alan //" | xargs echo -n');
if alanVersionExec.exitCode != 0 {
eprint('Error getting alan version');
}
const alanVersion = alanVersionExec.stdout;
const alanTechEnvExec = exec('echo $ALAN_TECH_ENV | xargs echo -n');
if alanTechEnvExec.exitCode != 0 {
eprint('Error retreiveing $ALAN_TECH_ENV variable');
}
const alanTechEnv = alanTechEnvExec.stdout;
const serviceUrl = alanTechEnv == 'local' ?
'http://localhost:8080/v1' :
(alanTechEnv == 'staging' ?
'https://deploy-staging.alantechnologies.com/v1' :
'https://deploy.alantechnologies.com/v1'
);
const errorEndpoint = '/daemonError';
const url = serviceUrl + errorEndpoint;
const clusterSecret = getClusterSecret();
if clusterSecret.isNone() {
const err = 'No cluster secret defined';
eprint(err);
emit exit 1;
}
const untarRes = exec('tar -xzf app.tar.gz');
if untarRes.exitCode != 0 {
eprint('app.tar.gz could not be unpacked');
eprint(untarRes.stderr);
emit exit untarRes.exitCode;
}
const dockerRes = exec('docker build -t anycloud/app .');
if dockerRes.exitCode != 0 {
const errStr = 'Docker container could not be built';
eprint(errStr);
eprint(dockerRes.stderr);
const cleanDockerErr = getCleanError(dockerRes.stderr);
const message = errStr + ': ' + cleanDockerErr;
sendDaemonError(url, message, alanVersion);
emit exit dockerRes.exitCode;
}
// We need to use the --env-file option only if we are sure the file exists
const envFile = exec('stat anycloud.env');
const containerStr = envFile.exitCode != 0 ?
('docker run --env CLUSTER_SECRET=' + clusterSecret.getOrExit() + ' --add-host=host.docker.internal:host-gateway -p 8088:8088 -d anycloud/app:latest') :
('docker run --env CLUSTER_SECRET=' + clusterSecret.getOrExit() + ' --env-file anycloud.env --add-host=host.docker.internal:host-gateway -p 8088:8088 -d anycloud/app:latest');
const containerRes = exec(containerStr);
if containerRes.exitCode != 0 {
const errStr = 'Docker container could not be started';
eprint(errStr);
eprint(containerRes.stderr);
const cleanDockerErr = getCleanError(containerRes.stderr);
const message = errStr + ': ' + cleanDockerErr;
sendDaemonError(url, message, alanVersion);
emit exit containerRes.exitCode;
}
let connected = tunnel(8088);
print(connected ? "Tunneling to 8088" : "Failed to establish a tunnel");
}
on controlPort fn (conn: Connection) {
const req = conn.req;
const res = conn.res;
const modeAndKey = getModeAndKey(req.url);
if modeAndKey.isErr() {
res.status(400).body(modeAndKey.getErr(error("Invalid access")).toString()).send();
} else {
const ns = namespace('kv');
const modeStr = modeAndKey.getOrExit().mode;
const keyStr = modeAndKey.getOrExit().key;
if modeStr == 'get' {
res.body(ns.getOr(keyStr, '<key not found>')).send();
} else if modeStr == 'set' {
ns.set(keyStr, req.body);
res.body('ok').send();
} else if modeStr == 'has' {
res.body(ns.has(keyStr).toString()).send();
} else if modeStr == 'del' {
res.body(ns.del(keyStr).toString()).send();
} else {
res.status(400).body('Invalid access').send();
}
}
}
fn getModeAndKey(url: string): Result<ModeAndKey> {
const parts = url.split('/');
const len = parts.length();
const mode = parts[len - 2];
const key = parts[len - 1];
if mode.isErr() || key.isErr() {
return err('Invalid URL');
} else {
return ok(new ModeAndKey {
mode: mode || 'invalid',
key: key || 'invalid',
});
}
}
fn toString(reqBody: ServiceRequestBody): string {
const errorCodeStr = '"errorCode": ' + reqBody.errorCode.toString();
const alanVersionStr = '"alanVersion": "' + reqBody.alanVersion + '"';
const levelStr = '"level": "' + reqBody.level + '"';
const messageStr = '"message": "' + reqBody.message + '"';
return '{' + errorCodeStr + ', ' + alanVersionStr + ', ' + levelStr + ', ' + messageStr + '}';
}
fn getCleanError(error: string): string {
return error
.split("\n").join(" ")
.split("").map(fn(value: string): string {
return value == '"' ? '\"' : value;
}).join("");
}
fn sendDaemonError(url: string, message: string, alanVersion: string) {
let version = 'v' + alanVersion;
const bodyStr = new ServiceRequestBody {
errorCode: 140,
alanVersion: version,
level: "error",
message: message,
}.toString();
const headers = newHashMap('Content-Length', bodyStr.length().toString());
headers.set('Content-type', 'application/json');
fetch(new Request {
method: 'POST',
url: url,
headers: headers,
body: bodyStr,
});
}