OLD | NEW |
1 package main | 1 package main |
2 | 2 |
3 import ( | 3 import ( |
4 "errors" | 4 "errors" |
5 "fmt" | 5 "fmt" |
6 "launchpad.net/gnuflag" | 6 "launchpad.net/gnuflag" |
7 "launchpad.net/juju-core/charm" | 7 "launchpad.net/juju-core/charm" |
8 "launchpad.net/juju-core/cmd" | 8 "launchpad.net/juju-core/cmd" |
9 "launchpad.net/juju-core/juju" | 9 "launchpad.net/juju-core/juju" |
10 "launchpad.net/juju-core/state" | 10 "launchpad.net/juju-core/state" |
11 "os" | 11 "os" |
12 ) | 12 ) |
13 | 13 |
14 // UpgradeCharm is responsible for upgrading a service's charm. | 14 // UpgradeCharm is responsible for upgrading a service's charm. |
15 type UpgradeCharmCommand struct { | 15 type UpgradeCharmCommand struct { |
16 EnvCommandBase | 16 EnvCommandBase |
17 ServiceName string | 17 ServiceName string |
18 Force bool | 18 Force bool |
19 RepoPath string // defaults to JUJU_REPOSITORY | 19 RepoPath string // defaults to JUJU_REPOSITORY |
| 20 SwitchURL string |
| 21 Revision int // defaults to -1 (latest) |
20 } | 22 } |
21 | 23 |
22 const upgradeCharmDoc = ` | 24 const upgradeCharmDoc = ` |
23 When no flags are set, the service's charm will be upgraded to the latest | 25 When no flags are set, the service's charm will be upgraded to the |
24 revision available in the repository from which it was originally deployed. | 26 latest revision available in the repository from which it was |
| 27 originally deployed. An explicit revision can be chosen with the |
| 28 --revision flag. |
25 | 29 |
26 If the charm came from a local repository, its path will be assumed to be | 30 If the charm came from a local repository, its path will be assumed to |
27 $JUJU_REPOSITORY unless overridden by --repository. If there is no newer | 31 be $JUJU_REPOSITORY unless overridden by --repository. If there is no |
28 revision of a local charm directory, the local directory's revision will be | 32 newer revision of a local charm directory, the local directory's |
29 automatically incremented to create a newer charm. | 33 revision will be automatically incremented to create a newer charm. |
30 | 34 |
31 The local repository behaviour is tuned specifically to the workflow of a charm | 35 The local repository behaviour is tuned specifically to the workflow |
32 author working on a single client machine; use of local repositories from | 36 of a charm author working on a single client machine; use of local |
33 multiple clients is not supported and may lead to confusing behaviour. | 37 repositories from multiple clients is not supported and may lead to |
| 38 confusing behaviour. |
34 | 39 |
35 Use of the --force flag is not generally recommended; units upgraded while in | 40 The --switch flag allows you to replace the charm with an entirely |
36 an error state will not have upgrade-charm hooks executed, and may cause | 41 different one. The new charm's URL and revision are inferred as they |
37 unexpected behavior. | 42 would be when running a deploy command. |
| 43 |
| 44 Please note that --switch is dangerous, because juju only has limited |
| 45 information with which to determine compatibility; the operation will |
| 46 succeed, regardless of potential havoc, so long as the following |
| 47 conditions hold: |
| 48 |
| 49 - The new charm must declare all relations that the service is |
| 50 currently participating in. |
| 51 - All config settings shared by the old and new charms must have the |
| 52 same types. |
| 53 |
| 54 The new charm may add new relations and configuration settings. |
| 55 |
| 56 --switch and --revision are mutually exclusive. To specify a given |
| 57 revision number with --switch, give it in the charm URL, for instance |
| 58 "cs:wordpress-5" would specify revision number 5 of the wordpress |
| 59 charm. |
| 60 |
| 61 Use of the --force flag is not generally recommended; units upgraded |
| 62 while in an error state will not have upgrade-charm hooks executed, |
| 63 and may cause unexpected behavior. |
38 ` | 64 ` |
39 | 65 |
40 func (c *UpgradeCharmCommand) Info() *cmd.Info { | 66 func (c *UpgradeCharmCommand) Info() *cmd.Info { |
41 return &cmd.Info{ | 67 return &cmd.Info{ |
42 Name: "upgrade-charm", | 68 Name: "upgrade-charm", |
43 Args: "<service>", | 69 Args: "<service>", |
44 Purpose: "upgrade a service's charm", | 70 Purpose: "upgrade a service's charm", |
45 Doc: upgradeCharmDoc, | 71 Doc: upgradeCharmDoc, |
46 } | 72 } |
47 } | 73 } |
48 | 74 |
49 func (c *UpgradeCharmCommand) SetFlags(f *gnuflag.FlagSet) { | 75 func (c *UpgradeCharmCommand) SetFlags(f *gnuflag.FlagSet) { |
50 c.EnvCommandBase.SetFlags(f) | 76 c.EnvCommandBase.SetFlags(f) |
51 f.BoolVar(&c.Force, "force", false, "upgrade all units immediately, even
if in error state") | 77 f.BoolVar(&c.Force, "force", false, "upgrade all units immediately, even
if in error state") |
52 f.StringVar(&c.RepoPath, "repository", os.Getenv("JUJU_REPOSITORY"), "lo
cal charm repository path") | 78 f.StringVar(&c.RepoPath, "repository", os.Getenv("JUJU_REPOSITORY"), "lo
cal charm repository path") |
| 79 f.StringVar(&c.SwitchURL, "switch", "", "crossgrade to a different charm
") |
| 80 f.IntVar(&c.Revision, "revision", -1, "explicit revision of current char
m") |
53 } | 81 } |
54 | 82 |
55 func (c *UpgradeCharmCommand) Init(args []string) error { | 83 func (c *UpgradeCharmCommand) Init(args []string) error { |
56 switch len(args) { | 84 switch len(args) { |
57 case 1: | 85 case 1: |
58 if !state.IsServiceName(args[0]) { | 86 if !state.IsServiceName(args[0]) { |
59 return fmt.Errorf("invalid service name %q", args[0]) | 87 return fmt.Errorf("invalid service name %q", args[0]) |
60 } | 88 } |
61 c.ServiceName = args[0] | 89 c.ServiceName = args[0] |
62 case 0: | 90 case 0: |
63 return errors.New("no service specified") | 91 return errors.New("no service specified") |
64 default: | 92 default: |
65 return cmd.CheckEmpty(args[1:]) | 93 return cmd.CheckEmpty(args[1:]) |
66 } | 94 } |
67 » // TODO(dimitern): add the other flags --switch and --revision. | 95 » if c.SwitchURL != "" && c.Revision != -1 { |
| 96 » » return fmt.Errorf("--switch and --revision are mutually exclusiv
e") |
| 97 » } |
68 return nil | 98 return nil |
69 } | 99 } |
70 | 100 |
71 // Run connects to the specified environment and starts the charm | 101 // Run connects to the specified environment and starts the charm |
72 // upgrade process. | 102 // upgrade process. |
73 func (c *UpgradeCharmCommand) Run(ctx *cmd.Context) error { | 103 func (c *UpgradeCharmCommand) Run(ctx *cmd.Context) error { |
74 conn, err := juju.NewConnFromName(c.EnvName) | 104 conn, err := juju.NewConnFromName(c.EnvName) |
75 if err != nil { | 105 if err != nil { |
76 return err | 106 return err |
77 } | 107 } |
78 defer conn.Close() | 108 defer conn.Close() |
79 service, err := conn.State.Service(c.ServiceName) | 109 service, err := conn.State.Service(c.ServiceName) |
80 if err != nil { | 110 if err != nil { |
81 return err | 111 return err |
82 } | 112 } |
83 » curl, _ := service.CharmURL() | 113 » oldURL, _ := service.CharmURL() |
84 » repo, err := charm.InferRepository(curl, ctx.AbsPath(c.RepoPath)) | 114 » var newURL *charm.URL |
| 115 » if c.SwitchURL != "" { |
| 116 » » // A new charm URL was explicitly specified. |
| 117 » » conf, err := conn.State.EnvironConfig() |
| 118 » » if err != nil { |
| 119 » » » return err |
| 120 » » } |
| 121 » » newURL, err = charm.InferURL(c.SwitchURL, conf.DefaultSeries()) |
| 122 » » if err != nil { |
| 123 » » » return err |
| 124 » » } |
| 125 » } else { |
| 126 » » // No new URL specified, but revision might have been. |
| 127 » » newURL = oldURL.WithRevision(c.Revision) |
| 128 » } |
| 129 » repo, err := charm.InferRepository(newURL, ctx.AbsPath(c.RepoPath)) |
85 if err != nil { | 130 if err != nil { |
86 return err | 131 return err |
87 } | 132 } |
88 » rev, err := repo.Latest(curl) | 133 » // If no explicit revision was set with either SwitchURL |
| 134 » // or Revision flags, discover the latest. |
| 135 » explicitRevision := true |
| 136 » if newURL.Revision == -1 { |
| 137 » » explicitRevision = false |
| 138 » » latest, err := repo.Latest(newURL) |
| 139 » » if err != nil { |
| 140 » » » return err |
| 141 » » } |
| 142 » » newURL = newURL.WithRevision(latest) |
| 143 » } |
| 144 » bumpRevision := false |
| 145 » if *newURL == *oldURL { |
| 146 » » if explicitRevision { |
| 147 » » » return fmt.Errorf("already running specified charm %q",
newURL) |
| 148 » » } |
| 149 » » // Only try bumping the revision when necessary (local dir charm
). |
| 150 » » if _, isLocal := repo.(*charm.LocalRepository); !isLocal { |
| 151 » » » // TODO(dimitern): If the --force flag is set to somethi
ng |
| 152 » » » // different to before, we might actually want to allow
this |
| 153 » » » // case (and the other error below). LP bug #1174287 |
| 154 » » » return fmt.Errorf("already running latest charm %q", new
URL) |
| 155 » » } |
| 156 » » // This is a local repository. |
| 157 » » if ch, err := repo.Get(newURL); err != nil { |
| 158 » » » return err |
| 159 » » } else if _, bumpRevision = ch.(*charm.Dir); !bumpRevision { |
| 160 » » » // Only bump the revision when it's a directory. |
| 161 » » » return fmt.Errorf("cannot increment revision of charm %q
: not a directory", newURL) |
| 162 » » } |
| 163 » } |
| 164 » sch, err := conn.PutCharm(newURL, repo, bumpRevision) |
89 if err != nil { | 165 if err != nil { |
90 return err | 166 return err |
91 } | 167 } |
92 bumpRevision := false | |
93 if curl.Revision == rev { | |
94 if _, isLocal := repo.(*charm.LocalRepository); !isLocal { | |
95 return fmt.Errorf("already running latest charm %q", cur
l) | |
96 } | |
97 // This is a local repository. | |
98 if ch, err := repo.Get(curl); err != nil { | |
99 return err | |
100 } else if _, bumpRevision = ch.(*charm.Dir); !bumpRevision { | |
101 // Only bump the revision when it's a directory. | |
102 return fmt.Errorf("already running latest charm %q", cur
l) | |
103 } | |
104 } | |
105 sch, err := conn.PutCharm(curl.WithRevision(rev), repo, bumpRevision) | |
106 if err != nil { | |
107 return err | |
108 } | |
109 return service.SetCharm(sch, c.Force) | 168 return service.SetCharm(sch, c.Force) |
110 } | 169 } |
OLD | NEW |