Erbitte Anfaengerkritik

26/02/2012 - 11:03 von Marc Haber | Report spam
Hallo,

ich möchte meinen Nagios-Config-Generator auf IPv6 erweitern und bei
dieser Gelegenheit noch einige Luxufeatures dazuimplementieren. Bisher
habe ich das mit perl und template-toolkit implementiert, habe mich
aber entschlossen, die neue Version "zum warmwerden" in ruby zu
programmieren.

Meine Arbeit erfordert im Moment sowieso, dass ich etwas firmer in
ruby werde. Außerdem finde ich den Ansatz, innerhalb der Templates
dieelbe Programmiersprache zu benutzen wie im eigentlichen Code,
erheblich eleganter als stàndig zwischen perl und dieser komischen
Template-Toolkit-Sprache hin- und her zu wechseln.

Hier mein vorlàufiger Code:

#!/usr/bin/ruby

require 'json'
require 'pp'
require 'erb'
require 'resolv'

# parse JSON input data

file = File.open("example.json", "r")
data = file.read
parsed = JSON.parse(data)
defaults = parsed["defaults"]
hosts = parsed["host"]

# templates

hostdefinitiontemplate = %q{
define host
use <%= hostdata["use"] %>
host_name <%= hostdata["name"] %>_<%= hostdata["thisip"]
%>
hostgroups <% last=hostdata["groups"].shift %><%= last
%><% hostdata["groups"].each do |group| %>, <%= group %><% end %>
alias <%= hostdata["name"] %> (<%hostdata["thisip"] %>)
address <%= hostdata["thisip"] %>
parents <%= hostdata["parents"] %>
}.gsub(/^ /, '')

# iterate through hosts

hosts.keys.each do |hostname|

# save us from writing "hosts[hostname]"
puts hostname.pretty_inspect if $DEBUG
hostdata=hosts[hostname]
puts hostdata.pretty_inspect if $DEBUG

# allow identical handling for host name and other properties
hostdata["name"] ||= hostname

# pull in defaults from defaults stanza
hostdata["use"] ||= defaults["use"]
hostdata["parents"] ||= defaults["parents"]
hostdata["groups"] = hostdata["services"]

# pull all host's IPs from DNS
hostdata["ip"] = []
dns = Resolv::DNS.new
dns.getresources(hostdata["name"],
Resolv::DNS::Resource::IN::A).collect do |r|
hostdata["ip"] << r.address
end
dns.getresources(hostdata["name"],
Resolv::DNS::Resource::IN::AAAA).collect do |r|
hostdata["ip"] << r.address
end
puts hostdata.pretty_inspect if $DEBUG

# expand variable once per IP address
hostdata["ip"].each do |ip|
hostdata["thisip"] = ip
template = ERB.new(hostdefinitiontemplate, 0, "%<>")
output = template.result(binding)
puts output
end
end

(leider sind die Zeilenumbrüche da hineingerutscht, das krieg ich
kurzfristig mit meinem Newsreader nicht anders hin; die Alternative
wàre das ganze Zeug mit | zu quoten, dann könnt Ihr kein cut&paste
mehr machen).

Hier sind Test-Eingabedaten:
$ cat example.json
{
"defaults" : {
"use": "mh-servers",
"hostcheck": "ping",
"hostgroups": [ "mh-servers" ],
"parents": [ "internet" ]
},

"host": {
"torres.zugschlus.de": {
"use": "bla",
"services": [ "ping", "smtp", "ssh" ],
"service": [
{
"name": "ping",
"target": [ "85.214.131.164" ]
}
],
"alias": ""
},
"q.bofh.de": {
"services": [ "ping", "ssh", "smtp", "http" ],
"alias": ""
},
"twoaddresses.zugschlus.de": {
"services": [ "ping", "ssh" ],
"alias": ""
}
}
}

Mein Zielsystem ist Debian squeeze, ich habe also nicht die
allerneuste Software zur Verfügung.

Nun meine Fragen:

Ich habe immer Schwierigkeiten, die Trennstelle zwischen dem
"eigentlichen Code" und der Template zu finden. Die beiden Extreme
sind natürlich nicht sinnvoll, aber ob ich hier (eine
Template-Expansion pro Hostdefinition) den richtigen Kompromiss
gefunden habe, làsst mich zweifeln

In den meisten ERB-Beispielen, die ich gefunden habe, wird eine
Extraklasse mit den Daten für die Template definiert. Ich vermute, in
so einem Setup kommt man in der Template ohne das stàndige
'hostdata["foo"]' aus, und kann stattdessen einfach <%= foo %>
schreiben. Aber: Wie muss mein Code dafür aussehen?

Geht meine Konstruktion zum Bau einer kommagetrennten Liste
| <% last=hostdata["groups"].shift %><%= last %><% hostdata["groups"].each do |group| %>, <%= group %><% end %>
auch etwas eleganter?

