UNPKG

exoframe-recipe-hobbit

Version:

A simple way to setup HOBBIT platform using [Exoframe](https://github.com/exoframejs/exoframe).

559 lines (506 loc) 18 kB
const path = require('path'); const tar = require('tar-fs'); // images const platformControllerImage = 'hobbitproject/hobbit-platform-controller'; const guiImage = 'hobbitproject/hobbit-gui'; const keycloakImage = 'git.project-hobbit.eu:4567/gitadmin/hobbit-keycloak:1.0.0'; const analysisImage = 'hobbitproject/hobbit-analysis-component'; const rabbitImage = 'rabbitmq:management'; const redisImage = 'redis:4.0.7'; const vosImage = 'openlink/virtuoso_opensource:v07.20.3217'; const storageServiceImage = 'hobbitproject/hobbit-storage-service'; // volumes const keycloakVolumeName = 'hobbit-keycloak-config'; const virtuosoVolumeName = 'hobbit-virtuoso-config'; const redisVolumeName = 'hobbit-redis-config'; // networks const hobbitNetwork = 'hobbit'; const hobbitCoreNetwork = 'hobbit-core'; const hobbitServiceNetwork = 'hobbit-services'; const sleep = t => new Promise(r => setTimeout(r, t)); exports.getQuestions = () => [ { type: 'input', name: 'projectName', message: 'HOBBIT project name:', default: 'hobbit', }, { type: 'confirm', name: 'isTesting', message: 'Running in testing mode?', }, { type: 'input', name: 'gitlabUser', message: 'You will need to provide your info from HOBBIT Gitlab (http://git.project-hobbit.eu/)\nGitlab username:', }, { type: 'input', name: 'gitlabEmail', message: 'Gitlab email:', }, { type: 'input', name: 'gitlabToken', message: 'Gitlab token:', }, { type: 'input', name: 'guiHost', message: 'Domain for GUI:', }, { type: 'input', name: 'keycloakHost', message: 'Domain for Keycloak:', }, { type: 'input', name: 'virtuosoHost', message: 'Domain for Virtuoso:', }, { type: 'input', name: 'rabbitmqHost', message: 'Domain for RabbitMQ:', }, ]; const prepareKeycloakConfig = async ({util, docker, answers, username, serverConfig, volume}) => { // build image that copies our keycloak config to that volume const logLine = data => util.logger.debug('BUILD-LOG:', data); const tag = 'hobbit-keycloak-volume-filler'; const tarStream = tar.pack(path.join(__dirname, 'keycloak')); const {log: buildLog, image: keycloakFillImage} = await docker.buildFromParams({tarStream, tag, logLine}); util.logger.debug('Built volume fill image:', keycloakFillImage, buildLog); // generate deployment name const deploymentName = util.nameFromImage(keycloakFillImage); // start that image with volume to copy data let fillerInspect = await docker.startFromParams({ image: keycloakFillImage, projectName: answers.projectName, username, deploymentName, hostname: deploymentName, restartPolicy: 'no', Mounts: [ { Type: 'volume', Source: volume.name, Target: '/cfg-volume', }, ], }); util.logger.debug('started filler:', fillerInspect); let isExited = false; if (serverConfig.swarm) { do { await sleep(3000); const tasks = await docker.daemon.listTasks({filters: `{"service": ["${fillerInspect.Spec.Name}"]}`}); const task = tasks.pop(); util.logger.debug('got task:', task); isExited = task.Status.State === 'complete'; if (isExited && task.Status.Message !== 'finished') { throw new Error(`Error creating config volume for keycloak: ${fillerInspect.Status.Message}`); } } while (!isExited); return; } do { // wait for 3s to give it time to copy config await sleep(3000); fillerInspect = await docker.daemon.getContainer(fillerInspect.Id).inspect(); isExited = fillerInspect.State.Status === 'exited'; if (isExited && fillerInspect.State.ExitCode !== 0) { throw new Error( `Error creating config volume for keycloak: ${fillerInspect.State.Error} (code: ${ fillerInspect.State.ExitCode })` ); } } while (!isExited); util.logger.debug('after sleep:', fillerInspect); }; const prepareVirtuosoConfig = async ({util, docker, answers, username, serverConfig, volume}) => { // build image that copies our virtuoso config to that volume const logLine = data => util.logger.debug('BUILD-LOG:', data); const tag = 'hobbit-virtuoso-volume-filler'; const tarStream = tar.pack(path.join(__dirname, 'virtuoso')); const {log: buildLog, image: virtuosoFillImage} = await docker.buildFromParams({tarStream, tag, logLine}); util.logger.debug('Built volume fill image:', virtuosoFillImage, buildLog); // generate deployment name const deploymentName = util.nameFromImage(virtuosoFillImage); // start that image with volume to copy data let fillerInspect = await docker.startFromParams({ image: virtuosoFillImage, projectName: answers.projectName, username, deploymentName, hostname: deploymentName, restartPolicy: 'no', Mounts: [ { Type: 'volume', Source: volume.name, Target: '/cfg-volume', }, ], }); util.logger.debug('started filler:', fillerInspect); let isExited = false; if (serverConfig.swarm) { do { await sleep(3000); const tasks = await docker.daemon.listTasks({filters: `{"service": ["${fillerInspect.Spec.Name}"]}`}); const task = tasks.pop(); util.logger.debug('got task:', task); isExited = task.Status.State === 'complete'; if (isExited && task.Status.Message !== 'finished') { throw new Error(`Error creating config volume for keycloak: ${fillerInspect.Status.Message}`); } } while (!isExited); return; } do { // wait for 3s to give it time to copy config await sleep(3000); fillerInspect = await docker.daemon.getContainer(fillerInspect.Id).inspect(); isExited = fillerInspect.State.Status === 'exited'; if (isExited && fillerInspect.State.ExitCode !== 0) { throw new Error( `Error creating config volume for keycloak: ${fillerInspect.State.Error} (code: ${ fillerInspect.State.ExitCode })` ); } } while (!isExited); util.logger.debug('after sleep:', fillerInspect); }; const prepareRedisConfig = async ({util, docker, answers, username, serverConfig, volume}) => { // build image that copies our keycloak config to that volume const logLine = data => util.logger.debug('BUILD-LOG:', data); const tag = 'hobbit-redis-volume-filler'; const tarStream = tar.pack(path.join(__dirname, 'redis')); const {log: buildLog, image: redisFillImage} = await docker.buildFromParams({tarStream, tag, logLine}); util.logger.debug('Built volume fill image:', redisFillImage, buildLog); // generate deployment name const deploymentName = util.nameFromImage(redisFillImage); // start that image with volume to copy data let fillerInspect = await docker.startFromParams({ image: redisFillImage, projectName: answers.projectName, username, deploymentName, hostname: deploymentName, restartPolicy: 'no', Mounts: [ { Type: 'volume', Source: volume.name, Target: '/cfg-volume', }, ], }); util.logger.debug('started filler:', fillerInspect); let isExited = false; if (serverConfig.swarm) { do { await sleep(3000); const tasks = await docker.daemon.listTasks({filters: `{"service": ["${fillerInspect.Spec.Name}"]}`}); const task = tasks.pop(); util.logger.debug('got task:', task); isExited = task.Status.State === 'complete'; if (isExited && task.Status.Message !== 'finished') { throw new Error(`Error creating config volume for keycloak: ${fillerInspect.Status.Message}`); } } while (!isExited); return; } do { // wait for 3s to give it time to copy config await sleep(3000); fillerInspect = await docker.daemon.getContainer(fillerInspect.Id).inspect(); isExited = fillerInspect.State.Status === 'exited'; if (isExited && fillerInspect.State.ExitCode !== 0) { throw new Error( `Error creating config volume for keycloak: ${fillerInspect.State.Error} (code: ${ fillerInspect.State.ExitCode })` ); } } while (!isExited); util.logger.debug('after sleep:', fillerInspect); }; const executeAfterServiceStarted = async ({service, command, serverConfig, util, docker}) => { // wait for service status let isRunning = false; if (serverConfig.swarm) { let task; do { await sleep(3000); const tasks = await docker.daemon.listTasks({filters: `{"service": ["${service.Spec.Name}"]}`}); task = tasks.pop(); util.logger.debug('got task:', task); isRunning = task.Status.State === 'running'; } while (!isRunning); // get container const containerId = task.Status.ContainerStatus.ContainerID; const container = docker.daemon.getContainer(containerId); // execute command const exec = await container.exec({Cmd: command}); const execResult = new Promise(async resolve => { const {output} = await exec.start(); const res = []; output.on('data', d => res.push(d)).on('end', () => { const out = Buffer.concat(res); resolve(out.toString('utf8', 8)); }); }); return execResult; } const container = docker.daemon.getContainer(service.Id); do { // wait for 3s to give it time to copy config await sleep(3000); const serviceInfo = await container.inspect(); isRunning = serviceInfo.State.Status === 'running'; } while (!isRunning); // execute command const exec = await container.exec({Cmd: command}); const execResult = new Promise(async resolve => { const {output} = await exec.start(); const res = []; output.on('data', d => res.push(d)).on('end', () => { const out = Buffer.concat(res); resolve(out.toString('utf8', 8)); }); }); return execResult; }; exports.runSetup = async ({answers, serverConfig, username, docker, util}) => { // init log const log = []; try { util.logger.debug('starting HOBBIT platform..'); // create networks util.logger.debug('creating networks..'); await docker.createNetwork(hobbitNetwork); await docker.createNetwork(hobbitCoreNetwork); await docker.createNetwork(hobbitServiceNetwork); // create new volume for keycloak config const keycloakVolume = await docker.daemon.createVolume({Name: keycloakVolumeName}); util.logger.debug(keycloakVolume); // fill volume with config await prepareKeycloakConfig({util, docker, serverConfig, answers, username, volume: keycloakVolume}); util.logger.debug('HOBBIT Keycloak config volume created'); // start keycloak const keycloakHost = `hobbit-keycloak-${answers.projectName}`; const keycloakService = await docker.startFromParams({ image: keycloakImage, projectName: answers.projectName, username, frontend: `Host:${answers.keycloakHost}`, restartPolicy: 'on-failure:2', hostname: keycloakHost, additionalLabels: { 'traefik.port': '8080', }, Mounts: [ { Type: 'volume', Source: keycloakVolume.name, Target: '/opt/jboss/keycloak/standalone/data/db', }, ], additionalNetworks: [hobbitNetwork], }); util.logger.debug('Keycloak started:', keycloakService); // create new volume for virtuoso config const virtuosoVolume = await docker.daemon.createVolume({Name: virtuosoVolumeName}); util.logger.debug(virtuosoVolume); // fill volume with config await prepareVirtuosoConfig({util, docker, serverConfig, answers, username, volume: virtuosoVolume}); util.logger.debug('HOBBIT Virtuoso config volume created'); // start virtuoso const virtuosoService = await docker.startFromParams({ image: vosImage, projectName: answers.projectName, username, frontend: `Host:${answers.virtuosoHost}`, restartPolicy: 'on-failure:2', additionalLabels: { 'traefik.port': '8890', }, Mounts: [ { Type: 'volume', Source: virtuosoVolume.name, Target: '/opt/virtuoso-opensource/database', }, ], additionalNetworks: [hobbitCoreNetwork], }); util.logger.debug('Virtuoso started:', virtuosoService); // execute virtuoso bootstrapping script const initResult = await executeAfterServiceStarted({ service: virtuosoService, command: ['sh', 'storage-init.sh'], docker, serverConfig, util, }); util.logger.debug('Virtuoso init executed:', initResult); // create new volume for redis config const redisVolume = await docker.daemon.createVolume({Name: redisVolumeName}); util.logger.debug(redisVolume); // fill volume with config await prepareRedisConfig({util, docker, serverConfig, answers, username, volume: redisVolume}); util.logger.debug('HOBBIT Redis config volume created'); // start redis const redisHostname = `hobbit-redis-${answers.projectName}`; const redisService = await docker.startFromParams({ image: redisImage, projectName: answers.projectName, username, restartPolicy: 'on-failure:2', hostname: redisHostname, Mounts: [ { Type: 'volume', Source: redisVolume.name, Target: '/data', }, ], additionalNetworks: [hobbitCoreNetwork], }); util.logger.debug('Redis started:', redisService); // start rabbit const rabbitHostname = `hobbit-rabbit-${answers.projectName}`; const rabbitService = await docker.startFromParams({ image: rabbitImage, projectName: answers.projectName, username, restartPolicy: 'on-failure:2', frontend: `Host:${answers.rabbitmqHost}`, hostname: rabbitHostname, additionalLabels: { 'traefik.port': '15672', }, additionalNetworks: [hobbitNetwork, hobbitCoreNetwork], }); util.logger.debug('Rabbit started:', rabbitService); // start platform controller const platformControllerService = await docker.startFromParams({ image: platformControllerImage, projectName: answers.projectName, username, restartPolicy: 'on-failure:2', Env: [ `HOBBIT_RABBIT_HOST=${rabbitHostname}`, `HOBBIT_REDIS_HOST=${redisHostname}`, `DEPLOY_ENV=${answers.isTesting ? 'testing' : 'production'}`, `GITLAB_USER=${answers.gitlabUser}`, `GITLAB_EMAIL=${answers.gitlabEmail}`, `GITLAB_TOKEN=${answers.gitlabToken}`, ], Mounts: [ { Source: '/var/run/docker.sock', Target: '/var/run/docker.sock', Type: 'bind', }, ], additionalNetworks: [hobbitCoreNetwork], }); util.logger.debug('Platform controller started:', platformControllerService); // start gui const guiService = await docker.startFromParams({ image: guiImage, projectName: answers.projectName, username, restartPolicy: 'on-failure:2', Env: [ `KEYCLOAK_AUTH_URL=http://${answers.keycloakHost}/auth`, `KEYCLOAK_DIRECT_URL=http://${keycloakHost}:8080/auth`, `HOBBIT_RABBIT_HOST=${rabbitHostname}`, `CHECK_REALM_URL=false`, ], frontend: `Host:${answers.guiHost}`, additionalLabels: { 'traefik.port': '8080', }, additionalNetworks: [hobbitNetwork, hobbitCoreNetwork], }); util.logger.debug('GUI started:', guiService); // start analysis const analysisService = await docker.startFromParams({ image: analysisImage, projectName: answers.projectName, username, restartPolicy: 'on-failure:2', Env: [`HOBBIT_RABBIT_HOST=${rabbitHostname}`], additionalNetworks: [hobbitCoreNetwork], }); util.logger.debug('Analysis service started:', analysisService); // start storage const storageService = await docker.startFromParams({ image: storageServiceImage, projectName: answers.projectName, username, restartPolicy: 'on-failure:2', Env: [ `HOBBIT_RABBIT_HOST=${rabbitHostname}`, `SPARQL_ENDPOINT_URL=http://${answers.virtuosoHost}/sparql`, `SPARQL_ENDPOINT_USERNAME=HobbitPlatform`, `SPARQL_ENDPOINT_PASSWORD=Password`, ], additionalNetworks: [hobbitCoreNetwork], }); util.logger.debug('Storage service started:', storageService); log.push({message: 'Done setting up HOBBIT platform!\n\n', level: 'info'}); log.push({ message: `You still need to navigate to http://${answers.keycloakHost} and configure realms.`, level: 'info', }); log.push({ message: `Once you've opened the page in the browser, click on Administration Console.`, level: 'info', }); log.push({ message: `Login into Keycloak using the username 'admin' and the password 'H16obbit'.`, level: 'info', }); log.push({ message: `Make sure that the realm Hobbit is selected in the left upper corner below the Keycloak logo.`, level: 'info', }); log.push({ message: `Click on Clients in the left menu and click on the Hobbit-GUI client.`, level: 'info', }); log.push({ message: `Add the address of the GUI to the list "Valid Redirect URIs" (with a trailing star, e.g., http://${ answers.guiHost }/*).`, level: 'info', }); log.push({ message: `Add the address of the GUI to the list "Web Origins" (without trailing slash e.g., http://${ answers.guiHost }).`, level: 'info', }); log.push({ message: `Click on "Save" at the bottom of the page.`, level: 'info', }); log.push({ message: `You are ready to go!`, level: 'info', }); } catch (e) { util.logger.error('error:', e); log.push({message: e.toString(), data: e, level: 'error'}); } return log; };