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