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  }