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 }