docker-storage-gc 3.5.12 → 4.0.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/.versionbot/CHANGELOG.yml +19 -1
- package/CHANGELOG.md +6 -0
- package/build/docker-event-stream.d.ts +8 -0
- package/build/docker-event-stream.js +80 -49
- package/build/docker-event-stream.js.map +1 -0
- package/build/docker-image-tree.d.ts +15 -0
- package/build/docker-image-tree.js +57 -76
- package/build/docker-image-tree.js.map +1 -0
- package/build/docker.d.ts +2 -0
- package/build/docker.js +21 -25
- package/build/docker.js.map +1 -0
- package/build/index.d.ts +33 -0
- package/build/index.js +174 -229
- package/build/index.js.map +1 -0
- package/lib/docker-event-stream.ts +93 -0
- package/lib/docker-image-tree.ts +94 -0
- package/lib/docker.ts +17 -0
- package/lib/index.ts +272 -0
- package/package.json +21 -13
- package/test/docker-event-stream.ts +46 -0
- package/test/docker-image-tree.ts +137 -0
- package/test/index.ts +228 -0
- package/tools/graphviz.ts +39 -0
- package/tsconfig.dev.json +13 -0
- package/tsconfig.json +22 -0
- package/index.d.ts +0 -23
- package/lib/docker-event-stream.coffee +0 -46
- package/lib/docker-image-tree.coffee +0 -56
- package/lib/docker.coffee +0 -13
- package/lib/index.coffee +0 -168
- package/test/docker-event-stream.coffee +0 -30
- package/test/docker-image-tree.coffee +0 -124
- package/test/index.coffee +0 -208
- package/tools/graphviz.coffee +0 -24
package/build/index.js
CHANGED
|
@@ -1,242 +1,187 @@
|
|
|
1
|
-
|
|
2
|
-
(function() {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
dockerMtimeStream = require('./docker-event-stream').dockerMtimeStream;
|
|
17
|
-
|
|
18
|
-
dockerImageTree = require('./docker-image-tree').dockerImageTree;
|
|
19
|
-
|
|
20
|
-
dockerUtils = require('./docker');
|
|
21
|
-
|
|
22
|
-
getUnusedTreeLeafs = function(tree, result) {
|
|
23
|
-
var child, children, i, len;
|
|
24
|
-
if (result == null) {
|
|
25
|
-
result = [];
|
|
26
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const bluebird_1 = __importDefault(require("bluebird"));
|
|
7
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
+
const eventemitter3_1 = require("eventemitter3");
|
|
9
|
+
const docker_progress_1 = require("docker-progress");
|
|
10
|
+
const dockerode_1 = __importDefault(require("dockerode"));
|
|
11
|
+
const docker_event_stream_1 = require("./docker-event-stream");
|
|
12
|
+
const docker_image_tree_1 = require("./docker-image-tree");
|
|
13
|
+
const docker_1 = require("./docker");
|
|
14
|
+
const getUnusedTreeLeafs = function (tree, result = []) {
|
|
27
15
|
if (!tree.removed) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
16
|
+
const children = (0, lodash_1.default)(tree.children)
|
|
17
|
+
.values()
|
|
18
|
+
.filter(lodash_1.default.negate(lodash_1.default.property('removed')))
|
|
19
|
+
.value();
|
|
20
|
+
if (children.length === 0 && !tree.isUsedByAContainer) {
|
|
21
|
+
result.push(tree);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
for (const child of children) {
|
|
25
|
+
getUnusedTreeLeafs(child, result);
|
|
26
|
+
}
|
|
35
27
|
}
|
|
36
|
-
}
|
|
37
28
|
}
|
|
38
29
|
return result;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
result = [];
|
|
45
|
-
size = 0;
|
|
30
|
+
};
|
|
31
|
+
const getImagesToRemove = function (tree, reclaimSpace, metrics) {
|
|
32
|
+
tree = lodash_1.default.clone(tree);
|
|
33
|
+
const result = [];
|
|
34
|
+
let size = 0;
|
|
46
35
|
while (size < reclaimSpace) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
36
|
+
const leafs = lodash_1.default.orderBy(getUnusedTreeLeafs(tree), ['mtime', 'size'], ['asc', 'desc']);
|
|
37
|
+
if (leafs.length === 0) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
const leaf = leafs[0];
|
|
41
|
+
if (leaf !== tree) {
|
|
42
|
+
result.push(leaf);
|
|
43
|
+
size += leaf.size;
|
|
44
|
+
}
|
|
45
|
+
leaf.removed = true;
|
|
57
46
|
}
|
|
58
47
|
metrics.emit('numberImagesToRemove', result.length);
|
|
59
48
|
return result;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
function DockerGC() {
|
|
83
|
-
this.garbageCollect = bind(this.garbageCollect, this);
|
|
84
|
-
this.tryRemoveImageBy = bind(this.tryRemoveImageBy, this);
|
|
85
|
-
this.removeImage = bind(this.removeImage, this);
|
|
86
|
-
this.metrics = new EventEmitter();
|
|
87
|
-
this.host = 'unknown';
|
|
49
|
+
};
|
|
50
|
+
const streamToString = (stream) => new Promise(function (resolve, reject) {
|
|
51
|
+
const chunks = [];
|
|
52
|
+
stream
|
|
53
|
+
.on('error', reject)
|
|
54
|
+
.on('data', (chunk) => chunks.push(chunk))
|
|
55
|
+
.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
56
|
+
});
|
|
57
|
+
const recordGcRunTime = function (t0, metrics) {
|
|
58
|
+
const dt = process.hrtime(t0);
|
|
59
|
+
const duration = dt[0] * 1000 + dt[1] / 1e6;
|
|
60
|
+
metrics.emit('gcRunTime', duration);
|
|
61
|
+
};
|
|
62
|
+
class DockerGC {
|
|
63
|
+
metrics = new eventemitter3_1.EventEmitter();
|
|
64
|
+
host = 'unknown';
|
|
65
|
+
docker;
|
|
66
|
+
dockerProgress;
|
|
67
|
+
currentMtimes = {};
|
|
68
|
+
baseImagePromise;
|
|
69
|
+
setHostname(hostname) {
|
|
70
|
+
this.host = hostname;
|
|
88
71
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
})(this));
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
DockerGC.prototype.setupMtimeStream = function() {
|
|
122
|
-
return dockerMtimeStream(this.docker).then((function(_this) {
|
|
123
|
-
return function(stream) {
|
|
124
|
-
return stream.on('data', function(layer_mtimes) {
|
|
125
|
-
return _this.currentMtimes = layer_mtimes;
|
|
126
|
-
});
|
|
127
|
-
};
|
|
128
|
-
})(this));
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
DockerGC.prototype.removeImage = function(image) {
|
|
132
|
-
return this.tryRemoveImageBy(image, image.repoTags, 'tag') || this.tryRemoveImageBy(image, image.repoDigests, 'digest') || this.tryRemoveImageBy(image, [image.id], 'id');
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
DockerGC.prototype.tryRemoveImageBy = function(image, attributes, removalType) {
|
|
136
|
-
if ((attributes != null) && attributes.length > 0) {
|
|
137
|
-
return Bluebird.each(attributes, (function(_this) {
|
|
138
|
-
return function(attribute) {
|
|
139
|
-
console.log("[GC (" + _this.host + "] Removing image : " + attribute + " (id: " + image.id + ")");
|
|
140
|
-
return _this.docker.getImage(attribute).remove({
|
|
141
|
-
noprune: true
|
|
142
|
-
}).then(function() {
|
|
143
|
-
return _this.metrics.emit('imageRemoved', removalType);
|
|
72
|
+
setDocker(hostObj) {
|
|
73
|
+
this.currentMtimes = {};
|
|
74
|
+
hostObj = lodash_1.default.defaults({ Promise: bluebird_1.default }, hostObj);
|
|
75
|
+
this.dockerProgress = new docker_progress_1.DockerProgress({
|
|
76
|
+
docker: new dockerode_1.default(hostObj),
|
|
77
|
+
});
|
|
78
|
+
return (0, docker_1.getDocker)(hostObj)
|
|
79
|
+
.then((docker) => {
|
|
80
|
+
this.docker = docker;
|
|
81
|
+
return (this.baseImagePromise = this.getDaemonArchitecture().then(function (arch) {
|
|
82
|
+
switch (arch) {
|
|
83
|
+
case 'arm':
|
|
84
|
+
return 'arm32v6/alpine:3.6';
|
|
85
|
+
case 'arm64':
|
|
86
|
+
return 'arm64v8/alpine:3.6';
|
|
87
|
+
case 'amd64':
|
|
88
|
+
return 'alpine:3.6';
|
|
89
|
+
default:
|
|
90
|
+
throw new Error('Could not detect architecture of remote host');
|
|
91
|
+
}
|
|
92
|
+
}));
|
|
93
|
+
})
|
|
94
|
+
.then(() => {
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
setupMtimeStream() {
|
|
98
|
+
return (0, docker_event_stream_1.dockerMtimeStream)(this.docker).then((stream) => {
|
|
99
|
+
stream.on('data', (layerMtimes) => {
|
|
100
|
+
this.currentMtimes = layerMtimes;
|
|
144
101
|
});
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
removeImage(image) {
|
|
105
|
+
return (this.tryRemoveImageBy(image, image.repoTags, 'tag') ||
|
|
106
|
+
this.tryRemoveImageBy(image, image.repoDigests, 'digest') ||
|
|
107
|
+
this.tryRemoveImageBy(image, [image.id], 'id'));
|
|
108
|
+
}
|
|
109
|
+
tryRemoveImageBy(image, attributes, removalType) {
|
|
110
|
+
if (attributes.length > 0) {
|
|
111
|
+
return bluebird_1.default.each(attributes, (attribute) => {
|
|
112
|
+
console.log(`[GC (${this.host}] Removing image : ${attribute} (id: ${image.id})`);
|
|
113
|
+
return this.docker
|
|
114
|
+
.getImage(attribute)
|
|
115
|
+
.remove({ noprune: true })
|
|
116
|
+
.then(() => {
|
|
117
|
+
this.metrics.emit('imageRemoved', removalType);
|
|
118
|
+
});
|
|
119
|
+
}).then(() => {
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
garbageCollect(reclaimSpace, attemptAll = false) {
|
|
124
|
+
let err;
|
|
125
|
+
const startTime = process.hrtime();
|
|
126
|
+
this.metrics.emit('spaceReclaimed', reclaimSpace);
|
|
127
|
+
return (0, docker_image_tree_1.dockerImageTree)(this.docker, this.currentMtimes)
|
|
128
|
+
.then((tree) => {
|
|
129
|
+
return getImagesToRemove(tree, reclaimSpace, this.metrics);
|
|
130
|
+
})
|
|
131
|
+
.each((image) => {
|
|
132
|
+
return this.removeImage(image).catch((e) => {
|
|
133
|
+
this.metrics.emit('imageRemovalError', e.statusCode);
|
|
134
|
+
console.log(`[GC ${this.host}]: Failed to remove image: `, image);
|
|
135
|
+
console.log(e);
|
|
136
|
+
if (attemptAll) {
|
|
137
|
+
err ??= e;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
recordGcRunTime(startTime, this.metrics);
|
|
142
|
+
throw e;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
})
|
|
146
|
+
.then(() => {
|
|
147
|
+
recordGcRunTime(startTime, this.metrics);
|
|
148
|
+
if (err != null) {
|
|
149
|
+
throw err;
|
|
173
150
|
}
|
|
174
|
-
});
|
|
175
|
-
};
|
|
176
|
-
})(this)).then((function(_this) {
|
|
177
|
-
return function() {
|
|
178
|
-
recordGcRunTime(startTime, _this.metrics);
|
|
179
|
-
if (err != null) {
|
|
180
|
-
throw err;
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
})(this));
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
DockerGC.prototype.getOutput = function(image, command) {
|
|
187
|
-
return Bluebird.using(this.runDisposer(image, command), function(container) {
|
|
188
|
-
return container.logs({
|
|
189
|
-
stdout: true,
|
|
190
|
-
follow: true
|
|
191
|
-
}).then(function(logs) {
|
|
192
|
-
return streamToString(logs);
|
|
193
151
|
});
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
152
|
+
}
|
|
153
|
+
getOutput(image, command) {
|
|
154
|
+
return bluebird_1.default.using(this.runDisposer(image, command), (container) => container
|
|
155
|
+
.logs({ stdout: true, follow: true })
|
|
156
|
+
.then((logs) => streamToString(logs)));
|
|
157
|
+
}
|
|
158
|
+
runDisposer(image, command) {
|
|
159
|
+
const containerPromise = this.docker.run(image, command, undefined);
|
|
160
|
+
return bluebird_1.default.resolve(containerPromise.then(([, container]) => container)).disposer((container) => container.wait().then(() => container.remove()));
|
|
161
|
+
}
|
|
162
|
+
getDaemonFreeSpace() {
|
|
163
|
+
return bluebird_1.default.resolve(this.baseImagePromise)
|
|
164
|
+
.tap((baseImage) => {
|
|
165
|
+
return this.dockerProgress.pull(baseImage, lodash_1.default.noop);
|
|
166
|
+
})
|
|
167
|
+
.then((baseImage) => {
|
|
168
|
+
return this.getOutput(baseImage, ['/bin/df', '-B', '1', '/']);
|
|
169
|
+
})
|
|
170
|
+
.then(function (spaceStr) {
|
|
171
|
+
const lines = spaceStr.trim().split(/\r?\n/);
|
|
172
|
+
if (lines.length !== 2) {
|
|
173
|
+
throw new Error('Coult not parse df output');
|
|
174
|
+
}
|
|
175
|
+
const parts = lines[1].split(/\s+/);
|
|
176
|
+
const total = parseInt(parts[1], 10);
|
|
177
|
+
const used = parseInt(parts[2], 10);
|
|
178
|
+
const free = parseInt(parts[3], 10);
|
|
179
|
+
return { used, total, free };
|
|
201
180
|
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
};
|
|
210
|
-
})(this)).then((function(_this) {
|
|
211
|
-
return function(baseImage) {
|
|
212
|
-
return _this.getOutput(baseImage, ['/bin/df', '-B', '1', '/']);
|
|
213
|
-
};
|
|
214
|
-
})(this)).then(function(spaceStr) {
|
|
215
|
-
var free, lines, parts, total, used;
|
|
216
|
-
lines = spaceStr.trim().split(/\r?\n/);
|
|
217
|
-
if (lines.length !== 2) {
|
|
218
|
-
throw new Error('Coult not parse df output');
|
|
219
|
-
}
|
|
220
|
-
parts = lines[1].split(/\s+/);
|
|
221
|
-
total = parseInt(parts[1]);
|
|
222
|
-
used = parseInt(parts[2]);
|
|
223
|
-
free = parseInt(parts[3]);
|
|
224
|
-
return {
|
|
225
|
-
used: used,
|
|
226
|
-
total: total,
|
|
227
|
-
free: free
|
|
228
|
-
};
|
|
229
|
-
});
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
DockerGC.prototype.getDaemonArchitecture = function() {
|
|
233
|
-
return this.docker.version().get('Arch');
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
return DockerGC;
|
|
237
|
-
|
|
238
|
-
})();
|
|
239
|
-
|
|
240
|
-
module.exports = DockerGC;
|
|
241
|
-
|
|
242
|
-
}).call(this);
|
|
181
|
+
}
|
|
182
|
+
getDaemonArchitecture() {
|
|
183
|
+
return this.docker.version().then(({ Arch }) => Arch);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
exports.default = DockerGC;
|
|
187
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import Bluebird from 'bluebird';
|
|
2
|
+
import * as es from 'event-stream';
|
|
3
|
+
import JSONStream from 'JSONStream';
|
|
4
|
+
import type Docker from 'dockerode';
|
|
5
|
+
|
|
6
|
+
const IMAGE_EVENTS = ['delete', 'import', 'pull', 'push', 'tag'];
|
|
7
|
+
|
|
8
|
+
const CONTAINER_EVENTS = [
|
|
9
|
+
'attach',
|
|
10
|
+
'commit',
|
|
11
|
+
'copy',
|
|
12
|
+
'create',
|
|
13
|
+
'destroy',
|
|
14
|
+
'die',
|
|
15
|
+
'exec_create',
|
|
16
|
+
'exec_start',
|
|
17
|
+
'export',
|
|
18
|
+
'kill',
|
|
19
|
+
'oom',
|
|
20
|
+
'pause',
|
|
21
|
+
'rename',
|
|
22
|
+
'resize',
|
|
23
|
+
'restart',
|
|
24
|
+
'start',
|
|
25
|
+
'stop',
|
|
26
|
+
'top',
|
|
27
|
+
'unpause',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export interface LayerMtimes {
|
|
31
|
+
[id: string]: string | number | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface DockerEvent {
|
|
35
|
+
status: string;
|
|
36
|
+
id: string;
|
|
37
|
+
from: string;
|
|
38
|
+
Type: 'container';
|
|
39
|
+
Action: 'destroy';
|
|
40
|
+
Actor: {
|
|
41
|
+
ID: '9f49d061590dc6d242f902dff33a4536ac1baf584c19a4e68c0675178b4e567b';
|
|
42
|
+
Attributes: {
|
|
43
|
+
image: 'sha256:8ecd94718638c95609ec3a91d3241ce84b025de9bd089e1c463c8b4e7f83fc25';
|
|
44
|
+
'io.balena.architecture': 'aarch64';
|
|
45
|
+
'io.balena.device-type': 'jetson-xavier';
|
|
46
|
+
'io.balena.qemu.version': '7.0.0+balena1-aarch64';
|
|
47
|
+
maintainer: 'charlie <carlos.alvarez@kiwibot.com> dadaroce <davidson@kiwibot.com>';
|
|
48
|
+
name: 'zen_lichterman';
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
scope: 'local';
|
|
52
|
+
time: 1701265973;
|
|
53
|
+
timeNano: '1701265973112542359';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const parseEventStream = (docker: Docker) =>
|
|
57
|
+
docker.listImages({ all: true }).then(function (images) {
|
|
58
|
+
const layerMtimes: LayerMtimes = {};
|
|
59
|
+
// Start off by setting all current images to an mtime of 0 as we've never seen them used
|
|
60
|
+
// If we've never seen the layer used then it's likely created before we started
|
|
61
|
+
// listening and so set the last used time to 0 as we know it should be older than
|
|
62
|
+
// anything we've seen
|
|
63
|
+
for (const image of images) {
|
|
64
|
+
layerMtimes[image.Id] = 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return es.pipeline(
|
|
68
|
+
JSONStream.parse(undefined) as any as es.MapStream,
|
|
69
|
+
es.mapSync(function ({ status, id, from, timeNano }: DockerEvent) {
|
|
70
|
+
if (IMAGE_EVENTS.includes(status)) {
|
|
71
|
+
if (status === 'delete') {
|
|
72
|
+
if (layerMtimes[id] != null) {
|
|
73
|
+
delete layerMtimes[id];
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
layerMtimes[id] = timeNano;
|
|
77
|
+
}
|
|
78
|
+
} else if (CONTAINER_EVENTS.includes(status)) {
|
|
79
|
+
layerMtimes[from] = timeNano;
|
|
80
|
+
}
|
|
81
|
+
return layerMtimes;
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export function dockerMtimeStream(docker: Docker) {
|
|
87
|
+
return Bluebird.join(
|
|
88
|
+
docker.getEvents(),
|
|
89
|
+
parseEventStream(docker),
|
|
90
|
+
(stream, streamParser) =>
|
|
91
|
+
es.pipeline(stream as any as es.MapStream, streamParser),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import Bluebird from 'bluebird';
|
|
2
|
+
import type Docker from 'dockerode';
|
|
3
|
+
import { LayerMtimes } from './docker-event-stream';
|
|
4
|
+
|
|
5
|
+
const saneRepoAttrs = function (repoAttrs: string[] | undefined) {
|
|
6
|
+
if (repoAttrs == null) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
if (
|
|
10
|
+
repoAttrs.includes('<none>:<none>') ||
|
|
11
|
+
repoAttrs.includes('<none>@<none>')
|
|
12
|
+
) {
|
|
13
|
+
return [];
|
|
14
|
+
} else {
|
|
15
|
+
return repoAttrs;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const createNode = (id: string): ImageNode => ({
|
|
20
|
+
id,
|
|
21
|
+
size: 0,
|
|
22
|
+
repoTags: [],
|
|
23
|
+
repoDigests: [],
|
|
24
|
+
mtime: undefined,
|
|
25
|
+
children: {},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const getMtimeFrom = function (layerMtimes: LayerMtimes, attributes: string[]) {
|
|
29
|
+
for (const key of attributes) {
|
|
30
|
+
if (layerMtimes[key] != null) {
|
|
31
|
+
return layerMtimes[key];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getMtime = function (tree: ImageNode, layerMtimes: LayerMtimes) {
|
|
37
|
+
return (
|
|
38
|
+
layerMtimes[tree.id] ??
|
|
39
|
+
getMtimeFrom(layerMtimes, tree.repoTags) ??
|
|
40
|
+
getMtimeFrom(layerMtimes, tree.repoDigests)
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export interface ImageNode {
|
|
45
|
+
id: string;
|
|
46
|
+
size: number;
|
|
47
|
+
repoTags: string[];
|
|
48
|
+
repoDigests: string[];
|
|
49
|
+
mtime: LayerMtimes[string];
|
|
50
|
+
children: Record<string, ImageNode>;
|
|
51
|
+
isUsedByAContainer?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const createTree = function (
|
|
55
|
+
images: Docker.ImageInfo[],
|
|
56
|
+
containers: Docker.ContainerInfo[],
|
|
57
|
+
layerMtimes: LayerMtimes,
|
|
58
|
+
): ImageNode {
|
|
59
|
+
const now = Date.now() * Math.pow(10, 6); // convert to nanoseconds
|
|
60
|
+
const usedImageIds = new Set(containers.map((c) => c.ImageID));
|
|
61
|
+
const tree: {
|
|
62
|
+
[key: string]: ImageNode;
|
|
63
|
+
} = {};
|
|
64
|
+
const root =
|
|
65
|
+
'0000000000000000000000000000000000000000000000000000000000000000';
|
|
66
|
+
|
|
67
|
+
for (const image of images) {
|
|
68
|
+
const node = (tree[image.Id] ??= createNode(image.Id));
|
|
69
|
+
const parentId = image.ParentId || root;
|
|
70
|
+
const parent = (tree[parentId] ??= createNode(parentId));
|
|
71
|
+
|
|
72
|
+
node.repoTags = saneRepoAttrs(image.RepoTags);
|
|
73
|
+
node.repoDigests = saneRepoAttrs(image.RepoDigests);
|
|
74
|
+
node.size = image.Size;
|
|
75
|
+
// If we haven't seen the image at all then assume it is brand new and default it's
|
|
76
|
+
// mtime to `now` to avoid removing it
|
|
77
|
+
node.mtime = getMtime(node, layerMtimes) ?? now;
|
|
78
|
+
node.isUsedByAContainer = usedImageIds.has(image.Id);
|
|
79
|
+
parent.children[image.Id] = node;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
tree[root].mtime = now;
|
|
83
|
+
tree[root].isUsedByAContainer = false;
|
|
84
|
+
return tree[root];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export function dockerImageTree(docker: Docker, layerMtimes: LayerMtimes) {
|
|
88
|
+
return Bluebird.join(
|
|
89
|
+
docker.listImages({ all: true }),
|
|
90
|
+
docker.listContainers({ all: true }),
|
|
91
|
+
layerMtimes,
|
|
92
|
+
createTree,
|
|
93
|
+
);
|
|
94
|
+
}
|
package/lib/docker.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Docker from 'dockerode';
|
|
2
|
+
import Bluebird from 'bluebird';
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
|
|
5
|
+
const getDockerConnectOpts = function (hostObj: Docker.DockerOptions) {
|
|
6
|
+
if (!_.isEmpty(hostObj)) {
|
|
7
|
+
return Promise.resolve(hostObj);
|
|
8
|
+
}
|
|
9
|
+
return Promise.resolve({
|
|
10
|
+
socketPath: '/var/run/docker.sock',
|
|
11
|
+
Promise: Bluebird as any as PromiseConstructor,
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function getDocker(hostObj: Docker.DockerOptions) {
|
|
16
|
+
return getDockerConnectOpts(hostObj).then((opts) => new Docker(opts));
|
|
17
|
+
}
|