github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/dnsproxy/resolvefile_unix.go (about)

     1  //go:build !windows
     2  
     3  package dnsproxy
     4  
     5  import (
     6  	"bufio"
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"slices"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/telepresenceio/telepresence/v2/pkg/ioutil"
    16  )
    17  
    18  type ResolveFile struct {
    19  	Port        int
    20  	Domain      string
    21  	Nameservers []string
    22  	Search      []string
    23  	Options     []string
    24  }
    25  
    26  func ReadResolveFile(fileName string) (*ResolveFile, error) {
    27  	fl, err := os.Open(fileName)
    28  	if err != nil {
    29  		return nil, err
    30  	}
    31  	defer fl.Close()
    32  	sc := bufio.NewScanner(fl)
    33  	rf := ResolveFile{}
    34  	line := 0
    35  
    36  	onlyOne := func(key string) error {
    37  		return fmt.Errorf("%q must have a value at %s line %d", key, fileName, line)
    38  	}
    39  
    40  	for sc.Scan() {
    41  		line++
    42  		txt := strings.TrimSpace(sc.Text())
    43  		if len(txt) == 0 || strings.HasPrefix(txt, "#") {
    44  			continue
    45  		}
    46  		fields := strings.Fields(txt)
    47  		fc := len(fields)
    48  		if fc == 0 {
    49  			continue
    50  		}
    51  		key := fields[0]
    52  		if fc == 1 {
    53  			return nil, fmt.Errorf("%q must have a value at %s line %d", key, fileName, line)
    54  		}
    55  		value := fields[1]
    56  		switch key {
    57  		case "port":
    58  			if fc != 2 {
    59  				return nil, onlyOne(key)
    60  			}
    61  			rf.Port, err = strconv.Atoi(value)
    62  			if err != nil {
    63  				return nil, fmt.Errorf("%q is not a valid integer at %s line %d", key, fileName, line)
    64  			}
    65  		case "domain":
    66  			if fc != 2 {
    67  				return nil, onlyOne(key)
    68  			}
    69  			rf.Domain = value
    70  		case "nameserver":
    71  			if fc != 2 {
    72  				return nil, onlyOne(key)
    73  			}
    74  			rf.Nameservers = append(rf.Nameservers, value)
    75  		case "search":
    76  			rf.Search = fields[1:]
    77  		case "options":
    78  			rf.Options = fields[1:]
    79  		default:
    80  			// This reader doesn't do options just yet
    81  			return nil, fmt.Errorf("%q is not a recognized key at %s line %d", key, fileName, line)
    82  		}
    83  	}
    84  	return &rf, nil
    85  }
    86  
    87  func (r *ResolveFile) String() string {
    88  	var buf strings.Builder
    89  	_, _ = r.WriteTo(&buf)
    90  	return buf.String()
    91  }
    92  
    93  func (r *ResolveFile) Equals(o *ResolveFile) bool {
    94  	if r == nil || o == nil {
    95  		return r == o
    96  	}
    97  	return r.Port == o.Port &&
    98  		r.Domain == o.Domain &&
    99  		slices.Equal(r.Nameservers, o.Nameservers) &&
   100  		slices.Equal(r.Search, o.Search) &&
   101  		slices.Equal(r.Options, o.Options)
   102  }
   103  
   104  func (r *ResolveFile) Write(fileName string) error {
   105  	var buf bytes.Buffer
   106  	_, _ = r.WriteTo(&buf)
   107  	return os.WriteFile(fileName, buf.Bytes(), 0o644)
   108  }
   109  
   110  func (r *ResolveFile) WriteTo(buf io.Writer) (int64, error) {
   111  	n := ioutil.Println(buf, "# Generated by telepresence\n")
   112  	if r.Port > 0 {
   113  		n += ioutil.Printf(buf, "port %d\n", r.Port)
   114  	}
   115  	if r.Domain != "" {
   116  		n += ioutil.Printf(buf, "domain %s\n", r.Domain)
   117  	}
   118  	for _, ns := range r.Nameservers {
   119  		n += ioutil.Printf(buf, "nameserver %s\n", ns)
   120  	}
   121  	if len(r.Search) > 0 {
   122  		n += ioutil.Printf(buf, "search %s\n", strings.Join(r.Search, " "))
   123  	}
   124  	if len(r.Options) > 0 {
   125  		n += ioutil.Printf(buf, "options %s\n", strings.Join(r.Options, " "))
   126  	}
   127  	return int64(n), nil
   128  }