/// <summary> /// Writes the module blocks for any modules this module refers to. /// </summary> /// <returns>Task to await.</returns> public async Task WriteModuleBlocksAsync() { var modulesFile = Path.Combine(this.ModuleDirectory, TerraformExporterConstants.ModulesFile); if (File.Exists(modulesFile)) { // In case we removed all pre-existing submodule references File.Delete(modulesFile); } if (!this.NestedModules.Any()) { return; } using (var fs = AsyncFileHelpers.OpenWriteAsync(modulesFile)) using (var writer = new StreamWriter(fs, AsyncFileHelpers.DefaultEncoding)) { foreach (var module in this.NestedModules) { var sb = new StringBuilder().AppendLine($"module \"{module.Name}\" {{").AppendLine( $" source = \"./{module.Settings.ModuleDirectory.Replace('\\', '/')}\""); if (module.IsImported) { // We can only emit module arguments once the module has been imported and // corresponding "variable" declarations are visible to Terraform command. foreach (var input in module.Inputs.Where(i => !(i.IsDataSource || i is PseudoParameterInput))) { sb.AppendLine($" {input.GenerateVariableAssignment()}"); } } sb.AppendLine("}"); await writer.WriteLineAsync(sb.ToString()); } } }
/// <summary> /// Processes the stack asynchronous. /// </summary> /// <returns>Module describing entire tree of nested stacks.</returns> public async Task <ModuleInfo> ProcessStackAsync() { if (this.settings.ExportNestedStacks) { return(await ProcessChildModule(this.settings, this.warnings)); } this.settings.Logger.LogInformation($"Processing stack {this.settings.StackName}..."); using (var fs = AsyncFileHelpers.OpenAppendAsync(TerraformExporterConstants.MainScriptFile)) using (var writer = new StreamWriter(fs, AsyncFileHelpers.DefaultEncoding)) { var module = new ModuleInfo( this.settings, null, new InputVariableProcessor(this.settings, this.warnings).ProcessInputs(), null); await module.ProcessResources(writer, this.warnings); return(module); } }
/// <summary> /// Performs Terraform export. /// </summary> /// <returns><c>true</c> if resources were imported; else <c>false</c></returns> public async Task <bool> Export() { this.settings.Logger.LogInformation("\nInitializing inputs and resources..."); using (new WorkingDirectoryContext(this.settings.WorkspaceDirectory)) { var configurationBlocks = new ConfigurationBlockBuilder().WithRegion(this.settings.AwsRegion) .WithDefaultTag(this.settings.AddDefaultTag ? this.settings.StackName : null).WithZipper( this.settings.Template.Resources.Any( r => r.Type == TerraformExporterConstants.AwsLambdaFunction && r.GetResourcePropertyValue(TerraformExporterConstants.LambdaZipFile) != null)).Build(); // Write out terraform and provider blocks. using (var writer = new StreamWriter(TerraformExporterConstants.MainScriptFile)) { await writer.WriteLineAsync(configurationBlocks); } // Gather and write out initial stack resources var mapper = new ResourceMapper(this.settings, this.warnings); var rootModule = await mapper.ProcessStackAsync(); var allModules = rootModule?.DescendentsAndThis().ToList(); if (rootModule == null || !allModules.Any()) { this.settings.Logger.LogWarning("No resources were found that could be imported."); return(false); } var terraformExecutionErrorCount = 0; var warningCount = 0; try { this.InitializeWorkspace(); foreach (var module in allModules) { var importedResources = await module.ImportResources(this.warnings, this.errors); if (!this.settings.ExportNestedStacks) { // If leaving nested stacks as aws_cloudformation_stack, then fix up S3 locations of these. await this.FixCloudFormationStacksAsync(importedResources); } var stateFile = JsonConvert.DeserializeObject <StateFile>( await AsyncFileHelpers.ReadAllTextAsync(this.stateFilePath)); var writer = new HclWriter(module, this.warnings, this.errors); warningCount += await writer.Serialize(stateFile); this.settings.Logger.LogInformation( $"\nExport of stack \"{module.Settings.StackName}\" to terraform complete!"); } } catch (TerraformRunnerException e) { // Errors already reported by output of terraform terraformExecutionErrorCount += e.Errors; warningCount += e.Warnings; throw; } catch (HclSerializerException e) { this.errors.Add(e.Message); throw; } catch (Exception e) { this.errors.Add($"ERROR: Internal error: {e.Message}"); throw; } finally { this.WriteSummary(terraformExecutionErrorCount, warningCount); } } return(true); }
/// <summary> /// Recursively process nested stacks. /// </summary> /// <param name="settings">The settings.</param> /// <param name="warnings">The warnings.</param> /// <returns>Module being processed.</returns> private static async Task <ModuleInfo> ProcessChildModule( ITerraformExportSettings settings, IList <string> warnings) { var childModules = new List <ModuleInfo>(); foreach (var child in settings.Resources.Where( r => r.ResourceType == TerraformExporterConstants.AwsCloudFormationStack)) { var logicalId = child.LogicalResourceId; var stackName = GetStackName(child.StackResource.PhysicalResourceId); var stackData = await StackHelper.ReadStackAsync( settings.CloudFormationClient, stackName, new Dictionary <string, object>()); var childModuleSettings = settings.CopyWith( stackData.Template, stackData.Resources, stackData.Outputs, stackName, Path.Combine("modules", stackName), logicalId); childModules.Add(await ProcessChildModule(childModuleSettings, warnings)); } var workingDirectory = Path.Combine(settings.WorkspaceDirectory, settings.ModuleDirectory); if (!Directory.Exists(workingDirectory)) { Directory.CreateDirectory(workingDirectory); } settings.Logger.LogInformation($"Processing stack {settings.StackName}..."); var scriptFile = Path.Combine(workingDirectory, TerraformExporterConstants.MainScriptFile); ModuleInfo module; using (var fs = settings.IsRootModule ? AsyncFileHelpers.OpenAppendAsync(scriptFile) : AsyncFileHelpers.OpenWriteAsync(scriptFile)) using (var writer = new StreamWriter(fs, AsyncFileHelpers.DefaultEncoding)) { var thisModuleSettings = settings.CopyWith( settings.Template, settings.Resources.Where(r => r.ResourceType != TerraformExporterConstants.AwsCloudFormationStack), null, settings.StackName, settings.IsRootModule ? "." : settings.ModuleDirectory, settings.LogicalId); module = new ModuleInfo( thisModuleSettings, childModules, new InputVariableProcessor(thisModuleSettings, warnings).ProcessInputs(), thisModuleSettings.CloudFormationOutputs); await module.ProcessResources(writer, warnings); } await module.WriteModuleBlocksAsync(); return(module); }