Created
November 13, 2018 01:28
-
-
Save outofcoffee/ebb82e13809554b5a1dd2d123c6abd45 to your computer and use it in GitHub Desktop.
Migrates Terraform resources between modules.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env groovy | |
import groovy.json.JsonSlurper | |
// Migrates Terraform resources between modules. | |
// | |
// Resources are read from the source Terraform module, imported into the target module, | |
// then removed from the source module. The resource names can differ between source and target | |
// modules. | |
// | |
// Configure the source and target mappings in a JSON file, e.g. | |
// [ | |
// { | |
// "source": { | |
// "module": "api_gw", | |
// "resource": "module.alb_api_gw.aws_route53_record.r" | |
// }, | |
// "target": { | |
// "module": "endpoints", | |
// "resource": "aws_route53_record.api_origin" | |
// } | |
// }, | |
// // ...other mappings here | |
// ] | |
// | |
OptionAccessor parseArguments() { | |
def cli = new CliBuilder(usage: 'migrateTerraformResources.groovy [options]') | |
cli.h(required: false, longOpt: 'help', 'help/usage') | |
cli.e(required: true, longOpt: 'env', args: 1, 'environment') | |
cli.f(required: true, longOpt: 'mappings', args: 1, 'migration mapping file') | |
cli.stopAtNonOption = false | |
def options = cli.parse(args) | |
if (!options) { | |
System.exit(1) | |
} else if (options.h) { | |
cli.usage() | |
System.exit(0) | |
} | |
options | |
} | |
class Migrator { | |
def initialisedDirs = [] | |
def migratedCount = 0 | |
String exec(File workingDir, String command, boolean followOutput = false) { | |
println "\n> ${command}" | |
def commandElements = command.replaceAll("[\\r\\n]*", "").split("\\s+") | |
def procBuilder = new ProcessBuilder(commandElements) | |
.redirectErrorStream(true) | |
.directory(workingDir) | |
if (followOutput) procBuilder.inheritIO() | |
def proc = procBuilder.start() | |
if (0 != proc.waitFor()) throw new RuntimeException(proc.text) else proc.text | |
} | |
void initForEnvironment(File moduleDir, String environmentName) { | |
if (initialisedDirs.contains(moduleDir.toString())) { | |
return | |
} | |
exec(moduleDir, "terraform init", true) | |
try { | |
exec(moduleDir, "terraform workspace select ${environmentName}", true) | |
} catch (ignored) { | |
exec(moduleDir, "terraform workspace new ${environmentName}", true) | |
} | |
initialisedDirs += moduleDir.toString() | |
} | |
void migrateResources(List<Map<String, Map<String, String>>> mappings, String environmentName) { | |
for (mapping in mappings) { | |
println "\nChecking resource ${mapping.source.resource} in ${mapping.source.module}..." | |
File srcDir = new File(mapping.source.module) | |
initForEnvironment(srcDir, environmentName) | |
String resourceInfo = exec(srcDir, "terraform state show ${mapping.source.resource}") | |
if (null == resourceInfo || resourceInfo.trim().isEmpty()) { | |
println "Source resource not found: ${mapping.source.resource} - skipping" | |
continue | |
} | |
String resourceId = findResourceId(resourceInfo, mapping.source.resource) | |
File targetDir = new File(mapping.target.module) | |
initForEnvironment(targetDir, environmentName) | |
println "Importing resource into ${targetDir} state with ID: ${resourceId}..." | |
exec(targetDir, "terraform import ${mapping.target.resource} ${resourceId}", true) | |
migratedCount++ | |
println "Removing resource from ${srcDir} state: ${mapping.source.resource}..." | |
exec(srcDir, "terraform state rm ${mapping.source.resource}", true) | |
} | |
} | |
String findResourceId(String resourceInfo, sourceResource) { | |
String resourceId | |
for (line in resourceInfo.split("\\n")) { | |
String[] parts = line.trim().split("\\s+") | |
if (parts.length < 3) { | |
println "Resource details not found: ${sourceResource} - skipping" | |
break | |
} | |
if (parts[0] == 'id') { | |
resourceId = parts[2] | |
break | |
} | |
} | |
resourceId | |
} | |
} | |
def options = parseArguments() | |
def mappings = new JsonSlurper().parseText(new File(options.f).text) | |
println "Attempting to migrate ${mappings.size()} resources using mappings in ${options.f}..." | |
def migrator = new Migrator() | |
migrator.migrateResources(mappings, options.e) | |
println "\n${migrator.migratedCount} resources migrated." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment