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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/mgo/v3/bson"
    12  	"github.com/juju/mgo/v3/txn"
    13  	"github.com/juju/names/v5"
    14  	jujutxn "github.com/juju/txn/v3"
    15  
    16  	"github.com/juju/juju/cloud"
    17  	"github.com/juju/juju/core/status"
    18  )
    19  
    20  // InvalidateModelCredential invalidate cloud credential for the model
    21  // of the given state.
    22  func (st *State) InvalidateModelCredential(reason string) error {
    23  	m, err := st.Model()
    24  	if err != nil {
    25  		return errors.Trace(err)
    26  	}
    27  
    28  	tag, exists := m.CloudCredentialTag()
    29  	if !exists {
    30  		// Model is on the cloud that does not require auth - nothing to do.
    31  		return nil
    32  	}
    33  
    34  	if err := st.InvalidateCloudCredential(tag, reason); err != nil {
    35  		return errors.Trace(err)
    36  	}
    37  	if err := st.suspendCredentialModels(tag, reason); err != nil {
    38  		// These updates are optimistic. If they fail, it's unfortunate but we are not going to stop the call.
    39  		logger.Warningf("could not suspend models that use credential %v: %v", tag.Id(), err)
    40  	}
    41  	return nil
    42  }
    43  
    44  func (st *State) suspendCredentialModels(tag names.CloudCredentialTag, reason string) error {
    45  	models, err := st.modelsWithCredential(tag)
    46  	if err != nil {
    47  		return errors.Annotatef(err, "could not determine what models use credential %v", tag.Id())
    48  	}
    49  	infos := make([]string, len(models))
    50  	for i, m := range models {
    51  		infos[i] = fmt.Sprintf("%s (%s)", m.Name, m.UUID)
    52  	}
    53  	logger.Warningf("suspending these models:\n%s\n because their credential has become invalid:\n%s",
    54  		strings.Join(infos, " - "),
    55  		reason)
    56  	sts := modelStatusInvalidCredential(reason)
    57  	doc := statusDoc{
    58  		Status:     sts.Status,
    59  		StatusInfo: sts.Message,
    60  		StatusData: sts.Data,
    61  		Updated:    timeOrNow(nil, st.clock()).UnixNano(),
    62  	}
    63  	for _, m := range models {
    64  		st.maybeSetModelStatusHistoryDoc(m.UUID, doc)
    65  	}
    66  	return nil
    67  }
    68  
    69  func (st *State) maybeSetModelStatusHistoryDoc(modelUUID string, doc statusDoc) {
    70  	one, closer, err := st.model(modelUUID)
    71  	defer func() { _ = closer() }()
    72  	if err != nil {
    73  		logger.Warningf("model %v error: %v", modelUUID, err)
    74  		return
    75  	}
    76  
    77  	if _, err = probablyUpdateStatusHistory(one.st.db(), one.globalKey(), doc); err != nil {
    78  		logger.Warningf("%v", err)
    79  	}
    80  }
    81  
    82  // ValidateCloudCredential validates new cloud credential for this model.
    83  func (m *Model) ValidateCloudCredential(tag names.CloudCredentialTag, credential cloud.Credential) error {
    84  	aCloud, err := m.st.Cloud(m.CloudName())
    85  	if err != nil {
    86  		return errors.Annotatef(err, "getting cloud %q", m.CloudName())
    87  	}
    88  
    89  	err = validateCredentialForCloud(aCloud, tag, convertCloudCredentialToState(tag, credential))
    90  	if err != nil {
    91  		return errors.Annotatef(err, "validating credential %q for cloud %q", tag.Id(), aCloud.Name)
    92  	}
    93  	return nil
    94  }
    95  
    96  // SetCloudCredential sets new cloud credential for this model.
    97  // Returned bool indicates if model credential was set.
    98  func (m *Model) SetCloudCredential(tag names.CloudCredentialTag) (bool, error) {
    99  	// If model is suspended, after this call, it may be reverted since,
   100  	// if updated, model credential will be set to a valid credential.
   101  	modelStatus, err := m.Status()
   102  	if err != nil {
   103  		return false, errors.Annotatef(err, "getting model status %q", m.UUID())
   104  	}
   105  	revert := modelStatus.Status == status.Suspended
   106  	aCloud, err := m.st.Cloud(m.CloudName())
   107  	if err != nil {
   108  		return false, errors.Annotatef(err, "getting cloud %q", m.CloudName())
   109  	}
   110  	updating := true
   111  	buildTxn := func(attempt int) ([]txn.Op, error) {
   112  		if attempt > 0 {
   113  			if err := m.Refresh(); err != nil {
   114  				return nil, errors.Trace(err)
   115  			}
   116  		}
   117  		if tag.Id() == m.doc.CloudCredential {
   118  			updating = false
   119  			return nil, jujutxn.ErrNoOperations
   120  		}
   121  		// Must be a valid credential that is already on the controller.
   122  		credential, err := m.st.CloudCredential(tag)
   123  		if err != nil {
   124  			return nil, errors.Trace(err)
   125  		}
   126  		if !credential.IsValid() {
   127  			return nil, errors.NotValidf("credential %q", tag.Id())
   128  		}
   129  		if err := validateCredentialForCloud(aCloud, tag, credential); err != nil {
   130  			return nil, errors.Trace(err)
   131  		}
   132  		return []txn.Op{{
   133  			C:      modelsC,
   134  			Id:     m.doc.UUID,
   135  			Assert: txn.DocExists,
   136  			Update: bson.D{{"$set", bson.D{{"cloud-credential", tag.Id()}}}},
   137  		}}, nil
   138  	}
   139  	if err := m.st.db().Run(buildTxn); err != nil {
   140  		return false, errors.Trace(err)
   141  	}
   142  	if updating && revert {
   143  		if err := m.maybeRevertModelStatus(); err != nil {
   144  			logger.Warningf("could not revert status for model %v: %v", m.UUID(), err)
   145  		}
   146  	}
   147  	return updating, m.Refresh()
   148  }
   149  
   150  // WatchModelCredential returns a new NotifyWatcher that watches
   151  // a model reference to a cloud credential.
   152  func (m *Model) WatchModelCredential() NotifyWatcher {
   153  	current := m.doc.CloudCredential
   154  	modelUUID := m.doc.UUID
   155  	filter := func(id interface{}) bool {
   156  		id, ok := id.(string)
   157  		if !ok || id != modelUUID {
   158  			return false
   159  		}
   160  
   161  		models, closer := m.st.db().GetCollection(modelsC)
   162  		defer closer()
   163  
   164  		var doc *modelDoc
   165  		if err := models.FindId(id).One(&doc); err != nil {
   166  			return false
   167  		}
   168  
   169  		match := current != doc.CloudCredential
   170  		current = doc.CloudCredential
   171  		return match
   172  	}
   173  	return newNotifyCollWatcher(m.st, modelsC, filter)
   174  }