Wiki.js

Unified Deploy of Frontend and Backend

Unified deloy of Frontend and Backend from separate repos. Sequential build, parallel deploy

When some plugins are installed Jenkins can raise error for 'pipeline' function name. It should be replaced in that case by any other.
This code can be used for separate pipelines. Just add scenario parameter with value 'frontend' or 'backend'.

node {
  def frontend_repo = "$env.front_repo";
  def backend_repo = "$env.backend_repo";
  def configs = "$env.deploy_repo";
  def creds = "github-ssh";
  def branch = (env.gitlabMergeRequestLastCommit) ? env.gitlabMergeRequestLastCommit : "${env.BRANCH}";
  def project = "$env.project";
  def project_hub = "$env.docker_hub";
  def image_tag = (env.environment == 'staging') ? "latest" : "prod";
  def envs = [];

    // get code
    stage ("Get app code") {
        if(pipeline('frontend')) {
            dir("build/frontend") {
                checkout scm: [$class: 'GitSCM',
                    userRemoteConfigs: [[url: "${frontend_repo}", credentialsId: "github-ssh"]],
                    branches: [[name: "${env.frontend_BRANCH}"]]], changelog: false, poll: false
            }
        }
        if(pipeline('backend')) {    
            dir("build/backend") {
                checkout scm: [$class: 'GitSCM',
                    userRemoteConfigs: [[url: "${backend_repo}", credentialsId: "github-ssh"]],
                    branches: [[name: "${env.backend_BRANCH}"]]], changelog: false, poll: false
            }
        }    
    }

    // get configs
    stage("Get configs") {
       dir("deploy") {
        checkout scm: [$class: 'GitSCM',
                 userRemoteConfigs: [[url: "${configs}", credentialsId: "github-ssh"]],
                 branches: [[name: "master"]]], changelog: false, poll: false
       }
       
       dir('build') {
            //Get .env file
            configFileProvider(
                  [configFile(fileId: "${env.environment}-docker-env", variable: 'envs')]) {
                   sh 'cp ${envs} ./.env';
            }
            
            for (String i : readFile('.env').split("\r?\n")) {
                if(i != "") {
                    envs.push(i);
                }
            }    
        }
    }

    // build frontend
    if(pipeline('frontend')) {
        stage ("Build Frontend Image") {
            dir ("build/frontend") {
                sh "cp ../../deploy/configs/nginx_front.conf ."
                sh "cp ../../deploy/dockerfiles/Frontend.Dockerfile ."
                def build_args = (env.environment == 'staging') ? "--build-arg dev=true" : "";
                def customImage = docker.build ("${project_hub}/${project}/frontend:${image_tag}", "${build_args} -f Frontend.Dockerfile .");
                sh "docker push ${project_hub}/${project}/frontend:${image_tag}" ;
            }
        }
        
        stage ("Build Uppy Image") {
            if(env.DEPLOY_UPPY == "yes") {
                dir ("build/frontend") {
                    sh "cp ../../deploy/dockerfiles/FrontUppy.Dockerfile ."
                    def customImage = docker.build ("${project_hub}/${project}/frontuppy:${image_tag}", "-f FrontUppy.Dockerfile .");
                    sh "docker push ${project_hub}/${project}/frontuppy:${image_tag}" ;
                }
                
                shCommand("sudo docker pull ${project_hub}/${project}/frontuppy:${image_tag}");
            }    
        }
        shCommand("sudo docker pull ${project_hub}/${project}/frontend:${image_tag}");
    }  // end of frontend build  

    //build backend
    if(pipeline('backend')) {
        // get backend configs
        dir("build/backend") {
            sh "cp ../../deploy/dockerfiles/Backend.Dockerfile ./Backend.Dockerfile";
            sh "cp ../../deploy/configs/default_nginx.conf ./config/default_nginx.conf";
            sh "cp ../../deploy/dockerfiles/Proxy.Dockerfile ./Proxy.Dockerfile"
            sh "cp ../../deploy/dockerfiles/Test.Dockerfile ./Test.Dockerfile";
            sh "cp ../../deploy/configs/database.yml ./config/database.yml";
            sh "cp ../../deploy/configs/entrypoint*.sh ./"
            sh "cp ../../deploy/docker-compose.docs.yml ./docker-compose.docs.yml"
            sh "cp ../../deploy/dockerfiles/Static.Dockerfile ./Static.Dockerfile"  
        }

        // generate docs
        if(env.BUILD_DOCS == 'yes') {
            stage ("Generate Docs"){
                dir ("build/backend") {
                        def customImage = docker.build("backend:docs", "-f Test.Dockerfile .");
                        def current_dir  = pwd();
                        configFileProvider(
                            [configFile(fileId: 'test-docker-env', variable: 'envs')]) {
                            sh 'cp ${envs} ./.env';
                            sh "docker-compose -f docker-compose.docs.yml up -d";
                            def test_container = docker.image('backend:docs');
                            test_container.inside("--env-file=${current_dir}/.env --network=backend_docs") {
                                    sh "bundle exec rails db:drop db:create RAILS_ENV=test";
                                    sh "bundle exec rake docs:generate SIMPLECOV=false";
                            }
                            sh "docker-compose -f docker-compose.docs.yml stop";
                    }
                }
            }
        }

        // build backend image
        stage ("Build App") {              
            dir ("build/backend") {
                withCredentials([file(credentialsId: 'cloudfront-key', variable: 'FILE')]) {
                        sh "rm -f ./.cfk";
                        sh 'cp $FILE ./.cfk';
                        sh "chmod 644 .cfk";
                }
                configFileProvider(
                      [configFile(fileId: "${env.environment}-docker-env", variable: 'envs')]) {
                      sh 'cp ${envs} ./.env';
                      def customImage = docker.build ("${project_hub}/${project}/backend:${image_tag}", "-f Backend.Dockerfile .");
                      sh "docker push ${project_hub}/${project}/backend:${image_tag}";
                      def staticImage = docker.build ("${project_hub}/${project}/backend_static:${image_tag}", "-f Static.Dockerfile .");
                      sh "docker push ${project_hub}/${project}/backend_static:${image_tag}";
                }
            }
        }

        shCommand("sudo docker pull ${project_hub}/${project}/backend:${image_tag}");
        shCommand("sudo docker pull ${project_hub}/${project}/backend_static:${image_tag}");

    } // end of build backend     

    stage("Deploy app") {
        def jenvs = envs.join(" --env-add ");
        def frontend_services = [];
        def backend_services = [];

        if(pipeline('frontend')) {
            frontend_services = [['frontend', 'frontend']];
            if(env.DEPLOY_UPPY == "yes") {
                frontend_services.add(['frontuppy','frontuppy']);
            }

        }
        if(pipeline('backend')) {
            backend_services = [
                    ["backend", "backend"], 
                    ["sidekiq", "backend"],
                    ["rpc", "backend"],
                    ["ws", "backend"],
                    ["static", "backend_static"]
            ]    
        }

        def services = frontend_services + backend_services;
        parallel rolling_update(services, jenvs, image_tag)
    }

    dir("build") {
            
        GIT_COMMIT = sh (
            script: 'git rev-parse --short HEAD',
            returnStdout: true
        ).trim()

        wrap([$class: 'BuildUser']) {
           sh """curl https://api.rollbar.com/api/1/deploy/ \
          -F access_token=${env.ROLLBAR_KEY} \
          -F environment=${env.environment} \
          -F revision=${GIT_COMMIT} \
          -F local_username=${BUILD_USER}"""      
            sh 'echo "${BUILD_USER}"'
          }
    }

}

def rolling_update(services, jenvs, image_tag) {

  def buildParallelMap = [:]
    for (name in services) {
      buildParallelMap.put(name, update(name, jenvs, image_tag));
    }
  
  return buildParallelMap
}

def update(name, jenvs, image_tag) {
    return {
       shCommand("sudo docker service update --env-add ${jenvs} --force --image ${env.docker_hub}/${env.project}/${name[1]}:${image_tag} ${env.project}_${name[0]}");
    }
}

def pipeline(pipe) {
    if(env.scenario == null) {
        return true;
    }
    if(env.scenario == pipe) {
        return true;
    }
    return false;
}


def shCommand(command) {
    sshagent (credentials: ['ssh-key']) {
        def ip = (env.environment == 'staging') ? env.dev_ip : env.prod_ip
        sh "ssh -o StrictHostKeyChecking=no -l $env.user $ip $command"
    }
}