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