github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/charmdownloader/charmdownloader.go (about) 1 // Copyright 2021 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charmdownloader 5 6 import ( 7 "strings" 8 9 "github.com/juju/errors" 10 "github.com/juju/names/v5" 11 "github.com/juju/worker/v3" 12 "github.com/juju/worker/v3/catacomb" 13 14 "github.com/juju/juju/core/watcher" 15 ) 16 17 // Config defines the operation of a Worker. 18 type Config struct { 19 Logger Logger 20 CharmDownloaderAPI CharmDownloaderAPI 21 } 22 23 // Validate returns an error if cfg cannot drive a Worker. 24 func (cfg Config) Validate() error { 25 if cfg.Logger == nil { 26 return errors.NotValidf("nil Logger") 27 } 28 if cfg.CharmDownloaderAPI == nil { 29 return errors.NotValidf("nil CharmDownloader API") 30 } 31 return nil 32 } 33 34 // CharmDownloader watches applications that reference charms that have not 35 // yet been downloaded and triggers an asynchronous download request for each 36 // one. 37 type CharmDownloader struct { 38 charmDownloaderAPI CharmDownloaderAPI 39 logger Logger 40 41 catacomb catacomb.Catacomb 42 appWatcher watcher.StringsWatcher 43 } 44 45 // NewCharmDownloader returns a new CharmDownloader worker. 46 func NewCharmDownloader(cfg Config) (worker.Worker, error) { 47 if err := cfg.Validate(); err != nil { 48 return nil, errors.Trace(err) 49 } 50 51 cd := &CharmDownloader{ 52 logger: cfg.Logger, 53 charmDownloaderAPI: cfg.CharmDownloaderAPI, 54 } 55 56 err := catacomb.Invoke(catacomb.Plan{ 57 Site: &cd.catacomb, 58 Work: cd.loop, 59 }) 60 if err != nil { 61 return nil, errors.Trace(err) 62 } 63 return cd, nil 64 } 65 66 func (cd *CharmDownloader) setup() error { 67 var err error 68 cd.appWatcher, err = cd.charmDownloaderAPI.WatchApplicationsWithPendingCharms() 69 if err != nil { 70 return errors.Trace(err) 71 } 72 if err := cd.catacomb.Add(cd.appWatcher); err != nil { 73 return errors.Trace(err) 74 } 75 76 cd.logger.Debugf("started watching applications referencing charms that have not yet been downloaded") 77 return nil 78 } 79 80 func (cd *CharmDownloader) loop() error { 81 if err := cd.setup(); err != nil { 82 return errors.Trace(err) 83 } 84 85 for { 86 select { 87 case <-cd.catacomb.Dying(): 88 return cd.catacomb.ErrDying() 89 case changes, ok := <-cd.appWatcher.Changes(): 90 if !ok { 91 return errors.New("application watcher closed") 92 } 93 94 if len(changes) == 0 { 95 continue 96 } 97 98 appTags := make([]names.ApplicationTag, len(changes)) 99 for i, appName := range changes { 100 appTags[i] = names.NewApplicationTag(appName) 101 } 102 103 cd.logger.Debugf("triggering asynchronous download of charms for the following applications: %v", strings.Join(changes, ", ")) 104 if err := cd.charmDownloaderAPI.DownloadApplicationCharms(appTags); err != nil { 105 return errors.Trace(err) 106 } 107 } 108 } 109 } 110 111 // Kill is part of the worker.Worker interface. 112 func (cd *CharmDownloader) Kill() { 113 cd.catacomb.Kill(nil) 114 } 115 116 // Wait is part of the worker.Worker interface. 117 func (cd *CharmDownloader) Wait() error { 118 return cd.catacomb.Wait() 119 }