github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/resources_staged.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "bytes" 8 9 "github.com/juju/errors" 10 "github.com/juju/mgo/v3/txn" 11 ) 12 13 // StagedResource represents resource info that has been added to the 14 // "staging" area of the underlying data store. It remains unavailable 15 // until finalized, at which point it moves out of the staging area and 16 // replaces the current active resource info. 17 type StagedResource struct { 18 p *resourcePersistence 19 id string 20 stored storedResource 21 } 22 23 func (staged StagedResource) stage() error { 24 buildTxn := func(attempt int) ([]txn.Op, error) { 25 var ops []txn.Op 26 switch attempt { 27 case 0: 28 ops = newInsertStagedResourceOps(staged.stored) 29 case 1: 30 ops = newEnsureStagedResourceSameOps(staged.stored) 31 default: 32 return nil, errors.NewAlreadyExists(nil, "already staged") 33 } 34 if staged.stored.PendingID == "" { 35 // Only non-pending resources must have an existing application. 36 ops = append(ops, applicationExistsOps(staged.stored.ApplicationID)...) 37 } 38 39 return ops, nil 40 } 41 if err := staged.p.st.db().Run(buildTxn); err != nil { 42 return errors.Trace(err) 43 } 44 return nil 45 } 46 47 // Unstage ensures that the resource is removed 48 // from the staging area. If it isn't in the staging area 49 // then this is a noop. 50 func (staged StagedResource) Unstage() error { 51 buildTxn := func(attempt int) ([]txn.Op, error) { 52 if attempt > 0 { 53 // The op has no assert so we should not get here. 54 return nil, errors.New("unstaging the resource failed") 55 } 56 57 ops := newRemoveStagedResourceOps(staged.id) 58 return ops, nil 59 } 60 if err := staged.p.st.db().Run(buildTxn); err != nil { 61 return errors.Trace(err) 62 } 63 return nil 64 } 65 66 // Activate makes the staged resource the active resource. 67 func (staged StagedResource) Activate(incrementCharmModifiedVersion IncrementCharmModifiedVersionType) error { 68 buildTxn := func(attempt int) ([]txn.Op, error) { 69 // This is an "upsert". 70 var ops []txn.Op 71 switch attempt { 72 case 0: 73 ops = newInsertResourceOps(staged.stored) 74 case 1: 75 ops = newUpdateResourceOps(staged.stored) 76 default: 77 return nil, errors.New("setting the resource failed") 78 } 79 if staged.stored.PendingID == "" { 80 // Only non-pending resources must have an existing application. 81 ops = append(ops, applicationExistsOps(staged.stored.ApplicationID)...) 82 } 83 // No matter what, we always remove any staging. 84 ops = append(ops, newRemoveStagedResourceOps(staged.id)...) 85 86 // If we are changing the bytes for a resource, we increment the 87 // CharmModifiedVersion on the application, since resources are integral to 88 // the high level "version" of the charm. 89 if staged.stored.PendingID == "" { 90 hasNewBytes, err := staged.hasNewBytes() 91 if err != nil { 92 logger.Errorf("can't read existing resource during activate: %v", errors.Details(err)) 93 return nil, errors.Trace(err) 94 } 95 if hasNewBytes && incrementCharmModifiedVersion == IncrementCharmModifiedVersion { 96 incOps := incCharmModifiedVersionOps(staged.stored.ApplicationID) 97 ops = append(ops, incOps...) 98 } 99 } 100 return ops, nil 101 } 102 if err := staged.p.st.db().Run(buildTxn); err != nil { 103 return errors.Trace(err) 104 } 105 return nil 106 } 107 108 func (staged StagedResource) hasNewBytes() (bool, error) { 109 var current resourceDoc 110 err := staged.p.one(resourcesC, staged.stored.ID, ¤t) 111 switch { 112 case errors.IsNotFound(err): 113 // if there's no current resource stored, then any non-zero bytes will 114 // be new. 115 return !staged.stored.Fingerprint.IsZero(), nil 116 case err != nil: 117 return false, errors.Annotate(err, "couldn't read existing resource") 118 default: 119 diff := !bytes.Equal(staged.stored.Fingerprint.Bytes(), current.Fingerprint) 120 return diff, nil 121 } 122 }