Set up an automated deployment service

This article describes how to build a front-end automated deployment service. For example, see auto_deploy

The deployment service has a simple HTML front end and a KOA back end that starts with PM2. The service is implemented by executing the command child_process, killing the process with taskkill, and deploying the target project using HTTP-server.

Install the global PM2 and HTTP-Server in advance


Writing deployment services

  1. Deploy the directory structure of service items

Static: Stores the packaged files of the target project

Statichtml: A front-end interface for hosting the deployment service

App.js: the back end to deploy the service

  1. Deploy service dependencies
"devDependencies": {
    "koa": "^ 2.13.0"."koa-mount": "^ 4.0.0"."taskkill": "^ 3.1.0"
}
Copy the code
  1. Deploy the back end of the service

app.js

const fs = require('fs');
const koa = require('koa');
const mount = require('koa-mount');

const app = new koa();
const reloadKoa = new koa();

app.use(
    mount('/favivon.ico'.function(ctx){
        ctx.status = 200;
    })
)

app.use(
    mount('/reload', reloadKoa)
)

reloadKoa.use(
    async function(ctx, next){
        // Deploy functionality
    }
)

app.use( 
    mount('/'.function(ctx) {
        ctx.body = fs.readFileSync(__dirname + '/statichtml/index.html'.'utf-8');
    })
)

app.listen(8003);

Copy the code

Listen on port 8003 and return /statichtml/index.html when accessing the ‘/’ root directory.

  1. Deploy the front end of the service

/statichtml/index.html

<div class="form">
    <label class="label">Branch name:</label>
    <input id="input" class="input"type="text" value="master" />
    <br/>
    <label class="label">Connection environment:</label>
    <select id="select" class="select">
        <option value ="http://127.0.0.1:3001/">The development environment</option>
    </select>
    <br/>
    <button class="btn" id="btn" onclick="handleClick()">The deployment of</button>
</div>
<div id="output"></div>
Copy the code
function handleClick(){
    var $btn = document.getElementById("btn");
    var $output = document.getElementById("output");

    var $input = document.getElementById("input");
    var $select = document.getElementById("select");
    var branch = encodeURI($input.value);
    var proxy = encodeURI($select.value);
    
    $btn.disabled = true;
    $btn.classList.add("disabled");
    $output.innerText = "Deploying. Please be patient.";

    fetch(`http://${location.host}/reload? branch=` + branch + `&proxy=` + proxy)
    .then((res) = >{
        return res.text()
    }).then((text) = >{
        $output.innerText = text;
        $btn.disabled = false;
        $btn.classList.remove("disabled"); })}Copy the code

In the simplest case, a front-end page requires only a deployment button to initiate a deployment request.

Some form interactions can be added to extend the deployment service capabilities, such as branch selection, back-end IP for the target project connection, and so on.

Click the deploy button, request the ‘/reload’ interface of the current service port with two parameters of branch and proxy IP, display the text returned by the interface, and disable the button.

  1. Before writing deployment functionality, declare constants, variables, and methods
// These parameters can be configured from the front end
const protocol = "https"; Git clone protocol
const projectUrl = "github.com/Jiuto/ding_yapi.git"; // git clone project address
const projectDir = __dirname + '/ding_yapi'; // Target project name
const staticDir = __dirname + '/static/dist'; // The path to the package file
const buildStaticDir = __dirname + '/ding_yapi/client/dist'; // The packaged file path of the target project

const git_username='username'; // git username
const git_password='password'; / / git password

// The target project starts the process
var subprocess = null;

