1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
11
"launchpad.net/juju-core/charm"
12
"launchpad.net/juju-core/utils/set"
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
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 {
25
return gitDeployer, nil
27
return NewManifestDeployer(charmPath, dataPath, bundles), nil
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
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)
46
gitDeployer, ok := (*deployer).(*gitDeployer)
48
return fmt.Errorf("cannot fix unknown deployer type: %T", *deployer)
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,
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
60
deployedURL, err := ReadCharmURL(manifestDeployer.CharmPath(charmURLPath))
61
if err != nil && !os.IsNotExist(err) {
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 {
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())
77
if err := manifestDeployer.storeManifest(deployedURL, manifest); err != nil {
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 {
89
// Note potential race alluded to at the start of this func.
90
collectGitOrphans(gitDeployer.dataPath)
93
*deployer = manifestDeployer
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 {
102
repo := gitDeployer.current
104
stagedURL, err := gitDeployer.current.ReadCharmURL()
108
logger.Debugf("staged url: %s", stagedURL)
109
if *stagedURL == *expectURL {
112
if err := repo.cmd("checkout", fmt.Sprintf("master~%d", i)); err != nil {
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)
128
return set.NewStrings(), err
130
manifest := set.NewStrings()
131
err = filepath.Walk(dirPath, func(path string, fileInfo os.FileInfo, err error) error {
135
relPath, err := filepath.Rel(dirPath, path)
140
case ".", charmURLPath:
143
err = filepath.SkipDir
145
manifest.Add(filepath.ToSlash(relPath))
149
return set.NewStrings(), err