-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
142 lines (127 loc) · 4.27 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
const util = require('util');
const exec = util.promisify(require('child_process').exec);
function isTrue(v) {
return v === true || v && v.toLowerCase() === 'true';
}
const config = {
pruneImages: isTrue(process.env.PRUNE_IMAGES) || false,
pruneContainers: isTrue(process.env.PRUNE_CONTAINERS) || false,
registry: process.env.REGISTRY || '',
registryUser: process.env.REGISTRY_USER || '',
registryPassword: process.env.REGISTRY_PASSWORD || '',
verbose: isTrue(process.env.VERBOSE) || false,
interval: process.env.INTERVAL ? parseInt(process.env.INTERVAL, 10) : 30
};
console.log('Starting service-update.');
console.log('Configuration: ', config);
async function getServices() {
const { stdout, stderr } = await exec('docker service ls --format "{{.Name}}"');
if (stderr) {
throw new Error('Cannot read services');
}
return stdout.split('\n').filter(it => !!it).map(it => {
return { name: it };
});
}
async function getServiceInfo(name) {
const { stdout, stderr } = await exec(`docker service inspect ${name}`);
if (stderr) {
throw new Error(`Cannot read service information for ${name}: ${stderr}`);
}
try {
var data = JSON.parse(stdout);
} catch (err) {
throw new Error(`Cannot parse service information for ${name}`);
}
return {
name,
labels: data[0].Spec.Labels,
enabled: data[0].Spec.Labels['com.pymet.serviceupdate.watch'] === 'true',
image: data[0].Spec.Labels['com.docker.stack.image']
};
}
async function pullImage(image) {
if (config.verbose) {
console.log(`Checking new images of ${image}`);
}
const { stdout, stderr } = await exec(`docker pull ${image}`);
if (stderr) {
throw new Error('Cannot pull images ' + stderr);
}
if (stdout.indexOf(`Image is up to date for ${image}`) !== -1) {
if (config.verbose) {
console.log(`No new image for ${image}`);
}
return false;
}
if (stdout.indexOf(`Downloaded newer image for ${image}`) !== -1) {
console.log(`Downloaded newer image for ${image}`);
return true;
}
if (stderr) {
throw new Error('Unknown pull response');
}
}
async function updateService(service) {
console.log(`Updating service ${service.name}`);
const { stdout, stderr } = await exec(`docker service update --force --image ${service.image} ${service.name} --detach=false`);
console.log(`Service ${service.name} is updated: ${stdout}`);
if (stderr) {
throw new Error('Cannot update service: ' + stderr);
}
}
async function login(registry, username, password) {
const { stdout, stderr } = await exec(`docker login ${registry} -u="${username}" -p="${password}"`);
if (stderr) {
throw new Error(`Cannot login: ${stderr}`);
}
console.log(`Logging in: ${stdout}`);
}
async function cleanup() {
if (config.pruneContainers) {
console.log('Removing stopped containers.');
await exec('docker container prune -f');
console.log('Stopped containers removed.');
}
if (config.pruneImages) {
console.log('Removing untagged images.');
await exec('docker rmi $(docker images -q --filter "dangling=true")');
console.log('Untagged images removed.');
}
}
async function watch() {
const services = await getServices();
const serviceInfo = await Promise.all(services.map(service => getServiceInfo(service.name)));
const enabledServices = serviceInfo.filter(service => service.enabled);
if (config.verbose) {
console.log('Detected services: ');
console.log(enabledServices.map(service => `${service.name} ${service.image}`).join('\n'));
}
enabledServices.forEach(async service => {
const imageDownloaded = await pullImage(service.image);
if (imageDownloaded) {
await updateService(service);
await cleanup();
}
});
setTimeout(watch, config.interval * 1000);
}
async function init() {
const loginInfo = await login(config.registry, config.registryUser, config.registryPassword);
watch();
}
try {
init();
} catch (err) {
console.error('Fatal error:', err);
process.exit(1);
}
process.on('SIGINT', () => {
console.info('Got SIGINT (aka ctrl-c in docker). Graceful shutdown ', new Date().toISOString());
process.exit();
});
// quit properly on docker stop
process.on('SIGTERM', () => {
console.info('Got SIGTERM (docker container stop). Graceful shutdown ', new Date().toISOString());
process.exit();
})