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 }