github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/runner/jujuc/relation-get.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 9 "github.com/juju/cmd/v3" 10 "github.com/juju/errors" 11 "github.com/juju/gnuflag" 12 "github.com/juju/names/v5" 13 14 jujucmd "github.com/juju/juju/cmd" 15 "github.com/juju/juju/rpc/params" 16 ) 17 18 // RelationGetCommand implements the relation-get command. 19 type RelationGetCommand struct { 20 cmd.CommandBase 21 ctx Context 22 23 RelationId int 24 relationIdProxy gnuflag.Value 25 Application bool 26 27 Key string 28 UnitOrAppName string 29 out cmd.Output 30 } 31 32 func NewRelationGetCommand(ctx Context) (cmd.Command, error) { 33 var err error 34 cmd := &RelationGetCommand{ctx: ctx} 35 cmd.relationIdProxy, err = NewRelationIdValue(ctx, &cmd.RelationId) 36 if err != nil { 37 return nil, errors.Trace(err) 38 } 39 40 return cmd, nil 41 } 42 43 // Info is part of the cmd.Command interface. 44 func (c *RelationGetCommand) Info() *cmd.Info { 45 args := "<key> <unit id>" 46 doc := ` 47 relation-get prints the value of a unit's relation setting, specified by key. 48 If no key is given, or if the key is "-", all keys and values will be printed. 49 50 A unit can see its own settings by calling "relation-get - MYUNIT", this will include 51 any changes that have been made with "relation-set". 52 53 When reading remote relation data, a charm can call relation-get --app - to get 54 the data for the application data bag that is set by the remote applications 55 leader. 56 ` 57 // There's nothing we can really do about the error here. 58 if name, err := c.ctx.RemoteUnitName(); err == nil { 59 args = "[<key> [<unit id>]]" 60 doc += fmt.Sprintf("Current default unit id is %q.", name) 61 } else if !errors.IsNotFound(err) { 62 logger.Errorf("Failed to retrieve remote unit name: %v", err) 63 } 64 return jujucmd.Info(&cmd.Info{ 65 Name: "relation-get", 66 Args: args, 67 Purpose: "get relation settings", 68 Doc: doc, 69 }) 70 } 71 72 // SetFlags is part of the cmd.Command interface. 73 func (c *RelationGetCommand) SetFlags(f *gnuflag.FlagSet) { 74 c.out.AddFlags(f, "smart", cmd.DefaultFormatters.Formatters()) 75 f.Var(c.relationIdProxy, "r", "Specify a relation by id") 76 f.Var(c.relationIdProxy, "relation", "") 77 78 f.BoolVar(&c.Application, "app", false, 79 `Get the relation data for the overall application, not just a unit`) 80 } 81 82 func (c *RelationGetCommand) determineUnitOrAppName(args *[]string) error { 83 // The logic is as follows: 84 // 1) If a user supplies a unit or app name, that overrides any default 85 // a) If they supply --app and a unit name, we turn that back into an application name 86 // b) note, if they *don't* supply --app, and they specify an app name, that should be an error 87 // 2) If no unit/app is supplied then we look at our context 88 // a) If --app is specified, then we use the context app 89 // b) If --app is not specified, but we don't have a context unit but do have a context app 90 // then we set --app, and set the target as the app 91 // c) If we have a context unit, then that is used 92 if len(*args) > 0 { 93 userSupplied := (*args)[0] 94 *args = (*args)[1:] 95 if c.Application { 96 if names.IsValidApplication(userSupplied) { 97 c.UnitOrAppName = userSupplied 98 } else if names.IsValidUnit(userSupplied) { 99 appName, err := names.UnitApplication(userSupplied) 100 if err != nil { 101 // Shouldn't happen, as we just validated it is a valid unit name 102 return errors.Trace(err) 103 } 104 c.UnitOrAppName = appName 105 } 106 } else { 107 if !names.IsValidUnit(userSupplied) { 108 if names.IsValidApplication(userSupplied) { 109 return fmt.Errorf("expected unit name, got application name %q", userSupplied) 110 } 111 return fmt.Errorf("invalid unit name %q", userSupplied) 112 } 113 c.UnitOrAppName = userSupplied 114 } 115 return nil 116 } 117 if c.Application { 118 name, err := c.ctx.RemoteApplicationName() 119 if errors.Is(err, errors.NotFound) { 120 return fmt.Errorf("no unit or application specified") 121 } else if err != nil { 122 return errors.Trace(err) 123 } 124 c.UnitOrAppName = name 125 return nil 126 } 127 // No args, no flags, check if there is a Unit context, or an App context 128 if name, err := c.ctx.RemoteUnitName(); err == nil { 129 c.UnitOrAppName = name 130 return nil 131 } else if !errors.IsNotFound(err) { 132 return errors.Trace(err) 133 } 134 // Unit name not found, look for app context 135 136 if name, err := c.ctx.RemoteApplicationName(); err == nil { 137 c.UnitOrAppName = name 138 c.Application = true 139 return nil 140 } else if !errors.IsNotFound(err) { 141 return errors.Trace(err) 142 } 143 // If we got this far, there is no default value to give and nothing was 144 // supplied, so it is an error 145 return errors.New("no unit or application specified") 146 } 147 148 // Init is part of the cmd.Command interface. 149 func (c *RelationGetCommand) Init(args []string) error { 150 if c.RelationId == -1 { 151 return fmt.Errorf("no relation id specified") 152 } 153 c.Key = "" 154 if len(args) > 0 { 155 if c.Key = args[0]; c.Key == "-" { 156 c.Key = "" 157 } 158 args = args[1:] 159 } 160 161 if err := c.determineUnitOrAppName(&args); err != nil { 162 return errors.Trace(err) 163 } 164 return cmd.CheckEmpty(args) 165 } 166 167 func (c *RelationGetCommand) Run(ctx *cmd.Context) error { 168 r, err := c.ctx.Relation(c.RelationId) 169 if err != nil { 170 return errors.Trace(err) 171 } 172 173 settingsReaderFn := c.readLocalUnitOrAppSettings 174 175 getFromController, err := c.mustReadSettingsFromController() 176 if err != nil { 177 return errors.Trace(err) 178 } 179 if getFromController { 180 settingsReaderFn = c.readRemoteUnitOrAppSettings 181 } 182 183 settings, err := settingsReaderFn(r) 184 if err != nil { 185 return err 186 } 187 188 if c.Key == "" { 189 return c.out.Write(ctx, settings) 190 } 191 if value, ok := settings[c.Key]; ok { 192 return c.out.Write(ctx, value) 193 } 194 return c.out.Write(ctx, nil) 195 } 196 197 func (c *RelationGetCommand) mustReadSettingsFromController() (bool, error) { 198 localUnitName := c.ctx.UnitName() 199 if c.UnitOrAppName == localUnitName { 200 return false, nil 201 } 202 203 localAppName, _ := names.UnitApplication(c.ctx.UnitName()) 204 if c.UnitOrAppName == localAppName { 205 isLeader, err := c.ctx.IsLeader() 206 if err != nil { 207 return false, errors.Annotate(err, "cannot determine leadership status") 208 } 209 210 // If we are the leader for the requested app, read from local 211 // uniter context 212 if isLeader { 213 return false, nil 214 } 215 } 216 217 // Delegate the read to the controller 218 return true, nil 219 } 220 221 func (c *RelationGetCommand) readLocalUnitOrAppSettings(r ContextRelation) (params.Settings, error) { 222 var ( 223 node Settings 224 err error 225 ) 226 227 if c.Application { 228 node, err = r.ApplicationSettings() 229 } else { 230 node, err = r.Settings() 231 } 232 if err != nil { 233 return nil, err 234 } 235 236 return node.Map(), nil 237 } 238 239 func (c *RelationGetCommand) readRemoteUnitOrAppSettings(r ContextRelation) (params.Settings, error) { 240 if !c.Application { 241 return r.ReadSettings(c.UnitOrAppName) 242 } 243 244 return r.ReadApplicationSettings(c.UnitOrAppName) 245 }