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 }