github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/service/set.go (about)

     1  // Copyright 2012-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package service
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"strings"
    12  	"unicode/utf8"
    13  
    14  	"github.com/juju/cmd"
    15  	"github.com/juju/utils/keyvalues"
    16  	"launchpad.net/gnuflag"
    17  
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/cmd/envcmd"
    20  	"github.com/juju/juju/cmd/juju/block"
    21  )
    22  
    23  // SetCommand updates the configuration of a service.
    24  type SetCommand struct {
    25  	envcmd.EnvCommandBase
    26  	ServiceName     string
    27  	SettingsStrings map[string]string
    28  	SettingsYAML    cmd.FileVar
    29  	api             SetServiceAPI
    30  }
    31  
    32  const setDoc = `
    33  Set one or more configuration options for the specified service. See also the
    34  unset command which sets one or more configuration options for a specified
    35  service to their default value.
    36  
    37  In case a value starts with an at sign (@) the rest of the value is interpreted
    38  as a filename. The value itself is then read out of the named file. The maximum
    39  size of this value is 5M.
    40  
    41  Option values may be any UTF-8 encoded string. UTF-8 is accepted on the command
    42  line and in configuration files.
    43  `
    44  
    45  const maxValueSize = 5242880
    46  
    47  func (c *SetCommand) Info() *cmd.Info {
    48  	return &cmd.Info{
    49  		Name:    "set",
    50  		Args:    "<service> name=value ...",
    51  		Purpose: "set service config options",
    52  		Doc:     setDoc,
    53  	}
    54  }
    55  
    56  func (c *SetCommand) SetFlags(f *gnuflag.FlagSet) {
    57  	f.Var(&c.SettingsYAML, "config", "path to yaml-formatted service config")
    58  }
    59  
    60  func (c *SetCommand) Init(args []string) error {
    61  	if len(args) == 0 || len(strings.Split(args[0], "=")) > 1 {
    62  		return errors.New("no service name specified")
    63  	}
    64  	if c.SettingsYAML.Path != "" && len(args) > 1 {
    65  		return errors.New("cannot specify --config when using key=value arguments")
    66  	}
    67  	c.ServiceName = args[0]
    68  	settings, err := keyvalues.Parse(args[1:], true)
    69  	if err != nil {
    70  		return err
    71  	}
    72  	c.SettingsStrings = settings
    73  	return nil
    74  }
    75  
    76  // SetServiceAPI defines the methods on the client API
    77  // that the service set command calls.
    78  type SetServiceAPI interface {
    79  	Close() error
    80  	ServiceSetYAML(service string, yaml string) error
    81  	ServiceGet(service string) (*params.ServiceGetResults, error)
    82  	ServiceSet(service string, options map[string]string) error
    83  }
    84  
    85  func (c *SetCommand) getAPI() (SetServiceAPI, error) {
    86  	if c.api != nil {
    87  		return c.api, nil
    88  	}
    89  	return c.NewAPIClient()
    90  }
    91  
    92  // Run updates the configuration of a service.
    93  func (c *SetCommand) Run(ctx *cmd.Context) error {
    94  	api, err := c.getAPI()
    95  	if err != nil {
    96  		return err
    97  	}
    98  	defer api.Close()
    99  
   100  	if c.SettingsYAML.Path != "" {
   101  		b, err := c.SettingsYAML.Read(ctx)
   102  		if err != nil {
   103  			return err
   104  		}
   105  		return block.ProcessBlockedError(api.ServiceSetYAML(c.ServiceName, string(b)), block.BlockChange)
   106  	} else if len(c.SettingsStrings) == 0 {
   107  		return nil
   108  	}
   109  	settings := map[string]string{}
   110  	for k, v := range c.SettingsStrings {
   111  		//empty string is also valid as a setting value
   112  		if v == "" {
   113  			settings[k] = v
   114  			continue
   115  		}
   116  
   117  		if v[0] != '@' {
   118  			if !utf8.ValidString(v) {
   119  				return fmt.Errorf("value for option %q contains non-UTF-8 sequences", k)
   120  			}
   121  			settings[k] = v
   122  			continue
   123  		}
   124  		nv, err := readValue(ctx, v[1:])
   125  		if err != nil {
   126  			return err
   127  		}
   128  		if !utf8.ValidString(nv) {
   129  			return fmt.Errorf("value for option %q contains non-UTF-8 sequences", k)
   130  		}
   131  		settings[k] = nv
   132  	}
   133  
   134  	result, err := api.ServiceGet(c.ServiceName)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	for k, v := range settings {
   140  		configValue := result.Config[k]
   141  
   142  		configValueMap, ok := configValue.(map[string]interface{})
   143  		if ok {
   144  			// convert the value to string and compare
   145  			if fmt.Sprintf("%v", configValueMap["value"]) == v {
   146  				logger.Warningf("the configuration setting %q already has the value %q", k, v)
   147  			}
   148  		}
   149  	}
   150  
   151  	return block.ProcessBlockedError(api.ServiceSet(c.ServiceName, settings), block.BlockChange)
   152  }
   153  
   154  // readValue reads the value of an option out of the named file.
   155  // An empty content is valid, like in parsing the options. The upper
   156  // size is 5M.
   157  func readValue(ctx *cmd.Context, filename string) (string, error) {
   158  	absFilename := ctx.AbsPath(filename)
   159  	fi, err := os.Stat(absFilename)
   160  	if err != nil {
   161  		return "", fmt.Errorf("cannot read option from file %q: %v", filename, err)
   162  	}
   163  	if fi.Size() > maxValueSize {
   164  		return "", fmt.Errorf("size of option file is larger than 5M")
   165  	}
   166  	content, err := ioutil.ReadFile(ctx.AbsPath(filename))
   167  	if err != nil {
   168  		return "", fmt.Errorf("cannot read option from file %q: %v", filename, err)
   169  	}
   170  	return string(content), nil
   171  }