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"
}
}