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