@abtnode/core 1.16.52-beta-20251003-083412-fdfc4e36 → 1.16.52-beta-20251008-091027-c46c73e3
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/lib/api/team.js +593 -47
- package/lib/blocklet/downloader/blocklet-downloader.js +2 -2
- package/lib/blocklet/downloader/bundle-downloader.js +13 -38
- package/lib/blocklet/manager/disk.js +219 -89
- package/lib/blocklet/manager/ensure-blocklet-running.js +3 -2
- package/lib/blocklet/manager/helper/blue-green-get-componentids.js +59 -0
- package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +301 -0
- package/lib/blocklet/manager/helper/blue-green-update-blocklet-status.js +18 -0
- package/lib/blocklet/manager/helper/blue-green-upgrade-blocklet.js +191 -0
- package/lib/blocklet/manager/helper/upgrade-components.js +2 -9
- package/lib/blocklet/migration-dist/migration.cjs +4 -1
- package/lib/blocklet/passport/index.js +8 -2
- package/lib/index.js +18 -0
- package/lib/monitor/blocklet-runtime-monitor.js +12 -7
- package/lib/states/audit-log.js +54 -2
- package/lib/states/blocklet.js +22 -6
- package/lib/states/index.js +3 -0
- package/lib/states/org.js +663 -0
- package/lib/team/manager.js +10 -3
- package/lib/util/blocklet.js +217 -137
- package/lib/util/docker/is-docker-only-single-instances.js +17 -0
- package/lib/util/org.js +99 -0
- package/lib/validators/org.js +19 -0
- package/package.json +27 -26
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
const logger = require('@abtnode/logger')('@abtnode/core:blocklet-manager:blue-green');
|
|
3
|
+
const { BlockletStatus, BlockletGroup, BlockletEvents, BlockletInternalEvents } = require('@blocklet/constant');
|
|
4
|
+
const {
|
|
5
|
+
hasRunnableComponent,
|
|
6
|
+
getDisplayName,
|
|
7
|
+
isExternalBlocklet,
|
|
8
|
+
forEachBlockletSync,
|
|
9
|
+
getComponentMissingConfigs,
|
|
10
|
+
} = require('@blocklet/meta/lib/util');
|
|
11
|
+
const { getBlockletEngine, hasStartEngine } = require('@blocklet/meta/lib/engine');
|
|
12
|
+
const { getComponentsInternalInfo } = require('@blocklet/meta/lib/blocklet');
|
|
13
|
+
const {
|
|
14
|
+
forEachBlocklet,
|
|
15
|
+
validateBlocklet,
|
|
16
|
+
validateBlockletChainInfo,
|
|
17
|
+
ensureAppPortsNotOccupied,
|
|
18
|
+
getHealthyCheckTimeout,
|
|
19
|
+
getHookArgs,
|
|
20
|
+
shouldSkipComponent,
|
|
21
|
+
} = require('../../../util/blocklet');
|
|
22
|
+
const { startBlockletProcess } = require('../../../util/blocklet');
|
|
23
|
+
const hooks = require('../../hooks');
|
|
24
|
+
const checkNeedRunDocker = require('../../../util/docker/check-need-run-docker');
|
|
25
|
+
const { blueGreenGetComponentIds } = require('./blue-green-get-componentids');
|
|
26
|
+
const { dockerExec } = require('../../../util/docker/docker-exec');
|
|
27
|
+
const { isDockerOnlySingleInstance } = require('../../../util/docker/is-docker-only-single-instances');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 绿(Green)环境:指一个并行的、与蓝环境几乎一模一样的环境,用来部署新的版本。新代码、新配置都会先部署到绿环境中。
|
|
31
|
+
*
|
|
32
|
+
* @param {Object} params - 启动参数
|
|
33
|
+
* @param {string} params.did - blocklet DID
|
|
34
|
+
* @param {Array<string>} params.componentDids - 组件 DID 列表
|
|
35
|
+
* @param {string} params.operator - 操作者
|
|
36
|
+
* @param {Object} context - 上下文信息
|
|
37
|
+
* @param {Object} manager - blocklet 管理器实例
|
|
38
|
+
* @param {Object} states - 状态管理器
|
|
39
|
+
* @returns {Promise<Object>} 返回启动后的 blocklet 对象
|
|
40
|
+
*/
|
|
41
|
+
const blueGreenStartBlocklet = async (
|
|
42
|
+
{ did, componentDids, operator: _operator, ignoreErrorNotification },
|
|
43
|
+
context,
|
|
44
|
+
manager,
|
|
45
|
+
states
|
|
46
|
+
) => {
|
|
47
|
+
const operator = _operator || context?.user?.did;
|
|
48
|
+
const throwOnError = true;
|
|
49
|
+
const checkHealthImmediately = true;
|
|
50
|
+
const e2eMode = false;
|
|
51
|
+
|
|
52
|
+
logger.info('start green blocklet (blue-green deployment)', {
|
|
53
|
+
did,
|
|
54
|
+
componentDids,
|
|
55
|
+
throwOnError,
|
|
56
|
+
checkHealthImmediately,
|
|
57
|
+
e2eMode,
|
|
58
|
+
operator,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// 获取并验证 blocklet
|
|
62
|
+
const blocklet1 = await manager.ensureBlocklet(did, { e2eMode });
|
|
63
|
+
did = blocklet1.meta.did; // eslint-disable-line no-param-reassign
|
|
64
|
+
|
|
65
|
+
// 验证组件需求和引擎
|
|
66
|
+
await validateBlocklet(blocklet1);
|
|
67
|
+
await validateBlockletChainInfo(blocklet1);
|
|
68
|
+
|
|
69
|
+
if (!hasRunnableComponent(blocklet1)) {
|
|
70
|
+
throw new Error('No runnable component found');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const customerDockerUseVolumeComponentIds = [];
|
|
74
|
+
const otherComponentIds = [];
|
|
75
|
+
await forEachBlockletSync(blocklet1, (b) => {
|
|
76
|
+
if (!componentDids.includes(b.meta.did)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (isDockerOnlySingleInstance(b.meta)) {
|
|
80
|
+
customerDockerUseVolumeComponentIds.push(b.meta.did);
|
|
81
|
+
} else {
|
|
82
|
+
otherComponentIds.push(b.meta.did);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (customerDockerUseVolumeComponentIds.length) {
|
|
87
|
+
await manager.stop(
|
|
88
|
+
{ did, componentDids: customerDockerUseVolumeComponentIds, updateStatus: false, operator },
|
|
89
|
+
context
|
|
90
|
+
);
|
|
91
|
+
await manager.start({ did, componentDids: customerDockerUseVolumeComponentIds, operator }, context);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 分类组件 ID
|
|
95
|
+
const entryComponentIds = [];
|
|
96
|
+
const nonEntryComponentIds = [];
|
|
97
|
+
const componentDidsSet = new Set(otherComponentIds);
|
|
98
|
+
|
|
99
|
+
await forEachBlocklet(
|
|
100
|
+
blocklet1,
|
|
101
|
+
(b) => {
|
|
102
|
+
if (!componentDidsSet.has(b.meta.did)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (b.meta.group === BlockletGroup.gateway) {
|
|
107
|
+
nonEntryComponentIds.push(b.meta.did);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const engine = getBlockletEngine(b.meta);
|
|
111
|
+
if (engine.interpreter === 'blocklet') {
|
|
112
|
+
nonEntryComponentIds.push(b.meta.did);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!hasStartEngine(b.meta)) {
|
|
116
|
+
nonEntryComponentIds.push(b.meta.did);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
entryComponentIds.push(b.meta.did);
|
|
120
|
+
},
|
|
121
|
+
{ parallel: true, concurrencyLimit: 4 }
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (nonEntryComponentIds.length) {
|
|
125
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
|
|
126
|
+
componentDids: nonEntryComponentIds,
|
|
127
|
+
operator,
|
|
128
|
+
isGreen: true,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!entryComponentIds.length) {
|
|
133
|
+
await manager.emit(BlockletEvents.started, {
|
|
134
|
+
...blocklet1,
|
|
135
|
+
componentDids: nonEntryComponentIds,
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// check required config
|
|
141
|
+
try {
|
|
142
|
+
for (const component of blocklet1.children) {
|
|
143
|
+
if (!entryComponentIds.includes(component.meta.did)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (!shouldSkipComponent(component.meta.did, entryComponentIds)) {
|
|
147
|
+
const missingProps = getComponentMissingConfigs(component, blocklet1);
|
|
148
|
+
if (missingProps.length) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Missing required configuration to start ${component.meta.title}: ${missingProps.map((x) => x.key).join(',')}`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
const description = `Green environment start failed for ${getDisplayName(blocklet1)}: ${error.message}`;
|
|
157
|
+
if (!ignoreErrorNotification) {
|
|
158
|
+
manager._createNotification(did, {
|
|
159
|
+
title: 'Blue-Green Deployment: Green Start Failed',
|
|
160
|
+
description,
|
|
161
|
+
entityType: 'blocklet',
|
|
162
|
+
entityId: did,
|
|
163
|
+
severity: 'error',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const blueGreenComponentIds = await blueGreenGetComponentIds(blocklet1, entryComponentIds);
|
|
170
|
+
|
|
171
|
+
// eslint-disable-next-line no-unreachable-loop
|
|
172
|
+
for (const item of blueGreenComponentIds) {
|
|
173
|
+
if (!item.componentDids.length) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const nextBlocklet = await ensureAppPortsNotOccupied({
|
|
177
|
+
blocklet: blocklet1,
|
|
178
|
+
componentDids: item.componentDids,
|
|
179
|
+
states,
|
|
180
|
+
manager,
|
|
181
|
+
isGreen: item.changeToGreen,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const doc1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, {
|
|
186
|
+
componentDids: item.componentDids,
|
|
187
|
+
operator,
|
|
188
|
+
isGreen: item.changeToGreen,
|
|
189
|
+
});
|
|
190
|
+
nextBlocklet.greenStatus = BlockletStatus.starting;
|
|
191
|
+
manager.emit(BlockletEvents.statusChange, doc1);
|
|
192
|
+
|
|
193
|
+
const nodeInfo = await states.node.read();
|
|
194
|
+
const nodeEnvironments = await states.node.getEnvironments();
|
|
195
|
+
|
|
196
|
+
// 钩子函数设置
|
|
197
|
+
const getHookFn =
|
|
198
|
+
(hookName) =>
|
|
199
|
+
async (b, { env }) => {
|
|
200
|
+
const hookArgs = getHookArgs(b);
|
|
201
|
+
const needRunDocker = await checkNeedRunDocker(b.meta, env, nodeInfo, isExternalBlocklet(nextBlocklet));
|
|
202
|
+
if (!b.meta.scripts?.[hookName]) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
if (needRunDocker) {
|
|
206
|
+
return dockerExec({
|
|
207
|
+
blocklet: nextBlocklet,
|
|
208
|
+
meta: b.meta,
|
|
209
|
+
script: b.meta.scripts?.[hookName],
|
|
210
|
+
hookName,
|
|
211
|
+
nodeInfo,
|
|
212
|
+
env,
|
|
213
|
+
...hookArgs,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return hooks[hookName](b, {
|
|
217
|
+
appDir: b.env.appDir,
|
|
218
|
+
hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
|
|
219
|
+
env,
|
|
220
|
+
did, // root blocklet did,
|
|
221
|
+
teamManager: manager.teamManager,
|
|
222
|
+
...hookArgs,
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
await startBlockletProcess(nextBlocklet, {
|
|
227
|
+
...context,
|
|
228
|
+
preFlight: getHookFn('preFlight'),
|
|
229
|
+
preStart: getHookFn('preStart'),
|
|
230
|
+
postStart: getHookFn('postStart'),
|
|
231
|
+
nodeEnvironments,
|
|
232
|
+
nodeInfo,
|
|
233
|
+
e2eMode,
|
|
234
|
+
componentDids: item.componentDids,
|
|
235
|
+
configSynchronizer: manager.configSynchronizer,
|
|
236
|
+
isGreen: item.changeToGreen,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// 健康检查绿色环境
|
|
240
|
+
const { startTimeout, minConsecutiveTime } = getHealthyCheckTimeout(nextBlocklet, {
|
|
241
|
+
checkHealthImmediately,
|
|
242
|
+
componentDids: item.componentDids,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await manager._onCheckIfStarted(
|
|
246
|
+
{
|
|
247
|
+
did,
|
|
248
|
+
context,
|
|
249
|
+
minConsecutiveTime,
|
|
250
|
+
timeout: startTimeout,
|
|
251
|
+
componentDids: item.componentDids,
|
|
252
|
+
},
|
|
253
|
+
{ throwOnError: true, isGreen: item.changeToGreen, needUpdateBlueStatus: true }
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
logger.info('Green environment started successfully', {
|
|
257
|
+
did,
|
|
258
|
+
componentDids: item.componentDids,
|
|
259
|
+
});
|
|
260
|
+
} catch (err) {
|
|
261
|
+
const error = Array.isArray(err) ? err[0] : err;
|
|
262
|
+
logger.error('Failed to start green environment', { error, did, title: blocklet1.meta.title });
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
await manager.deleteProcess({ did, componentDids: item.componentDids, isGreen: item.changeToGreen });
|
|
266
|
+
manager.emit(BlockletEvents.statusChange, blocklet1);
|
|
267
|
+
} catch (cleanupError) {
|
|
268
|
+
logger.error('Failed to cleanup green environment', { cleanupError });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const description = `Green environment start failed for ${getDisplayName(blocklet1)}: ${error.message}`;
|
|
272
|
+
if (!ignoreErrorNotification) {
|
|
273
|
+
manager._createNotification(did, {
|
|
274
|
+
title: 'Blue-Green Deployment: Green Start Failed',
|
|
275
|
+
description,
|
|
276
|
+
entityType: 'blocklet',
|
|
277
|
+
entityId: did,
|
|
278
|
+
severity: 'error',
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (throwOnError) {
|
|
283
|
+
throw new Error(description);
|
|
284
|
+
}
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const nextBlocklet = await manager.getBlocklet(did);
|
|
290
|
+
const componentsInfo = getComponentsInternalInfo(nextBlocklet);
|
|
291
|
+
manager.emit(BlockletInternalEvents.componentStarted, {
|
|
292
|
+
appDid: nextBlocklet.appDid,
|
|
293
|
+
components: componentsInfo,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
manager.emit(BlockletEvents.statusChange, nextBlocklet);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
module.exports = {
|
|
300
|
+
blueGreenStartBlocklet,
|
|
301
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const blueGreenUpdateBlockletStatus = async ({ states, did, status, blueGreenComponentIds }) => {
|
|
2
|
+
const outputBlocklet = {};
|
|
3
|
+
await Promise.all(
|
|
4
|
+
blueGreenComponentIds.map(async (item) => {
|
|
5
|
+
if (!item.componentDids.length) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const res = await states.blocklet.setBlockletStatus(did, status, {
|
|
9
|
+
componentDids: item.componentDids,
|
|
10
|
+
isGreen: item.changeToGreen,
|
|
11
|
+
});
|
|
12
|
+
Object.assign(outputBlocklet, res);
|
|
13
|
+
})
|
|
14
|
+
);
|
|
15
|
+
return outputBlocklet;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
module.exports = { blueGreenUpdateBlockletStatus };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
const logger = require('@abtnode/logger')('@abtnode/core:blocklet-manager:upgrade-blue-green');
|
|
3
|
+
const { BlockletStatus, BlockletEvents, BlockletInternalEvents } = require('@blocklet/constant');
|
|
4
|
+
const { INSTALL_ACTIONS, WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
|
|
5
|
+
const { getDisplayName, hasStartEngine } = require('@blocklet/meta/lib/util');
|
|
6
|
+
const { getComponentsInternalInfo } = require('@blocklet/meta/lib/blocklet');
|
|
7
|
+
const { getComponentNamesWithVersion, updateBlockletFallbackLogo } = require('../../../util/blocklet');
|
|
8
|
+
const { blueGreenStartBlocklet } = require('./blue-green-start-blocklet');
|
|
9
|
+
|
|
10
|
+
const blueGreenUpgradeBlocklet = async (
|
|
11
|
+
{ newBlocklet, oldBlocklet, componentDids, action, shouldCleanUploadFile, url },
|
|
12
|
+
context,
|
|
13
|
+
manager,
|
|
14
|
+
states
|
|
15
|
+
) => {
|
|
16
|
+
const { meta, source, deployedFrom, children } = newBlocklet;
|
|
17
|
+
const { did, version, name } = meta;
|
|
18
|
+
const title = getDisplayName(newBlocklet);
|
|
19
|
+
|
|
20
|
+
for (const child of newBlocklet.children) {
|
|
21
|
+
for (const child2 of oldBlocklet.children) {
|
|
22
|
+
if (child && child2 && child.meta.did === child2.meta.did) {
|
|
23
|
+
child.status = child2.status;
|
|
24
|
+
child.greenStatus = child2.greenStatus;
|
|
25
|
+
child.ports = child2.ports;
|
|
26
|
+
child.greenPorts = child2.greenPorts;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
await states.blocklet.upgradeBlocklet({ meta, source, deployedFrom, children });
|
|
33
|
+
logger.info('updated blocklet for upgrading', { did, componentDids, source, name });
|
|
34
|
+
|
|
35
|
+
await manager._setConfigsFromMeta(did);
|
|
36
|
+
|
|
37
|
+
let blocklet = await manager.ensureBlocklet(did);
|
|
38
|
+
|
|
39
|
+
await manager._updateBlockletEnvironment(did);
|
|
40
|
+
blocklet = await manager.getBlocklet(did);
|
|
41
|
+
|
|
42
|
+
await manager._runUserHook('preInstall', blocklet, context);
|
|
43
|
+
await manager._runUserHook('postInstall', blocklet, context);
|
|
44
|
+
await manager._runUserHook('preFlight', blocklet, context);
|
|
45
|
+
await manager._runMigration({
|
|
46
|
+
parallel: false,
|
|
47
|
+
did,
|
|
48
|
+
blocklet,
|
|
49
|
+
oldBlocklet,
|
|
50
|
+
componentDids,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const runningDids = [];
|
|
54
|
+
const stoppedDids = [];
|
|
55
|
+
|
|
56
|
+
if (action === INSTALL_ACTIONS.INSTALL_COMPONENT) {
|
|
57
|
+
for (const componentDid of componentDids) {
|
|
58
|
+
const component = blocklet.children.find((x) => x.meta.did === componentDid);
|
|
59
|
+
if (!component) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (hasStartEngine(component.meta)) {
|
|
63
|
+
stoppedDids.push(componentDid);
|
|
64
|
+
} else {
|
|
65
|
+
runningDids.push(componentDid);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
for (const componentDid of componentDids) {
|
|
70
|
+
const oldComponent = oldBlocklet.children.find((x) => x.meta.did === componentDid);
|
|
71
|
+
if (oldComponent?.status === BlockletStatus.running || oldComponent?.greenStatus === BlockletStatus.running) {
|
|
72
|
+
runningDids.push(componentDid);
|
|
73
|
+
} else {
|
|
74
|
+
stoppedDids.push(componentDid);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const initialized = !!blocklet.settings?.initialized;
|
|
80
|
+
|
|
81
|
+
if (runningDids.length) {
|
|
82
|
+
if (initialized) {
|
|
83
|
+
await blueGreenStartBlocklet(
|
|
84
|
+
{ did, componentDids: runningDids, operator: context?.user?.did, ignoreErrorNotification: true },
|
|
85
|
+
context,
|
|
86
|
+
manager,
|
|
87
|
+
states
|
|
88
|
+
);
|
|
89
|
+
await states.blocklet.setInstalledAt(did);
|
|
90
|
+
} else {
|
|
91
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, {
|
|
92
|
+
componentDids: runningDids,
|
|
93
|
+
isGreenAndBlue: true,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (stoppedDids.length) {
|
|
99
|
+
const status = action === INSTALL_ACTIONS.INSTALL_COMPONENT ? BlockletStatus.installed : BlockletStatus.stopped;
|
|
100
|
+
await states.blocklet.setBlockletStatus(did, status, {
|
|
101
|
+
componentDids: stoppedDids,
|
|
102
|
+
isGreenAndBlue: true,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
blocklet = await manager.getBlocklet(did, context);
|
|
107
|
+
|
|
108
|
+
await updateBlockletFallbackLogo(blocklet);
|
|
109
|
+
|
|
110
|
+
await manager._updateDependents(did);
|
|
111
|
+
|
|
112
|
+
manager.refreshListCache();
|
|
113
|
+
|
|
114
|
+
manager.configSynchronizer.throttledSyncAppConfig(blocklet);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
manager.emit(BlockletEvents.upgraded, { blocklet, context });
|
|
118
|
+
|
|
119
|
+
const isInstallAction = action === INSTALL_ACTIONS.INSTALL_COMPONENT;
|
|
120
|
+
const notificationEvent = isInstallAction ? BlockletEvents.componentInstalled : BlockletEvents.componentUpgraded;
|
|
121
|
+
const actionName = isInstallAction ? 'installed' : 'upgraded';
|
|
122
|
+
|
|
123
|
+
manager.emit(notificationEvent, { ...blocklet, componentDids, oldBlocklet, context });
|
|
124
|
+
|
|
125
|
+
manager._createNotification(did, {
|
|
126
|
+
title: `Component ${actionName} succeed`,
|
|
127
|
+
description: `${getComponentNamesWithVersion(
|
|
128
|
+
newBlocklet,
|
|
129
|
+
componentDids
|
|
130
|
+
)} is ${actionName} successfully for ${title}`,
|
|
131
|
+
action: `/blocklets/${did}/overview`,
|
|
132
|
+
blockletDashboardAction: `${WELLKNOWN_SERVICE_PATH_PREFIX}/admin/blocklets`,
|
|
133
|
+
entityType: 'blocklet',
|
|
134
|
+
entityId: did,
|
|
135
|
+
severity: 'success',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (shouldCleanUploadFile && url) {
|
|
139
|
+
manager._cleanUploadFile(url);
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error('emit upgrade notification failed', { name, version, error });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await manager._ensureDeletedChildrenInSettings(blocklet);
|
|
146
|
+
|
|
147
|
+
if (oldBlocklet.status === BlockletStatus.running || oldBlocklet.greenStatus === BlockletStatus.running) {
|
|
148
|
+
manager.emit(
|
|
149
|
+
action === INSTALL_ACTIONS.INSTALL_COMPONENT
|
|
150
|
+
? BlockletInternalEvents.componentInstalled
|
|
151
|
+
: BlockletInternalEvents.componentUpgraded,
|
|
152
|
+
{
|
|
153
|
+
appDid: blocklet.appDid,
|
|
154
|
+
components: getComponentsInternalInfo(blocklet).filter((c) => componentDids.includes(c.did)),
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return blocklet;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
logger.error('failed to upgrade blocklet', { did, version, name, error: err });
|
|
162
|
+
manager.configSynchronizer.throttledSyncAppConfig(oldBlocklet);
|
|
163
|
+
await manager._updateDependents(did);
|
|
164
|
+
|
|
165
|
+
const actionName = action === INSTALL_ACTIONS.INSTALL_COMPONENT ? 'install' : 'upgrade';
|
|
166
|
+
const notificationEvent =
|
|
167
|
+
action === INSTALL_ACTIONS.INSTALL_COMPONENT
|
|
168
|
+
? BlockletEvents.componentInstallFailed
|
|
169
|
+
: BlockletEvents.componentUpgradeFailed;
|
|
170
|
+
|
|
171
|
+
manager.emit(notificationEvent, {
|
|
172
|
+
blocklet: { ...newBlocklet, componentDids, error: { message: err.message } },
|
|
173
|
+
context,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
manager._createNotification(did, {
|
|
177
|
+
title: `Component ${actionName} failed`,
|
|
178
|
+
description: `${getComponentNamesWithVersion(newBlocklet, componentDids)} ${actionName} failed for ${title}: ${
|
|
179
|
+
err.message
|
|
180
|
+
}.`,
|
|
181
|
+
entityType: 'blocklet',
|
|
182
|
+
entityId: did,
|
|
183
|
+
severity: 'error',
|
|
184
|
+
});
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
module.exports = {
|
|
190
|
+
blueGreenUpgradeBlocklet,
|
|
191
|
+
};
|
|
@@ -3,7 +3,7 @@ const cloneDeep = require('@abtnode/util/lib/deep-clone');
|
|
|
3
3
|
|
|
4
4
|
const logger = require('@abtnode/logger')('@abtnode/core:upgrade-component');
|
|
5
5
|
|
|
6
|
-
const {
|
|
6
|
+
const { BlockletEvents } = require('@blocklet/constant');
|
|
7
7
|
const { INSTALL_ACTIONS } = require('@abtnode/constant');
|
|
8
8
|
const { parseOptionalComponents } = require('@blocklet/resolver');
|
|
9
9
|
const {
|
|
@@ -127,17 +127,11 @@ const upgrade = async ({ updateId, componentDids, context, states, manager }) =>
|
|
|
127
127
|
children: children.map((x) => ({ name: x.meta.name, version: x.meta.version })),
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
const newBlocklet = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting, { componentDids });
|
|
132
|
-
|
|
133
|
-
newBlocklet.children = children;
|
|
130
|
+
const newBlocklet = { ...oldBlocklet, children };
|
|
134
131
|
await validateBlocklet(newBlocklet);
|
|
135
132
|
|
|
136
|
-
manager.emit(BlockletEvents.statusChange, newBlocklet);
|
|
137
|
-
|
|
138
133
|
const action = INSTALL_ACTIONS.UPGRADE_COMPONENT;
|
|
139
134
|
|
|
140
|
-
// backup rollback data
|
|
141
135
|
await manager._rollbackCache.backup({ did, action, oldBlocklet });
|
|
142
136
|
|
|
143
137
|
// add to queue
|
|
@@ -158,7 +152,6 @@ const upgrade = async ({ updateId, componentDids, context, states, manager }) =>
|
|
|
158
152
|
ticket.on('failed', async (err) => {
|
|
159
153
|
logger.error('queue failed', { entity: 'blocklet', action, did, error: err });
|
|
160
154
|
await manager._rollback(action, did, oldBlocklet);
|
|
161
|
-
|
|
162
155
|
manager.emit(BlockletEvents.componentUpgradeFailed, {
|
|
163
156
|
blocklet: { ...oldBlocklet, componentDids, error: { message: err.message } },
|
|
164
157
|
context: { ...context, createAuditLog: false },
|
|
@@ -730,6 +730,8 @@ module.exports = Object.freeze({
|
|
|
730
730
|
'deleteBlockletResponseHeaderPolicy',
|
|
731
731
|
'deleteBlockletAccessPolicy',
|
|
732
732
|
'configVault',
|
|
733
|
+
'removeOrgMember',
|
|
734
|
+
'deleteOrg',
|
|
733
735
|
],
|
|
734
736
|
|
|
735
737
|
// 如果是 multiple tenant 的情况下, 这些白名单不会验证 RBAC
|
|
@@ -860,6 +862,7 @@ module.exports = Object.freeze({
|
|
|
860
862
|
'https://*.didspaces.com',
|
|
861
863
|
'https://*.blocklet.dev',
|
|
862
864
|
'https://domain.didlabs.org',
|
|
865
|
+
'https://cdn.blocklet.io',
|
|
863
866
|
],
|
|
864
867
|
CSP_SYSTEM_SOURCES: [
|
|
865
868
|
'https://*.did.abtnet.io',
|
|
@@ -38946,7 +38949,7 @@ module.exports = require("zlib");
|
|
|
38946
38949
|
/***/ ((module) => {
|
|
38947
38950
|
|
|
38948
38951
|
"use strict";
|
|
38949
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.51","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib","test":"node tools/jest.js","test:disk":"
|
|
38952
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.51","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib","test":"node tools/jest.js","test:disk":"CI=true npm run test tests/blocklet/manager/disk.spec.js","test:blue":"CI=true npm run test tests/blocklet/manager/disk-blue-green.spec.js","coverage":"npm run test -- --coverage"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.16.51","@abtnode/auth":"1.16.51","@abtnode/certificate-manager":"1.16.51","@abtnode/constant":"1.16.51","@abtnode/cron":"1.16.51","@abtnode/db-cache":"1.16.51","@abtnode/docker-utils":"1.16.51","@abtnode/logger":"1.16.51","@abtnode/models":"1.16.51","@abtnode/queue":"1.16.51","@abtnode/rbac":"1.16.51","@abtnode/router-provider":"1.16.51","@abtnode/static-server":"1.16.51","@abtnode/timemachine":"1.16.51","@abtnode/util":"1.16.51","@aigne/aigne-hub":"^0.10.0","@arcblock/did":"1.25.6","@arcblock/did-connect-js":"1.25.6","@arcblock/did-ext":"1.25.6","@arcblock/did-motif":"^1.1.14","@arcblock/did-util":"1.25.6","@arcblock/event-hub":"1.25.6","@arcblock/jwt":"1.25.6","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"1.25.6","@arcblock/vc":"1.25.6","@blocklet/constant":"1.16.51","@blocklet/did-space-js":"^1.1.29","@blocklet/env":"1.16.51","@blocklet/error":"^0.2.5","@blocklet/meta":"1.16.51","@blocklet/resolver":"1.16.51","@blocklet/sdk":"1.16.51","@blocklet/server-js":"1.16.51","@blocklet/store":"1.16.51","@blocklet/theme":"^3.1.45","@fidm/x509":"^1.2.1","@ocap/mcrypto":"1.25.6","@ocap/util":"1.25.6","@ocap/wallet":"1.25.6","@slack/webhook":"^5.0.4","archiver":"^7.0.1","axios":"^1.7.9","axon":"^2.0.3","chalk":"^4.1.2","cross-spawn":"^7.0.3","dayjs":"^1.11.13","deep-diff":"^1.0.2","detect-port":"^1.5.1","envfile":"^7.1.0","escape-string-regexp":"^4.0.0","fast-glob":"^3.3.2","filesize":"^10.1.1","flat":"^5.0.2","fs-extra":"^11.2.0","get-port":"^5.1.1","hasha":"^5.2.2","is-base64":"^1.1.0","is-cidr":"4","is-ip":"3","is-url":"^1.2.4","joi":"17.12.2","joi-extension-semver":"^5.0.0","js-yaml":"^4.1.0","kill-port":"^2.0.1","lodash":"^4.17.21","node-stream-zip":"^1.15.0","p-all":"^3.0.0","p-limit":"^3.1.0","p-map":"^4.0.0","p-retry":"^4.6.2","p-wait-for":"^3.2.0","private-ip":"^2.3.4","rate-limiter-flexible":"^5.0.5","read-last-lines":"^1.8.0","semver":"^7.6.3","sequelize":"^6.35.0","shelljs":"^0.8.5","slugify":"^1.6.6","ssri":"^8.0.1","stream-throttle":"^0.1.3","stream-to-promise":"^3.0.0","systeminformation":"^5.23.3","tail":"^2.2.4","tar":"^6.1.11","transliteration":"^2.3.5","ua-parser-js":"^1.0.2","ufo":"^1.5.3","uuid":"^11.1.0","valid-url":"^1.0.9","which":"^2.0.2","xbytes":"^1.8.0"},"devDependencies":{"expand-tilde":"^2.0.2","express":"^4.18.2","jest":"^29.7.0","unzipper":"^0.10.11"},"gitHead":"e5764f753181ed6a7c615cd4fc6682aacf0cb7cd"}');
|
|
38950
38953
|
|
|
38951
38954
|
/***/ }),
|
|
38952
38955
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { EventEmitter } = require('events');
|
|
2
2
|
const getRequestIP = require('@abtnode/util/lib/get-request-ip');
|
|
3
|
+
const { Op } = require('sequelize');
|
|
3
4
|
const logger = require('@abtnode/logger')('@abtnode/core:blocklet-passport');
|
|
4
5
|
|
|
5
6
|
const Cron = require('@abtnode/cron');
|
|
@@ -72,8 +73,13 @@ class PassportAPI extends EventEmitter {
|
|
|
72
73
|
const { page, pageSize } = validatePaging(paging);
|
|
73
74
|
|
|
74
75
|
const where = query;
|
|
75
|
-
if (query.role === '$all') {
|
|
76
|
-
|
|
76
|
+
if (query.role === '$all' || !query.role) {
|
|
77
|
+
// 只展示非 Org passport
|
|
78
|
+
const roles = await this.teamManager.getRoles(teamDid);
|
|
79
|
+
const queryRoles = roles.filter((x) => !x.orgId).map((x) => x.name);
|
|
80
|
+
where.role = {
|
|
81
|
+
[Op.in]: queryRoles,
|
|
82
|
+
};
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
const result = await passportState.passports(where, { issuanceDate: -1 }, { pageSize, page });
|
package/lib/index.js
CHANGED
|
@@ -555,6 +555,24 @@ function ABTNode(options) {
|
|
|
555
555
|
|
|
556
556
|
createTagging: teamAPI.createTagging.bind(teamAPI),
|
|
557
557
|
deleteTagging: teamAPI.deleteTagging.bind(teamAPI),
|
|
558
|
+
// Org
|
|
559
|
+
createOrg: teamAPI.createOrg.bind(teamAPI),
|
|
560
|
+
updateOrg: teamAPI.updateOrg.bind(teamAPI),
|
|
561
|
+
deleteOrg: teamAPI.deleteOrg.bind(teamAPI),
|
|
562
|
+
getOrgs: teamAPI.getOrgs.bind(teamAPI),
|
|
563
|
+
getOrg: teamAPI.getOrg.bind(teamAPI),
|
|
564
|
+
// Org Member
|
|
565
|
+
getOrgMembers: teamAPI.getOrgMembers.bind(teamAPI),
|
|
566
|
+
addOrgMember: teamAPI.addOrgMember.bind(teamAPI), // 加入的方式是通过邀请,因此不能批量添加
|
|
567
|
+
updateOrgMember: teamAPI.updateOrgMember.bind(teamAPI), // 加入的方式是通过邀请,因此不能批量添加
|
|
568
|
+
removeOrgMember: teamAPI.removeOrgMember.bind(teamAPI),
|
|
569
|
+
getOrgInvitableUsers: teamAPI.getOrgInvitableUsers.bind(teamAPI),
|
|
570
|
+
inviteMembersToOrg: teamAPI.inviteMembersToOrg.bind(teamAPI), // TODO: 批量邀请用户到组织 与 addOrgMember 可能重复
|
|
571
|
+
|
|
572
|
+
// org resource
|
|
573
|
+
getOrgResource: teamAPI.getOrgResource.bind(teamAPI),
|
|
574
|
+
addOrgResource: teamAPI.addOrgResource.bind(teamAPI),
|
|
575
|
+
migrateOrgResource: teamAPI.migrateOrgResource.bind(teamAPI),
|
|
558
576
|
|
|
559
577
|
// Access Control
|
|
560
578
|
getRBAC: (did = options.nodeDid) => teamManager.getRBAC(did),
|
|
@@ -11,7 +11,7 @@ const defaultLogger = require('@abtnode/logger')('blocklet-runtime-monitor');
|
|
|
11
11
|
|
|
12
12
|
const { Op } = require('sequelize');
|
|
13
13
|
const { isInstanceWorker } = require('@abtnode/util/lib/pm2/is-instence-worker');
|
|
14
|
-
const { getRuntimeInfo
|
|
14
|
+
const { getRuntimeInfo } = require('../util/blocklet');
|
|
15
15
|
|
|
16
16
|
const insertThrottleMap = new Map();
|
|
17
17
|
|
|
@@ -73,9 +73,10 @@ class BlockletRuntimeMonitor extends EventEmitter {
|
|
|
73
73
|
const {
|
|
74
74
|
meta: { did: blockletDid },
|
|
75
75
|
status,
|
|
76
|
+
greenStatus,
|
|
76
77
|
} = blocklet;
|
|
77
78
|
|
|
78
|
-
if (status !== BlockletStatus.running) {
|
|
79
|
+
if (status !== BlockletStatus.running && greenStatus !== BlockletStatus.running) {
|
|
79
80
|
if (this.data[blockletDid]) {
|
|
80
81
|
Object.keys(this.data[blockletDid]).forEach((key) => {
|
|
81
82
|
this.data[blockletDid][key].runtimeInfo = {};
|
|
@@ -105,14 +106,18 @@ class BlockletRuntimeMonitor extends EventEmitter {
|
|
|
105
106
|
blocklet,
|
|
106
107
|
async (component, { id: componentId, ancestors }) => {
|
|
107
108
|
const { meta } = component;
|
|
108
|
-
if (
|
|
109
|
-
|
|
109
|
+
if (
|
|
110
|
+
!isGatewayBlocklet(meta) &&
|
|
111
|
+
hasStartEngine(meta) &&
|
|
112
|
+
(component.status === BlockletStatus.running || component.greenStatus === BlockletStatus.running)
|
|
113
|
+
) {
|
|
114
|
+
const _processId = getComponentProcessId(component, ancestors);
|
|
115
|
+
const processId = component.greenStatus === BlockletStatus.running ? `${_processId}-green` : _processId;
|
|
116
|
+
|
|
110
117
|
try {
|
|
111
118
|
const runtimeInfo = await getRuntimeInfo(processId);
|
|
112
|
-
const dockerName = component.environments?.find((x) => x.key === 'BLOCKLET_DOCKER_NAME')?.value;
|
|
113
|
-
const dockerRuntimeInfo = dockerName ? await getDockerRuntimeInfoByDockerName(dockerName) : {};
|
|
114
119
|
|
|
115
|
-
this.data[blockletDid][componentId] = { runtimeInfo
|
|
120
|
+
this.data[blockletDid][componentId] = { runtimeInfo };
|
|
116
121
|
|
|
117
122
|
if (!component.mountPoint || component.mountPoint === '/') {
|
|
118
123
|
this.data[blockletDid].app.runtimeInfo = cloneDeep(runtimeInfo);
|