// Delete files
function deleteFolder(path) {
    var files = [];
    if (fs.existsSync(path)) { // The directory exists
        if (fs.statSync(path).isDirectory()) { / / folder
            files = fs.readdirSync(path); // Returns an array of all file names
            files.forEach(function (file, index) {
                var curPath = path + "/" + file;
                if (fs.statSync(curPath).isDirectory()) { / / folder
                    deleteFolder(curPath);
                } else { / / filefs.unlinkSync(curPath); }}); fs.rmdirSync(path); }else { / / file
            fs.unlinkSync(path); // Delete files}}}// Copy the file
function copyFolder(from, to) {
    var files = [];
    if (fs.existsSync(to)) { // The directory exists
        files = fs.readdirSync(from);
        files.forEach(function (file, index) {
            var targetPath = from + "/" + file;
            var toPath = to + '/' + file;
            if (fs.statSync(targetPath).isDirectory()) { / / folder
                copyFolder(targetPath, toPath);
            } else { / / file
                fs.copyFileSync(targetPath, toPath); // Copy the file}}); }else { // Directory does not exist
        fs.mkdirSync(to); // Create a file
        copyFolder(from, to); }}Copy the code
  1. Deployment capabilities
reloadKoa.use(
    async function(ctx, next){
        // Parse the input parameter
        const query = ctx.query;
        const branch = decodeURI(query.branch);
        const proxy = decodeURI(query.proxy);

        // Declare two variables to store the results of the pull code and update the environment
        var rtn1 = {},
            rtn2 = {};

        // Whether to clone the project again
        if(branch){
            // Delete the last pulled item
            deleteFolder(projectDir);
            // Build the clone command
            let cmdStr = `git clone -b ` + branch + ` ` + protocol + ` : / / ` + git_username + ` : ` + git_password + ` @ ` + projectUrl;
            // Call the method that executes the clone command
            rtn1 = await reloadCode(cmdStr);  
        }

        // Whether to switch proxies
        if(proxy){
            // Build the start project command to deploy the target project on port 8002
            let cmdStr = `http-server .\\static\\dist -p 8002 --proxy ` + proxy;
            // Calls the method that executes the command to start the project
            rtn2 = await changeProxy(cmdStr);
        }

        // Integrate the result
        var rtn = {
            code: rtn1.code === 200 || rtn2.code === 200 ? 200 : 500.msg: rtn1.msg 
                        ? rtn2.msg
                            ? rtn1.msg + ' and ' + rtn2.msg
                            : rtn1.msg
                        : rtn2.msg
                            ? rtn2.msg
                            : 'parameter can nott be empty'
        }

        / / responsectx.status = rtn.code; ctx.body = rtn.msg; })Copy the code

The method of executing the clone command

function reloadCode(cmdStr){
    return new Promise(function(resolve, reject) {
        try {
            child_process.execSync(cmdStr); // Clone project
            process.chdir(projectDir) // Go to the subdirectory
            child_process.execSync(`npm install`); // Install dependencies
            child_process.execSync(`npm run build`); // Package the project
            process.chdir(__dirname) Return to the root directory
            deleteFolder(staticDir) // Delete the original static file
            copyFolder(buildStaticDir, staticDir) // Copy the file
            resolve({
                code: 200.msg: 'load code successful'})}catch (e) {
            reject({
                code: 500.msg: 'load failed, error message:\n' + e
            })
        }
    })
}
Copy the code

Method of executing the command to start the project

function changeProxy(cmdStr){
    var result2 = {
        code: 200.msg: ' '
    };
    return new Promise(function(resolve, reject) {
        // If the project is started, the process is killed
        if(subprocess) {
            (async() = > {awaittaskkill(subprocess.pid); }) (); } subprocess = child_process.exec(cmdStr);/ / start the HTTP server
        subprocess.on('error'.(err) = > {
            result2.code = 500;
            result2.msg += 'http-server failed, error message:\n' + err;
            reject(result2)
        });
        result2.code = 200;
        result2.msg += 'set proxy successful';
        resolve(result2)
    })
}
Copy the code

Starting the Deployment Service

  1. Switch to the auto_deploy directory and runpm2 start app.jsStarting the Deployment Service

Then, you can see the front end page of the deployment service on local port 8003

Automated deployment

Being deployed

dist

httpserver