|
| 1 | +# Templates guide |
| 2 | + |
| 3 | +Exoframe allows extending the types of deployments it supports using third party plugins. |
| 4 | +This guide aims to explain basics you need to know to create your own templates. |
| 5 | +If you are looking for template usage - please see [Basics](Basics.md) part of the docs. |
| 6 | + |
| 7 | +## Basics |
| 8 | + |
| 9 | +Exoframe uses [yarn](https://yarnpkg.com/) to install and remove third-party templates. |
| 10 | +The templates then are added to Exoframe server using Node.js `require()` method. |
| 11 | +So, make sure that your template's `package.json` has correct `main` attribute. |
| 12 | + |
| 13 | +Your template main script needs to export the following variables and methods: |
| 14 | + |
| 15 | +```js |
| 16 | +// template name |
| 17 | +// can be used by user to specify the template in config |
| 18 | +exports.name = 'mytemplate'; |
| 19 | + |
| 20 | +// function to check if the template fits the project |
| 21 | +// will be executed unless template is specified by user explicitly |
| 22 | +exports.checkTemplate = async props => {}; |
| 23 | + |
| 24 | +// function to execute current template |
| 25 | +// handle building and starting the containers |
| 26 | +exports.executeTemplate = async props => {}; |
| 27 | +``` |
| 28 | + |
| 29 | +## Template props |
| 30 | + |
| 31 | +Both `checkTemplate` and `executeTemplate` get the same properties object upon execution. |
| 32 | +This object contains all data and methods required to build and execute new docker containers. |
| 33 | +Here's a snippet from the Exoframe server code that shows the props object being assembled: |
| 34 | + |
| 35 | +```js |
| 36 | +// generate template props |
| 37 | +const templateProps = { |
| 38 | + // user project config |
| 39 | + config, |
| 40 | + // current user username |
| 41 | + username, |
| 42 | + // response stream, used to send log back to user |
| 43 | + resultStream, |
| 44 | + // temp dir that contains the project |
| 45 | + tempDockerDir, |
| 46 | + // docker-related things |
| 47 | + docker: { |
| 48 | + // docker daemon, instance of dockerode |
| 49 | + daemon: docker, |
| 50 | + // exoframe build function |
| 51 | + // has following signature: async ({username, resultStream}) => {} |
| 52 | + // executes `docker build` in project temp dir |
| 53 | + // returns following object: {log, image} |
| 54 | + build, |
| 55 | + // exoframe start function |
| 56 | + // has the following signature: async ({image, username, resultStream}) => {} |
| 57 | + // executes `docker start` with given image while setting all required labels, env vars, etc |
| 58 | + // returns inspect info from started container |
| 59 | + start, |
| 60 | + }, |
| 61 | + // exoframe utilities & logger |
| 62 | + // see code here: https://github.com/exoframejs/exoframe-server/blob/master/src/util/index.js |
| 63 | + util: Object.assign({}, util, { |
| 64 | + logger, |
| 65 | + }), |
| 66 | +}; |
| 67 | +``` |
| 68 | + |
| 69 | +## Checking if the projects fit your template |
| 70 | + |
| 71 | +First thing Exoframe server will do is execute your `checkTemplate` function to see if your template fits the current project. |
| 72 | +Typically you'd want to read the list of files in temporary project folder and see if it contains files related to your template type. |
| 73 | +Here's an example of the core static HTML template, it check whether folder contains `index.html` file: |
| 74 | + |
| 75 | +```js |
| 76 | +// function to check if the template fits this recipe |
| 77 | +exports.checkTemplate = async ({tempDockerDir}) => { |
| 78 | + // if project already has dockerfile - just exit |
| 79 | + try { |
| 80 | + const filesList = fs.readdirSync(tempDockerDir); |
| 81 | + if (filesList.includes('index.html')) { |
| 82 | + return true; |
| 83 | + } |
| 84 | + return false; |
| 85 | + } catch (e) { |
| 86 | + return false; |
| 87 | + } |
| 88 | +}; |
| 89 | +``` |
| 90 | + |
| 91 | +## Executing the template |
| 92 | + |
| 93 | +Once you've determined that the project is indeed supported by your template, you will need to execute it. |
| 94 | +It is up to you to build _and_ start a docker image. |
| 95 | +Here's an example for the same static HTML core template that deploys current project using Nginx Dockerfile: |
| 96 | + |
| 97 | +```js |
| 98 | +const nginxDockerfile = `FROM nginx:latest |
| 99 | +COPY . /usr/share/nginx/html |
| 100 | +RUN chmod -R 755 /usr/share/nginx/html |
| 101 | +`; |
| 102 | + |
| 103 | +// function to execute current template |
| 104 | +exports.executeTemplate = async ({username, tempDockerDir, resultStream, util, docker}) => { |
| 105 | + try { |
| 106 | + // generate new dockerfile |
| 107 | + const dockerfile = nginxDockerfile; |
| 108 | + // write the file to project temp dir |
| 109 | + const dfPath = path.join(tempDockerDir, 'Dockerfile'); |
| 110 | + fs.writeFileSync(dfPath, dockerfile, 'utf-8'); |
| 111 | + // send log to user |
| 112 | + util.writeStatus(resultStream, {message: 'Deploying Static HTML project..', level: 'info'}); |
| 113 | + |
| 114 | + // build docker image |
| 115 | + const buildRes = await docker.build({username, resultStream}); |
| 116 | + // send results to user |
| 117 | + util.logger.debug('Build result:', buildRes); |
| 118 | + |
| 119 | + // check for errors in build log |
| 120 | + if ( |
| 121 | + buildRes.log |
| 122 | + .map(it => it.toLowerCase()) |
| 123 | + .some(it => it.includes('error') || (it.includes('failed') && !it.includes('optional'))) |
| 124 | + ) { |
| 125 | + // if there are - add to server log |
| 126 | + util.logger.debug('Build log conains error!'); |
| 127 | + // and report to user |
| 128 | + util.writeStatus(resultStream, {message: 'Build log contains errors!', level: 'error'}); |
| 129 | + // and end the result stream immediately |
| 130 | + resultStream.end(''); |
| 131 | + return; |
| 132 | + } |
| 133 | + |
| 134 | + // start image |
| 135 | + const containerInfo = await docker.start(Object.assign({}, buildRes, {username, resultStream})); |
| 136 | + // log results in server logs |
| 137 | + util.logger.debug(containerInfo.Name); |
| 138 | + |
| 139 | + // clean temp folder |
| 140 | + await util.cleanTemp(); |
| 141 | + |
| 142 | + // get container info |
| 143 | + const containerData = docker.daemon.getContainer(containerInfo.Id); |
| 144 | + const container = await containerData.inspect(); |
| 145 | + // return new deployments to user |
| 146 | + util.writeStatus(resultStream, {message: 'Deployment success!', deployments: [container], level: 'info'}); |
| 147 | + // end result stream |
| 148 | + resultStream.end(''); |
| 149 | + } catch (e) { |
| 150 | + // if there was an error - log it in server log |
| 151 | + util.logger.debug('build failed!', e); |
| 152 | + // return it to user |
| 153 | + util.writeStatus(resultStream, {message: e.error, error: e.error, log: e.log, level: 'error'}); |
| 154 | + // end result stream |
| 155 | + resultStream.end(''); |
| 156 | + } |
| 157 | +}; |
| 158 | +``` |
| 159 | + |
| 160 | +## Examples |
| 161 | + |
| 162 | +* [Core templates](https://github.com/exoframejs/exoframe-server/tree/master/src/docker/templates) (incl. node, nginx, dockerfile and docker-compose) |
| 163 | +* [Maven template](https://github.com/exoframejs/exoframe-template-maven) |
| 164 | +* [Java template](https://github.com/exoframejs/exoframe-template-java) |
| 165 | +* [Tomcat template](https://github.com/exoframejs/exoframe-template-tomcat) |
0 commit comments