Changed groups and hosts to use maps in config, reorganized code, cleaned up command line arguments
This commit is contained in:
parent
aa1d4d10fa
commit
826564a060
8
alrmrc
8
alrmrc
@ -1,11 +1,13 @@
|
|||||||
set interval 30
|
set interval 30
|
||||||
|
|
||||||
monitor host gateway address 10.11.135.100
|
|
||||||
|
|
||||||
monitor group webservers
|
monitor group webservers
|
||||||
host www1.example.com address 10.11.135.101
|
host www1.example.com address 10.11.135.101
|
||||||
check ping
|
check ping
|
||||||
host www2.example.com address 10.11.135.102
|
host www2.example.com address 10.11.135.102
|
||||||
check ping
|
# comments can occur at the beginning
|
||||||
|
check ping # or the end of a line
|
||||||
|
check ping # checks are not named, so multiple is okay
|
||||||
|
|
||||||
|
monitor host gateway address 10.11.135.100
|
||||||
|
|
||||||
monitor host database
|
monitor host database
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
package main
|
package check
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AlrmCheck interface {
|
||||||
|
Parse(string) (bool, error)
|
||||||
|
Check() error
|
||||||
|
}
|
||||||
|
|
||||||
func NewCheck(name string, addr string) (AlrmCheck, error) {
|
func NewCheck(name string, addr string) (AlrmCheck, error) {
|
||||||
switch name {
|
switch name {
|
||||||
case "ping":
|
case "ping":
|
||||||
return &CheckPing{Address: addr}, nil
|
return &CheckPing{Type: "ping", Address: addr}, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown check name \"%s\"", name)
|
return nil, fmt.Errorf("unknown check name \"%s\"", name)
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package main
|
package check
|
||||||
|
|
||||||
type CheckPing struct {
|
type CheckPing struct {
|
||||||
|
Type string
|
||||||
Address string
|
Address string
|
||||||
}
|
}
|
||||||
|
|
660
check/ping/ping.go
Normal file
660
check/ping/ping.go
Normal file
@ -0,0 +1,660 @@
|
|||||||
|
// Package ping is an ICMP ping library seeking to emulate the unix "ping"
|
||||||
|
// command.
|
||||||
|
//
|
||||||
|
// Here is a very simple example that sends & receives 3 packets:
|
||||||
|
//
|
||||||
|
// pinger, err := ping.NewPinger("www.google.com")
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pinger.Count = 3
|
||||||
|
// pinger.Run() // blocks until finished
|
||||||
|
// stats := pinger.Statistics() // get send/receive/rtt stats
|
||||||
|
//
|
||||||
|
// Here is an example that emulates the unix ping command:
|
||||||
|
//
|
||||||
|
// pinger, err := ping.NewPinger("www.google.com")
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Printf("ERROR: %s\n", err.Error())
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pinger.OnRecv = func(pkt *ping.Packet) {
|
||||||
|
// fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v\n",
|
||||||
|
// pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt)
|
||||||
|
// }
|
||||||
|
// pinger.OnFinish = func(stats *ping.Statistics) {
|
||||||
|
// fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr)
|
||||||
|
// fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n",
|
||||||
|
// stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
|
||||||
|
// fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n",
|
||||||
|
// stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr())
|
||||||
|
// pinger.Run()
|
||||||
|
//
|
||||||
|
// It sends ICMP packet(s) and waits for a response. If it receives a response,
|
||||||
|
// it calls the "receive" callback. When it's finished, it calls the "finish"
|
||||||
|
// callback.
|
||||||
|
//
|
||||||
|
// For a full ping example, see "cmd/ping/ping.go".
|
||||||
|
//
|
||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/icmp"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
timeSliceLength = 8
|
||||||
|
trackerLength = 8
|
||||||
|
protocolICMP = 1
|
||||||
|
protocolIPv6ICMP = 58
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ipv4Proto = map[string]string{"icmp": "ip4:icmp", "udp": "udp4"}
|
||||||
|
ipv6Proto = map[string]string{"icmp": "ip6:ipv6-icmp", "udp": "udp6"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns a new Pinger struct pointer.
|
||||||
|
func New(addr string) *Pinger {
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
return &Pinger{
|
||||||
|
Count: -1,
|
||||||
|
Interval: time.Second,
|
||||||
|
RecordRtts: true,
|
||||||
|
Size: timeSliceLength,
|
||||||
|
Timeout: time.Second * 100000,
|
||||||
|
Tracker: r.Int63n(math.MaxInt64),
|
||||||
|
|
||||||
|
addr: addr,
|
||||||
|
done: make(chan bool),
|
||||||
|
id: r.Intn(math.MaxInt16),
|
||||||
|
ipaddr: nil,
|
||||||
|
ipv4: false,
|
||||||
|
network: "ip",
|
||||||
|
protocol: "udp",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPinger returns a new Pinger and resolves the address.
|
||||||
|
func NewPinger(addr string) (*Pinger, error) {
|
||||||
|
p := New(addr)
|
||||||
|
return p, p.Resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pinger represents a packet sender/receiver.
|
||||||
|
type Pinger struct {
|
||||||
|
// Interval is the wait time between each packet send. Default is 1s.
|
||||||
|
Interval time.Duration
|
||||||
|
|
||||||
|
// Timeout specifies a timeout before ping exits, regardless of how many
|
||||||
|
// packets have been received.
|
||||||
|
Timeout time.Duration
|
||||||
|
|
||||||
|
// Count tells pinger to stop after sending (and receiving) Count echo
|
||||||
|
// packets. If this option is not specified, pinger will operate until
|
||||||
|
// interrupted.
|
||||||
|
Count int
|
||||||
|
|
||||||
|
// Debug runs in debug mode
|
||||||
|
Debug bool
|
||||||
|
|
||||||
|
// Number of packets sent
|
||||||
|
PacketsSent int
|
||||||
|
|
||||||
|
// Number of packets received
|
||||||
|
PacketsRecv int
|
||||||
|
|
||||||
|
// If true, keep a record of rtts of all received packets.
|
||||||
|
// Set to false to avoid memory bloat for long running pings.
|
||||||
|
RecordRtts bool
|
||||||
|
|
||||||
|
// rtts is all of the Rtts
|
||||||
|
rtts []time.Duration
|
||||||
|
|
||||||
|
// OnSend is called when Pinger sends a packet
|
||||||
|
OnSend func(*Packet)
|
||||||
|
|
||||||
|
// OnRecv is called when Pinger receives and processes a packet
|
||||||
|
OnRecv func(*Packet)
|
||||||
|
|
||||||
|
// OnFinish is called when Pinger exits
|
||||||
|
OnFinish func(*Statistics)
|
||||||
|
|
||||||
|
// Size of packet being sent
|
||||||
|
Size int
|
||||||
|
|
||||||
|
// Tracker: Used to uniquely identify packet when non-priviledged
|
||||||
|
Tracker int64
|
||||||
|
|
||||||
|
// Source is the source IP address
|
||||||
|
Source string
|
||||||
|
|
||||||
|
// stop chan bool
|
||||||
|
done chan bool
|
||||||
|
|
||||||
|
ipaddr *net.IPAddr
|
||||||
|
addr string
|
||||||
|
|
||||||
|
ipv4 bool
|
||||||
|
id int
|
||||||
|
sequence int
|
||||||
|
// network is one of "ip", "ip4", or "ip6".
|
||||||
|
network string
|
||||||
|
// protocol is "icmp" or "udp".
|
||||||
|
protocol string
|
||||||
|
}
|
||||||
|
|
||||||
|
type packet struct {
|
||||||
|
bytes []byte
|
||||||
|
nbytes int
|
||||||
|
ttl int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packet represents a received and processed ICMP echo packet.
|
||||||
|
type Packet struct {
|
||||||
|
// Rtt is the round-trip time it took to ping.
|
||||||
|
Rtt time.Duration
|
||||||
|
|
||||||
|
// IPAddr is the address of the host being pinged.
|
||||||
|
IPAddr *net.IPAddr
|
||||||
|
|
||||||
|
// Addr is the string address of the host being pinged.
|
||||||
|
Addr string
|
||||||
|
|
||||||
|
// NBytes is the number of bytes in the message.
|
||||||
|
Nbytes int
|
||||||
|
|
||||||
|
// Seq is the ICMP sequence number.
|
||||||
|
Seq int
|
||||||
|
|
||||||
|
// TTL is the Time To Live on the packet.
|
||||||
|
Ttl int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statistics represent the stats of a currently running or finished
|
||||||
|
// pinger operation.
|
||||||
|
type Statistics struct {
|
||||||
|
// PacketsRecv is the number of packets received.
|
||||||
|
PacketsRecv int
|
||||||
|
|
||||||
|
// PacketsSent is the number of packets sent.
|
||||||
|
PacketsSent int
|
||||||
|
|
||||||
|
// PacketLoss is the percentage of packets lost.
|
||||||
|
PacketLoss float64
|
||||||
|
|
||||||
|
// IPAddr is the address of the host being pinged.
|
||||||
|
IPAddr *net.IPAddr
|
||||||
|
|
||||||
|
// Addr is the string address of the host being pinged.
|
||||||
|
Addr string
|
||||||
|
|
||||||
|
// Rtts is all of the round-trip times sent via this pinger.
|
||||||
|
Rtts []time.Duration
|
||||||
|
|
||||||
|
// MinRtt is the minimum round-trip time sent via this pinger.
|
||||||
|
MinRtt time.Duration
|
||||||
|
|
||||||
|
// MaxRtt is the maximum round-trip time sent via this pinger.
|
||||||
|
MaxRtt time.Duration
|
||||||
|
|
||||||
|
// AvgRtt is the average round-trip time sent via this pinger.
|
||||||
|
AvgRtt time.Duration
|
||||||
|
|
||||||
|
// StdDevRtt is the standard deviation of the round-trip times sent via
|
||||||
|
// this pinger.
|
||||||
|
StdDevRtt time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIPAddr sets the ip address of the target host.
|
||||||
|
func (p *Pinger) SetIPAddr(ipaddr *net.IPAddr) {
|
||||||
|
p.ipv4 = isIPv4(ipaddr.IP)
|
||||||
|
|
||||||
|
p.ipaddr = ipaddr
|
||||||
|
p.addr = ipaddr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddr returns the ip address of the target host.
|
||||||
|
func (p *Pinger) IPAddr() *net.IPAddr {
|
||||||
|
return p.ipaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve does the DNS lookup for the Pinger address and sets IP protocol.
|
||||||
|
func (p *Pinger) Resolve() error {
|
||||||
|
if len(p.addr) == 0 {
|
||||||
|
return errors.New("addr cannot be empty")
|
||||||
|
}
|
||||||
|
ipaddr, err := net.ResolveIPAddr(p.network, p.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ipv4 = isIPv4(ipaddr.IP)
|
||||||
|
|
||||||
|
p.ipaddr = ipaddr
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAddr resolves and sets the ip address of the target host, addr can be a
|
||||||
|
// DNS name like "www.google.com" or IP like "127.0.0.1".
|
||||||
|
func (p *Pinger) SetAddr(addr string) error {
|
||||||
|
oldAddr := p.addr
|
||||||
|
p.addr = addr
|
||||||
|
err := p.Resolve()
|
||||||
|
if err != nil {
|
||||||
|
p.addr = oldAddr
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the string ip address of the target host.
|
||||||
|
func (p *Pinger) Addr() string {
|
||||||
|
return p.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNetwork allows configuration of DNS resolution.
|
||||||
|
// * "ip" will automatically select IPv4 or IPv6.
|
||||||
|
// * "ip4" will select IPv4.
|
||||||
|
// * "ip6" will select IPv6.
|
||||||
|
func (p *Pinger) SetNetwork(n string) {
|
||||||
|
switch n {
|
||||||
|
case "ip4":
|
||||||
|
p.network = "ip4"
|
||||||
|
case "ip6":
|
||||||
|
p.network = "ip6"
|
||||||
|
default:
|
||||||
|
p.network = "ip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrivileged sets the type of ping pinger will send.
|
||||||
|
// false means pinger will send an "unprivileged" UDP ping.
|
||||||
|
// true means pinger will send a "privileged" raw ICMP ping.
|
||||||
|
// NOTE: setting to true requires that it be run with super-user privileges.
|
||||||
|
func (p *Pinger) SetPrivileged(privileged bool) {
|
||||||
|
if privileged {
|
||||||
|
p.protocol = "icmp"
|
||||||
|
} else {
|
||||||
|
p.protocol = "udp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Privileged returns whether pinger is running in privileged mode.
|
||||||
|
func (p *Pinger) Privileged() bool {
|
||||||
|
return p.protocol == "icmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the pinger. This is a blocking function that will exit when it's
|
||||||
|
// done. If Count or Interval are not specified, it will run continuously until
|
||||||
|
// it is interrupted.
|
||||||
|
func (p *Pinger) Run() error {
|
||||||
|
var conn *icmp.PacketConn
|
||||||
|
var err error
|
||||||
|
if p.ipaddr == nil {
|
||||||
|
err = p.Resolve()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if p.ipv4 {
|
||||||
|
if conn, err = p.listen(ipv4Proto[p.protocol]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = conn.IPv4PacketConn().SetControlMessage(ipv4.FlagTTL, true); runtime.GOOS != "windows" && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if conn, err = p.listen(ipv6Proto[p.protocol]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = conn.IPv6PacketConn().SetControlMessage(ipv6.FlagHopLimit, true); runtime.GOOS != "windows" && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
defer p.finish()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
recv := make(chan *packet, 5)
|
||||||
|
defer close(recv)
|
||||||
|
wg.Add(1)
|
||||||
|
//nolint:errcheck
|
||||||
|
go p.recvICMP(conn, recv, &wg)
|
||||||
|
|
||||||
|
err = p.sendICMP(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := time.NewTicker(p.Timeout)
|
||||||
|
defer timeout.Stop()
|
||||||
|
interval := time.NewTicker(p.Interval)
|
||||||
|
defer interval.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-p.done:
|
||||||
|
wg.Wait()
|
||||||
|
return nil
|
||||||
|
case <-timeout.C:
|
||||||
|
close(p.done)
|
||||||
|
wg.Wait()
|
||||||
|
return nil
|
||||||
|
case <-interval.C:
|
||||||
|
if p.Count > 0 && p.PacketsSent >= p.Count {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = p.sendICMP(conn)
|
||||||
|
if err != nil {
|
||||||
|
// FIXME: this logs as FATAL but continues
|
||||||
|
fmt.Println("FATAL: ", err.Error())
|
||||||
|
}
|
||||||
|
case r := <-recv:
|
||||||
|
err := p.processPacket(r)
|
||||||
|
if err != nil {
|
||||||
|
// FIXME: this logs as FATAL but continues
|
||||||
|
fmt.Println("FATAL: ", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Count > 0 && p.PacketsRecv >= p.Count {
|
||||||
|
close(p.done)
|
||||||
|
wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) Stop() {
|
||||||
|
close(p.done)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) finish() {
|
||||||
|
handler := p.OnFinish
|
||||||
|
if handler != nil {
|
||||||
|
s := p.Statistics()
|
||||||
|
handler(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statistics returns the statistics of the pinger. This can be run while the
|
||||||
|
// pinger is running or after it is finished. OnFinish calls this function to
|
||||||
|
// get it's finished statistics.
|
||||||
|
func (p *Pinger) Statistics() *Statistics {
|
||||||
|
loss := float64(p.PacketsSent-p.PacketsRecv) / float64(p.PacketsSent) * 100
|
||||||
|
var min, max, total time.Duration
|
||||||
|
if len(p.rtts) > 0 {
|
||||||
|
min = p.rtts[0]
|
||||||
|
max = p.rtts[0]
|
||||||
|
}
|
||||||
|
for _, rtt := range p.rtts {
|
||||||
|
if rtt < min {
|
||||||
|
min = rtt
|
||||||
|
}
|
||||||
|
if rtt > max {
|
||||||
|
max = rtt
|
||||||
|
}
|
||||||
|
total += rtt
|
||||||
|
}
|
||||||
|
s := Statistics{
|
||||||
|
PacketsSent: p.PacketsSent,
|
||||||
|
PacketsRecv: p.PacketsRecv,
|
||||||
|
PacketLoss: loss,
|
||||||
|
Rtts: p.rtts,
|
||||||
|
Addr: p.addr,
|
||||||
|
IPAddr: p.ipaddr,
|
||||||
|
MaxRtt: max,
|
||||||
|
MinRtt: min,
|
||||||
|
}
|
||||||
|
if len(p.rtts) > 0 {
|
||||||
|
s.AvgRtt = total / time.Duration(len(p.rtts))
|
||||||
|
var sumsquares time.Duration
|
||||||
|
for _, rtt := range p.rtts {
|
||||||
|
sumsquares += (rtt - s.AvgRtt) * (rtt - s.AvgRtt)
|
||||||
|
}
|
||||||
|
s.StdDevRtt = time.Duration(math.Sqrt(
|
||||||
|
float64(sumsquares / time.Duration(len(p.rtts)))))
|
||||||
|
}
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) recvICMP(
|
||||||
|
conn *icmp.PacketConn,
|
||||||
|
recv chan<- *packet,
|
||||||
|
wg *sync.WaitGroup,
|
||||||
|
) error {
|
||||||
|
defer wg.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-p.done:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
bytes := make([]byte, 512)
|
||||||
|
if err := conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var n, ttl int
|
||||||
|
var err error
|
||||||
|
if p.ipv4 {
|
||||||
|
var cm *ipv4.ControlMessage
|
||||||
|
n, cm, _, err = conn.IPv4PacketConn().ReadFrom(bytes)
|
||||||
|
if cm != nil {
|
||||||
|
ttl = cm.TTL
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var cm *ipv6.ControlMessage
|
||||||
|
n, cm, _, err = conn.IPv6PacketConn().ReadFrom(bytes)
|
||||||
|
if cm != nil {
|
||||||
|
ttl = cm.HopLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if neterr, ok := err.(*net.OpError); ok {
|
||||||
|
if neterr.Timeout() {
|
||||||
|
// Read timeout
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
close(p.done)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-p.done:
|
||||||
|
return nil
|
||||||
|
case recv <- &packet{bytes: bytes, nbytes: n, ttl: ttl}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) processPacket(recv *packet) error {
|
||||||
|
receivedAt := time.Now()
|
||||||
|
var proto int
|
||||||
|
if p.ipv4 {
|
||||||
|
proto = protocolICMP
|
||||||
|
} else {
|
||||||
|
proto = protocolIPv6ICMP
|
||||||
|
}
|
||||||
|
|
||||||
|
var m *icmp.Message
|
||||||
|
var err error
|
||||||
|
if m, err = icmp.ParseMessage(proto, recv.bytes); err != nil {
|
||||||
|
return fmt.Errorf("error parsing icmp message: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply {
|
||||||
|
// Not an echo reply, ignore it
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
outPkt := &Packet{
|
||||||
|
Nbytes: recv.nbytes,
|
||||||
|
IPAddr: p.ipaddr,
|
||||||
|
Addr: p.addr,
|
||||||
|
Ttl: recv.ttl,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch pkt := m.Body.(type) {
|
||||||
|
case *icmp.Echo:
|
||||||
|
// If we are priviledged, we can match icmp.ID
|
||||||
|
if p.protocol == "icmp" {
|
||||||
|
// Check if reply from same ID
|
||||||
|
if pkt.ID != p.id {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pkt.Data) < timeSliceLength+trackerLength {
|
||||||
|
return fmt.Errorf("insufficient data received; got: %d %v",
|
||||||
|
len(pkt.Data), pkt.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
tracker := bytesToInt(pkt.Data[timeSliceLength:])
|
||||||
|
timestamp := bytesToTime(pkt.Data[:timeSliceLength])
|
||||||
|
|
||||||
|
if tracker != p.Tracker {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
outPkt.Rtt = receivedAt.Sub(timestamp)
|
||||||
|
outPkt.Seq = pkt.Seq
|
||||||
|
p.PacketsRecv++
|
||||||
|
default:
|
||||||
|
// Very bad, not sure how this can happen
|
||||||
|
return fmt.Errorf("invalid ICMP echo reply; type: '%T', '%v'", pkt, pkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.RecordRtts {
|
||||||
|
p.rtts = append(p.rtts, outPkt.Rtt)
|
||||||
|
}
|
||||||
|
handler := p.OnRecv
|
||||||
|
if handler != nil {
|
||||||
|
handler(outPkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) sendICMP(conn *icmp.PacketConn) error {
|
||||||
|
var typ icmp.Type
|
||||||
|
if p.ipv4 {
|
||||||
|
typ = ipv4.ICMPTypeEcho
|
||||||
|
} else {
|
||||||
|
typ = ipv6.ICMPTypeEchoRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
var dst net.Addr = p.ipaddr
|
||||||
|
if p.protocol == "udp" {
|
||||||
|
dst = &net.UDPAddr{IP: p.ipaddr.IP, Zone: p.ipaddr.Zone}
|
||||||
|
}
|
||||||
|
|
||||||
|
t := append(timeToBytes(time.Now()), intToBytes(p.Tracker)...)
|
||||||
|
if remainSize := p.Size - timeSliceLength - trackerLength; remainSize > 0 {
|
||||||
|
t = append(t, bytes.Repeat([]byte{1}, remainSize)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &icmp.Echo{
|
||||||
|
ID: p.id,
|
||||||
|
Seq: p.sequence,
|
||||||
|
Data: t,
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &icmp.Message{
|
||||||
|
Type: typ,
|
||||||
|
Code: 0,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
|
||||||
|
msgBytes, err := msg.Marshal(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if _, err := conn.WriteTo(msgBytes, dst); err != nil {
|
||||||
|
if neterr, ok := err.(*net.OpError); ok {
|
||||||
|
if neterr.Err == syscall.ENOBUFS {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handler := p.OnSend
|
||||||
|
if handler != nil {
|
||||||
|
outPkt := &Packet{
|
||||||
|
Nbytes: len(msgBytes),
|
||||||
|
IPAddr: p.ipaddr,
|
||||||
|
Addr: p.addr,
|
||||||
|
Seq: p.sequence,
|
||||||
|
}
|
||||||
|
handler(outPkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.PacketsSent++
|
||||||
|
p.sequence++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) listen(netProto string) (*icmp.PacketConn, error) {
|
||||||
|
conn, err := icmp.ListenPacket(netProto, p.Source)
|
||||||
|
if err != nil {
|
||||||
|
close(p.done)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bytesToTime(b []byte) time.Time {
|
||||||
|
var nsec int64
|
||||||
|
for i := uint8(0); i < 8; i++ {
|
||||||
|
nsec += int64(b[i]) << ((7 - i) * 8)
|
||||||
|
}
|
||||||
|
return time.Unix(nsec/1000000000, nsec%1000000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIPv4(ip net.IP) bool {
|
||||||
|
return len(ip.To4()) == net.IPv4len
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeToBytes(t time.Time) []byte {
|
||||||
|
nsec := t.UnixNano()
|
||||||
|
b := make([]byte, 8)
|
||||||
|
for i := uint8(0); i < 8; i++ {
|
||||||
|
b[i] = byte((nsec >> ((7 - i) * 8)) & 0xff)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func bytesToInt(b []byte) int64 {
|
||||||
|
return int64(binary.BigEndian.Uint64(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func intToBytes(tracker int64) []byte {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(b, uint64(tracker))
|
||||||
|
return b
|
||||||
|
}
|
50
config.go
50
config.go
@ -1,50 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
type AlrmConfig struct {
|
|
||||||
Groups []*AlrmGroup
|
|
||||||
Interval int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AlrmConfig) NewGroup() *AlrmGroup {
|
|
||||||
group := &AlrmGroup{}
|
|
||||||
ac.Groups = append(ac.Groups, group)
|
|
||||||
return group
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlrmGroup struct {
|
|
||||||
Name string
|
|
||||||
Hosts []*AlrmHost
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ag *AlrmGroup) NewHost() *AlrmHost {
|
|
||||||
host := &AlrmHost{}
|
|
||||||
ag.Hosts = append(ag.Hosts, host)
|
|
||||||
return host
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlrmHost struct {
|
|
||||||
Name string
|
|
||||||
Address string
|
|
||||||
Checks []AlrmCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ah *AlrmHost) GetAddress() string {
|
|
||||||
if ah.Address != "" {
|
|
||||||
return ah.Address
|
|
||||||
}
|
|
||||||
return ah.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlrmCheck interface {
|
|
||||||
Parse(string) (bool, error)
|
|
||||||
Check() error
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadConfig(fn string) (*AlrmConfig, error) {
|
|
||||||
parser := &Parser{}
|
|
||||||
config, err := parser.Parse(fn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return config, nil
|
|
||||||
}
|
|
44
config/config.go
Normal file
44
config/config.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AlrmConfig struct {
|
||||||
|
Groups map[string]*AlrmGroup
|
||||||
|
Interval int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *AlrmConfig) NewGroup(name string) (*AlrmGroup, error) {
|
||||||
|
if ac.Groups == nil {
|
||||||
|
ac.Groups = make(map[string]*AlrmGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := ac.Groups[name]; exists {
|
||||||
|
return nil, fmt.Errorf("group %s already exists", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
group := &AlrmGroup{Name: name}
|
||||||
|
ac.Groups[name] = group
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *AlrmConfig) SetInterval(val string) error {
|
||||||
|
interval, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ac.Interval = interval
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadConfig(fn string, debuglvl int) (*AlrmConfig, error) {
|
||||||
|
parser := &Parser{DebugLevel: debuglvl}
|
||||||
|
config, err := parser.Parse(fn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
24
config/group.go
Normal file
24
config/group.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AlrmGroup struct {
|
||||||
|
Name string
|
||||||
|
Hosts map[string]*AlrmHost
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ag *AlrmGroup) NewHost(name string) (*AlrmHost, error) {
|
||||||
|
if ag.Hosts == nil {
|
||||||
|
ag.Hosts = make(map[string]*AlrmHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := ag.Hosts[name]; exists {
|
||||||
|
return nil, fmt.Errorf("host %s already exists", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
host := &AlrmHost{Name: name}
|
||||||
|
ag.Hosts[name] = host
|
||||||
|
return host, nil
|
||||||
|
}
|
27
config/host.go
Normal file
27
config/host.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"alrm/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AlrmHost struct {
|
||||||
|
Name string
|
||||||
|
Address string
|
||||||
|
Checks []check.AlrmCheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *AlrmHost) GetAddress() string {
|
||||||
|
if ah.Address != "" {
|
||||||
|
return ah.Address
|
||||||
|
}
|
||||||
|
return ah.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *AlrmHost) NewCheck(name string) (check.AlrmCheck, error) {
|
||||||
|
chk, err := check.NewCheck(name, ah.GetAddress())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ah.Checks = append(ah.Checks, chk)
|
||||||
|
return chk, nil
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
package main
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"alrm/check"
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,8 +18,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
|
DebugLevel int
|
||||||
Line int
|
Line int
|
||||||
states []int
|
states []int
|
||||||
|
lasthost *AlrmHost
|
||||||
|
lastgroup *AlrmGroup
|
||||||
|
lastcheck check.AlrmCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
|
func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
|
||||||
@ -30,9 +34,6 @@ func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
|
|||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
config := &AlrmConfig{}
|
config := &AlrmConfig{}
|
||||||
var group *AlrmGroup
|
|
||||||
var host *AlrmHost
|
|
||||||
var check AlrmCheck
|
|
||||||
|
|
||||||
scan := bufio.NewScanner(file)
|
scan := bufio.NewScanner(file)
|
||||||
scan.Split(p.Split)
|
scan.Split(p.Split)
|
||||||
@ -61,7 +62,7 @@ func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
|
|||||||
value := scan.Text()
|
value := scan.Text()
|
||||||
switch key {
|
switch key {
|
||||||
case "interval":
|
case "interval":
|
||||||
config.Interval, err = strconv.Atoi(value)
|
err := config.SetInterval(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"invalid number for interval in %s, line %d: \"%s\"",
|
"invalid number for interval in %s, line %d: \"%s\"",
|
||||||
@ -78,12 +79,9 @@ func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
|
|||||||
case TK_MONITOR:
|
case TK_MONITOR:
|
||||||
switch strings.ToLower(tk) {
|
switch strings.ToLower(tk) {
|
||||||
case "host":
|
case "host":
|
||||||
group = config.NewGroup()
|
|
||||||
host = group.NewHost()
|
|
||||||
p.setState(TK_HOST)
|
p.setState(TK_HOST)
|
||||||
|
|
||||||
case "group":
|
case "group":
|
||||||
group = config.NewGroup()
|
|
||||||
p.setState(TK_GROUP)
|
p.setState(TK_GROUP)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -92,33 +90,43 @@ func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case TK_GROUP:
|
case TK_GROUP:
|
||||||
if group == nil {
|
if p.lastgroup == nil {
|
||||||
return nil, fmt.Errorf("group without initialization")
|
p.lastgroup, err = config.NewGroup(tk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s in %s, line %d",
|
||||||
|
err.Error(), fn, p.Line+1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch strings.ToLower(tk) {
|
switch strings.ToLower(tk) {
|
||||||
case "host":
|
case "host":
|
||||||
host = group.NewHost()
|
|
||||||
p.setState(TK_HOST)
|
p.setState(TK_HOST)
|
||||||
continue
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if group.Name == "" {
|
|
||||||
group.Name = tk
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
p.prevState()
|
p.prevState()
|
||||||
goto stateswitch
|
goto stateswitch
|
||||||
}
|
}
|
||||||
|
|
||||||
case TK_HOST:
|
case TK_HOST:
|
||||||
if host == nil {
|
// If a host has no group, inherit the host name
|
||||||
return nil, fmt.Errorf("host token without initialization")
|
if p.lastgroup == nil {
|
||||||
|
p.lastgroup, err = config.NewGroup(tk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s in %s, line %d",
|
||||||
|
err.Error(), fn, p.Line+1,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if host.Name == "" {
|
if p.lasthost == nil {
|
||||||
host.Name = tk
|
p.lasthost, err = p.lastgroup.NewHost(tk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s in %s, line %d",
|
||||||
|
err.Error(), fn, p.Line+1,
|
||||||
|
)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,10 +136,9 @@ func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
|
|||||||
return nil, fmt.Errorf("empty address for host in %s, line %d",
|
return nil, fmt.Errorf("empty address for host in %s, line %d",
|
||||||
fn, p.Line+1)
|
fn, p.Line+1)
|
||||||
}
|
}
|
||||||
host.Address = scan.Text()
|
p.lasthost.Address = scan.Text()
|
||||||
|
|
||||||
case "check":
|
case "check":
|
||||||
check = nil
|
|
||||||
p.setState(TK_CHECK)
|
p.setState(TK_CHECK)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -140,23 +147,20 @@ func (p *Parser) Parse(fn string) (*AlrmConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case TK_CHECK:
|
case TK_CHECK:
|
||||||
if check == nil {
|
if p.lastcheck == nil {
|
||||||
if host == nil {
|
p.lastcheck, err = p.lasthost.NewCheck(tk)
|
||||||
return nil, fmt.Errorf("check token without initialization")
|
|
||||||
}
|
|
||||||
check, err = NewCheck(strings.ToLower(tk), host.GetAddress())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s in %s, line %d",
|
return nil, fmt.Errorf("%s in %s, line %d",
|
||||||
err.Error(), fn, p.Line+1)
|
err.Error(), fn, p.Line+1)
|
||||||
}
|
}
|
||||||
host.Checks = append(host.Checks, check)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cont, err := check.Parse(tk)
|
cont, err := p.lastcheck.Parse(tk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !cont {
|
if !cont {
|
||||||
|
p.lastcheck = nil
|
||||||
p.prevState()
|
p.prevState()
|
||||||
goto stateswitch
|
goto stateswitch
|
||||||
}
|
}
|
||||||
@ -179,9 +183,24 @@ func (p *Parser) state() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) setState(state int) {
|
func (p *Parser) setState(state int) {
|
||||||
// fmt.Printf("%s", p.stateName())
|
switch state {
|
||||||
|
case TK_SET, TK_MONITOR:
|
||||||
|
fallthrough
|
||||||
|
case TK_GROUP:
|
||||||
|
p.lastgroup = nil
|
||||||
|
fallthrough
|
||||||
|
case TK_HOST:
|
||||||
|
p.lasthost = nil
|
||||||
|
p.lastcheck = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.DebugLevel > 0 {
|
||||||
|
fmt.Printf("Parser state: %s", p.stateName())
|
||||||
|
}
|
||||||
p.states = append(p.states, state)
|
p.states = append(p.states, state)
|
||||||
// fmt.Printf(" -> %s\n", p.stateName())
|
if p.DebugLevel > 0 {
|
||||||
|
fmt.Printf(" -> %s\n", p.stateName())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) prevState() int {
|
func (p *Parser) prevState() int {
|
||||||
@ -222,7 +241,7 @@ func (p *Parser) Split(data []byte, atEOF bool) (int, []byte, error) {
|
|||||||
|
|
||||||
for i := 0; i < len(data); i++ {
|
for i := 0; i < len(data); i++ {
|
||||||
c := data[i]
|
c := data[i]
|
||||||
// fmt.Printf("%c (%t) (%t)\n", c, started, ignoreline)
|
// fmt.Printf("%c (%t) (%t)\n", c, started, ignoreline)
|
||||||
switch c {
|
switch c {
|
||||||
case '\f', '\n', '\r':
|
case '\f', '\n', '\r':
|
||||||
p.Line++
|
p.Line++
|
30
main.go
30
main.go
@ -6,43 +6,51 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"alrm/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
configPath := flag.String("config", "", "path to configuration file")
|
cfgPath := flag.String("c", "", "path to configuration file")
|
||||||
|
debuglvl := flag.Int("d", 0, "debug level")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *configPath == "" {
|
if *cfgPath == "" {
|
||||||
if _, err := os.Stat("./alrmrc"); err == nil {
|
if _, err := os.Stat("./alrmrc"); err == nil {
|
||||||
*configPath = "./alrmrc"
|
*cfgPath = "./alrmrc"
|
||||||
}
|
}
|
||||||
if _, err := os.Stat("/etc/alrmrc"); err == nil {
|
if _, err := os.Stat("/etc/alrmrc"); err == nil {
|
||||||
*configPath = "/etc/alrmrc"
|
*cfgPath = "/etc/alrmrc"
|
||||||
}
|
}
|
||||||
if *configPath == "" {
|
if *cfgPath == "" {
|
||||||
fmt.Fprintf(os.Stderr, "Cannot find configuration\n")
|
fmt.Fprintf(os.Stderr, "Cannot find configuration\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := ReadConfig(*configPath)
|
command := strings.ToLower(flag.Arg(0))
|
||||||
|
switch command {
|
||||||
|
case "json":
|
||||||
|
cfg, err := config.ReadConfig(*cfgPath, *debuglvl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
command := strings.ToLower(flag.Arg(0))
|
o, err := json.MarshalIndent(cfg, "", " ")
|
||||||
switch command {
|
|
||||||
case "json":
|
|
||||||
o, err := json.MarshalIndent(config, "", " ")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "JSON error: %s\n", err.Error())
|
fmt.Fprintf(os.Stderr, "JSON error: %s\n", err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stdout, "%s", string(o))
|
fmt.Fprintf(os.Stdout, "%s\n", string(o))
|
||||||
|
|
||||||
case "", "config":
|
case "", "config":
|
||||||
|
_, err := config.ReadConfig(*cfgPath, *debuglvl)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Fprintf(os.Stdout, "Config is OK.\n")
|
fmt.Fprintf(os.Stdout, "Config is OK.\n")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user