Skip to content

Commit

Permalink
Merge pull request #12 from ddelnano/use-reflection-to-improve-respon…
Browse files Browse the repository at this point in the history
…se-parsing

Use reflection to improve response parsing
  • Loading branch information
ddelnano authored May 3, 2020
2 parents a217572 + be7b8cd commit f728ff2
Show file tree
Hide file tree
Showing 10 changed files with 464 additions and 227 deletions.
207 changes: 94 additions & 113 deletions client/client.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package client

import (
"errors"
"fmt"
"log"
"math"
"os"
"reflect"
"strconv"
"strings"
"time"
Expand All @@ -19,135 +21,74 @@ type Mikrotik struct {
Password string
}

type DnsRecord struct {
// .id field that mikrotik uses as the 'real' ID
Id string
Name string
Ttl int
Address string
}
func Unmarshal(reply routeros.Reply, v interface{}) error {
rv := reflect.ValueOf(v)
elem := rv.Elem()

func NewClient(host, username, password string) Mikrotik {
return Mikrotik{
Host: host,
Username: username,
Password: password,
if rv.Kind() != reflect.Ptr {
panic("Unmarshal cannot work without a pointer")
}
}

func GetConfigFromEnv() (host, username, password string) {
host = os.Getenv("MIKROTIK_HOST")
username = os.Getenv("MIKROTIK_USER")
password = os.Getenv("MIKROTIK_PASSWORD")
if host == "" || username == "" || password == "" {
// panic("Unable to find the MIKROTIK_HOST, MIKROTIK_USER or MIKROTIK_PASSWORD environment variable")
}
return host, username, password
}

func (client Mikrotik) getMikrotikClient() (c *routeros.Client, err error) {
address := client.Host
username := client.Username
password := client.Password
c, err = routeros.Dial(address, username, password)

if err != nil {
log.Printf("[ERROR] Failed to login to routerOS with error: %v", err)
}

return
}

func (client Mikrotik) AddDnsRecord(name, address string, ttl int) (*routeros.Reply, error) {
c, err := client.getMikrotikClient()

if err != nil {
return nil, err
}
cmd := strings.Split(fmt.Sprintf("/ip/dns/static/add =name=%s =address=%s =ttl=%d", name, address, ttl), " ")
log.Printf("[INFO] Running the mikrotik command: `%s`", cmd)
r, err := c.RunArgs(cmd)
return r, err
}

func (client Mikrotik) FindDnsRecord(name string) (*DnsRecord, error) {
c, err := client.getMikrotikClient()
switch elem.Kind() {
case reflect.Slice:
l := len(reply.Re)
if l <= 1 {
panic(fmt.Sprintf("Cannot Unmarshal %d sentence(s) into a slice", l))
}

if err != nil {
return nil, err
}
cmd := "/ip/dns/static/print"
log.Printf("[INFO] Running the mikrotik command: `%s`", cmd)
r, err := c.Run(cmd)
found := false
var sentence *proto.Sentence
t := elem.Type()
d := reflect.MakeSlice(t, l, l)

if err != nil {
return nil, err
}
for i := 0; i < l; i++ {
item := d.Index(i)
sentence := reply.Re[i]

for _, reply := range r.Re {
for _, item := range reply.List {
if item.Value == name {
found = true
sentence = reply
log.Printf("[DEBUG] Found dns record we were looking for: %v", sentence)
}
parseStruct(&item, *sentence)
}
}

if !found {
return nil, nil
}

// TODO: Add error checking
elem.Set(d)

address := ""
ttl := ""
id := ""
for _, pair := range sentence.List {
if pair.Key == ".id" {
id = pair.Value
case reflect.Struct:
if len(reply.Re) < 1 {
// This is an empty message
return nil
}
if pair.Key == "address" {
address = pair.Value
if len(reply.Re) > 1 {
msg := fmt.Sprintf("Failed to decode reply: %v", reply)
return errors.New(msg)
}

if pair.Key == "ttl" {
ttl = pair.Value
}
parseStruct(&elem, *reply.Re[0])
}

return &DnsRecord{
Id: id,
Address: address,
Name: name,
Ttl: ttlToSeconds(ttl),
}, nil
}

func (client Mikrotik) UpdateDnsRecord(id, name, address string, ttl int) error {
c, err := client.getMikrotikClient()

if err != nil {
return err
}
cmd := strings.Split(fmt.Sprintf("/ip/dns/static/set =numbers=%s =name=%s =address=%s =ttl=%d", id, name, address, ttl), " ")
log.Printf("[INFO] Running the mikrotik command: `%s`", cmd)
_, err = c.RunArgs(cmd)
return err
return nil
}
func parseStruct(v *reflect.Value, sentence proto.Sentence) {
elem := *v
for i := 0; i < elem.NumField(); i++ {
field := elem.Field(i)
fieldType := elem.Type().Field(i)
tags := strings.Split(fieldType.Tag.Get("mikrotik"), ",")

path := strings.ToLower(fieldType.Name)
fieldName := tags[0]

for _, pair := range sentence.List {
if strings.Compare(pair.Key, path) == 0 || strings.Compare(pair.Key, fieldName) == 0 {
switch fieldType.Type.Kind() {
case reflect.String:
field.SetString(pair.Value)
case reflect.Bool:
b, _ := strconv.ParseBool(pair.Value)
field.SetBool(b)
case reflect.Int:
if contains(tags, "ttlToSeconds") {
field.SetInt(int64(ttlToSeconds(pair.Value)))
}
}

func (client Mikrotik) DeleteDnsRecord(id string) error {
c, err := client.getMikrotikClient()

if err != nil {
return err
}
}
}
cmd := strings.Split(fmt.Sprintf("/ip/dns/static/remove =numbers=%s", id), " ")
log.Printf("[INFO] Running the mikrotik command: `%s`", cmd)
_, err = c.RunArgs(cmd)
return err
}

func ttlToSeconds(ttl string) int {
Expand Down Expand Up @@ -182,3 +123,43 @@ func ttlToSeconds(ttl string) int {
return 86400*days + int(d)/int(math.Pow10(9))

}

func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}

func NewClient(host, username, password string) Mikrotik {
return Mikrotik{
Host: host,
Username: username,
Password: password,
}
}

func GetConfigFromEnv() (host, username, password string) {
host = os.Getenv("MIKROTIK_HOST")
username = os.Getenv("MIKROTIK_USER")
password = os.Getenv("MIKROTIK_PASSWORD")
if host == "" || username == "" || password == "" {
// panic("Unable to find the MIKROTIK_HOST, MIKROTIK_USER or MIKROTIK_PASSWORD environment variable")
}
return host, username, password
}

func (client Mikrotik) getMikrotikClient() (c *routeros.Client, err error) {
address := client.Host
username := client.Username
password := client.Password
c, err = routeros.Dial(address, username, password)

if err != nil {
log.Printf("[ERROR] Failed to login to routerOS with error: %v", err)
}

return
}
Loading

0 comments on commit f728ff2

Please sign in to comment.