const Docker = require('dockerode'); const docker = new Docker({ 'socketPath': '/home/docker.sock' }); const fs = require('fs'); const config = require('qmi-cloud-common/config'); const GIT_SCENARIOS = process.env.GIT_SCENARIOS; const GIT_TAG = process.env.GIT_TAG || "master"; const DOCKERIMAGE = process.env.DOCKERIMAGE_TERRAFORM || "qlikgear/terraform:1.0.1"; const SSHPATH = process.env.SSHPATH; function hook_stdout(callback) { var old_write = process.stdout.write process.stdout.write = (function(write) { return function(string, encoding, fd) { write.apply(process.stdout, arguments) callback(string, encoding, fd) } })(process.stdout.write) return function() { process.stdout.write = old_write } } function _buildVarsExec( exec, provision, scenario ) { let prefix = provision.scenario.toUpperCase(); prefix = prefix.replace(/AZQMI/g, 'QMI'); exec.push('-var'); exec.push(`prefix=${prefix}`); if ( provision.deployOpts && provision.deployOpts.subsId ) { exec.push('-var'); exec.push(`subscription_id=${provision.deployOpts.subsId}`); } //Deprecated else if ( scenario && scenario.subscription && scenario.subscription.subsId ) { exec.push('-var'); exec.push(`subscription_id=${scenario.subscription.subsId}`); } if ( provision.deployOpts ) { if ( provision.deployOpts.location ) { exec.push('-var'); exec.push(`location=${provision.deployOpts.location}`); } if ( provision.deployOpts.vnetExists ) { exec.push('-var'); exec.push(`subnet_id=${provision.deployOpts.subnetId}`); if ( provision.isExternalAccess ) { exec.push('-var'); exec.push(`app_gw_subnet=${provision.deployOpts.appGwSubnetId}`); } } } //Deprecated else if ( scenario && scenario.subscription && scenario.subscription.vnetExists ) { exec.push('-var'); exec.push(`subnet_id=${scenario.subscription.subnetId}`); if ( scenario.isExternal || provision.isExternalAccess) { exec.push('-var'); exec.push(`app_gw_subnet=${scenario.subscription.appGwSubnetId}`); } } exec.push('-var'); exec.push(`provision_id=${provision._id}`); exec.push('-var'); exec.push(`user_id=${provision.user.displayName}`); if ( provision.scenario.indexOf('azqmi-qdi') !== -1 && provision.version && provision.version >= config.PROVISION_VERSION ) { exec.push('-var'); exec.push(`user_email=${provision.user.upn}`); } if ( provision.scenario.indexOf('azqmi-synapse') !== -1 || provision.scenario.indexOf('azqmi-qdi') !== -1 ) { exec.push('-var'); exec.push(`user_oid=${provision.user.oid}`); } if (!provision.vmImage) { //Old way if ( provision.vmType ) { exec.push('-var'); exec.push(`vm_type=${provision.vmType}`); } if ( provision.nodeCount) { exec.push('-var'); exec.push(`agent_count=${provision.nodeCount}`); } } else if ( provision.vmImage ) { //New way for ( let key in provision.vmImage ) { if ( !provision.vmImage[key].disabled ) { if ( provision.vmImage[key].nodeCount ) { exec.push('-var'); exec.push(`agent_count_${key}=${provision[key].nodeCount}`); } if ( provision.vmImage[key].vmType ) { exec.push('-var'); exec.push(`vm_type_${key}=${provision.vmImage[key].vmType}`); } else { exec.push('-var'); exec.push(`vm_type_${key}=ENABLED`); } if ( provision.vmImage[key].diskSizeGb ) { exec.push('-var'); exec.push(`disk_size_gb_${key}=${provision.vmImage[key].diskSizeGb}`); } if ( provision.vmImage[key].version && provision.vmImage[key].version.image) { exec.push('-var'); exec.push(`image_reference_${key}=${provision.vmImage[key].version.image}`); } if ( provision.vmImage[key].version && provision.vmImage[key].version.init_password) { exec.push('-var'); exec.push(`image_reference_${key}_init_password=${provision.vmImage[key].version.init_password}`); } } } } if ( provision.isExternalAccess ) { exec.push('-var'); exec.push(`is_external_access=${provision.isExternalAccess}`); } if ( provision.schedule ) { if ( provision.schedule.is24x7 === true ) { exec.push('-var'); exec.push(`is_24x7=true`); } else if ( provision.schedule.is24x7 === false ) { exec.push('-var'); exec.push(`is_24x7=false`); if ( provision.schedule.utcTagStartupTime && provision.schedule.isStartupTimeEnable ) { exec.push('-var'); exec.push(`startupTime=${provision.schedule.utcTagStartupTime}`); } if ( provision.schedule.utcTagShutdownTime ) { exec.push('-var'); exec.push(`shutdownTime=${provision.schedule.utcTagShutdownTime}`); } } } return exec; } const init = function( provision ) { const name = `qmi-tf-init-${provision._id}`; console.log(`Terraform# Init: will spin up container: ${name}`); var processStream = fs.createWriteStream(provision.logFile, {flags:'a'}); let gitBranch = GIT_TAG; if ( provision._scenarioDoc && provision._scenarioDoc.gitBranch && provision._scenarioDoc.gitBranch.trim() !== "") { gitBranch = provision._scenarioDoc.gitBranch; } let exec = ['terraform', 'init', '-no-color', `-from-module=${GIT_SCENARIOS}//${provision.scenario}?ref=${gitBranch}`]; console.log('Terraform# Init: exec: '+exec.join(" ")); var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE; console.log('Terraform# Init: version to use is : '+terraformImage); return docker.run(terraformImage, exec, processStream, { //"Env": ["VAR_ENV=whatever"], "name": name, "WorkingDir": "/app", "HostConfig": { "Binds": [ `${provision.path}:/app` ] } }).then(function(data) { var output = data[0]; var container = data[1]; console.log(`Terraform# Init: ${name} (${container.id}) has finished with code: ${output.StatusCode}`); return container.remove().then(function(){ console.log(`Terraform# Init: ${name} removed!`); return output.StatusCode; }); }).then(function(statusCode) { return {"provision": provision, "statusCode": statusCode}; }); }; const plan = function( provision, scenario ) { const name = `qmi-tf-plan-${provision._id}`; console.log(`Terraform# Plan: will spin up container: ${name}`); var processStream = fs.createWriteStream(provision.logFile, {flags:'a'}); //var processStream = process.stdout; var exec = ['terraform', 'plan', '-no-color', '-input=false', '-out=tfplan' ]; exec = _buildVarsExec(exec, provision, scenario); console.log('Terraform# Plan: exec: '+exec.join(" ")); var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE; console.log('Terraform# Plan: version to use is : '+terraformImage); return docker.run(terraformImage, exec, processStream, { //"Env": ["VAR_ENV=whatever"], "name": name, "WorkingDir": "/app", "HostConfig": { "Binds": [ `${provision.path}:/app`, `${SSHPATH}:/root/.ssh` ], "NetworkMode": "host" } }).then(function(data){ var container = data[1]; console.log(`Terraform# Plan: ${name} (${container.id}) has finished with code: ${data[0].StatusCode}`); return container.remove().then(function(){ console.log(`Terraform# Plan: ${name} removed!`); return data[0].StatusCode; }); }).then(function(statusCode) { return {"output": fs.readFileSync(provision.logFile), "statusCode": statusCode}; }) }; const apply = function( provision ) { const name = `qmi-tf-apply-${provision._id}`; console.log(`Terraform# Apply: will spin up container: ${name}`); var processStream = fs.createWriteStream(provision.logFile, {flags:'a'}); //var processStream = process.stdout; var exec = ['terraform', 'apply', 'tfplan', '-no-color']; console.log('Terraform# Apply: exec: '+exec.join(" ")); var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE; console.log('Terraform# Apply: version to use is : '+terraformImage); return docker.run(terraformImage, exec, processStream, { //"Env": ["VAR_ENV=whatever"], "name": name, "WorkingDir": "/app", "HostConfig": { "Binds": [ `${provision.path}:/app`, `${SSHPATH}:/root/.ssh` ], "NetworkMode": "host" } }).then(function(data){ let container = data[1]; console.log(`Terraform# Apply: ${name} (${container.id}) has finished with code: ${data[0].StatusCode}`); return container.remove().then(function(){ console.log(`Terraform# Apply: Container '${name}' removed!`); return data[0].StatusCode; }); }).then(function(statusCode) { return {"output": fs.readFileSync(provision.logFile), "statusCode": statusCode}; }) } const destroy = function(destroyMongo, provision, scenario) { const name = `qmi-tf-destroy-${destroyMongo._id}`; console.log(`Terraform# Destroy: will spin up container: ${name}`); var processStream = fs.createWriteStream(destroyMongo.logFile, {flags:'a'}); var exec = ['terraform', 'destroy', '-auto-approve', '-no-color']; exec = _buildVarsExec(exec, provision, scenario); console.log('Terraform# Destroy: exec: '+exec.join(" ")); var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE; console.log('Terraform# Destroy: version to use is : '+terraformImage); return docker.run(terraformImage, exec, processStream, { //"Env": ["VAR_ENV=whatever"], "name": name, "WorkingDir": "/app", "HostConfig": { "Binds": [ `${provision.path}:/app`, `${SSHPATH}:/root/.ssh` ] } }).then(function(data) { var container = data[1]; console.log(`Terraform# Destroy: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`); return container.remove().then(function(){ console.log(`Terraform# Destroy: Container '${name}' removed!`); return data[0].StatusCode; }); }).then(async function(statusCode) { return {"output": fs.readFileSync(destroyMongo.logFile), "statusCode": statusCode}; }); }; const outputs = function(provision) { const name = `qmi-tf-output-${provision._id}`; console.log(`Terraform# Output: will spin up container: ${name}`); var exec = ['terraform', 'output', '-no-color', '-json']; console.log('Terraform# Output: exec: '+exec.join(" ")); var tfout = ""; var unhook = hook_stdout(function(string, encoding, fd) { tfout += string.trim(); }); var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE; return docker.run(terraformImage, exec, process.stdout, { //"Env": ["VAR_ENV=whatever"], "name": name, "WorkingDir": "/app", "HostConfig": { "Binds": [ `${provision.path}:/app` ] } }).then(function(data) { unhook(); var container = data[1]; console.log(`Terraform# Output: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`); return container.remove(); }).then(async function(data) { console.log(`Terraform# Output: Container '${name}' removed!`); //console.log("Terraform# Output: tfout: " + tfout); var out = JSON.parse(tfout); var o = {}; for (var key in out) { o[key] = out[key].value; } return o; }); }; const stop = function(provision) { if ( provision.status !== "provisioning" ) { console.log(`Terraform# Stop: provision (${provision._id}) is not provisioning`); return {"message": `Won't stop container: provision (${provision._id}) is not provisioning`}; } const name = `qmi-tf-apply-${provision._id}`; console.log(`Terraform# Stop: will try to stop container: ${name}`); return docker.listContainers().then(function(containers) { var containerID; for (let i=0;i