@kapeta/local-cluster-service 0.40.4 → 0.40.5

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 CHANGED
@@ -1,3 +1,10 @@
1
+ ## [0.40.5](https://github.com/kapetacom/local-cluster-service/compare/v0.40.4...v0.40.5) (2024-04-09)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Move most / big env vars to config file instead ([#137](https://github.com/kapetacom/local-cluster-service/issues/137)) ([21c5c50](https://github.com/kapetacom/local-cluster-service/commit/21c5c50341665c2919b9c1ed718b933a10a459fb))
7
+
1
8
  ## [0.40.4](https://github.com/kapetacom/local-cluster-service/compare/v0.40.3...v0.40.4) (2024-04-04)
2
9
 
3
10
 
@@ -22,7 +22,7 @@ declare class OperatorManager {
22
22
  /**
23
23
  * Get operator definition for resource type
24
24
  */
25
- getOperator(fullName: string, version: string): Promise<Operator>;
25
+ getOperator(fullName: string, version: string): Promise<Operator | null>;
26
26
  /**
27
27
  * Get information about a specific consumed resource
28
28
  */
@@ -35,7 +35,7 @@ declare class OperatorManager {
35
35
  * @param kind the full name - e.g. myhandle/rabbitmq
36
36
  * @param version the version of the operator
37
37
  */
38
- ensureOperator(systemId: string, kind: string, version: string): Promise<ContainerInfo>;
38
+ ensureOperator(systemId: string, kind: string, version: string): Promise<ContainerInfo | null>;
39
39
  }
40
40
  export declare const operatorManager: OperatorManager;
41
41
  export {};
@@ -61,7 +61,8 @@ class OperatorManager {
61
61
  throw new Error(`Unknown operator type: ${fullName}:${version}`);
62
62
  }
63
63
  if (!operator.definition.spec || !operator.definition.spec.local) {
64
- throw new Error(`Operator missing local definition: ${fullName}:${version}`);
64
+ console.warn(`Operator missing local definition: ${fullName}:${version}`);
65
+ return null;
65
66
  }
66
67
  return new Operator(operator);
67
68
  }
@@ -95,6 +96,16 @@ class OperatorManager {
95
96
  }
96
97
  const kindUri = (0, nodejs_utils_1.parseKapetaUri)(blockResource.kind);
97
98
  const operator = await this.getOperator(resourceType, kindUri.version);
99
+ if (!operator) {
100
+ return {
101
+ host: '',
102
+ port: 0,
103
+ type: '',
104
+ protocol: '',
105
+ options: {},
106
+ credentials: {},
107
+ };
108
+ }
98
109
  const credentials = operator.getCredentials();
