require 'pdk' module PDK module Module class Convert def self.invoke(module_dir, options) new(module_dir, options).run end attr_reader :module_dir, :options def initialize(module_dir, options = {}) @module_dir = module_dir @options = options end def convert? instance_of?(PDK::Module::Convert) end def run stage_changes! unless update_manager.changes? if adding_tests? add_tests! print_result 'Convert completed' else require 'pdk/report' PDK::Report.default_target.puts('No changes required.') end return end print_summary full_report('convert_report.txt') unless update_manager.changes[:modified].empty? return if noop? unless force? require 'pdk/cli/util' PDK.logger.info 'Module conversion is a potentially destructive action. ' \ 'Ensure that you have committed your module to a version control ' \ 'system or have a backup, and review the changes above before continuing.' continue = PDK::CLI::Util.prompt_for_yes('Do you want to continue and make these changes to your module?') return unless continue end # Remove these files straight away as these changes are not something # that the user needs to review. update_manager.unlink_file('Gemfile.lock') update_manager.unlink_file(File.join('.bundle', 'config')) update_manager.sync_changes! require 'pdk/util/bundler' PDK::Util::Bundler.ensure_bundle! add_tests! if adding_tests? print_result 'Convert completed' end def noop? options[:noop] end def force? options[:force] end def add_tests? options[:'add-tests'] end def adding_tests? add_tests? && missing_tests? end def missing_tests? !available_test_generators.empty? end def available_test_generators # Only select generators which can run and have no pre-existing files test_generators.select do |gen| if gen.can_run? gen.template_files.none? { |_, dst_path| PDK::Util::Filesystem.exist?(File.join(gen.context.root_path, dst_path)) } else false end end end def test_generators(context = PDK.context) return @test_generators unless @test_generators.nil? require 'pdk/util/puppet_strings' test_gens = PDK::Util::PuppetStrings.all_objects.map do |generator, objects| (objects || []).map do |obj| generator.new(context, obj['name'], spec_only: true) end end @test_generators = test_gens.flatten end def stage_tests!(manager) available_test_generators.each do |gen| gen.stage_changes(manager) end manager end def add_tests! update_manager.clear! stage_tests!(update_manager) if update_manager.changes? update_manager.sync_changes! print_summary else PDK::Report.default_target.puts('No test changes required.') end end def stage_changes!(context = PDK.context) require 'pdk/util/filesystem' metadata_path = File.join(module_dir, 'metadata.json') PDK::Template.with(template_uri, context) do |template_dir| new_metadata = update_metadata(metadata_path, template_dir.metadata) if options[:noop] && new_metadata.nil? update_manager.add_file(metadata_path, '') elsif PDK::Util::Filesystem.file?(metadata_path) update_manager.modify_file(metadata_path, new_metadata.to_json) else update_manager.add_file(metadata_path, new_metadata.to_json) end # new_metadata == nil when creating a new module but with --noop@ module_name = new_metadata.nil? ? 'new-module' : new_metadata.data['name'] metadata_for_render = new_metadata.nil? ? {} : new_metadata.data template_dir.render_new_module(module_name, metadata_for_render) do |relative_file_path, file_content, file_status, file_executable| absolute_file_path = File.join(module_dir, relative_file_path) case file_status when :unmanage PDK.logger.debug(format("skipping '%{path}'", path: absolute_file_path)) when :delete update_manager.remove_file(absolute_file_path) when :init if convert? && !PDK::Util::Filesystem.exist?(absolute_file_path) update_manager.add_file(absolute_file_path, file_content) update_manager.make_file_executable(absolute_file_path) if file_executable end when :manage if PDK::Util::Filesystem.exist?(absolute_file_path) update_manager.modify_file(absolute_file_path, file_content) update_manager.make_file_executable(absolute_file_path) if file_executable && !PDK::Util::Filesystem.stat(absolute_file_path).executable? else update_manager.add_file(absolute_file_path, file_content) update_manager.make_file_executable(absolute_file_path) if file_executable end end end end rescue ArgumentError => e raise PDK::CLI::ExitWithError, e end def update_manager require 'pdk/module/update_manager' @update_manager ||= PDK::Module::UpdateManager.new end def template_uri require 'pdk/util/template_uri' @template_uri ||= PDK::Util::TemplateURI.new(options) end def update_metadata(metadata_path, template_metadata) require 'pdk/generate/module' require 'pdk/util/filesystem' require 'pdk/module/metadata' if PDK::Util::Filesystem.file?(metadata_path) raise PDK::CLI::ExitWithError, format('Unable to update module metadata; %{path} exists but it is not readable.', path: metadata_path) unless PDK::Util::Filesystem.readable?(metadata_path) begin metadata = PDK::Module::Metadata.from_file(metadata_path) new_values = PDK::Module::Metadata::DEFAULTS.select do |key, _| !metadata.data.key?(key) || metadata.data[key].nil? || (key == 'requirements' && metadata.data[key].empty?) end metadata.update!(new_values) rescue ArgumentError metadata = PDK::Generate::Module.prepare_metadata(options) unless options[:noop] end elsif PDK::Util::Filesystem.exist?(metadata_path) raise PDK::CLI::ExitWithError, format('Unable to update module metadata; %{path} exists but it is not a file.', path: metadata_path) else return if options[:noop] project_dir = File.basename(Dir.pwd) options[:module_name] = PDK.context.is_a?(PDK::Context::ControlRepo) ? project_dir : project_dir.split('-', 2).compact[-1] options[:prompt] = false options[:'skip-interview'] = true if options[:force] metadata = PDK::Generate::Module.prepare_metadata(options) end metadata.update!(template_metadata) metadata end def summary summary = {} update_manager.changes.each do |category, update_category| if update_category.respond_to?(:keys) updated_files = update_category.keys else begin updated_files = update_category.map { |file| file[:path] } rescue TypeError updated_files = update_category.to_a end end summary[category] = updated_files end summary end def print_summary require 'pdk/report' footer = false summary.each_key do |category| next if summary[category].empty? PDK::Report.default_target.puts(format("\n%{banner}", banner: generate_banner("Files to be #{category}", 40))) PDK::Report.default_target.puts(summary[category]) footer = true end PDK::Report.default_target.puts(format("\n%{banner}", banner: generate_banner('', 40))) if footer end def print_result(banner_text) require 'pdk/report' PDK::Report.default_target.puts(format("\n%{banner}", banner: generate_banner(banner_text, 40))) summary_to_print = summary.filter_map { |k, v| "#{v.length} files #{k}" unless v.empty? } PDK::Report.default_target.puts(format("\n%{summary}\n\n", summary: "#{summary_to_print.join(', ')}.")) end def full_report(path) require 'pdk/report' report = ["/* Report generated by PDK at #{Time.now} */"] report.concat(update_manager.changes[:modified].map { |_, diff| "\n\n\n#{diff}" }) PDK::Util::Filesystem.write_file(path, report.join) PDK::Report.default_target.puts(format("\nYou can find a report of differences in %{path}.\n\n", path: path)) end def generate_banner(text, width = 80) padding = width - text.length banner = '' padding_char = '-' (padding / 2.0).ceil.times { banner << padding_char } banner << text (padding / 2.0).floor.times { banner << padding_char } banner end end end end