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

     1  // Copyright 2022 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package secrets
     5  
     6  import (
     7  	"reflect"
     8  	"sync"
     9  
    10  	"github.com/juju/collections/set"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names/v5"
    13  
    14  	coresecrets "github.com/juju/juju/core/secrets"
    15  	"github.com/juju/juju/worker/uniter/hook"
    16  	"github.com/juju/juju/worker/uniter/remotestate"
    17  )
    18  
    19  // SecretsClient is used by the secrets tracker to access the Juju model.
    20  type SecretsClient interface {
    21  	remotestate.SecretsClient
    22  	SecretMetadata() ([]coresecrets.SecretOwnerMetadata, error)
    23  }
    24  
    25  // Secrets generates storage hooks in response to changes to
    26  // storage Secrets, and provides access to information about
    27  // storage Secrets to hooks.
    28  type Secrets struct {
    29  	client  SecretsClient
    30  	unitTag names.UnitTag
    31  	logger  Logger
    32  
    33  	secretsState *State
    34  	stateOps     *stateOps
    35  
    36  	mu sync.Mutex
    37  }
    38  
    39  // NewSecrets returns a new secrets tracker.
    40  func NewSecrets(
    41  	client SecretsClient,
    42  	tag names.UnitTag,
    43  	rw UnitStateReadWriter,
    44  	logger Logger,
    45  ) (SecretStateTracker, error) {
    46  	s := &Secrets{
    47  		client:   client,
    48  		unitTag:  tag,
    49  		logger:   logger,
    50  		stateOps: NewStateOps(rw),
    51  	}
    52  	if err := s.init(); err != nil {
    53  		return nil, err
    54  	}
    55  	return s, nil
    56  }
    57  
    58  // init processes initialises the tracker based on the unit
    59  // state read from the controller.
    60  func (s *Secrets) init() error {
    61  	existingSecretsState, err := s.stateOps.Read()
    62  	if err != nil && !errors.IsNotFound(err) {
    63  		return errors.Annotate(err, "reading secrets state")
    64  	}
    65  	s.secretsState = existingSecretsState
    66  
    67  	changed := false
    68  	if len(s.secretsState.ConsumedSecretInfo) > 0 {
    69  		uris := set.NewStrings()
    70  		for uri := range s.secretsState.ConsumedSecretInfo {
    71  			uris.Add(uri)
    72  		}
    73  		info, err := s.client.GetConsumerSecretsRevisionInfo(s.unitTag.Id(), uris.SortedValues())
    74  		if err != nil {
    75  			return errors.Annotate(err, "getting consumed secret info")
    76  		}
    77  		updated := make(map[string]int)
    78  		for u, v := range info {
    79  			updated[u] = v.Revision
    80  		}
    81  		changed = !reflect.DeepEqual(updated, s.secretsState.ConsumedSecretInfo)
    82  		if changed {
    83  			s.secretsState.ConsumedSecretInfo = updated
    84  		}
    85  	}
    86  	metadata, err := s.client.SecretMetadata()
    87  	if err != nil {
    88  		return errors.Annotate(err, "reading secret metadata")
    89  	}
    90  	owned := set.NewStrings()
    91  	for _, md := range metadata {
    92  		owned.Add(md.Metadata.URI.String())
    93  	}
    94  	for uri := range s.secretsState.SecretObsoleteRevisions {
    95  		if !owned.Contains(uri) {
    96  			changed = true
    97  			delete(s.secretsState.SecretObsoleteRevisions, uri)
    98  		}
    99  	}
   100  	if !changed {
   101  		return nil
   102  	}
   103  
   104  	return s.stateOps.Write(s.secretsState)
   105  }
   106  
   107  // ConsumedSecretRevision implements SecretStateTracker.
   108  func (s *Secrets) ConsumedSecretRevision(uri string) int {
   109  	s.mu.Lock()
   110  	defer s.mu.Unlock()
   111  
   112  	return s.secretsState.ConsumedSecretInfo[uri]
   113  }
   114  
   115  // SecretObsoleteRevisions implements SecretStateTracker.
   116  func (s *Secrets) SecretObsoleteRevisions(uri string) []int {
   117  	s.mu.Lock()
   118  	defer s.mu.Unlock()
   119  
   120  	revs := s.secretsState.SecretObsoleteRevisions[uri]
   121  	result := make([]int, len(revs))
   122  	copy(result, revs)
   123  	return result
   124  }
   125  
   126  // PrepareHook implements SecretStateTracker.
   127  func (s *Secrets) PrepareHook(hi hook.Info) error {
   128  	if !hi.Kind.IsSecret() {
   129  		return errors.Errorf("not a secret hook: %#v", hi)
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  // CommitHook implements SecretStateTracker.
   136  func (s *Secrets) CommitHook(hi hook.Info) error {
   137  	if !hi.Kind.IsSecret() {
   138  		return errors.Errorf("not a secret hook: %#v", hi)
   139  	}
   140  
   141  	s.mu.Lock()
   142  	defer s.mu.Unlock()
   143  
   144  	s.secretsState.UpdateStateForHook(hi)
   145  	if err := s.stateOps.Write(s.secretsState); err != nil {
   146  		return err
   147  	}
   148  	return nil
   149  }
   150  
   151  // SecretsRemoved implements SecretStateTracker.
   152  func (s *Secrets) SecretsRemoved(uris []string) error {
   153  	s.mu.Lock()
   154  	defer s.mu.Unlock()
   155  
   156  	for _, uri := range uris {
   157  		delete(s.secretsState.ConsumedSecretInfo, uri)
   158  		delete(s.secretsState.SecretObsoleteRevisions, uri)
   159  	}
   160  	if err := s.stateOps.Write(s.secretsState); err != nil {
   161  		return err
   162  	}
   163  	return nil
   164  }
   165  
   166  // Report provides information for the engine report.
   167  func (s *Secrets) Report() map[string]interface{} {
   168  	s.mu.Lock()
   169  	defer s.mu.Unlock()
   170  
   171  	result := make(map[string]interface{})
   172  	obsolete := make(map[string][]int)
   173  	for u, v := range s.secretsState.SecretObsoleteRevisions {
   174  		rCopy := make([]int, len(v))
   175  		copy(rCopy, v)
   176  		obsolete[u] = rCopy
   177  	}
   178  	result["obsolete-revisions"] = obsolete
   179  
   180  	consumed := make(map[string]int)
   181  	for u, v := range s.secretsState.ConsumedSecretInfo {
   182  		consumed[u] = v
   183  	}
   184  	result["consumed-revisions"] = consumed
   185  
   186  	return result
   187  }