Gatsby programmatisch steuern
Außer den Routen in der Grafik kommen noch eine zum Löschen des WordPress Caches (hauptsächlich Bilder) hinzu. Das Starten des Gatsby Prozess wurde in zwei Funktionen ausgelagert. Die Gatsby Kommandozeilenausgabe wird HTML formatiert in Redis gespeichert. Von dort wird sie dann per Polling in einem Wordpress Plugin abgerufen.
./api/routes/gatsby.js /*
Module laden
*/
require('dotenv').config();
const path = require('path');
const { promises: fs } = require('fs');
const { spawn } = require('child_process');
module.exports = async (fastify) => {
/*
Ohne Bearer Token im Request Header geht es hier nicht weiter.
*/
fastify.requireAuthentication();
/*
Diese Funktion prüft ob das Flag isBuilding gesetzt ist
und startet je nach dem Gatsby mit übergebenen Kommando.
Es gibt direkt ein Objekt zurück, welches zum Client
gesendet wird.
*/
const handleRequest = async gatsbyCmd => {
const { redis } = fastify;
const isBuilding = await redis.get('isBuilding');
if (isBuilding === 'true') {
if (gatsbyCmd === 'clean') {
redis.set('isBuilding', 'false');
}
return '{ "result": 0 }';
}
await redis.set('isBuilding', 'true');
gatsby(gatsbyCmd);
return '{ "result": 1 }';
}
/*
Beim Data Event wird dem message[] Array der Lesebuffer angefügt.
Vorab werden die Konsolensteuerzeichen für die farbliche Anpassung
durch HTML Markup ersetzt. Nach dem Schließen des Prozesses und wenn
das Kommando *build* war, wird das Bash Script cp.sh ausgeführt.
Es kopiert den public Ordner in ein Verzeichniss, das den aktuellen
Timestamp als Namen hat, löscht ein symbolic Link und erstellt einen
zum kopierten Verzeichniss. Dadurch gibt es praktisch keine Downtime
und man hat die Möglichkeit eines schnellen Rollbacks im Fehlerfall.
*/
const gatsby = async cmd => {
const messages = [];
const proc = spawn(`gatsby`, [cmd === 'rebuild' ? 'clean' : cmd], {cwd: path.join(`../`, `gatsby`), shell: true});
fastify.redis.set(`gatsby-${cmd}`, JSON.stringify({ code: -1, message: messages }));
proc.stdout.on('data', data => {
const msg = data.toString()
.replace('\u001b[2K\u001b[1A\u001b[2K\u001b[G', '')
.replace('success', '<span style="color:darkgreen; font-weight: bold;">success</span>')
.replace('info', '<span style="color:navy; font-weight: bold;">info</span>')
.replace('warning', '<span style="color:orange; font-weight: bold;">warning</span>')
.replace('ERROR', '<span style="color:red; font-weight: bold;">ERROR</span>')
.replaceAll(' ', ' ')
.replace('\n\n','\n');
if (msg === '\n')
return;
messages.push(msg);
fastify.redis.set(`gatsby-${cmd}`, JSON.stringify({ code: -1, message: messages }))
fastify.log.info(msg);
});
proc.on('error', error => {
fastify.redis.set(`gatsby-${cmd}`, JSON.stringify({ code: 1, message: error.message }));
fastify.redis.set('isBuilding', 'false');
fastify.log.error(error.message);
});
proc.on('close', async code => {
fastify.redis.set(`gatsby-${cmd}`, JSON.stringify({ code, message: messages }));
fastify.redis.set('isBuilding', 'false');
fastify.log.info(`Gatsby exited (${code})`);
if (cmd === 'rebuild') {
gatsby('build');
} else if (cmd === 'build' && code === 0) {
const cp = spawn('/copy.sh', { shell: true })
cp.on('data', data => fastify.log.info(data.toString()));
}
});
}
/*
Es werden je Route und Methode der Funktion handleRequest() schlicht
das entsprechende Gatsby Kommando übergeben.
*/
fastify.post(
'/api/gatsby',
async (req, res) => res
.header('Content-Type', 'application/json; charset=utf-8')
.send((await handleRequest('build')))
);
fastify.delete(
'/api/gatsby',
async (req, res) => res
.header('Content-Type', 'application/json; charset=utf-8')
.send((await handleRequest('clean')))
);
fastify.post(
'/api/gatsby/rebuild',
async (req, res) => res
.header('Content-Type', 'application/json; charset=utf-8')
.send((await handleRequest('rebuild')))
);
/*
Beim WordPress Cache handelt es sich um eine Besonderheit vom
gatsby-source-wordpress Plugin. Es lässt sich einstellen die
Mediendateien in ein gesondertes Verzeichniss zu speichern
damit sie nicht unnötig gelöscht werden. Hierdurch soll es
möglich sein diese zu löschen, sollte es doch mal nötig sein.
*/
fastify.delete('/api/wordpress-cache', async (req, res) => {
try {
await fs.rmdir('/usr/lib/gatsby/.wordpress-cache', { recursive: true });
res.send({ result: 1 });
} catch(error) {
fastify.log.error(error.message);
res.send({ result: 0, message: error.message });
}
})
}
Die "privaten" Routen In den anderen Dateien fehlen die Routen mit den DELETE Methoden. Diese sollen auch nicht für Jedermann verfügbar sein. Diese habe ich in einer extra Datei geschrieben.
./api/routes/private.js const path = require('path');
const { spawn } = require('child_process');
const { promises: fs } = require('fs');
module.exports = async function(fastify) {
/*
Der Client muss authentifiziert sein.
*/
fastify.requireAuthentication();
/*
Cookies ...
*/
fastify.get('/api/cookies', async (req, res) => {
const { redis } = fastify;
const cookies = await redis.get('cookies');
res.send(cookies.split(';').map(cookie => cookie.split('=')));
});
/*
... speichern
*/
fastify.post('/api/cookies', async (req, res) => {
const { redis } = fastify;
const { cookies } = req.body;
const cookieString = cookies.map(cookie => cookie.join('=')).join(';');
fs.writeFile(path.join(__dirname, '../data/cookie'), cookieString);
const result = await redis.set('cookies', cookieString);
res.send({ result });
});
/*
Instagram Meta Cache löschen
*/
fastify.delete('/api/instagram/:user', async (req, res) => {
const { redis } = fastify;
const user = req.params.user;
const result = await redis.del(user);
res.header('Content-Type', 'application/json; charset=utf-8');
res.send({ result });
});
/*
Bild löschen
*/
fastify.delete('/api/static/:image', async (req, res) => {
const image = req.params.image;
const [filename] = image.split('?');
const file = path.join(__dirname, 'static', filename);
try {
await fs.rm(file);
} catch(error) {
if (error.code === 'ENOENT') {
fastify.log.warn(error.message);
res
.status(404)
.send({ result: `${filename} not found`});
} else {
fastify.log.error(error.message);
throw error;
}
}
});
/*
Die Gatsby Ausgabe senden (Zum Polling im Plugin)
*/
fastify.get('/api/status/:cmd', async (req, res) => {
const { cmd } = req.params;
const { redis } = fastify;
const status = await redis.get(cmd);
if (status) {
res.send(status);
return;
}
res.status(204).send();
});
}
Letzte Änderungen Abschließend müssen noch in zwei Dateien aus kommentierter Code... "dekommentiert" werden.
./api/routes/index.js module.exports = {
access: require('./access-token'),
gatsby: require('./gatsby'),
instagram: require('./instagram'),
private: require('./private'),
};
./api/app.js /* ... */
fastify.register(routes.access);
fastify.register(routes.gatsby);
fastify.register(routes.instagram, { ...external, expire });
fastify.register(routes.private);
/* ... */
Der ganze Aufwand nur um Gatsby "malen" zulassen Blog - xmalanderssein.de
Ich bin Anne und dies hier ist mein persönlicher Blog. Im Fluss des Lebens zeigt sich hier ein Mensch als x-mal anders fühlend und fragend.
Weitere Blog Posts aus dieser Serie
Gatsby Webhook Service
Gatsby Webhook Service (2) - Das große Containern
Gatsby Webhook Service (3) - JWT Authentifizierung
Gatsby Webhook Service (4) - Instagram
Gatsby Webhook Service (5) - Gatsby