go-dev 0.4.2 → 0.5.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.
@@ -1,198 +1,195 @@
1
- const { BaseService } = require('./base');
2
-
3
- class DockerService extends BaseService {
4
- /** @type {Map<string, string[]>} */
5
- static _servicesToStop = new Map();
6
-
7
- static async cleanup() {
8
- if (!DockerService._processManager) {
9
- console.warn('[DockerService] ProcessManager not initialized, skipping static cleanup.');
10
- return;
11
- }
12
-
13
- if (DockerService._servicesToStop.size > 0) {
14
- console.log('[DockerService] Stopping all docker services from used compose files...');
15
- for (const [composeFile, services] of this._servicesToStop.entries()) {
16
- try {
17
- console.log(`[DockerService] Stopping services ${services.map(service => `'${service}'`).join(', ')} of '${composeFile}'`);
18
- DockerService._processManager.runSync(
19
- 'docker',
20
- ['compose', '-f', composeFile, 'stop', ...services],
21
- { stdio: 'inherit' },
22
- );
23
- } catch (error) {
24
- console.error(
25
- `[DockerService] Failed to stop services of '${composeFile}': ${error.message}`,
26
- );
27
- }
28
- }
29
- DockerService._servicesToStop.clear();
30
- } else {
31
- console.log('[DockerService] No docker services were started by orchestrator, skipping global stop.');
32
- }
33
- }
34
-
35
- constructor(name, mode, config) {
36
- super(name, mode, config);
37
- this.dockerServiceName = config.service;
38
- this.dockerComposeFile = config.composeFile;
39
- this.containerName = null;
40
- }
41
-
42
- async start() {
43
- console.log(`[${this.name}:${this.mode}] Starting docker service '${this.dockerServiceName}' (using ${this.dockerComposeFile})...`);
44
-
45
- let status = this._getContainerStatus();
46
- if (status === 'running') {
47
- console.log(
48
- `[${this.name}:${this.mode}] Docker container for '${this.dockerServiceName}' is already running.`,
49
- );
50
- } else {
51
- console.log(`[${this.name}:${this.mode}] Bringing up docker service '${this.dockerServiceName}'...`);
52
- try {
53
- const servicesBeforeStart = this._getCurrentlyRunningServices();
54
- await DockerService._processManager.runInherited(
55
- 'docker',
56
- ['compose', '-f', this.dockerComposeFile, 'up', this.dockerServiceName, '-d'],
57
- );
58
- const servicesAfterStart = this._getCurrentlyRunningServices();
59
- let servicesOfComposeFile = DockerService._servicesToStop.get(this.dockerComposeFile);
60
- if (servicesOfComposeFile == null) {
61
- servicesOfComposeFile = [];
62
- }
63
- const newServices = servicesAfterStart.filter(service => {
64
- return !(
65
- servicesBeforeStart.includes(service) ||
66
- servicesOfComposeFile.includes(service)
67
- );
68
- });
69
- DockerService._servicesToStop.set(
70
- this.dockerComposeFile,
71
- servicesOfComposeFile.concat(newServices)
72
- );
73
- console.log(`[${this.name}:${this.mode}] Docker service '${this.dockerServiceName}' brought up.`);
74
- if (newServices.length > 1) {
75
- console.log(`[${this.name}:${this.mode}] Dependency service${newServices.length > 2 ? 's' : ''} for '${this.dockerServiceName}': ${newServices.filter(service => service.name !== this.dockerServiceName).join(', ')}`);
76
- }
77
- } catch (error) {
78
- throw new Error(
79
- `[${this.name}:${this.mode}] Failed to bring up docker service '${this.dockerServiceName}': ${error.message}`,
80
- );
81
- }
82
- }
83
-
84
- if (this.config.healthCheck) {
85
- console.log(`[${this.name}:${this.mode}] Checking healthiness for '${this.dockerServiceName}'...`);
86
- try {
87
- await this._checkServiceHealthiness();
88
- console.log(`[${this.name}:${this.mode}] Docker service '${this.dockerServiceName}' is healthy.`);
89
- } catch (error) {
90
- throw new Error(
91
- `[${this.name}:${this.mode}] Health check failed for '${this.dockerServiceName}': ${error.message}`,
92
- );
93
- }
94
- } else {
95
- console.log(`[${this.name}:${this.mode}] Skipping health check for '${this.dockerServiceName}'.`);
96
- }
97
- }
98
-
99
- async stop() {
100
- console.log(`[${this.name}:${this.mode}] Relying on orchestrator's static docker compose stop for '${this.dockerServiceName}'.`);
101
- this.containerName = null;
102
- }
103
-
104
- async checkHealth() {
105
- return await this._checkServiceHealthiness();
106
- }
107
-
108
- _getCurrentlyRunningServices() {
109
- try {
110
- const services = DockerService._processManager.runSync(
111
- 'docker',
112
- ['compose', '-f', this.dockerComposeFile, 'ps', '--services'],
113
- );
114
- return (services
115
- .split('\n')
116
- .map(serviceName => serviceName.trim())
117
- .filter(serviceName => serviceName !== '')
118
- );
119
- } catch (error) {
120
- // console.warn(...);
121
- }
122
- return null;
123
- }
124
-
125
- _getContainerName() {
126
- if (this.containerName) {
127
- return this.containerName;
128
- }
129
- try {
130
- const name = DockerService._processManager.runSync(
131
- 'docker',
132
- ['compose', '-f', this.dockerComposeFile, 'ps', '-a', '-q', this.dockerServiceName],
133
- );
134
- if (name) {
135
- this.containerName = name;
136
- return name;
137
- }
138
- } catch (error) {
139
- // console.warn(...);
140
- }
141
- return null;
142
- }
143
-
144
- _getContainerStatus() {
145
- const containerName = this._getContainerName();
146
- if (!containerName) {
147
- return null;
148
- }
149
- try {
150
- return DockerService._processManager.runSync(
151
- 'docker',
152
- ['container', 'inspect', '-f', '{{.State.Status}}', containerName],
153
- );
154
- } catch (error) {
155
- // console.warn(...);
156
- return null;
157
- }
158
- }
159
-
160
- async _checkServiceHealthiness(maxAttempts = 30, delayMs = 1000) {
161
- const containerName = this._getContainerName();
162
- if (!containerName) {
163
- throw new Error(`[${this.name}:${this.mode}] Cannot check health: Container for '${this.dockerServiceName}' not found.`);
164
- }
165
-
166
- console.log(`[${this.name}:${this.mode}] Checking healthiness for '${containerName}'`);
167
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
168
- if (attempt > 1) {
169
- await new Promise(resolve => setTimeout(resolve, delayMs));
170
- }
171
-
172
- try {
173
- const healthStatus = DockerService._processManager.runSync(
174
- 'docker',
175
- ['container', 'inspect', '-f', '{{.State.Health.Status}}', containerName],
176
- { stdio: 'pipe' }
177
- );
178
-
179
- if (healthStatus === 'healthy' || healthStatus === 'none') {
180
- console.log(`[${this.name}:${this.mode}] Container '${containerName}' healthy!`);
181
- return true;
182
- }
183
-
184
- if (healthStatus === 'starting' || healthStatus === 'unhealthy') {
185
- continue;
186
- }
187
-
188
- throw new Error(`[${this.name}:${this.mode}] Container '${containerName}' is in unexpected health state: ${healthStatus}`);
189
- } catch (error) {
190
- throw new Error(`[${this.name}:${this.mode}] Failed to check health for '${containerName}': ${error.message}`);
191
- }
192
- }
193
-
194
- throw new Error(`[${this.name}:${this.mode}] Service '${this.dockerServiceName}' wasn't healthy in time after ${maxAttempts} attempts.`);
195
- }
196
- }
197
-
1
+ const log = require('../logger');
2
+ const { BaseService } = require('./base');
3
+
4
+ class DockerService extends BaseService {
5
+ /** @type {Map<string, string[]>} */
6
+ static _servicesToStop = new Map();
7
+
8
+ static async cleanup() {
9
+ if (!DockerService._processManager) {
10
+ log.warn('[DockerService] ProcessManager not initialized, skipping static cleanup.');
11
+ return;
12
+ }
13
+
14
+ if (DockerService._servicesToStop.size > 0) {
15
+ log.info('');
16
+ }
17
+
18
+ for (const [composeFile, services] of this._servicesToStop.entries()) {
19
+ try {
20
+ DockerService._processManager.runSync(
21
+ 'docker',
22
+ ['compose', '-f', composeFile, 'stop', ...services],
23
+ { stdio: 'inherit' },
24
+ );
25
+ } catch (error) {
26
+ log.error(`Failed to stop docker services of '${composeFile}': ${error.message}`);
27
+ }
28
+ }
29
+ DockerService._servicesToStop.clear();
30
+ }
31
+
32
+ constructor(name, mode, config) {
33
+ super(name, mode, config);
34
+ this.dockerServiceName = config.service;
35
+ this.dockerComposeFile = config.composeFile;
36
+ this.containerName = null;
37
+ }
38
+
39
+ async start() {
40
+ log.info(`[${this.coloredId}] Starting docker service '${this.dockerServiceName}' (using ${this.dockerComposeFile})...`);
41
+
42
+ let status = this._getContainerStatus();
43
+ if (status === 'running') {
44
+ log.info(
45
+ `[${this.coloredId}] Docker container for '${this.dockerServiceName}' is already running.`,
46
+ );
47
+ } else {
48
+ log.info(`[${this.coloredId}] Bringing up docker service '${this.dockerServiceName}'...`);
49
+ try {
50
+ const servicesBeforeStart = this._getCurrentlyRunningServices();
51
+ await DockerService._processManager.runInherited(
52
+ 'docker',
53
+ ['compose', '-f', this.dockerComposeFile, 'up', this.dockerServiceName, '-d'],
54
+ );
55
+ const servicesAfterStart = this._getCurrentlyRunningServices();
56
+ let servicesOfComposeFile = DockerService._servicesToStop.get(this.dockerComposeFile);
57
+ if (servicesOfComposeFile == null) {
58
+ servicesOfComposeFile = [];
59
+ }
60
+ const newServices = servicesAfterStart.filter(service => {
61
+ return !(
62
+ servicesBeforeStart.includes(service) ||
63
+ servicesOfComposeFile.includes(service)
64
+ );
65
+ });
66
+ DockerService._servicesToStop.set(
67
+ this.dockerComposeFile,
68
+ servicesOfComposeFile.concat(newServices)
69
+ );
70
+ log.info(`[${this.coloredId}] Docker service '${this.dockerServiceName}' brought up.`);
71
+ if (newServices.length > 1) {
72
+ log.info(`[${this.coloredId}] Dependency service${newServices.length > 2 ? 's' : ''} for '${this.dockerServiceName}': ${newServices.filter(service => service.name !== this.dockerServiceName).join(', ')}`);
73
+ }
74
+ } catch (error) {
75
+ throw new Error(
76
+ `[${this.coloredId}] Failed to bring up docker service '${this.dockerServiceName}': ${error.message}`,
77
+ );
78
+ }
79
+ }
80
+
81
+ if (this.config.healthCheck) {
82
+ log.info(`[${this.coloredId}] Checking healthiness for '${this.dockerServiceName}'...`);
83
+ try {
84
+ await this._checkServiceHealthiness();
85
+ log.info(`[${this.coloredId}] Docker service '${this.dockerServiceName}' is healthy.`);
86
+ } catch (error) {
87
+ throw new Error(
88
+ `[${this.coloredId}] Health check failed for '${this.dockerServiceName}': ${error.message}`,
89
+ );
90
+ }
91
+ } else {
92
+ log.info(`[${this.coloredId}] Skipping health check for '${this.dockerServiceName}'.`);
93
+ }
94
+ }
95
+
96
+ async stop() {
97
+ log.debug(`[${this.coloredId}] Relying on orchestrator's static docker compose stop for '${this.dockerServiceName}'.`);
98
+ this.containerName = null;
99
+ }
100
+
101
+ async checkHealth() {
102
+ return await this._checkServiceHealthiness();
103
+ }
104
+
105
+ _getCurrentlyRunningServices() {
106
+ try {
107
+ const services = DockerService._processManager.runSync(
108
+ 'docker',
109
+ ['compose', '-f', this.dockerComposeFile, 'ps', '--services'],
110
+ );
111
+ return (services
112
+ .split('\n')
113
+ .map(serviceName => serviceName.trim())
114
+ .filter(serviceName => serviceName !== '')
115
+ );
116
+ } catch (error) {
117
+ // console.warn(...);
118
+ }
119
+ return null;
120
+ }
121
+
122
+ _getContainerName() {
123
+ if (this.containerName) {
124
+ return this.containerName;
125
+ }
126
+ try {
127
+ const name = DockerService._processManager.runSync(
128
+ 'docker',
129
+ ['compose', '-f', this.dockerComposeFile, 'ps', '-a', '-q', this.dockerServiceName],
130
+ );
131
+ if (name) {
132
+ this.containerName = name;
133
+ return name;
134
+ }
135
+ } catch (error) {
136
+ // console.warn(...);
137
+ }
138
+ return null;
139
+ }
140
+
141
+ _getContainerStatus() {
142
+ const containerName = this._getContainerName();
143
+ if (!containerName) {
144
+ return null;
145
+ }
146
+ try {
147
+ return DockerService._processManager.runSync(
148
+ 'docker',
149
+ ['container', 'inspect', '-f', '{{.State.Status}}', containerName],
150
+ );
151
+ } catch (error) {
152
+ // console.warn(...);
153
+ return null;
154
+ }
155
+ }
156
+
157
+ async _checkServiceHealthiness(maxAttempts = 30, delayMs = 1000) {
158
+ const containerName = this._getContainerName();
159
+ if (!containerName) {
160
+ throw new Error(`[${this.coloredId}] Cannot check health: Container for '${this.dockerServiceName}' not found.`);
161
+ }
162
+
163
+ log.info(`[${this.coloredId}] Checking healthiness for '${containerName}'`);
164
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
165
+ if (attempt > 1) {
166
+ await new Promise(resolve => setTimeout(resolve, delayMs));
167
+ }
168
+
169
+ try {
170
+ const healthStatus = DockerService._processManager.runSync(
171
+ 'docker',
172
+ ['container', 'inspect', '-f', '{{.State.Health.Status}}', containerName],
173
+ { stdio: 'pipe' }
174
+ );
175
+
176
+ if (healthStatus === 'healthy' || healthStatus === 'none') {
177
+ log.info(`[${this.coloredId}] Container '${containerName}' healthy!`);
178
+ return true;
179
+ }
180
+
181
+ if (healthStatus === 'starting' || healthStatus === 'unhealthy') {
182
+ continue;
183
+ }
184
+
185
+ throw new Error(`[${this.coloredId}] Container '${containerName}' is in unexpected health state: ${healthStatus}`);
186
+ } catch (error) {
187
+ throw new Error(`[${this.coloredId}] Failed to check health for '${containerName}': ${error.message}`);
188
+ }
189
+ }
190
+
191
+ throw new Error(`[${this.coloredId}] Service '${this.dockerServiceName}' wasn't healthy in time after ${maxAttempts} attempts.`);
192
+ }
193
+ }
194
+
198
195
  module.exports = { DockerService };