github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/worker/uniter/runner/jujuc/relation-set.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package jujuc
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/utils/keyvalues"
    14  	goyaml "gopkg.in/yaml.v1"
    15  	"launchpad.net/gnuflag"
    16  )
    17  
    18  const relationSetDoc = `
    19  "relation-set" writes the local unit's settings for some relation.
    20  If no relation is specified then the current relation is used. The
    21  setting values are not inspected and are stored as strings. Setting
    22  an empty string causes the setting to be removed. Duplicate settings
    23  are not allowed.
    24  
    25  The --file option should be used when one or more key-value pairs are
    26  too long to fit within the command length limit of the shell or
    27  operating system. The file will contain a YAML map containing the
    28  settings.  Settings in the file will be overridden by any duplicate
    29  key-value arguments. A value of "-" for the filename means <stdin>.
    30  `
    31  
    32  // RelationSetCommand implements the relation-set command.
    33  type RelationSetCommand struct {
    34  	cmd.CommandBase
    35  	ctx             Context
    36  	RelationId      int
    37  	relationIdProxy gnuflag.Value
    38  	Settings        map[string]string
    39  	settingsFile    cmd.FileVar
    40  	formatFlag      string // deprecated
    41  }
    42  
    43  func NewRelationSetCommand(ctx Context) (cmd.Command, error) {
    44  	c := &RelationSetCommand{ctx: ctx}
    45  
    46  	rV, err := newRelationIdValue(ctx, &c.RelationId)
    47  	if err != nil {
    48  		return nil, errors.Trace(err)
    49  	}
    50  	c.relationIdProxy = rV
    51  
    52  	return c, nil
    53  }
    54  
    55  func (c *RelationSetCommand) Info() *cmd.Info {
    56  	return &cmd.Info{
    57  		Name:    "relation-set",
    58  		Args:    "key=value [key=value ...]",
    59  		Purpose: "set relation settings",
    60  		Doc:     relationSetDoc,
    61  	}
    62  }
    63  
    64  func (c *RelationSetCommand) SetFlags(f *gnuflag.FlagSet) {
    65  	f.Var(c.relationIdProxy, "r", "specify a relation by id")
    66  	f.Var(c.relationIdProxy, "relation", "")
    67  
    68  	c.settingsFile.SetStdin()
    69  	f.Var(&c.settingsFile, "file", "file containing key-value pairs")
    70  
    71  	f.StringVar(&c.formatFlag, "format", "", "deprecated format flag")
    72  }
    73  
    74  func (c *RelationSetCommand) Init(args []string) error {
    75  	if c.RelationId == -1 {
    76  		return errors.Errorf("no relation id specified")
    77  	}
    78  
    79  	// The overrides will be applied during Run when c.settingsFile is handled.
    80  	overrides, err := keyvalues.Parse(args, true)
    81  	if err != nil {
    82  		return errors.Trace(err)
    83  	}
    84  	c.Settings = overrides
    85  	return nil
    86  }
    87  
    88  func (c *RelationSetCommand) readSettings(in io.Reader) (map[string]string, error) {
    89  	data, err := ioutil.ReadAll(in)
    90  	if err != nil {
    91  		return nil, errors.Trace(err)
    92  	}
    93  
    94  	skipValidation := false // for debugging
    95  	if !skipValidation {
    96  		// Can this validation be done more simply or efficiently?
    97  
    98  		var scalar string
    99  		if err := goyaml.Unmarshal(data, &scalar); err != nil {
   100  			return nil, errors.Trace(err)
   101  		}
   102  		if scalar != "" {
   103  			return nil, errors.Errorf("expected YAML map, got %q", scalar)
   104  		}
   105  
   106  		var sequence []string
   107  		if err := goyaml.Unmarshal(data, &sequence); err != nil {
   108  			return nil, errors.Trace(err)
   109  		}
   110  		if len(sequence) != 0 {
   111  			return nil, errors.Errorf("expected YAML map, got %#v", sequence)
   112  		}
   113  	}
   114  
   115  	kvs := make(map[string]string)
   116  	if err := goyaml.Unmarshal(data, kvs); err != nil {
   117  		return nil, errors.Trace(err)
   118  	}
   119  
   120  	return kvs, nil
   121  }
   122  
   123  func (c *RelationSetCommand) handleSettingsFile(ctx *cmd.Context) error {
   124  	if c.settingsFile.Path == "" {
   125  		return nil
   126  	}
   127  
   128  	file, err := c.settingsFile.Open(ctx)
   129  	if err != nil {
   130  		return errors.Trace(err)
   131  	}
   132  	defer file.Close()
   133  
   134  	settings, err := c.readSettings(file)
   135  	if err != nil {
   136  		return errors.Trace(err)
   137  	}
   138  
   139  	overrides := c.Settings
   140  	for k, v := range overrides {
   141  		settings[k] = v
   142  	}
   143  	c.Settings = settings
   144  	return nil
   145  }
   146  
   147  func (c *RelationSetCommand) Run(ctx *cmd.Context) (err error) {
   148  	if c.formatFlag != "" {
   149  		fmt.Fprintf(ctx.Stderr, "--format flag deprecated for command %q", c.Info().Name)
   150  	}
   151  	if err := c.handleSettingsFile(ctx); err != nil {
   152  		return errors.Trace(err)
   153  	}
   154  
   155  	r, err := c.ctx.Relation(c.RelationId)
   156  	if err != nil {
   157  		return errors.Trace(err)
   158  	}
   159  	settings, err := r.Settings()
   160  	if err != nil {
   161  		return errors.Annotate(err, "cannot read relation settings")
   162  	}
   163  	for k, v := range c.Settings {
   164  		if v != "" {
   165  			settings.Set(k, v)
   166  		} else {
   167  			settings.Delete(k)
   168  		}
   169  	}
   170  	return nil
   171  }