Context-based programming is DRY
Recently, I needed to document firewall rules for a cluster of machines. The document needed to spell out rules by machine, but that meant that connections between machines were described in two places: the machine requesting the connection, and the one servicing it.
Context-based programming gave me an easy to describe a connection in a single place, but generate a document that connection in two places.
I can also use the same source file to generate the firewall rules themselves.
List of machines
I begin by putting the description of each connection in a new file, conn.rb. By wrapping everything in a function, the description can be evaluated whenever desired.
This example shows two connections: one from the application server to the database, and another from the background worker to the database:
def connections # from to port tcp :app :db 5432 tcp :worker :db 5432 end
Another file, main.rb, will load conn.rb and control the context. To get a list of all machines mentioned, tcp is defined as a function that accumulates both of the connection's machines into a set:
require 'set' require './conn.rb' $machines = Set.new def tcp client, server, port $machines << client << server end
By calling connections in this context, we can print out all of the mentioned machines:
connections p $machines
#<Set: {:app, :db, :worker}>
Connections by machine
Now that we have a list of all the machines, we can generate a document with one section per machine. Each machine's section can have a table for inbound connections and another for outbound connections.
First, looping over each machine and generating its section:
$m = nil
for $m in $machines
puts "# #{$m} #"
puts
# Inbound connections...
# Outbound connections...
end
After a machine's heading is output, we'll define tcp to collect rows for inbound connections, call connections to gather them, then output them:
# Inbound connections...
$inbound = ""
def tcp client, server, port
$inbound << table_row(client, port) if server == $m
end
connections
print_subsection "Inbound", $inbound
Likewise for outbound connections:
# Outbound connections...
$outbound = ""
def tcp client, server, port
$outbound << table_row(server, port) if client == $m
end
connections
print_subsection "Outbound", $outbound
We can now generate a document describing each machine's connections:
# app # ## Outbound ## | db | 5432 | # db # ## Inbound ## | app | 5432 | | worker | 5432 | # worker # ## Outbound ## | db | 5432 |
Final source code
require 'set'
require './conn.rb'
def table_row machine, port
format "| %-6s | %4i |\n", machine, port
end
def print_subsection heading, body
unless body.empty?
puts "## #{heading} ##"
puts
puts body
puts
end
end
$machines = Set.new
def tcp client, server, port
$machines << client << server
end
connections
$m = nil
for $m in $machines
puts "# #{$m} #"
puts
# Inbound connections...
$inbound = ""
def tcp client, server, port
$inbound << table_row(client, port) if server == $m
end
connections
print_subsection "Inbound", $inbound
# Outbound connections...
$outbound = ""
def tcp client, server, port
$outbound << table_row(server, port) if client == $m
end
connections
print_subsection "Outbound", $outbound
end
Trackbacks
The author does not allow comments to this entry
Comments
Display comments as Linear | Threaded