diff --git a/rootfs/labels/app.js b/rootfs/labels/app.js deleted file mode 100644 index 6e86596..0000000 --- a/rootfs/labels/app.js +++ /dev/null @@ -1,135 +0,0 @@ -const Docker = require('dockerode'); -const redis = require('redis'); -const ERROR = 1; - -class Labels{ - #docker; - #redis; - #poll = false; - #webhook = { - headers:{'Content-Type':'application/json'} - }; - - constructor(){ - this.#docker = new Docker({socketPath:'/run/docker.sock'}); - if('' !== process.env.LABELS_WEBHOOK_AUTH_BASIC){ - this.#webhook.headers['Authorization'] = 'Basic ' + Buffer.from(process.env.LABELS_WEBHOOK_AUTH_BASIC).toString('base64') - } - } - - #log(message, type){ - console.log(JSON.stringify({ - time:new Date().toISOString(), - type:(type === ERROR) ? 'ERROR' : 'INFO', - message:message, - })); - } - - async watch(){ - if('' !== process.env.LABELS_WEBHOOK){ - this.#log(`using webhook ${process.env.LABELS_WEBHOOK}`); - } - - this.#redis = await redis.createClient({ - url:process.env.LABELS_REDIS_URL, - pingInterval:30000, - socket:{ - rejectUnauthorized: false, - } - }); - - this.#redis.connect(); - this.#redis.on('ready', ()=>{ - this.#log('successfully connected to redis database'); - (async() => { - await this.dockerPoll(); - })(); - this.dockerEvents(); - }); - - this.#redis.on('error', error =>{ - this.#log(error, ERROR); - }); - - setInterval(async() => { - await this.dockerPoll(); - }, parseInt(process.env.LABELS_INTERVAL)*1000); - } - - dockerEvents(){ - this.#docker.getEvents({}, (error, data) => { - if(error){ - this.#log(error, ERROR); - }else{ - data.on('data', async(chunk) => { - const event = JSON.parse(chunk.toString('utf8')); - if(/Container/i.test(event?.Type) && /^(start|stop|restart|kill|die|destroy)$/i.test(event?.status)){ - await this.dockerInspect(event.id, event.status); - } - }); - } - }); - } - - async dockerPoll(){ - if(!this.#poll){ - try{ - this.#poll = true; - this.#docker.listContainers((error, containers) => { - if(!error){ - containers.forEach(async(container) => { - await this.dockerInspect(container.Id, 'poll'); - }); - } - }); - }catch(e){ - this.#log(e, ERROR); - }finally{ - this.#poll = false; - } - } - } - - dockerInspect(id, status = null){ - return(new Promise((resolve, reject) => { - const container = this.#docker.getContainer(id); - container.inspect(async(error, data) => { - if(!error){ - let update = false; - const webHook = {event:status, labels:{}}; - if(/start|restart|poll/i.test(status)){ - update = true; - } - - this.#log(`inspect container {${(data?.Name || data?.id).replace(/^\//i, '')}}${( - (null === status) ? '' : ` event[${status}]` - )}`); - - for(const label in data?.Config?.Labels){ - if(/traefik\//i.test(label)){ - webHook.labels[label] = data?.Config?.Labels[label]; - if(update){ - this.#log(`${label} = ${data?.Config?.Labels[label]}`); - await this.#redis.set(label, data?.Config?.Labels[label], {EX:parseInt(process.env.LABELS_INTERVAL) + parseInt(process.env.LABELS_TIMEOUT)}); - }else{ - await this.#redis.del(label); - } - } - } - if('' !== process.env.LABELS_WEBHOOK){ - try{ - await fetch(process.env.LABELS_WEBHOOK, {method:( - (update) ? 'PUT' : 'DELETE' - ), body:JSON.stringify(webHook), headers:this.#webhook.headers, signal:AbortSignal.timeout(5000)}); - }catch(e){ - this.#log(e, ERROR); - } - } - resolve(true); - } - }); - })); - } -} - -new Labels().watch(); \ No newline at end of file diff --git a/rootfs/labels/main.js b/rootfs/labels/main.js index fe0a551..17e50c1 100644 --- a/rootfs/labels/main.js +++ b/rootfs/labels/main.js @@ -1,19 +1,141 @@ process.once('SIGTERM', () => process.exit(0)); process.once('SIGINT', () => process.exit(0)); -const { fork } = require('node:child_process'); -const child = fork(`${__dirname}/app.js`, [], { - env:{ - LABELS_REDIS_URL:process.env?.LABELS_REDIS_URL || 'rediss://localhost:6379/0', - LABELS_INTERVAL:parseInt(process.env?.LABELS_INTERVAL || 300), - LABELS_TIMEOUT:parseInt(process.env?.LABELS_TIMEOUT|| 30), - LABELS_WEBHOOK:process.env?.LABELS_WEBHOOK || '', - LABELS_WEBHOOK_AUTH_BASIC:process.env?.LABELS_WEBHOOK_AUTH_BASIC || '', + +const Docker = require('dockerode'); +const redis = require('redis'); + +const ERROR = 1; +const POLL_INTERVAL = parseInt(process.env?.LABELS_INTERVAL || 300); +const POLL_TIMEOUT = parseInt(process.env?.LABELS_TIMEOUT|| 30); + +class Labels{ + #docker; + #redis; + #poll = false; + #webhook = { + headers:{'Content-Type':'application/json'} + }; + + constructor(){ + this.#docker = new Docker({socketPath:'/run/docker.sock'}); + if('' !== process.env.LABELS_WEBHOOK_AUTH_BASIC){ + this.#webhook.headers['Authorization'] = 'Basic ' + Buffer.from(process.env.LABELS_WEBHOOK_AUTH_BASIC).toString('base64') + } } -}); -child.on('error', (error) =>{ - console.error(error); -}); -child.on('close', (code) =>{ - console.warn(`child process closed with exit code ${code}`); - process.exit(code); -}); \ No newline at end of file + + #log(message, type){ + console.log(JSON.stringify({ + time:new Date().toISOString(), + type:(type === ERROR) ? 'ERROR' : 'INFO', + message:message, + })); + } + + async watch(){ + if('' !== process.env.LABELS_WEBHOOK){ + this.#log(`using webhook ${process.env.LABELS_WEBHOOK}`); + } + + this.#redis = await redis.createClient({ + url:process.env.LABELS_REDIS_URL, + pingInterval:30000, + socket:{ + rejectUnauthorized: false, + } + }); + + this.#redis.connect(); + this.#redis.on('ready', ()=>{ + this.#log('successfully connected to redis'); + (async() => { + await this.dockerPoll(); + })(); + this.dockerEvents(); + }); + + this.#redis.on('error', error =>{ + this.#log(error, ERROR); + }); + + setInterval(async() => { + await this.dockerPoll(); + }, POLL_INTERVAL*1000); + } + + dockerEvents(){ + this.#docker.getEvents({}, (error, data) => { + if(error){ + this.#log(error, ERROR); + }else{ + data.on('data', async(chunk) => { + const event = JSON.parse(chunk.toString('utf8')); + if(/Container/i.test(event?.Type) && /^(start|kill)$/i.test(event?.status)){ + await this.dockerInspect(event.id, event.status); + } + }); + } + }); + } + + async dockerPoll(){ + if(!this.#poll){ + try{ + this.#poll = true; + this.#docker.listContainers((error, containers) => { + if(!error){ + containers.forEach(async(container) => { + await this.dockerInspect(container.Id, 'poll'); + }); + } + }); + }catch(e){ + this.#log(e, ERROR); + }finally{ + this.#poll = false; + } + } + } + + dockerInspect(id, status = null){ + return(new Promise((resolve, reject) => { + const container = this.#docker.getContainer(id); + container.inspect(async(error, data) => { + if(!error){ + const update = (/start|poll/i.test(status)) ? true : false; + const webHook = {event:status, labels:{}}; + + this.#log(`inspect container {${(data?.Name || data?.id).replace(/^\//i, '')}}${( + (null === status) ? '' : ` event[${status}]` + )}`); + + for(const label in data?.Config?.Labels){ + if(/traefik\//i.test(label)){ + if('' !== process.env.LABELS_WEBHOOK){ + webHook.labels[label] = data?.Config?.Labels[label]; + } + if(update){ + await this.#redis.set(label, data?.Config?.Labels[label], {EX:POLL_INTERVAL + POLL_TIMEOUT}); + }else{ + await this.#redis.del(label); + } + } + } + + if('' !== process.env.LABELS_WEBHOOK){ + try{ + await fetch(process.env.LABELS_WEBHOOK, {method:( + (update) ? 'PUT' : 'DELETE' + ), body:JSON.stringify(webHook), headers:this.#webhook.headers, signal:AbortSignal.timeout(2500)}); + }catch(e){ + this.#log(e, ERROR); + } + } + + resolve(true); + } + }); + })); + } +} + +new Labels().watch(); \ No newline at end of file