Trying to convert some configuration files to a different output

How you doing? I’m trying to make my life easier in translating some
configuration files from an old version to a new one, and I’m
stuck/frustrated since I’m new to Ruby. Basically i would need a script
to convert from this input:

route-map 6300-test-in permit 5
match as-path 1
set local-preference 90
set community 65000:1 65000:3 65000:1000 65000:3000 65000:3500
65000:3613
!
route-map 6300-test-in permit 10
match ip address prefix-list 6300-pref-low
set local-preference 90
set community 65000:1 65000:3 65000:1000 65000:3000 65000:3500
65000:3613
!
route-map 6300-test-in permit 5000
set local-preference 200
set community 65000:1 65000:3 65000:1000 65000:3000 65000:3500
65000:3613

to this output:
route-policy 6300-test-in
apply bogons
if as-path in aspath1 then
set local-preference 90
set community (65000:1 65000:3 65000:1000 65000:3000 65000:3500
65000:3613)
elseif destination in 6300-pref-low then
set local-preference 90
set community (65000:1 65000:3 65000:1000 65000:3000 65000:3500
65000:3613)
else
set local-preference 200
set community 65000:1 65000:3 65000:1000 65000:3000 65000:3500
65000:3613)
endif
end-policy

I tried with line.match but it doesn’t really help since half the
configuration just repeats by section. Was trying to hash the input but
just
getting nowhere. Any help would be appreciated.

thanks

Well you seem to have quite a few changes in there. Have you looked into
regular expressions? That might be enough to match a pattern and make
your changes.

Without multiple examples and the rules laid out clearly it’d be
difficult to write something flexible enough to cover a repetitive task.

I’d start out with something like
output =
input.gsub(“#{input.match(/[\d:\s]{60,}/).to_s.strip}”,“(#{input.match(/[\d:\s]{60,}/).to_s.strip})”)

That’d add the parentheses where you seem to want them, assuming your
number sequence is identical in each line as in your example. You should
be able to use variations on this kind of approach for the rest of the
changes.

Maybe someone else will come up with a more efficient method :slight_smile:

=begin
A note about the structure:

  • a route map has a name and a list of entries
  • a route map entry has a priority (in Cisco world anyway), a list
    of matches, and a list of actions

So let’s make an object which holds the data for one route map entry
=end

class RouteMapEntry
attr_accessor :priority, :conditions, :actions
def initialize
@priority = nil
@conditions = []
@actions = []
end
end

=begin
Parsing a single route map entry is pretty simple; we can use regular
expressions to match the ‘match’ and ‘set’ lines, and convert them to
the relevant Juniper form before storing them in a RouteMapEntry.

For flexibility, we will write this parser so it assumes that the first
line (the route-map header) has already been parsed, and it returns the
first unparseable line. This allows us easily to assemble different
parsers
if we want to parse an entire Cisco config.
=end

def parse_cisco_routemap_entry(src, priority)
rme = RouteMapEntry.new
rme.priority = priority
loop do
line = src.gets
line.strip! if line # remove leading/trailing whitespace
case line
when /^match as-path (\d+)$/
rme.conditions << “as-path in aspath#{$1}”
when /^match ip address prefix-list (\S+)$/
rme.conditions << “destination in #{$1}”
when /^set local-preference (\d+)$/
rme.actions << “set local-preference #{$1}”
when /^set community (.*)$/
rme.actions << “set community (#{$1})”
# add more here as required
else
# EOF or any unrecognised line terminates this routemap entry
return [rme, line]
end
end
end

=begin
So now we parse a whole series of routemaps. In general a config
may have multiple named routemaps, so we build a hash of them.
=end

def parse_cisco_routemaps(src)
routemaps = {} # {“name” => [RouteMapEntry, RouteMapEntry, …]}
line = src.gets
while line
line.strip! # remove leading/trailing whitespace
case line
when /^!/, /^\s*$/
# skip comment or empty line
line = src.gets
when /^route-map (\S+) permit (\d+)$/
name, priority = $1, $2.to_i
rme, line = parse_cisco_routemap_entry(src, priority)
routemaps[name] ||= []
routemaps[name] << rme
else
$stderr.puts “Unrecognized line: #{line.inspect}”
line = src.gets
end
end
return routemaps
end

=begin
We need to be able to assemble a list of routemaps into Juniper form
=end

def write_juniper_routemaps(routemaps, dest=$stdout)
routemaps.each do |name,routemap_entries|
dest << “route-policy #{name}\n”
dest << " apply bogons\n"
routemap_entries = routemap_entries.sort_by { |e| e.priority }
cond = “if”
routemap_entries.each do |rme|
if rme.conditions.empty?
dest << " else\n"
else
dest << " #{cond} #{rme.conditions.join(" and “)} then\n”
end
rme.actions.each do |action|
dest << " #{action}\n"
end
break if rme.conditions.empty?
cond = “elseif”
end
dest << " endif\n"
dest << “end-policy\n”
end
end

Testing

src = <<EOS
route-map 6300-test-in permit 5
match as-path 1
set local-preference 90
set community 65000:1 65000:3 65000:1000 65000:3000 65000:3500
65000:3613
!
route-map 6300-test-in permit 10
match ip address prefix-list 6300-pref-low
set local-preference 90
set community 65000:1 65000:3 65000:1000 65000:3000 65000:3500
65000:3613
!
route-map 6300-test-in permit 5000
set local-preference 200
set community 65000:1 65000:3 65000:1000 65000:3000 65000:3500
65000:3613
EOS

require ‘stringio’
rms = parse_cisco_routemaps(StringIO.new(src))
write_juniper_routemaps(rms)

Real main program might look like this:

rms = parse_cisco_routemaps($stdin)

write_juniper_routemaps(rms)