github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/caasmodelconfigmanager/worker.go (about) 1 // Copyright 2021 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package caasmodelconfigmanager 5 6 import ( 7 "reflect" 8 "time" 9 10 "github.com/juju/clock" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/names/v5" 14 "github.com/juju/worker/v3" 15 "github.com/juju/worker/v3/catacomb" 16 17 "github.com/juju/juju/api/base" 18 api "github.com/juju/juju/api/controller/caasmodelconfigmanager" 19 "github.com/juju/juju/controller" 20 "github.com/juju/juju/core/watcher" 21 "github.com/juju/juju/docker" 22 "github.com/juju/juju/docker/registry" 23 ) 24 25 const ( 26 retryDuration = 1 * time.Second 27 refreshDuration = 30 * time.Second 28 ) 29 30 // Logger represents the methods used by the worker to log details. 31 type Logger interface { 32 Debugf(string, ...interface{}) 33 Infof(string, ...interface{}) 34 Errorf(string, ...interface{}) 35 Warningf(string, ...interface{}) 36 Tracef(string, ...interface{}) 37 38 Child(string) loggo.Logger 39 } 40 41 //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/facade_mock.go github.com/juju/juju/worker/caasmodelconfigmanager Facade 42 type Facade interface { 43 ControllerConfig() (controller.Config, error) 44 WatchControllerConfig() (watcher.NotifyWatcher, error) 45 } 46 47 //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/broker_mock.go github.com/juju/juju/worker/caasmodelconfigmanager CAASBroker 48 type CAASBroker interface { 49 EnsureImageRepoSecret(docker.ImageRepoDetails) error 50 } 51 52 // Config holds the configuration and dependencies for a worker. 53 type Config struct { 54 ModelTag names.ModelTag 55 56 Facade Facade 57 Broker CAASBroker 58 Logger Logger 59 Clock clock.Clock 60 RegistryFunc func(docker.ImageRepoDetails) (registry.Registry, error) 61 } 62 63 // Validate returns an error if the config cannot be expected 64 // to drive a functional worker. 65 func (config Config) Validate() error { 66 if config.ModelTag == (names.ModelTag{}) { 67 return errors.NotValidf("ModelTag is missing") 68 } 69 if config.Facade == nil { 70 return errors.NotValidf("Facade is missing") 71 } 72 if config.Broker == nil { 73 return errors.NotValidf("Broker is missing") 74 } 75 if config.Logger == nil { 76 return errors.NotValidf("Logger is missing") 77 } 78 if config.Clock == nil { 79 return errors.NotValidf("Clock is missing") 80 } 81 if config.RegistryFunc == nil { 82 return errors.NotValidf("RegistryFunc is missing") 83 } 84 return nil 85 } 86 87 type manager struct { 88 catacomb catacomb.Catacomb 89 90 name string 91 config Config 92 logger Logger 93 clock clock.Clock 94 95 registryFunc func(docker.ImageRepoDetails) (registry.Registry, error) 96 } 97 98 // NewFacade returns a facade for caasapplicationprovisioner worker to use. 99 func NewFacade(caller base.APICaller) (Facade, error) { 100 return api.NewClient(caller) 101 } 102 103 // NewWorker returns a worker that unlocks the model upgrade gate. 104 func NewWorker(config Config) (worker.Worker, error) { 105 if err := config.Validate(); err != nil { 106 return nil, errors.Trace(err) 107 } 108 w := &manager{ 109 name: config.ModelTag.Id(), 110 config: config, 111 logger: config.Logger, 112 clock: config.Clock, 113 registryFunc: config.RegistryFunc, 114 } 115 err := catacomb.Invoke(catacomb.Plan{ 116 Site: &w.catacomb, 117 Work: w.loop, 118 }) 119 if err != nil { 120 return nil, errors.Trace(err) 121 } 122 return w, nil 123 } 124 125 // Kill is part of the worker.Worker interface. 126 func (w *manager) Kill() { 127 w.catacomb.Kill(nil) 128 } 129 130 // Wait is part of the worker.Worker interface. 131 func (w *manager) Wait() error { 132 return w.catacomb.Wait() 133 } 134 135 func (w *manager) loop() (err error) { 136 watcher, err := w.config.Facade.WatchControllerConfig() 137 if err != nil { 138 return errors.Trace(err) 139 } 140 err = w.catacomb.Add(watcher) 141 if err != nil { 142 return errors.Trace(err) 143 } 144 145 var ( 146 refresh <-chan struct{} 147 timeout <-chan time.Time 148 deadline time.Time 149 reg registry.Registry 150 lastRepoDetails docker.ImageRepoDetails 151 ) 152 first := false 153 signal := make(chan struct{}) 154 close(signal) 155 defer func() { 156 if reg != nil { 157 _ = reg.Close() 158 } 159 }() 160 161 for { 162 select { 163 case <-w.catacomb.Dying(): 164 return w.catacomb.ErrDying() 165 case <-watcher.Changes(): 166 controllerConfig, err := w.config.Facade.ControllerConfig() 167 if err != nil { 168 return errors.Trace(err) 169 } 170 repoDetails, err := docker.NewImageRepoDetails(controllerConfig.CAASImageRepo()) 171 if err != nil { 172 return errors.Annotatef(err, "parsing %s", controller.CAASImageRepo) 173 } 174 if reflect.DeepEqual(repoDetails, lastRepoDetails) { 175 continue 176 } 177 lastRepoDetails = repoDetails 178 if !repoDetails.IsPrivate() { 179 timeout = nil 180 refresh = nil 181 continue 182 } 183 if reg != nil { 184 _ = reg.Close() 185 } 186 reg, err = w.registryFunc(repoDetails) 187 if err != nil { 188 return errors.Trace(err) 189 } 190 if err = reg.Ping(); err != nil { 191 return errors.Trace(err) 192 } 193 first = true 194 refresh = signal 195 case <-timeout: 196 timeout = nil 197 if refresh == nil { 198 refresh = signal 199 } 200 case <-refresh: 201 refresh = nil 202 next, err := w.ensureImageRepoSecret(reg, first) 203 if err != nil { 204 w.logger.Errorf("failed to update repository secret: %s", err.Error()) 205 next = retryDuration 206 } else { 207 first = false 208 } 209 if nextDeadline := w.clock.Now().Add(next); timeout == nil || nextDeadline.Before(deadline) { 210 deadline = nextDeadline 211 timeout = w.clock.After(next) 212 } 213 } 214 } 215 } 216 217 func (w *manager) ensureImageRepoSecret(reg registry.Registry, force bool) (time.Duration, error) { 218 shouldRefresh, nextRefresh := reg.ShouldRefreshAuth() 219 if nextRefresh == time.Duration(0) { 220 nextRefresh = refreshDuration 221 } 222 if !shouldRefresh && !force { 223 return nextRefresh, nil 224 } 225 226 w.logger.Debugf("refreshing auth token for %q", w.name) 227 if err := reg.RefreshAuth(); err != nil { 228 return time.Duration(0), errors.Annotatef(err, "refreshing registry auth token for %q", w.name) 229 } 230 231 w.logger.Debugf("applying refreshed auth token for %q", w.name) 232 err := w.config.Broker.EnsureImageRepoSecret(reg.ImageRepoDetails()) 233 if err != nil { 234 return time.Duration(0), errors.Annotatef(err, "ensuring image repository secret for %q", w.name) 235 } 236 return nextRefresh, nil 237 }