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