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  }