@kapeta/local-cluster-service 0.37.0 → 0.39.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/CHANGELOG.md +14 -0
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/src/assetManager.d.ts +2 -1
- package/dist/cjs/src/assetManager.js +3 -2
- package/dist/cjs/src/config/routes.js +2 -2
- package/dist/cjs/src/containerManager.d.ts +6 -3
- package/dist/cjs/src/containerManager.js +101 -21
- package/dist/cjs/src/instanceManager.d.ts +4 -2
- package/dist/cjs/src/instanceManager.js +71 -32
- package/dist/cjs/src/operatorManager.d.ts +6 -3
- package/dist/cjs/src/operatorManager.js +32 -23
- package/dist/cjs/src/progressListener.d.ts +8 -1
- package/dist/cjs/src/progressListener.js +12 -1
- package/dist/cjs/src/repositoryManager.js +3 -2
- package/dist/cjs/src/serviceManager.d.ts +0 -1
- package/dist/cjs/src/serviceManager.js +2 -8
- package/dist/cjs/src/taskManager.d.ts +2 -0
- package/dist/cjs/src/taskManager.js +9 -0
- package/dist/cjs/src/types.d.ts +1 -48
- package/dist/cjs/src/types.js +2 -1
- package/dist/cjs/src/utils/BlockInstanceRunner.js +45 -33
- package/dist/cjs/src/utils/InternalConfigProvider.d.ts +38 -0
- package/dist/cjs/src/utils/InternalConfigProvider.js +146 -0
- package/dist/cjs/src/utils/commandLineUtils.d.ts +2 -1
- package/dist/cjs/src/utils/commandLineUtils.js +7 -1
- package/dist/cjs/src/utils/utils.d.ts +26 -4
- package/dist/cjs/src/utils/utils.js +48 -8
- package/dist/esm/index.js +4 -1
- package/dist/esm/src/assetManager.d.ts +2 -1
- package/dist/esm/src/assetManager.js +3 -2
- package/dist/esm/src/config/routes.js +2 -2
- package/dist/esm/src/containerManager.d.ts +6 -3
- package/dist/esm/src/containerManager.js +101 -21
- package/dist/esm/src/instanceManager.d.ts +4 -2
- package/dist/esm/src/instanceManager.js +71 -32
- package/dist/esm/src/operatorManager.d.ts +6 -3
- package/dist/esm/src/operatorManager.js +32 -23
- package/dist/esm/src/progressListener.d.ts +8 -1
- package/dist/esm/src/progressListener.js +12 -1
- package/dist/esm/src/repositoryManager.js +3 -2
- package/dist/esm/src/serviceManager.d.ts +0 -1
- package/dist/esm/src/serviceManager.js +2 -8
- package/dist/esm/src/taskManager.d.ts +2 -0
- package/dist/esm/src/taskManager.js +9 -0
- package/dist/esm/src/types.d.ts +1 -48
- package/dist/esm/src/types.js +2 -1
- package/dist/esm/src/utils/BlockInstanceRunner.js +45 -33
- package/dist/esm/src/utils/InternalConfigProvider.d.ts +38 -0
- package/dist/esm/src/utils/InternalConfigProvider.js +146 -0
- package/dist/esm/src/utils/commandLineUtils.d.ts +2 -1
- package/dist/esm/src/utils/commandLineUtils.js +7 -1
- package/dist/esm/src/utils/utils.d.ts +26 -4
- package/dist/esm/src/utils/utils.js +48 -8
- package/index.ts +5 -2
- package/package.json +16 -14
- package/src/assetManager.ts +5 -4
- package/src/config/routes.ts +4 -2
- package/src/containerManager.ts +115 -26
- package/src/instanceManager.ts +86 -44
- package/src/operatorManager.ts +48 -40
- package/src/progressListener.ts +15 -1
- package/src/repositoryManager.ts +5 -3
- package/src/serviceManager.ts +3 -11
- package/src/taskManager.ts +11 -0
- package/src/types.ts +2 -50
- package/src/utils/BlockInstanceRunner.ts +60 -44
- package/src/utils/InternalConfigProvider.ts +214 -0
- package/src/utils/commandLineUtils.ts +10 -2
- package/src/utils/utils.ts +53 -10
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# [0.39.0](https://github.com/kapetacom/local-cluster-service/compare/v0.38.0...v0.39.0) (2024-02-26)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Add support for dotenv and config templates ([#127](https://github.com/kapetacom/local-cluster-service/issues/127)) ([5e78610](https://github.com/kapetacom/local-cluster-service/commit/5e786106f1d6ca69fcd79cde351cc37908c2b4b2))
|
7
|
+
|
8
|
+
# [0.38.0](https://github.com/kapetacom/local-cluster-service/compare/v0.37.0...v0.38.0) (2024-02-24)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* Add support for running local dockerfiles ([2ee08c5](https://github.com/kapetacom/local-cluster-service/commit/2ee08c58eb19666834262d8011c8f901957b5f63))
|
14
|
+
|
1
15
|
# [0.37.0](https://github.com/kapetacom/local-cluster-service/compare/v0.36.1...v0.37.0) (2024-02-23)
|
2
16
|
|
3
17
|
|
package/dist/cjs/index.js
CHANGED
@@ -210,7 +210,10 @@ exports.default = {
|
|
210
210
|
}
|
211
211
|
reject(err);
|
212
212
|
});
|
213
|
-
|
213
|
+
// On Linux we need to bind to 0.0.0.0 to be able to connect to it from docker containers.
|
214
|
+
// TODO: This might pose a security risk - so we should authenticate all requests using a
|
215
|
+
// shared secret/nonce that we pass around.
|
216
|
+
const bindHost = (0, utils_1.isLinux)() ? '0.0.0.0' : host;
|
214
217
|
currentServer.listen(port, bindHost, async () => {
|
215
218
|
try {
|
216
219
|
const ensureCLITask = await (0, commandLineUtils_1.ensureCLI)();
|
@@ -4,6 +4,7 @@
|
|
4
4
|
*/
|
5
5
|
import { Definition } from '@kapeta/local-cluster-config';
|
6
6
|
import { BlockDefinition, BlockInstance, Plan } from '@kapeta/schemas';
|
7
|
+
import { Task } from './taskManager';
|
7
8
|
import { SourceOfChange } from './types';
|
8
9
|
export interface EnrichedAsset {
|
9
10
|
ref: string;
|
@@ -31,7 +32,7 @@ declare class AssetManager {
|
|
31
32
|
updateAsset(ref: string, yaml: Definition, sourceOfChange?: SourceOfChange): Promise<void>;
|
32
33
|
importFile(filePath: string): Promise<EnrichedAsset[]>;
|
33
34
|
unregisterAsset(ref: string): Promise<void>;
|
34
|
-
installAsset(ref: string, wait?: boolean): Promise<
|
35
|
+
installAsset(ref: string, wait?: boolean): Promise<Task<void>[] | undefined>;
|
35
36
|
private cleanupUnusedProviders;
|
36
37
|
private upgradeAllProviders;
|
37
38
|
private maybeGenerateCode;
|
@@ -233,11 +233,12 @@ class AssetManager {
|
|
233
233
|
return;
|
234
234
|
}
|
235
235
|
console.log('Installing updates', refs);
|
236
|
-
const updateAll = async () => {
|
236
|
+
const updateAll = async (task) => {
|
237
|
+
const progressListener = new progressListener_1.TaskProgressListener(task);
|
237
238
|
try {
|
238
239
|
//We change to a temp dir to avoid issues with the current working directory
|
239
240
|
process.chdir(node_os_1.default.tmpdir());
|
240
|
-
await nodejs_registry_utils_1.Actions.install(
|
241
|
+
await nodejs_registry_utils_1.Actions.install(progressListener, refs, {});
|
241
242
|
await this.cleanupUnusedProviders();
|
242
243
|
}
|
243
244
|
catch (e) {
|
@@ -136,7 +136,7 @@ router.get('/provides/:type', async (req, res) => {
|
|
136
136
|
* assign port numbers to it etc.
|
137
137
|
*/
|
138
138
|
router.get('/consumes/resource/:resourceType/:portType/:name', async (req, res) => {
|
139
|
-
const operatorInfo = await operatorManager_1.operatorManager.getConsumerResourceInfo(req.kapeta.systemId, req.kapeta.instanceId, req.params.resourceType, req.params.portType, req.params.name, req.kapeta.environment);
|
139
|
+
const operatorInfo = await operatorManager_1.operatorManager.getConsumerResourceInfo(req.kapeta.systemId, req.kapeta.instanceId, req.params.resourceType, req.params.portType, req.params.name, req.kapeta.environment, req.query.ensure !== 'false');
|
140
140
|
res.send(operatorInfo);
|
141
141
|
});
|
142
142
|
/**
|
@@ -154,7 +154,7 @@ router.get('/consumes/:resourceName/:type', (req, res) => {
|
|
154
154
|
* If the remote service is not already running it will be started
|
155
155
|
*/
|
156
156
|
router.get('/operator/:instanceId', async (req, res) => {
|
157
|
-
const operatorInfo = await instanceManager_1.instanceManager.getInstanceOperator(req.kapeta.systemId, req.params.instanceId, req.kapeta.environment);
|
157
|
+
const operatorInfo = await instanceManager_1.instanceManager.getInstanceOperator(req.kapeta.systemId, req.params.instanceId, req.kapeta.environment, req.query.ensure !== 'false');
|
158
158
|
res.send(operatorInfo);
|
159
159
|
});
|
160
160
|
exports.default = router;
|
@@ -5,7 +5,9 @@
|
|
5
5
|
/// <reference types="node" />
|
6
6
|
import FSExtra from 'fs-extra';
|
7
7
|
import Docker from 'dockerode';
|
8
|
-
import {
|
8
|
+
import { InstanceInfo, LogEntry } from './types';
|
9
|
+
import { Task } from './taskManager';
|
10
|
+
import { LocalInstanceHealth } from '@kapeta/schemas';
|
9
11
|
type StringMap = {
|
10
12
|
[key: string]: string;
|
11
13
|
};
|
@@ -50,11 +52,11 @@ declare class ContainerManager {
|
|
50
52
|
getContainerByName(containerName: string): Promise<ContainerInfo | undefined>;
|
51
53
|
pull(image: string): Promise<boolean>;
|
52
54
|
toDockerMounts(mounts: StringMap): DockerMounts[];
|
53
|
-
toDockerHealth(health:
|
55
|
+
toDockerHealth(health: LocalInstanceHealth): {
|
54
56
|
Test: string[];
|
55
57
|
Interval: number;
|
56
58
|
Timeout: number;
|
57
|
-
Retries:
|
59
|
+
Retries: any;
|
58
60
|
};
|
59
61
|
private applyHash;
|
60
62
|
ensureContainer(opts: any): Promise<Docker.Container>;
|
@@ -74,6 +76,7 @@ declare class ContainerManager {
|
|
74
76
|
getLogs(instance: InstanceInfo): Promise<LogEntry[]>;
|
75
77
|
stopLogListening(systemId: string, instanceId: string): Promise<void>;
|
76
78
|
ensureLogListening(systemId: string, instanceId: string, handler: (log: LogEntry) => void): Promise<void>;
|
79
|
+
buildDockerImage(dockerFile: string, imageName: string): Task<void>;
|
77
80
|
}
|
78
81
|
declare class ClosableLogStream {
|
79
82
|
private readonly stream;
|
@@ -19,6 +19,7 @@ const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-co
|
|
19
19
|
const node_uuid_1 = __importDefault(require("node-uuid"));
|
20
20
|
const md5_1 = __importDefault(require("md5"));
|
21
21
|
const utils_1 = require("./utils/utils");
|
22
|
+
const types_1 = require("./types");
|
22
23
|
const nodejs_api_client_1 = require("@kapeta/nodejs-api-client");
|
23
24
|
const taskManager_1 = require("./taskManager");
|
24
25
|
const node_events_1 = require("node:events");
|
@@ -207,14 +208,8 @@ class ContainerManager {
|
|
207
208
|
return this._docker;
|
208
209
|
}
|
209
210
|
async getContainerByName(containerName) {
|
210
|
-
|
211
|
-
|
212
|
-
return container.Names.indexOf(`/${containerName}`) > -1;
|
213
|
-
});
|
214
|
-
if (out) {
|
215
|
-
return this.get(out.Id);
|
216
|
-
}
|
217
|
-
return undefined;
|
211
|
+
// The container can be fetched by name or by id using the same API call
|
212
|
+
return this.get(containerName);
|
218
213
|
}
|
219
214
|
async pull(image) {
|
220
215
|
let [imageName, tag] = image.split(/:/);
|
@@ -279,6 +274,10 @@ class ContainerManager {
|
|
279
274
|
};
|
280
275
|
}
|
281
276
|
const chunk = chunks[data.id];
|
277
|
+
if (data.stream) {
|
278
|
+
// Emit raw output to the task log
|
279
|
+
task.addLog(data.stream);
|
280
|
+
}
|
282
281
|
switch (data.status) {
|
283
282
|
case DockerPullEventTypes.PreparingPhase:
|
284
283
|
case DockerPullEventTypes.WaitingPhase:
|
@@ -510,7 +509,8 @@ class ContainerManager {
|
|
510
509
|
const newName = 'deleting-' + node_uuid_1.default.v4();
|
511
510
|
// Rename the container first to avoid name conflicts if people start the same container
|
512
511
|
await container.rename({ name: newName });
|
513
|
-
|
512
|
+
const newContainer = this.docker().getContainer(newName);
|
513
|
+
await newContainer.remove({ force: !!opts?.force });
|
514
514
|
}
|
515
515
|
/**
|
516
516
|
*
|
@@ -520,7 +520,7 @@ class ContainerManager {
|
|
520
520
|
async get(name) {
|
521
521
|
let dockerContainer = null;
|
522
522
|
try {
|
523
|
-
dockerContainer =
|
523
|
+
dockerContainer = this.docker().getContainer(name);
|
524
524
|
await dockerContainer.stats();
|
525
525
|
}
|
526
526
|
catch (err) {
|
@@ -545,7 +545,7 @@ class ContainerManager {
|
|
545
545
|
},
|
546
546
|
];
|
547
547
|
}
|
548
|
-
return containerInfo.getLogs();
|
548
|
+
return await containerInfo.getLogs();
|
549
549
|
}
|
550
550
|
async stopLogListening(systemId, instanceId) {
|
551
551
|
const containerName = await (0, utils_1.getBlockInstanceContainerName)(systemId, instanceId);
|
@@ -616,6 +616,28 @@ class ContainerManager {
|
|
616
616
|
// Ignore
|
617
617
|
}
|
618
618
|
}
|
619
|
+
buildDockerImage(dockerFile, imageName) {
|
620
|
+
const taskName = `Building docker image: ${imageName}`;
|
621
|
+
const processor = async (task) => {
|
622
|
+
const timeStarted = Date.now();
|
623
|
+
const stream = await this.docker().buildImage({
|
624
|
+
context: path_1.default.dirname(dockerFile),
|
625
|
+
src: [path_1.default.basename(dockerFile)],
|
626
|
+
}, {
|
627
|
+
t: imageName,
|
628
|
+
dockerfile: path_1.default.basename(dockerFile),
|
629
|
+
});
|
630
|
+
await processJsonStream(`image:build:${imageName}`, stream, (data) => {
|
631
|
+
if (data.stream) {
|
632
|
+
// Emit raw output to the task log
|
633
|
+
task.addLog(data.stream);
|
634
|
+
}
|
635
|
+
});
|
636
|
+
};
|
637
|
+
return taskManager_1.taskManager.add(`docker:image:build:${imageName}`, processor, {
|
638
|
+
name: taskName,
|
639
|
+
});
|
640
|
+
}
|
619
641
|
}
|
620
642
|
function readLogBuffer(logBuffer) {
|
621
643
|
const out = [];
|
@@ -830,15 +852,73 @@ class ContainerInfo {
|
|
830
852
|
timestamps: true,
|
831
853
|
});
|
832
854
|
const out = readLogBuffer(logs);
|
833
|
-
if (out.length
|
834
|
-
out
|
835
|
-
time: Date.now(),
|
836
|
-
message: 'No logs found for container',
|
837
|
-
level: 'INFO',
|
838
|
-
source: 'stdout',
|
839
|
-
});
|
855
|
+
if (out.length > 0) {
|
856
|
+
return out;
|
840
857
|
}
|
841
|
-
|
858
|
+
const status = await this.status();
|
859
|
+
const healthLogs = status?.Health?.Log
|
860
|
+
? status?.Health?.Log.map((log) => {
|
861
|
+
return {
|
862
|
+
source: 'stdout',
|
863
|
+
level: log.ExitCode === 0 ? 'INFO' : 'ERROR',
|
864
|
+
time: Date.now(),
|
865
|
+
message: 'Health check: ' + log.Output,
|
866
|
+
};
|
867
|
+
})
|
868
|
+
: [];
|
869
|
+
if (status?.Running) {
|
870
|
+
return [
|
871
|
+
{
|
872
|
+
source: 'stdout',
|
873
|
+
level: 'INFO',
|
874
|
+
time: Date.now(),
|
875
|
+
message: 'Container is starting...',
|
876
|
+
},
|
877
|
+
...healthLogs,
|
878
|
+
];
|
879
|
+
}
|
880
|
+
if (status?.Restarting) {
|
881
|
+
return [
|
882
|
+
{
|
883
|
+
source: 'stdout',
|
884
|
+
level: 'INFO',
|
885
|
+
time: Date.now(),
|
886
|
+
message: 'Container is restarting...',
|
887
|
+
},
|
888
|
+
...healthLogs,
|
889
|
+
];
|
890
|
+
}
|
891
|
+
if (status?.Paused) {
|
892
|
+
return [
|
893
|
+
{
|
894
|
+
source: 'stdout',
|
895
|
+
level: 'INFO',
|
896
|
+
time: Date.now(),
|
897
|
+
message: 'Container is paused...',
|
898
|
+
},
|
899
|
+
...healthLogs,
|
900
|
+
];
|
901
|
+
}
|
902
|
+
if (status?.Error) {
|
903
|
+
return [
|
904
|
+
{
|
905
|
+
source: 'stderr',
|
906
|
+
level: 'ERROR',
|
907
|
+
time: Date.now(),
|
908
|
+
message: 'Container failed to start:\n' + status.Error,
|
909
|
+
},
|
910
|
+
...healthLogs,
|
911
|
+
];
|
912
|
+
}
|
913
|
+
return [
|
914
|
+
{
|
915
|
+
source: 'stdout',
|
916
|
+
level: 'INFO',
|
917
|
+
time: Date.now(),
|
918
|
+
message: 'Container not running',
|
919
|
+
...healthLogs,
|
920
|
+
},
|
921
|
+
];
|
842
922
|
}
|
843
923
|
}
|
844
924
|
exports.ContainerInfo = ContainerInfo;
|
@@ -847,11 +927,11 @@ function getExtraHosts(dockerVersion) {
|
|
847
927
|
const [major, minor] = dockerVersion.split('.');
|
848
928
|
if (parseInt(major) >= 20 && parseInt(minor) >= 10) {
|
849
929
|
// Docker 20.10+ on Linux supports adding host.docker.internal to point to host-gateway
|
850
|
-
return [
|
930
|
+
return [`${types_1.DOCKER_HOST_INTERNAL}:host-gateway`];
|
851
931
|
}
|
852
932
|
// Docker versions lower than 20.10 needs an actual IP address. We use the default network bridge which
|
853
933
|
// is always 172.17.0.1
|
854
|
-
return [
|
934
|
+
return [`${types_1.DOCKER_HOST_INTERNAL}:172.17.0.1`];
|
855
935
|
}
|
856
936
|
return undefined;
|
857
937
|
}
|
@@ -2,8 +2,9 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
-
import { EnvironmentType, InstanceInfo, LogEntry
|
5
|
+
import { EnvironmentType, InstanceInfo, LogEntry } from './types';
|
6
6
|
import { Task } from './taskManager';
|
7
|
+
import { InstanceOperator } from '@kapeta/sdk-config';
|
7
8
|
export declare class InstanceManager {
|
8
9
|
private _interval;
|
9
10
|
private readonly _instances;
|
@@ -14,6 +15,7 @@ export declare class InstanceManager {
|
|
14
15
|
getInstancesForPlan(systemId: string): Promise<InstanceInfo[]>;
|
15
16
|
getInstance(systemId: string, instanceId: string): InstanceInfo | undefined;
|
16
17
|
private exclusive;
|
18
|
+
private isLocked;
|
17
19
|
getLogs(systemId: string, instanceId: string): Promise<LogEntry[]>;
|
18
20
|
saveInternalInstance(instance: InstanceInfo): Promise<InstanceInfo>;
|
19
21
|
/**
|
@@ -25,7 +27,7 @@ export declare class InstanceManager {
|
|
25
27
|
markAsStopped(systemId: string, instanceId: string): Promise<void>;
|
26
28
|
startAllForPlan(systemId: string): Promise<Task<InstanceInfo[]>>;
|
27
29
|
stopAllForPlan(systemId: string): Task<void>;
|
28
|
-
getInstanceOperator(systemId: string, instanceId: string, environment?: EnvironmentType): Promise<
|
30
|
+
getInstanceOperator(systemId: string, instanceId: string, environment?: EnvironmentType, ensureContainer?: boolean): Promise<InstanceOperator<any, any>>;
|
29
31
|
stop(systemId: string, instanceId: string): Promise<void>;
|
30
32
|
private stopInner;
|
31
33
|
start(systemId: string, instanceId: string, checkForSingleton?: boolean): Promise<InstanceInfo | Task<InstanceInfo>>;
|
@@ -79,6 +79,9 @@ class InstanceManager {
|
|
79
79
|
//console.log(`Releasing lock for ${key}`, this.instanceLocks.isBusy(key));
|
80
80
|
return result;
|
81
81
|
}
|
82
|
+
isLocked(systemId, instanceId) {
|
83
|
+
return this.instanceLocks.isBusy(`${systemId}/${instanceId}`);
|
84
|
+
}
|
82
85
|
async getLogs(systemId, instanceId) {
|
83
86
|
const instance = this.getInstance(systemId, instanceId);
|
84
87
|
if (!instance) {
|
@@ -212,7 +215,9 @@ class InstanceManager {
|
|
212
215
|
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
213
216
|
const instance = lodash_1.default.find(this._instances, { systemId, instanceId });
|
214
217
|
if (instance && instance.owner === types_1.InstanceOwner.EXTERNAL && instance.status !== types_1.InstanceStatus.STOPPED) {
|
215
|
-
instance.status
|
218
|
+
if (instance.status != types_1.InstanceStatus.FAILED) {
|
219
|
+
instance.status = types_1.InstanceStatus.STOPPED;
|
220
|
+
}
|
216
221
|
instance.pid = null;
|
217
222
|
instance.health = null;
|
218
223
|
socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
|
@@ -266,7 +271,7 @@ class InstanceManager {
|
|
266
271
|
name: `Stopping plan ${systemId}`,
|
267
272
|
});
|
268
273
|
}
|
269
|
-
async getInstanceOperator(systemId, instanceId, environment) {
|
274
|
+
async getInstanceOperator(systemId, instanceId, environment, ensureContainer = true) {
|
270
275
|
const blockInstance = await assetManager_1.assetManager.getBlockInstance(systemId, instanceId);
|
271
276
|
if (!blockInstance) {
|
272
277
|
throw new Error(`Instance not found: ${systemId}/${instanceId}`);
|
@@ -277,30 +282,48 @@ class InstanceManager {
|
|
277
282
|
throw new Error(`Block not found: ${blockRef}`);
|
278
283
|
}
|
279
284
|
const operatorDefinition = await definitionsManager_1.definitionsManager.getDefinition(block.kind);
|
280
|
-
if (!operatorDefinition
|
285
|
+
if (!operatorDefinition) {
|
286
|
+
throw new Error(`Operator not found: ${block.kind}`);
|
287
|
+
}
|
288
|
+
if (operatorDefinition.definition.kind !== types_1.KIND_BLOCK_TYPE_OPERATOR) {
|
289
|
+
throw new Error(`Block is not an operator: ${blockRef}`);
|
290
|
+
}
|
291
|
+
if (!operatorDefinition.definition.spec.local) {
|
281
292
|
throw new Error(`Operator block has no local definition: ${blockRef}`);
|
282
293
|
}
|
283
294
|
const localConfig = operatorDefinition.definition.spec.local;
|
284
|
-
|
285
|
-
if (
|
286
|
-
instance = await
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
295
|
+
const ports = {};
|
296
|
+
if (ensureContainer) {
|
297
|
+
let instance = await this.start(systemId, instanceId);
|
298
|
+
if (instance instanceof taskManager_1.Task) {
|
299
|
+
instance = await instance.wait();
|
300
|
+
}
|
301
|
+
const container = await containerManager_1.containerManager.get(instance.pid);
|
302
|
+
if (!container) {
|
303
|
+
throw new Error(`Container not found: ${instance.pid}`);
|
304
|
+
}
|
305
|
+
const portInfo = await container.getPorts();
|
306
|
+
if (!portInfo) {
|
307
|
+
throw new Error(`No ports found for instance: ${instanceId}`);
|
308
|
+
}
|
309
|
+
Object.entries(portInfo).forEach(([key, value]) => {
|
310
|
+
ports[key] = {
|
311
|
+
protocol: value.protocol,
|
312
|
+
port: parseInt(value.hostPort),
|
313
|
+
};
|
314
|
+
});
|
291
315
|
}
|
292
|
-
|
293
|
-
|
294
|
-
|
316
|
+
else {
|
317
|
+
// If we're not ensuring the container is running we just get the ports from the local config
|
318
|
+
const instancePorts = await (0, utils_1.getOperatorInstancePorts)(systemId, instanceId, localConfig);
|
319
|
+
instancePorts.forEach((port) => {
|
320
|
+
ports[port.portType] = {
|
321
|
+
protocol: port.protocol,
|
322
|
+
port: port.hostPort,
|
323
|
+
};
|
324
|
+
});
|
295
325
|
}
|
296
|
-
const hostname =
|
297
|
-
const ports = {};
|
298
|
-
Object.entries(portInfo).forEach(([key, value]) => {
|
299
|
-
ports[key] = {
|
300
|
-
protocol: value.protocol,
|
301
|
-
port: parseInt(value.hostPort),
|
302
|
-
};
|
303
|
-
});
|
326
|
+
const hostname = (0, utils_1.getRemoteHostForEnvironment)(environment);
|
304
327
|
return {
|
305
328
|
hostname,
|
306
329
|
ports,
|
@@ -345,6 +368,7 @@ class InstanceManager {
|
|
345
368
|
if (changeDesired && instance.desiredStatus !== types_1.DesiredInstanceStatus.EXTERNAL) {
|
346
369
|
instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
|
347
370
|
}
|
371
|
+
const wasFailed = instance.status === types_1.InstanceStatus.FAILED;
|
348
372
|
instance.status = types_1.InstanceStatus.STOPPING;
|
349
373
|
socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
|
350
374
|
console.log('Stopping instance: %s::%s [desired: %s] [intentional: %s]', systemId, instanceId, instance.desiredStatus, changeDesired);
|
@@ -355,7 +379,12 @@ class InstanceManager {
|
|
355
379
|
const container = await containerManager_1.containerManager.getContainerByName(containerName);
|
356
380
|
if (container) {
|
357
381
|
try {
|
358
|
-
|
382
|
+
if (wasFailed) {
|
383
|
+
await container.remove();
|
384
|
+
}
|
385
|
+
else {
|
386
|
+
await container.stop();
|
387
|
+
}
|
359
388
|
instance.status = types_1.InstanceStatus.STOPPED;
|
360
389
|
socketManager_1.socketManager.emitSystemEvent(systemId, socketManager_1.EVENT_STATUS_CHANGED, instance);
|
361
390
|
this.save();
|
@@ -411,7 +440,7 @@ class InstanceManager {
|
|
411
440
|
existingInstance = undefined;
|
412
441
|
}
|
413
442
|
}
|
414
|
-
if (existingInstance
|
443
|
+
if (existingInstance && existingInstance.pid) {
|
415
444
|
if (existingInstance.status === types_1.InstanceStatus.READY) {
|
416
445
|
// Instance is already running
|
417
446
|
return existingInstance;
|
@@ -472,8 +501,7 @@ class InstanceManager {
|
|
472
501
|
return existingInstance;
|
473
502
|
}
|
474
503
|
}
|
475
|
-
const
|
476
|
-
const resolvedConfig = (0, utils_1.getResolvedConfiguration)(blockSpec.configuration, instanceConfig, blockInstance.defaultConfiguration);
|
504
|
+
const resolvedConfig = await configManager_1.configManager.getConfigForBlockInstance(systemId, instanceId);
|
477
505
|
const task = taskManager_1.taskManager.add(`instance:start:${systemId}:${instanceId}`, async () => {
|
478
506
|
const runner = new BlockInstanceRunner_1.BlockInstanceRunner(systemId);
|
479
507
|
const startTime = Date.now();
|
@@ -501,8 +529,7 @@ class InstanceManager {
|
|
501
529
|
];
|
502
530
|
const out = await this.saveInternalInstance({
|
503
531
|
...instance,
|
504
|
-
type: types_1.InstanceType.
|
505
|
-
pid: null,
|
532
|
+
type: types_1.InstanceType.DOCKER,
|
506
533
|
health: null,
|
507
534
|
portType: DEFAULT_HEALTH_PORT_TYPE,
|
508
535
|
status: types_1.InstanceStatus.FAILED,
|
@@ -622,9 +649,8 @@ class InstanceManager {
|
|
622
649
|
}
|
623
650
|
if (instance.status !== newStatus) {
|
624
651
|
const oldStatus = instance.status;
|
625
|
-
const skipUpdate = (
|
626
|
-
|
627
|
-
instance.status === types_1.InstanceStatus.STOPPING) ||
|
652
|
+
const skipUpdate = ([types_1.InstanceStatus.READY, types_1.InstanceStatus.UNHEALTHY].includes(newStatus) &&
|
653
|
+
instance.status === types_1.InstanceStatus.STOPPING) ||
|
628
654
|
(newStatus === types_1.InstanceStatus.STOPPED &&
|
629
655
|
instance.status === types_1.InstanceStatus.STARTING &&
|
630
656
|
instance.desiredStatus === types_1.DesiredInstanceStatus.RUN);
|
@@ -637,7 +663,7 @@ class InstanceManager {
|
|
637
663
|
}
|
638
664
|
}
|
639
665
|
if (instance.desiredStatus === types_1.DesiredInstanceStatus.RUN &&
|
640
|
-
[types_1.InstanceStatus.STOPPED, types_1.InstanceStatus.
|
666
|
+
[types_1.InstanceStatus.STOPPED, types_1.InstanceStatus.STOPPING].includes(newStatus)) {
|
641
667
|
//If the instance is stopped but we want it to run, start it
|
642
668
|
try {
|
643
669
|
await this.start(instance.systemId, instance.instanceId);
|
@@ -708,10 +734,23 @@ class InstanceManager {
|
|
708
734
|
return types_1.InstanceStatus.READY;
|
709
735
|
}
|
710
736
|
if (statusType === 'created') {
|
737
|
+
if (state.ExitCode !== undefined && state.ExitCode !== 0) {
|
738
|
+
// Failed during creation. Exit code is not always reliable though
|
739
|
+
if (state.Error) {
|
740
|
+
return types_1.InstanceStatus.FAILED;
|
741
|
+
}
|
742
|
+
else {
|
743
|
+
return types_1.InstanceStatus.STOPPED;
|
744
|
+
}
|
745
|
+
}
|
711
746
|
return types_1.InstanceStatus.STARTING;
|
712
747
|
}
|
713
748
|
if (statusType === 'exited' || statusType === 'dead') {
|
714
|
-
|
749
|
+
if (!state.Error) {
|
750
|
+
// Exit code is not always reliable - if there is no error we assume it's stopped
|
751
|
+
return types_1.InstanceStatus.STOPPED;
|
752
|
+
}
|
753
|
+
return types_1.InstanceStatus.FAILED;
|
715
754
|
}
|
716
755
|
if (statusType === 'removing') {
|
717
756
|
return types_1.InstanceStatus.BUSY;
|
@@ -4,11 +4,13 @@
|
|
4
4
|
*/
|
5
5
|
import { DefinitionInfo } from '@kapeta/local-cluster-config';
|
6
6
|
import { ContainerInfo } from './containerManager';
|
7
|
-
import {
|
7
|
+
import { AnyMap, EnvironmentType } from './types';
|
8
|
+
import { LocalInstance } from '@kapeta/schemas';
|
9
|
+
import { ResourceInfo } from '@kapeta/sdk-config';
|
8
10
|
declare class Operator {
|
9
11
|
private readonly _data;
|
10
12
|
constructor(data: DefinitionInfo);
|
11
|
-
getLocalData():
|
13
|
+
getLocalData(): LocalInstance;
|
12
14
|
getDefinitionInfo(): DefinitionInfo;
|
13
15
|
getCredentials(): any;
|
14
16
|
}
|
@@ -24,7 +26,8 @@ declare class OperatorManager {
|
|
24
26
|
/**
|
25
27
|
* Get information about a specific consumed resource
|
26
28
|
*/
|
27
|
-
getConsumerResourceInfo(systemId: string, fromServiceId: string, resourceType: string, portType: string, name: string, environment?: EnvironmentType): Promise<
|
29
|
+
getConsumerResourceInfo(systemId: string, fromServiceId: string, resourceType: string, portType: string, name: string, environment?: EnvironmentType, ensureContainer?: boolean): Promise<ResourceInfo<any, any>>;
|
30
|
+
getOperatorPorts(systemId: string, kind: string, version: string): Promise<AnyMap>;
|
28
31
|
/**
|
29
32
|
* Ensure we have a running operator of given type
|
30
33
|
*
|
@@ -68,7 +68,7 @@ class OperatorManager {
|
|
68
68
|
/**
|
69
69
|
* Get information about a specific consumed resource
|
70
70
|
*/
|
71
|
-
async getConsumerResourceInfo(systemId, fromServiceId, resourceType, portType, name, environment) {
|
71
|
+
async getConsumerResourceInfo(systemId, fromServiceId, resourceType, portType, name, environment, ensureContainer = true) {
|
72
72
|
systemId = (0, nodejs_utils_1.normalizeKapetaUri)(systemId);
|
73
73
|
const plans = await definitionsManager_1.definitionsManager.getDefinitions(KIND_PLAN);
|
74
74
|
const planUri = (0, nodejs_utils_1.parseKapetaUri)(systemId);
|
@@ -96,18 +96,20 @@ class OperatorManager {
|
|
96
96
|
const kindUri = (0, nodejs_utils_1.parseKapetaUri)(blockResource.kind);
|
97
97
|
const operator = await this.getOperator(resourceType, kindUri.version);
|
98
98
|
const credentials = operator.getCredentials();
|
99
|
-
|
100
|
-
|
101
|
-
|
99
|
+
if (ensureContainer) {
|
100
|
+
await this.ensureOperator(systemId, resourceType, kindUri.version);
|
101
|
+
}
|
102
|
+
const hostPort = await serviceManager_1.serviceManager.ensureServicePort(systemId, resourceType, portType);
|
103
|
+
if (!hostPort) {
|
102
104
|
throw new Error('Unknown resource port type : ' + resourceType + '#' + portType);
|
103
105
|
}
|
104
106
|
const dbName = name + '_' + fromServiceId.replace(/[^a-z0-9]/gi, '');
|
105
107
|
const safeName = dbName.replace('_', '-');
|
106
108
|
return {
|
107
|
-
host:
|
108
|
-
port:
|
109
|
+
host: (0, utils_1.getRemoteHostForEnvironment)(environment),
|
110
|
+
port: hostPort,
|
109
111
|
type: portType,
|
110
|
-
protocol:
|
112
|
+
protocol: 'tcp',
|
111
113
|
options: {
|
112
114
|
// expose as fullName since that is not operator specific, but unique
|
113
115
|
fullName: safeName,
|
@@ -116,6 +118,26 @@ class OperatorManager {
|
|
116
118
|
credentials,
|
117
119
|
};
|
118
120
|
}
|
121
|
+
async getOperatorPorts(systemId, kind, version) {
|
122
|
+
const operator = await this.getOperator(kind, version);
|
123
|
+
const operatorData = operator.getLocalData();
|
124
|
+
const portTypes = Object.keys(operatorData.ports);
|
125
|
+
portTypes.sort();
|
126
|
+
const ports = {};
|
127
|
+
for (let i = 0; i < portTypes.length; i++) {
|
128
|
+
const portType = portTypes[i];
|
129
|
+
let containerPortInfo = operatorData.ports[portType];
|
130
|
+
const hostPort = await serviceManager_1.serviceManager.ensureServicePort(systemId, kind, portType);
|
131
|
+
const portInfo = (0, utils_1.toPortInfo)(containerPortInfo);
|
132
|
+
const portId = portInfo.port + '/' + portInfo.type;
|
133
|
+
ports[portId] = {
|
134
|
+
type: portType,
|
135
|
+
hostPort,
|
136
|
+
protocol: portInfo.type,
|
137
|
+
};
|
138
|
+
}
|
139
|
+
return ports;
|
140
|
+
}
|
119
141
|
/**
|
120
142
|
* Ensure we have a running operator of given type
|
121
143
|
*
|
@@ -129,20 +151,7 @@ class OperatorManager {
|
|
129
151
|
return await this.operatorLock.acquire(key, async () => {
|
130
152
|
const operator = await this.getOperator(kind, version);
|
131
153
|
const operatorData = operator.getLocalData();
|
132
|
-
const
|
133
|
-
portTypes.sort();
|
134
|
-
const ports = {};
|
135
|
-
for (let i = 0; i < portTypes.length; i++) {
|
136
|
-
const portType = portTypes[i];
|
137
|
-
let containerPortInfo = operatorData.ports[portType];
|
138
|
-
const hostPort = await serviceManager_1.serviceManager.ensureServicePort(systemId, kind, portType);
|
139
|
-
const portInfo = (0, utils_1.toPortInfo)(containerPortInfo);
|
140
|
-
const portId = portInfo.port + '/' + portInfo.type;
|
141
|
-
ports[portId] = {
|
142
|
-
type: portType,
|
143
|
-
hostPort,
|
144
|
-
};
|
145
|
-
}
|
154
|
+
const ports = await this.getOperatorPorts(systemId, kind, version);
|
146
155
|
const nameParts = [systemId, kind.toLowerCase(), version];
|
147
156
|
const containerName = `kapeta-resource-${(0, md5_1.default)(nameParts.join('_'))}`;
|
148
157
|
const PortBindings = {};
|
@@ -154,14 +163,14 @@ class OperatorManager {
|
|
154
163
|
[containerManager_1.COMPOSE_LABEL_SERVICE]: [kind, version].join('_').replace(/[^a-z0-9]/gi, '_'),
|
155
164
|
};
|
156
165
|
const operatorMetadata = operator.getDefinitionInfo().definition.metadata;
|
157
|
-
const
|
166
|
+
const hostIp = (0, utils_1.getDockerHostIp)();
|
158
167
|
const ExposedPorts = {};
|
159
168
|
lodash_1.default.forEach(ports, (portInfo, containerPort) => {
|
160
169
|
ExposedPorts['' + containerPort] = {};
|
161
170
|
PortBindings['' + containerPort] = [
|
162
171
|
{
|
163
172
|
HostPort: '' + portInfo.hostPort,
|
164
|
-
HostIp:
|
173
|
+
HostIp: hostIp,
|
165
174
|
},
|
166
175
|
];
|
167
176
|
Labels[containerManager_1.CONTAINER_LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|