docker-storage-gc 4.0.0 → 4.0.1

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/index.ts CHANGED
@@ -102,42 +102,33 @@ export default class DockerGC {
102
102
  this.host = hostname;
103
103
  }
104
104
 
105
- public setDocker(hostObj: Docker.DockerOptions): Promise<void> {
105
+ public async setDocker(hostObj: Docker.DockerOptions): Promise<void> {
106
106
  this.currentMtimes = {};
107
- hostObj = _.defaults({ Promise: Bluebird }, hostObj);
108
107
  this.dockerProgress = new DockerProgress({
109
108
  docker: new Docker(hostObj),
110
109
  });
111
- return getDocker(hostObj)
112
- .then((docker) => {
113
- // Docker info can take a while so do it here,
114
- // and don't wait on the results
115
- this.docker = docker;
116
- return (this.baseImagePromise = this.getDaemonArchitecture().then(
117
- function (arch) {
118
- switch (arch) {
119
- case 'arm':
120
- return 'arm32v6/alpine:3.6';
121
- case 'arm64':
122
- return 'arm64v8/alpine:3.6';
123
- case 'amd64':
124
- return 'alpine:3.6';
125
- default:
126
- throw new Error('Could not detect architecture of remote host');
127
- }
128
- },
129
- ));
130
- })
131
- .then(() => {
132
- // noop
133
- });
110
+ const docker = getDocker(hostObj);
111
+ // Docker info can take a while so do it here,
112
+ // and don't wait on the results
113
+ this.docker = docker;
114
+ await (this.baseImagePromise = this.getDaemonArchitecture().then((arch) => {
115
+ switch (arch) {
116
+ case 'arm':
117
+ return 'arm32v6/alpine:3.6';
118
+ case 'arm64':
119
+ return 'arm64v8/alpine:3.6';
120
+ case 'amd64':
121
+ return 'alpine:3.6';
122
+ default:
123
+ throw new Error('Could not detect architecture of remote host');
124
+ }
125
+ }));
134
126
  }
135
127
 
136
- public setupMtimeStream(): Promise<void> {
137
- return dockerMtimeStream(this.docker).then((stream) => {
138
- stream.on('data', (layerMtimes: LayerMtimes) => {
139
- this.currentMtimes = layerMtimes;
140
- });
128
+ public async setupMtimeStream(): Promise<void> {
129
+ const stream = await dockerMtimeStream(this.docker);
130
+ stream.on('data', (layerMtimes: LayerMtimes) => {
131
+ this.currentMtimes = layerMtimes;
141
132
  });
142
133
  }
143
134
 
@@ -165,60 +156,56 @@ export default class DockerGC {
165
156
  removalType: 'tag' | 'digest' | 'id',
166
157
  ): Promise<void> | undefined {
167
158
  if (attributes.length > 0) {
168
- return Bluebird.each(attributes, (attribute) => {
169
- console.log(
170
- `[GC (${this.host}] Removing image : ${attribute} (id: ${image.id})`,
171
- );
172
- return this.docker
173
- .getImage(attribute)
174
- .remove({ noprune: true })
175
- .then(() => {
176
- this.metrics.emit('imageRemoved', removalType);
177
- });
178
- }).then(() => {
179
- // noop
180
- });
159
+ return (async () => {
160
+ for (const attribute of attributes) {
161
+ console.log(
162
+ `[GC (${this.host}] Removing image : ${attribute} (id: ${image.id})`,
163
+ );
164
+ await this.docker.getImage(attribute).remove({ noprune: true });
165
+ this.metrics.emit('imageRemoved', removalType);
166
+ }
167
+ })();
181
168
  }
182
169
  }
183
170
 
184
- public garbageCollect(
171
+ public async garbageCollect(
185
172
  reclaimSpace: number,
186
173
  attemptAll = false,
187
174
  ): Promise<void> {
188
175
  let err: any;
189
176
  const startTime = process.hrtime();
190
177
  this.metrics.emit('spaceReclaimed', reclaimSpace);
191
- return dockerImageTree(this.docker, this.currentMtimes)
192
- .then((tree) => {
193
- return getImagesToRemove(tree, reclaimSpace, this.metrics);
194
- })
195
- .each((image) => {
196
- return this.removeImage(image).catch((e) => {
197
- this.metrics.emit('imageRemovalError', e.statusCode);
198
- console.log(`[GC ${this.host}]: Failed to remove image: `, image);
199
- console.log(e);
200
- if (attemptAll) {
201
- err ??= e;
202
- return;
203
- } else {
204
- recordGcRunTime(startTime, this.metrics);
205
- throw e;
206
- }
207
- });
208
- })
209
- .then(() => {
210
- recordGcRunTime(startTime, this.metrics);
211
- if (err != null) {
212
- throw err;
178
+
179
+ const tree = await dockerImageTree(this.docker, this.currentMtimes);
180
+ const images = await getImagesToRemove(tree, reclaimSpace, this.metrics);
181
+ for (const image of images) {
182
+ try {
183
+ await this.removeImage(image);
184
+ } catch (e: any) {
185
+ this.metrics.emit('imageRemovalError', e.statusCode);
186
+ console.log(`[GC ${this.host}]: Failed to remove image: `, image);
187
+ console.log(e);
188
+ if (attemptAll) {
189
+ err ??= e;
190
+ } else {
191
+ recordGcRunTime(startTime, this.metrics);
192
+ throw e;
213
193
  }
214
- });
194
+ }
195
+ }
196
+ recordGcRunTime(startTime, this.metrics);
197
+ if (err != null) {
198
+ throw err;
199
+ }
215
200
  }
216
201
 
217
202
  private getOutput(image: string, command: string[]): Promise<string> {
218
- return Bluebird.using(this.runDisposer(image, command), (container) =>
219
- container
220
- .logs({ stdout: true, follow: true })
221
- .then((logs) => streamToString(logs)),
203
+ return Bluebird.using(
204
+ this.runDisposer(image, command),
205
+ async (container) => {
206
+ const logs = await container.logs({ stdout: true, follow: true });
207
+ return await streamToString(logs);
208
+ },
222
209
  );
223
210
  }
224
211
 
@@ -235,38 +222,43 @@ export default class DockerGC {
235
222
  );
236
223
  return Bluebird.resolve(
237
224
  containerPromise.then(([, container]) => container),
238
- ).disposer((container) => container.wait().then(() => container.remove()));
225
+ ).disposer(async (container) => {
226
+ await container.wait();
227
+ await container.remove();
228
+ });
239
229
  }
240
230
 
241
- public getDaemonFreeSpace(): Promise<{
231
+ public async getDaemonFreeSpace(): Promise<{
242
232
  used: number;
243
233
  total: number;
244
234
  free: number;
245
235
  }> {
246
- return Bluebird.resolve(this.baseImagePromise)
247
- .tap((baseImage) => {
248
- // Ensure the image is available (if it is this is essentially a no-op)
249
- return this.dockerProgress.pull(baseImage, _.noop);
250
- })
251
- .then((baseImage) => {
252
- return this.getOutput(baseImage, ['/bin/df', '-B', '1', '/']);
253
- })
254
- .then(function (spaceStr) {
255
- // First split the lines, as we're only interested in the second one
256
- const lines = spaceStr.trim().split(/\r?\n/);
257
- if (lines.length !== 2) {
258
- throw new Error('Coult not parse df output');
259
- }
236
+ const baseImage = await this.baseImagePromise;
237
+
238
+ // Ensure the image is available (if it is this is essentially a no-op)
239
+ await this.dockerProgress.pull(baseImage, _.noop);
240
+
241
+ const spaceStr = await this.getOutput(baseImage, [
242
+ '/bin/df',
243
+ '-B',
244
+ '1',
245
+ '/',
246
+ ]);
247
+ // First split the lines, as we're only interested in the second one
248
+ const lines = spaceStr.trim().split(/\r?\n/);
249
+ if (lines.length !== 2) {
250
+ throw new Error('Coult not parse df output');
251
+ }
260
252
 
261
- const parts = lines[1].split(/\s+/);
262
- const total = parseInt(parts[1], 10);
263
- const used = parseInt(parts[2], 10);
264
- const free = parseInt(parts[3], 10);
265
- return { used, total, free };
266
- });
253
+ const parts = lines[1].split(/\s+/);
254
+ const total = parseInt(parts[1], 10);
255
+ const used = parseInt(parts[2], 10);
256
+ const free = parseInt(parts[3], 10);
257
+ return { used, total, free };
267
258
  }
268
259
 
269
- private getDaemonArchitecture() {
270
- return this.docker.version().then(({ Arch }) => Arch);
260
+ private async getDaemonArchitecture() {
261
+ const { Arch } = await this.docker.version();
262
+ return Arch;
271
263
  }
272
264
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docker-storage-gc",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "main": "build/index.js",
5
5
  "scripts": {
6
6
  "lint": "balena-lint -t tsconfig.dev.json -e ts -e js lib test tools",
@@ -39,6 +39,6 @@
39
39
  "typescript": "^5.3.2"
40
40
  },
41
41
  "versionist": {
42
- "publishedAt": "2023-12-04T17:24:21.020Z"
42
+ "publishedAt": "2023-12-05T22:24:21.230Z"
43
43
  }
44
44
  }
@@ -11,36 +11,32 @@ describe('parseEventStream', function () {
11
11
  // TODO
12
12
  });
13
13
 
14
- it('should return updated mtimes', () =>
15
- getDocker({})
16
- .then((docker) => parseEventStream(docker))
17
- .then(
18
- (streamParser) =>
19
- new Promise<LayerMtimes>(function (resolve, reject) {
20
- let mtimes: LayerMtimes;
14
+ it('should return updated mtimes', async () => {
15
+ const docker = await getDocker({});
16
+ const streamParser = await parseEventStream(docker);
17
+ const data = await new Promise<LayerMtimes>(function (resolve, reject) {
18
+ let mtimes: LayerMtimes;
21
19
 
22
- return fs
23
- .createReadStream(__dirname + '/fixtures/docker-events.json')
24
- .pipe(streamParser)
25
- .on('error', reject)
26
- .pipe(es.mapSync((data: LayerMtimes) => (mtimes = data)))
27
- .on('end', () => resolve(mtimes))
28
- .on('error', reject);
29
- }),
20
+ return fs
21
+ .createReadStream(__dirname + '/fixtures/docker-events.json')
22
+ .pipe(streamParser)
23
+ .on('error', reject)
24
+ .pipe(es.mapSync(($data: LayerMtimes) => (mtimes = $data)))
25
+ .on('end', () => resolve(mtimes))
26
+ .on('error', reject);
27
+ });
28
+ expect(data)
29
+ .to.have.property('busybox:latest')
30
+ .that.equals(1448576072937294800);
31
+ expect(data)
32
+ .to.have.property(
33
+ 'sha256:6d41a4a0bf8168363e29da8a5ecbf3cd6c37e3f5a043decd5e7da6e427ba869c',
30
34
  )
31
- .then(function (data) {
32
- expect(data)
33
- .to.have.property('busybox:latest')
34
- .that.equals(1448576072937294800);
35
- expect(data)
36
- .to.have.property(
37
- 'sha256:6d41a4a0bf8168363e29da8a5ecbf3cd6c37e3f5a043decd5e7da6e427ba869c',
38
- )
39
- .that.equals(1448576073085559800);
40
- expect(data)
41
- .to.have.property(
42
- 'sha256:9a61b6b1315e6b457c31a03346ab94486a2f5397f4a82219bee01eead1c34c2e',
43
- )
44
- .that.equals(1448576073203895800);
45
- }));
35
+ .that.equals(1448576073085559800);
36
+ expect(data)
37
+ .to.have.property(
38
+ 'sha256:9a61b6b1315e6b457c31a03346ab94486a2f5397f4a82219bee01eead1c34c2e',
39
+ )
40
+ .that.equals(1448576073203895800);
41
+ });
46
42
  });
@@ -7,22 +7,20 @@ import { LayerMtimes, parseEventStream } from '../build/docker-event-stream';
7
7
  import { createTree } from '../build/docker-image-tree';
8
8
  import { getDocker } from '../build/docker';
9
9
 
10
- const getLayerMtimes = () =>
11
- getDocker({})
12
- .then((docker) => parseEventStream(docker))
13
- .then(
14
- (streamParser) =>
15
- new Promise<LayerMtimes>(function (resolve, reject) {
16
- let mtimes: LayerMtimes;
17
- return fs
18
- .createReadStream(__dirname + '/fixtures/docker-events.json')
19
- .pipe(streamParser)
20
- .on('error', reject)
21
- .pipe(es.mapSync((data: LayerMtimes) => (mtimes = data)))
22
- .on('end', () => resolve(mtimes))
23
- .on('error', reject);
24
- }),
25
- );
10
+ const getLayerMtimes = async () => {
11
+ const docker = await getDocker({});
12
+ const streamParser = await parseEventStream(docker);
13
+ return await new Promise<LayerMtimes>(function (resolve, reject) {
14
+ let mtimes: LayerMtimes;
15
+ return fs
16
+ .createReadStream(__dirname + '/fixtures/docker-events.json')
17
+ .pipe(streamParser)
18
+ .on('error', reject)
19
+ .pipe(es.mapSync((data: LayerMtimes) => (mtimes = data)))
20
+ .on('end', () => resolve(mtimes))
21
+ .on('error', reject);
22
+ });
23
+ };
26
24
 
27
25
  describe('createTree', function () {
28
26
  it.skip('should work with empty input', function () {
@@ -40,98 +38,97 @@ describe('createTree', function () {
40
38
  assert: { type: 'json' },
41
39
  })
42
40
  ).default as ContainerInfo[];
43
- return getLayerMtimes().then(function (mtimes) {
44
- tk.freeze(Date.UTC(2016, 0, 1));
45
- const tree = createTree(images, containers, mtimes);
46
- tk.reset();
47
- const output = {
48
- id: '0000000000000000000000000000000000000000000000000000000000000000',
49
- size: 0,
50
- repoTags: [],
51
- repoDigests: [],
52
- mtime: 1451606400000000000,
53
- isUsedByAContainer: false,
54
- children: {
55
- 'sha256:6d15899cef812e2876b9d5d43d4cd863eda7b278f7b52d00975f6a9a8e817c74':
56
- {
57
- id: 'sha256:6d15899cef812e2876b9d5d43d4cd863eda7b278f7b52d00975f6a9a8e817c74',
58
- size: 125151141,
59
- repoTags: [],
60
- repoDigests: [],
61
- mtime: 1451606400000000000,
62
- isUsedByAContainer: false,
63
- children: {
64
- 'sha256:e53bd4df04f86919156c4510cdc6e6c9491ec8ec226381d36aca573b46bbbbbc':
65
- {
66
- id: 'sha256:e53bd4df04f86919156c4510cdc6e6c9491ec8ec226381d36aca573b46bbbbbc',
67
- size: 0,
68
- repoTags: ['project1'],
69
- repoDigests: [],
70
- mtime: 1451606400000000000,
71
- isUsedByAContainer: false,
72
- children: {
73
- 'sha256:6d41a4a0bf8168363e29da8a5ecbf3cd6c37e3f5a043decd5e7da6e427ba869c':
74
- {
75
- id: 'sha256:6d41a4a0bf8168363e29da8a5ecbf3cd6c37e3f5a043decd5e7da6e427ba869c',
76
- size: 330389,
77
- repoTags: ['project2'],
78
- repoDigests: [],
79
- // eslint-disable-next-line @typescript-eslint/no-loss-of-precision
80
- mtime: 1448576073085559863,
81
- isUsedByAContainer: false,
82
- children: {
83
- 'sha256:80dc79d29cd8618e678da508fc32f7289e6f72defb534f3f287731b1f8b355ea':
84
- {
85
- id: 'sha256:80dc79d29cd8618e678da508fc32f7289e6f72defb534f3f287731b1f8b355ea',
86
- size: 98872,
87
- repoTags: [],
88
- repoDigests: [],
89
- mtime: 1451606400000000000,
90
- isUsedByAContainer: false,
91
- children: {},
92
- },
93
- },
41
+ const mtimes = await getLayerMtimes();
42
+ tk.freeze(Date.UTC(2016, 0, 1));
43
+ const tree = createTree(images, containers, mtimes);
44
+ tk.reset();
45
+ const output = {
46
+ id: '0000000000000000000000000000000000000000000000000000000000000000',
47
+ size: 0,
48
+ repoTags: [],
49
+ repoDigests: [],
50
+ mtime: 1451606400000000000,
51
+ isUsedByAContainer: false,
52
+ children: {
53
+ 'sha256:6d15899cef812e2876b9d5d43d4cd863eda7b278f7b52d00975f6a9a8e817c74':
54
+ {
55
+ id: 'sha256:6d15899cef812e2876b9d5d43d4cd863eda7b278f7b52d00975f6a9a8e817c74',
56
+ size: 125151141,
57
+ repoTags: [],
58
+ repoDigests: [],
59
+ mtime: 1451606400000000000,
60
+ isUsedByAContainer: false,
61
+ children: {
62
+ 'sha256:e53bd4df04f86919156c4510cdc6e6c9491ec8ec226381d36aca573b46bbbbbc':
63
+ {
64
+ id: 'sha256:e53bd4df04f86919156c4510cdc6e6c9491ec8ec226381d36aca573b46bbbbbc',
65
+ size: 0,
66
+ repoTags: ['project1'],
67
+ repoDigests: [],
68
+ mtime: 1451606400000000000,
69
+ isUsedByAContainer: false,
70
+ children: {
71
+ 'sha256:6d41a4a0bf8168363e29da8a5ecbf3cd6c37e3f5a043decd5e7da6e427ba869c':
72
+ {
73
+ id: 'sha256:6d41a4a0bf8168363e29da8a5ecbf3cd6c37e3f5a043decd5e7da6e427ba869c',
74
+ size: 330389,
75
+ repoTags: ['project2'],
76
+ repoDigests: [],
77
+ // eslint-disable-next-line @typescript-eslint/no-loss-of-precision
78
+ mtime: 1448576073085559863,
79
+ isUsedByAContainer: false,
80
+ children: {
81
+ 'sha256:80dc79d29cd8618e678da508fc32f7289e6f72defb534f3f287731b1f8b355ea':
82
+ {
83
+ id: 'sha256:80dc79d29cd8618e678da508fc32f7289e6f72defb534f3f287731b1f8b355ea',
84
+ size: 98872,
85
+ repoTags: [],
86
+ repoDigests: [],
87
+ mtime: 1451606400000000000,
88
+ isUsedByAContainer: false,
89
+ children: {},
90
+ },
94
91
  },
95
- },
96
- },
97
- },
98
- },
99
- 'sha256:902b87aaaec929e80541486828959f14fa061f529ad7f37ab300d4ef9f3a0dbf':
100
- {
101
- id: 'sha256:902b87aaaec929e80541486828959f14fa061f529ad7f37ab300d4ef9f3a0dbf',
102
- size: 125151141,
103
- repoTags: [],
104
- repoDigests: [],
105
- mtime: 1451606400000000000,
106
- isUsedByAContainer: false,
107
- children: {
108
- 'sha256:9a61b6b1315e6b457c31a03346ab94486a2f5397f4a82219bee01eead1c34c2e':
109
- {
110
- id: 'sha256:9a61b6b1315e6b457c31a03346ab94486a2f5397f4a82219bee01eead1c34c2e',
111
- size: 0,
112
- repoTags: ['resin/project3'],
113
- repoDigests: [],
114
- mtime: 1448576073203895800,
115
- isUsedByAContainer: false,
116
- children: {},
92
+ },
117
93
  },
118
- },
94
+ },
119
95
  },
120
- 'sha256:5b0d59026729b68570d99bc4f3f7c31a2e4f2a5736435641565d93e7c25bd2c3':
121
- {
122
- id: 'sha256:5b0d59026729b68570d99bc4f3f7c31a2e4f2a5736435641565d93e7c25bd2c3',
123
- size: 125151141,
124
- repoTags: ['busybox:latest'],
125
- repoDigests: [
126
- 'sha256:a8cf7ff6367c2afa2a90acd081b484cbded349a7076e7bdf37a05279f276bc12',
127
- ],
128
- mtime: 1448576072937294800,
129
- isUsedByAContainer: true,
130
- children: {},
96
+ },
97
+ 'sha256:902b87aaaec929e80541486828959f14fa061f529ad7f37ab300d4ef9f3a0dbf':
98
+ {
99
+ id: 'sha256:902b87aaaec929e80541486828959f14fa061f529ad7f37ab300d4ef9f3a0dbf',
100
+ size: 125151141,
101
+ repoTags: [],
102
+ repoDigests: [],
103
+ mtime: 1451606400000000000,
104
+ isUsedByAContainer: false,
105
+ children: {
106
+ 'sha256:9a61b6b1315e6b457c31a03346ab94486a2f5397f4a82219bee01eead1c34c2e':
107
+ {
108
+ id: 'sha256:9a61b6b1315e6b457c31a03346ab94486a2f5397f4a82219bee01eead1c34c2e',
109
+ size: 0,
110
+ repoTags: ['resin/project3'],
111
+ repoDigests: [],
112
+ mtime: 1448576073203895800,
113
+ isUsedByAContainer: false,
114
+ children: {},
115
+ },
131
116
  },
132
- },
133
- };
134
- expect(tree).to.deep.equal(output);
135
- });
117
+ },
118
+ 'sha256:5b0d59026729b68570d99bc4f3f7c31a2e4f2a5736435641565d93e7c25bd2c3':
119
+ {
120
+ id: 'sha256:5b0d59026729b68570d99bc4f3f7c31a2e4f2a5736435641565d93e7c25bd2c3',
121
+ size: 125151141,
122
+ repoTags: ['busybox:latest'],
123
+ repoDigests: [
124
+ 'sha256:a8cf7ff6367c2afa2a90acd081b484cbded349a7076e7bdf37a05279f276bc12',
125
+ ],
126
+ mtime: 1448576072937294800,
127
+ isUsedByAContainer: true,
128
+ children: {},
129
+ },
130
+ },
131
+ };
132
+ expect(tree).to.deep.equal(output);
136
133
  });
137
134
  });