github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/common/lifeflag/facade.go (about) 1 // Copyright 2023 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lifeflag 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/names/v5" 9 10 "github.com/juju/juju/api/base" 11 apiwatcher "github.com/juju/juju/api/watcher" 12 "github.com/juju/juju/core/life" 13 "github.com/juju/juju/core/watcher" 14 "github.com/juju/juju/rpc/params" 15 ) 16 17 // NewWatcherFunc exists to let us test Watch properly. 18 type NewWatcherFunc func(base.APICaller, params.NotifyWatchResult) watcher.NotifyWatcher 19 20 // Client makes calls to the LifeFlag facade. 21 type Client struct { 22 caller base.FacadeCaller 23 } 24 25 // NewClient returns a new Facade using the supplied caller. 26 func NewClient(caller base.APICaller, facadeName string) *Client { 27 return &Client{ 28 caller: base.NewFacadeCaller(caller, facadeName), 29 } 30 } 31 32 // ErrEntityNotFound indicates that the requested entity no longer exists. 33 // 34 // We avoid errors.NotFound, because errors.NotFound is non-specific, and 35 // it's our job to communicate *this specific condition*. There are many 36 // possible sources of errors.NotFound in the world, and it's not safe or 37 // sane for a client to treat a generic NotFound as specific to the entity 38 // in question. 39 // 40 // We're still vulnerable to apiservers returning unjustified CodeNotFound 41 // but at least we're safe from accidental errors.NotFound injection in 42 // the api client mechanism. 43 const ErrEntityNotFound = errors.ConstError("entity not found") 44 45 // Watch returns a NotifyWatcher that sends a value whenever the 46 // entity's life value may have changed; or ErrEntityNotFound; or some 47 // other error. 48 func (c *Client) Watch(entity names.Tag) (watcher.NotifyWatcher, error) { 49 args := params.Entities{ 50 Entities: []params.Entity{{Tag: entity.String()}}, 51 } 52 var results params.NotifyWatchResults 53 err := c.caller.FacadeCall("Watch", args, &results) 54 if err != nil { 55 return nil, errors.Trace(err) 56 } 57 if count := len(results.Results); count != 1 { 58 return nil, errors.Errorf("expected 1 Watch result, got %d", count) 59 } 60 result := results.Results[0] 61 if err := result.Error; err != nil { 62 if params.IsCodeNotFound(err) { 63 return nil, ErrEntityNotFound 64 } 65 return nil, errors.Trace(result.Error) 66 } 67 w := apiwatcher.NewNotifyWatcher(c.caller.RawAPICaller(), result) 68 return w, nil 69 } 70 71 // Life returns the entity's life value; or ErrEntityNotFound; or some 72 // other error. 73 func (c *Client) Life(entity names.Tag) (life.Value, error) { 74 args := params.Entities{ 75 Entities: []params.Entity{{Tag: entity.String()}}, 76 } 77 var results params.LifeResults 78 err := c.caller.FacadeCall("Life", args, &results) 79 if err != nil { 80 return "", errors.Trace(err) 81 } 82 if count := len(results.Results); count != 1 { 83 return "", errors.Errorf("expected 1 Life result, got %d", count) 84 } 85 result := results.Results[0] 86 if err := result.Error; err != nil { 87 if params.IsCodeNotFound(err) { 88 return "", ErrEntityNotFound 89 } 90 return "", errors.Trace(result.Error) 91 } 92 life := result.Life 93 if err := life.Validate(); err != nil { 94 return "", errors.Trace(err) 95 } 96 return life, nil 97 }