github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/uniter/charm/converter.go (about)

     1  // Copyright 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/set"
    12  	"github.com/juju/utils/symlink"
    13  	"gopkg.in/juju/charm.v6-unstable"
    14  )
    15  
    16  // NewDeployer returns a Deployer of whatever kind is currently in use for the
    17  // supplied paths, or a manifest deployer if none exists yet. It is a var so
    18  // that it can be patched for uniter tests.
    19  var NewDeployer = newDeployer
    20  
    21  func newDeployer(charmPath, dataPath string, bundles BundleReader) (Deployer, error) {
    22  	gitDeployer := NewGitDeployer(charmPath, dataPath, bundles).(*gitDeployer)
    23  	if exists, err := gitDeployer.current.Exists(); err != nil {
    24  		return nil, err
    25  	} else if exists {
    26  		return gitDeployer, nil
    27  	}
    28  	return NewManifestDeployer(charmPath, dataPath, bundles), nil
    29  }
    30  
    31  // FixDeployer ensures that the supplied Deployer address points to a manifest
    32  // deployer. If a git deployer is passed into FixDeployer, it will be converted
    33  // to a manifest deployer, and the git deployer data will be removed. The charm
    34  // is assumed to be in a stable state; this should not be called if there is any
    35  // chance the git deployer is partway through an upgrade, or in a conflicted state.
    36  // It is a var so that it can be patched for uniter tests.
    37  var FixDeployer = fixDeployer
    38  
    39  func fixDeployer(deployer *Deployer) error {
    40  	if manifestDeployer, ok := (*deployer).(*manifestDeployer); ok {
    41  		// This works around a race at the very end of this func, in which
    42  		// the process could have been killed after removing the "current"
    43  		// symlink but before removing the orphan repos from the data dir.
    44  		collectGitOrphans(manifestDeployer.dataPath)
    45  		return nil
    46  	}
    47  	gitDeployer, ok := (*deployer).(*gitDeployer)
    48  	if !ok {
    49  		return fmt.Errorf("cannot fix unknown deployer type: %T", *deployer)
    50  	}
    51  	logger.Infof("converting git-based deployer to manifest deployer")
    52  	manifestDeployer := &manifestDeployer{
    53  		charmPath: gitDeployer.target.Path(),
    54  		dataPath:  gitDeployer.dataPath,
    55  		bundles:   gitDeployer.bundles,
    56  	}
    57  
    58  	// Ensure that the staged charm matches the deployed charm: it's possible
    59  	// that the uniter was stopped after staging, but before deploying, a new
    60  	// bundle.
    61  	deployedURL, err := ReadCharmURL(manifestDeployer.CharmPath(CharmURLPath))
    62  	if err != nil && !os.IsNotExist(err) {
    63  		return err
    64  	}
    65  
    66  	// If we deployed something previously, we need to copy some state over.
    67  	if deployedURL != nil {
    68  		if err := ensureCurrentGitCharm(gitDeployer, deployedURL); err != nil {
    69  			return err
    70  		}
    71  		// Now we know we've got the right stuff checked out in gitDeployer.current,
    72  		// we can turn that into a manifest that will be used in future upgrades...
    73  		// even if users desparate for space deleted the original bundle.
    74  		manifest, err := gitManifest(gitDeployer.current.Path())
    75  		if err != nil {
    76  			return err
    77  		}
    78  		if err := manifestDeployer.storeManifest(deployedURL, manifest); err != nil {
    79  			return err
    80  		}
    81  	}
    82  
    83  	// We're left with the staging repo and a symlink to it. We decide deployer
    84  	// type by checking existence of the symlink's target, so we start off by
    85  	// trashing the symlink itself; collectGitOrphans will then delete all the
    86  	// original deployer's repos.
    87  	if err := os.RemoveAll(gitDeployer.current.Path()); err != nil {
    88  		return err
    89  	}
    90  	// Note potential race alluded to at the start of this func.
    91  	collectGitOrphans(gitDeployer.dataPath)
    92  
    93  	// Phew. Done.
    94  	*deployer = manifestDeployer
    95  	return nil
    96  }
    97  
    98  // ensureCurrentGitCharm checks out progressively earlier versions of the
    99  // gitDeployer's current staging repo, until it finds one in which the
   100  // content of charmURLPath matches the supplied charm URL.
   101  func ensureCurrentGitCharm(gitDeployer *gitDeployer, expectURL *charm.URL) error {
   102  	i := 1
   103  	repo := gitDeployer.current
   104  	for {
   105  		stagedURL, err := gitDeployer.current.ReadCharmURL()
   106  		if err != nil {
   107  			return err
   108  		}
   109  		logger.Debugf("staged url: %s", stagedURL)
   110  		if *stagedURL == *expectURL {
   111  			return nil
   112  		}
   113  		if err := repo.cmd("checkout", fmt.Sprintf("master~%d", i)); err != nil {
   114  			return err
   115  		}
   116  		i++
   117  	}
   118  }
   119  
   120  // gitManifest returns every file path in the supplied directory, *except* for:
   121  //    * paths below .git, because we don't need to track every file: we just
   122  //      want them all gone
   123  //    * charmURLPath, because we don't ever want to remove that: that's how
   124  //      the manifestDeployer keeps track of what version it's upgrading from.
   125  // All paths are slash-separated, to match the bundle manifest format.
   126  func gitManifest(linkPath string) (set.Strings, error) {
   127  	dirPath, err := symlink.Read(linkPath)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	manifest := make(set.Strings)
   132  	err = filepath.Walk(dirPath, func(path string, fileInfo os.FileInfo, err error) error {
   133  		if err != nil {
   134  			return err
   135  		}
   136  		relPath, err := filepath.Rel(dirPath, path)
   137  		if err != nil {
   138  			return err
   139  		}
   140  		switch relPath {
   141  		case ".", CharmURLPath:
   142  			return nil
   143  		case ".git":
   144  			err = filepath.SkipDir
   145  		}
   146  		manifest.Add(filepath.ToSlash(relPath))
   147  		return err
   148  	})
   149  	return manifest, err
   150  }