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/build/index.js CHANGED
@@ -1,242 +1,187 @@
1
- // Generated by CoffeeScript 1.12.7
2
- (function() {
3
- var Bluebird, Docker, DockerGC, DockerProgress, EventEmitter, _, dockerImageTree, dockerMtimeStream, dockerUtils, getImagesToRemove, getUnusedTreeLeafs, recordGcRunTime, streamToString,
4
- bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
5
-
6
- Bluebird = require('bluebird');
7
-
8
- _ = require('lodash');
9
-
10
- EventEmitter = require('eventemitter3').EventEmitter;
11
-
12
- DockerProgress = require('docker-progress').DockerProgress;
13
-
14
- Docker = require('dockerode');
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
- children = _(tree.children).values().filter(_.negate(_.property('removed'))).value();
29
- if (children.length === 0 && !tree.isUsedByAContainer) {
30
- result.push(tree);
31
- } else {
32
- for (i = 0, len = children.length; i < len; i++) {
33
- child = children[i];
34
- getUnusedTreeLeafs(child, result);
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
- getImagesToRemove = function(tree, reclaimSpace, metrics) {
42
- var leaf, leafs, result, size;
43
- tree = _.clone(tree);
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
- leafs = _.orderBy(getUnusedTreeLeafs(tree), ['mtime', 'size'], ['asc', 'desc']);
48
- if (leafs.length === 0) {
49
- break;
50
- }
51
- leaf = leafs[0];
52
- if (leaf !== tree) {
53
- result.push(leaf);
54
- size += leaf.size;
55
- }
56
- leaf.removed = true;
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
- streamToString = function(stream) {
63
- return new Bluebird(function(resolve, reject) {
64
- var chunks;
65
- chunks = [];
66
- return stream.on('error', reject).on('data', function(chunk) {
67
- return chunks.push(chunk);
68
- }).on('end', function() {
69
- return resolve(Buffer.concat(chunks).toString());
70
- });
71
- });
72
- };
73
-
74
- recordGcRunTime = function(t0, metrics) {
75
- var dt, duration;
76
- dt = process.hrtime(t0);
77
- duration = dt[0] * 1000 + dt[1] / 1e6;
78
- return metrics.emit('gcRunTime', duration);
79
- };
80
-
81
- DockerGC = (function() {
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
- DockerGC.prototype.setHostname = function(hostname) {
91
- return this.host = hostname;
92
- };
93
-
94
- DockerGC.prototype.setDocker = function(hostObj) {
95
- this.currentMtimes = {};
96
- this.hostObj = _.defaults({
97
- Promise: Bluebird
98
- }, hostObj);
99
- this.dockerProgress = new DockerProgress({
100
- docker: new Docker(this.hostObj)
101
- });
102
- return dockerUtils.getDocker(this.hostObj).then((function(_this) {
103
- return function(docker) {
104
- _this.docker = docker;
105
- return _this.baseImagePromise = _this.getDaemonArchitecture().then(function(arch) {
106
- switch (arch) {
107
- case 'arm':
108
- return 'arm32v6/alpine:3.6';
109
- case 'arm64':
110
- return 'arm64v8/alpine:3.6';
111
- case 'amd64':
112
- return 'alpine:3.6';
113
- default:
114
- throw new Error('Could not detect architecture of remote host');
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
- })(this));
147
- }
148
- };
149
-
150
- DockerGC.prototype.garbageCollect = function(reclaimSpace, attemptAll) {
151
- var err, startTime;
152
- if (attemptAll == null) {
153
- attemptAll = false;
154
- }
155
- err = null;
156
- startTime = process.hrtime();
157
- this.metrics.emit('spaceReclaimed', reclaimSpace);
158
- return dockerImageTree(this.docker, this.currentMtimes).then((function(_this) {
159
- return function(tree) {
160
- return getImagesToRemove(tree, reclaimSpace, _this.metrics);
161
- };
162
- })(this)).each((function(_this) {
163
- return function(image) {
164
- return _this.removeImage(image)["catch"](function(e) {
165
- _this.metrics.emit('imageRemovalError', e.statusCode);
166
- console.log("[GC " + _this.host + "]: Failed to remove image: ", image);
167
- console.log(e);
168
- if (attemptAll) {
169
- return err != null ? err : err = e;
170
- } else {
171
- recordGcRunTime(startTime, _this.metrics);
172
- throw e;
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
- DockerGC.prototype.runDisposer = function(image, command) {
198
- return this.docker.run(image, command).disposer(function(container) {
199
- return container.wait().then(function() {
200
- return container.remove();
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
- DockerGC.prototype.getDaemonFreeSpace = function() {
206
- return this.baseImagePromise.tap((function(_this) {
207
- return function(baseImage) {
208
- return _this.dockerProgress.pull(baseImage, _.noop);
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
+ }