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  }