1
- import { Database } from "../app" ;
1
+ import { currentContext , Database } from "../app" ;
2
2
import createEngine , { ServiceEngine } from "./engine" ;
3
3
import loadTemplate , { Template } from "./template" ;
4
4
import crypto from "crypto" ;
5
5
import { randomPort as retrieveRandomPort } from "../util/port" ;
6
6
import { loadYamlFile } from "../util/yaml" ;
7
7
import * as fs from "fs" ;
8
+ import { PermaModel } from "../database" ;
9
+ import { asyncServiceRun , isServicePending } from "./asyncp" ;
8
10
9
11
export type Options = {
10
12
/**
@@ -74,13 +76,32 @@ export type ServiceManager = {
74
76
* @returns Whether the service was deleted
75
77
*/
76
78
deleteService ( id : string ) : Promise < boolean > ;
79
+ /**
80
+ * Update the options of a service.
81
+ *
82
+ * @param id The service ID
83
+ * @param options The new options
84
+ */
85
+ updateOptions ( id : string , options : Options ) : Promise < boolean > ;
77
86
/**
78
87
* Get the template by ID.
79
88
*
80
89
* @param id The template ID
81
90
* @returns The template wrapper
82
91
*/
83
92
getTemplate ( id : string ) : Template | undefined ;
93
+ /**
94
+ * Get the service by ID.
95
+ *
96
+ * @param id The service ID
97
+ */
98
+ getService ( id : string ) : Promise < PermaModel | undefined > ;
99
+ /**
100
+ * Get the last power error of a service.
101
+ *
102
+ * @param id The service ID
103
+ */
104
+ getLastPowerError ( id : string ) : Error | undefined ;
84
105
/**
85
106
* List all available services.
86
107
*
@@ -95,6 +116,14 @@ export type ServiceManager = {
95
116
listTemplates ( ) : Promise < string [ ] > ;
96
117
}
97
118
119
+ // Utils
120
+
121
+ function reqNoPending ( id : string ) {
122
+ if ( isServicePending ( id ) ) {
123
+ throw new Error ( 'Service is pending another action.' ) ;
124
+ }
125
+ }
126
+
98
127
// Implementation
99
128
100
129
export default async function ( db : Database , appConfig : any ) : Promise < ServiceManager > {
@@ -112,6 +141,9 @@ export default async function (db: Database, appConfig: any): Promise<ServiceMan
112
141
const settings = ( template : string ) => {
113
142
return loadYamlFile ( buildDir ( template ) + '/settings.yml' ) ;
114
143
}
144
+ // Save errors somewhere else?
145
+ // Could it be a memory leak if there are tons of them??
146
+ const errors = { } ;
115
147
return { // Manager
116
148
engine,
117
149
@@ -129,40 +161,47 @@ export default async function (db: Database, appConfig: any): Promise<ServiceMan
129
161
port_range . max as number
130
162
) ;
131
163
const serviceId = crypto . randomUUID ( ) ; // Create new unique service id
132
- // Container id
133
- const containerId = await engine . build (
134
- buildDir ( template ) ,
135
- volumeDir ( serviceId ) ,
136
- {
137
- ram : ram ?? defaults . ram as number ,
138
- cpu : cpu ?? defaults . cpu as number ,
139
- disk : disk ?? defaults . disk as number ,
140
- env : env ?? { } ,
141
- port : port ,
142
- ports : ports ?? [ ]
164
+ asyncServiceRun ( serviceId , async ( ) => {
165
+ // Container id
166
+ const containerId = await engine . build (
167
+ buildDir ( template ) ,
168
+ volumeDir ( serviceId ) ,
169
+ {
170
+ ram : ram ?? defaults . ram as number ,
171
+ cpu : cpu ?? defaults . cpu as number ,
172
+ disk : disk ?? defaults . disk as number ,
173
+ env : env ?? { } ,
174
+ port : port ,
175
+ ports : ports ?? [ ]
176
+ }
177
+ ) ;
178
+ if ( ! containerId ) {
179
+ throw new Error ( 'Failed to create container' ) ;
143
180
}
144
- ) ;
145
- if ( ! containerId ) {
146
- throw new Error ( 'Failed to create container' ) ;
147
- }
148
- const rollback = async ( ) => {
149
- await engine . delete ( containerId ) ;
150
- }
151
- const options = { ram, cpu, ports} ;
152
- // Save permanent info
153
- if ( ! await db . savePerma ( { serviceId, template, nodeId, port, options, env } ) ) {
154
- await rollback ( ) ;
155
- throw new Error ( 'Failed to save perma info to database' ) ;
156
- }
157
- // Save this session's info
158
- if ( ! await db . saveSession ( { serviceId, nodeId, containerId } ) ) {
159
- await rollback ( ) ;
160
- throw new Error ( 'Failed to save session info to database' ) ;
161
- }
181
+ const rollback = async ( ) => {
182
+ await engine . delete ( containerId ) ;
183
+ }
184
+ const options = { ram, cpu, ports} ;
185
+ // Save permanent info
186
+ if ( ! await db . savePerma ( { serviceId, template, nodeId, port, options, env } ) ) {
187
+ await rollback ( ) ;
188
+ throw new Error ( 'Failed to save perma info to database' ) ;
189
+ }
190
+ // Save this session's info
191
+ if ( ! await db . saveSession ( { serviceId, nodeId, containerId } ) ) {
192
+ await rollback ( ) ;
193
+ throw new Error ( 'Failed to save session info to database' ) ;
194
+ }
195
+ } ) . catch ( e => {
196
+ // Save to be later retrieved
197
+ errors [ serviceId ] = e ;
198
+ currentContext . logger . error ( e . message ) ;
199
+ } ) ;
162
200
return serviceId ;
163
201
} ,
164
202
165
203
async resumeService ( id ) {
204
+ reqNoPending ( id ) ;
166
205
const perma_ = await db . getPerma ( id ) ;
167
206
if ( ! perma_ ) {
168
207
return false ;
@@ -173,31 +212,39 @@ export default async function (db: Database, appConfig: any): Promise<ServiceMan
173
212
if ( ! perma ) {
174
213
return false ;
175
214
}
176
- // Rebuild container using existing volume directory,
177
- // stored options and custom env variables.
178
- const containerId = await engine . build (
179
- buildDir ( template ) ,
180
- volumeDir ( id ) ,
181
- {
182
- ram : options . ram ?? defaults . ram as number ,
183
- cpu : options . cpu ?? defaults . cpu as number ,
184
- disk : options . disk ?? defaults . disk as number ,
185
- env : env ?? defaults . env as { [ key : string ] : string } ,
186
- port : perma_ . port ,
187
- ports : options . ports ?? [ ]
215
+ asyncServiceRun ( id , async ( ) => {
216
+ // Rebuild container using existing volume directory,
217
+ // stored options and custom env variables.
218
+ const containerId = await engine . build (
219
+ buildDir ( template ) ,
220
+ volumeDir ( id ) ,
221
+ {
222
+ ram : options . ram ?? defaults . ram as number ,
223
+ cpu : options . cpu ?? defaults . cpu as number ,
224
+ disk : options . disk ?? defaults . disk as number ,
225
+ env : env ?? defaults . env as { [ key : string ] : string } ,
226
+ port : perma_ . port ,
227
+ ports : options . ports ?? [ ]
228
+ }
229
+ )
230
+ // Save new session info
231
+ if ( await db . saveSession ( { serviceId : id , nodeId, containerId } ) ) {
232
+ return true ;
233
+ } else {
234
+ // Cleanup if err with db
235
+ await engine . stop ( containerId ) ;
236
+ return false ;
188
237
}
189
- )
190
- // Save new session info
191
- if ( await db . saveSession ( { serviceId : id , nodeId, containerId } ) ) {
192
- return true ;
193
- } else {
194
- // Cleanup if err with db
195
- await engine . stop ( containerId ) ;
196
- return false ;
197
- }
238
+ } ) . then ( success => {
239
+ if ( ! success ) {
240
+ errors [ id ] = new Error ( 'Failed to resume service' ) ;
241
+ }
242
+ } ) ;
243
+ return true ;
198
244
} ,
199
245
200
246
async stopService ( id ) {
247
+ reqNoPending ( id ) ;
201
248
const session = await db . getSession ( id ) ;
202
249
if ( ! session ) {
203
250
return false ;
@@ -213,10 +260,32 @@ export default async function (db: Database, appConfig: any): Promise<ServiceMan
213
260
return db . deletePerma ( id ) ;
214
261
} ,
215
262
263
+ async updateOptions ( id : string , options : Options ) : Promise < boolean > {
264
+ reqNoPending ( id ) ;
265
+ const perma = await db . getPerma ( id ) ;
266
+ const data : PermaModel = {
267
+ ...perma ,
268
+ ...options ,
269
+ env : {
270
+ ...perma . env ,
271
+ ...options . env
272
+ }
273
+ } ;
274
+ return db . savePerma ( data ) ;
275
+ } ,
276
+
216
277
getTemplate ( id : string ) : Template | undefined {
217
278
return loadTemplate ( id ) ;
218
279
} ,
219
280
281
+ async getService ( id : string ) : Promise < PermaModel | undefined > {
282
+ return db . getPerma ( id ) ;
283
+ } ,
284
+
285
+ getLastPowerError ( id : string ) : Error | undefined {
286
+ return errors [ id ] ;
287
+ } ,
288
+
220
289
listServices ( ) : Promise < string [ ] > {
221
290
return db . list ( nodeId ) ;
222
291
} ,
0 commit comments