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  }