99
110
  if (ensureContainer) {
100
111
  await this.ensureOperator(systemId, resourceType, kindUri.version);
@@ -120,6 +131,9 @@ class OperatorManager {
120
131
  }
121
132
  async getOperatorPorts(systemId, kind, version) {
122
133
  const operator = await this.getOperator(kind, version);
134
+ if (!operator) {
135
+ return {};
136
+ }
123
137
  const operatorData = operator.getLocalData();
124
138
  const portTypes = Object.keys(operatorData.ports);
125
139
  portTypes.sort();
@@ -150,6 +164,9 @@ class OperatorManager {
150
164
  const key = `${systemId}#${kind}:${version}`;
151
165
  return await this.operatorLock.acquire(key, async () => {
152
166
  const operator = await this.getOperator(kind, version);
167
+ if (!operator) {
168
+ return null;
169
+ }
153
170
  const operatorData = operator.getLocalData();
154
171
  const ports = await this.getOperatorPorts(systemId, kind, version);
155
172
  const nameParts = [systemId, kind.toLowerCase(), version];
@@ -25,4 +25,5 @@ export declare class BlockInstanceRunner {
25
25
  private getServiceBlockPortBindings;
26
26
  private ensureContainer;
27
27
  private _handleContainer;
28
+ private getFileHash;
28
29
  }
@@ -23,9 +23,12 @@ const node_path_1 = __importDefault(require("node:path"));
23
23
  const taskManager_1 = require("../taskManager");
24
24
  const InternalConfigProvider_1 = require("./InternalConfigProvider");
25
25
  const config_mapper_1 = require("@kapeta/config-mapper");
26
+ const crypto_1 = __importDefault(require("crypto"));
26
27
  const KAPETA_SYSTEM_ID = 'KAPETA_SYSTEM_ID';
27
28
  const KAPETA_BLOCK_REF = 'KAPETA_BLOCK_REF';
28
29
  const KAPETA_INSTANCE_ID = 'KAPETA_INSTANCE_ID';
30
+ // The maximum length of an environment variable - this is to avoid hitting the command line length limits
31
+ const MAX_ENV_LENGTH = 256;
29
32
  /**
30
33
  * Needed when running local docker containers as part of plan
31
34
  * @type {string[]}
@@ -111,21 +114,28 @@ class BlockInstanceRunner {
111
114
  const realBaseDir = await fs_extra_1.default.realpath(baseDir);
112
115
  const internalConfigProvider = await (0, InternalConfigProvider_1.createInternalConfigProvider)(this._systemId, blockInstance.id, assetVersion);
113
116
  // Resolve the environment variables
114
- const envVars = await (0, config_mapper_1.resolveKapetaVariables)(realBaseDir, internalConfigProvider);
117
+ const variables = await (0, config_mapper_1.resolveKapetaVariables)(realBaseDir, internalConfigProvider);
115
118
  // Write out the config templates if they exist
116
- await (0, config_mapper_1.writeConfigTemplates)(envVars, realBaseDir);
119
+ await (0, config_mapper_1.writeConfigTemplates)(variables, realBaseDir);
120
+ const env = await (0, config_mapper_1.getEnvironmentVariables)(variables);
121
+ // Gets the tmp path to write the config file
122
+ const configFilePath = (0, config_mapper_1.getConfigFilePath)(blockInstance.id);
123
+ // Write the config to a file - will be read by the SDKs
124
+ await (0, config_mapper_1.writeEnvConfigFile)(variables, configFilePath);
125
+ // Add the config file path to the environment variables
126
+ env[config_mapper_1.KAPETA_CONFIG_ENV_VAR] = configFilePath;
117
127
  let processInfo;
118
128
  if (providerVersion.definition.kind === types_1.KIND_BLOCK_TYPE_OPERATOR) {
119
- processInfo = await this._startOperatorProcess(blockInstance, blockUri, providerVersion, envVars);
129
+ processInfo = await this._startOperatorProcess(blockInstance, blockUri, providerVersion, env);
120
130
  }
121
131
  else {
122
132
  //We need a port type to know how to connect to the block consistently
123
133
  const portTypes = getServiceProviderPorts(assetVersion, providerVersion);
124
134
  if (blockUri.version === 'local') {
125
- processInfo = await this._startLocalProcess(blockInstance, blockUri, envVars, assetVersion);
135
+ processInfo = await this._startLocalProcess(blockInstance, blockUri, env, assetVersion);
126
136
  }
127
137
  else {
128
- processInfo = await this._startDockerProcess(blockInstance, blockUri, envVars, assetVersion);
138
+ processInfo = await this._startDockerProcess(blockInstance, blockUri, env, assetVersion);
129
139
  }
130
140
  if (portTypes.length > 0) {
131
141
  processInfo.portType = portTypes[0];
@@ -200,6 +210,14 @@ class BlockInstanceRunner {
200
210
  if (localContainer.healthcheck) {
201
211
  HealthCheck = containerManager_1.containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
202
212
  }
213
+ if (env[config_mapper_1.KAPETA_CONFIG_ENV_VAR]) {
214
+ // If we have a config file, we need to bind it to the container and adjust the env var
215
+ const localConfig = `/${config_mapper_1.KAPETA_ENV_CONFIG_FILE}`;
216
+ Binds.push(`${(0, containerManager_1.toLocalBindVolume)(env[config_mapper_1.KAPETA_CONFIG_ENV_VAR])}:${localConfig}:ro`);
217
+ // We also provide the hash to detect changes to the config file
218
+ env['KAPETA_CONFIG_HASH'] = await this.getFileHash(env[config_mapper_1.KAPETA_CONFIG_ENV_VAR]);
219
+ env[config_mapper_1.KAPETA_CONFIG_ENV_VAR] = localConfig;
220
+ }
203
221
  const Mounts = isDockerImage
204
222
  ? // For docker images we mount the local directory to the working directory
205
223
  containerManager_1.containerManager.toDockerMounts({
@@ -208,6 +226,15 @@ class BlockInstanceRunner {
208
226
  : // For dockerfiles we don't mount anything
209
227
  [];
210
228
  const systemUri = (0, nodejs_utils_1.parseKapetaUri)(this._systemId);
229
+ const Env = [
230
+ ...customEnvs,
231
+ ...DOCKER_ENV_VARS,
232
+ `KAPETA_LOCAL_CLUSTER_PORT=${clusterService_1.clusterService.getClusterServicePort()}`,
233
+ ...Object.entries({
234
+ ...env,
235
+ ...addonEnv,
236
+ }).map(([key, value]) => `${key}=${value}`),
237
+ ];
211
238
  return this.ensureContainer({
212
239
  ...dockerOpts,
213
240
  Image: dockerImage,
@@ -222,15 +249,7 @@ class BlockInstanceRunner {
222
249
  HealthCheck,
223
250
  ExposedPorts,
224
251
  Cmd: startCmd ? startCmd.split(/\s+/g) : [],
225
- Env: [
226
- ...customEnvs,
227
- ...DOCKER_ENV_VARS,
228
- `KAPETA_LOCAL_CLUSTER_PORT=${clusterService_1.clusterService.getClusterServicePort()}`,
229
- ...Object.entries({
230
- ...env,
231
- ...addonEnv,
232
- }).map(([key, value]) => `${key}=${value}`),
233
- ],
252
+ Env,
234
253
  HostConfig: {
235
254
  ...customHostConfigs,
236
255
  Binds: [
@@ -275,6 +294,15 @@ class BlockInstanceRunner {
275
294
  // For windows we need to default to root
276
295
  const innerHome = process.platform === 'win32' ? '/root/.kapeta' : local_cluster_config_1.default.getKapetaBasedir();
277
296
  const systemUri = (0, nodejs_utils_1.parseKapetaUri)(this._systemId);
297
+ const Binds = [`${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`];
298
+ if (env[config_mapper_1.KAPETA_CONFIG_ENV_VAR]) {
299
+ // If we have a config file, we need to bind it to the container and adjust the env var
300
+ const localConfig = `/${config_mapper_1.KAPETA_ENV_CONFIG_FILE}`;
301
+ Binds.push(`${(0, containerManager_1.toLocalBindVolume)(env[config_mapper_1.KAPETA_CONFIG_ENV_VAR])}:${localConfig}:ro`);
302
+ // We also provide the hash to detect changes to the config file
303
+ env['KAPETA_CONFIG_HASH'] = await this.getFileHash(env[config_mapper_1.KAPETA_CONFIG_ENV_VAR]);
304
+ env[config_mapper_1.KAPETA_CONFIG_ENV_VAR] = localConfig;
305
+ }
278
306
  return this.ensureContainer({
279
307
  Image: dockerImage,
280
308
  name: containerName,
@@ -293,7 +321,7 @@ class BlockInstanceRunner {
293
321
  }).map(([key, value]) => `${key}=${value}`),
294
322
  ],
295
323
  HostConfig: {
296
- Binds: [`${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`],
324
+ Binds,
297
325
  PortBindings,
298
326
  },
299
327
  });
@@ -360,6 +388,14 @@ class BlockInstanceRunner {
360
388
  `${(0, containerManager_1.toLocalBindVolume)(kapetaYmlPath)}:/kapeta.yml:ro`,
361
389
  `${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`,
362
390
  ];
391
+ if (!local.singleton && env[config_mapper_1.KAPETA_CONFIG_ENV_VAR]) {
392
+ // If we have a config file, we need to bind it to the container and adjust the env var
393
+ const localConfig = `/${config_mapper_1.KAPETA_ENV_CONFIG_FILE}`;
394
+ Binds.push(`${(0, containerManager_1.toLocalBindVolume)(env[config_mapper_1.KAPETA_CONFIG_ENV_VAR])}:${localConfig}:ro`);
395
+ // We also provide the hash to detect changes to the config file
396
+ env['KAPETA_CONFIG_HASH'] = await this.getFileHash(env[config_mapper_1.KAPETA_CONFIG_ENV_VAR]);
397
+ env[config_mapper_1.KAPETA_CONFIG_ENV_VAR] = localConfig;
398
+ }
363
399
  const systemUri = (0, nodejs_utils_1.parseKapetaUri)(this._systemId);
364
400
  console.log(`Ensuring container for operator block: ${containerName} [singleton: ${!!local.singleton}]`);
365
401
  logs.addLog(`Ensuring container for operator block: ${containerName}`);
@@ -436,5 +472,13 @@ class BlockInstanceRunner {
436
472
  pid: container.id,
437
473
  };
438
474
  }
475
+ async getFileHash(filePath) {
476
+ const content = await fs_extra_1.default.readFile(filePath);
477
+ const hash = crypto_1.default.createHash('md5');
478
+ //passing the data to be hashed
479
+ const data = hash.update(content);
480
+ //Creating the hash in the required format
481
+ return data.digest('hex');
482
+ }
439
483
  }
440
484
  exports.BlockInstanceRunner = BlockInstanceRunner;
@@ -22,7 +22,7 @@ declare class OperatorManager {
22
22
  /**
23
23
  * Get operator definition for resource type
24
24
  */
25
- getOperator(fullName: string, version: string): Promise<Operator>;
25
+ getOperator(fullName: string, version: string): Promise<Operator | null>;
26
26
  /**
27
27
  * Get information about a specific consumed resource
28
28
  */
@@ -35,7 +35,7 @@ declare class OperatorManager {
35
35
  * @param kind the full name - e.g. myhandle/rabbitmq
36
36
  * @param version the version of the operator
37
37
  */
38
- ensureOperator(systemId: string, kind: string, version: string): Promise<ContainerInfo>;
38
+ ensureOperator(systemId: string, kind: string, version: string): Promise<ContainerInfo | null>;
39
39
  }
40
40
  export declare const operatorManager: OperatorManager;
41
41
  export {};
@@ -61,7 +61,8 @@ class OperatorManager {
61
61
  throw new Error(`Unknown operator type: ${fullName}:${version}`);
62
62
  }
63
63
  if (!operator.definition.spec || !operator.definition.spec.local) {
64
- throw new Error(`Operator missing local definition: ${fullName}:${version}`);
64
+ console.warn(`Operator missing local definition: ${fullName}:${version}`);
65
+ return null;
65
66
  }
66
67
  return new Operator(operator);
67
68
  }
@@ -95,6 +96,16 @@ class OperatorManager {
95
96
  }
96
97
  const kindUri = (0, nodejs_utils_1.parseKapetaUri)(blockResource.kind);
97
98
  const operator = await this.getOperator(resourceType, kindUri.version);
99
+ if (!operator) {
100
+ return {
101
+ host: '',
102
+ port: 0,
103
+ type: '',
104
+ protocol: '',
105
+ options: {},
106
+ credentials: {},
107
+ };
108
+ }
98
109
  const credentials = operator.getCredentials();
99
110
  if (ensureContainer) {
100
111
  await this.ensureOperator(systemId, resourceType, kindUri.version);
@@ -120,6 +131,9 @@ class OperatorManager {
120
131
  }
121
132
  async getOperatorPorts(systemId, kind, version) {
122
133
  const operator = await this.getOperator(kind, version);
134
+ if (!operator) {
135
+ return {};
136
+ }
123
137
  const operatorData = operator.getLocalData();
124
138
  const portTypes = Object.keys(operatorData.ports);
125
139
  portTypes.sort();
@@ -150,6 +164,9 @@ class OperatorManager {
150
164
  const key = `${systemId}#${kind}:${version}`;
151
165
  return await this.operatorLock.acquire(key, async () => {
152
166
  const operator = await this.getOperator(kind, version);
167
+ if (!operator) {
168
+ return null;
169
+ }
153
170
  const operatorData = operator.getLocalData();
154
171
  const ports = await this.getOperatorPorts(systemId, kind, version);
155
172
  const nameParts = [systemId, kind.toLowerCase(), version];
@@ -25,4 +25,5 @@ export declare class BlockInstanceRunner {
25
25
  private getServiceBlockPortBindings;
26
26
  private ensureContainer;
27
27
  private _handleContainer;
28
+ private getFileHash;
28
29
  }
@@ -23,9 +23,12 @@ const node_path_1 = __importDefault(require("node:path"));
23
23
  const taskManager_1 = require("../taskManager");
24
24
  const InternalConfigProvider_1 = require("./InternalConfigProvider");
25
25
  const config_mapper_1 = require("@kapeta/config-mapper");
26
+ const crypto_1 = __importDefault(require("crypto"));
26
27
  const KAPETA_SYSTEM_ID = 'KAPETA_SYSTEM_ID';
27
28
  const KAPETA_BLOCK_REF = 'KAPETA_BLOCK_REF';
28
29
  const KAPETA_INSTANCE_ID = 'KAPETA_INSTANCE_ID';
30
+ // The maximum length of an environment variable - this is to avoid hitting the command line length limits
31
+ const MAX_ENV_LENGTH = 256;
29
32
  /**
30
33
  * Needed when running local docker containers as part of plan
31
34
  * @type {string[]}
@@ -111,21 +114,28 @@ class BlockInstanceRunner {
111
114
  const realBaseDir = await fs_extra_1.default.realpath(baseDir);
112
115
  const internalConfigProvider = await (0, InternalConfigProvider_1.createInternalConfigProvider)(this._systemId, blockInstance.id, assetVersion);
113
116
  // Resolve the environment variables
114
- const envVars = await (0, config_mapper_1.resolveKapetaVariables)(realBaseDir, internalConfigProvider);
117
+ const variables = await (0, config_mapper_1.resolveKapetaVariables)(realBaseDir, internalConfigProvider);
115
118
  // Write out the config templates if they exist
116
- await (0, config_mapper_1.writeConfigTemplates)(envVars, realBaseDir);
119
+ await (0, config_mapper_1.writeConfigTemplates)(variables, realBaseDir);
120
+ const env = await (0, config_mapper_1.getEnvironmentVariables)(variables);
121
+ // Gets the tmp path to write the config file
122
+ const configFilePath = (0, config_mapper_1.getConfigFilePath)(blockInstance.id);
123
+ // Write the config to a file - will be read by the SDKs
124
+ await (0, config_mapper_1.writeEnvConfigFile)(variables, configFilePath);
125
+ // Add the config file path to the environment variables
126
+ env[config_mapper_1.KAPETA_CONFIG_ENV_VAR] = configFilePath;
117
127
  let processInfo;
118
128
  if (providerVersion.definition.kind === types_1.KIND_BLOCK_TYPE_OPERATOR) {
119
- processInfo = await this._startOperatorProcess(blockInstance, blockUri, providerVersion, envVars);
129
+ processInfo = await this._startOperatorProcess(blockInstance, blockUri, providerVersion, env);
120
130
  }
121
131
  else {
122
132
  //We need a port type to know how to connect to the block consistently
123
133
  const portTypes = getServiceProviderPorts(assetVersion, providerVersion);
124
134
  if (blockUri.version === 'local') {
125
- processInfo = await this._startLocalProcess(blockInstance, blockUri, envVars, assetVersion);
135
+ processInfo = await this._startLocalProcess(blockInstance, blockUri, env, assetVersion);
126
136
  }
127
137
  else {
128
- processInfo = await this._startDockerProcess(blockInstance, blockUri, envVars, assetVersion);
138
+ processInfo = await this._startDockerProcess(blockInstance, blockUri, env, assetVersion);
129
139
  }
130
140
  if (portTypes.length > 0) {
131
141
  processInfo.portType = portTypes[0];
@@ -200,6 +210,14 @@ class BlockInstanceRunner {
200
210
  if (localContainer.healthcheck) {
201
211
  HealthCheck = containerManager_1.containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
202
212
  }
213
+ if (env[config_mapper_1.KAPETA_CONFIG_ENV_VAR]) {
214
+ // If we have a config file, we need to bind it to the container and adjust the env var
215
+ const localConfig = `/${config_mapper_1.KAPETA_ENV_CONFIG_FILE}`;
216
+ Binds.push(`${(0, containerManager_1.toLocalBindVolume)(env[config_mapper_1.KAPETA_CONFIG_ENV_VAR])}:${localConfig}:ro`);
217
+ // We also provide the hash to detect changes to the config file
218
+ env['KAPETA_CONFIG_HASH'] = await this.getFileHash(env[config_mapper_1.KAPETA_CONFIG_ENV_VAR]);
219
+ env[config_mapper_1.KAPETA_CONFIG_ENV_VAR] = localConfig;
220
+ }
203
221
  const Mounts = isDockerImage
204
222
  ? // For docker images we mount the local directory to the working directory
205
223
  containerManager_1.containerManager.toDockerMounts({
@@ -208,6 +226,15 @@ class BlockInstanceRunner {
208
226
  : // For dockerfiles we don't mount anything
209
227
  [];
210
228
  const systemUri = (0, nodejs_utils_1.parseKapetaUri)(this._systemId);
229
+ const Env = [
230
+ ...customEnvs,
231
+ ...DOCKER_ENV_VARS,
232
+ `KAPETA_LOCAL_CLUSTER_PORT=${clusterService_1.clusterService.getClusterServicePort()}`,
233
+ ...Object.entries({
234
+ ...env,
235
+ ...addonEnv,
236
+ }).map(([key, value]) => `${key}=${value}`),
237
+ ];
211
238
  return this.ensureContainer({
212
239
  ...dockerOpts,
213
240
  Image: dockerImage,
@@ -222,15 +249,7 @@ class BlockInstanceRunner {
222
249
  HealthCheck,
223
250
  ExposedPorts,
224
251
  Cmd: startCmd ? startCmd.split(/\s+/g) : [],
225
- Env: [
226
- ...customEnvs,
227
- ...DOCKER_ENV_VARS,
228
- `KAPETA_LOCAL_CLUSTER_PORT=${clusterService_1.clusterService.getClusterServicePort()}`,
229
- ...Object.entries({
230
- ...env,
231
- ...addonEnv,
232
- }).map(([key, value]) => `${key}=${value}`),
233
- ],
252
+ Env,
234
253
  HostConfig: {
235
254
  ...customHostConfigs,
236
255
  Binds: [
@@ -275,6 +294,15 @@ class BlockInstanceRunner {
275
294
  // For windows we need to default to root
276
295
  const innerHome = process.platform === 'win32' ? '/root/.kapeta' : local_cluster_config_1.default.getKapetaBasedir();
277
296
  const systemUri = (0, nodejs_utils_1.parseKapetaUri)(this._systemId);
297
+ const Binds = [`${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`];
298
+ if (env[config_mapper_1.KAPETA_CONFIG_ENV_VAR]) {
299
+ // If we have a config file, we need to bind it to the container and adjust the env var
300
+ const localConfig = `/${config_mapper_1.KAPETA_ENV_CONFIG_FILE}`;
301
+ Binds.push(`${(0, containerManager_1.toLocalBindVolume)(env[config_mapper_1.KAPETA_CONFIG_ENV_VAR])}:${localConfig}:ro`);
302
+ // We also provide the hash to detect changes to the config file
303
+ env['KAPETA_CONFIG_HASH'] = await this.getFileHash(env[config_mapper_1.KAPETA_CONFIG_ENV_VAR]);
304
+ env[config_mapper_1.KAPETA_CONFIG_ENV_VAR] = localConfig;
305
+ }
278
306
  return this.ensureContainer({
279
307
  Image: dockerImage,
280
308
  name: containerName,
@@ -293,7 +321,7 @@ class BlockInstanceRunner {
293
321
  }).map(([key, value]) => `${key}=${value}`),
294
322
  ],
295
323
  HostConfig: {
296
- Binds: [`${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`],
324
+ Binds,
297
325
  PortBindings,
298
326
  },
299
327
  });
@@ -360,6 +388,14 @@ class BlockInstanceRunner {
360
388
  `${(0, containerManager_1.toLocalBindVolume)(kapetaYmlPath)}:/kapeta.yml:ro`,
361
389
  `${(0, containerManager_1.toLocalBindVolume)(local_cluster_config_1.default.getKapetaBasedir())}:${innerHome}`,
362
390
  ];
391
+ if (!local.singleton && env[config_mapper_1.KAPETA_CONFIG_ENV_VAR]) {
392
+ // If we have a config file, we need to bind it to the container and adjust the env var
393
+ const localConfig = `/${config_mapper_1.KAPETA_ENV_CONFIG_FILE}`;
394
+ Binds.push(`${(0, containerManager_1.toLocalBindVolume)(env[config_mapper_1.KAPETA_CONFIG_ENV_VAR])}:${localConfig}:ro`);
395
+ // We also provide the hash to detect changes to the config file
396
+ env['KAPETA_CONFIG_HASH'] = await this.getFileHash(env[config_mapper_1.KAPETA_CONFIG_ENV_VAR]);
397
+ env[config_mapper_1.KAPETA_CONFIG_ENV_VAR] = localConfig;
398
+ }
363
399
  const systemUri = (0, nodejs_utils_1.parseKapetaUri)(this._systemId);
364
400
  console.log(`Ensuring container for operator block: ${containerName} [singleton: ${!!local.singleton}]`);
365
401
  logs.addLog(`Ensuring container for operator block: ${containerName}`);
@@ -436,5 +472,13 @@ class BlockInstanceRunner {
436
472
  pid: container.id,
437
473
  };
438
474
  }
475
+ async getFileHash(filePath) {
476
+ const content = await fs_extra_1.default.readFile(filePath);
477
+ const hash = crypto_1.default.createHash('md5');
478
+ //passing the data to be hashed
479
+ const data = hash.update(content);
480
+ //Creating the hash in the required format
481
+ return data.digest('hex');
482
+ }
439
483
  }
440
484
  exports.BlockInstanceRunner = BlockInstanceRunner;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.40.4",
3
+ "version": "0.40.5",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -49,7 +49,7 @@
49
49
  "homepage": "https://github.com/kapetacom/local-cluster-service#readme",
50
50
  "dependencies": {
51
51
  "@kapeta/codegen": "^1.3.0",
52
- "@kapeta/config-mapper": "^1.1.1",
52
+ "@kapeta/config-mapper": "^1.2.0",
53
53
  "@kapeta/local-cluster-config": "^0.4.1",
54
54
  "@kapeta/nodejs-api-client": ">=0.2.0 <2",
55
55
  "@kapeta/nodejs-process": "^1.2.0",
@@ -83,7 +83,8 @@ class OperatorManager {
83
83
  }
84
84
 
85
85
  if (!operator.definition.spec || !operator.definition.spec.local) {
86
- throw new Error(`Operator missing local definition: ${fullName}:${version}`);
86
+ console.warn(`Operator missing local definition: ${fullName}:${version}`);
87
+ return null;
87
88
  }
88
89
 
89
90
  return new Operator(operator);
@@ -138,6 +139,16 @@ class OperatorManager {
138
139
 
139
140
  const kindUri = parseKapetaUri(blockResource.kind);
140
141
  const operator = await this.getOperator(resourceType, kindUri.version);
142
+ if (!operator) {
143
+ return {
144
+ host: '',
145
+ port: 0,
146
+ type: '',
147
+ protocol: '',
148
+ options: {},
149
+ credentials: {},
150
+ };
151
+ }
141
152
  const credentials = operator.getCredentials();
142
153
  if (ensureContainer) {
143
154
  await this.ensureOperator(systemId, resourceType, kindUri.version);
@@ -168,6 +179,9 @@ class OperatorManager {
168
179
 
169
180
  async getOperatorPorts(systemId: string, kind: string, version: string) {
170
181
  const operator = await this.getOperator(kind, version);
182
+ if (!operator) {
183
+ return {};
184
+ }
171
185
 
172
186
  const operatorData = operator.getLocalData();
173
187
 
@@ -201,13 +215,16 @@ class OperatorManager {
201
215
  * @param kind the full name - e.g. myhandle/rabbitmq
202
216
  * @param version the version of the operator
203
217
  */
204
- async ensureOperator(systemId: string, kind: string, version: string): Promise<ContainerInfo> {
218
+ async ensureOperator(systemId: string, kind: string, version: string): Promise<ContainerInfo | null> {
205
219
  systemId = normalizeKapetaUri(systemId);
206
220
 
207
221
  const key = `${systemId}#${kind}:${version}`;
208
222
 
209
223
  return await this.operatorLock.acquire(key, async () => {
210
224
  const operator = await this.getOperator(kind, version);
225
+ if (!operator) {
226
+ return null;
227
+ }
211
228
 
212
229
  const operatorData = operator.getLocalData();
213
230
 
@@ -34,12 +34,24 @@ import Path from 'node:path';
34
34
  import { taskManager } from '../taskManager';
35
35
  import { LocalDevContainer, LocalInstance } from '@kapeta/schemas';
36
36
  import { createInternalConfigProvider } from './InternalConfigProvider';
37
- import { resolveKapetaVariables, writeConfigTemplates } from '@kapeta/config-mapper';
37
+ import {
38
+ getConfigFilePath,
39
+ getEnvironmentVariables,
40
+ KAPETA_CONFIG_ENV_VAR,
41
+ KAPETA_ENV_CONFIG_FILE,
42
+ resolveKapetaVariables,
43
+ writeConfigTemplates,
44
+ writeEnvConfigFile,
45
+ } from '@kapeta/config-mapper';
46
+ import crypto from 'crypto';
38
47
 
39
48
  const KAPETA_SYSTEM_ID = 'KAPETA_SYSTEM_ID';
40
49
  const KAPETA_BLOCK_REF = 'KAPETA_BLOCK_REF';
41
50
  const KAPETA_INSTANCE_ID = 'KAPETA_INSTANCE_ID';
42
51
 
52
+ // The maximum length of an environment variable - this is to avoid hitting the command line length limits
53
+ const MAX_ENV_LENGTH = 256;
54
+
43
55
  /**
44
56
  * Needed when running local docker containers as part of plan
45
57
  * @type {string[]}
@@ -145,23 +157,34 @@ export class BlockInstanceRunner {
145
157
  );
146
158
 
147
159
  // Resolve the environment variables
148
- const envVars = await resolveKapetaVariables(realBaseDir, internalConfigProvider);
160
+ const variables = await resolveKapetaVariables(realBaseDir, internalConfigProvider);
149
161
 
150
162
  // Write out the config templates if they exist
151
- await writeConfigTemplates(envVars, realBaseDir);
163
+ await writeConfigTemplates(variables, realBaseDir);
164
+
165
+ const env = await getEnvironmentVariables(variables);
166
+
167
+ // Gets the tmp path to write the config file
168
+ const configFilePath = getConfigFilePath(blockInstance.id);
169
+
170
+ // Write the config to a file - will be read by the SDKs
171
+ await writeEnvConfigFile(variables, configFilePath);
172
+
173
+ // Add the config file path to the environment variables
174
+ env[KAPETA_CONFIG_ENV_VAR] = configFilePath;
152
175
 
153
176
  let processInfo: ProcessInfo;
154
177
 
155
178
  if (providerVersion.definition.kind === KIND_BLOCK_TYPE_OPERATOR) {
156
- processInfo = await this._startOperatorProcess(blockInstance, blockUri, providerVersion, envVars);
179
+ processInfo = await this._startOperatorProcess(blockInstance, blockUri, providerVersion, env);
157
180
  } else {
158
181
  //We need a port type to know how to connect to the block consistently
159
182
  const portTypes = getServiceProviderPorts(assetVersion, providerVersion);
160
183
 
161
184
  if (blockUri.version === 'local') {
162
- processInfo = await this._startLocalProcess(blockInstance, blockUri, envVars, assetVersion);
185
+ processInfo = await this._startLocalProcess(blockInstance, blockUri, env, assetVersion);
163
186
  } else {
164
- processInfo = await this._startDockerProcess(blockInstance, blockUri, envVars, assetVersion);
187
+ processInfo = await this._startDockerProcess(blockInstance, blockUri, env, assetVersion);
165
188
  }
166
189
 
167
190
  if (portTypes.length > 0) {
@@ -269,6 +292,15 @@ export class BlockInstanceRunner {
269
292
  HealthCheck = containerManager.toDockerHealth({ cmd: localContainer.healthcheck });
270
293
  }
271
294
 
295
+ if (env[KAPETA_CONFIG_ENV_VAR]) {
296
+ // If we have a config file, we need to bind it to the container and adjust the env var
297
+ const localConfig = `/${KAPETA_ENV_CONFIG_FILE}`;
298
+ Binds.push(`${toLocalBindVolume(env[KAPETA_CONFIG_ENV_VAR])}:${localConfig}:ro`);
299
+ // We also provide the hash to detect changes to the config file
300
+ env['KAPETA_CONFIG_HASH'] = await this.getFileHash(env[KAPETA_CONFIG_ENV_VAR]);
301
+ env[KAPETA_CONFIG_ENV_VAR] = localConfig;
302
+ }
303
+
272
304
  const Mounts = isDockerImage
273
305
  ? // For docker images we mount the local directory to the working directory
274
306
  containerManager.toDockerMounts({
@@ -279,6 +311,16 @@ export class BlockInstanceRunner {
279
311
 
280
312
  const systemUri = parseKapetaUri(this._systemId);
281
313
 
314
+ const Env = [
315
+ ...customEnvs,
316
+ ...DOCKER_ENV_VARS,
317
+ `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
318
+ ...Object.entries({
319
+ ...env,
320
+ ...addonEnv,
321
+ }).map(([key, value]) => `${key}=${value}`),
322
+ ];
323
+
282
324
  return this.ensureContainer({
283
325
  ...dockerOpts,
284
326
  Image: dockerImage,
@@ -293,15 +335,7 @@ export class BlockInstanceRunner {
293
335
  HealthCheck,
294
336
  ExposedPorts,
295
337
  Cmd: startCmd ? startCmd.split(/\s+/g) : [],
296
- Env: [
297
- ...customEnvs,
298
- ...DOCKER_ENV_VARS,
299
- `KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
300
- ...Object.entries({
301
- ...env,
302
- ...addonEnv,
303
- }).map(([key, value]) => `${key}=${value}`),
304
- ],
338
+ Env,
305
339
  HostConfig: {
306
340
  ...customHostConfigs,
307
341
  Binds: [
@@ -372,6 +406,16 @@ export class BlockInstanceRunner {
372
406
  const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
373
407
  const systemUri = parseKapetaUri(this._systemId);
374
408
 
409
+ const Binds = [`${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`];
410
+ if (env[KAPETA_CONFIG_ENV_VAR]) {
411
+ // If we have a config file, we need to bind it to the container and adjust the env var
412
+ const localConfig = `/${KAPETA_ENV_CONFIG_FILE}`;
413
+ Binds.push(`${toLocalBindVolume(env[KAPETA_CONFIG_ENV_VAR])}:${localConfig}:ro`);
414
+ // We also provide the hash to detect changes to the config file
415
+ env['KAPETA_CONFIG_HASH'] = await this.getFileHash(env[KAPETA_CONFIG_ENV_VAR]);
416
+ env[KAPETA_CONFIG_ENV_VAR] = localConfig;
417
+ }
418
+
375
419
  return this.ensureContainer({
376
420
  Image: dockerImage,
377
421
  name: containerName,
@@ -390,7 +434,7 @@ export class BlockInstanceRunner {
390
434
  }).map(([key, value]) => `${key}=${value}`),
391
435
  ],
392
436
  HostConfig: {
393
- Binds: [`${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`],
437
+ Binds,
394
438
  PortBindings,
395
439
  },
396
440
  });
@@ -486,6 +530,15 @@ export class BlockInstanceRunner {
486
530
  `${toLocalBindVolume(ClusterConfig.getKapetaBasedir())}:${innerHome}`,
487
531
  ];
488
532
 
533
+ if (!local.singleton && env[KAPETA_CONFIG_ENV_VAR]) {
534
+ // If we have a config file, we need to bind it to the container and adjust the env var
535
+ const localConfig = `/${KAPETA_ENV_CONFIG_FILE}`;
536
+ Binds.push(`${toLocalBindVolume(env[KAPETA_CONFIG_ENV_VAR])}:${localConfig}:ro`);
537
+ // We also provide the hash to detect changes to the config file
538
+ env['KAPETA_CONFIG_HASH'] = await this.getFileHash(env[KAPETA_CONFIG_ENV_VAR]);
539
+ env[KAPETA_CONFIG_ENV_VAR] = localConfig;
540
+ }
541
+
489
542
  const systemUri = parseKapetaUri(this._systemId);
490
543
 
491
544
  console.log(
@@ -583,4 +636,13 @@ export class BlockInstanceRunner {
583
636
  pid: container.id,
584
637
  };
585
638
  }
639
+
640
+ private async getFileHash(filePath: string) {
641
+ const content = await FSExtra.readFile(filePath);
642
+ const hash = crypto.createHash('md5');
643
+ //passing the data to be hashed
644
+ const data = hash.update(content);
645
+ //Creating the hash in the required format
646
+ return data.digest('hex');
647
+ }
586
648
  }