@git.zone/cli 3.2.1 → 5.0.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/.smartconfig.json +0 -1
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/classes.gitzoneconfig.js +1 -1
- package/dist_ts/classes.project.js +1 -1
- package/dist_ts/gitzone.cli.js +1 -1
- package/dist_ts/gitzone.logging.js +1 -1
- package/dist_ts/helpers.changelog.js +1 -1
- package/dist_ts/helpers.climode.js +1 -1
- package/dist_ts/helpers.smartconfig.js +61 -11
- package/dist_ts/helpers.smartconfigmigrations.js +1 -1
- package/dist_ts/helpers.tsdocker.js +1 -1
- package/dist_ts/helpers.workflow.d.ts +0 -2
- package/dist_ts/helpers.workflow.js +23 -12
- package/dist_ts/index.js +1 -1
- package/dist_ts/mod_audit/index.js +1 -1
- package/dist_ts/mod_commit/index.js +1 -1
- package/dist_ts/mod_commit/mod.helpers.d.ts +3 -3
- package/dist_ts/mod_commit/mod.helpers.js +10 -10
- package/dist_ts/mod_commit/mod.plugins.js +1 -1
- package/dist_ts/mod_commit/mod.ui.js +1 -1
- package/dist_ts/mod_config/classes.commitconfig.js +1 -1
- package/dist_ts/mod_config/classes.releaseconfig.js +1 -1
- package/dist_ts/mod_config/index.js +23 -24
- package/dist_ts/mod_config/mod.plugins.js +1 -1
- package/dist_ts/mod_deprecate/index.js +1 -1
- package/dist_ts/mod_deprecate/mod.plugins.js +1 -1
- package/dist_ts/mod_docker/classes.dockerpruner.js +7 -1
- package/dist_ts/mod_docker/index.js +1 -1
- package/dist_ts/mod_docker/mod.plugins.js +1 -1
- package/dist_ts/mod_format/classes.baseformatter.js +1 -1
- package/dist_ts/mod_format/classes.diffreporter.js +1 -1
- package/dist_ts/mod_format/classes.formatcontext.js +1 -1
- package/dist_ts/mod_format/classes.formatplanner.js +1 -1
- package/dist_ts/mod_format/classes.formatstats.js +1 -1
- package/dist_ts/mod_format/formatters/assets.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/cleanup.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/copy.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/gitignore.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/license.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/packagejson.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/prettier.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/readme.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/smartconfig.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/templates.formatter.js +1 -1
- package/dist_ts/mod_format/formatters/tsconfig.formatter.js +1 -1
- package/dist_ts/mod_format/index.js +1 -1
- package/dist_ts/mod_format/interfaces.format.js +1 -1
- package/dist_ts/mod_format/mod.plugins.js +1 -1
- package/dist_ts/mod_helpers/index.js +1 -1
- package/dist_ts/mod_helpers/mod.plugins.js +1 -1
- package/dist_ts/mod_meta/index.js +1 -1
- package/dist_ts/mod_meta/meta.classes.meta.js +1 -1
- package/dist_ts/mod_meta/meta.interfaces.js +1 -1
- package/dist_ts/mod_meta/meta.plugins.js +1 -1
- package/dist_ts/mod_open/index.js +1 -1
- package/dist_ts/mod_open/mod.plugins.js +1 -1
- package/dist_ts/mod_release/helpers.releasebranch.d.ts +46 -0
- package/dist_ts/mod_release/helpers.releasebranch.js +579 -0
- package/dist_ts/mod_release/index.d.ts +10 -0
- package/dist_ts/mod_release/index.js +300 -92
- package/dist_ts/mod_release/mod.plugins.js +1 -1
- package/dist_ts/mod_services/classes.dockercontainer.d.ts +50 -3
- package/dist_ts/mod_services/classes.dockercontainer.js +646 -84
- package/dist_ts/mod_services/classes.globalregistry.d.ts +10 -24
- package/dist_ts/mod_services/classes.globalregistry.js +210 -62
- package/dist_ts/mod_services/classes.globalregistrystore.d.ts +48 -0
- package/dist_ts/mod_services/classes.globalregistrystore.js +156 -0
- package/dist_ts/mod_services/classes.interprocesslock.d.ts +21 -0
- package/dist_ts/mod_services/classes.interprocesslock.js +141 -0
- package/dist_ts/mod_services/classes.serviceconfiguration.d.ts +15 -12
- package/dist_ts/mod_services/classes.serviceconfiguration.js +188 -91
- package/dist_ts/mod_services/classes.servicedatamarker.d.ts +32 -7
- package/dist_ts/mod_services/classes.servicedatamarker.js +145 -49
- package/dist_ts/mod_services/classes.servicemanager.d.ts +70 -28
- package/dist_ts/mod_services/classes.servicemanager.js +995 -421
- package/dist_ts/mod_services/classes.servicenames.d.ts +8 -0
- package/dist_ts/mod_services/classes.servicenames.js +54 -0
- package/dist_ts/mod_services/classes.serviceoptions.js +1 -1
- package/dist_ts/mod_services/classes.servicepruner.d.ts +3 -0
- package/dist_ts/mod_services/classes.servicepruner.js +208 -46
- package/dist_ts/mod_services/helpers.js +1 -1
- package/dist_ts/mod_services/index.d.ts +2 -0
- package/dist_ts/mod_services/index.js +81 -58
- package/dist_ts/mod_services/mod.plugins.js +1 -1
- package/dist_ts/mod_standard/index.js +2 -2
- package/dist_ts/mod_standard/mod.plugins.js +1 -1
- package/dist_ts/mod_start/index.js +1 -1
- package/dist_ts/mod_start/mod.plugins.js +1 -1
- package/dist_ts/mod_template/index.js +1 -1
- package/dist_ts/mod_template/mod.plugins.js +1 -1
- package/dist_ts/mod_tools/classes.packagemanager.js +1 -1
- package/dist_ts/mod_tools/index.js +1 -1
- package/dist_ts/mod_tools/mod.plugins.js +1 -1
- package/dist_ts/paths.js +1 -1
- package/dist_ts/plugins.d.ts +3 -1
- package/dist_ts/plugins.js +4 -2
- package/dist_ts_migration/001.service-selection.d.ts +9 -0
- package/dist_ts_migration/001.service-selection.js +51 -0
- package/dist_ts_migration/002.runtime-config.d.ts +19 -0
- package/dist_ts_migration/002.runtime-config.js +170 -0
- package/dist_ts_migration/003.service-data-marker.d.ts +9 -0
- package/dist_ts_migration/003.service-data-marker.js +156 -0
- package/dist_ts_migration/004.global-registry.d.ts +13 -0
- package/dist_ts_migration/004.global-registry.js +165 -0
- package/dist_ts_migration/index.d.ts +11 -0
- package/dist_ts_migration/index.js +59 -0
- package/package.json +6 -3
- package/readme.hints.md +102 -25
- package/readme.md +144 -41
- package/readme.plan.md +29 -16
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/helpers.smartconfig.ts +63 -10
- package/ts/helpers.workflow.ts +41 -12
- package/ts/mod_commit/mod.helpers.ts +12 -8
- package/ts/mod_config/index.ts +22 -23
- package/ts/mod_docker/classes.dockerpruner.ts +6 -0
- package/ts/mod_release/helpers.releasebranch.ts +1282 -0
- package/ts/mod_release/index.ts +608 -129
- package/ts/mod_services/classes.dockercontainer.ts +912 -96
- package/ts/mod_services/classes.globalregistry.ts +290 -89
- package/ts/mod_services/classes.globalregistrystore.ts +279 -0
- package/ts/mod_services/classes.interprocesslock.ts +189 -0
- package/ts/mod_services/classes.serviceconfiguration.ts +215 -108
- package/ts/mod_services/classes.servicedatamarker.ts +210 -52
- package/ts/mod_services/classes.servicemanager.ts +1332 -512
- package/ts/mod_services/classes.servicenames.ts +76 -0
- package/ts/mod_services/classes.servicepruner.ts +310 -52
- package/ts/mod_services/index.ts +89 -62
- package/ts/mod_standard/index.ts +1 -1
- package/ts/plugins.ts +4 -0
- package/ts_migration/001.service-selection.ts +77 -0
- package/ts_migration/002.runtime-config.ts +218 -0
- package/ts_migration/003.service-data-marker.ts +216 -0
- package/ts_migration/004.global-registry.ts +272 -0
- package/ts_migration/index.ts +83 -0
|
@@ -1,39 +1,40 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
|
-
import { DockerContainer } from './classes.dockercontainer.js';
|
|
2
|
+
import { DockerContainer, type IContainerInspectInfo } from './classes.dockercontainer.js';
|
|
3
3
|
import { logger } from '../gitzone.logging.js';
|
|
4
|
+
import {
|
|
5
|
+
getServiceContainerLabels,
|
|
6
|
+
getServiceDataDirectory,
|
|
7
|
+
hasExactServiceContainerLabels,
|
|
8
|
+
serviceNames,
|
|
9
|
+
type TServiceName,
|
|
10
|
+
} from './classes.servicedatamarker.js';
|
|
11
|
+
import { withServiceDataOperationLock } from './classes.interprocesslock.js';
|
|
12
|
+
import {
|
|
13
|
+
GlobalRegistryStore,
|
|
14
|
+
assertCanonicalGlobalRegistryData,
|
|
15
|
+
type IGlobalRegistryData,
|
|
16
|
+
type IRegisteredProject,
|
|
17
|
+
} from './classes.globalregistrystore.js';
|
|
4
18
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
minio?: string;
|
|
11
|
-
elasticsearch?: string;
|
|
12
|
-
};
|
|
13
|
-
ports: {
|
|
14
|
-
mongo?: number;
|
|
15
|
-
s3?: number;
|
|
16
|
-
s3Console?: number;
|
|
17
|
-
elasticsearch?: number;
|
|
18
|
-
};
|
|
19
|
-
enabledServices: string[];
|
|
20
|
-
lastActive: number;
|
|
21
|
-
}
|
|
19
|
+
export { assertCanonicalGlobalRegistryData } from './classes.globalregistrystore.js';
|
|
20
|
+
export type {
|
|
21
|
+
IGlobalRegistryData,
|
|
22
|
+
IRegisteredProject,
|
|
23
|
+
} from './classes.globalregistrystore.js';
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
const registryServiceNames: Record<string, TServiceName> = {
|
|
26
|
+
mongo: 'mongodb',
|
|
27
|
+
objectstorage: 'objectstorage',
|
|
28
|
+
elasticsearch: 'elasticsearch',
|
|
29
|
+
};
|
|
26
30
|
|
|
27
31
|
export class GlobalRegistry {
|
|
28
32
|
private static instance: GlobalRegistry | null = null;
|
|
29
|
-
private
|
|
33
|
+
private store: GlobalRegistryStore;
|
|
30
34
|
private docker: DockerContainer;
|
|
31
35
|
|
|
32
|
-
private constructor() {
|
|
33
|
-
this.
|
|
34
|
-
typeArg: 'userHomeDir',
|
|
35
|
-
identityArg: 'gitzone-services',
|
|
36
|
-
});
|
|
36
|
+
private constructor(storeArg: GlobalRegistryStore = new GlobalRegistryStore()) {
|
|
37
|
+
this.store = storeArg;
|
|
37
38
|
this.docker = new DockerContainer();
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -51,49 +52,84 @@ export class GlobalRegistry {
|
|
|
51
52
|
* Register or update a project in the global registry
|
|
52
53
|
*/
|
|
53
54
|
public async registerProject(data: Omit<IRegisteredProject, 'lastActive'>): Promise<void> {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
const projectPath = plugins.path.resolve(data.projectPath);
|
|
56
|
+
await this.store.transact((rawData) => {
|
|
57
|
+
const allData = assertCanonicalGlobalRegistryData(rawData);
|
|
58
|
+
const projects = { ...allData.projects };
|
|
59
|
+
const existingLegacy = projects[projectPath]?.legacy;
|
|
60
|
+
projects[projectPath] = {
|
|
61
|
+
...data,
|
|
62
|
+
projectPath,
|
|
63
|
+
...(existingLegacy ? { legacy: existingLegacy } : {}),
|
|
64
|
+
lastActive: Date.now(),
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
result: undefined,
|
|
68
|
+
writeData: { schemaVersion: 2, projects },
|
|
69
|
+
};
|
|
70
|
+
});
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
/**
|
|
66
74
|
* Remove a project from the registry
|
|
67
75
|
*/
|
|
68
|
-
public async unregisterProject(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
public async unregisterProject(
|
|
77
|
+
projectPathArg: string,
|
|
78
|
+
expectedProjectArg: IRegisteredProject,
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
const projectPath = plugins.path.resolve(projectPathArg);
|
|
81
|
+
await this.store.transact((rawData) => {
|
|
82
|
+
const allData = assertCanonicalGlobalRegistryData(rawData);
|
|
83
|
+
const projects = { ...allData.projects };
|
|
84
|
+
const currentProject = projects[projectPath];
|
|
85
|
+
if (!currentProject) {
|
|
86
|
+
return { result: undefined };
|
|
87
|
+
}
|
|
88
|
+
if (JSON.stringify(currentProject) !== JSON.stringify(expectedProjectArg)) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Refusing to unregister changed global registry entry: ${projectPath}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
73
93
|
delete projects[projectPath];
|
|
74
|
-
|
|
75
|
-
|
|
94
|
+
return {
|
|
95
|
+
result: undefined,
|
|
96
|
+
writeData: { schemaVersion: 2, projects },
|
|
97
|
+
};
|
|
98
|
+
});
|
|
76
99
|
}
|
|
77
100
|
|
|
78
101
|
/**
|
|
79
102
|
* Update the lastActive timestamp for a project
|
|
80
103
|
*/
|
|
81
104
|
public async touchProject(projectPath: string): Promise<void> {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
projects[
|
|
87
|
-
|
|
88
|
-
|
|
105
|
+
const resolvedProjectPath = plugins.path.resolve(projectPath);
|
|
106
|
+
await this.store.transact((rawData) => {
|
|
107
|
+
const allData = assertCanonicalGlobalRegistryData(rawData);
|
|
108
|
+
const projects = { ...allData.projects };
|
|
109
|
+
const existing = projects[resolvedProjectPath];
|
|
110
|
+
if (!existing) {
|
|
111
|
+
return { result: undefined };
|
|
112
|
+
}
|
|
113
|
+
projects[resolvedProjectPath] = {
|
|
114
|
+
...existing,
|
|
115
|
+
lastActive: Date.now(),
|
|
116
|
+
};
|
|
117
|
+
return {
|
|
118
|
+
result: undefined,
|
|
119
|
+
writeData: { schemaVersion: 2, projects },
|
|
120
|
+
};
|
|
121
|
+
});
|
|
89
122
|
}
|
|
90
123
|
|
|
91
124
|
/**
|
|
92
125
|
* Get all registered projects
|
|
93
126
|
*/
|
|
94
127
|
public async getAllProjects(): Promise<{ [path: string]: IRegisteredProject }> {
|
|
95
|
-
|
|
96
|
-
|
|
128
|
+
return (await this.readCanonicalData()).projects;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private async readCanonicalData(): Promise<IGlobalRegistryData> {
|
|
132
|
+
return this.store.readCanonical();
|
|
97
133
|
}
|
|
98
134
|
|
|
99
135
|
/**
|
|
@@ -101,7 +137,7 @@ export class GlobalRegistry {
|
|
|
101
137
|
*/
|
|
102
138
|
public async isRegistered(projectPath: string): Promise<boolean> {
|
|
103
139
|
const projects = await this.getAllProjects();
|
|
104
|
-
return !!projects[projectPath];
|
|
140
|
+
return !!projects[plugins.path.resolve(projectPath)];
|
|
105
141
|
}
|
|
106
142
|
|
|
107
143
|
/**
|
|
@@ -111,7 +147,11 @@ export class GlobalRegistry {
|
|
|
111
147
|
Array<{
|
|
112
148
|
projectPath: string;
|
|
113
149
|
projectName: string;
|
|
114
|
-
containers: Array<{
|
|
150
|
+
containers: Array<{
|
|
151
|
+
name: string;
|
|
152
|
+
status: string;
|
|
153
|
+
ownership?: 'owned' | 'conflict';
|
|
154
|
+
}>;
|
|
115
155
|
lastActive: number;
|
|
116
156
|
}>
|
|
117
157
|
> {
|
|
@@ -119,15 +159,48 @@ export class GlobalRegistry {
|
|
|
119
159
|
const result: Array<{
|
|
120
160
|
projectPath: string;
|
|
121
161
|
projectName: string;
|
|
122
|
-
containers: Array<{
|
|
162
|
+
containers: Array<{
|
|
163
|
+
name: string;
|
|
164
|
+
status: string;
|
|
165
|
+
ownership?: 'owned' | 'conflict';
|
|
166
|
+
}>;
|
|
123
167
|
lastActive: number;
|
|
124
168
|
}> = [];
|
|
125
169
|
|
|
126
170
|
for (const [path, project] of Object.entries(projects)) {
|
|
127
|
-
const containerStatuses: Array<{
|
|
171
|
+
const containerStatuses: Array<{
|
|
172
|
+
name: string;
|
|
173
|
+
status: string;
|
|
174
|
+
ownership?: 'owned' | 'conflict';
|
|
175
|
+
}> = [];
|
|
128
176
|
|
|
129
|
-
for (const containerName of Object.
|
|
177
|
+
for (const [serviceKey, containerName] of Object.entries(project.containers)) {
|
|
130
178
|
if (containerName) {
|
|
179
|
+
if (serviceKey === 'objectstorage') {
|
|
180
|
+
const objectStorage = await this.inspectRegisteredContainer(
|
|
181
|
+
project,
|
|
182
|
+
containerName,
|
|
183
|
+
);
|
|
184
|
+
if (!objectStorage) {
|
|
185
|
+
containerStatuses.push({ name: containerName, status: 'not_exists' });
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const expectedLabels = getServiceContainerLabels({
|
|
189
|
+
projectPath: project.projectPath,
|
|
190
|
+
service: 'objectstorage',
|
|
191
|
+
dataPath: getServiceDataDirectory(project.projectPath, 'objectstorage'),
|
|
192
|
+
});
|
|
193
|
+
const owned = hasExactServiceContainerLabels(
|
|
194
|
+
objectStorage.labels,
|
|
195
|
+
expectedLabels,
|
|
196
|
+
);
|
|
197
|
+
containerStatuses.push({
|
|
198
|
+
name: containerName,
|
|
199
|
+
status: objectStorage.state,
|
|
200
|
+
ownership: owned ? 'owned' : 'conflict',
|
|
201
|
+
});
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
131
204
|
const status = await this.docker.getStatus(containerName);
|
|
132
205
|
containerStatuses.push({ name: containerName, status });
|
|
133
206
|
}
|
|
@@ -153,13 +226,75 @@ export class GlobalRegistry {
|
|
|
153
226
|
const failed: string[] = [];
|
|
154
227
|
|
|
155
228
|
for (const project of Object.values(projects)) {
|
|
156
|
-
for (const containerName of Object.
|
|
229
|
+
for (const [serviceKey, containerName] of Object.entries(project.containers)) {
|
|
157
230
|
if (containerName) {
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
231
|
+
const service = registryServiceNames[serviceKey];
|
|
232
|
+
if (!service) {
|
|
233
|
+
failed.push(containerName);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
await withServiceDataOperationLock(project.projectPath, service, async () => {
|
|
238
|
+
const currentProjects = await this.getAllProjects();
|
|
239
|
+
const claims: Array<{ projectPath: string; service: TServiceName }> = [];
|
|
240
|
+
for (const [registeredPathArg, registeredProjectArg] of Object.entries(
|
|
241
|
+
currentProjects,
|
|
242
|
+
)) {
|
|
243
|
+
for (const [registeredServiceKeyArg, registeredNameArg] of Object.entries(
|
|
244
|
+
registeredProjectArg.containers,
|
|
245
|
+
)) {
|
|
246
|
+
const registeredService = registryServiceNames[registeredServiceKeyArg];
|
|
247
|
+
if (registeredService && registeredNameArg === containerName) {
|
|
248
|
+
claims.push({
|
|
249
|
+
projectPath: plugins.path.resolve(registeredPathArg),
|
|
250
|
+
service: registeredService,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const projectPath = plugins.path.resolve(project.projectPath);
|
|
256
|
+
const currentProject = Object.entries(currentProjects).find(
|
|
257
|
+
([registeredPathArg]) =>
|
|
258
|
+
plugins.path.resolve(registeredPathArg) === projectPath,
|
|
259
|
+
)?.[1];
|
|
260
|
+
if (!currentProject || currentProject.containers[serviceKey] !== containerName) {
|
|
261
|
+
throw new Error('Registry container claim changed before stop');
|
|
262
|
+
}
|
|
263
|
+
const inspection = await this.inspectRegisteredContainer(
|
|
264
|
+
currentProject,
|
|
265
|
+
containerName,
|
|
266
|
+
);
|
|
267
|
+
if (!inspection) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const expectedLabels = getServiceContainerLabels({
|
|
271
|
+
projectPath,
|
|
272
|
+
service,
|
|
273
|
+
dataPath: getServiceDataDirectory(projectPath, service),
|
|
274
|
+
});
|
|
275
|
+
const labelOwned = hasExactServiceContainerLabels(
|
|
276
|
+
inspection.labels,
|
|
277
|
+
expectedLabels,
|
|
278
|
+
);
|
|
279
|
+
const hasGitZoneLabels = Object.keys(inspection.labels).some((keyArg) =>
|
|
280
|
+
keyArg.startsWith('git.zone.'),
|
|
281
|
+
);
|
|
282
|
+
const registryOwned =
|
|
283
|
+
service !== 'objectstorage' &&
|
|
284
|
+
!hasGitZoneLabels &&
|
|
285
|
+
claims.length === 1 &&
|
|
286
|
+
claims[0].projectPath === projectPath &&
|
|
287
|
+
claims[0].service === service;
|
|
288
|
+
if (!labelOwned && !registryOwned) {
|
|
289
|
+
throw new Error('Container ownership conflict');
|
|
290
|
+
}
|
|
291
|
+
if (inspection.running) {
|
|
292
|
+
await this.docker.execArgv(['stop', inspection.id], { timeoutMs: 60_000 });
|
|
293
|
+
stopped.push(containerName);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
} catch {
|
|
297
|
+
if (!failed.includes(containerName)) {
|
|
163
298
|
failed.push(containerName);
|
|
164
299
|
}
|
|
165
300
|
}
|
|
@@ -170,6 +305,41 @@ export class GlobalRegistry {
|
|
|
170
305
|
return { stopped, failed };
|
|
171
306
|
}
|
|
172
307
|
|
|
308
|
+
private async inspectRegisteredContainer(
|
|
309
|
+
projectArg: IRegisteredProject,
|
|
310
|
+
containerNameArg: string,
|
|
311
|
+
): Promise<IContainerInspectInfo | undefined> {
|
|
312
|
+
const ids = await this.docker.listIds([`name=^/${containerNameArg}$`]);
|
|
313
|
+
if (ids.length === 0) {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
const inspections = (await this.docker.inspectMany(ids)).filter(
|
|
317
|
+
(inspection) => inspection.name === containerNameArg,
|
|
318
|
+
);
|
|
319
|
+
if (inspections.length !== 1) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
`Docker returned ambiguous ObjectStorage identity for ${projectArg.projectPath}.`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
return inspections[0];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private async exactContainerExists(containerNameArg: string): Promise<boolean> {
|
|
328
|
+
const ids = await this.docker.listIds([`name=^/${containerNameArg}$`]);
|
|
329
|
+
if (ids.length === 0) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
const inspections = (await this.docker.inspectMany(ids)).filter(
|
|
333
|
+
(inspection) => inspection.name === containerNameArg,
|
|
334
|
+
);
|
|
335
|
+
if (inspections.length !== 1) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
`Docker returned ambiguous container identity for ${containerNameArg}.`,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
|
|
173
343
|
/**
|
|
174
344
|
* Cheap staleness summary for a non-blocking hint after ordinary commands.
|
|
175
345
|
*
|
|
@@ -205,38 +375,69 @@ export class GlobalRegistry {
|
|
|
205
375
|
const removed: string[] = [];
|
|
206
376
|
const kept: Array<{ projectPath: string; reason: string }> = [];
|
|
207
377
|
|
|
208
|
-
for (const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// Same rule ServicePruner enforces: for containers created before labels
|
|
214
|
-
// existed, this registry entry is the only thing that can still identify
|
|
215
|
-
// them. Dropping it while one exists would hide the container from prune,
|
|
216
|
-
// `stop -g` and `cleanup -g` alike, leaving it running forever under
|
|
217
|
-
// `restart: unless-stopped`.
|
|
218
|
-
const survivingContainers: string[] = [];
|
|
219
|
-
for (const containerName of Object.values(project.containers)) {
|
|
220
|
-
if (!containerName) {
|
|
221
|
-
continue;
|
|
378
|
+
for (const projectPath of Object.keys(projects)) {
|
|
379
|
+
await this.withProjectServiceLocks(projectPath, async () => {
|
|
380
|
+
if (await plugins.smartfs.directory(projectPath).exists()) {
|
|
381
|
+
return;
|
|
222
382
|
}
|
|
223
|
-
|
|
224
|
-
|
|
383
|
+
const currentProject = Object.entries(await this.getAllProjects()).find(
|
|
384
|
+
([registeredPathArg]) =>
|
|
385
|
+
plugins.path.resolve(registeredPathArg) === plugins.path.resolve(projectPath),
|
|
386
|
+
)?.[1];
|
|
387
|
+
if (!currentProject) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (currentProject.legacy?.minioContainer) {
|
|
391
|
+
kept.push({
|
|
392
|
+
projectPath,
|
|
393
|
+
reason: `preserved legacy MinIO container reference: ${currentProject.legacy.minioContainer}`,
|
|
394
|
+
});
|
|
395
|
+
return;
|
|
225
396
|
}
|
|
226
|
-
}
|
|
227
397
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
398
|
+
// Same rule ServicePruner enforces: for containers created before labels
|
|
399
|
+
// existed, this registry entry is the only thing that can still identify
|
|
400
|
+
// them. Re-read under all service locks so a concurrent start cannot
|
|
401
|
+
// appear between the Docker check and compare-delete.
|
|
402
|
+
const survivingContainers: string[] = [];
|
|
403
|
+
for (const containerName of Object.values(currentProject.containers)) {
|
|
404
|
+
if (containerName && (await this.exactContainerExists(containerName))) {
|
|
405
|
+
survivingContainers.push(containerName);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const labeledIds = await this.docker.listIds([
|
|
409
|
+
`label=git.zone.project-path=${plugins.path.resolve(projectPath)}`,
|
|
410
|
+
]);
|
|
411
|
+
if (labeledIds.length > 0) {
|
|
412
|
+
survivingContainers.push('project-labeled container');
|
|
413
|
+
}
|
|
414
|
+
if (survivingContainers.length > 0) {
|
|
415
|
+
kept.push({
|
|
416
|
+
projectPath,
|
|
417
|
+
reason: `container(s) still exist: ${survivingContainers.join(', ')}`,
|
|
418
|
+
});
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
235
421
|
|
|
236
|
-
|
|
237
|
-
|
|
422
|
+
await this.unregisterProject(projectPath, currentProject);
|
|
423
|
+
removed.push(projectPath);
|
|
424
|
+
});
|
|
238
425
|
}
|
|
239
426
|
|
|
240
427
|
return { removed, kept };
|
|
241
428
|
}
|
|
429
|
+
|
|
430
|
+
private async withProjectServiceLocks<T>(
|
|
431
|
+
projectPathArg: string,
|
|
432
|
+
actionArg: () => Promise<T>,
|
|
433
|
+
serviceIndexArg = 0,
|
|
434
|
+
): Promise<T> {
|
|
435
|
+
const service = serviceNames[serviceIndexArg];
|
|
436
|
+
if (!service) {
|
|
437
|
+
return actionArg();
|
|
438
|
+
}
|
|
439
|
+
return withServiceDataOperationLock(projectPathArg, service, () =>
|
|
440
|
+
this.withProjectServiceLocks(projectPathArg, actionArg, serviceIndexArg + 1),
|
|
441
|
+
);
|
|
442
|
+
}
|
|
242
443
|
}
|