github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/cmd/juju/upgradecharm.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 "errors" 8 "fmt" 9 "os" 10 11 "launchpad.net/gnuflag" 12 13 "launchpad.net/juju-core/charm" 14 "launchpad.net/juju-core/cmd" 15 "launchpad.net/juju-core/environs/config" 16 "launchpad.net/juju-core/juju" 17 "launchpad.net/juju-core/names" 18 "launchpad.net/juju-core/state/api/params" 19 ) 20 21 // UpgradeCharm is responsible for upgrading a service's charm. 22 type UpgradeCharmCommand struct { 23 cmd.EnvCommandBase 24 ServiceName string 25 Force bool 26 RepoPath string // defaults to JUJU_REPOSITORY 27 SwitchURL string 28 Revision int // defaults to -1 (latest) 29 } 30 31 const upgradeCharmDoc = ` 32 When no flags are set, the service's charm will be upgraded to the latest 33 revision available in the repository from which it was originally deployed. An 34 explicit revision can be chosen with the --revision flag. 35 36 If the charm came from a local repository, its path will be assumed to be 37 $JUJU_REPOSITORY unless overridden by --repository. 38 39 The local repository behaviour is tuned specifically to the workflow of a charm 40 author working on a single client machine; use of local repositories from 41 multiple clients is not supported and may lead to confusing behaviour. Each 42 local charm gets uploaded with the revision specified in the charm, if possible, 43 otherwise it gets a unique revision (highest in state + 1). 44 45 The --switch flag allows you to replace the charm with an entirely different 46 one. The new charm's URL and revision are inferred as they would be when running 47 a deploy command. 48 49 Please note that --switch is dangerous, because juju only has limited 50 information with which to determine compatibility; the operation will succeed, 51 regardless of potential havoc, so long as the following conditions hold: 52 53 - The new charm must declare all relations that the service is currently 54 participating in. 55 - All config settings shared by the old and new charms must 56 have the same types. 57 58 The new charm may add new relations and configuration settings. 59 60 --switch and --revision are mutually exclusive. To specify a given revision 61 number with --switch, give it in the charm URL, for instance "cs:wordpress-5" 62 would specify revision number 5 of the wordpress charm. 63 64 Use of the --force flag is not generally recommended; units upgraded while in an 65 error state will not have upgrade-charm hooks executed, and may cause unexpected 66 behavior. 67 ` 68 69 func (c *UpgradeCharmCommand) Info() *cmd.Info { 70 return &cmd.Info{ 71 Name: "upgrade-charm", 72 Args: "<service>", 73 Purpose: "upgrade a service's charm", 74 Doc: upgradeCharmDoc, 75 } 76 } 77 78 func (c *UpgradeCharmCommand) SetFlags(f *gnuflag.FlagSet) { 79 c.EnvCommandBase.SetFlags(f) 80 f.BoolVar(&c.Force, "force", false, "upgrade all units immediately, even if in error state") 81 f.StringVar(&c.RepoPath, "repository", os.Getenv("JUJU_REPOSITORY"), "local charm repository path") 82 f.StringVar(&c.SwitchURL, "switch", "", "crossgrade to a different charm") 83 f.IntVar(&c.Revision, "revision", -1, "explicit revision of current charm") 84 } 85 86 func (c *UpgradeCharmCommand) Init(args []string) error { 87 switch len(args) { 88 case 1: 89 if !names.IsService(args[0]) { 90 return fmt.Errorf("invalid service name %q", args[0]) 91 } 92 c.ServiceName = args[0] 93 case 0: 94 return errors.New("no service specified") 95 default: 96 return cmd.CheckEmpty(args[1:]) 97 } 98 if c.SwitchURL != "" && c.Revision != -1 { 99 return fmt.Errorf("--switch and --revision are mutually exclusive") 100 } 101 return nil 102 } 103 104 // Run connects to the specified environment and starts the charm 105 // upgrade process. 106 func (c *UpgradeCharmCommand) Run(ctx *cmd.Context) error { 107 client, err := juju.NewAPIClientFromName(c.EnvName) 108 if err != nil { 109 return err 110 } 111 defer client.Close() 112 oldURL, err := client.ServiceGetCharmURL(c.ServiceName) 113 if params.IsCodeNotImplemented(err) { 114 logger.Infof("ServiceGetCharmURL is not implemented by the API server, switching to 1.16 compatibility mode (direct DB connection).") 115 return c.run1dot16(ctx) 116 } 117 if err != nil { 118 return err 119 } 120 121 attrs, err := client.EnvironmentGet() 122 if err != nil { 123 return err 124 } 125 conf, err := config.New(config.NoDefaults, attrs) 126 if err != nil { 127 return err 128 } 129 130 var newURL *charm.URL 131 if c.SwitchURL != "" { 132 // A new charm URL was explicitly specified. 133 newURL, err = charm.InferURL(c.SwitchURL, conf.DefaultSeries()) 134 if err != nil { 135 return err 136 } 137 } else { 138 // No new URL specified, but revision might have been. 139 newURL = oldURL.WithRevision(c.Revision) 140 } 141 repo, err := charm.InferRepository(newURL, ctx.AbsPath(c.RepoPath)) 142 if err != nil { 143 return err 144 } 145 146 repo = config.SpecializeCharmRepo(repo, conf) 147 148 // If no explicit revision was set with either SwitchURL 149 // or Revision flags, discover the latest. 150 explicitRevision := true 151 if newURL.Revision == -1 { 152 explicitRevision = false 153 latest, err := charm.Latest(repo, newURL) 154 if err != nil { 155 return err 156 } 157 newURL = newURL.WithRevision(latest) 158 } 159 if *newURL == *oldURL { 160 if explicitRevision { 161 return fmt.Errorf("already running specified charm %q", newURL) 162 } else if newURL.Schema == "cs" { 163 // No point in trying to upgrade a charm store charm when 164 // we just determined that's the latest revision 165 // available. 166 return fmt.Errorf("already running latest charm %q", newURL) 167 } 168 } 169 170 addedURL, err := addCharmViaAPI(client, ctx, newURL, repo) 171 if err != nil { 172 return err 173 } 174 175 return client.ServiceSetCharm(c.ServiceName, addedURL.String(), c.Force) 176 } 177 178 // run1dot16 perfoms the charm upgrade using a 1.16 compatible code 179 // path, with a direct state connection. Remove once the support for 180 // 1.16 is dropped. 181 func (c *UpgradeCharmCommand) run1dot16(ctx *cmd.Context) error { 182 conn, err := juju.NewConnFromName(c.EnvName) 183 if err != nil { 184 return err 185 } 186 defer conn.Close() 187 service, err := conn.State.Service(c.ServiceName) 188 if err != nil { 189 return err 190 } 191 192 conf, err := conn.State.EnvironConfig() 193 if err != nil { 194 return err 195 } 196 197 oldURL, _ := service.CharmURL() 198 var newURL *charm.URL 199 if c.SwitchURL != "" { 200 // A new charm URL was explicitly specified. 201 conf, err := conn.State.EnvironConfig() 202 if err != nil { 203 return err 204 } 205 newURL, err = charm.InferURL(c.SwitchURL, conf.DefaultSeries()) 206 if err != nil { 207 return err 208 } 209 } else { 210 // No new URL specified, but revision might have been. 211 newURL = oldURL.WithRevision(c.Revision) 212 } 213 repo, err := charm.InferRepository(newURL, ctx.AbsPath(c.RepoPath)) 214 if err != nil { 215 return err 216 } 217 218 repo = config.SpecializeCharmRepo(repo, conf) 219 220 // If no explicit revision was set with either SwitchURL 221 // or Revision flags, discover the latest. 222 explicitRevision := true 223 if newURL.Revision == -1 { 224 explicitRevision = false 225 latest, err := charm.Latest(repo, newURL) 226 if err != nil { 227 return err 228 } 229 newURL = newURL.WithRevision(latest) 230 } 231 bumpRevision := false 232 if *newURL == *oldURL { 233 if explicitRevision { 234 return fmt.Errorf("already running specified charm %q", newURL) 235 } 236 // Only try bumping the revision when necessary (local dir charm). 237 if _, isLocal := repo.(*charm.LocalRepository); !isLocal { 238 // TODO(dimitern): If the --force flag is set to something 239 // different to before, we might actually want to allow this 240 // case (and the other error below). LP bug #1174287 241 return fmt.Errorf("already running latest charm %q", newURL) 242 } 243 // This is a local repository. 244 if ch, err := repo.Get(newURL); err != nil { 245 return err 246 } else if _, bumpRevision = ch.(*charm.Dir); !bumpRevision { 247 // Only bump the revision when it's a directory. 248 return fmt.Errorf("cannot increment revision of charm %q: not a directory", newURL) 249 } 250 } 251 sch, err := conn.PutCharm(newURL, repo, bumpRevision) 252 if err != nil { 253 return err 254 } 255 return service.SetCharm(sch, c.Force) 256 }