UNPKG

ranflare

Version:

Rancher + Cloudflare External DNS

181 lines (149 loc) 6.17 kB
#!/usr/bin/env node const axios = require("axios"); const program = require("caporal"); const winston = require("winston"); const CloudFlare = require("cloudflare"); const package = require("./package.json"); /** * Updates cloudflare data */ async function update(args, options, logger, cloudflare) { logger.info(`Updating ${options.cloudflareZone.name} (${options.cloudflareUser.email}) zone`); const version = "2016-07-29"; const response = await axios.request({ url: `/${version}`, baseURL: options.rancherMetadataUrl, headers: { "Accept": "application/json", } }); const data = response.data; const balancers = data.services.filter(service => service.kind === "loadBalancerService" && service.state === "active" && !!service.lb_config); if (balancers.length <= 0) { logger.error("No load balancers running."); return; } const hosts = data.hosts.filter(host => { for (let b in balancers) { for (let c in balancers[b].containers) { if (balancers[b].containers[c].state !== 'running') { continue; } if (host.uuid === balancers[b].containers[c].host_uuid) { return true; } } } return false; }); if (hosts.length <= 0) { logger.error("No available hosts running the load balancer."); return; } const addresses = Array.from(new Set(hosts.map(host => host.agent_ip))); const entries = balancers .map(balancer => balancer.lb_config) .reduce((prev, next) => prev.concat(next.port_rules), []) .map(config => config.hostname); // List all registered entries let pages = 1; let allRecords = []; for (let i = 0; i < pages; i++) { const page = await cloudflare.dnsRecords.browse(options.cloudflareZone.id, { page: i + 1 }); allRecords = allRecords.concat(page.result); pages = page.result_info.total_pages; } // Removes entries if they point to invalid (outdated/removed) host addresses const existingRecords = allRecords.filter(r => r.type === "A" && entries.includes(r.name)); for (const record of existingRecords) { if (!addresses.includes(record.content)) { logger.info(`Removing "${record.content}" that points to unknown address "${record.content}"`); try { await cloudflare.dnsRecords.del(options.cloudflareZone.id, record.id); } catch (err) { logger.error(`Failed to remove dns record "${record.name}" (${record.id})`); } } } for (const entry of entries) { for (const address of addresses) { const existingAddresses = allRecords.filter(r => r.type === "A" && r.name === entry).map(record => record.content); if (!existingAddresses.includes(address)) { logger.info(`Adding DNS record "${entry}" pointing to "${address}"`); try { await cloudflare.dnsRecords.add(options.cloudflareZone.id, { type: "A", name: entry, content: address, proxied: !options.cloudflareNoproxy, }); } catch (err) { logger.error(`Failed to add dns record ${entry} -> ${address}`); logger.error(err); } } } } } /** * Bootstrap code */ async function bootstrap() { winston.configure({ transports: [ new winston.transports.Console({ colorize: true, prettyPrint: true, }) ], }) program .version(package.version) .logger(winston) .command("run", "Runs the agent, monitoring docker and updating cloudflare entries.") .option("--cloudflare.email <email>", "CloudFlare Email", program.STRING) .option("--cloudflare.key <email>", "CloudFlare API Key", program.STRING) .option("--cloudflare.zone <zone>", "CloudFlare Zone ID", program.STRING) .option("--cloudflare.noproxy", "CloudFlare Proxy Usage", program.BOOLEAN, false) .option("--rancher.metadata.url <url>", "Rancher Metadata URL", program.STRING, "http://rancher-metadata") .option("--poll.interval <interval>", "Poll interval", program.NUMBER, 300000) .action(async (args, options, logger) => { if (!options.cloudflareEmail || !options.cloudflareKey || !options.cloudflareZone) { throw new Error("Missing cloudflare email and/or key.") } const cloudflare = CloudFlare({ email: options.cloudflareEmail, key: options.cloudflareKey, }); try { const response = await cloudflare.user.read(); options.cloudflareUser = response.result; } catch (err) { if (err.statusCode === 403) { throw new Error("Invalid cloudflare's email and/or key"); } else { throw new Error("Failed to retrieve cloudflare's user information"); } } try { const response = await cloudflare.zones.read(options.cloudflareZone); options.cloudflareZone = response.result; } catch (err) { if (err.statusCode === 403) { throw new Error("Invalid cloudflare's zone id"); } else { throw new Error("Failed to retrieve cloudflare's zone information"); } } while (true) { try { await update(args, options, logger, cloudflare); } catch (err) { logger.error(err); } await new Promise(resolve => setTimeout(resolve, options.pollInterval)); } }); program.parse(process.argv); } bootstrap();