aboutsummaryrefslogtreecommitdiff
path: root/bin/mauveclient
blob: b51d6877f4abfb2c4fe27e7436956d7abc96a3db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#! /usr/bin/ruby1.8
# == Synopsis
#
# mauvesend: send alert(s) to a given alert station
#
# == Usage
#
# mauvesend [destination]
#   [--source | -o <source>] [--replace | -p] [--verbose | -v]
#   [--id <alert ID> [alert options] ... ]
#
# <destination>:
#   where the alert should go, can be one of:
#     SRV record from DNS (we add _mauvealert._udp to record name)
#     normal hostname (i.e. A record)
#     IP address:port number
#
#   if no destination is supplied, reads parameter from file 
#   /etc/mauvealert/mauvesend.destination (otherwise throws an error).
# 
# --source | -o <source>:
#   identify the source of the alert (defaults to hostname, but you might
#   want to name your monitoring systems more explicitly).
#
# --replace | -p:
#   Send an update replacing all other alerts for this source - any previous
#   alerts not specified in this update are assumed to be cleared.  If you
#   specify this option, you don't have to supply *any* alerts to raise or 
#   clear (in which case all alerts from that source will be cleared).
#
# --verbose | -v:
#   If you specify this option once, it will print the transmission ID
#   of the packet for debugging.  If you specify it twice, it will print
#   the entire data structure.
#
# You can specify any number of alerts in an update - every time you specify
# --id starts a new alert.
#
# --id | -i <alert ID>:
#   alert ID; unique specified for each alert raised.
#
# --summary | -s <summary>:
#   text for humans describing the nature of the alert, first 100 characters
#   are only ones guaranteed to make it to pagers, twitter, SMS etc.
#
# --detail | -d <detail>:
#   HTML fragment describing the alert in more detail, no limit on length.
#
# --subject | -u <subject>:
#   set the subject of the alert (i.e. the server/entity that this alert
#   concerns).
#
# --clear | -c <time>:
#   mark the alert to be cleared at the given time, or +N where N is a number
#   of seconds, or 'now'.  If not supplied, the alert is deemed to be raised 
#   until explicitly cleared.
#
# --raise | -r <time>:
#   mark the alert to be (re)raised at the given time.  If not supplied, the
#   alert will be raised immediately.
# 

require 'getoptlong'
require 'mauve/sender'
require 'mauve/proto'
require 'mauve/mauve_time'
require 'pp'

def error(msg)
  STDERR.print "*** Error: #{msg}\n"
  STDERR.print "*** For help, type: #{$0} -h\n"
  exit 1
end

def parse_time_spec(spec)
  now = Mauve::MauveTime.now

  return now if spec == 'now'

  case spec[0]
    when ?+ then multiplier = 1
    when ?- then multiplier = -1
    else
      return Mauve::MauveTime.parse(spec)
  end
  multiplier *= case spec[-1]
    when ?m then 60
    when ?h then 3600
    when ?d then 86400
    else
      1
  end

  now + spec[1..-1].to_i * multiplier
end

update = Mauve::Proto::AlertUpdate.new
update.replace = false
update.alert = []
message = nil
verbose = 0
help    = false

opts = GetoptLong.new(
  ['-h', '--help',    GetoptLong::NO_ARGUMENT], 
  ['-o', '--source',  GetoptLong::OPTIONAL_ARGUMENT],
  ['-p', '--replace', GetoptLong::NO_ARGUMENT],
  ['-i', '--id',      GetoptLong::OPTIONAL_ARGUMENT],
  ['-s', '--summary', GetoptLong::OPTIONAL_ARGUMENT],
  ['-u', '--subject', GetoptLong::OPTIONAL_ARGUMENT],
  ['-c', '--clear',   GetoptLong::OPTIONAL_ARGUMENT],
  ['-r', '--raise',   GetoptLong::OPTIONAL_ARGUMENT],
  ['-d', '--detail',  GetoptLong::OPTIONAL_ARGUMENT],
  ['-v', '--verbose', GetoptLong::NO_ARGUMENT]
).each do |opt,arg|

  #
  # Can catch empty arguments better if we set the GetoptLong things to
  # "optional" rather than "required" and catch the empty arg here.
  error "#{opt} cannot be empty" if arg.empty? and not %w(-h -p -v).include?(opt)

  case opt
    when '-h'
      help = true
    when '-p'
      update.replace = true
    when '-i'
      message = Mauve::Proto::Alert.new
      message.id = arg
      update.alert << message
    when '-o'
      error "Can only specify one source" if update.source
      update.source = arg
    when '-v'
      verbose += 1
    else
      error "Must specify --id before message" unless message
      case opt
        when '-s' then message.summary = arg
        when '-u' then message.subject = arg
        when '-d' then message.detail = arg
        when '-c' then message.clear_time = parse_time_spec(arg).to_i
        when '-r' then message.raise_time = parse_time_spec(arg).to_i
        else
          error "Unknown option #{opt}"
      end
  end
end

# CAUTION! Kwality kode.
#
if help
  # Open the file, stripping the shebang line
  lines = File.open(__FILE__){|fh| fh.readlines}[2..-1]

  lines.each do |line|
  line.chomp!
    break if line.empty?
    puts line[2..-1].to_s
  end

  exit 0
end

error "No alerts specified" unless !update.alert.empty? || update.replace

update.transmission_id = rand(2**63)

begin
  Mauve::Sender.new(ARGV).send(update, verbose)
rescue ArgumentError => ae
  error ae.message
rescue Protobuf::NotInitializedError => bad
  error "Alert not initialized fully - you must supply an ID"
end