github.com/outbrain/consul@v1.4.5/agent/util.go (about)

     1  package agent
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/md5"
     6  	"fmt"
     7  	"os"
     8  	"os/exec"
     9  	"os/signal"
    10  	osuser "os/user"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/hashicorp/consul/types"
    16  	"github.com/hashicorp/go-msgpack/codec"
    17  )
    18  
    19  // msgpackHandle is a shared handle for encoding/decoding of
    20  // messages
    21  var msgpackHandle = &codec.MsgpackHandle{
    22  	RawToString: true,
    23  	WriteExt:    true,
    24  }
    25  
    26  // decodeMsgPack is used to decode a MsgPack encoded object
    27  func decodeMsgPack(buf []byte, out interface{}) error {
    28  	return codec.NewDecoder(bytes.NewReader(buf), msgpackHandle).Decode(out)
    29  }
    30  
    31  // encodeMsgPack is used to encode an object with msgpack
    32  func encodeMsgPack(msg interface{}) ([]byte, error) {
    33  	var buf bytes.Buffer
    34  	err := codec.NewEncoder(&buf, msgpackHandle).Encode(msg)
    35  	return buf.Bytes(), err
    36  }
    37  
    38  // stringHash returns a simple md5sum for a string.
    39  func stringHash(s string) string {
    40  	return fmt.Sprintf("%x", md5.Sum([]byte(s)))
    41  }
    42  
    43  // checkIDHash returns a simple md5sum for a types.CheckID.
    44  func checkIDHash(checkID types.CheckID) string {
    45  	return stringHash(string(checkID))
    46  }
    47  
    48  // setFilePermissions handles configuring ownership and permissions
    49  // settings on a given file. All permission/ownership settings are
    50  // optional. If no user or group is specified, the current user/group
    51  // will be used. Mode is optional, and has no default (the operation is
    52  // not performed if absent). User may be specified by name or ID, but
    53  // group may only be specified by ID.
    54  func setFilePermissions(path string, user, group, mode string) error {
    55  	var err error
    56  	uid, gid := os.Getuid(), os.Getgid()
    57  
    58  	if user != "" {
    59  		if uid, err = strconv.Atoi(user); err == nil {
    60  			goto GROUP
    61  		}
    62  
    63  		// Try looking up the user by name
    64  		u, err := osuser.Lookup(user)
    65  		if err != nil {
    66  			return fmt.Errorf("failed to look up user %s: %v", user, err)
    67  		}
    68  		uid, _ = strconv.Atoi(u.Uid)
    69  	}
    70  
    71  GROUP:
    72  	if group != "" {
    73  		if gid, err = strconv.Atoi(group); err != nil {
    74  			return fmt.Errorf("invalid group specified: %v", group)
    75  		}
    76  	}
    77  	if err := os.Chown(path, uid, gid); err != nil {
    78  		return fmt.Errorf("failed setting ownership to %d:%d on %q: %s",
    79  			uid, gid, path, err)
    80  	}
    81  
    82  	if mode != "" {
    83  		mode, err := strconv.ParseUint(mode, 8, 32)
    84  		if err != nil {
    85  			return fmt.Errorf("invalid mode specified: %v", mode)
    86  		}
    87  		if err := os.Chmod(path, os.FileMode(mode)); err != nil {
    88  			return fmt.Errorf("failed setting permissions to %d on %q: %s",
    89  				mode, path, err)
    90  		}
    91  	}
    92  
    93  	return nil
    94  }
    95  
    96  // ForwardSignals will fire up a goroutine to forward signals to the given
    97  // subprocess until the shutdown channel is closed.
    98  func ForwardSignals(cmd *exec.Cmd, logFn func(error), shutdownCh <-chan struct{}) {
    99  	go func() {
   100  		signalCh := make(chan os.Signal, 10)
   101  		signal.Notify(signalCh, forwardSignals...)
   102  		defer signal.Stop(signalCh)
   103  
   104  		for {
   105  			select {
   106  			case sig := <-signalCh:
   107  				if err := cmd.Process.Signal(sig); err != nil {
   108  					logFn(fmt.Errorf("failed to send signal %q: %v", sig, err))
   109  				}
   110  
   111  			case <-shutdownCh:
   112  				return
   113  			}
   114  		}
   115  	}()
   116  }
   117  
   118  type durationFixer map[string]bool
   119  
   120  func NewDurationFixer(fields ...string) durationFixer {
   121  	d := make(map[string]bool)
   122  	for _, field := range fields {
   123  		d[field] = true
   124  	}
   125  	return d
   126  }
   127  
   128  // FixupDurations is used to handle parsing any field names in the map to time.Durations
   129  func (d durationFixer) FixupDurations(raw interface{}) error {
   130  	rawMap, ok := raw.(map[string]interface{})
   131  	if !ok {
   132  		return nil
   133  	}
   134  	for key, val := range rawMap {
   135  		switch val.(type) {
   136  		case map[string]interface{}:
   137  			if err := d.FixupDurations(val); err != nil {
   138  				return err
   139  			}
   140  
   141  		case []interface{}:
   142  			for _, v := range val.([]interface{}) {
   143  				if err := d.FixupDurations(v); err != nil {
   144  					return err
   145  				}
   146  			}
   147  
   148  		case []map[string]interface{}:
   149  			for _, v := range val.([]map[string]interface{}) {
   150  				if err := d.FixupDurations(v); err != nil {
   151  					return err
   152  				}
   153  			}
   154  
   155  		default:
   156  			if d[strings.ToLower(key)] {
   157  				// Convert a string value into an integer
   158  				if vStr, ok := val.(string); ok {
   159  					dur, err := time.ParseDuration(vStr)
   160  					if err != nil {
   161  						return err
   162  					}
   163  					rawMap[key] = dur
   164  				}
   165  			}
   166  		}
   167  	}
   168  	return nil
   169  }