package pkg import ( "errors" "fmt" "regexp" "strconv" "strings" ) type Node struct { Address string `json:"address"` AppService string `json:"app_service"` ConnectionLimit int `json:"connection_limit"` Description string `json:"description"` Monitor string `json:"monitor"` Partition string `json:"partition"` Session string `json:"session"` Ephemeral bool `json:"ephemeral"` State string `json:"state"` DynamicRatio int `json:"dynamic_ratio"` Logging string `json:"logging"` RateLimit string `json:"rate_limit"` Ratio int `json:"ratio"` FQDN NodeFQDN `json:"fqdn"` Metadata map[string]NodeMeta `json:"metadata"` } type NodeFQDN struct { AddressFamily string `json:"address_family"` Autopopulate string `json:"autopopulate"` DownInterval int `json:"down_interval"` Interval int `json:"interval"` Name string `json:"name"` } type NodeMeta struct { AppService string `json:"app_service"` Persist bool `json:"persist"` Value string `json:"value"` } func ParseNodeFQDN(raw string) (NodeFQDN, error) { var n NodeFQDN fqdnRE, err := regexp.Compile(`{ address-family (.*) autopopulate (.*) down-interval (.*) interval (.*) name (.*) }`) if err != nil { return n, fmt.Errorf("error compiling fqdn regex: %s", err.Error()) } fq := fqdnRE.FindStringSubmatch(raw) if len(fq) != 6 { return n, errors.New("unable to find all required fields in fqdn line via regex") } n.AddressFamily = fq[1] n.Autopopulate = fq[2] i, err := strconv.Atoi(fq[3]) if err != nil { return n, fmt.Errorf("unable to convert `%s` to integer for fqdn.down-interval", fq[3]) } n.DownInterval = i i, err = strconv.Atoi(fq[4]) if err != nil { return n, fmt.Errorf("unable to convert `%s` to integer for fqdn.interval", fq[4]) } n.Interval = i n.Name = fq[5] return n, nil } func ParseNode(raw string) (*Node, error) { n := new(Node) n.Metadata = make(map[string]NodeMeta) re, err := regexp.Compile(`ltm node (.*?) { address (.*?) app-service (.*?) connection-limit (.*?) description (.*?) dynamic-ratio (.*?) ephemeral (.*?) fqdn (.*?) logging (.*?) metadata (.*?) monitor (.*?) partition (.*?) rate-limit (.*?) ratio (.*?) session (.*?) state (.*?) }`) if err != nil { return nil, err } f := re.FindStringSubmatch(raw) if len(f) != 17 { return nil, errors.New("unable to find all required fields in node line via regex") } n.Address = f[2] n.AppService = f[3] i, err := strconv.Atoi(f[4]) if err != nil { return nil, fmt.Errorf("unable to convert `%s` to integer for n.connection_limit", f[4]) } n.ConnectionLimit = i n.Description = func() string { desc := f[5] if desc[0] == '"' && desc[len(desc)-1] == '"' { // if it starts and ends with a string, strip em desc = desc[1 : len(desc)-1] } return desc }() i, err = strconv.Atoi(f[6]) if err != nil { return nil, fmt.Errorf("unable to convert `%s` to integer for n.dynamic-ratio", f[6]) } n.DynamicRatio = i n.Ephemeral = parseBool(f[7]) n.Logging = f[9] n.Monitor = f[11] n.Partition = f[12] n.RateLimit = f[13] i, err = strconv.Atoi(f[14]) if err != nil { return nil, fmt.Errorf("unable to convert `%s` to integer for n.ratio", f[14]) } n.Ratio = i n.Session = f[15] n.State = f[16] // Process FQDN n.FQDN, err = ParseNodeFQDN(f[8]) if err != nil { return nil, fmt.Errorf("error parsing node fqdn: %s", err.Error()) } // Process metadata if f[10] != "none" { metaRE, err := regexp.Compile(`((\S+) { app-service (\S+) persist (\S+) value (.*?) })+`) if err != nil { return nil, fmt.Errorf("error compiling regex for node metadata: %s", err.Error()) } mf := metaRE.FindAllStringSubmatch(f[10], -1) for _, match := range mf { n.Metadata[match[2]] = NodeMeta{ AppService: match[3], Persist: parseBool(match[4]), Value: match[5], } } } return n, nil } func ParseNodes(s string) (map[string]*Node, error) { nodes := make(map[string]*Node) split := strings.Split(s, "\n") for i, line := range split { if i == 0 || i == len(split)-1 { continue // skip first & last lines } f := strings.Fields(line) n, err := ParseNode(line) if err != nil { return nil, err } nodes[f[2]] = n } return nodes, nil }