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

     1  // Copyright 2019 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/collections/set"
    10  	"github.com/juju/description/v5"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/mgo/v3"
    13  	"github.com/juju/mgo/v3/bson"
    14  	"github.com/juju/mgo/v3/txn"
    15  	"github.com/juju/names/v5"
    16  
    17  	"github.com/juju/juju/core/crossmodel"
    18  	"github.com/juju/juju/core/network/firewall"
    19  	"github.com/juju/juju/core/permission"
    20  	"github.com/juju/juju/core/secrets"
    21  	"github.com/juju/juju/environs/config"
    22  )
    23  
    24  // Migration import tasks provide a boundary of isolation between the
    25  // description package and the state package. Input types are modelled as small
    26  // descrete interfaces, that can be composed to provide more functionality.
    27  // Output types, normally a transaction runner can then take the migrated
    28  // description entity as a txn.Op.
    29  //
    30  // The goal of these input tasks are to be moved out of the state package into
    31  // a similar setup as export migrations. That way we can isolate migrations away
    32  // from state and start creating richer types.
    33  //
    34  // Modelling it this way should provide better test coverage and protection
    35  // around state changes.
    36  
    37  // TransactionRunner is an in-place usage for running transactions to a
    38  // persistence store.
    39  type TransactionRunner interface {
    40  	RunTransaction([]txn.Op) error
    41  }
    42  
    43  // DocModelNamespace takes a document model ID and ensures it has a model id
    44  // associated with the model.
    45  type DocModelNamespace interface {
    46  	DocID(string) string
    47  }
    48  
    49  type stateModelNamspaceShim struct {
    50  	description.Model
    51  	st *State
    52  }
    53  
    54  func (s stateModelNamspaceShim) DocID(localID string) string {
    55  	return s.st.docID(localID)
    56  }
    57  
    58  // stateApplicationOfferDocumentFactoryShim is required to allow the new
    59  // vertical boundary around importing a applicationOffer, from being accessed by
    60  // the existing state package code.
    61  // That way we can keep the importing code clean from the proliferation of state
    62  // code in the juju code base.
    63  type stateApplicationOfferDocumentFactoryShim struct {
    64  	stateModelNamspaceShim
    65  	importer *importer
    66  }
    67  
    68  func (s stateApplicationOfferDocumentFactoryShim) MakeApplicationOfferDoc(app description.ApplicationOffer) (applicationOfferDoc, error) {
    69  	ao := &applicationOffers{st: s.importer.st}
    70  	return ao.makeApplicationOfferDoc(s.importer.st, app.OfferUUID(), crossmodel.AddApplicationOfferArgs{
    71  		OfferName:              app.OfferName(),
    72  		ApplicationName:        app.ApplicationName(),
    73  		ApplicationDescription: app.ApplicationDescription(),
    74  		Endpoints:              app.Endpoints(),
    75  	}), nil
    76  }
    77  
    78  func (s stateApplicationOfferDocumentFactoryShim) MakeApplicationOffersRefOp(name string, startCnt int) (txn.Op, error) {
    79  	return newApplicationOffersRefOp(s.importer.st, name, startCnt)
    80  }
    81  
    82  type applicationDescriptionShim struct {
    83  	stateApplicationOfferDocumentFactoryShim
    84  	ApplicationDescription
    85  }
    86  
    87  // ApplicationDescription is an in-place description of an application
    88  type ApplicationDescription interface {
    89  	Offers() []description.ApplicationOffer
    90  }
    91  
    92  // ApplicationOfferStateDocumentFactory creates documents that are useful with
    93  // in the state package. In essence this just allows us to model our
    94  // dependencies correctly without having to construct dependencies everywhere.
    95  // Note: we need public methods here because gomock doesn't mock private methods
    96  type ApplicationOfferStateDocumentFactory interface {
    97  	MakeApplicationOfferDoc(description.ApplicationOffer) (applicationOfferDoc, error)
    98  	MakeApplicationOffersRefOp(string, int) (txn.Op, error)
    99  }
   100  
   101  // ApplicationOfferDescription defines an in-place usage for reading
   102  // application offers.
   103  type ApplicationOfferDescription interface {
   104  	Offers() []description.ApplicationOffer
   105  }
   106  
   107  // ApplicationOfferInput describes the input used for migrating application
   108  // offers.
   109  type ApplicationOfferInput interface {
   110  	DocModelNamespace
   111  	ApplicationOfferStateDocumentFactory
   112  	ApplicationOfferDescription
   113  }
   114  
   115  // ImportApplicationOffer describes a way to import application offers from a
   116  // description.
   117  type ImportApplicationOffer struct {
   118  }
   119  
   120  // Execute the import on the application offer description, carefully modelling
   121  // the dependencies we have.
   122  func (i ImportApplicationOffer) Execute(src ApplicationOfferInput,
   123  	runner TransactionRunner,
   124  ) error {
   125  	offers := src.Offers()
   126  	if len(offers) == 0 {
   127  		return nil
   128  	}
   129  	refCounts := make(map[string]int, len(offers))
   130  	ops := make([]txn.Op, 0)
   131  	for _, offer := range offers {
   132  		appDoc, err := src.MakeApplicationOfferDoc(offer)
   133  		if err != nil {
   134  			return errors.Trace(err)
   135  		}
   136  		appOps, err := i.addApplicationOfferOps(src,
   137  			addApplicationOfferOpsArgs{
   138  				applicationOfferDoc: appDoc,
   139  				acl:                 offer.ACL(),
   140  			})
   141  		if err != nil {
   142  			return errors.Trace(err)
   143  		}
   144  		ops = append(ops, appOps...)
   145  		appName := offer.ApplicationName()
   146  		if appCnt, ok := refCounts[appName]; ok {
   147  			refCounts[appName] = appCnt + 1
   148  		} else {
   149  			refCounts[appName] = 1
   150  		}
   151  	}
   152  	// range the offers again to create refcount docs, an application
   153  	// may have more than one offer.
   154  	for appName, cnt := range refCounts {
   155  		refCntOpps, err := src.MakeApplicationOffersRefOp(appName, cnt)
   156  		if err != nil {
   157  			return errors.Trace(err)
   158  		}
   159  		ops = append(ops, refCntOpps)
   160  	}
   161  	if err := runner.RunTransaction(ops); err != nil {
   162  		return errors.Trace(err)
   163  	}
   164  	return nil
   165  }
   166  
   167  type addApplicationOfferOpsArgs struct {
   168  	applicationOfferDoc applicationOfferDoc
   169  	acl                 map[string]string
   170  }
   171  
   172  func (i ImportApplicationOffer) addApplicationOfferOps(src ApplicationOfferInput,
   173  	args addApplicationOfferOpsArgs,
   174  ) ([]txn.Op, error) {
   175  	ops := []txn.Op{
   176  		{
   177  			C:      applicationOffersC,
   178  			Id:     src.DocID(args.applicationOfferDoc.OfferName),
   179  			Assert: txn.DocMissing,
   180  			Insert: args.applicationOfferDoc,
   181  		},
   182  	}
   183  	for userName, access := range args.acl {
   184  		user := names.NewUserTag(userName)
   185  		h := createPermissionOp(applicationOfferKey(
   186  			args.applicationOfferDoc.OfferUUID), userGlobalKey(userAccessID(user)), permission.Access(access))
   187  		ops = append(ops, h)
   188  	}
   189  	return ops, nil
   190  }
   191  
   192  // StateDocumentFactory creates documents that are useful with in the state
   193  // package. In essence this just allows us to model our dependencies correctly
   194  // without having to construct dependencies everywhere.
   195  // Note: we need public methods here because gomock doesn't mock private methods
   196  type StateDocumentFactory interface {
   197  	NewRemoteApplication(*remoteApplicationDoc) *RemoteApplication
   198  	MakeRemoteApplicationDoc(description.RemoteApplication) *remoteApplicationDoc
   199  	MakeStatusDoc(description.Status) statusDoc
   200  	MakeStatusOp(string, statusDoc) txn.Op
   201  }
   202  
   203  // stateDocumentFactoryShim is required to allow the new vertical boundary
   204  // around importing a remoteApplication and firewallRules, from being accessed
   205  // by the existing state package code.
   206  // That way we can keep the importing code clean from the proliferation of state
   207  // code in the juju code base.
   208  type stateDocumentFactoryShim struct {
   209  	stateModelNamspaceShim
   210  	importer *importer
   211  }
   212  
   213  func (s stateDocumentFactoryShim) NewRemoteApplication(doc *remoteApplicationDoc) *RemoteApplication {
   214  	return newRemoteApplication(s.importer.st, doc)
   215  }
   216  
   217  func (s stateDocumentFactoryShim) MakeRemoteApplicationDoc(app description.RemoteApplication) *remoteApplicationDoc {
   218  	return s.importer.makeRemoteApplicationDoc(app)
   219  }
   220  
   221  func (s stateDocumentFactoryShim) MakeStatusDoc(status description.Status) statusDoc {
   222  	return s.importer.makeStatusDoc(status)
   223  }
   224  
   225  func (s stateDocumentFactoryShim) MakeStatusOp(globalKey string, doc statusDoc) txn.Op {
   226  	return createStatusOp(s.importer.st, globalKey, doc)
   227  }
   228  
   229  // FirewallRulesDescription defines an in-place usage for reading firewall
   230  // rules.
   231  type FirewallRulesDescription interface {
   232  	FirewallRules() []description.FirewallRule
   233  }
   234  
   235  // FirewallRulesInput describes the input used for migrating firewall rules.
   236  type FirewallRulesInput interface {
   237  	FirewallRulesDescription
   238  }
   239  
   240  // FirewallRulesOutput describes the methods used to set firewall rules
   241  // on the dest model
   242  type FirewallRulesOutput interface {
   243  	UpdateModelConfig(map[string]interface{}, []string, ...ValidateConfigFunc) error
   244  }
   245  
   246  // ImportFirewallRules describes a way to import firewallRules from a
   247  // description.
   248  type ImportFirewallRules struct{}
   249  
   250  // Execute the import on the firewall rules description, carefully modelling
   251  // the dependencies we have.
   252  func (rules ImportFirewallRules) Execute(src FirewallRulesInput, dst FirewallRulesOutput) error {
   253  	firewallRules := src.FirewallRules()
   254  	if len(firewallRules) == 0 {
   255  		return nil
   256  	}
   257  
   258  	for _, rule := range firewallRules {
   259  		var err error
   260  		cidrs := strings.Join(rule.WhitelistCIDRs(), ",")
   261  		switch firewall.WellKnownServiceType(rule.WellKnownService()) {
   262  		case firewall.SSHRule:
   263  			err = dst.UpdateModelConfig(map[string]interface{}{
   264  				config.SSHAllowKey: cidrs,
   265  			}, nil)
   266  		case firewall.JujuApplicationOfferRule:
   267  			// SAASIngressAllow cannot be empty. If it is, leave as it's default value
   268  			if cidrs != "" {
   269  				err = dst.UpdateModelConfig(map[string]interface{}{
   270  					config.SAASIngressAllowKey: cidrs,
   271  				}, nil)
   272  			}
   273  		}
   274  		if err != nil {
   275  			return errors.Trace(err)
   276  		}
   277  	}
   278  	return nil
   279  }
   280  
   281  // RemoteApplicationsDescription defines an in-place usage for reading remote
   282  // applications.
   283  type RemoteApplicationsDescription interface {
   284  	RemoteApplications() []description.RemoteApplication
   285  }
   286  
   287  // RemoteApplicationsInput describes the input used for migrating remote
   288  // applications.
   289  type RemoteApplicationsInput interface {
   290  	DocModelNamespace
   291  	StateDocumentFactory
   292  	RemoteApplicationsDescription
   293  }
   294  
   295  // ImportRemoteApplications describes a way to import remote applications from a
   296  // description.
   297  type ImportRemoteApplications struct{}
   298  
   299  // Execute the import on the remote entities description, carefully modelling
   300  // the dependencies we have.
   301  func (i ImportRemoteApplications) Execute(src RemoteApplicationsInput, runner TransactionRunner) error {
   302  	remoteApplications := src.RemoteApplications()
   303  	if len(remoteApplications) == 0 {
   304  		return nil
   305  	}
   306  	ops := make([]txn.Op, 0)
   307  	for _, app := range remoteApplications {
   308  		appDoc := src.MakeRemoteApplicationDoc(app)
   309  
   310  		// Status maybe empty for some remoteApplications. Ensure we handle
   311  		// that correctly by checking if we get one before making a new
   312  		// StatusDoc
   313  		var appStatusDoc *statusDoc
   314  		if status := app.Status(); status != nil {
   315  			doc := src.MakeStatusDoc(status)
   316  			appStatusDoc = &doc
   317  		}
   318  		app := src.NewRemoteApplication(appDoc)
   319  
   320  		remoteAppOps, err := i.addRemoteApplicationOps(src, app, addRemoteApplicationOpsArgs{
   321  			remoteApplicationDoc: appDoc,
   322  			statusDoc:            appStatusDoc,
   323  		})
   324  		if err != nil {
   325  			return errors.Trace(err)
   326  		}
   327  		ops = append(ops, remoteAppOps...)
   328  	}
   329  	if err := runner.RunTransaction(ops); err != nil {
   330  		return errors.Trace(err)
   331  	}
   332  	return nil
   333  }
   334  
   335  type addRemoteApplicationOpsArgs struct {
   336  	remoteApplicationDoc *remoteApplicationDoc
   337  	statusDoc            *statusDoc
   338  }
   339  
   340  func (i ImportRemoteApplications) addRemoteApplicationOps(src RemoteApplicationsInput,
   341  	app *RemoteApplication,
   342  	args addRemoteApplicationOpsArgs,
   343  ) ([]txn.Op, error) {
   344  	globalKey := app.globalKey()
   345  	docID := src.DocID(app.Name())
   346  
   347  	ops := []txn.Op{
   348  		{
   349  			C:      applicationsC,
   350  			Id:     app.Name(),
   351  			Assert: txn.DocMissing,
   352  		},
   353  		{
   354  			C:      remoteApplicationsC,
   355  			Id:     docID,
   356  			Assert: txn.DocMissing,
   357  			Insert: args.remoteApplicationDoc,
   358  		},
   359  	}
   360  	// The status doc can be optional with a remoteApplication. To ensure that
   361  	// we correctly handle this situation check for it.
   362  	if args.statusDoc != nil {
   363  		ops = append(ops, src.MakeStatusOp(globalKey, *args.statusDoc))
   364  	}
   365  
   366  	return ops, nil
   367  }
   368  
   369  // RemoteEntitiesDescription defines an in-place usage for reading remote entities.
   370  type RemoteEntitiesDescription interface {
   371  	RemoteEntities() []description.RemoteEntity
   372  }
   373  
   374  // ApplicationOffersState is used to look up all application offers.
   375  type ApplicationOffersState interface {
   376  	OfferUUIDForApp(appName string) (string, error)
   377  }
   378  
   379  // RemoteEntitiesInput describes the input used for migrating remote entities.
   380  type RemoteEntitiesInput interface {
   381  	DocModelNamespace
   382  	RemoteEntitiesDescription
   383  	ApplicationOffersState
   384  
   385  	// OfferUUID returns the uuid for a given offer name.
   386  	OfferUUID(offerName string) (string, bool)
   387  }
   388  
   389  // ImportRemoteEntities describes a way to import remote entities from a
   390  // description.
   391  type ImportRemoteEntities struct{}
   392  
   393  // Execute the import on the remote entities description, carefully modelling
   394  // the dependencies we have.
   395  func (im *ImportRemoteEntities) Execute(src RemoteEntitiesInput, runner TransactionRunner) error {
   396  	remoteEntities := src.RemoteEntities()
   397  	if len(remoteEntities) == 0 {
   398  		return nil
   399  	}
   400  	ops := make([]txn.Op, len(remoteEntities))
   401  	for i, entity := range remoteEntities {
   402  		var (
   403  			id  string
   404  			ok  bool
   405  			err error
   406  		)
   407  		if id, ok = im.maybeConvertApplicationOffer(src, entity.ID()); !ok {
   408  			id, err = im.legacyAppToOffer(entity.ID(), src.OfferUUIDForApp)
   409  			if err != nil {
   410  				return errors.Trace(err)
   411  			}
   412  		}
   413  		docID := src.DocID(id)
   414  		ops[i] = txn.Op{
   415  			C:      remoteEntitiesC,
   416  			Id:     docID,
   417  			Assert: txn.DocMissing,
   418  			Insert: &remoteEntityDoc{
   419  				DocID: docID,
   420  				Token: entity.Token(),
   421  			},
   422  		}
   423  	}
   424  	if err := runner.RunTransaction(ops); err != nil {
   425  		return errors.Trace(err)
   426  	}
   427  	return nil
   428  }
   429  
   430  // maybeConvertApplicationOffer returns the offer uuid if an offer name is passed in.
   431  func (im *ImportRemoteEntities) maybeConvertApplicationOffer(src RemoteEntitiesInput, id string) (string, bool) {
   432  	if !strings.HasPrefix(id, names.ApplicationOfferTagKind+"-") {
   433  		return id, false
   434  	}
   435  	offerName := strings.TrimPrefix(id, names.ApplicationOfferTagKind+"-")
   436  	if uuid, ok := src.OfferUUID(offerName); ok {
   437  		return names.NewApplicationOfferTag(uuid).String(), true
   438  	}
   439  	return id, false
   440  }
   441  
   442  func (im *ImportRemoteEntities) legacyAppToOffer(id string, offerUUIDForApp func(string) (string, error)) (string, error) {
   443  	tag, err := names.ParseTag(id)
   444  	if err != nil || tag.Kind() != names.ApplicationTagKind || strings.HasPrefix(tag.Id(), "remote-") {
   445  		return id, err
   446  	}
   447  	offerUUID, err := offerUUIDForApp(tag.Id())
   448  	if errors.Is(err, errors.NotFound) {
   449  		return id, nil
   450  	}
   451  
   452  	return names.NewApplicationOfferTag(offerUUID).String(), err
   453  }
   454  
   455  type applicationOffersStateShim struct {
   456  	stateModelNamspaceShim
   457  
   458  	offerUUIDByName map[string]string
   459  }
   460  
   461  func (s *applicationOffersStateShim) OfferUUID(offerName string) (string, bool) {
   462  	uuid, ok := s.offerUUIDByName[offerName]
   463  	return uuid, ok
   464  }
   465  
   466  func (a applicationOffersStateShim) OfferUUIDForApp(appName string) (string, error) {
   467  	applicationOffersCollection, closer := a.st.db().GetCollection(applicationOffersC)
   468  	defer closer()
   469  
   470  	var doc applicationOfferDoc
   471  	err := applicationOffersCollection.Find(bson.D{{"application-name", appName}}).One(&doc)
   472  	if err == mgo.ErrNotFound {
   473  		return "", errors.NotFoundf("offer for app %q", appName)
   474  	}
   475  	if err != nil {
   476  		return "", errors.Annotate(err, "getting application offer documents")
   477  	}
   478  	return doc.OfferUUID, nil
   479  }
   480  
   481  // RelationNetworksDescription defines an in-place usage for reading relation networks.
   482  type RelationNetworksDescription interface {
   483  	RelationNetworks() []description.RelationNetwork
   484  }
   485  
   486  // RelationNetworksInput describes the input used for migrating relation
   487  // networks.
   488  type RelationNetworksInput interface {
   489  	DocModelNamespace
   490  	RelationNetworksDescription
   491  }
   492  
   493  // ImportRelationNetworks describes a way to import relation networks from a
   494  // description.
   495  type ImportRelationNetworks struct{}
   496  
   497  // Execute the import on the relation networks description, carefully modelling
   498  // the dependencies we have.
   499  func (ImportRelationNetworks) Execute(src RelationNetworksInput, runner TransactionRunner) error {
   500  	relationNetworks := src.RelationNetworks()
   501  	if len(relationNetworks) == 0 {
   502  		return nil
   503  	}
   504  
   505  	ops := make([]txn.Op, len(relationNetworks))
   506  	for i, entity := range relationNetworks {
   507  		docID := src.DocID(entity.ID())
   508  		ops[i] = txn.Op{
   509  			C:      relationNetworksC,
   510  			Id:     docID,
   511  			Assert: txn.DocMissing,
   512  			Insert: relationNetworksDoc{
   513  				Id:          docID,
   514  				RelationKey: entity.RelationKey(),
   515  				CIDRS:       entity.CIDRS(),
   516  			},
   517  		}
   518  	}
   519  
   520  	if err := runner.RunTransaction(ops); err != nil {
   521  		return errors.Trace(err)
   522  	}
   523  	return nil
   524  }
   525  
   526  // ExternalControllerStateDocumentFactory creates documents that are useful with
   527  // in the state package. In essence this just allows us to model our
   528  // dependencies correctly without having to construct dependencies everywhere.
   529  // Note: we need public methods here because gomock doesn't mock private methods
   530  type ExternalControllerStateDocumentFactory interface {
   531  	ExternalControllerDoc(string) (*externalControllerDoc, error)
   532  	MakeExternalControllerOp(externalControllerDoc, *externalControllerDoc) txn.Op
   533  }
   534  
   535  // ExternalControllersDescription defines an in-place usage for reading external
   536  // controllers
   537  type ExternalControllersDescription interface {
   538  	ExternalControllers() []description.ExternalController
   539  }
   540  
   541  // ExternalControllersInput describes the input used for migrating external
   542  // controllers.
   543  type ExternalControllersInput interface {
   544  	ExternalControllerStateDocumentFactory
   545  	ExternalControllersDescription
   546  }
   547  
   548  // stateExternalControllerDocumentFactoryShim is required to allow the new
   549  // vertical boundary around importing a external controller, from being accessed
   550  // by the existing state package code.
   551  // That way we can keep the importing code clean from the proliferation of state
   552  // code in the juju code base.
   553  type stateExternalControllerDocumentFactoryShim struct {
   554  	stateModelNamspaceShim
   555  	importer *importer
   556  }
   557  
   558  func (s stateExternalControllerDocumentFactoryShim) ExternalControllerDoc(uuid string) (*externalControllerDoc, error) {
   559  	service := NewExternalControllers(s.importer.st)
   560  	return service.controller(uuid)
   561  }
   562  
   563  func (s stateExternalControllerDocumentFactoryShim) MakeExternalControllerOp(doc externalControllerDoc, existing *externalControllerDoc) txn.Op {
   564  	return upsertExternalControllerOp(&doc, existing, doc.Models)
   565  }
   566  
   567  // ImportExternalControllers describes a way to import external controllers
   568  // from a description.
   569  type ImportExternalControllers struct{}
   570  
   571  // Execute the import on the external controllers description, carefully
   572  // modelling the dependencies we have.
   573  func (ImportExternalControllers) Execute(src ExternalControllersInput, runner TransactionRunner) error {
   574  	externalControllers := src.ExternalControllers()
   575  	if len(externalControllers) == 0 {
   576  		return nil
   577  	}
   578  
   579  	ops := make([]txn.Op, len(externalControllers))
   580  	for i, entity := range externalControllers {
   581  		controllerID := entity.ID().Id()
   582  		doc := externalControllerDoc{
   583  			Id:     controllerID,
   584  			Alias:  entity.Alias(),
   585  			Addrs:  entity.Addrs(),
   586  			CACert: entity.CACert(),
   587  			Models: entity.Models(),
   588  		}
   589  		existing, err := src.ExternalControllerDoc(controllerID)
   590  		if err != nil && !errors.IsNotFound(err) {
   591  			return errors.Trace(err)
   592  		}
   593  		ops[i] = src.MakeExternalControllerOp(doc, existing)
   594  	}
   595  
   596  	if err := runner.RunTransaction(ops); err != nil {
   597  		return errors.Trace(err)
   598  	}
   599  	return nil
   600  }
   601  
   602  // SecretsDescription defines an in-place usage for reading secrets.
   603  type SecretsDescription interface {
   604  	Secrets() []description.Secret
   605  	RemoteSecrets() []description.RemoteSecret
   606  }
   607  
   608  // SecretConsumersState is used to create secret consumer keys
   609  // for use in the state model.
   610  type SecretConsumersState interface {
   611  	SecretConsumerKey(uri *secrets.URI, subject string) string
   612  }
   613  
   614  // BackendRevisionCountProcesser is used to create a backend revision reference count.
   615  type BackendRevisionCountProcesser interface {
   616  	IncBackendRevisionCountOps(backendID string) ([]txn.Op, error)
   617  }
   618  
   619  // SecretsInput describes the input used for migrating secrets.
   620  type SecretsInput interface {
   621  	DocModelNamespace
   622  	SecretConsumersState
   623  	BackendRevisionCountProcesser
   624  	SecretsDescription
   625  }
   626  
   627  type secretStateShim struct {
   628  	stateModelNamspaceShim
   629  }
   630  
   631  func (s *secretStateShim) SecretConsumerKey(uri *secrets.URI, subject string) string {
   632  	return s.st.secretConsumerKey(uri, subject)
   633  }
   634  
   635  func (s *secretStateShim) IncBackendRevisionCountOps(backendID string) ([]txn.Op, error) {
   636  	return s.st.incBackendRevisionCountOps(backendID, 1)
   637  }
   638  
   639  // ImportSecrets describes a way to import secrets from a
   640  // description.
   641  type ImportSecrets struct{}
   642  
   643  // Execute the import on the secrets description, carefully modelling
   644  // the dependencies we have.
   645  func (ImportSecrets) Execute(src SecretsInput, runner TransactionRunner, knownSecretBackends set.Strings) error {
   646  	allSecrets := src.Secrets()
   647  	if len(allSecrets) == 0 {
   648  		return nil
   649  	}
   650  
   651  	seenBackendIds := set.NewStrings()
   652  	var ops []txn.Op
   653  	for _, secret := range allSecrets {
   654  		uri := &secrets.URI{ID: secret.Id()}
   655  		docID := src.DocID(secret.Id())
   656  		owner, err := secret.Owner()
   657  		if err != nil {
   658  			return errors.Annotatef(err, "invalid owner for secret %q", secret.Id())
   659  		}
   660  		ops = append(ops, txn.Op{
   661  			C:      secretMetadataC,
   662  			Id:     docID,
   663  			Assert: txn.DocMissing,
   664  			Insert: secretMetadataDoc{
   665  				DocID:            docID,
   666  				Version:          secret.Version(),
   667  				OwnerTag:         owner.String(),
   668  				Description:      secret.Description(),
   669  				Label:            secret.Label(),
   670  				LatestRevision:   secret.LatestRevision(),
   671  				LatestExpireTime: secret.LatestExpireTime(),
   672  				RotatePolicy:     secret.RotatePolicy(),
   673  				AutoPrune:        secret.AutoPrune(),
   674  				CreateTime:       secret.Created(),
   675  				UpdateTime:       secret.Updated(),
   676  			},
   677  		})
   678  		if secret.NextRotateTime() != nil {
   679  			nextRotateTime := secret.NextRotateTime()
   680  			ops = append(ops, txn.Op{
   681  				C:      secretRotateC,
   682  				Id:     docID,
   683  				Assert: txn.DocMissing,
   684  				Insert: secretRotationDoc{
   685  					DocID:          docID,
   686  					NextRotateTime: *nextRotateTime,
   687  				},
   688  			})
   689  		}
   690  		for _, rev := range secret.Revisions() {
   691  			key := secretRevisionKey(uri, rev.Number())
   692  			dataCopy := make(secretsDataMap)
   693  			for k, v := range rev.Content() {
   694  				dataCopy[k] = v
   695  			}
   696  			var valueRef *valueRefDoc
   697  			if len(dataCopy) == 0 {
   698  				valueRef = &valueRefDoc{
   699  					BackendID:  rev.ValueRef().BackendID(),
   700  					RevisionID: rev.ValueRef().RevisionID(),
   701  				}
   702  				if !secrets.IsInternalSecretBackendID(valueRef.BackendID) && !seenBackendIds.Contains(valueRef.BackendID) {
   703  					if !knownSecretBackends.Contains(valueRef.BackendID) {
   704  						return errors.New("target controller does not have all required secret backends set up")
   705  					}
   706  					ops = append(ops, txn.Op{
   707  						C:      secretBackendsC,
   708  						Id:     valueRef.BackendID,
   709  						Assert: txn.DocExists,
   710  					})
   711  				}
   712  				seenBackendIds.Add(valueRef.BackendID)
   713  			}
   714  			ops = append(ops, txn.Op{
   715  				C:      secretRevisionsC,
   716  				Id:     key,
   717  				Assert: txn.DocMissing,
   718  				Insert: secretRevisionDoc{
   719  					DocID:         key,
   720  					Revision:      rev.Number(),
   721  					CreateTime:    rev.Created(),
   722  					UpdateTime:    rev.Updated(),
   723  					ExpireTime:    rev.ExpireTime(),
   724  					Obsolete:      rev.Obsolete(),
   725  					PendingDelete: rev.PendingDelete(),
   726  					Data:          dataCopy,
   727  					ValueRef:      valueRef,
   728  					OwnerTag:      owner.String(),
   729  				},
   730  			})
   731  			if valueRef != nil {
   732  				refOps, err := src.IncBackendRevisionCountOps(valueRef.BackendID)
   733  				if err != nil {
   734  					return errors.Trace(err)
   735  				}
   736  				ops = append(ops, refOps...)
   737  			}
   738  		}
   739  		for subject, access := range secret.ACL() {
   740  			key := src.SecretConsumerKey(uri, subject)
   741  			ops = append(ops, txn.Op{
   742  				C:      secretPermissionsC,
   743  				Id:     key,
   744  				Assert: txn.DocMissing,
   745  				Insert: secretPermissionDoc{
   746  					DocID:   key,
   747  					Subject: subject,
   748  					Scope:   access.Scope(),
   749  					Role:    access.Role(),
   750  				},
   751  			})
   752  		}
   753  		for _, info := range secret.Consumers() {
   754  			consumer, err := info.Consumer()
   755  			if err != nil {
   756  				return errors.Annotatef(err, "invalid consumer for secret %q", secret.Id())
   757  			}
   758  			key := src.SecretConsumerKey(uri, consumer.String())
   759  			currentRev := info.CurrentRevision()
   760  			latestRev := info.LatestRevision()
   761  			// Older models may have set the consumed rev info to 0 (assuming the latest revision always).
   762  			// So set the latest values explicitly.
   763  			if currentRev == 0 {
   764  				currentRev = secret.LatestRevision()
   765  				latestRev = secret.LatestRevision()
   766  			}
   767  			ops = append(ops, txn.Op{
   768  				C:      secretConsumersC,
   769  				Id:     key,
   770  				Assert: txn.DocMissing,
   771  				Insert: secretConsumerDoc{
   772  					DocID:           key,
   773  					ConsumerTag:     consumer.String(),
   774  					Label:           info.Label(),
   775  					CurrentRevision: currentRev,
   776  					LatestRevision:  latestRev,
   777  				},
   778  			})
   779  		}
   780  		for _, info := range secret.RemoteConsumers() {
   781  			consumer, err := info.Consumer()
   782  			if err != nil {
   783  				return errors.Annotatef(err, "invalid consumer for secret %q", secret.Id())
   784  			}
   785  			key := src.SecretConsumerKey(uri, consumer.String())
   786  			ops = append(ops, txn.Op{
   787  				C:      secretRemoteConsumersC,
   788  				Id:     key,
   789  				Assert: txn.DocMissing,
   790  				Insert: secretRemoteConsumerDoc{
   791  					DocID:           key,
   792  					ConsumerTag:     consumer.String(),
   793  					CurrentRevision: info.CurrentRevision(),
   794  					LatestRevision:  info.LatestRevision(),
   795  				},
   796  			})
   797  		}
   798  	}
   799  
   800  	if err := runner.RunTransaction(ops); err != nil {
   801  		return errors.Trace(err)
   802  	}
   803  	return nil
   804  }
   805  
   806  // RemoteSecretsInput describes the input used for migrating remote secret consumer info.
   807  type RemoteSecretsInput interface {
   808  	DocModelNamespace
   809  	SecretConsumersState
   810  	SecretsDescription
   811  }
   812  
   813  // ImportRemoteSecrets describes a way to import remote
   814  // secrets from a description.
   815  type ImportRemoteSecrets struct{}
   816  
   817  // Execute the import on the remote secrets description.
   818  func (ImportRemoteSecrets) Execute(src RemoteSecretsInput, runner TransactionRunner) error {
   819  	allRemoteSecrets := src.RemoteSecrets()
   820  	if len(allRemoteSecrets) == 0 {
   821  		return nil
   822  	}
   823  
   824  	var ops []txn.Op
   825  	for _, info := range allRemoteSecrets {
   826  		uri := &secrets.URI{ID: info.ID(), SourceUUID: info.SourceUUID()}
   827  		consumer, err := info.Consumer()
   828  		if err != nil {
   829  			return errors.Annotatef(err, "invalid consumer for remote secret %q", uri)
   830  		}
   831  		key := src.SecretConsumerKey(uri, consumer.String())
   832  		ops = append(ops, txn.Op{
   833  			C:      secretConsumersC,
   834  			Id:     key,
   835  			Assert: txn.DocMissing,
   836  			Insert: secretConsumerDoc{
   837  				DocID:           key,
   838  				ConsumerTag:     consumer.String(),
   839  				Label:           info.Label(),
   840  				CurrentRevision: info.CurrentRevision(),
   841  				LatestRevision:  info.LatestRevision(),
   842  			},
   843  		})
   844  	}
   845  
   846  	if err := runner.RunTransaction(ops); err != nil {
   847  		return errors.Trace(err)
   848  	}
   849  	return nil
   850  }