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 }