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 }