You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
9.4 KiB
325 lines
9.4 KiB
package runtime
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/google/renameio"
|
|
|
|
native_errors "github.com/haproxytech/client-native/v6/errors"
|
|
"github.com/haproxytech/client-native/v6/models"
|
|
)
|
|
|
|
// ShowMaps returns map files description from runtime
|
|
func (s *SingleRuntime) ShowMaps() (models.Maps, error) {
|
|
response, err := s.ExecuteWithResponse("show map")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s %w", err.Error(), native_errors.ErrNotFound)
|
|
}
|
|
return s.parseMaps(response), nil
|
|
}
|
|
|
|
// CreateMap creates a new map file with its entries. Returns an error if file already exists
|
|
func CreateMap(name string, file io.Reader) (*models.Map, error) {
|
|
ext := filepath.Ext(name) //nolint:ifshort
|
|
if ext != ".map" {
|
|
return nil, fmt.Errorf("provided file with %s extension, but supported .map %w", ext, native_errors.ErrGeneral)
|
|
}
|
|
|
|
if _, err := os.Stat(name); err == nil {
|
|
return nil, fmt.Errorf("file %s %w. You should delete an existing file first", name, native_errors.ErrAlreadyExists)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
_, err := io.Copy(&buf, file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = renameio.WriteFile(name, buf.Bytes(), 0o644)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &models.Map{File: name}, nil
|
|
}
|
|
|
|
// parseMaps parses output from `show map` command and return array of map files
|
|
// First line in output represents format and is ignored
|
|
// Sample output format:
|
|
// # id (file) description
|
|
// -1 (/etc/haproxy/maps/hosts.map) pattern loaded from file '/etc/haproxy/maps/hosts.map' used by map at file '/etc/haproxy/haproxy.cfg' line 26
|
|
func (s *SingleRuntime) parseMaps(output string) models.Maps {
|
|
if output == "" {
|
|
return nil
|
|
}
|
|
maps := models.Maps{}
|
|
|
|
lines := strings.Split(strings.TrimSpace(output), "\n")
|
|
for _, line := range lines {
|
|
m := s.parseMap(line)
|
|
if m != nil {
|
|
maps = append(maps, m)
|
|
}
|
|
}
|
|
return maps
|
|
}
|
|
|
|
// parseMap parses one line from map files array and return it structured
|
|
func (s *SingleRuntime) parseMap(line string) *models.Map {
|
|
if line == "" || strings.HasPrefix(strings.TrimSpace(line), "# id") {
|
|
return nil
|
|
}
|
|
|
|
parts := strings.Fields(line)
|
|
if len(parts) < 3 {
|
|
return nil
|
|
}
|
|
|
|
m := &models.Map{
|
|
ID: parts[0],
|
|
File: strings.TrimSuffix(strings.TrimPrefix(parts[1], "("), ")"),
|
|
Description: strings.Join(parts[2:], " "),
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
// GetMap returns one structured runtime map file
|
|
func (s *SingleRuntime) GetMap(name string) (*models.Map, error) {
|
|
maps, err := s.ShowMaps()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, m := range maps {
|
|
if m.File == name {
|
|
return m, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("%s %w", name, native_errors.ErrNotFound)
|
|
}
|
|
|
|
// ClearMap removes all map entries from the map file.
|
|
func (s *SingleRuntime) ClearMap(name string) error {
|
|
cmd := fmt.Sprintf("clear map %s", name)
|
|
if err := s.Execute(cmd); err != nil {
|
|
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrNotFound)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClearMapVersioned removes all map entries from the map file.
|
|
func (s *SingleRuntime) ClearMapVersioned(name, version string) error {
|
|
cmd := fmt.Sprintf("clear map @%s %s", version, name)
|
|
if err := s.Execute(cmd); err != nil {
|
|
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrNotFound)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ShowMapEntries returns one map runtime entries
|
|
func (s *SingleRuntime) ShowMapEntries(name string) (models.MapEntries, error) {
|
|
cmd := fmt.Sprintf("show map %s", name)
|
|
response, err := s.ExecuteWithResponse(cmd)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s %w", err.Error(), native_errors.ErrNotFound)
|
|
}
|
|
return ParseMapEntries(response, true), nil
|
|
}
|
|
|
|
// ShowMapEntriesVersioned returns one map runtime entries
|
|
func (s *SingleRuntime) ShowMapEntriesVersioned(name, version string) (models.MapEntries, error) {
|
|
cmd := fmt.Sprintf("show map @%s %s", version, name)
|
|
response, err := s.ExecuteWithResponse(cmd)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s %w", err.Error(), native_errors.ErrNotFound)
|
|
}
|
|
return ParseMapEntries(response, true), nil
|
|
}
|
|
|
|
// ParseMapEntries parses array of entries in one map file
|
|
// One line sample entry:
|
|
// ID Key Value
|
|
// 0x55d155c6fbf0 static.example.com be_static
|
|
func ParseMapEntries(output string, hasID bool) models.MapEntries {
|
|
if output == "" {
|
|
return nil
|
|
}
|
|
me := models.MapEntries{}
|
|
|
|
lines := strings.Split(strings.TrimSpace(output), "\n")
|
|
for _, line := range lines {
|
|
e := parseMapEntry(line, hasID)
|
|
if e != nil {
|
|
me = append(me, e)
|
|
}
|
|
}
|
|
return me
|
|
}
|
|
|
|
// parseMapEntry parses one entry in one map file/runtime and returns it structured
|
|
func parseMapEntry(line string, hasID bool) *models.MapEntry {
|
|
if line == "" || strings.HasPrefix(strings.TrimSpace(line), "#") {
|
|
return nil
|
|
}
|
|
|
|
parts := strings.Fields(line)
|
|
if len(parts) < 2 {
|
|
return nil
|
|
}
|
|
|
|
m := &models.MapEntry{}
|
|
if hasID {
|
|
m.ID = parts[0] // map entries from runtime have ID
|
|
m.Key = parts[1]
|
|
m.Value = parts[2]
|
|
} else {
|
|
m.Key = parts[0] // map entries from file
|
|
m.Value = parts[1]
|
|
}
|
|
return m
|
|
}
|
|
|
|
func parseMapEntriesFromFile(inputFile io.Reader, hasID bool) models.MapEntries {
|
|
me := models.MapEntries{}
|
|
|
|
scanner := bufio.NewScanner(inputFile)
|
|
for scanner.Scan() {
|
|
e := parseMapEntry(scanner.Text(), hasID)
|
|
if e != nil {
|
|
me = append(me, e)
|
|
}
|
|
}
|
|
return me
|
|
}
|
|
|
|
// AddMapEntry adds an entry into the map file
|
|
func (s *SingleRuntime) AddMapEntry(name, key, value string) error {
|
|
m, _ := s.GetMapEntry(name, key)
|
|
if m != nil {
|
|
return fmt.Errorf("%w", native_errors.ErrAlreadyExists)
|
|
}
|
|
cmd := fmt.Sprintf("add map %s %s %s", name, key, value)
|
|
if err := s.Execute(cmd); err != nil {
|
|
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AddMapEntryVersioned adds an entry into the map file
|
|
func (s *SingleRuntime) AddMapEntryVersioned(version, name, key, value string) error {
|
|
m, _ := s.GetMapEntry(name, key)
|
|
if m != nil {
|
|
return fmt.Errorf("%w", native_errors.ErrAlreadyExists)
|
|
}
|
|
cmd := fmt.Sprintf("add map @%s %s %s %s", version, name, key, value)
|
|
if err := s.Execute(cmd); err != nil {
|
|
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AddMapPayload adds multiple entries to the map file
|
|
// payload param is a multi-line string where each line is a key/value pair
|
|
func (s *SingleRuntime) AddMapPayload(name, payload string) error {
|
|
prefix := "<<\n"
|
|
if len(payload) < len(prefix) || payload[0:len(prefix)] != prefix {
|
|
payload = "<<\n" + payload + "\n"
|
|
}
|
|
cmd := fmt.Sprintf("add map %s %s", name, payload)
|
|
if err := s.Execute(cmd); err != nil {
|
|
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *SingleRuntime) PrepareMap(name string) (version string, err error) {
|
|
cmd := fmt.Sprintf("prepare map %s", name)
|
|
response, err := s.ExecuteWithResponse(cmd)
|
|
if err != nil {
|
|
return "", fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
|
|
}
|
|
parts := strings.Split(response, ":")
|
|
if len(parts) < 3 {
|
|
return "", fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
|
|
}
|
|
version = strings.TrimSpace(parts[2])
|
|
if _, err = strconv.ParseInt(version, 10, 64); err == nil {
|
|
return version, nil
|
|
}
|
|
return "", fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
|
|
}
|
|
|
|
func (s *SingleRuntime) AddMapPayloadVersioned(version, name, payload string) error {
|
|
prefix := "<<\n"
|
|
if len(payload) < len(prefix) || payload[0:len(prefix)] != prefix {
|
|
payload = "<<\n" + payload + "\n"
|
|
}
|
|
cmd := fmt.Sprintf("add map @%s %s %s", version, name, payload)
|
|
if err := s.Execute(cmd); err != nil {
|
|
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *SingleRuntime) CommitMap(version, name string) error {
|
|
cmd := fmt.Sprintf("commit map @%s %s", version, name)
|
|
if err := s.Execute(cmd); err != nil {
|
|
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrGeneral)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetMapEntry returns one map runtime setting
|
|
func (s *SingleRuntime) GetMapEntry(name, id string) (*models.MapEntry, error) {
|
|
cmd := fmt.Sprintf("get map %s %s", name, id)
|
|
response, err := s.ExecuteWithResponse(cmd)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s %w", err.Error(), native_errors.ErrNotFound)
|
|
}
|
|
|
|
m := &models.MapEntry{}
|
|
parts := strings.Split(response, ",")
|
|
for _, p := range parts {
|
|
kv := strings.Split(p, "=")
|
|
switch key := strings.TrimSpace(kv[0]); {
|
|
case key == "key":
|
|
m.Key = strings.TrimPrefix(strings.TrimSuffix(kv[1], "\""), "\"")
|
|
case key == "value":
|
|
m.Value = strings.TrimPrefix(strings.TrimSuffix(kv[1], "\""), "\"")
|
|
}
|
|
}
|
|
// safe guard m.Key != id:
|
|
// when id doesn't exist in runtime maps,
|
|
// but any existing key is substring of id
|
|
// get map command returns wrong result(BUG in HAProxy)
|
|
// so we need to check it
|
|
if m.Key == "" || m.Value == "" || m.Key != id {
|
|
return nil, fmt.Errorf("%s %w", id, native_errors.ErrNotFound)
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// SetMapEntry replaces the value corresponding to each id in a map
|
|
func (s *SingleRuntime) SetMapEntry(name, id, value string) error {
|
|
cmd := fmt.Sprintf("set map %s %s %s", name, id, value)
|
|
if err := s.Execute(cmd); err != nil {
|
|
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrNotFound)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteMapEntry deletes all the map entries from the map by its id
|
|
func (s *SingleRuntime) DeleteMapEntry(name, id string) error {
|
|
cmd := fmt.Sprintf("del map %s %s", name, id)
|
|
if err := s.Execute(cmd); err != nil {
|
|
return fmt.Errorf("%s %w", err.Error(), native_errors.ErrNotFound)
|
|
}
|
|
return nil
|
|
}
|
|
|