github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/service/common/conf.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common
     5  
     6  import (
     7  	"reflect"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/utils/v3/shell"
    13  )
    14  
    15  // Conf is responsible for defining services. Its fields
    16  // represent elements of a service configuration.
    17  type Conf struct {
    18  	// Desc is the init service's description.
    19  	Desc string
    20  
    21  	// Transient indicates whether or not the service is a one-off.
    22  	Transient bool
    23  
    24  	// AfterStopped is the name, if any, of another service. This
    25  	// service will not start until after the other stops.
    26  	AfterStopped string
    27  
    28  	// Env holds the environment variables that will be set when the
    29  	// command runs.
    30  	Env map[string]string
    31  
    32  	// TODO(ericsnow) Add a Limit type, since the possible keys are known.
    33  
    34  	// Limit holds the ulimit values that will be set when the command
    35  	// runs. Each value will be used as both the soft and hard limit.
    36  	// Valid values are integers or "infinity"
    37  	Limit map[string]string
    38  
    39  	// Timeout is how many seconds may pass before an exec call (e.g.
    40  	// ExecStart) times out. Values less than or equal to 0 (the
    41  	// default) are treated as though there is no timeout.
    42  	Timeout int
    43  
    44  	// ExecStart is the command (with arguments) that will be run. The
    45  	// path to the executable must be absolute.
    46  	// The command will be restarted if it exits with a non-zero exit code.
    47  	ExecStart string
    48  
    49  	// ExecStopPost is the command that will be run after the service stops.
    50  	// The path to the executable must be absolute.
    51  	ExecStopPost string
    52  
    53  	// Logfile, if set, indicates where the service's output should be
    54  	// written.
    55  	Logfile string
    56  
    57  	// TODO(ericsnow) Turn ExtraScript into ExecStartPre.
    58  
    59  	// ExtraScript allows to insert script before command execution.
    60  	ExtraScript string
    61  
    62  	// ServiceBinary is the actual binary without any arguments.
    63  	ServiceBinary string
    64  
    65  	// ServiceArgs is a string array of unquoted arguments
    66  	ServiceArgs []string
    67  }
    68  
    69  // IsZero determines whether or not the conf is a zero value.
    70  func (c Conf) IsZero() bool {
    71  	return reflect.DeepEqual(c, Conf{})
    72  }
    73  
    74  // Validate checks the conf's values for correctness.
    75  func (c Conf) Validate(renderer shell.Renderer) error {
    76  	if c.Desc == "" {
    77  		return errors.New("missing Desc")
    78  	}
    79  
    80  	// Check the Exec* fields.
    81  	if c.ExecStart == "" {
    82  		return errors.New("missing ExecStart")
    83  	}
    84  
    85  	for field, cmd := range map[string]string{
    86  		"ExecStart":    c.ExecStart,
    87  		"ExecStopPost": c.ExecStopPost,
    88  	} {
    89  		if cmd == "" {
    90  			continue
    91  		}
    92  		if err := c.checkExec(field, cmd, renderer); err != nil {
    93  			return errors.Trace(err)
    94  		}
    95  	}
    96  
    97  	for _, value := range c.Limit {
    98  		switch value {
    99  		case "unlimited", "infinity":
   100  		default:
   101  			_, err := strconv.Atoi(value)
   102  			if err != nil {
   103  				newErr := errors.NotValidf("limit must be \"infinity\", \"unlimited\", or an integer, %q", value)
   104  				return errors.Wrap(err, newErr)
   105  			}
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  func (c Conf) checkExec(name, cmd string, renderer shell.Renderer) error {
   113  	executable := strings.Fields(cmd)[0]
   114  	executable = Unquote(executable)
   115  	if !renderer.IsAbs(executable) {
   116  		return errors.NotValidf("relative path in %s (%s)", name, executable)
   117  	}
   118  	return nil
   119  }
   120  
   121  // Unquote returns the string embedded between matching quotation marks.
   122  // If there aren't any matching quotation marks then the string is
   123  // returned as-is.
   124  func Unquote(str string) string {
   125  	if len(str) < 2 {
   126  		return str
   127  	}
   128  	for _, quote := range []string{`'`, `"`} {
   129  		if str[0:1] == quote && str[len(str)-1:] == quote {
   130  			return strings.Trim(str, quote)
   131  		}
   132  	}
   133  	return str
   134  }