github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/juju/set.go (about)

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