github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/credentialvalidator/worker.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package credentialvalidator
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/worker/v3"
     9  	"github.com/juju/worker/v3/catacomb"
    10  
    11  	"github.com/juju/juju/api/base"
    12  	"github.com/juju/juju/core/watcher"
    13  )
    14  
    15  // logger is here to stop the desire of creating a package level logger.
    16  // Don't do this, instead use the one passed as manifold config.
    17  type logger interface{}
    18  
    19  var _ logger = struct{}{}
    20  
    21  // ErrValidityChanged indicates that a Worker has bounced because its
    22  // credential validity has changed: either a valid credential became invalid
    23  // or invalid credential became valid.
    24  var ErrValidityChanged = errors.New("cloud credential validity has changed")
    25  
    26  // ErrModelCredentialChanged indicates that a Worker has bounced because its
    27  // model's cloud credential has changed.
    28  var ErrModelCredentialChanged = errors.New("model cloud credential has changed")
    29  
    30  // Facade exposes functionality required by a Worker to access and watch
    31  // a cloud credential that a model uses.
    32  type Facade interface {
    33  	// ModelCredential gets model's cloud credential.
    34  	// Models that are on the clouds that do not require auth will return
    35  	// false to signify that credential was not set.
    36  	ModelCredential() (base.StoredCredential, bool, error)
    37  
    38  	// WatchCredential gets cloud credential watcher.
    39  	WatchCredential(string) (watcher.NotifyWatcher, error)
    40  
    41  	// WatchModelCredential gets model's cloud credential watcher.
    42  	WatchModelCredential() (watcher.NotifyWatcher, error)
    43  }
    44  
    45  // Config holds the dependencies and configuration for a Worker.
    46  type Config struct {
    47  	Facade Facade
    48  	Logger Logger
    49  }
    50  
    51  // Validate returns an error if the config cannot be expected to
    52  // drive a functional Worker.
    53  func (config Config) Validate() error {
    54  	if config.Facade == nil {
    55  		return errors.NotValidf("nil Facade")
    56  	}
    57  	if config.Logger == nil {
    58  		return errors.NotValidf("nil Logger")
    59  	}
    60  	return nil
    61  }
    62  
    63  // NewWorker returns a Worker that tracks the validity of the Model's cloud
    64  // credential, as exposed by the Facade.
    65  func NewWorker(config Config) (worker.Worker, error) {
    66  	if err := config.Validate(); err != nil {
    67  		return nil, errors.Trace(err)
    68  	}
    69  
    70  	mc, err := modelCredential(config.Facade)
    71  	if err != nil {
    72  		return nil, errors.Trace(err)
    73  	}
    74  
    75  	// This worker needs to monitor both the changes to the credential content that
    76  	// this model uses as well as what credential the model uses.
    77  	// It needs to be restarted if there is a change in either.
    78  	mcw, err := config.Facade.WatchModelCredential()
    79  	if err != nil {
    80  		return nil, errors.Trace(err)
    81  	}
    82  
    83  	v := &validator{
    84  		validatorFacade:        config.Facade,
    85  		logger:                 config.Logger,
    86  		credential:             mc,
    87  		modelCredentialWatcher: mcw,
    88  	}
    89  
    90  	// The watcher needs to be added to the worker's catacomb plan
    91  	// here in order to be controlled by this worker's lifecycle events:
    92  	// for example, to be destroyed when this worker is destroyed, etc.
    93  	// We also add the watcher to the Plan.Init collection to ensure that
    94  	// the worker's Plan.Work method is executed after the watcher
    95  	// is initialised and watcher's changes collection obtains the changes.
    96  	// Watchers that are added using catacomb.Add method
    97  	// miss out on a first call of Worker's Plan.Work method and can, thus,
    98  	// be missing out on an initial change.
    99  	plan := catacomb.Plan{
   100  		Site: &v.catacomb,
   101  		Work: v.loop,
   102  		Init: []worker.Worker{v.modelCredentialWatcher},
   103  	}
   104  
   105  	if mc.CloudCredential != "" {
   106  		var err error
   107  		v.credentialWatcher, err = config.Facade.WatchCredential(mc.CloudCredential)
   108  		if err != nil {
   109  			return nil, errors.Trace(err)
   110  		}
   111  		plan.Init = append(plan.Init, v.credentialWatcher)
   112  	}
   113  
   114  	if err := catacomb.Invoke(plan); err != nil {
   115  		return nil, errors.Trace(err)
   116  	}
   117  	return v, nil
   118  }
   119  
   120  type validator struct {
   121  	catacomb        catacomb.Catacomb
   122  	validatorFacade Facade
   123  	logger          Logger
   124  
   125  	modelCredentialWatcher watcher.NotifyWatcher
   126  
   127  	credential base.StoredCredential
   128  	// could be nil when there is no model credential to watch
   129  	credentialWatcher watcher.NotifyWatcher
   130  }
   131  
   132  // Kill is part of the worker.Worker interface.
   133  func (v *validator) Kill() {
   134  	v.catacomb.Kill(nil)
   135  }
   136  
   137  // Wait is part of the worker.Worker interface.
   138  func (v *validator) Wait() error {
   139  	return v.catacomb.Wait()
   140  }
   141  
   142  // Check is part of the util.Flag interface.
   143  func (v *validator) Check() bool {
   144  	return v.credential.Valid
   145  }
   146  
   147  func (v *validator) loop() error {
   148  	var watcherChanges watcher.NotifyChannel
   149  	if v.credentialWatcher != nil {
   150  		watcherChanges = v.credentialWatcher.Changes()
   151  	}
   152  
   153  	for {
   154  		select {
   155  		case <-v.catacomb.Dying():
   156  			return v.catacomb.ErrDying()
   157  		case _, ok := <-v.modelCredentialWatcher.Changes():
   158  			if !ok {
   159  				return v.catacomb.ErrDying()
   160  			}
   161  			updatedCredential, err := modelCredential(v.validatorFacade)
   162  			if err != nil {
   163  				return errors.Trace(err)
   164  			}
   165  			if v.credential.CloudCredential != updatedCredential.CloudCredential {
   166  				return ErrModelCredentialChanged
   167  			}
   168  		case _, ok := <-watcherChanges:
   169  			if !ok {
   170  				return v.catacomb.ErrDying()
   171  			}
   172  			updatedCredential, err := modelCredential(v.validatorFacade)
   173  			if err != nil {
   174  				return errors.Trace(err)
   175  			}
   176  			if v.credential.Valid != updatedCredential.Valid {
   177  				return ErrValidityChanged
   178  			}
   179  		}
   180  	}
   181  }
   182  
   183  func modelCredential(v Facade) (base.StoredCredential, error) {
   184  	mc, _, err := v.ModelCredential()
   185  	if err != nil {
   186  		return base.StoredCredential{}, errors.Trace(err)
   187  	}
   188  	return mc, nil
   189  }