github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/secretspruner/worker.go (about)

     1  // Copyright 2023 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package secretspruner
     5  
     6  import (
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/juju/collections/set"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/worker/v3"
    13  	"github.com/juju/worker/v3/catacomb"
    14  
    15  	coresecrets "github.com/juju/juju/core/secrets"
    16  	"github.com/juju/juju/core/watcher"
    17  )
    18  
    19  // logger is here to stop the desire of creating a package level logger.
    20  // Don't do this, instead use the one passed as manifold config.
    21  type logger interface{}
    22  
    23  var _ logger = struct{}{}
    24  
    25  // Logger represents the methods used by the worker to log information.
    26  type Logger interface {
    27  	Debugf(string, ...interface{})
    28  	Warningf(string, ...interface{})
    29  	Infof(string, ...interface{})
    30  }
    31  
    32  // SecretsFacade instances provide a set of API for the worker to deal with secret prune.
    33  type SecretsFacade interface {
    34  	WatchRevisionsToPrune() (watcher.StringsWatcher, error)
    35  	DeleteRevisions(uri *coresecrets.URI, revisions ...int) error
    36  }
    37  
    38  // Config defines the operation of the Worker.
    39  type Config struct {
    40  	SecretsFacade
    41  	Logger Logger
    42  }
    43  
    44  // Validate returns an error if config cannot drive the Worker.
    45  func (config Config) Validate() error {
    46  	if config.SecretsFacade == nil {
    47  		return errors.NotValidf("nil SecretsFacade")
    48  	}
    49  	if config.Logger == nil {
    50  		return errors.NotValidf("nil Logger")
    51  	}
    52  	return nil
    53  }
    54  
    55  // NewWorker returns a secretspruner Worker backed by config, or an error.
    56  func NewWorker(config Config) (worker.Worker, error) {
    57  	if err := config.Validate(); err != nil {
    58  		return nil, errors.Trace(err)
    59  	}
    60  
    61  	w := &Worker{config: config}
    62  	err := catacomb.Invoke(catacomb.Plan{
    63  		Site: &w.catacomb,
    64  		Work: w.loop,
    65  	})
    66  	return w, errors.Trace(err)
    67  }
    68  
    69  // Worker prunes the user supplied secret revisions.
    70  type Worker struct {
    71  	catacomb catacomb.Catacomb
    72  	config   Config
    73  }
    74  
    75  // Kill is defined on worker.Worker.
    76  func (w *Worker) Kill() {
    77  	w.catacomb.Kill(nil)
    78  }
    79  
    80  // Wait is part of the worker.Worker interface.
    81  func (w *Worker) Wait() error {
    82  	return w.catacomb.Wait()
    83  }
    84  
    85  func (w *Worker) loop() (err error) {
    86  	watcher, err := w.config.SecretsFacade.WatchRevisionsToPrune()
    87  	if err != nil {
    88  		return errors.Trace(err)
    89  	}
    90  	if err := w.catacomb.Add(watcher); err != nil {
    91  		return errors.Trace(err)
    92  	}
    93  
    94  	for {
    95  		select {
    96  		case <-w.catacomb.Dying():
    97  			return errors.Trace(w.catacomb.ErrDying())
    98  		// TODO: watch for secret's auto-prune config changes.
    99  		// then delete any obsolete revisions.
   100  		case changes, ok := <-watcher.Changes():
   101  			if !ok {
   102  				return errors.New("secret prune changed watch closed")
   103  			}
   104  			w.config.Logger.Debugf("got user supplied secret revisions to prune")
   105  
   106  			if len(changes) == 0 {
   107  				w.config.Logger.Debugf("no secret revisions to prune")
   108  				continue
   109  			}
   110  			revisions, err := w.processChanges(changes...)
   111  			if err != nil {
   112  				return errors.Trace(err)
   113  			}
   114  			for uriStr, revs := range revisions {
   115  				w.config.Logger.Debugf("pruning secret revisions %q: %v", uriStr, revs.SortedValues())
   116  				uri, err := coresecrets.ParseURI(uriStr)
   117  				if err != nil {
   118  					return errors.Trace(err)
   119  				}
   120  				if err := w.config.SecretsFacade.DeleteRevisions(uri, revs.SortedValues()...); err != nil {
   121  					return errors.Trace(err)
   122  				}
   123  			}
   124  		}
   125  	}
   126  }
   127  
   128  func (w *Worker) processChanges(changes ...string) (map[string]set.Ints, error) {
   129  	out := make(map[string]set.Ints)
   130  	for _, revInfo := range changes {
   131  		w.config.Logger.Warningf("revInfo %q, out %#v", revInfo, out)
   132  		parts := strings.Split(revInfo, "/")
   133  		uri := parts[0]
   134  		if len(parts) < 2 {
   135  			// This should never happen.
   136  			w.config.Logger.Debugf("secret %q has been removed, no need to prune", revInfo)
   137  			continue
   138  		}
   139  		rev, err := strconv.Atoi(parts[1])
   140  		if err != nil {
   141  			// This should never happen.
   142  			return nil, errors.NotValidf("secret revision %q for %q", parts[1], uri)
   143  		}
   144  		if _, ok := out[uri]; !ok {
   145  			out[uri] = set.NewInts()
   146  		}
   147  		out[uri].Add(rev)
   148  	}
   149  	return out, nil
   150  }