Gibt es eine JSON-Library, deren "Pingeligkeit" man reduzieren kann?
In perl gibt es JON::XS->new->relaxed->decode, wo man Listen mit
Extrakommas abschließen kann, wo man Kommentare schreiben kann und
grundsàtzlich etwas laxer mit der Syntax sein kann, ohne dass man
direkt einen Parse Error um die Ohren geworfen bekommt. Gibt es sowas
auch für Ruby? Kann man die Ruby-JSON-Library irgendwie dazu bewegen,
etwas genauere Fehlermeldungen zu produzieren, und mir wenigstens zu
sagen, in welcher Zeile der Eingabe der 'parse error near {'
aufgetreten ist?

In vielem Ruby-Code gibt es Konstruktionen wie object[:index] - hat
der Doppelpunkt eine besondere Bewandtnis?


Natürlich stehe ich auch stylistischen Kommentaren offen gegenüber,
ich habe noch nicht viel Gefühl für die Sprache.

Vielen Dank für Eure Mühe.

Grüße
Marc, hoffend, dass immer noch Leute mitlesen.
Marc Haber | " Questions are the | Mailadresse im Header
Mannheim, Germany | Beginning of Wisdom " | http://www.zugschlus.de/
Nordisch by Nature | Lt. Worf, TNG "Rightful Heir" | Fon: *49 621 72739834
 

Lesen sie die antworten

#1 Robert Klemme
26/02/2012 - 13:44 | Warnen spam
On 26.02.2012 11:03, Marc Haber wrote:
Hallo,



Oh, es gibt noch Leben in der Gruppe!

ich möchte meinen Nagios-Config-Generator auf IPv6 erweitern und bei
dieser Gelegenheit noch einige Luxufeatures dazuimplementieren. Bisher
habe ich das mit perl und template-toolkit implementiert, habe mich
aber entschlossen, die neue Version "zum warmwerden" in ruby zu
programmieren.



Sehr lobenswert!

Meine Arbeit erfordert im Moment sowieso, dass ich etwas firmer in
ruby werde. Außerdem finde ich den Ansatz, innerhalb der Templates
dieelbe Programmiersprache zu benutzen wie im eigentlichen Code,
erheblich eleganter als stàndig zwischen perl und dieser komischen
Template-Toolkit-Sprache hin- und her zu wechseln.

Hier mein vorlàufiger Code:

#!/usr/bin/ruby

require 'json'
require 'pp'
require 'erb'
require 'resolv'

# parse JSON input data

file = File.open("example.json", "r")
data = file.read
parsed = JSON.parse(data)



Nutze besser die Block-Form von File.open um sicherzustellen, dass der
Filedeskriptor unter allen Umstànden auch sauber geschlossen wird.
Außerdem liest man besser nicht erst in den Speicher (ineffizient)
sondern làsst den Parser gleicht den Stream lesen:

parsed = File.open("example.json") {|io| JSON.load(io)}

defaults = parsed["defaults"]
hosts = parsed["host"]

# templates

hostdefinitiontemplate = %q{
define host
use<%= hostdata["use"] %>
host_name<%= hostdata["name"] %>_<%= hostdata["thisip"]
%>
hostgroups<% last=hostdata["groups"].shift %><%= last
%><% hostdata["groups"].each do |group| %>,<%= group %><% end %>
alias<%= hostdata["name"] %> (<%> hostdata["thisip"] %>)
address<%= hostdata["thisip"] %>
parents<%= hostdata["parents"] %>
}.gsub(/^ /, '')



Warum ànderst Du die Einrückung nachtràglich?

# iterate through hosts

hosts.keys.each do |hostname|



Gleich über Schlüssel und Wert iterieren:

hosts.each do |hostname, hostdata|

# save us from writing "hosts[hostname]"
puts hostname.pretty_inspect if $DEBUG
hostdata=hosts[hostname]



raus (s.o.)

puts hostdata.pretty_inspect if $DEBUG

# allow identical handling for host name and other properties
hostdata["name"] ||= hostname

# pull in defaults from defaults stanza
hostdata["use"] ||= defaults["use"]
hostdata["parents"] ||= defaults["parents"]
hostdata["groups"] = hostdata["services"]

# pull all host's IPs from DNS
hostdata["ip"] = []
dns = Resolv::DNS.new
dns.getresources(hostdata["name"],
Resolv::DNS::Resource::IN::A).collect do |r|
hostdata["ip"]<< r.address
end
dns.getresources(hostdata["name"],
Resolv::DNS::Resource::IN::AAAA).collect do |r|
hostdata["ip"]<< r.address
end
puts hostdata.pretty_inspect if $DEBUG

# expand variable once per IP address
hostdata["ip"].each do |ip|
hostdata["thisip"] = ip
template = ERB.new(hostdefinitiontemplate, 0, "%<>")
output = template.result(binding)
puts output
end
end

(leider sind die Zeilenumbrüche da hineingerutscht, das krieg ich
kurzfristig mit meinem Newsreader nicht anders hin; die Alternative
wàre das ganze Zeug mit | zu quoten, dann könnt Ihr kein cut&paste
mehr machen).



