import fs from 'fs' ; import path from 'path' ; import sleep from 'sleep-promise' ; import { execCmd } from './cmd-utils' ; const terraformModulesPath = path . join ( __dirname , '../../../terraform-modules' ) ; export interface TerraformVars { [ key ] : string ; } export async function initTerraformModule ( moduleName , vars , backendConfigVars ) { const modulePath = getModulePath ( moduleName ) ; return buildAndExecTerraformCmd ( 'init' , modulePath , modulePath , getVarOptions ( vars ) , getVarOptions ( backendConfigVars , 'backend-config' ) , '-reconfigure' , '> /dev/null' ) ; } export function planTerraformModule ( moduleName , vars , destroy = false ) { const planPath = getPlanPath ( moduleName ) ; const planDir = path . dirname ( planPath ) ; if ( ! fs . existsSync ( planDir ) ) { fs . mkdirSync ( planDir ) ; } const modulePath = getModulePath ( moduleName ) ; return buildAndExecTerraformCmd ( 'plan' , modulePath , modulePath , `-out=${ planPath }` , getVarOptions ( vars ) , destroy ? '-destroy' : '' ) ; } export function applyTerraformModule ( moduleName ) { return buildAndExecTerraformCmd ( 'apply' , getModulePath ( moduleName ) , getPlanPath ( moduleName ) ) ; } export function destroyTerraformModule ( moduleName , vars ) { return buildAndExecTerraformCmd ( 'destroy' , getModulePath ( moduleName ) , getVarOptions ( vars ) , '-force' ) ; } export function taintTerraformModuleResource ( moduleName , resourceName ) { if ( resourceName . endsWith ( '.*' ) ) { return taintEveryResourceWithPrefix ( moduleName , resourceName . replace ( '.*' , '' ) ) ; } else { return taintResource ( moduleName , resourceName ) ; } } export function untaintTerraformModuleResource ( moduleName , resourceName ) { if ( resourceName . endsWith ( '.*' ) ) { return untaintEveryResourceWithPrefix ( moduleName , resourceName . replace ( '.*' , '' ) ) ; } else { return untaintResource ( moduleName , resourceName ) ; } } async function taintEveryResourceWithPrefix ( moduleName , resourceName ) { const matches = await getEveryResourceWithPrefix ( moduleName , resourceName ) ; for ( const match of matches ) { await taintResource ( moduleName , match ) ; await sleep ( 100 ) ; } } async function untaintEveryResourceWithPrefix ( moduleName , resourceName ) { const matches = await getEveryResourceWithPrefix ( moduleName , resourceName ) ; for ( const match of matches ) { await untaintResource ( moduleName , match ) ; await sleep ( 100 ) ; } } async function getEveryResourceWithPrefix ( moduleName , resourcePrefix ) { const resources = await getTerraformModuleResourceNames ( moduleName ) ; return resources . filter ( ( resource ) => resource . startsWith ( resourcePrefix ) ) ; } function taintResource ( moduleName , resourceName ) { try { const escapedResourceName = resourceName . replace ( /"/g , '\\"' ) ; return execTerraformCmd ( `terraform taint ${ escapedResourceName }` , getModulePath ( moduleName ) , false ) ; } catch ( e ) { console . info ( `Could not taint ${ resourceName }` , e ) ; return Promise . resolve ( ) ; } } function untaintResource ( moduleName , resourceName ) { try { const escapedResourceName = resourceName . replace ( /"/g , '\\"' ) ; return execTerraformCmd ( `terraform untaint ${ escapedResourceName }` , getModulePath ( moduleName ) , false ) ; } catch ( e ) { console . info ( `Could not taint ${ resourceName }` , e ) ; return Promise . resolve ( ) ; } } function refreshTerraformModule ( moduleName , vars ) { return buildAndExecTerraformCmd ( 'refresh' , getModulePath ( moduleName ) , getVarOptions ( vars ) ) ; } export async function getTerraformModuleOutputs ( moduleName , vars ) { await refreshTerraformModule ( moduleName , vars ) ; const modulePath = getModulePath ( moduleName ) ; const [ output ] = await execCmd ( `cd ${ modulePath } && terraform output -json` ) ; return JSON . parse ( output ) ; } export async function getTerraformModuleResourceNames ( moduleName ) { const [ output ] = await execTerraformCmd ( `terraform state list` , getModulePath ( moduleName ) , false ) ; return output . split ( '\n' ) ; } export function showTerraformModulePlan ( moduleName ) { return execTerraformCmd ( `terraform show ${ getPlanPath ( moduleName ) }` , getModulePath ( moduleName ) , true ) ; } function getModulePath ( moduleName ) { return path . join ( terraformModulesPath , moduleName ) ; } function getPlanPath ( moduleName ) { return path . join ( terraformModulesPath , 'plan' , moduleName ) ; } function getVarOptions ( vars , optionName = 'var' ) { const nameValuePairs = Object . keys ( vars ) . map ( ( varName ) => `-${ optionName }='${ varName }=${ vars [ varName ] }'` ) ; return nameValuePairs . join ( ' ' ) ; } function execTerraformCmd ( command , modulePath , pipeOutput ) { return execCmd ( `cd ${ modulePath } && ${ command }` , { } , false , pipeOutput ) ; } function buildAndExecTerraformCmd ( commandName , modulePath , cmdPath , ... options ) { const terraformCmd = buildTerraformCmd ( commandName , cmdPath , ... options ) ; return execTerraformCmd ( terraformCmd , modulePath , true ) ; } function buildTerraformCmd ( command , cmdPath , ... options ) { const optionsStr = options ? options . join ( ' ' ) : '' ; const applyPlanDirStr = command === 'apply' ? cmdPath : '' ; return `terraform ${ command } -input=false ${ optionsStr } ${ applyPlanDirStr }` ; }