hologit 0.43.2 → 0.44.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/index.d.ts +83 -73
- package/lib/Studio.js +141 -126
- package/package.json +2 -3
package/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
declare module 'git-client' {
|
|
2
|
+
export class Git {
|
|
3
|
+
constructor(options: { gitDir: string; workTree?: string });
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
declare module 'hologit' {
|
|
2
8
|
import { Git as GitClient } from 'git-client';
|
|
3
9
|
import { Docker } from 'dockerode';
|
|
@@ -60,15 +66,15 @@ declare module 'hologit' {
|
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
export class Git {
|
|
63
|
-
static
|
|
69
|
+
static get(): Promise<typeof GitClient>;
|
|
64
70
|
constructor(options: GitOptions);
|
|
65
71
|
gitDir: string;
|
|
66
72
|
workTree?: string;
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
export class BlobObject {
|
|
70
|
-
static
|
|
71
|
-
static
|
|
76
|
+
static write(repo: Repo, content: string): Promise<BlobObject>;
|
|
77
|
+
static writeFromFile(repo: Repo, filePath: string): Promise<BlobObject>;
|
|
72
78
|
|
|
73
79
|
constructor(repo: Repo, options: GitObjectOptions);
|
|
74
80
|
|
|
@@ -78,12 +84,12 @@ declare module 'hologit' {
|
|
|
78
84
|
isBlob: boolean;
|
|
79
85
|
type: 'blob';
|
|
80
86
|
|
|
81
|
-
|
|
87
|
+
read(): Promise<string>;
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
export class TreeObject {
|
|
85
91
|
static getEmptyTreeHash(): string;
|
|
86
|
-
static
|
|
92
|
+
static createFromRef(repo: Repo, ref: string): Promise<TreeObject>;
|
|
87
93
|
|
|
88
94
|
constructor(repo: Repo, options?: { hash?: string; parent?: TreeObject | null });
|
|
89
95
|
|
|
@@ -95,19 +101,19 @@ declare module 'hologit' {
|
|
|
95
101
|
type: 'tree';
|
|
96
102
|
mode: '040000';
|
|
97
103
|
|
|
98
|
-
|
|
104
|
+
getHash(): Promise<string>;
|
|
99
105
|
getWrittenHash(): string | null;
|
|
100
106
|
markDirty(): void;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
getChild(childPath: string): Promise<TreeObject | BlobObject | CommitObject | null>;
|
|
108
|
+
writeChild(childPath: string, content: string | BlobObject): Promise<BlobObject>;
|
|
109
|
+
getChildren(): Promise<{ [key: string]: TreeObject | BlobObject | CommitObject }>;
|
|
110
|
+
getBlobMap(): Promise<{ [key: string]: BlobObject }>;
|
|
111
|
+
deleteChild(childPath: string): Promise<void>;
|
|
112
|
+
getSubtree(subtreePath: string, create?: boolean): Promise<TreeObject | null>;
|
|
113
|
+
getSubtreeStack(subtreePath: string, create?: boolean): Promise<TreeObject[] | null>;
|
|
114
|
+
write(): Promise<string>;
|
|
115
|
+
merge(input: TreeObject, options?: MergeOptions, basePath?: string, preloadChildren?: boolean): Promise<void>;
|
|
116
|
+
clone(): Promise<TreeObject>;
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
export class CommitObject {
|
|
@@ -128,10 +134,10 @@ declare module 'hologit' {
|
|
|
128
134
|
|
|
129
135
|
getWorkspace(): Workspace;
|
|
130
136
|
getRepo(): Repo;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
readConfig(): Promise<any>;
|
|
138
|
+
writeConfig(config?: any): Promise<void>;
|
|
139
|
+
getConfig(): Promise<any>;
|
|
140
|
+
getCachedConfig(): Promise<any>;
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
export class Branch extends Configurable {
|
|
@@ -141,17 +147,17 @@ declare module 'hologit' {
|
|
|
141
147
|
|
|
142
148
|
getKind(): 'holobranch';
|
|
143
149
|
getConfigPath(): string;
|
|
144
|
-
|
|
150
|
+
isDefined(): Promise<boolean>;
|
|
145
151
|
getMapping(key: string): Mapping;
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
getMappings(): Promise<Map<string, Mapping>>;
|
|
153
|
+
composite(options: {
|
|
148
154
|
outputTree?: TreeObject;
|
|
149
155
|
fetch?: boolean | string[];
|
|
150
156
|
cacheFrom?: string | null;
|
|
151
157
|
cacheTo?: string | null;
|
|
152
158
|
}): Promise<TreeObject>;
|
|
153
159
|
getLens(name: string): Lens;
|
|
154
|
-
|
|
160
|
+
getLenses(): Promise<Map<string, Lens>>;
|
|
155
161
|
}
|
|
156
162
|
|
|
157
163
|
export class Source extends Configurable {
|
|
@@ -163,21 +169,21 @@ declare module 'hologit' {
|
|
|
163
169
|
|
|
164
170
|
getKind(): 'holosource';
|
|
165
171
|
getConfigPath(): string;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
172
|
+
getSpec(): Promise<{ hash: string; ref: string; data: any }>;
|
|
173
|
+
getCachedSpec(): Promise<{ hash: string; ref: string; data: any }>;
|
|
174
|
+
queryRef(): Promise<{ hash: string; ref: string } | null>;
|
|
175
|
+
hashWorkTree(): Promise<string | null>;
|
|
176
|
+
getOutputTree(options?: {
|
|
171
177
|
working?: boolean | null;
|
|
172
178
|
fetch?: boolean | string[];
|
|
173
179
|
cacheFrom?: string | null;
|
|
174
180
|
cacheTo?: string | null;
|
|
175
181
|
}): Promise<string>;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
182
|
+
getHead(options?: { required?: boolean; working?: boolean | null }): Promise<string | null>;
|
|
183
|
+
getCachedHead(): Promise<string | null>;
|
|
184
|
+
getBranch(): Promise<string | null>;
|
|
185
|
+
fetch(options?: { depth?: number; unshallow?: boolean | null }, ...refs: string[]): Promise<{ refs: string[] }>;
|
|
186
|
+
checkout(options?: { submodule?: boolean }): Promise<{
|
|
181
187
|
path: string;
|
|
182
188
|
head: string;
|
|
183
189
|
branch: string | null;
|
|
@@ -195,13 +201,13 @@ declare module 'hologit' {
|
|
|
195
201
|
|
|
196
202
|
getKind(): 'hololens';
|
|
197
203
|
getConfigPath(): string;
|
|
198
|
-
|
|
199
|
-
|
|
204
|
+
buildInputTree(inputRoot?: TreeObject): Promise<TreeObject>;
|
|
205
|
+
buildSpec(inputTree: TreeObject): Promise<{
|
|
200
206
|
hash: string;
|
|
201
207
|
ref: string;
|
|
202
208
|
data: any;
|
|
203
209
|
}>;
|
|
204
|
-
|
|
210
|
+
executeSpec(specHash: string, options: {
|
|
205
211
|
refresh?: boolean;
|
|
206
212
|
save?: boolean;
|
|
207
213
|
repo?: Repo | null;
|
|
@@ -209,7 +215,7 @@ declare module 'hologit' {
|
|
|
209
215
|
cacheTo?: string | null;
|
|
210
216
|
}): Promise<string>;
|
|
211
217
|
|
|
212
|
-
static
|
|
218
|
+
static executeSpec(specHash: string, options: {
|
|
213
219
|
refresh?: boolean;
|
|
214
220
|
save?: boolean;
|
|
215
221
|
repo?: Repo | null;
|
|
@@ -226,18 +232,18 @@ declare module 'hologit' {
|
|
|
226
232
|
getWorkspace(): Workspace;
|
|
227
233
|
getKind(): 'holospace';
|
|
228
234
|
getConfigPath(): string;
|
|
229
|
-
|
|
235
|
+
writeWorkingChanges(): Promise<void>;
|
|
230
236
|
getBranch(name: string): Branch;
|
|
231
|
-
|
|
237
|
+
getBranches(): Promise<Map<string, Branch>>;
|
|
232
238
|
getSource(name: string): Source;
|
|
233
|
-
|
|
234
|
-
|
|
239
|
+
getSources(): Promise<Map<string, Source>>;
|
|
240
|
+
getLayers(): Promise<Map<string, Map<string, Mapping>>>;
|
|
235
241
|
getLens(name: string): Lens;
|
|
236
|
-
|
|
242
|
+
getLenses(): Promise<Map<string, Lens>>;
|
|
237
243
|
}
|
|
238
244
|
|
|
239
245
|
export class Repo {
|
|
240
|
-
static
|
|
246
|
+
static getFromEnvironment(options?: { ref?: string; working?: boolean }): Promise<Repo>;
|
|
241
247
|
|
|
242
248
|
constructor(options: RepoOptions);
|
|
243
249
|
|
|
@@ -245,27 +251,27 @@ declare module 'hologit' {
|
|
|
245
251
|
ref: string;
|
|
246
252
|
workTree: string | null;
|
|
247
253
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
254
|
+
getWorkspace(): Promise<Workspace>;
|
|
255
|
+
createWorkspaceFromRef(ref: string): Promise<Workspace>;
|
|
256
|
+
createWorkspaceFromTreeHash(hash: string): Promise<Workspace>;
|
|
257
|
+
getGit(): Promise<GitClient>;
|
|
258
|
+
resolveRef(ref?: string | null): Promise<string | null>;
|
|
253
259
|
createBlob(options: GitObjectOptions): BlobObject;
|
|
254
|
-
|
|
255
|
-
|
|
260
|
+
writeBlob(content: string): Promise<BlobObject>;
|
|
261
|
+
writeBlobFromFile(filePath: string): Promise<BlobObject>;
|
|
256
262
|
createTree(options?: { hash?: string; parent?: TreeObject | null }): TreeObject;
|
|
257
|
-
|
|
263
|
+
createTreeFromRef(ref: string): Promise<TreeObject>;
|
|
258
264
|
createCommit(options: GitObjectOptions): CommitObject;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
265
|
+
hasCommit(commit: string): Promise<boolean>;
|
|
266
|
+
hashWorkTree(): Promise<string>;
|
|
267
|
+
watch(options: { callback: (treeHash: string, commitHash?: string) => void }): Promise<{
|
|
262
268
|
watching: Promise<void>;
|
|
263
269
|
cancel: () => void;
|
|
264
270
|
}>;
|
|
265
271
|
}
|
|
266
272
|
|
|
267
273
|
export class Projection {
|
|
268
|
-
static
|
|
274
|
+
static projectBranch(branch: Branch, options?: ProjectionOptions): Promise<string>;
|
|
269
275
|
|
|
270
276
|
constructor(options: { branch: Branch });
|
|
271
277
|
|
|
@@ -273,27 +279,27 @@ declare module 'hologit' {
|
|
|
273
279
|
workspace: Workspace;
|
|
274
280
|
output: Workspace;
|
|
275
281
|
|
|
276
|
-
|
|
282
|
+
composite(options: {
|
|
277
283
|
fetch?: boolean | string[];
|
|
278
284
|
cacheFrom?: string | null;
|
|
279
285
|
cacheTo?: string | null;
|
|
280
286
|
}): Promise<void>;
|
|
281
|
-
|
|
287
|
+
lens(options: {
|
|
282
288
|
cacheFrom?: string | null;
|
|
283
289
|
cacheTo?: string | null;
|
|
284
290
|
}): Promise<void>;
|
|
285
|
-
|
|
291
|
+
commit(ref: string, options?: {
|
|
286
292
|
parentCommit?: string | null;
|
|
287
293
|
commitMessage?: string | null;
|
|
288
294
|
}): Promise<string>;
|
|
289
295
|
}
|
|
290
296
|
|
|
291
297
|
export class Studio {
|
|
292
|
-
static
|
|
293
|
-
static
|
|
294
|
-
static
|
|
295
|
-
static
|
|
296
|
-
static
|
|
298
|
+
static cleanup(): Promise<void>;
|
|
299
|
+
static getHab(): Promise<any>;
|
|
300
|
+
static getDocker(): Promise<Docker>;
|
|
301
|
+
static isEnvironmentStudio(): Promise<boolean>;
|
|
302
|
+
static get(gitDir: string): Promise<Studio>;
|
|
297
303
|
|
|
298
304
|
constructor(options: { gitDir: string; container: any });
|
|
299
305
|
|
|
@@ -301,11 +307,11 @@ declare module 'hologit' {
|
|
|
301
307
|
gitDir: string;
|
|
302
308
|
|
|
303
309
|
isLocal(): boolean;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
310
|
+
habExec(...command: any[]): Promise<string>;
|
|
311
|
+
habPkgExec(pkg: string, bin: string, ...args: any[]): Promise<string>;
|
|
312
|
+
holoExec(...command: any[]): Promise<string>;
|
|
313
|
+
holoLensExec(spec: string): Promise<string>;
|
|
314
|
+
getPackage(query: string, options?: { install?: boolean }): Promise<string | null>;
|
|
309
315
|
}
|
|
310
316
|
|
|
311
317
|
export class Mapping extends Configurable {
|
|
@@ -319,13 +325,17 @@ declare module 'hologit' {
|
|
|
319
325
|
getConfigPath(): string;
|
|
320
326
|
}
|
|
321
327
|
|
|
322
|
-
export class SpecObject
|
|
323
|
-
|
|
328
|
+
export class SpecObject {
|
|
329
|
+
constructor(repo: Repo, options: GitObjectOptions);
|
|
330
|
+
|
|
331
|
+
repo: Repo;
|
|
332
|
+
hash: string;
|
|
333
|
+
isSpec: boolean;
|
|
334
|
+
|
|
335
|
+
static write(repo: Repo, kind: string, data: any): Promise<{
|
|
324
336
|
hash: string;
|
|
325
337
|
ref: string;
|
|
326
338
|
}>;
|
|
327
339
|
static buildRef(kind: string, hash: string): string;
|
|
328
|
-
|
|
329
|
-
isSpec: boolean;
|
|
330
340
|
}
|
|
331
341
|
}
|
package/lib/Studio.js
CHANGED
|
@@ -1,16 +1,54 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
1
2
|
const stream = require('stream');
|
|
2
|
-
const Docker = require('dockerode');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const exitHook = require('async-exit-hook');
|
|
5
5
|
const fs = require('mz/fs');
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
const logger = require('./logger');
|
|
9
8
|
|
|
10
|
-
|
|
11
9
|
const studioCache = new Map();
|
|
12
|
-
let hab
|
|
10
|
+
let hab;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Helper function to execute a Docker CLI command.
|
|
14
|
+
* @param {Array<string>} args - The arguments to pass to the docker command.
|
|
15
|
+
* @param {Object} options - Options for child_process.spawn.
|
|
16
|
+
* @returns {Promise<string>} - Resolves with stdout data.
|
|
17
|
+
*/
|
|
18
|
+
function execDocker(args, options = { $relayStderr: false, $relayStdout: false }) {
|
|
19
|
+
logger.debug(`docker ${args.join(' ')}`);
|
|
20
|
+
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const dockerProcess = spawn('docker', args, { stdio: 'pipe', ...options });
|
|
23
|
+
|
|
24
|
+
if (options.$relayStderr !== false) {
|
|
25
|
+
dockerProcess.stderr.pipe(process.stderr);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (options.$relayStdout !== false) {
|
|
29
|
+
dockerProcess.stdout.pipe(process.stdout);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let stdout = '';
|
|
33
|
+
let stderr = '';
|
|
34
|
+
|
|
35
|
+
dockerProcess.stdout.on('data', (data) => {
|
|
36
|
+
stdout += data.toString();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
dockerProcess.stderr.on('data', (data) => {
|
|
40
|
+
stderr += data.toString();
|
|
41
|
+
});
|
|
13
42
|
|
|
43
|
+
dockerProcess.on('close', (code) => {
|
|
44
|
+
if (code === 0) {
|
|
45
|
+
resolve(stdout.trim());
|
|
46
|
+
} else {
|
|
47
|
+
reject(new Error(stderr.trim()));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
14
52
|
|
|
15
53
|
/**
|
|
16
54
|
* A studio session that can be used to run multiple commands, using chroot if available or docker container
|
|
@@ -23,11 +61,15 @@ class Studio {
|
|
|
23
61
|
for (const [gitDir, studio] of studioCache) {
|
|
24
62
|
const { container } = studio;
|
|
25
63
|
|
|
26
|
-
if (container && container.type
|
|
64
|
+
if (container && container.type !== 'studio') {
|
|
27
65
|
logger.info(`terminating studio container: ${container.id}`);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
66
|
+
try {
|
|
67
|
+
await execDocker(['stop', container.id]);
|
|
68
|
+
await execDocker(['rm', container.id]);
|
|
69
|
+
cleanupCount++;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
logger.error(`Failed to stop/remove container ${container.id}: ${err.message}`);
|
|
72
|
+
}
|
|
31
73
|
}
|
|
32
74
|
|
|
33
75
|
studioCache.delete(gitDir);
|
|
@@ -46,19 +88,6 @@ class Studio {
|
|
|
46
88
|
return hab;
|
|
47
89
|
}
|
|
48
90
|
|
|
49
|
-
static async getDocker () {
|
|
50
|
-
if (!docker) {
|
|
51
|
-
const { DOCKER_HOST: dockerHost } = process.env;
|
|
52
|
-
const dockerHostMatch = dockerHost && dockerHost.match(/^unix:\/\/(\/.*)$/);
|
|
53
|
-
const socketPath = dockerHostMatch ? dockerHostMatch[1] : '/var/run/docker.sock';
|
|
54
|
-
|
|
55
|
-
docker = new Docker({ socketPath });
|
|
56
|
-
logger.info(`connected to docker on: ${socketPath}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return docker;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
91
|
static async isEnvironmentStudio () {
|
|
63
92
|
return Boolean(process.env.STUDIO_TYPE);
|
|
64
93
|
}
|
|
@@ -91,39 +120,9 @@ class Studio {
|
|
|
91
120
|
}
|
|
92
121
|
|
|
93
122
|
|
|
94
|
-
// connect with Docker API
|
|
95
|
-
const docker = await Studio.getDocker();
|
|
96
|
-
|
|
97
|
-
|
|
98
123
|
// pull latest studio container
|
|
99
124
|
try {
|
|
100
|
-
await
|
|
101
|
-
docker.pull('jarvus/hologit-studio:latest', (streamErr, stream) => {
|
|
102
|
-
if (streamErr) {
|
|
103
|
-
reject(streamErr);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
let lastStatus;
|
|
108
|
-
|
|
109
|
-
docker.modem.followProgress(
|
|
110
|
-
stream,
|
|
111
|
-
(err, output) => {
|
|
112
|
-
if (err) {
|
|
113
|
-
reject(err);
|
|
114
|
-
} else {
|
|
115
|
-
resolve(output);
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
event => {
|
|
119
|
-
if (event.status != lastStatus) {
|
|
120
|
-
logger.info(`docker pull: ${event.status}`);
|
|
121
|
-
lastStatus = event.status;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
125
|
+
await execDocker(['pull', 'jarvus/hologit-studio:latest'], { $relayStdout: true, $relayStderr: true });
|
|
127
126
|
} catch (err) {
|
|
128
127
|
logger.error(`failed to pull studio image via docker: ${err.message}`);
|
|
129
128
|
}
|
|
@@ -157,65 +156,70 @@ class Studio {
|
|
|
157
156
|
}
|
|
158
157
|
|
|
159
158
|
|
|
160
|
-
//
|
|
161
|
-
let
|
|
159
|
+
// create studio container
|
|
160
|
+
let containerId;
|
|
161
|
+
let defaultUser;
|
|
162
162
|
try {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// }
|
|
191
|
-
// ]
|
|
192
|
-
// }
|
|
193
|
-
}
|
|
194
|
-
});
|
|
163
|
+
// Prepare environment variables
|
|
164
|
+
const envArgs = [
|
|
165
|
+
'--env', 'STUDIO_TYPE=holo',
|
|
166
|
+
'--env', 'GIT_DIR=/git',
|
|
167
|
+
'--env', 'GIT_WORK_TREE=/hab/cache',
|
|
168
|
+
'--env', `DEBUG=${process.env.DEBUG || ''}`,
|
|
169
|
+
'--env', 'HAB_LICENSE=accept-no-persist'
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
// Prepare volume bindings
|
|
173
|
+
const volumeArgs = [];
|
|
174
|
+
for (const bind of bindsConfig) {
|
|
175
|
+
volumeArgs.push('-v', bind);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Create container
|
|
179
|
+
const createArgs = [
|
|
180
|
+
'create',
|
|
181
|
+
'--label', 'sh.holo.studio=yes',
|
|
182
|
+
'--workdir', '/git',
|
|
183
|
+
...envArgs,
|
|
184
|
+
...volumeArgs,
|
|
185
|
+
'jarvus/hologit-studio:latest'
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
containerId = await execDocker(createArgs);
|
|
189
|
+
containerId = containerId.split('\n').pop().trim(); // Get the container ID from output
|
|
195
190
|
|
|
196
191
|
logger.info('starting studio container');
|
|
197
|
-
await
|
|
192
|
+
await execDocker(['start', containerId]);
|
|
198
193
|
|
|
199
194
|
const { uid, gid, username } = os.userInfo();
|
|
200
195
|
|
|
201
196
|
if (uid && gid && username) {
|
|
202
197
|
logger.info(`configuring container to use user: ${username}`);
|
|
203
|
-
await containerExec(
|
|
204
|
-
await containerExec(
|
|
205
|
-
await containerExec(
|
|
206
|
-
|
|
198
|
+
await containerExec({ id: containerId }, 'adduser', '-u', `${uid}`, '-G', 'developer', '-D', username);
|
|
199
|
+
await containerExec({ id: containerId }, 'mkdir', '-p', `/home/${username}/.hab`);
|
|
200
|
+
await containerExec({ id: containerId }, 'ln', '-sf', '/hab/cache', `/home/${username}/.hab/`);
|
|
201
|
+
if (!artifactCachePath) await containerExec({ id: containerId }, 'chown', '-R', `${uid}:${gid}`, '/hab/cache');
|
|
202
|
+
defaultUser = `${uid}`;
|
|
207
203
|
}
|
|
208
204
|
|
|
209
|
-
const studio = new Studio({ gitDir, container });
|
|
205
|
+
const studio = new Studio({ gitDir, container: { id: containerId, defaultUser } });
|
|
210
206
|
studioCache.set(gitDir, studio);
|
|
211
207
|
return studio;
|
|
212
208
|
|
|
213
209
|
} catch (err) {
|
|
214
|
-
logger.error(
|
|
210
|
+
logger.error(`container failed: ${err.message}`);
|
|
215
211
|
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
|
|
212
|
+
if (containerId) {
|
|
213
|
+
try {
|
|
214
|
+
await execDocker(['stop', containerId]);
|
|
215
|
+
} catch (stopErr) {
|
|
216
|
+
logger.error(`Failed to stop container ${containerId}: ${stopErr.message}`);
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
await execDocker(['rm', containerId]);
|
|
220
|
+
} catch (rmErr) {
|
|
221
|
+
logger.error(`Failed to remove container ${containerId}: ${rmErr.message}`);
|
|
222
|
+
}
|
|
219
223
|
}
|
|
220
224
|
}
|
|
221
225
|
}
|
|
@@ -227,14 +231,14 @@ class Studio {
|
|
|
227
231
|
}
|
|
228
232
|
|
|
229
233
|
isLocal () {
|
|
230
|
-
return this.container.type
|
|
234
|
+
return this.container.type === 'studio';
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
/**
|
|
234
238
|
* Run a command in the studio
|
|
235
239
|
*/
|
|
236
240
|
async habExec (...command) {
|
|
237
|
-
const options = typeof command[command.length-1]
|
|
241
|
+
const options = typeof command[command.length-1] === 'object'
|
|
238
242
|
? command.pop()
|
|
239
243
|
: {};
|
|
240
244
|
|
|
@@ -274,7 +278,7 @@ class Studio {
|
|
|
274
278
|
// $env: { PATH }
|
|
275
279
|
// }
|
|
276
280
|
// );
|
|
277
|
-
if (logger.level
|
|
281
|
+
if (logger.level === 'debug') {
|
|
278
282
|
command.unshift('--debug');
|
|
279
283
|
}
|
|
280
284
|
|
|
@@ -286,55 +290,66 @@ class Studio {
|
|
|
286
290
|
}
|
|
287
291
|
|
|
288
292
|
async getPackage (query, { install } = { install: false }) {
|
|
289
|
-
let packagePath
|
|
293
|
+
let packagePath;
|
|
294
|
+
try {
|
|
295
|
+
packagePath = await this.habExec('pkg', 'path', query, { $nullOnError: true, $relayStderr: false });
|
|
296
|
+
} catch (err) {
|
|
297
|
+
packagePath = null;
|
|
298
|
+
}
|
|
290
299
|
|
|
291
300
|
if (!packagePath && install) {
|
|
292
301
|
await this.habExec('pkg', 'install', query);
|
|
293
|
-
|
|
302
|
+
try {
|
|
303
|
+
packagePath = await this.habExec('pkg', 'path', query);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
packagePath = null;
|
|
306
|
+
}
|
|
294
307
|
}
|
|
295
308
|
|
|
296
309
|
return packagePath ? packagePath.substr(10) : null;
|
|
297
310
|
}
|
|
298
311
|
}
|
|
299
312
|
|
|
300
|
-
|
|
301
313
|
exitHook(callback => Studio.cleanup().then(callback));
|
|
302
314
|
|
|
303
|
-
|
|
315
|
+
/**
|
|
316
|
+
* Executes a command inside the specified Docker container.
|
|
317
|
+
* @param {Object} container - The container object containing at least the `id` and optionally `defaultUser`.
|
|
318
|
+
* @param {...string} command - The command and its arguments to execute.
|
|
319
|
+
* @returns {Promise<string>} - Resolves with the command's stdout output.
|
|
320
|
+
*/
|
|
304
321
|
async function containerExec (container, ...command) {
|
|
305
|
-
const options = typeof command[command.length-1]
|
|
322
|
+
const options = typeof command[command.length-1] === 'object'
|
|
306
323
|
? command.pop()
|
|
307
324
|
: {};
|
|
308
325
|
|
|
309
326
|
logger.info(`studio-exec: ${command.join(' ')}`);
|
|
310
327
|
|
|
311
|
-
const
|
|
328
|
+
const execArgs = ['exec'];
|
|
329
|
+
|
|
330
|
+
if (options.$user) {
|
|
331
|
+
execArgs.push('--user', options.$user);
|
|
332
|
+
} else if (container.defaultUser) {
|
|
333
|
+
execArgs.push('--user', container.defaultUser);
|
|
334
|
+
}
|
|
335
|
+
|
|
312
336
|
if (options.$env) {
|
|
313
|
-
for (const key of Object.
|
|
314
|
-
|
|
337
|
+
for (const [key, value] of Object.entries(options.$env)) {
|
|
338
|
+
execArgs.push('--env', `${key}=${value}`);
|
|
315
339
|
}
|
|
316
340
|
}
|
|
317
341
|
|
|
318
|
-
|
|
319
|
-
Cmd: command,
|
|
320
|
-
AttachStdout: true,
|
|
321
|
-
AttachStderr: options.$relayStderr !== false,
|
|
322
|
-
Env: env,
|
|
323
|
-
User: `${container.defaultUser || options.$user || ''}`
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
const execStream = await exec.start();
|
|
342
|
+
execArgs.push(container.id, ...command);
|
|
327
343
|
|
|
328
|
-
|
|
329
|
-
const output =
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
344
|
+
try {
|
|
345
|
+
const output = await execDocker(execArgs, { $relayStdout: true, $relayStderr: true });
|
|
346
|
+
return output;
|
|
347
|
+
} catch (err) {
|
|
348
|
+
if (options.$nullOnError) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
throw err;
|
|
352
|
+
}
|
|
337
353
|
}
|
|
338
354
|
|
|
339
|
-
|
|
340
355
|
module.exports = Studio;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hologit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"description": "Hologit automates the projection of layered composite file trees based on flat, declarative plans",
|
|
5
5
|
"repository": "https://github.com/EmergencePlatform/hologit",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -12,9 +12,8 @@
|
|
|
12
12
|
"@iarna/toml": "^2.2.5",
|
|
13
13
|
"async-exit-hook": "^2.0.1",
|
|
14
14
|
"axios": "^1.7.7",
|
|
15
|
-
"chokidar": "^
|
|
15
|
+
"chokidar": "^4.0.1",
|
|
16
16
|
"debounce": "^2.0.0",
|
|
17
|
-
"dockerode": "^4.0.2",
|
|
18
17
|
"fb-watchman": "^2.0.1",
|
|
19
18
|
"git-client": "^1.8.3",
|
|
20
19
|
"hab-client": "^1.1.3",
|