~wallyworld/juju-core/fast-lxc-everywhere

« back to all changes in this revision

Viewing changes to worker/uniter/charm/converter.go

  • Committer: Andrew Wilkins
  • Date: 2014-04-22 09:23:39 UTC
  • mfrom: (2660 juju-core)
  • mto: This revision was merged to the branch mainline in revision 2666.
  • Revision ID: andrew.wilkins@canonical.com-20140422092339-f4sih3li3p0tmbkd
Merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        "launchpad.net/juju-core/charm"
 
12
        "launchpad.net/juju-core/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
}