github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/worker/uniter/charm/manifest_deployer.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charm 5 6 import ( 7 "fmt" 8 "os" 9 "path/filepath" 10 11 "github.com/juju/utils" 12 "github.com/juju/utils/set" 13 14 "github.com/juju/juju/charm" 15 ) 16 17 const ( 18 // deployingURLPath holds the path in the charm dir where the manifest 19 // deployer writes what charm is currently being deployed. 20 deployingURLPath = ".juju-deploying" 21 22 // manifestsDataPath holds the path in the data dir where the manifest 23 // deployer stores the manifests for its charms. 24 manifestsDataPath = "manifests" 25 ) 26 27 // NewManifestDeployer returns a Deployer that installs bundles from the 28 // supplied BundleReader into charmPath, and which reads and writes its 29 // persistent data into dataPath. 30 // 31 // It works by always writing the full contents of a deployed charm; and, if 32 // another charm was previously deployed, deleting only those files unique to 33 // that base charm. It thus leaves user files in place, with the exception of 34 // those in directories referenced only in the original charm, which will be 35 // deleted. 36 func NewManifestDeployer(charmPath, dataPath string, bundles BundleReader) Deployer { 37 return &manifestDeployer{ 38 charmPath: charmPath, 39 dataPath: dataPath, 40 bundles: bundles, 41 } 42 } 43 44 type manifestDeployer struct { 45 charmPath string 46 dataPath string 47 bundles BundleReader 48 staged struct { 49 url *charm.URL 50 bundle Bundle 51 manifest set.Strings 52 } 53 } 54 55 func (d *manifestDeployer) Stage(info BundleInfo, abort <-chan struct{}) error { 56 bundle, err := d.bundles.Read(info, abort) 57 if err != nil { 58 return err 59 } 60 manifest, err := bundle.Manifest() 61 if err != nil { 62 return err 63 } 64 url := info.URL() 65 if err := d.storeManifest(url, manifest); err != nil { 66 return err 67 } 68 d.staged.url = url 69 d.staged.bundle = bundle 70 d.staged.manifest = manifest 71 return nil 72 } 73 74 func (d *manifestDeployer) Deploy() (err error) { 75 if d.staged.url == nil { 76 return fmt.Errorf("charm deployment failed: no charm set") 77 } 78 79 // Detect and resolve state of charm directory. 80 baseURL, baseManifest, err := d.loadManifest(charmURLPath) 81 if err != nil && !os.IsNotExist(err) { 82 return err 83 } 84 upgrading := baseURL != nil 85 defer manifestDeployError(&err, upgrading) 86 if err := d.ensureBaseFiles(baseManifest); err != nil { 87 return err 88 } 89 90 // Write or overwrite the deploying URL to point to the staged one. 91 if err := d.startDeploy(); err != nil { 92 return err 93 } 94 95 // Delete files in the base version not present in the staged charm. 96 if upgrading { 97 if err := d.removeDiff(baseManifest, d.staged.manifest); err != nil { 98 return err 99 } 100 } 101 102 // Overwrite whatever's in place with the staged charm. 103 logger.Debugf("deploying charm %q", d.staged.url) 104 if err := d.staged.bundle.ExpandTo(d.charmPath); err != nil { 105 return err 106 } 107 108 // Move the deploying file over the charm URL file, and we're done. 109 return d.finishDeploy() 110 } 111 112 func (d *manifestDeployer) NotifyResolved() error { 113 // Maybe it is resolved, maybe not. We'll find out soon enough, but we 114 // don't need to take any action now; if it's not, we'll just ErrConflict 115 // out of Deploy again. 116 return nil 117 } 118 119 func (d *manifestDeployer) NotifyRevert() error { 120 // The Deploy implementation always effectively reverts when required 121 // anyway, so we need take no action right now. 122 return nil 123 } 124 125 // startDeploy persists the fact that we've started deploying the staged bundle. 126 func (d *manifestDeployer) startDeploy() error { 127 logger.Debugf("preparing to deploy charm %q", d.staged.url) 128 if err := os.MkdirAll(d.charmPath, 0755); err != nil { 129 return err 130 } 131 return WriteCharmURL(d.CharmPath(deployingURLPath), d.staged.url) 132 } 133 134 // removeDiff removes every path in oldManifest that is not present in newManifest. 135 func (d *manifestDeployer) removeDiff(oldManifest, newManifest set.Strings) error { 136 diff := oldManifest.Difference(newManifest) 137 for _, path := range diff.SortedValues() { 138 fullPath := filepath.Join(d.charmPath, filepath.FromSlash(path)) 139 if err := os.RemoveAll(fullPath); err != nil { 140 return err 141 } 142 } 143 return nil 144 } 145 146 // finishDeploy persists the fact that we've finished deploying the staged bundle. 147 func (d *manifestDeployer) finishDeploy() error { 148 logger.Debugf("finishing deploy of charm %q", d.staged.url) 149 oldPath := d.CharmPath(deployingURLPath) 150 newPath := d.CharmPath(charmURLPath) 151 return utils.ReplaceFile(oldPath, newPath) 152 } 153 154 // ensureBaseFiles checks for an interrupted deploy operation and, if it finds 155 // one, removes all entries in the manifest unique to the interrupted operation. 156 // This leaves files from the base charm in an indeterminate state, but ready to 157 // be either removed (if they are not referenced by the new charm) or overwritten 158 // (if they are referenced by the new charm). 159 // 160 // Note that deployingURLPath is *not* written, because the charm state remains 161 // indeterminate; that file will be removed when and only when a deploy completes 162 // successfully. 163 func (d *manifestDeployer) ensureBaseFiles(baseManifest set.Strings) error { 164 deployingURL, deployingManifest, err := d.loadManifest(deployingURLPath) 165 if err == nil { 166 logger.Infof("detected interrupted deploy of charm %q", deployingURL) 167 if *deployingURL != *d.staged.url { 168 logger.Infof("removing files from charm %q", deployingURL) 169 if err := d.removeDiff(deployingManifest, baseManifest); err != nil { 170 return err 171 } 172 } 173 } 174 if os.IsNotExist(err) { 175 err = nil 176 } 177 return err 178 } 179 180 // storeManifest stores, into dataPath, the supplied manifest for the supplied charm. 181 func (d *manifestDeployer) storeManifest(url *charm.URL, manifest set.Strings) error { 182 if err := os.MkdirAll(d.DataPath(manifestsDataPath), 0755); err != nil { 183 return err 184 } 185 name := charm.Quote(url.String()) 186 path := filepath.Join(d.DataPath(manifestsDataPath), name) 187 return utils.WriteYaml(path, manifest.SortedValues()) 188 } 189 190 // loadManifest loads, from dataPath, the manifest for the charm identified by the 191 // identity file at the supplied path within the charm directory. 192 func (d *manifestDeployer) loadManifest(urlFilePath string) (*charm.URL, set.Strings, error) { 193 url, err := ReadCharmURL(d.CharmPath(urlFilePath)) 194 if err != nil { 195 return nil, set.NewStrings(), err 196 } 197 name := charm.Quote(url.String()) 198 path := filepath.Join(d.DataPath(manifestsDataPath), name) 199 manifest := []string{} 200 err = utils.ReadYaml(path, &manifest) 201 if os.IsNotExist(err) { 202 logger.Warningf("manifest not found at %q: files from charm %q may be left unremoved", path, url) 203 err = nil 204 } 205 return url, set.NewStrings(manifest...), err 206 } 207 208 // CharmPath returns the supplied path joined to the ManifestDeployer's charm directory. 209 func (d *manifestDeployer) CharmPath(path string) string { 210 return filepath.Join(d.charmPath, path) 211 } 212 213 // DataPath returns the supplied path joined to the ManifestDeployer's data directory. 214 func (d *manifestDeployer) DataPath(path string) string { 215 return filepath.Join(d.dataPath, path) 216 } 217 218 // manifestDeployError annotates or replaces the supplied error according 219 // to whether or not an upgrade operation is in play. It was extracted from 220 // Deploy to aid that method's readability. 221 func manifestDeployError(err *error, upgrading bool) { 222 if *err != nil { 223 if upgrading { 224 // We now treat any failure to overwrite the charm -- or otherwise 225 // manipulate the charm directory -- as a conflict, because it's 226 // actually plausible for a user (or at least a charm author, who 227 // is the real audience for this case) to get in there and fix it. 228 logger.Errorf("cannot upgrade charm: %v", *err) 229 *err = ErrConflict 230 } else { 231 // ...but if we can't install at all, we just fail out as the old 232 // gitDeployer did, because I'm not willing to mess around with 233 // the uniter to enable ErrConflict handling on install. We've 234 // never heard of it actually happening, so this is probably not 235 // a big deal. 236 *err = fmt.Errorf("cannot install charm: %v", *err) 237 } 238 } 239 }