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 }