Created
January 4, 2020 13:53
-
-
Save thomascube/77702323bd2852d7c28f801d077a20a6 to your computer and use it in GitHub Desktop.
Automated Docker builds with Google cloud functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const bent = require('bent'); | |
const httpJSON = bent('json'); | |
const admin = require('firebase-admin'); | |
const DOCKER_BUILD_TRIGGER = 'https://hub.docker.com/api/build/v1/source/<this-is-secret>/trigger/<another-secret>/call/'; | |
const BUILD_TAGS = { | |
'1.3.x-apache': '7.2-apache', | |
'1.3.x-fpm': '7.2-fpm', | |
'1.3.x-fpm-alpine': '7.2-fpm-alpine', | |
'1.4.x-apache': '7.3-apache', | |
'1.4.x-fpm': '7.3-fpm', | |
'1.4.x-fpm-alpine': '7.3-fpm-alpine', | |
'latest-apache': '7.3-apache', | |
'latest-fpm': '7.3-fpm', | |
'latest-fpm-alpine': '7.3-fpm-alpine', | |
}; | |
let database; | |
let imageVersionCache = {}; | |
/** | |
* Modify the given tag name into a valid database key | |
* | |
* @param {String} tag | |
* @return {String} | |
*/ | |
function getDBKey(tag) { | |
return tag.replace(/[.#$]/g, 'x'); | |
} | |
/** | |
* Fetch PHP docker image info for the given tag | |
* | |
* @param {String} tag | |
* @return {Object} | |
*/ | |
async function fetchImageVersion(tag) { | |
// return cached result | |
if (imageVersionCache[tag]) { | |
return imageVersionCache[tag]; | |
} | |
// console.log('DO fetchImageVersion', tag); | |
let data = await httpJSON('https://hub.docker.com/v2/repositories/library/php/tags/?page_size=25&page=1&name=' + encodeURIComponent(tag)); | |
let result = null; | |
// console.log('DONE fetchImageVersion', tag, data.results); | |
if (Array.isArray(data.results)) { | |
data.results.forEach((item) => { | |
if (item.name === tag) { | |
result = item; | |
} | |
}); | |
imageVersionCache[tag] = result; | |
} | |
return result; | |
} | |
/** | |
* Fetch the build version stored from last check/build | |
* | |
* @param {String} tag | |
* @return {String} | |
*/ | |
async function fetchBuildVersion(tag) { | |
// read from databse | |
return database.ref('build-version/' + getDBKey(tag)).once('value') | |
.then((data) => { | |
// console.log('DONE fetchBuildVersion', tag, data.val()) | |
return data.val() ? data.val().version : null; | |
}); | |
} | |
/** | |
* Trigger a new Roundcube docker build for the given tag | |
* | |
* @param {String} tag | |
*/ | |
async function triggerBuild(tag) { | |
// console.log('DO Trigger Build for ' + tag); | |
const httpPost = bent('POST', 'json', 202); | |
const response = await httpPost(DOCKER_BUILD_TRIGGER, {docker_tag: tag}); | |
// console.log('DONE Trigger Build', response); | |
return response.state === 'Success' || response.state === 'Building'; | |
} | |
/** | |
* Write the last build version into the database | |
* | |
* @param {String} tag | |
* @param {String} version | |
*/ | |
async function updateBuildVersion(tag, version) { | |
// console.log('DO updateBuildVersion', tag, version); | |
return database.ref('build-version/' + getDBKey(tag)).set({version: version}); | |
} | |
/** | |
* Main routine to check for new PHP image versions | |
* | |
* This triggers a new Roundcube build if the specific PHP image | |
* has been updated since the last build. | |
* | |
* @param {String} phpTag | |
* @param {String} rcubeTag | |
* @param {Boolean} dryRun | |
*/ | |
async function checkPhpImage(phpTag, rcubeTag, dryRun) { | |
const [latest, build] = await Promise.all([fetchImageVersion(phpTag), fetchBuildVersion(phpTag)]); | |
if (latest && latest.last_updated && (!build || build < latest.last_updated)) { | |
if (!dryRun) { | |
try { | |
await triggerBuild(rcubeTag); | |
await updateBuildVersion(phpTag, latest.last_updated); | |
} catch (err) { | |
console.error('Failed to checkPhpImage(' + BUILD_TAGS[rcubeTag] + ', ' + rcubeTag + ')', err); | |
return '! New build failed for ' + rcubeTag; | |
} | |
} | |
return '* New build triggered for ' + rcubeTag + ' from php:' + phpTag + (dryRun ? ' (dry-run)' : ''); | |
} | |
return '* No update for php:' + phpTag + ' (' + rcubeTag + ')'; | |
} | |
// main entry point for runtime | |
module.exports.handler = async (event) => { | |
database = admin.database(); | |
imageVersionCache = {}; // clear in-memory cache | |
let tasks = []; | |
let dryRun = event.query && Boolean(event.query.dryrun); | |
for (let rcubeTag in BUILD_TAGS) { | |
tasks.push(checkPhpImage(BUILD_TAGS[rcubeTag], rcubeTag, dryRun)); | |
} | |
let buildLog = await Promise.all(tasks); | |
return {log: buildLog.join('\n')}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const functions = require('firebase-functions'); | |
const admin = require('firebase-admin'); | |
const app = require('./app'); | |
admin.initializeApp(); | |
// register http endpoint | |
exports.dockerSync = functions.https.onRequest(async (request, response) => { | |
let res = await app.handler(request) | |
var msg = 'SUCCESS!\n'; | |
if (res.log) { | |
msg += res.log + '\n'; | |
} | |
response.send(msg); | |
}); | |
// register scheduled function | |
exports.scheduledFunctionCrontab = functions.pubsub.schedule('0 9 * * *').timeZone('UTC').onRun(async (context) => { | |
console.log('Start scheduled execution'); | |
let res = await app.handler(context); | |
if (res.log) { | |
console.log(res.log); | |
} else { | |
console.log('Done'); | |
} | |
return null; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "functions", | |
"description": "Cloud Functions for Firebase", | |
"scripts": { | |
"lint": "eslint .", | |
"serve": "firebase serve --only functions", | |
"shell": "firebase functions:shell", | |
"start": "npm run shell", | |
"deploy": "firebase deploy --only functions", | |
"logs": "firebase functions:log" | |
}, | |
"engines": { | |
"node": "8" | |
}, | |
"dependencies": { | |
"bent": "^7.0.2", | |
"firebase-admin": "^8.6.0", | |
"firebase-functions": "^3.3.0" | |
}, | |
"devDependencies": { | |
"eslint": "^5.12.0", | |
"eslint-plugin-promise": "^4.0.1", | |
"firebase-functions-test": "^0.1.6" | |
}, | |
"private": true | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment