Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/util/db-schema/projects.ts
1447 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { State } from "@cocalc/util/compute-states";
7
import { PurchaseInfo } from "@cocalc/util/licenses/purchase/types";
8
import { deep_copy } from "@cocalc/util/misc";
9
import {
10
ExecuteCodeOptions,
11
ExecuteCodeOptionsAsyncGet,
12
ExecuteCodeOutput,
13
} from "@cocalc/util/types/execute-code";
14
import { DEFAULT_QUOTAS } from "@cocalc/util/upgrade-spec";
15
16
import { NOTES } from "./crm";
17
import { FALLBACK_COMPUTE_IMAGE } from "./defaults";
18
import { SCHEMA as schema } from "./index";
19
import { Table } from "./types";
20
21
export const MAX_FILENAME_SEARCH_RESULTS = 100;
22
23
Table({
24
name: "projects",
25
rules: {
26
primary_key: "project_id",
27
//# A lot depends on this being right at all times, e.g., restart state,
28
//# so do not use db_standby yet.
29
//# It is simply not robust enough.
30
//# db_standby : 'safer'
31
32
pg_indexes: [
33
"last_edited",
34
"created", // TODO: this could have a fillfactor of 100
35
"USING GIN (users)", // so get_collaborator_ids is fast
36
"lti_id",
37
"USING GIN (state)", // so getting all running projects is fast (e.g. for site_license_usage_log... but also manage-state)
38
"((state #>> '{state}'))", // projecting the "state" (running, etc.) for its own index – the GIN index above still causes a scan, which we want to avoid.
39
"((state ->> 'state'))", // same reason as above. both syntaxes appear and we have to index both.
40
"((state IS NULL))", // not covered by the above
41
"((settings ->> 'always_running'))", // to quickly know which projects have this setting
42
"((run_quota ->> 'always_running'))", // same reason as above
43
"deleted", // in various queries we quickly fiter deleted projects
44
"site_license", // for queries across projects related to site_license#>>{license_id}
45
],
46
47
crm_indexes: ["last_edited"],
48
49
user_query: {
50
get: {
51
pg_where: ["last_edited >= NOW() - interval '21 days'", "projects"],
52
pg_where_load: ["last_edited >= NOW() - interval '2 days'", "projects"],
53
options: [{ limit: 100, order_by: "-last_edited" }],
54
options_load: [{ limit: 15, order_by: "-last_edited" }],
55
pg_changefeed: "projects",
56
throttle_changes: 2000,
57
fields: {
58
project_id: null,
59
name: null,
60
title: "",
61
description: "",
62
users: {},
63
invite: null, // who has been invited to this project via email
64
invite_requests: null, // who has requested to be invited
65
deleted: null,
66
host: null,
67
settings: DEFAULT_QUOTAS,
68
run_quota: null,
69
site_license: null,
70
status: null,
71
// security model is anybody with access to the project should be allowed to know this token.
72
secret_token: null,
73
state: null,
74
last_edited: null,
75
last_active: null,
76
action_request: null, // last requested action -- {action:?, time:?, started:?, finished:?, err:?}
77
course: null,
78
// if the value is not set, we have to use the old default prior to summer 2020 (Ubuntu 18.04, not 20.04!)
79
compute_image: FALLBACK_COMPUTE_IMAGE,
80
created: null,
81
env: null,
82
sandbox: null,
83
avatar_image_tiny: null,
84
// do NOT add avatar_image_full here or it will get included in changefeeds, which we don't want.
85
// instead it gets its own virtual table.
86
pay_as_you_go_quotas: null,
87
},
88
},
89
set: {
90
// NOTE: for security reasons users CANNOT set the course field via a user query;
91
// instead use the api/v2/projects/course/set-course-field api endpoint.
92
fields: {
93
project_id: "project_write",
94
title: true,
95
name: true,
96
description: true,
97
deleted: true,
98
invite_requests: true, // project collabs can modify this (e.g., to remove from it once user added or rejected)
99
users(obj, db, account_id) {
100
return db._user_set_query_project_users(obj, account_id);
101
},
102
action_request: true, // used to request that an action be performed, e.g., "save"; handled by before_change
103
compute_image: true,
104
site_license: true,
105
env: true,
106
sandbox: true,
107
avatar_image_tiny: true,
108
avatar_image_full: true,
109
},
110
required_fields: {
111
project_id: true,
112
},
113
before_change(database, old_val, new_val, account_id, cb) {
114
database._user_set_query_project_change_before(
115
old_val,
116
new_val,
117
account_id,
118
cb,
119
);
120
},
121
122
on_change(database, old_val, new_val, account_id, cb) {
123
database._user_set_query_project_change_after(
124
old_val,
125
new_val,
126
account_id,
127
cb,
128
);
129
},
130
},
131
},
132
133
project_query: {
134
get: {
135
pg_where: [{ "project_id = $::UUID": "project_id" }],
136
fields: {
137
project_id: null,
138
title: null,
139
description: null,
140
status: null,
141
},
142
},
143
set: {
144
fields: {
145
project_id: "project_id",
146
title: true,
147
description: true,
148
status: true,
149
},
150
},
151
},
152
},
153
fields: {
154
project_id: {
155
type: "uuid",
156
desc: "The project id, which is the primary key that determines the project.",
157
},
158
name: {
159
type: "string",
160
pg_type: "VARCHAR(100)",
161
desc: "The optional name of this project. Must be globally unique (up to case) across all projects with a given *owner*. It can be between 1 and 100 characters from a-z A-Z 0-9 period and dash.",
162
render: { type: "text", maxLen: 100, editable: true },
163
},
164
title: {
165
type: "string",
166
desc: "The short title of the project. Should use no special formatting, except hashtags.",
167
render: { type: "project_link", project_id: "project_id" },
168
},
169
description: {
170
type: "string",
171
desc: "A longer textual description of the project. This can include hashtags and should be formatted using markdown.",
172
render: {
173
type: "markdown",
174
maxLen: 1024,
175
editable: true,
176
},
177
}, // markdown rendering possibly not implemented
178
users: {
179
title: "Collaborators",
180
type: "map",
181
desc: "This is a map from account_id's to {hide:bool, group:'owner'|'collaborator', upgrades:{memory:1000, ...}, ssh:{...}}.",
182
render: { type: "usersmap", editable: true },
183
},
184
invite: {
185
type: "map",
186
desc: "Map from email addresses to {time:when invite sent, error:error message if there was one}",
187
date: ["time"],
188
},
189
invite_requests: {
190
type: "map",
191
desc: "This is a map from account_id's to {timestamp:?, message:'i want to join because...'}.",
192
date: ["timestamp"],
193
},
194
deleted: {
195
type: "boolean",
196
desc: "Whether or not this project is deleted.",
197
render: { type: "boolean", editable: true },
198
},
199
host: {
200
type: "map",
201
desc: "This is a map {host:'hostname_of_server', assigned:timestamp of when assigned to that server}.",
202
date: ["assigned"],
203
},
204
settings: {
205
type: "map",
206
desc: 'This is a map that defines the free base quotas that a project has. It is of the form {cores: 1.5, cpu_shares: 768, disk_quota: 1000, memory: 2000, mintime: 36000000, network: 0, ephemeral_state:0, ephemeral_disk:0, always_running:0}. WARNING: some of the values are strings not numbers in the database right now, e.g., disk_quota:"1000".',
207
},
208
site_license: {
209
type: "map",
210
desc: "This is a map that defines upgrades (just when running the project) that come from a site license, and also the licenses that are applied to this project. The format is {license_id:{memory:?, mintime:?, ...}} where the target of the license_id is the same as for the settings field. The license_id is the uuid of the license that contributed these upgrades. To tell cocalc to use a license for a project, a user sets site_license to {license_id:{}}, and when it is requested to start the project, the backend decides what allocation license_id provides and changes the field accordingly, i.e., changes {license_id:{},...} to {license_id:{memory:?,...},...}",
211
},
212
status: {
213
type: "map",
214
desc: "This is a map computed by the status command run inside a project, and slightly enhanced by the compute server, which gives extensive status information about a project. See the exported ProjectStatus interface defined in the code here.",
215
},
216
state: {
217
type: "map",
218
desc: 'Info about the state of this project of the form {error: "", state: "running" (etc), time: timestamp, ip?:"ip address where project is"}, where time is when the state was last computed. See COMPUTE_STATES in the compute-states file for state.state and the ProjectState interface defined below in code.',
219
date: ["time"],
220
},
221
last_edited: {
222
type: "timestamp",
223
desc: "The last time some file was edited in this project. This is the last time that the file_use table was updated for this project.",
224
},
225
last_started: {
226
type: "timestamp",
227
desc: "The last time the project started running.",
228
},
229
last_active: {
230
type: "map",
231
desc: "Map from account_id's to the timestamp of when the user with that account_id touched this project.",
232
date: "all",
233
},
234
created: {
235
type: "timestamp",
236
desc: "When the project was created.",
237
},
238
action_request: {
239
type: "map",
240
desc: "Request state change action for project: {action:['start', 'stop'], started:timestamp, err:?, finished:timestamp}",
241
date: ["started", "finished"],
242
},
243
storage: {
244
type: "map",
245
desc: "(DEPRECATED) This is a map {host:'hostname_of_server', assigned:when first saved here, saved:when last saved here}.",
246
date: ["assigned", "saved"],
247
},
248
last_backup: {
249
type: "timestamp",
250
desc: "(DEPRECATED) Timestamp of last off-disk successful backup using bup to Google cloud storage",
251
},
252
storage_request: {
253
type: "map",
254
desc: "(DEPRECATED) {action:['save', 'close', 'move', 'open'], requested:timestap, pid:?, target:?, started:timestamp, finished:timestamp, err:?}",
255
date: ["started", "finished", "requested"],
256
},
257
course: {
258
type: "map",
259
desc: "{project_id:[id of project that contains .course file], path:[path to .course file], pay:?, payInfo:?, email_address:[optional email address of student -- used if account_id not known], account_id:[account id of student]}, where pay is either not set (or equals falseish) or is a timestamp by which the students must pay. If payInfo is set, it specifies the parameters of the license the students should purchase.",
260
date: ["pay"],
261
},
262
storage_server: {
263
type: "integer",
264
desc: "(DEPRECATED) Number of the Kubernetes storage server with the data for this project: one of 0, 1, 2, ...",
265
},
266
storage_ready: {
267
type: "boolean",
268
desc: "(DEPRECATED) Whether storage is ready to be used on the storage server. Do NOT try to start project until true; this gets set by storage daemon when it notices that run is true.",
269
},
270
disk_size: {
271
type: "integer",
272
desc: "Size in megabytes of the project disk.",
273
},
274
resources: {
275
type: "map",
276
desc: 'Object of the form {requests:{memory:"30Mi",cpu:"5m"}, limits:{memory:"100Mi",cpu:"300m"}} which is passed to the k8s resources section for this pod.',
277
},
278
preemptible: {
279
type: "boolean",
280
desc: "If true, allow to run on preemptible nodes.",
281
},
282
idle_timeout: {
283
type: "integer",
284
desc: "If given and nonzero, project will be killed if it is idle for this many **minutes**, where idle *means* that last_edited has not been updated.",
285
},
286
run_quota: {
287
type: "map",
288
desc: "If project is running, this is the quota that it is running with.",
289
},
290
compute_image: {
291
type: "string",
292
desc: "Specify the name of the underlying (kucalc) compute image.",
293
},
294
addons: {
295
type: "map",
296
desc: "Configure (kucalc specific) addons for projects. (e.g. academic software, license keys, ...)",
297
},
298
lti_id: {
299
type: "array",
300
pg_type: "TEXT[]",
301
desc: "This is a specific ID derived from an LTI context",
302
},
303
lti_data: {
304
type: "map",
305
desc: "extra information related to LTI",
306
},
307
env: {
308
type: "map",
309
desc: "Additional environment variables (TS: {[key:string]:string})",
310
render: { type: "json", editable: true },
311
},
312
sandbox: {
313
type: "boolean",
314
desc: "If set to true, then any user who attempts to access this project is automatically added as a collaborator to it. Only the project owner can change this setting.",
315
render: { type: "boolean", editable: true },
316
},
317
avatar_image_tiny: {
318
title: "Image",
319
type: "string",
320
desc: "tiny (32x32) visual image associated with the project. Suitable to include as part of changefeed, since about 3kb.",
321
render: { type: "image" },
322
},
323
avatar_image_full: {
324
title: "Image",
325
type: "string",
326
desc: "A visual image associated with the project. Could be 150kb. NOT include as part of changefeed of projects, since potentially big (e.g., 200kb x 1000 projects = 200MB!).",
327
render: { type: "image" },
328
},
329
pay_as_you_go_quotas: {
330
type: "map",
331
desc: "Pay as you go quotas that users set so that when they run this project, it gets upgraded to at least what is specified here, and user gets billed later for what is used. Any changes to this table could result in money being spent, so should only be done via the api. This is a map from the account_id of the user that set the quota to the value of the quota spec (which is purchase-quotas.ProjectQuota).",
332
render: { type: "json", editable: false },
333
},
334
notes: NOTES,
335
secret_token: {
336
type: "string",
337
pg_type: "VARCHAR(256)",
338
desc: "Random ephemeral secret token used temporarily by project to authenticate with hub.",
339
},
340
},
341
});
342
343
export interface ApiKeyInfo {
344
name: string;
345
trunc: string;
346
hash?: string;
347
used?: number;
348
}
349
350
// Same query above, but without the last_edited time constraint.
351
schema.projects_all = deep_copy(schema.projects);
352
if (
353
schema.projects_all.user_query?.get == null ||
354
schema.projects.user_query?.get == null
355
) {
356
throw Error("make typescript happy");
357
}
358
schema.projects_all.user_query.get.options = [];
359
schema.projects_all.user_query.get.options_load = [];
360
schema.projects_all.virtual = "projects";
361
schema.projects_all.user_query.get.pg_where = ["projects"];
362
schema.projects_all.user_query.get.pg_where_load = ["projects"];
363
364
// Table that provides extended read info about a single project
365
// but *ONLY* for admin.
366
Table({
367
name: "projects_admin",
368
fields: schema.projects.fields,
369
rules: {
370
primary_key: schema.projects.primary_key,
371
virtual: "projects",
372
user_query: {
373
get: {
374
admin: true, // only admins can do get queries on this table
375
// (without this, users who have read access could read)
376
pg_where: [{ "project_id = $::UUID": "project_id" }],
377
fields: schema.projects.user_query.get.fields,
378
},
379
},
380
},
381
});
382
383
/*
384
Table that enables set queries to the course field of a project. Only
385
project owners are allowed to use this table. The point is that this makes
386
it possible for the owner of the project to set things, but not for the
387
collaborators to set those things.
388
**wARNING:** right now we're not using this since when multiple people add
389
students to a course and the 'course' field doesn't get properly set,
390
much confusion and misery arises.... and it is very hard to fix.
391
In theory a malicous student could not pay via this. But if they could
392
mess with their client, they could easily not pay anyways.
393
*/
394
Table({
395
name: "projects_owner",
396
rules: {
397
virtual: "projects",
398
user_query: {
399
set: {
400
fields: {
401
project_id: "project_owner",
402
course: true,
403
},
404
},
405
},
406
},
407
fields: {
408
project_id: true,
409
course: true,
410
},
411
});
412
413
/*
414
415
Table that enables any signed-in user to set an invite request.
416
Later: we can make an index so that users can see all outstanding requests they have made easily.
417
How to test this from the browser console:
418
project_id = '4e0f5bfd-3f1b-4d7b-9dff-456dcf8725b8' // id of a project you have
419
invite_requests = {}; invite_requests[smc.client.account_id] = {timestamp:new Date(), message:'please invite me'}
420
smc.client.query({cb:console.log, query:{project_invite_requests:{project_id:project_id, invite_requests:invite_requests}}}) // set it
421
smc.redux.getStore('projects').get_project(project_id).invite_requests // see requests for this project
422
423
CURRENTLY NOT USED, but probably will be...
424
425
database._user_set_query_project_invite_requests(old_val, new_val, account_id, cb)
426
For now don't check anything -- this is how we will make it secure later.
427
This will:
428
- that user setting this is signed in
429
- ensure user only modifies their own entry (for their own id).
430
- enforce some hard limit on number of outstanding invites (say 30).
431
- enforce limit on size of invite message.
432
- sanity check on timestamp
433
- with an index as mentioned above we could limit the number of projects
434
to which a single user has requested to be invited.
435
436
*/
437
Table({
438
name: "project_invite_requests",
439
rules: {
440
virtual: "projects",
441
primary_key: "project_id",
442
user_query: {
443
set: {
444
fields: {
445
project_id: true,
446
invite_requests: true,
447
},
448
before_change(_database, _old_val, _new_val, _account_id, cb) {
449
cb();
450
},
451
},
452
},
453
}, // actual function will be database._user...
454
fields: {
455
project_id: true,
456
invite_requests: true,
457
}, // {account_id:{timestamp:?, message:?}, ...}
458
});
459
460
/*
461
Virtual table to get project avatar_images.
462
We don't put this in the main projects table,
463
since we don't want the avatar_image_full to be
464
the projects queries or changefeeds, since it
465
is big, and by default all get fields appear there.
466
*/
467
468
Table({
469
name: "project_avatar_images",
470
rules: {
471
virtual: "projects",
472
primary_key: "project_id",
473
user_query: {
474
get: {
475
pg_where: ["projects"],
476
fields: {
477
project_id: null,
478
avatar_image_full: null,
479
},
480
},
481
},
482
},
483
fields: {
484
project_id: true,
485
avatar_image_full: true,
486
},
487
});
488
489
/*
490
Table to get/set the datastore config in addons.
491
492
The main idea is to set/update/delete entries in the dict addons.datastore.[key] = {...}
493
*/
494
Table({
495
name: "project_datastore",
496
rules: {
497
virtual: "projects",
498
primary_key: "project_id",
499
user_query: {
500
set: {
501
// this also deals with delete requests
502
fields: {
503
project_id: true,
504
addons: true,
505
},
506
async instead_of_change(
507
db,
508
_old_value,
509
new_val,
510
account_id,
511
cb,
512
): Promise<void> {
513
try {
514
// to delete an entry, pretend to set the datastore = {delete: [name]}
515
if (typeof new_val.addons.datastore.delete === "string") {
516
await db.project_datastore_del(
517
account_id,
518
new_val.project_id,
519
new_val.addons.datastore.delete,
520
);
521
cb(undefined);
522
} else {
523
// query should set addons.datastore.[new key] = config, such that we see here
524
// new_val = {"project_id":"...","addons":{"datastore":{"key3":{"type":"xxx", ...}}}}
525
// which will be merged into the existing addons.datastore dict
526
const res = await db.project_datastore_set(
527
account_id,
528
new_val.project_id,
529
new_val.addons.datastore,
530
);
531
cb(undefined, res);
532
}
533
} catch (err) {
534
cb(`${err}`);
535
}
536
},
537
},
538
get: {
539
fields: {
540
project_id: true,
541
addons: true,
542
},
543
async instead_of_query(db, opts, cb): Promise<void> {
544
if (opts.multi) {
545
throw Error("'multi' is not implemented");
546
}
547
try {
548
// important: the config dicts for each key must not expose secret credentials!
549
// check if opts.query.addons === null ?!
550
const data = await db.project_datastore_get(
551
opts.account_id,
552
opts.query.project_id,
553
);
554
cb(undefined, data);
555
} catch (err) {
556
cb(`${err}`);
557
}
558
},
559
},
560
},
561
},
562
fields: {
563
project_id: true,
564
addons: true,
565
},
566
});
567
568
export interface ProjectStatus {
569
"project.pid"?: number; // pid of project server process
570
"hub-server.port"?: number; // port of tcp server that is listening for conn from hub
571
"browser-server.port"?: number; // port listening for http/websocket conn from browser client
572
"sage_server.port"?: number; // port where sage server is listening.
573
"sage_server.pid"?: number; // pid of sage server process
574
start_ts?: number; // timestamp, when project server started
575
session_id?: string; // unique identifyer
576
version?: number; // version number of project code
577
disk_MB?: number; // MB of used disk
578
installed?: boolean; // whether code is installed
579
memory?: {
580
count?: number;
581
pss?: number;
582
rss?: number;
583
swap?: number;
584
uss?: number;
585
}; // output by smem
586
}
587
588
export interface ProjectState {
589
ip?: string; // where the project is running
590
error?: string;
591
state?: State; // running, stopped, etc.
592
time?: Date;
593
}
594
595
Table({
596
name: "crm_projects",
597
fields: schema.projects.fields,
598
rules: {
599
primary_key: schema.projects.primary_key,
600
virtual: "projects",
601
user_query: {
602
get: {
603
admin: true, // only admins can do get queries on this table
604
// (without this, users who have read access could read)
605
pg_where: [],
606
fields: {
607
...schema.projects.user_query?.get?.fields,
608
notes: null,
609
},
610
},
611
set: {
612
admin: true,
613
fields: {
614
project_id: true,
615
name: true,
616
title: true,
617
description: true,
618
deleted: true,
619
notes: true,
620
},
621
},
622
},
623
},
624
});
625
626
export type Datastore = boolean | string[] | undefined;
627
628
// in the future, we might want to extend this to include custom environmment variables
629
export interface EnvVarsRecord {
630
inherit?: boolean;
631
}
632
export type EnvVars = EnvVarsRecord | undefined;
633
634
export interface StudentProjectFunctionality {
635
disableActions?: boolean;
636
disableJupyterToggleReadonly?: boolean;
637
disableJupyterClassicServer?: boolean;
638
disableJupyterClassicMode?: boolean;
639
disableJupyterLabServer?: boolean;
640
disableRServer?: boolean;
641
disableVSCodeServer?: boolean;
642
disableLibrary?: boolean;
643
disableNetworkWarningBanner?: boolean;
644
disablePlutoServer?: boolean;
645
disableTerminals?: boolean;
646
disableUploads?: boolean;
647
disableNetwork?: boolean;
648
disableSSH?: boolean;
649
disableCollaborators?: boolean;
650
disableChatGPT?: boolean;
651
disableSharing?: boolean;
652
}
653
654
export interface CourseInfo {
655
type: "student" | "shared" | "nbgrader";
656
account_id?: string; // account_id of the student that this project is for.
657
project_id: string; // the course project, i.e., project with the .course file
658
path: string; // path to the .course file in project_id
659
pay?: string; // iso timestamp or ""
660
paid?: string; // iso timestamp with *when* they paid.
661
purchase_id?: number; // id of purchase record in purchases table.
662
payInfo?: PurchaseInfo;
663
email_address?: string;
664
datastore: Datastore;
665
student_project_functionality?: StudentProjectFunctionality;
666
envvars?: EnvVars;
667
}
668
669
type ExecOptsCommon = {
670
project_id: string;
671
cb?: Function; // if given use a callback interface *instead* of async.
672
};
673
674
export type ExecOptsBlocking = ExecOptsCommon & {
675
compute_server_id?: number; // if true, run on the compute server (if available)
676
filesystem?: boolean; // run in fileserver container on compute server; otherwise, runs on main compute container.
677
path?: string;
678
command: string;
679
args?: string[];
680
timeout?: number;
681
max_output?: number;
682
bash?: boolean;
683
aggregate?: string | number | { value: string | number };
684
err_on_exit?: boolean;
685
env?: { [key: string]: string }; // custom environment variables.
686
async_call?: ExecuteCodeOptions["async_call"];
687
};
688
689
export type ExecOptsAsync = ExecOptsCommon & {
690
async_get?: ExecuteCodeOptionsAsyncGet["async_get"];
691
async_stats?: ExecuteCodeOptionsAsyncGet["async_stats"];
692
async_await?: ExecuteCodeOptionsAsyncGet["async_await"];
693
};
694
695
export type ExecOpts = ExecOptsBlocking | ExecOptsAsync;
696
697
export function isExecOptsBlocking(opts: unknown): opts is ExecOptsBlocking {
698
return (
699
typeof opts === "object" &&
700
typeof (opts as any).project_id === "string" &&
701
typeof (opts as any).command === "string"
702
);
703
}
704
705
export type ExecOutput = ExecuteCodeOutput & {
706
time: number; // time in ms, from user point of view.
707
};
708
709
export interface CreateProjectOptions {
710
account_id?: string;
711
title?: string;
712
description?: string;
713
// (optional) image ID
714
image?: string;
715
// (optional) license id (or multiple ids separated by commas) -- if given, project will be created with this license
716
license?: string;
717
public_path_id?: string; // may imply use of a license
718
// noPool = do not allow using the pool (e.g., need this when creating projects to put in the pool);
719
// not a real issue since when creating for pool account_id is null, and then we wouldn't use the pool...
720
noPool?: boolean;
721
// start running the moment the project is created -- uses more resources, but possibly better user experience
722
start?: boolean;
723
724
// admins can specify the project_id - nobody else can -- useful for debugging.
725
project_id?: string;
726
}
727
728
interface BaseCopyOptions {
729
target_project_id?: string;
730
target_path?: string; // path into project; if not given, defaults to source path above.
731
overwrite_newer?: boolean; // if true, newer files in target are copied over (otherwise, uses rsync's --update)
732
delete_missing?: boolean; // if true, delete files in dest path not in source, **including** newer files
733
backup?: boolean; // make backup files
734
timeout?: number; // in **seconds**, not milliseconds
735
bwlimit?: number;
736
wait_until_done?: boolean; // by default, wait until done. false only gives the ID to query the status later
737
scheduled?: string | Date; // kucalc only: string (parseable by new Date()), or a Date
738
public?: boolean; // kucalc only: if true, may use the share server files rather than start the source project running
739
exclude?: string[]; // options passed to rsync via --exclude
740
}
741
export interface UserCopyOptions extends BaseCopyOptions {
742
account_id?: string;
743
src_project_id: string;
744
src_path: string;
745
// simulate copy taking at least this long -- useful for dev/debugging.
746
debug_delay_ms?: number;
747
}
748
749
// for copying files within and between projects
750
export interface CopyOptions extends BaseCopyOptions {
751
path: string;
752
}
753
754