github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/api/controller/crossmodelrelations/crossmodelrelations.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package crossmodelrelations
     5  
     6  import (
     7  	"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery"
     8  	"github.com/juju/clock"
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"gopkg.in/macaroon.v2"
    12  
    13  	"github.com/juju/juju/api/base"
    14  	apiwatcher "github.com/juju/juju/api/watcher"
    15  	"github.com/juju/juju/core/watcher"
    16  	"github.com/juju/juju/rpc/params"
    17  )
    18  
    19  var logger = loggo.GetLogger("juju.api.crossmodelrelations")
    20  
    21  // Client provides access to the crossmodelrelations api facade.
    22  type Client struct {
    23  	base.ClientFacade
    24  	facade base.FacadeCaller
    25  
    26  	cache *MacaroonCache
    27  }
    28  
    29  // NewClient creates a new client-side CrossModelRelations facade.
    30  func NewClient(caller base.APICallCloser) *Client {
    31  	return NewClientWithCache(caller, NewMacaroonCache(clock.WallClock))
    32  }
    33  
    34  // NewClientWithCache creates a new client-side CrossModelRelations facade
    35  // with the specified cache.
    36  func NewClientWithCache(caller base.APICallCloser, cache *MacaroonCache) *Client {
    37  	frontend, backend := base.NewClientFacade(caller, "CrossModelRelations")
    38  	return &Client{
    39  		ClientFacade: frontend,
    40  		facade:       backend,
    41  		cache:        cache,
    42  	}
    43  }
    44  
    45  // handleError is used to process an error obtained when making a facade call.
    46  // If the error indicates that a macaroon discharge is required, this is done
    47  // and the resulting discharge macaroons passed back so the api call can be retried.
    48  func (c *Client) handleError(apiErr error) (macaroon.Slice, error) {
    49  	if params.ErrCode(apiErr) != params.CodeDischargeRequired {
    50  		return nil, apiErr
    51  	}
    52  	errResp := errors.Cause(apiErr).(*params.Error)
    53  	if errResp.Info == nil {
    54  		return nil, errors.Annotatef(apiErr, "no error info found in discharge-required response error")
    55  	}
    56  	logger.Debugf("attempting to discharge macaroon due to error: %v", apiErr)
    57  	var info params.DischargeRequiredErrorInfo
    58  	if errUnmarshal := errResp.UnmarshalInfo(&info); errUnmarshal != nil {
    59  		return nil, errors.Annotatef(apiErr, "unable to extract macaroon details from discharge-required response error")
    60  	}
    61  
    62  	// Prefer the new bakery macaroon.
    63  	m := info.BakeryMacaroon
    64  	if m == nil {
    65  		var err error
    66  		m, err = bakery.NewLegacyMacaroon(info.Macaroon)
    67  		if err != nil {
    68  			return nil, errors.Wrap(apiErr, err)
    69  		}
    70  	}
    71  	ms, err := c.facade.RawAPICaller().BakeryClient().DischargeAll(c.facade.RawAPICaller().Context(), m)
    72  	if err == nil && logger.IsTraceEnabled() {
    73  		logger.Tracef("discharge macaroon ids:")
    74  		for _, m := range ms {
    75  			logger.Tracef("  - %v", m.Id())
    76  		}
    77  	}
    78  	if err != nil {
    79  		return nil, errors.Wrap(apiErr, err)
    80  	}
    81  	return ms, err
    82  }
    83  
    84  func (c *Client) getCachedMacaroon(opName, token string) (macaroon.Slice, bool) {
    85  	ms, ok := c.cache.Get(token)
    86  	if ok {
    87  		logger.Debugf("%s using cached macaroons for %s", opName, token)
    88  		if logger.IsTraceEnabled() {
    89  			for _, m := range ms {
    90  				logger.Tracef("  - %v", m.Id())
    91  			}
    92  		}
    93  	}
    94  	return ms, ok
    95  }
    96  
    97  // PublishRelationChange publishes relation changes to the
    98  // model hosting the remote application involved in the relation.
    99  func (c *Client) PublishRelationChange(change params.RemoteRelationChangeEvent) error {
   100  	args := params.RemoteRelationsChanges{
   101  		Changes: []params.RemoteRelationChangeEvent{change},
   102  	}
   103  	// Use any previously cached discharge macaroons.
   104  	if ms, ok := c.getCachedMacaroon("publish relation changed", change.RelationToken); ok {
   105  		args.Changes[0].Macaroons = ms
   106  		args.Changes[0].BakeryVersion = bakery.LatestVersion
   107  	}
   108  
   109  	apiCall := func() error {
   110  		var results params.ErrorResults
   111  		if err := c.facade.FacadeCall("PublishRelationChanges", args, &results); err != nil {
   112  			return errors.Trace(err)
   113  		}
   114  		err := results.OneError()
   115  		if params.IsCodeNotFound(err) {
   116  			return errors.NotFoundf("relation for event %v", change)
   117  		}
   118  		return err
   119  	}
   120  	// Make the api call the first time.
   121  	err := apiCall()
   122  	if err == nil || errors.IsNotFound(err) {
   123  		return errors.Trace(err)
   124  	}
   125  
   126  	// On error, possibly discharge the macaroon and retry.
   127  	mac, err2 := c.handleError(err)
   128  	if err2 != nil {
   129  		return errors.Trace(err2)
   130  	}
   131  	args.Changes[0].Macaroons = mac
   132  	args.Changes[0].BakeryVersion = bakery.LatestVersion
   133  	c.cache.Upsert(args.Changes[0].RelationToken, mac)
   134  	return apiCall()
   135  }
   136  
   137  func (c *Client) PublishIngressNetworkChange(change params.IngressNetworksChangeEvent) error {
   138  	args := params.IngressNetworksChanges{
   139  		Changes: []params.IngressNetworksChangeEvent{change},
   140  	}
   141  	// Use any previously cached discharge macaroons.
   142  	if ms, ok := c.getCachedMacaroon("publish ingress network change", change.RelationToken); ok {
   143  		args.Changes[0].Macaroons = ms
   144  		args.Changes[0].BakeryVersion = bakery.LatestVersion
   145  	}
   146  
   147  	apiCall := func() error {
   148  		var results params.ErrorResults
   149  		if err := c.facade.FacadeCall("PublishIngressNetworkChanges", args, &results); err != nil {
   150  			return errors.Trace(err)
   151  		}
   152  		return results.OneError()
   153  	}
   154  
   155  	// Make the api call the first time.
   156  	err := apiCall()
   157  	if err == nil {
   158  		return nil
   159  	}
   160  
   161  	// On error, possibly discharge the macaroon and retry.
   162  	mac, err2 := c.handleError(err)
   163  	if err2 != nil {
   164  		return errors.Trace(err2)
   165  	}
   166  	args.Changes[0].Macaroons = mac
   167  	args.Changes[0].BakeryVersion = bakery.LatestVersion
   168  	c.cache.Upsert(args.Changes[0].RelationToken, mac)
   169  	return apiCall()
   170  }
   171  
   172  // RegisterRemoteRelations sets up the remote model to participate
   173  // in the specified relations.
   174  func (c *Client) RegisterRemoteRelations(relations ...params.RegisterRemoteRelationArg) ([]params.RegisterRemoteRelationResult, error) {
   175  	var (
   176  		args         params.RegisterRemoteRelationArgs
   177  		retryIndices []int
   178  	)
   179  
   180  	args = params.RegisterRemoteRelationArgs{Relations: relations}
   181  	// Use any previously cached discharge macaroons.
   182  	for i, arg := range relations {
   183  		if ms, ok := c.getCachedMacaroon("register remote relation", arg.RelationToken); ok {
   184  			newArg := arg
   185  			newArg.Macaroons = ms
   186  			newArg.BakeryVersion = bakery.LatestVersion
   187  			args.Relations[i] = newArg
   188  		}
   189  	}
   190  
   191  	var results params.RegisterRemoteRelationResults
   192  	apiCall := func() error {
   193  		// Reset the results struct before each api call.
   194  		results = params.RegisterRemoteRelationResults{}
   195  		err := c.facade.FacadeCall("RegisterRemoteRelations", args, &results)
   196  		if err != nil {
   197  			return errors.Trace(err)
   198  		}
   199  		if len(results.Results) != len(args.Relations) {
   200  			return errors.Errorf("expected %d result(s), got %d", len(args.Relations), len(results.Results))
   201  		}
   202  		return nil
   203  	}
   204  
   205  	// Make the api call the first time.
   206  	if err := apiCall(); err != nil {
   207  		return nil, errors.Trace(err)
   208  	}
   209  	// On error, possibly discharge the macaroon and retry.
   210  	result := results.Results
   211  	args = params.RegisterRemoteRelationArgs{}
   212  	// Separate the successful calls from those needing a retry.
   213  	for i, res := range results.Results {
   214  		if res.Error == nil {
   215  			continue
   216  		}
   217  		mac, err := c.handleError(res.Error)
   218  		if err != nil {
   219  			resCopy := res
   220  			resCopy.Error.Message = err.Error()
   221  			result[i] = resCopy
   222  			continue
   223  		}
   224  		retryArg := relations[i]
   225  		retryArg.Macaroons = mac
   226  		retryArg.BakeryVersion = bakery.LatestVersion
   227  		args.Relations = append(args.Relations, retryArg)
   228  		retryIndices = append(retryIndices, i)
   229  		c.cache.Upsert(retryArg.RelationToken, mac)
   230  	}
   231  	// Nothing to retry so return the original result.
   232  	if len(args.Relations) == 0 {
   233  		return result, nil
   234  	}
   235  
   236  	if err := apiCall(); err != nil {
   237  		return nil, errors.Trace(err)
   238  	}
   239  	// After a retry, insert the results into the original result slice.
   240  	for j, res := range results.Results {
   241  		resCopy := res
   242  		result[retryIndices[j]] = resCopy
   243  	}
   244  	return result, nil
   245  }
   246  
   247  // WatchRelationChanges returns a watcher that notifies of changes to
   248  // the units or application settings in the remote model for the
   249  // relation with the given remote token.
   250  func (c *Client) WatchRelationChanges(relationToken, applicationToken string, macs macaroon.Slice) (apiwatcher.RemoteRelationWatcher, error) {
   251  	args := params.RemoteEntityArgs{Args: []params.RemoteEntityArg{{
   252  		Token:         relationToken,
   253  		Macaroons:     macs,
   254  		BakeryVersion: bakery.LatestVersion,
   255  	}}}
   256  	// Use any previously cached discharge macaroons.
   257  	if ms, ok := c.getCachedMacaroon("watch relation changes", relationToken); ok {
   258  		args.Args[0].Macaroons = ms
   259  		args.Args[0].BakeryVersion = bakery.LatestVersion
   260  	}
   261  
   262  	var results params.RemoteRelationWatchResults
   263  	apiCall := func() error {
   264  		// Reset the results struct before each api call.
   265  		results = params.RemoteRelationWatchResults{}
   266  		if err := c.facade.FacadeCall("WatchRelationChanges", args, &results); err != nil {
   267  			return errors.Trace(err)
   268  		}
   269  		if len(results.Results) != 1 {
   270  			return errors.Errorf("expected 1 result, got %d", len(results.Results))
   271  		}
   272  		return nil
   273  	}
   274  
   275  	// Make the api call the first time.
   276  	if err := apiCall(); err != nil {
   277  		return nil, errors.Trace(err)
   278  	}
   279  
   280  	// On error, possibly discharge the macaroon and retry.
   281  	result := results.Results[0]
   282  	if result.Error != nil {
   283  		mac, err := c.handleError(result.Error)
   284  		if err != nil {
   285  			result.Error.Message = err.Error()
   286  			return nil, result.Error
   287  		}
   288  		args.Args[0].Macaroons = mac
   289  		args.Args[0].BakeryVersion = bakery.LatestVersion
   290  		c.cache.Upsert(args.Args[0].Token, mac)
   291  
   292  		if err := apiCall(); err != nil {
   293  			return nil, errors.Trace(err)
   294  		}
   295  		result = results.Results[0]
   296  	}
   297  	if result.Error != nil {
   298  		return nil, result.Error
   299  	}
   300  
   301  	w := apiwatcher.NewRemoteRelationWatcher(c.facade.RawAPICaller(), result)
   302  	return w, nil
   303  }
   304  
   305  // WatchEgressAddressesForRelation returns a watcher that notifies when addresses,
   306  // from which connections will originate to the offering side of the relation, change.
   307  // Each event contains the entire set of addresses which the offering side is required
   308  // to allow for access to the other side of the relation.
   309  func (c *Client) WatchEgressAddressesForRelation(remoteRelationArg params.RemoteEntityArg) (watcher.StringsWatcher, error) {
   310  	args := params.RemoteEntityArgs{Args: []params.RemoteEntityArg{remoteRelationArg}}
   311  	// Use any previously cached discharge macaroons.
   312  	if ms, ok := c.getCachedMacaroon("watch relation egress addresses", remoteRelationArg.Token); ok {
   313  		args.Args[0].Macaroons = ms
   314  		args.Args[0].BakeryVersion = bakery.LatestVersion
   315  	}
   316  
   317  	var results params.StringsWatchResults
   318  	apiCall := func() error {
   319  		// Reset the results struct before each api call.
   320  		results = params.StringsWatchResults{}
   321  		if err := c.facade.FacadeCall("WatchEgressAddressesForRelations", args, &results); err != nil {
   322  			return errors.Trace(err)
   323  		}
   324  		if len(results.Results) != 1 {
   325  			return errors.Errorf("expected 1 result, got %d", len(results.Results))
   326  		}
   327  		return nil
   328  	}
   329  
   330  	// Make the api call the first time.
   331  	if err := apiCall(); err != nil {
   332  		return nil, errors.Trace(err)
   333  	}
   334  
   335  	// On error, possibly discharge the macaroon and retry.
   336  	result := results.Results[0]
   337  	if result.Error != nil {
   338  		mac, err := c.handleError(result.Error)
   339  		if err != nil {
   340  			result.Error.Message = err.Error()
   341  			return nil, result.Error
   342  		}
   343  		args.Args[0].Macaroons = mac
   344  		args.Args[0].BakeryVersion = bakery.LatestVersion
   345  		c.cache.Upsert(args.Args[0].Token, mac)
   346  
   347  		if err := apiCall(); err != nil {
   348  			return nil, errors.Trace(err)
   349  		}
   350  		result = results.Results[0]
   351  	}
   352  	if result.Error != nil {
   353  		return nil, result.Error
   354  	}
   355  
   356  	w := apiwatcher.NewStringsWatcher(c.facade.RawAPICaller(), result)
   357  	return w, nil
   358  }
   359  
   360  // WatchRelationSuspendedStatus starts a RelationStatusWatcher for watching the life and
   361  // suspended status of the specified relation in the remote model.
   362  func (c *Client) WatchRelationSuspendedStatus(arg params.RemoteEntityArg) (watcher.RelationStatusWatcher, error) {
   363  	args := params.RemoteEntityArgs{Args: []params.RemoteEntityArg{arg}}
   364  	// Use any previously cached discharge macaroons.
   365  	if ms, ok := c.getCachedMacaroon("watch relation status", arg.Token); ok {
   366  		args.Args[0].Macaroons = ms
   367  		args.Args[0].BakeryVersion = bakery.LatestVersion
   368  	}
   369  
   370  	var results params.RelationStatusWatchResults
   371  	apiCall := func() error {
   372  		// Reset the results struct before each api call.
   373  		results = params.RelationStatusWatchResults{}
   374  		if err := c.facade.FacadeCall("WatchRelationsSuspendedStatus", args, &results); err != nil {
   375  			return errors.Trace(err)
   376  		}
   377  		if len(results.Results) != 1 {
   378  			return errors.Errorf("expected 1 result, got %d", len(results.Results))
   379  		}
   380  		return nil
   381  	}
   382  
   383  	// Make the api call the first time.
   384  	if err := apiCall(); err != nil {
   385  		return nil, errors.Trace(err)
   386  	}
   387  
   388  	// On error, possibly discharge the macaroon and retry.
   389  	result := results.Results[0]
   390  	if result.Error != nil {
   391  		mac, err := c.handleError(result.Error)
   392  		if err != nil {
   393  			result.Error.Message = err.Error()
   394  			return nil, result.Error
   395  		}
   396  		args.Args[0].Macaroons = mac
   397  		args.Args[0].BakeryVersion = bakery.LatestVersion
   398  		c.cache.Upsert(args.Args[0].Token, mac)
   399  
   400  		if err := apiCall(); err != nil {
   401  			return nil, errors.Trace(err)
   402  		}
   403  		result = results.Results[0]
   404  	}
   405  	if result.Error != nil {
   406  		return nil, result.Error
   407  	}
   408  
   409  	w := apiwatcher.NewRelationStatusWatcher(c.facade.RawAPICaller(), result)
   410  	return w, nil
   411  }
   412  
   413  // WatchOfferStatus starts an OfferStatusWatcher for watching the status
   414  // of the specified offer in the remote model.
   415  func (c *Client) WatchOfferStatus(arg params.OfferArg) (watcher.OfferStatusWatcher, error) {
   416  	args := params.OfferArgs{Args: []params.OfferArg{arg}}
   417  	// Use any previously cached discharge macaroons.
   418  	if ms, ok := c.getCachedMacaroon("watch offer status", arg.OfferUUID); ok {
   419  		args.Args[0].Macaroons = ms
   420  		args.Args[0].BakeryVersion = bakery.LatestVersion
   421  	}
   422  
   423  	var results params.OfferStatusWatchResults
   424  	apiCall := func() error {
   425  		// Reset the results struct before each api call.
   426  		results = params.OfferStatusWatchResults{}
   427  		if err := c.facade.FacadeCall("WatchOfferStatus", args, &results); err != nil {
   428  			return errors.Trace(err)
   429  		}
   430  		if len(results.Results) != 1 {
   431  			return errors.Errorf("expected 1 result, got %d", len(results.Results))
   432  		}
   433  		return nil
   434  	}
   435  
   436  	// Make the api call the first time.
   437  	if err := apiCall(); err != nil {
   438  		return nil, errors.Trace(err)
   439  	}
   440  
   441  	// On error, possibly discharge the macaroon and retry.
   442  	result := results.Results[0]
   443  	if result.Error != nil {
   444  		mac, err := c.handleError(result.Error)
   445  		if err != nil {
   446  			result.Error.Message = err.Error()
   447  			return nil, result.Error
   448  		}
   449  		args.Args[0].Macaroons = mac
   450  		c.cache.Upsert(args.Args[0].OfferUUID, mac)
   451  
   452  		if err := apiCall(); err != nil {
   453  			return nil, errors.Trace(err)
   454  		}
   455  		result = results.Results[0]
   456  	}
   457  	if result.Error != nil {
   458  		return nil, result.Error
   459  	}
   460  
   461  	w := apiwatcher.NewOfferStatusWatcher(c.facade.RawAPICaller(), result)
   462  	return w, nil
   463  }
   464  
   465  // WatchConsumedSecretsChanges returns a watcher which notifies of new secret revisions consumed by the
   466  // app with the specified token.
   467  func (c *Client) WatchConsumedSecretsChanges(applicationToken, relationToken string, mac *macaroon.Macaroon) (watcher.SecretsRevisionWatcher, error) {
   468  	// TODO(wallyworld) - when juju 3.4 is no longer supported, we can change this to < 3.
   469  	if c.BestAPIVersion() < 2 {
   470  		return nil, errors.NotImplemented
   471  	}
   472  	var macs macaroon.Slice
   473  	if mac != nil {
   474  		macs = macaroon.Slice{mac}
   475  	}
   476  
   477  	args := params.WatchRemoteSecretChangesArgs{Args: []params.WatchRemoteSecretChangesArg{{
   478  		ApplicationToken: applicationToken,
   479  		RelationToken:    relationToken,
   480  		Macaroons:        macs,
   481  		BakeryVersion:    bakery.LatestVersion,
   482  	}}}
   483  
   484  	// Use any previously cached discharge macaroons.
   485  	if ms, ok := c.getCachedMacaroon("watch consumed secret changes", relationToken); ok {
   486  		args.Args[0].Macaroons = ms
   487  		args.Args[0].BakeryVersion = bakery.LatestVersion
   488  	}
   489  
   490  	var results params.SecretRevisionWatchResults
   491  	apiCall := func() error {
   492  		// Reset the results struct before each api call.
   493  		results = params.SecretRevisionWatchResults{}
   494  		if err := c.facade.FacadeCall("WatchConsumedSecretsChanges", args, &results); err != nil {
   495  			return params.TranslateWellKnownError(err)
   496  		}
   497  		if len(results.Results) != 1 {
   498  			return errors.Errorf("expected 1 result, got %d", len(results.Results))
   499  		}
   500  		return nil
   501  	}
   502  
   503  	// Make the api call the first time.
   504  	if err := apiCall(); err != nil {
   505  		return nil, errors.Trace(err)
   506  	}
   507  
   508  	// On error, possibly discharge the macaroon and retry.
   509  	result := results.Results[0]
   510  	if result.Error != nil {
   511  		mac, err := c.handleError(result.Error)
   512  		if err != nil {
   513  			result.Error.Message = err.Error()
   514  			return nil, result.Error
   515  		}
   516  		args.Args[0].Macaroons = mac
   517  		c.cache.Upsert(relationToken, mac)
   518  
   519  		if err := apiCall(); err != nil {
   520  			return nil, errors.Trace(err)
   521  		}
   522  		result = results.Results[0]
   523  	}
   524  	if result.Error != nil {
   525  		return nil, params.TranslateWellKnownError(result.Error)
   526  	}
   527  
   528  	w := apiwatcher.NewSecretsRevisionWatcher(c.facade.RawAPICaller(), result)
   529  	return w, nil
   530  }