Oder Du nutzt pastie, einen anderen öffentlichen Pastebin oder machst
einen Gist auf github daraus.

Hier sind Test-Eingabedaten:
$ cat example.json
{
"defaults" : {
"use": "mh-servers",
"hostcheck": "ping",
"hostgroups": [ "mh-servers" ],
"parents": [ "internet" ]
},

"host": {
"torres.zugschlus.de": {
"use": "bla",
"services": [ "ping", "smtp", "ssh" ],
"service": [
{
"name": "ping",
"target": [ "85.214.131.164" ]
}
],
"alias": ""
},
"q.bofh.de": {
"services": [ "ping", "ssh", "smtp", "http" ],
"alias": ""
},
"twoaddresses.zugschlus.de": {
"services": [ "ping", "ssh" ],
"alias": ""
}
}
}

Mein Zielsystem ist Debian squeeze, ich habe also nicht die
allerneuste Software zur Verfügung.

Nun meine Fragen:

Ich habe immer Schwierigkeiten, die Trennstelle zwischen dem
"eigentlichen Code" und der Template zu finden. Die beiden Extreme
sind natürlich nicht sinnvoll, aber ob ich hier (eine
Template-Expansion pro Hostdefinition) den richtigen Kompromiss
gefunden habe, làsst mich zweifeln



Für diesen Anwendungsfall würde ich wohl ERB gar nicht verwenden. Ich
finde das deutlich einfacher:

puts %Q[
define host
use #{ hostdata["use"] }
host_name #{ hostdata["name"] }_#{ hostdata["thisip"]}
hostgroups #{ hostdata["groups"].join(', ')}
alias #{ hostdata["name"] } (#{ hostdata["thisip"] })
address #{ hostdata["thisip"] }
parents #{ hostdata["parents"] }
]

In den meisten ERB-Beispielen, die ich gefunden habe, wird eine
Extraklasse mit den Daten für die Template definiert. Ich vermute, in
so einem Setup kommt man in der Template ohne das stàndige
'hostdata["foo"]' aus, und kann stattdessen einfach<%= foo %>
schreiben. Aber: Wie muss mein Code dafür aussehen?



Du kannst es so machen:

Hostdata = Struct.new :host, :ip do
def bind
binding
end
end

hd = Hostdata.new "foo", "127.0.0.1"

erb = ERB.new "host = <%= host%>ip = <%= ip %>"
erb.result(hd.bind)

Aber, wie gesagt: ERB ist m.E. Overkill.

Geht meine Konstruktion zum Bau einer kommagetrennten Liste
|<% last=hostdata["groups"].shift %><%= last %><% hostdata["groups"].each do |group| %>,<%= group %><% end %>
auch etwas eleganter?



s.o.

Gibt es eine JSON-Library, deren "Pingeligkeit" man reduzieren kann?
In perl gibt es JON::XS->new->relaxed->decode, wo man Listen mit
Extrakommas abschließen kann, wo man Kommentare schreiben kann und
grundsàtzlich etwas laxer mit der Syntax sein kann, ohne dass man
direkt einen Parse Error um die Ohren geworfen bekommt. Gibt es sowas
auch für Ruby? Kann man die Ruby-JSON-Library irgendwie dazu bewegen,
etwas genauere Fehlermeldungen zu produzieren, und mir wenigstens zu
sagen, in welcher Zeile der Eingabe der 'parse error near {'
aufgetreten ist?



Weiß ich leider nicht. Ich würde aber sowieso eher mit dem strikten
Format arbeiten. Damit ist auf jeden Fall sichergestellt, dass das JSON
überall sauber verarbeitet wird. Wozu gibt es sonst Standards?

In vielem Ruby-Code gibt es Konstruktionen wie object[:index] - hat
der Doppelpunkt eine besondere Bewandtnis?



:index ist ein Symbol. Symbole sind jeweils nur ein Mal im Speicher,
egal wie oft Du sie referenzierst. Deshalb eignen sie sich gut für
feste Mengen von Schlüsseln.

Natürlich stehe ich auch stylistischen Kommentaren offen gegenüber,
ich habe noch nicht viel Gefühl für die Sprache.



Das kommt. Das Debuggen kann man auch so machen:

if $DEBUG
def debug
pp yield
end
else
def debug; end
end

Und dann

puts hostname.pretty_inspect if $DEBUG

->

debug { hostname }

Dann kannst Du in dem Block sogar komplizierte Berechnungen
unterbringen, die nur bei DEBUG auch tatsàchlich ausgeführt werden und
so die normale Performance nicht beeinflussen.

Vielen Dank für Eure Mühe.



Danichfür. Ich freue mich über jeden, der sich neu für Ruby entscheidet.

Grüße
Marc, hoffend, dass immer noch Leute mitlesen.



Ja, erstaunlich, nicht? ;-)

Viele Grüße

robert

remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Ähnliche fragen