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