# frozen_string_literal: true require 'bolt/error' require 'bolt/pal/yaml_plan/loader' require 'bolt/util' module Bolt class PAL class YamlPlan class Transpiler class ConvertError < Bolt::Error def initialize(msg, plan_path) super(msg, 'bolt/convert-error', { "plan_path" => plan_path }) end end def transpile(relative_path) @plan_path = File.expand_path(relative_path) @modulename = Bolt::Util.module_name(@plan_path) @filename = @plan_path.split(File::SEPARATOR)[-1] validate_path plan_object = parse_plan plan_string = String.new("# WARNING: This is an autogenerated plan. " \ "It may not behave as expected.\n" \ "plan #{plan_object.name}(") # Parameters are Bolt::PAL::YamlPlan::Parameter plan_object.parameters&.each_with_index do |param, i| plan_string << param.transpile # If it's the last parameter add a newline and no comma last = i + 1 == plan_object.parameters.length ? "\n" : "," # This encodes strangely if we << directly to plan_string plan_string << last end plan_string << ") {\n" plan_object.steps&.each do |step| # This only needs the plan path for raising errors plan_string << step.transpile(@plan_path) end plan_string << "\n return #{Bolt::Util.to_code(plan_object.return)}\n" if plan_object.return plan_string << "}" # We always print the plan, even if there's an error puts plan_string validate_plan(plan_string) plan_string end # Save me from all these rescue statements... def parse_plan begin file_contents = File.read(@plan_path) rescue Errno::ENOENT msg = "Could not read yaml plan file: #{@plan_path}" raise Bolt::FileError.new(msg, @plan_path) end begin result = Bolt::PAL::YamlPlan::Loader.parse_plan(file_contents, @plan_path) rescue Error => e raise ConvertError.new("Failed to convert yaml plan: #{e.message}", @plan_path) end unless result.is_a?(Hash) type = result.class.name raise ArgumentError, "The data loaded from #{source_ref} does not contain an object - its type is #{type}" end Bolt::PAL::YamlPlan.new(@modulename, result).freeze end def validate_path unless File.extname(@filename) == ".yaml" raise ConvertError.new("You can only convert plans written in yaml", @plan_path) end end def validate_plan(plan) Puppet::Pops::Parser::EvaluatingParser.new.parse_string(plan) rescue Puppet::Error => e $stderr.puts "The converted puppet plan contains invalid puppet code: #{e.message}" exit 1 end end end end end