Permalink
Browse files

white space update

  • Loading branch information...
1 parent c8ab75f commit b33deb2078d76ec09b311f28d7153a4369783174 @ripienaar committed Oct 26, 2012
View
4 agent/eximng/agent/eximng.ddl
@@ -2,10 +2,12 @@ metadata :name => "eximng",
:description => "SimpleRPC based Exim management agent",
:author => "R.I.Pienaar <rip@devco.net>",
:license => "ASL2",
- :version => "0.2",
+ :version => "0.7",
:url => "http://www.devco.net/",
:timeout => 30
+requires :mcollective => "2.1.1"
+
action "mailq", :description => "Retrieves the server mail queue" do
display :always
View
570 agent/eximng/agent/eximng.rb
@@ -1,353 +1,353 @@
module MCollective
- module Agent
- class Eximng<RPC::Agent
- def startup_hook
- @exim = config.pluginconf["exim.paths.exim"] || "/usr/sbin/exim"
- @mailq = config.pluginconf["exim.paths.mailq"] || "/usr/bin/mailq"
- @exiqsumm = config.pluginconf["exim.paths.exiqsumm"] || "/usr/sbin/exiqsumm"
- @exiwhat = config.pluginconf["exim.paths.exiwhat"] || "/usr/sbin/exiwhat"
- @exiqgrep = config.pluginconf["exim.paths.exigrep"] || "/usr/sbin/exiqgrep"
- @xargs = config.pluginconf["exim.paths.xargs"] || "/usr/bin/xargs"
- @spool = config.pluginconf["exim.paths.spool"] || "/var/spool/exim/input"
- @exigrep = config.pluginconf["exim.paths.exigrep"] || "/usr/sbin/exigrep"
- @mainlog = config.pluginconf["exim.paths.mainlog"] || "/var/log/exim/main.log"
- end
-
- action "mailq" do
- mailq = runcmd("#{@exiqgrep} #{exigrep_limiters.join ' '}")
- mailq = parse_mailq_output(mailq)
-
- reply[:mailq] = mailq
- reply[:size] = mailq.size
- reply[:frozen] = mailq.inject(0) {|i, q| q[:frozen] ? i += 1 : i }
- end
-
- action "size" do
- begin
- size.each_pair do |k,v|
- reply[k] = v
- end
- rescue Exception => e
- reply.fail "Failed to find the queue size, exiqgrep said: #{e}"
- end
- end
-
- action "summarytext" do
- reply[:summary] = summarytext
- end
+ module Agent
+ class Eximng<RPC::Agent
+ def startup_hook
+ @exim = config.pluginconf["exim.paths.exim"] || "/usr/sbin/exim"
+ @mailq = config.pluginconf["exim.paths.mailq"] || "/usr/bin/mailq"
+ @exiqsumm = config.pluginconf["exim.paths.exiqsumm"] || "/usr/sbin/exiqsumm"
+ @exiwhat = config.pluginconf["exim.paths.exiwhat"] || "/usr/sbin/exiwhat"
+ @exiqgrep = config.pluginconf["exim.paths.exigrep"] || "/usr/sbin/exiqgrep"
+ @xargs = config.pluginconf["exim.paths.xargs"] || "/usr/bin/xargs"
+ @spool = config.pluginconf["exim.paths.spool"] || "/var/spool/exim/input"
+ @exigrep = config.pluginconf["exim.paths.exigrep"] || "/usr/sbin/exigrep"
+ @mainlog = config.pluginconf["exim.paths.mainlog"] || "/var/log/exim/main.log"
+ end
+
+ action "mailq" do
+ mailq = runcmd("#{@exiqgrep} #{exigrep_limiters.join ' '}")
+ mailq = parse_mailq_output(mailq)
+
+ reply[:mailq] = mailq
+ reply[:size] = mailq.size
+ reply[:frozen] = mailq.inject(0) {|i, q| q[:frozen] ? i += 1 : i }
+ end
+
+ action "size" do
+ begin
+ size.each_pair do |k,v|
+ reply[k] = v
+ end
+ rescue Exception => e
+ reply.fail "Failed to find the queue size, exiqgrep said: #{e}"
+ end
+ end
- action "summary" do
- qsumm = summarytext
+ action "summarytext" do
+ reply[:summary] = summarytext
+ end
- reply[:summary] = []
+ action "summary" do
+ qsumm = summarytext
- qsumm.each do |q|
- domain = nil
+ reply[:summary] = []
- if q =~ /^\W+(\d+)\W+(\d+\w+)\W+(\w+)\W+(\w+)\W+(.+)$/
- domain = {}
+ qsumm.each do |q|
+ domain = nil
- domain[:count] = $1
- domain[:volume] = $2
- domain[:oldest] = $3
- domain[:newest] = $4
- domain[:domain] = $5
+ if q =~ /^\W+(\d+)\W+(\d+\w+)\W+(\w+)\W+(\w+)\W+(.+)$/
+ domain = {}
- reply[:summary] << domain unless $5 == "TOTAL" && $1 =~ /\d+/
- end
- end
- end
+ domain[:count] = $1
+ domain[:volume] = $2
+ domain[:oldest] = $3
+ domain[:newest] = $4
+ domain[:domain] = $5
- action "retrymsg" do
- validate :msgid, :shellsafe
+ reply[:summary] << domain unless $5 == "TOTAL" && $1 =~ /\d+/
+ end
+ end
+ end
- operate_on_msg(request[:msgid]) do |id|
- runcmd("#{@exim} -M #{id}", true, "mcollective exim agent retrying #{id}")
- reply[:status] = "Message #{id} has been retried"
- end
- end
+ action "retrymsg" do
+ validate :msgid, :shellsafe
- action "addrecipient" do
- validate :msgid, :shellsafe
- validate :recipient, :shellsafe
+ operate_on_msg(request[:msgid]) do |id|
+ runcmd("#{@exim} -M #{id}", true, "mcollective exim agent retrying #{id}")
+ reply[:status] = "Message #{id} has been retried"
+ end
+ end
- operate_on_msg(request[:msgid]) do |id|
- runcmd("#{@exim} -Mar #{id} '#{request[:recipient]}'")
- reply[:status] = "#{request[:recipient]} has been added to message #{id}"
- end
- end
+ action "addrecipient" do
+ validate :msgid, :shellsafe
+ validate :recipient, :shellsafe
- action "setsender" do
- validate :msgid, :shellsafe
- validate :sender, :shellsafe
+ operate_on_msg(request[:msgid]) do |id|
+ runcmd("#{@exim} -Mar #{id} '#{request[:recipient]}'")
+ reply[:status] = "#{request[:recipient]} has been added to message #{id}"
+ end
+ end
- operate_on_msg(request[:msgid]) do |id|
- runcmd("#{@exim} -Mes #{id} '#{request[:sender]}'")
- reply[:status] = "#{request[:sender]} has been set as the sender of message #{id}"
- end
- end
+ action "setsender" do
+ validate :msgid, :shellsafe
+ validate :sender, :shellsafe
- action "markdelivered" do
- validate :msgid, :shellsafe
-
- if request.include?(:recipient)
- validate :recipient, :shellsafe
- mode = :recipient
- else
- mode = :msgid
- end
-
- operate_on_msg(request[:msgid]) do |id|
- if mode == :recipient
- runcmd("#{@exim} -Mmd #{id} '#{request[:recipient]}'")
- reply[:status] = "Recipient #{request[:recipient]} on #{id} has been marked as delivered"
- else
- runcmd("#{@exim} -Mmad #{id}")
- reply[:status] = "#{id} has been marked as delivered"
- end
- end
- end
+ operate_on_msg(request[:msgid]) do |id|
+ runcmd("#{@exim} -Mes #{id} '#{request[:sender]}'")
+ reply[:status] = "#{request[:sender]} has been set as the sender of message #{id}"
+ end
+ end
- action "freeze" do
- validate :msgid, :shellsafe
+ action "markdelivered" do
+ validate :msgid, :shellsafe
- operate_on_msg(request[:msgid]) do |id|
- runcmd("#{@exim} -Mf #{id}")
- reply[:status] = "Message #{id} has been frozen"
- end
- end
+ if request.include?(:recipient)
+ validate :recipient, :shellsafe
+ mode = :recipient
+ else
+ mode = :msgid
+ end
- action "thaw" do
- validate :msgid, :shellsafe
+ operate_on_msg(request[:msgid]) do |id|
+ if mode == :recipient
+ runcmd("#{@exim} -Mmd #{id} '#{request[:recipient]}'")
+ reply[:status] = "Recipient #{request[:recipient]} on #{id} has been marked as delivered"
+ else
+ runcmd("#{@exim} -Mmad #{id}")
+ reply[:status] = "#{id} has been marked as delivered"
+ end
+ end
+ end
- operate_on_msg(request[:msgid]) do |id|
- runcmd("#{@exim} -Mt #{id}")
- reply[:status] = "Message #{id} has been unfrozen"
- end
- end
+ action "freeze" do
+ validate :msgid, :shellsafe
- action "giveup" do
- validate :msgid, :shellsafe
+ operate_on_msg(request[:msgid]) do |id|
+ runcmd("#{@exim} -Mf #{id}")
+ reply[:status] = "Message #{id} has been frozen"
+ end
+ end
- operate_on_msg(request[:msgid]) do |id|
- runcmd("#{@exim} -Mg #{id}")
- reply[:status] = "Message #{id} has been bounced"
- end
- end
+ action "thaw" do
+ validate :msgid, :shellsafe
- action "rm" do
- validate :msgid, :shellsafe
+ operate_on_msg(request[:msgid]) do |id|
+ runcmd("#{@exim} -Mt #{id}")
+ reply[:status] = "Message #{id} has been unfrozen"
+ end
+ end
- operate_on_msg(request[:msgid]) do |id|
- runcmd("#{@exim} -Mrm #{id}")
- reply[:status] = "Message #{id} has been removed"
- end
- end
+ action "giveup" do
+ validate :msgid, :shellsafe
- action "exiwhat" do
- reply[:exiwhat] = runcmd(@exiwhat)
- end
+ operate_on_msg(request[:msgid]) do |id|
+ runcmd("#{@exim} -Mg #{id}")
+ reply[:status] = "Message #{id} has been bounced"
+ end
+ end
- action "rmbounces" do
- rm(:bounce)
- end
+ action "rm" do
+ validate :msgid, :shellsafe
- action "rmfrozen" do
- rm(:frozen)
- end
+ operate_on_msg(request[:msgid]) do |id|
+ runcmd("#{@exim} -Mrm #{id}")
+ reply[:status] = "Message #{id} has been removed"
+ end
+ end
- action "runq" do
- runcmd("#{@exim} -v -q", true)
- reply[:status] = "Queue run has been requested"
- end
+ action "exiwhat" do
+ reply[:exiwhat] = runcmd(@exiwhat)
+ end
- action "delivermatching" do
- validate :pattern, :shellsafe
+ action "rmbounces" do
+ rm(:bounce)
+ end
- runcmd("#{@exim} -v -R '#{request[:pattern]}'", true)
- reply[:status] = "Delivery for pattern #{request[:pattern]} has been scheduled"
- end
+ action "rmfrozen" do
+ rm(:frozen)
+ end
- action "testaddress" do
- validate :address, :shellsafe
+ action "runq" do
+ runcmd("#{@exim} -v -q", true)
+ reply[:status] = "Queue run has been requested"
+ end
- reply[:routing_information] = runcmd("#{@exim} -bt '#{request[:address]}'")
- end
+ action "delivermatching" do
+ validate :pattern, :shellsafe
- action "exigrep" do
- validate :pattern, :shellsafe
+ runcmd("#{@exim} -v -R '#{request[:pattern]}'", true)
+ reply[:status] = "Delivery for pattern #{request[:pattern]} has been scheduled"
+ end
- matches = runcmd("#{@exigrep} '#{request[:pattern]}' #{@mainlog}")
+ action "testaddress" do
+ validate :address, :shellsafe
- reply.fail! "No lines matched #{request[:pattern]}" if matches.empty?
+ reply[:routing_information] = runcmd("#{@exim} -bt '#{request[:address]}'")
+ end
- reply[:matches] = matches
- end
+ action "exigrep" do
+ validate :pattern, :shellsafe
- private
- def rm(type)
- if type == :bounce
- exigreparg = "-f '<>'"
- elsif type == :frozen
- exigreparg = "-z"
- else
- raise "Don't know how to rm messages of type #{type}"
- end
-
- messages = runcmd("#{@exiqgrep} #{exigreparg} -c")
-
- if messages =~ /(\d+) matches out of (\d+) messages/
- messages = $1.to_i
-
- if messages > 0
- reply[:output] = runcmd("#{@exiqgrep} -i #{exigreparg}| #{@xargs} #{@exim} -Mrm")
- reply[:status] = "#{messages} messages deleted"
- reply[:count] = messages
- else
- reply.fail! "No #{type} messages found on this server"
- end
- else
- reply.fail! "Could not determine #{type} message count: #{messages}"
- end
- end
+ matches = runcmd("#{@exigrep} '#{request[:pattern]}' #{@mainlog}")
- def size
- totalsize = runcmd("#{@exiqgrep} #{exigrep_limiters.join ' '} -c")
- frozensize = runcmd("#{@exiqgrep} #{exigrep_limiters.join ' '} -c -z")
+ reply.fail! "No lines matched #{request[:pattern]}" if matches.empty?
- size = {:matched => 0, :total => 0, :frozen => 0}
+ reply[:matches] = matches
+ end
- if totalsize =~ /(\d+) matches out of (\d+) messages/
- size[:matched] = $1.to_i
- size[:total] = $2.to_i
- else
- raise "Failed to find the queue size, exiqgrep said: #{totalsize}"
- end
+ private
+ def rm(type)
+ if type == :bounce
+ exigreparg = "-f '<>'"
+ elsif type == :frozen
+ exigreparg = "-z"
+ else
+ raise "Don't know how to rm messages of type #{type}"
+ end
- if frozensize =~ /(\d+) matches out of (\d+) messages/
- size[:frozen] = $1.to_i
- end
+ messages = runcmd("#{@exiqgrep} #{exigreparg} -c")
- return size
- end
+ if messages =~ /(\d+) matches out of (\d+) messages/
+ messages = $1.to_i
- def operate_on_msg(msgid)
- if validid?(msgid) && hasmsg?(msgid)
- yield(msgid)
- end
- end
+ if messages > 0
+ reply[:output] = runcmd("#{@exiqgrep} -i #{exigreparg}| #{@xargs} #{@exim} -Mrm")
+ reply[:status] = "#{messages} messages deleted"
+ reply[:count] = messages
+ else
+ reply.fail! "No #{type} messages found on this server"
+ end
+ else
+ reply.fail! "Could not determine #{type} message count: #{messages}"
+ end
+ end
- def hasmsg?(id, shouldfail=true)
- unless File.exist?("#{@spool}/#{id}-D") || File.exist?("#{@spool}/#{id}-H")
- Log.debug("Could not find #{@spool}/#{id}-D or #{@spool}/#{id}-H")
- reply.fail! "No message matching #{id}" if shouldfail
- return false
- else
- return true
- end
- end
+ def size
+ totalsize = runcmd("#{@exiqgrep} #{exigrep_limiters.join ' '} -c")
+ frozensize = runcmd("#{@exiqgrep} #{exigrep_limiters.join ' '} -c -z")
- def validid?(msgid, shouldfail=true)
- return true if msgid == ""
+ size = {:matched => 0, :total => 0, :frozen => 0}
- if shouldfail
- reply.fail! "Did not receive a valid message id" unless msgid =~ /^\w+-\w+-\w+$/
- else
- return false
- end
+ if totalsize =~ /(\d+) matches out of (\d+) messages/
+ size[:matched] = $1.to_i
+ size[:total] = $2.to_i
+ else
+ raise "Failed to find the queue size, exiqgrep said: #{totalsize}"
+ end
- return true
- end
+ if frozensize =~ /(\d+) matches out of (\d+) messages/
+ size[:frozen] = $1.to_i
+ end
- def summarytext
- runcmd("#{@mailq} 2>&1 | #{@exiqsumm}")
- end
+ return size
+ end
- # parses requests for exigrep common limiters and returns an array of exigrep arguments
- def exigrep_limiters
- args = []
+ def operate_on_msg(msgid)
+ if validid?(msgid) && hasmsg?(msgid)
+ yield(msgid)
+ end
+ end
+
+ def hasmsg?(id, shouldfail=true)
+ unless File.exist?("#{@spool}/#{id}-D") || File.exist?("#{@spool}/#{id}-H")
+ Log.debug("Could not find #{@spool}/#{id}-D or #{@spool}/#{id}-H")
+ reply.fail! "No message matching #{id}" if shouldfail
+ return false
+ else
+ return true
+ end
+ end
- get_request(:limit_sender, :shellsafe) {|val| args << "-f #{val}"}
- get_request(:limit_recipient, :shellsafe) {|val| args << "-r #{val}"}
- get_request(:limit_younger_than, :shellsafe) {|val| args << "-y #{val}"}
- get_request(:limit_older_than, :shellsafe) {|val| args << "-o #{val}"}
- get_request(:limit_frozen_only, :boolean) {|val| args << "-z"}
- get_request(:limit_unfrozen_only, :boolean) {|val| args << "-x"}
+ def validid?(msgid, shouldfail=true)
+ return true if msgid == ""
- args
- end
+ if shouldfail
+ reply.fail! "Did not receive a valid message id" unless msgid =~ /^\w+-\w+-\w+$/
+ else
+ return false
+ end
- # runs a command and returns the output, fails if exit code is non zero
- def runcmd(command, background = false, psname = nil)
- Log.debug("Running #{command}: background: #{background}")
+ return true
+ end
- unless background
- out = ""
- err = ""
+ def summarytext
+ runcmd("#{@mailq} 2>&1 | #{@exiqsumm}")
+ end
- status = run(command, :stdout => out, :stderr => err, :chomp => true)
+ # parses requests for exigrep common limiters and returns an array of exigrep arguments
+ def exigrep_limiters
+ args = []
- reply.fail!("Command #{command} failed with status #{status} and error: #{err}") unless status == 0
+ get_request(:limit_sender, :shellsafe) {|val| args << "-f #{val}"}
+ get_request(:limit_recipient, :shellsafe) {|val| args << "-r #{val}"}
+ get_request(:limit_younger_than, :shellsafe) {|val| args << "-y #{val}"}
+ get_request(:limit_older_than, :shellsafe) {|val| args << "-o #{val}"}
+ get_request(:limit_frozen_only, :boolean) {|val| args << "-z"}
+ get_request(:limit_unfrozen_only, :boolean) {|val| args << "-x"}
- return out
- else
- pid = fork do
- $0 = psname if psname
+ args
+ end
- ::Process.setsid
- run(command)
- end
+ # runs a command and returns the output, fails if exit code is non zero
+ def runcmd(command, background = false, psname = nil)
+ Log.debug("Running #{command}: background: #{background}")
- ::Process.detach(pid)
- end
- end
+ unless background
+ out = ""
+ err = ""
- def parse_mailq_output(output)
- messages = []
- msg = nil
+ status = run(command, :stdout => out, :stderr => err, :chomp => true)
- output << "\n"
+ reply.fail!("Command #{command} failed with status #{status} and error: #{err}") unless status == 0
- output.each do |line|
- line.chomp!
+ return out
+ else
+ pid = fork do
+ $0 = psname if psname
- if line =~ /^\s*(.+?)\s+(.+?)\s+(.+-.+-.+) (<.*>)/
- msg = {}
- msg[:recipients] = Array.new
- msg[:frozen] = false
+ ::Process.setsid
+ run(command)
+ end
- msg[:age] = $1
- msg[:size] = $2
- msg[:msgid] = $3
- msg[:sender] = $4
+ ::Process.detach(pid)
+ end
+ end
+
+ def parse_mailq_output(output)
+ messages = []
+ msg = nil
+
+ output << "\n"
+
+ output.each do |line|
+ line.chomp!
+
+ if line =~ /^\s*(.+?)\s+(.+?)\s+(.+-.+-.+) (<.*>)/
+ msg = {}
+ msg[:recipients] = Array.new
+ msg[:frozen] = false
+
+ msg[:age] = $1
+ msg[:size] = $2
+ msg[:msgid] = $3
+ msg[:sender] = $4
+
+ msg[:frozen] = true if line =~ /frozen/
+ elsif line =~ /\s+(\S+?)@(.+)/ and msg
+ msg[:recipients] << "#{$1}@#{$2}"
+ elsif line =~ /^$/ && msg
+ messages << msg
+ msg = nil
+ end
+ end
- msg[:frozen] = true if line =~ /frozen/
- elsif line =~ /\s+(\S+?)@(.+)/ and msg
- msg[:recipients] << "#{$1}@#{$2}"
- elsif line =~ /^$/ && msg
- messages << msg
- msg = nil
- end
- end
+ messages
+ end
- messages
+ # takes a block, pass the value of the key to the block if its present
+ # and passes validation
+ def get_request(key, validation)
+ if request.include?(key)
+ if validation == :boolean
+ unless [TrueClass, FalseClass].include?(request[key].class)
+ raise "#{key} should be boolean"
end
+ else
+ validate key, validation
+ end
- # takes a block, pass the value of the key to the block if its present
- # and passes validation
- def get_request(key, validation)
- if request.include?(key)
- if validation == :boolean
- unless [TrueClass, FalseClass].include?(request[key].class)
- raise "#{key} should be boolean"
- end
- else
- validate key, validation
- end
-
- yield(request[key])
- end
- end
+ yield(request[key])
end
+ end
end
+ end
end
View
244 agent/eximng/application/exim.rb
@@ -1,146 +1,146 @@
class MCollective::Application::Exim<MCollective::Application
- description "MCollective Exim Manager"
- usage "mco exim [mailq|size|summary|exiwhat|rmbounces|rmfrozen|runq]"
- usage "mco exim runq <pattern>"
- usage "mco exim [retry|markdelivered|freeze|thaw|giveup|rm] <message id>"
- usage "mco exim [addrecipient|markdelivered] <message id> <recipient>"
- usage "mco exim setsender <message id> <sender>"
- usage "mco exim <message matchers> [mailq|size]"
- usage "mco exim test <address>"
- usage "mco exim exigrep pattern"
-
- VALID_COMMANDS = ["mailq", "size", "summary", "exiwhat", "rmbounces", "rmfrozen", "runq", "addrecipient", "markdelivered", "setsender", "retry", "freeze", "thaw", "giveup", "rm", "delivermatching", "exigrep", "test"]
- MSGID_REQ_COMMANDS = ["setsender", "retry", "markdelivered", "freeze", "thaw", "giveup", "rm"]
- RECIP_OPT_COMMANDS = ["markdelivered"]
- RECIP_REQ_COMMANDS = ["addrecipient"]
-
- option :limit_sender,
- :description => "Match sender pattern",
- :arguments => ["--match-sender SENDER", "--limit-sender"],
- :required => false
-
- option :limit_recipient,
- :description => "Match recipient pattern",
- :arguments => ["--match-recipient RECIPIENT", "--limit-recipient"],
- :required => false
-
- option :limit_younger_than,
- :description => "Match younger than seconds",
- :arguments => ["--match-younger SECONDS", "--limit-younger"],
- :required => false
-
- option :limit_older_than,
- :description => "Match older than seconds",
- :arguments => ["--match-older SECONDS", "--limit-older"],
- :required => false
-
- option :limit_frozen_only,
- :description => "Match only frozen messages",
- :arguments => ["--match-frozen", "--limit-frozen"],
- :type => :bool,
- :required => false
-
- option :limit_unfrozen_only,
- :description => "Match only active messages",
- :arguments => ["--match-active", "--limit-active"],
- :type => :bool,
- :required => false
-
- def post_option_parser(configuration)
- configuration[:command] = ARGV.shift if ARGV.size > 0
-
- if MSGID_REQ_COMMANDS.include?(configuration[:command])
- if ARGV.size > 0
- configuration[:message_id] = ARGV[0]
- else
- raise "#{configuration[:command]} requires a message id"
- end
- end
-
- if RECIP_REQ_COMMANDS.include?(configuration[:command])
- if ARGV.size == 2
- configuration[:message_id] = ARGV[0]
- configuration[:recipient] = ARGV[1]
- else
- raise "#{configuration[:command]} requires a message id and recipient"
- end
- end
-
- if RECIP_OPT_COMMANDS.include?(configuration[:command])
- if ARGV.size == 2
- configuration[:recipient] = ARGV[1]
- end
- end
+ description "MCollective Exim Manager"
+ usage "mco exim [mailq|size|summary|exiwhat|rmbounces|rmfrozen|runq]"
+ usage "mco exim runq <pattern>"
+ usage "mco exim [retry|markdelivered|freeze|thaw|giveup|rm] <message id>"
+ usage "mco exim [addrecipient|markdelivered] <message id> <recipient>"
+ usage "mco exim setsender <message id> <sender>"
+ usage "mco exim <message matchers> [mailq|size]"
+ usage "mco exim test <address>"
+ usage "mco exim exigrep pattern"
+
+ VALID_COMMANDS = ["mailq", "size", "summary", "exiwhat", "rmbounces", "rmfrozen", "runq", "addrecipient", "markdelivered", "setsender", "retry", "freeze", "thaw", "giveup", "rm", "delivermatching", "exigrep", "test"]
+ MSGID_REQ_COMMANDS = ["setsender", "retry", "markdelivered", "freeze", "thaw", "giveup", "rm"]
+ RECIP_OPT_COMMANDS = ["markdelivered"]
+ RECIP_REQ_COMMANDS = ["addrecipient"]
+
+ option :limit_sender,
+ :description => "Match sender pattern",
+ :arguments => ["--match-sender SENDER", "--limit-sender"],
+ :required => false
+
+ option :limit_recipient,
+ :description => "Match recipient pattern",
+ :arguments => ["--match-recipient RECIPIENT", "--limit-recipient"],
+ :required => false
+
+ option :limit_younger_than,
+ :description => "Match younger than seconds",
+ :arguments => ["--match-younger SECONDS", "--limit-younger"],
+ :required => false
+
+ option :limit_older_than,
+ :description => "Match older than seconds",
+ :arguments => ["--match-older SECONDS", "--limit-older"],
+ :required => false
+
+ option :limit_frozen_only,
+ :description => "Match only frozen messages",
+ :arguments => ["--match-frozen", "--limit-frozen"],
+ :type => :bool,
+ :required => false
+
+ option :limit_unfrozen_only,
+ :description => "Match only active messages",
+ :arguments => ["--match-active", "--limit-active"],
+ :type => :bool,
+ :required => false
+
+ def post_option_parser(configuration)
+ configuration[:command] = ARGV.shift if ARGV.size > 0
+
+ if MSGID_REQ_COMMANDS.include?(configuration[:command])
+ if ARGV.size > 0
+ configuration[:message_id] = ARGV[0]
+ else
+ raise "#{configuration[:command]} requires a message id"
+ end
end
- def validate_configuration(configuration)
- raise "Please specify a command, see --help for details" unless configuration[:command]
-
- raise "Unknown command #{configuration[:command]}, see --help for full help" unless VALID_COMMANDS.include?(configuration[:command])
+ if RECIP_REQ_COMMANDS.include?(configuration[:command])
+ if ARGV.size == 2
+ configuration[:message_id] = ARGV[0]
+ configuration[:recipient] = ARGV[1]
+ else
+ raise "#{configuration[:command]} requires a message id and recipient"
+ end
+ end
- if configuration.include?(:message_id)
- raise "Invalid message id format for id #{configuration[:message_id]}" unless configuration[:message_id] =~ /^\w+-\w+-\w+$/
- end
+ if RECIP_OPT_COMMANDS.include?(configuration[:command])
+ if ARGV.size == 2
+ configuration[:recipient] = ARGV[1]
+ end
end
+ end
- def exigrep_command(util)
- if ARGV.empty?
- raise("The exigrep command requires a pattern")
- else
- configuration[:pattern] = ARGV.first
- end
+ def validate_configuration(configuration)
+ raise "Please specify a command, see --help for details" unless configuration[:command]
- puts util.exigrep(configuration)
- end
+ raise "Unknown command #{configuration[:command]}, see --help for full help" unless VALID_COMMANDS.include?(configuration[:command])
- def test_command(util)
- if ARGV.empty?
- raise("The test command requires an address")
- else
- configuration[:address] = ARGV.first
- end
+ if configuration.include?(:message_id)
+ raise "Invalid message id format for id #{configuration[:message_id]}" unless configuration[:message_id] =~ /^\w+-\w+-\w+$/
+ end
+ end
- puts util.test(configuration)
+ def exigrep_command(util)
+ if ARGV.empty?
+ raise("The exigrep command requires a pattern")
+ else
+ configuration[:pattern] = ARGV.first
end
- def runq_command(util)
- unless ARGV.empty?
- configuration[:pattern] = ARGV.first
- end
+ puts util.exigrep(configuration)
+ end
- puts util.runq(configuration)
+ def test_command(util)
+ if ARGV.empty?
+ raise("The test command requires an address")
+ else
+ configuration[:address] = ARGV.first
end
- def setsender_command(util)
- if ARGV.size == 2
- configuration[:sender] = ARGV.first
- else
- raise "Please supply a sender"
- end
+ puts util.test(configuration)
+ end
- puts util.setsender(configuration)
+ def runq_command(util)
+ unless ARGV.empty?
+ configuration[:pattern] = ARGV.first
end
- def main
- MCollective::Util.loadclass("MCollective::Util::EximNG")
+ puts util.runq(configuration)
+ end
- mc = rpcclient("eximng", :options => options)
- util = MCollective::Util::EximNG.new(mc)
+ def setsender_command(util)
+ if ARGV.size == 2
+ configuration[:sender] = ARGV.first
+ else
+ raise "Please supply a sender"
+ end
+
+ puts util.setsender(configuration)
+ end
- cmd = "#{configuration[:command]}_command"
+ def main
+ MCollective::Util.loadclass("MCollective::Util::EximNG")
- # if there are local foo_command methods, use that to
- # render foo, else use M::U::EximNG#foo else fail
- if respond_to?(cmd)
- send(cmd, util)
- elsif util.respond_to?(configuration[:command])
- puts util.send(configuration[:command], configuration)
- else
- raise "Support for #{configuration[:command]} has not yet been implimented"
- end
+ mc = rpcclient("eximng", :options => options)
+ util = MCollective::Util::EximNG.new(mc)
- puts
+ cmd = "#{configuration[:command]}_command"
- mc.disconnect
+ # if there are local foo_command methods, use that to
+ # render foo, else use M::U::EximNG#foo else fail
+ if respond_to?(cmd)
+ send(cmd, util)
+ elsif util.respond_to?(configuration[:command])
+ puts util.send(configuration[:command], configuration)
+ else
+ raise "Support for #{configuration[:command]} has not yet been implimented"
end
+
+ puts
+
+ mc.disconnect
+ end
end
View
572 agent/eximng/util/eximng.rb
@@ -1,361 +1,361 @@
module MCollective
- module Util
- # a helper class for the application plugin and cli only, this would
- # probably be very awkward to use for anything past that as its designed
- # to assist those 2 apps only, everyting returns text for display to the
- # user no data etc.
- #
- # To get access to the RPC supplied data, use the RPC api direct.
- class EximNG
- attr_reader :mc
-
- def initialize(mc)
- @mc = mc
- end
-
- # rm(:message_id => "xyz")
- def rm(args)
- requires(args, :message_id)
+ module Util
+ # a helper class for the application plugin and cli only, this would
+ # probably be very awkward to use for anything past that as its designed
+ # to assist those 2 apps only, everyting returns text for display to the
+ # user no data etc.
+ #
+ # To get access to the RPC supplied data, use the RPC api direct.
+ class EximNG
+ attr_reader :mc
+
+ def initialize(mc)
+ @mc = mc
+ end
+
+ # rm(:message_id => "xyz")
+ def rm(args)
+ requires(args, :message_id)
+
+ std_msgid_action(:rm, args[:message_id])
+ end
+
+ # giveup(:message_id => "xyz")
+ def giveup(args)
+ requires(args, :message_id)
+
+ std_msgid_action(:giveup, args[:message_id])
+ end
+
+ # thaw(:message_id => "xyz")
+ def thaw(args)
+ requires(args, :message_id)
+
+ std_msgid_action(:thaw, args[:message_id])
+ end
+
+ # freeze(:message_id => "xyz")
+ def freeze(args)
+ requires(args, :message_id)
+
+ mc.instance_eval { undef :freeze }
+
+ std_msgid_action(:freeze, args[:message_id])
+ end
+
+ # markdelivered(:message_id => "xyz")
+ # markdelivered(:message_id => "xyz", :recipient => "foo@foo.com")
+ def markdelivered(args)
+ requires(args, :message_id)
+
+ if args.include?(:recipient)
+ std_recipient_action(:markdelivered, args[:message_id], args[:recipient])
+ else
+ std_msgid_action(:markdelivered, args[:message_id])
+ end
+ end
- std_msgid_action(:rm, args[:message_id])
- end
+ # retry(:message_id => "xyz")
+ def retry(args)
+ requires(args, :message_id)
- # giveup(:message_id => "xyz")
- def giveup(args)
- requires(args, :message_id)
+ std_msgid_action(:retrymsg, args[:message_id])
+ end
- std_msgid_action(:giveup, args[:message_id])
- end
+ # addrecipient(:message_id => "xyz", :recipient => "foo@foo.com")
+ def addrecipient(args)
+ requires(args, :message_id)
+ requires(args, :recipient)
- # thaw(:message_id => "xyz")
- def thaw(args)
- requires(args, :message_id)
+ std_recipient_action(:addrecipient, args[:message_id], args[:recipient])
+ end
- std_msgid_action(:thaw, args[:message_id])
- end
+ # setsender(:message_id => "xyz", :sender => "foo@foo.com")
+ def setsender(args)
+ requires(args, :message_id)
+ requires(args, :sender)
- # freeze(:message_id => "xyz")
- def freeze(args)
- requires(args, :message_id)
+ result = StringIO.new
- mc.instance_eval { undef :freeze }
+ mc.setsender(:msgid => args[:message_id], :sender => args[:sender]).each do |r|
+ if r[:statuscode] == 0
+ msg = r[:data][:status]
+ else
+ msg = r[:statusmsg]
+ end
- std_msgid_action(:freeze, args[:message_id])
- end
+ result.puts "%30s: %s" % [r[:sender], msg]
+ end
- # markdelivered(:message_id => "xyz")
- # markdelivered(:message_id => "xyz", :recipient => "foo@foo.com")
- def markdelivered(args)
- requires(args, :message_id)
+ result.string
+ end
- if args.include?(:recipient)
- std_recipient_action(:markdelivered, args[:message_id], args[:recipient])
- else
- std_msgid_action(:markdelivered, args[:message_id])
- end
- end
+ # runq()
+ # runq(:pattern => "foo.com")
+ def runq(args={})
+ result = StringIO.new
- # retry(:message_id => "xyz")
- def retry(args)
- requires(args, :message_id)
+ if args.include?(:pattern)
+ mcresult = mc.delivermatching(:pattern => args[:pattern])
+ else
+ mcresult = mc.runq
+ end
- std_msgid_action(:retrymsg, args[:message_id])
- end
+ mcresult.each do |r|
+ if r[:statuscode] == 0
+ msg = r[:data][:status]
+ else
+ msg = r[:statusmsg]
+ end
- # addrecipient(:message_id => "xyz", :recipient => "foo@foo.com")
- def addrecipient(args)
- requires(args, :message_id)
- requires(args, :recipient)
+ result.puts "%30s: %s" % [r[:sender], msg]
+ end
- std_recipient_action(:addrecipient, args[:message_id], args[:recipient])
- end
+ return result.string
+ end
- # setsender(:message_id => "xyz", :sender => "foo@foo.com")
- def setsender(args)
- requires(args, :message_id)
- requires(args, :sender)
+ # exigrep(:pattern => "foo")
+ def exigrep(args)
+ requires(args, :pattern)
- result = StringIO.new
+ MCollective::RPC::Helpers.rpcresults(mc.exigrep(:pattern => args[:pattern]), {:format => :console})
+ end
- mc.setsender(:msgid => args[:message_id], :sender => args[:sender]).each do |r|
- if r[:statuscode] == 0
- msg = r[:data][:status]
- else
- msg = r[:statusmsg]
- end
+ # test(:recipient => "foo@foo.com")
+ def test(args)
+ requires(args, :address)
- result.puts "%30s: %s" % [r[:sender], msg]
- end
+ MCollective::RPC::Helpers.rpcresults(mc.testaddress(:address => args[:address]), {:format => :console})
+ end
- result.string
- end
+ def rmfrozen(args={})
+ result = StringIO.new
- # runq()
- # runq(:pattern => "foo.com")
- def runq(args={})
- result = StringIO.new
+ mc.rmfrozen.each do |r|
+ if r[:statuscode] == 0
+ msg = r[:data][:status]
+ else
+ msg = "No frozen messages found"
+ end
- if args.include?(:pattern)
- mcresult = mc.delivermatching(:pattern => args[:pattern])
- else
- mcresult = mc.runq
- end
+ result.puts "%30s: %s" % [r[:sender], msg]
+ end
- mcresult.each do |r|
- if r[:statuscode] == 0
- msg = r[:data][:status]
- else
- msg = r[:statusmsg]
- end
+ return result.string
+ end
- result.puts "%30s: %s" % [r[:sender], msg]
- end
+ def rmbounces(args={})
+ result = StringIO.new
- return result.string
- end
+ mc.rmbounces.each do |r|
+ if r[:statuscode] == 0
+ msg = r[:data][:status]
+ else
+ msg = "No bounce messages found"
+ end
- # exigrep(:pattern => "foo")
- def exigrep(args)
- requires(args, :pattern)
+ result.puts "%30s: %s" % [r[:sender], msg]
+ end
- MCollective::RPC::Helpers.rpcresults(mc.exigrep(:pattern => args[:pattern]), {:format => :console})
- end
+ return result.string
+ end
- # test(:recipient => "foo@foo.com")
- def test(args)
- requires(args, :address)
+ def exiwhat(args={})
+ failed = []
+ result = StringIO.new
- MCollective::RPC::Helpers.rpcresults(mc.testaddress(:address => args[:address]), {:format => :console})
+ mc.exiwhat.each do |r|
+ begin
+ result.puts r[:sender]
+ r[:data][:exiwhat].each do |line|
+ result.puts "\t#{line}"
end
+ rescue
+ failed << r
+ end
- def rmfrozen(args={})
- result = StringIO.new
-
- mc.rmfrozen.each do |r|
- if r[:statuscode] == 0
- msg = r[:data][:status]
- else
- msg = "No frozen messages found"
- end
-
- result.puts "%30s: %s" % [r[:sender], msg]
- end
-
- return result.string
- end
+ result.puts
+ end
- def rmbounces(args={})
- result = StringIO.new
+ result.print failed_report(failed)
- mc.rmbounces.each do |r|
- if r[:statuscode] == 0
- msg = r[:data][:status]
- else
- msg = "No bounce messages found"
- end
+ return result.string
+ end
- result.puts "%30s: %s" % [r[:sender], msg]
- end
+ # size(:limit_sender => "foo@foo.com")
+ # size(:limit_recipient => "foo@foo.com")
+ # size(:limit_younger_than => 120)
+ # size(:limit_older_than => 120)
+ # size(:limit_frozen_only => true)
+ # size(:limit_unfrozen_only => true)
+ def size(args={})
+ result = StringIO.new
- return result.string
- end
+ stats = {:failed => [], :hosts => {}}
- def exiwhat(args={})
- failed = []
- result = StringIO.new
+ mc.size(args).each do |s|
+ if s[:statuscode] == 0
+ stats[:hosts][ s[:sender] ] = {:total => s[:data][:total], :matched => s[:data][:matched], :frozen => s[:data][:frozen]}
+ else
+ stats[:failed] << s
+ end
+ end
- mc.exiwhat.each do |r|
- begin
- result.puts r[:sender]
- r[:data][:exiwhat].each do |line|
- result.puts "\t#{line}"
- end
- rescue
- failed << r
- end
+ hosts = stats[:hosts]
+ total = 0; frozen = 0; matched = 0
- result.puts
- end
+ hosts.sort_by{|host| host[1][:total].to_i}.each do |host|
+ result.puts "%30s: total mail: %-5d matched mail: %-5d frozen mail: %-5d" % [host[0], host[1][:total], host[1][:matched], host[1][:frozen]]
- result.print failed_report(failed)
+ total += host[1][:total].to_i
+ matched += host[1][:matched].to_i
+ frozen += host[1][:frozen].to_i
+ end
- return result.string
- end
+ result.puts "%45s%-5s%16s%-5s%15s%-5s" % [" ", "-" * total.to_s.size, " ", "-" * matched.to_s.size, " ", "-" * frozen.to_s.size]
+ result.puts "%45s%-5d%16s%-5d%15s%-5d" % [" ", total, " ", matched, " ", frozen]
- # size(:limit_sender => "foo@foo.com")
- # size(:limit_recipient => "foo@foo.com")
- # size(:limit_younger_than => 120)
- # size(:limit_older_than => 120)
- # size(:limit_frozen_only => true)
- # size(:limit_unfrozen_only => true)
- def size(args={})
- result = StringIO.new
+ result.print failed_report(stats[:failed])
- stats = {:failed => [], :hosts => {}}
+ return result.string
+ end
- mc.size(args).each do |s|
- if s[:statuscode] == 0
- stats[:hosts][ s[:sender] ] = {:total => s[:data][:total], :matched => s[:data][:matched], :frozen => s[:data][:frozen]}
- else
- stats[:failed] << s
- end
- end
+ # mailq(:limit_sender => "foo@foo.com")
+ # mailq(:limit_recipient => "foo@foo.com")
+ # mailq(:limit_younger_than => 120)
+ # mailq(:limit_older_than => 120)
+ # mailq(:limit_frozen_only => true)
+ # mailq(:limit_unfrozen_only => true)
+ def mailq(args={})
+ failed = []
+ result = StringIO.new
- hosts = stats[:hosts]
- total = 0; frozen = 0; matched = 0
+ mc.progress = false
- hosts.sort_by{|host| host[1][:total].to_i}.each do |host|
- result.puts "%30s: total mail: %-5d matched mail: %-5d frozen mail: %-5d" % [host[0], host[1][:total], host[1][:matched], host[1][:frozen]]
+ mc.mailq(args) do |r, s|
+ begin
+ s[:data][:mailq].each do |message|
+ result.puts exim_like_mailq(message)
+ end
+ rescue
+ failed << s
+ end
+ end
- total += host[1][:total].to_i
- matched += host[1][:matched].to_i
- frozen += host[1][:frozen].to_i
- end
+ result.print failed_report(failed)
- result.puts "%45s%-5s%16s%-5s%15s%-5s" % [" ", "-" * total.to_s.size, " ", "-" * matched.to_s.size, " ", "-" * frozen.to_s.size]
- result.puts "%45s%-5d%16s%-5d%15s%-5d" % [" ", total, " ", matched, " ", frozen]
+ return result.string
+ end
- result.print failed_report(stats[:failed])
+ # summary(:limit_sender => "foo@foo.com")
+ # summary(:limit_recipient => "foo@foo.com")
+ # summary(:limit_younger_than => 120)
+ # summary(:limit_older_than => 120)
+ # summary(:limit_frozen_only => true)
+ # summary(:limit_unfrozen_only => true)
+ def summary(args={})
+ failed = []
+ result = StringIO.new
+ text = ""
- return result.string
- end
+ mc.progress = false
- # mailq(:limit_sender => "foo@foo.com")
- # mailq(:limit_recipient => "foo@foo.com")
- # mailq(:limit_younger_than => 120)
- # mailq(:limit_older_than => 120)
- # mailq(:limit_frozen_only => true)
- # mailq(:limit_unfrozen_only => true)
- def mailq(args={})
- failed = []
- result = StringIO.new
-
- mc.progress = false
-
- mc.mailq(args) do |r, s|
- begin
- s[:data][:mailq].each do |message|
- result.puts exim_like_mailq(message)
- end
- rescue
- failed << s
- end
- end
-
- result.print failed_report(failed)
-
- return result.string
+ mc.mailq(args) do |r, s|
+ begin
+ s[:data][:mailq].each do |message|
+ text += exim_like_mailq(message)
end
+ rescue
+ failed << s
+ end
+ end
- # summary(:limit_sender => "foo@foo.com")
- # summary(:limit_recipient => "foo@foo.com")
- # summary(:limit_younger_than => 120)
- # summary(:limit_older_than => 120)
- # summary(:limit_frozen_only => true)
- # summary(:limit_unfrozen_only => true)
- def summary(args={})
- failed = []
- result = StringIO.new
- text = ""
-
- mc.progress = false
-
- mc.mailq(args) do |r, s|
- begin
- s[:data][:mailq].each do |message|
- text += exim_like_mailq(message)
- end
- rescue
- failed << s
- end
- end
-
- IO.popen("exiqsumm", "r+") do |qsumm|
- qsumm.puts text
- qsumm.close_write
- result.puts qsumm.read
- end
-
- result.print failed_report(failed)
-
- return result.string
- end
+ IO.popen("exiqsumm", "r+") do |qsumm|
+ qsumm.puts text
+ qsumm.close_write
+ result.puts qsumm.read
+ end
- private
- # does the hard work for any action that takes just an id and returns a :status
- def std_msgid_action(action, msgid)
- result = StringIO.new
+ result.print failed_report(failed)
- mc.send(action, :msgid => msgid).each do |r|
- if r[:statuscode] == 0
- msg = r[:data][:status]
- else
- msg = r[:statusmsg]
- end
+ return result.string
+ end
- result.puts "%30s: %s" % [r[:sender], msg]
- end
+ private
+ # does the hard work for any action that takes just an id and returns a :status
+ def std_msgid_action(action, msgid)
+ result = StringIO.new
- return result.string
- end
+ mc.send(action, :msgid => msgid).each do |r|
+ if r[:statuscode] == 0
+ msg = r[:data][:status]
+ else
+ msg = r[:statusmsg]
+ end
- def std_recipient_action(action, msgid, recipient)
- result = StringIO.new
+ result.puts "%30s: %s" % [r[:sender], msg]
+ end
- if recipient
- results = mc.send(action, :msgid => msgid, :recipient => recipient)
- else
- results = mc.send(action, :msgid => msgid)
- end
+ return result.string
+ end
- results.each do |r|
- if r[:statuscode] == 0
- msg = r[:data][:status]
- else
- msg = r[:statusmsg]
- end
+ def std_recipient_action(action, msgid, recipient)
+ result = StringIO.new
- result.puts "%30s: %s" % [r[:sender], msg]
- end
+ if recipient
+ results = mc.send(action, :msgid => msgid, :recipient => recipient)
+ else
+ results = mc.send(action, :msgid => msgid)
+ end
- return result.string
- end
+ results.each do |r|
+ if r[:statuscode] == 0
+ msg = r[:data][:status]
+ else
+ msg = r[:statusmsg]
+ end
- # Takes a message structure as returned by mailq and turns it into exim like text
- def exim_like_mailq(message)
- result = StringIO.new
+ result.puts "%30s: %s" % [r[:sender], msg]
+ end
- message[:frozen] ? frozen = "*** frozen ***" : frozen = ""
+ return result.string
+ end
- result.puts "%3s%6s %s %s %s" % [ message[:age], message[:size], message[:msgid], message[:sender], frozen ]
+ # Takes a message structure as returned by mailq and turns it into exim like text
+ def exim_like_mailq(message)
+ result = StringIO.new
- message[:recipients].each do |recipient|
- result.puts " #{recipient}"
- end
+ message[:frozen] ? frozen = "*** frozen ***" : frozen = ""
- result.puts
+ result.puts "%3s%6s %s %s %s" % [ message[:age], message[:size], message[:msgid], message[:sender], frozen ]
- return result.string
- end
+ message[:recipients].each do |recipient|
+ result.puts " #{recipient}"
+ end
- def failed_report(failed)
- result = StringIO.new
+ result.puts
- unless failed.empty?
- result.puts
- result.puts "Failed to get data from some hosts:"
+ return result.string
+ end
- failed.each do |host|
- result.puts "%30s: %s" % [ host[:sender], host[:statusmsg] ]
- end
- end
+ def failed_report(failed)
+ result = StringIO.new
- return result.string
- end
+ unless failed.empty?
+ result.puts
+ result.puts "Failed to get data from some hosts:"
- def requires(haystack, needle)
- raise("#{needle} is required") unless haystack.include?(needle)
- end
+ failed.each do |host|
+ result.puts "%30s: %s" % [ host[:sender], host[:statusmsg] ]
+ end
end
+
+ return result.string
+ end
+
+ def requires(haystack, needle)
+ raise("#{needle} is required") unless haystack.include?(needle)
+ end
end
+ end
end
View
11 agent/eximng/validator/exim_msgid_validator.rb
@@ -1,15 +1,10 @@
module MCollective
module Validator
class Exim_msgidValidator
- def initialize(key, validator)
- @validator = validator
- @key = key
- end
-
- def validate
- raise DDLValidationError, "#{@key} should be a String" unless @validator.is_a?(String)
+ def self.validate(msgid)
+ Validator.typecheck(msgid, :string)
- raise(DDLValidationError, "%s should be a valid Exim Message ID" % @key) unless @validator.match(/(?:[+-]\d{4} )?(?:\[\d+\] )?(\w{6}\-\w{6}\-\w{2})/)
+ raise "Not a valid Exim Message ID" unless msgid.match(/(?:[+-]\d{4} )?(?:\[\d+\] )?(\w{6}\-\w{6}\-\w{2})/)
end
end
end

0 comments on commit b33deb2

Please sign in to comment.