launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/uniter/charm/deployer.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charm 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "time" 12 13 "launchpad.net/errgo/errors" 14 "launchpad.net/juju-core/charm" 15 "launchpad.net/juju-core/log" 16 ) 17 18 const ( 19 updatePrefix = "update-" 20 installPrefix = "install-" 21 ) 22 23 // Deployer maintains a git repository tracking a series of charm versions, 24 // and can install and upgrade charm deployments to the current version. 25 type Deployer struct { 26 path string 27 current *GitDir 28 } 29 30 // NewDeployer creates a new Deployer which stores its state in the supplied 31 // directory. 32 func NewDeployer(path string) *Deployer { 33 return &Deployer{ 34 path: path, 35 current: NewGitDir(filepath.Join(path, "current")), 36 } 37 } 38 39 // Stage causes subsequent calls to Deploy to deploy the supplied charm. 40 func (d *Deployer) Stage(bun *charm.Bundle, url *charm.URL) error { 41 // Read present state of current. 42 if err := os.MkdirAll(d.path, 0755); err != nil { 43 return mask(err) 44 } 45 defer d.collectOrphans() 46 srcExists, err := d.current.Exists() 47 if err != nil { 48 return mask(err) 49 } 50 if srcExists { 51 prevURL, err := ReadCharmURL(d.current) 52 if err != nil { 53 return mask(err) 54 } 55 if *url == *prevURL { 56 return nil 57 } 58 } 59 60 // Prepare a fresh repository for the update, using current's history 61 // if it exists. 62 updatePath, err := d.newDir(updatePrefix) 63 if err != nil { 64 return mask(err) 65 } 66 var repo *GitDir 67 if srcExists { 68 repo, err = d.current.Clone(updatePath) 69 } else { 70 repo = NewGitDir(updatePath) 71 err = repo.Init() 72 } 73 if err != nil { 74 return mask(err) 75 } 76 77 // Write the desired new state and commit. 78 if err = bun.ExpandTo(updatePath); err != nil { 79 return mask(err) 80 } 81 if err = WriteCharmURL(repo, url); err != nil { 82 return mask(err) 83 } 84 if err = repo.Snapshotf("Imported charm %q from %q.", url, bun.Path); err != nil { 85 return mask(err) 86 } 87 88 // Atomically rename fresh repository to current. 89 tmplink := filepath.Join(updatePath, "tmplink") 90 if err = os.Symlink(updatePath, tmplink); err != nil { 91 return mask(err) 92 } 93 return os.Rename(tmplink, d.current.Path()) 94 } 95 96 // Deploy deploys the current charm to the target directory. 97 func (d *Deployer) Deploy(target *GitDir) (err error) { 98 defer func() { 99 if errors.Cause(err) == ErrConflict { 100 log.Warningf("worker/uniter/charm: charm deployment completed with conflicts") 101 } else if err != nil { 102 err = errors.Newf("charm deployment failed: %s", err) 103 log.Errorf("worker/uniter/charm: %v", err) 104 } else { 105 log.Infof("worker/uniter/charm: charm deployment succeeded") 106 } 107 }() 108 if exists, err := d.current.Exists(); err != nil { 109 return mask(err) 110 } else if !exists { 111 return errors.Newf("no charm set") 112 } 113 if exists, err := target.Exists(); err != nil { 114 return mask(err) 115 } else if !exists { 116 return d.install(target) 117 } 118 return d.upgrade(target) 119 } 120 121 // install creates a new deployment of current, and atomically moves it to 122 // target. 123 func (d *Deployer) install(target *GitDir) error { 124 defer d.collectOrphans() 125 log.Infof("worker/uniter/charm: preparing new charm deployment") 126 url, err := ReadCharmURL(d.current) 127 if err != nil { 128 return mask(err) 129 } 130 installPath, err := d.newDir(installPrefix) 131 if err != nil { 132 return mask(err) 133 } 134 repo := NewGitDir(installPath) 135 if err = repo.Init(); err != nil { 136 return mask(err) 137 } 138 if err = repo.Pull(d.current); err != nil { 139 return mask(err) 140 } 141 if err = repo.Snapshotf("Deployed charm %q.", url); err != nil { 142 return mask(err) 143 } 144 log.Infof("worker/uniter/charm: deploying charm") 145 return os.Rename(installPath, target.Path()) 146 } 147 148 // upgrade pulls from current into target. If target has local changes, but 149 // no conflicts, it will be snapshotted before any changes are made. 150 func (d *Deployer) upgrade(target *GitDir) error { 151 log.Infof("worker/uniter/charm: preparing charm upgrade") 152 url, err := ReadCharmURL(d.current) 153 if err != nil { 154 return mask(err) 155 } 156 if err := target.Init(); err != nil { 157 return mask(err) 158 } 159 if dirty, err := target.Dirty(); err != nil { 160 return mask(err) 161 } else if dirty { 162 if conflicted, err := target.Conflicted(); err != nil { 163 return mask(err) 164 } else if !conflicted { 165 log.Infof("worker/uniter/charm: snapshotting dirty charm before upgrade") 166 if err = target.Snapshotf("Pre-upgrade snapshot."); err != nil { 167 return mask(err) 168 } 169 } 170 } 171 log.Infof("worker/uniter/charm: deploying charm") 172 if err := target.Pull(d.current); err != nil { 173 return mask(err, errors.Is(ErrConflict)) 174 } 175 return target.Snapshotf("Upgraded charm to %q.", url) 176 } 177 178 // collectOrphans deletes all repos in path except the one pointed to by current. 179 // Errors are generally ignored; some are logged. 180 func (d *Deployer) collectOrphans() { 181 current, err := os.Readlink(d.current.Path()) 182 if err != nil { 183 return 184 } 185 if !filepath.IsAbs(current) { 186 current = filepath.Join(d.path, current) 187 } 188 orphans, err := filepath.Glob(filepath.Join(d.path, fmt.Sprintf("%s*", updatePrefix))) 189 if err != nil { 190 return 191 } 192 installOrphans, err := filepath.Glob(filepath.Join(d.path, fmt.Sprintf("%s*", installPrefix))) 193 if err != nil { 194 return 195 } 196 orphans = append(orphans, installOrphans...) 197 for _, repoPath := range orphans { 198 if repoPath != d.path && repoPath != current { 199 if err = os.RemoveAll(repoPath); err != nil { 200 log.Warningf("worker/uniter/charm: failed to remove orphan repo at %s: %s", repoPath, err) 201 } 202 } 203 } 204 } 205 206 // newDir creates a new timestamped directory with the given prefix. It 207 // assumes that the deployer will not need to create more than 10 208 // directories in any given second. 209 func (d *Deployer) newDir(prefix string) (string, error) { 210 return ioutil.TempDir(d.path, prefix+time.Now().Format("20060102-150405")) 211 }