137
env, err := environs.New(cfg)
141
v, err := c.initVersions(cfg, env)
146
series := getUploadSeries(cfg, c.Series)
147
if err := v.uploadTools(env.Storage(), series); err != nil {
151
if err := v.validate(); err != nil {
154
log.Infof("upgrade version chosen: %s", v.chosen)
155
// TODO(fwereade): this list may be incomplete, pending envtools.Upload change.
156
log.Infof("available tools: %s", v.tools)
158
if err := client.SetEnvironAgentVersion(v.chosen); err != nil {
161
log.Noticef("started upgrade to %s", v.chosen)
165
// run1dot16 implements the command without access to the API. This is
166
// needed for compatibility, so 1.16 can be upgraded to newer
167
// releases. It should be removed in 1.18.
168
func (c *UpgradeJujuCommand) run1dot16() error {
169
log.Warningf("running in 1.16 compatibility mode")
170
conn, err := juju.NewConnFromName(c.EnvName)
176
if err == errUpToDate {
177
log.Noticef(err.Error())
182
// Determine the version to upgrade to, uploading tools if necessary.
184
cfg, err := conn.State.EnvironConfig()
188
v, err := c.initVersions(cfg, env)
193
series := getUploadSeries(cfg, c.Series)
194
if err := v.uploadTools(env.Storage(), series); err != nil {
198
if err := v.validate(); err != nil {
201
log.Infof("upgrade version chosen: %s", v.chosen)
202
// TODO(fwereade): this list may be incomplete, pending envtools.Upload change.
203
log.Infof("available tools: %s", v.tools)
205
if err := conn.State.SetEnvironAgentVersion(v.chosen); err != nil {
208
log.Noticef("started upgrade to %s", v.chosen)
140
context, err := c.initVersions(client, cfg)
145
series := bootstrap.SeriesToUpload(cfg, c.Series)
146
if err := context.uploadTools(series); err != nil {
150
if err := context.validate(); err != nil {
153
logger.Infof("upgrade version chosen: %s", context.chosen)
154
// TODO(fwereade): this list may be incomplete, pending envtools.Upload change.
155
logger.Infof("available tools: %s", context.tools)
157
if err := client.SetEnvironAgentVersion(context.chosen); err != nil {
160
logger.Infof("started upgrade to %s", context.chosen)
222
174
if c.Version == agent {
223
175
return nil, errUpToDate
225
client := version.Current.Number
226
// TODO use an API call rather than requiring the environment,
227
// so that we can restrict access to the provider secrets
228
// while still allowing users to upgrade.
229
available, err := envtools.FindTools(env, client.Major, -1, coretools.Filter{}, envtools.DoNotAllowRetry)
177
clientVersion := version.Current.Number
178
findResult, err := client.FindTools(clientVersion.Major, -1, "", "")
179
var availableTools coretools.List
180
if params.IsCodeNotImplemented(err) {
181
availableTools, err = findTools1dot17(cfg)
183
availableTools = findResult.List
231
if !errors.IsNotFoundError(err) {
188
err = findResult.Error
189
if findResult.Error != nil {
190
if !params.IsCodeNotFound(err) {
234
193
if !c.UploadTools {
235
// No tools found and we shouldn't upload any, so pretend
236
// there is no more recent version available.
237
if c.Version == version.Zero {
194
// No tools found and we shouldn't upload any, so if we are not asking for a
195
// major upgrade, pretend there is no more recent version available.
196
if c.Version == version.Zero && agent.Major == clientVersion.Major {
238
197
return nil, errUpToDate
243
return &upgradeVersions{
202
return &upgradeContext{
204
client: clientVersion,
206
tools: availableTools,
251
// upgradeVersions holds the version information for making upgrade decisions.
252
type upgradeVersions struct {
254
client version.Number
255
chosen version.Number
212
// findTools1dot17 allows 1.17.x versions to be upgraded.
213
func findTools1dot17(cfg *config.Config) (coretools.List, error) {
214
logger.Warningf("running find tools in 1.17 compatibility mode")
215
env, err := environs.New(cfg)
219
clientVersion := version.Current.Number
220
return envtools.FindTools(env, clientVersion.Major, -1, coretools.Filter{}, envtools.DoNotAllowRetry)
223
// upgradeContext holds the version information for making upgrade decisions.
224
type upgradeContext struct {
226
client version.Number
227
chosen version.Number
229
config *config.Config
230
apiClient *api.Client
259
233
// uploadTools compiles jujud from $GOPATH and uploads it into the supplied
276
250
// ...but there's no way we have time for that now. In the meantime,
277
251
// considering the use cases, this should work well enough; but it
278
252
// won't detect an incompatible major-version change, which is a shame.
279
if v.chosen == version.Zero {
282
v.chosen = uploadVersion(v.chosen, v.tools)
284
// TODO(fwereade): envtools.Upload should return envtools.List, and should
285
// include all the extra series we build, so we can set *that* onto
286
// v.available and maybe one day be able to check that a given upgrade
287
// won't leave out-of-date machines lying around, starved of envtools.
288
uploaded, err := sync.Upload(storage, &v.chosen, series...)
292
v.tools = coretools.List{uploaded}
253
if context.chosen == version.Zero {
254
context.chosen = context.client
256
context.chosen = uploadVersion(context.chosen, context.tools)
258
builtTools, err := sync.BuildToolsTarball(&context.chosen)
262
defer os.RemoveAll(builtTools.Dir)
264
var uploaded *coretools.Tools
265
toolsPath := path.Join(builtTools.Dir, builtTools.StorageName)
266
logger.Infof("uploading tools %v (%dkB) to Juju state server", builtTools.Version, (builtTools.Size+512)/1024)
267
uploaded, err = context.apiClient.UploadTools(toolsPath, builtTools.Version, series...)
268
if params.IsCodeNotImplemented(err) {
269
uploaded, err = context.uploadTools1dot17(builtTools, series...)
274
context.tools = coretools.List{uploaded}
278
func (context *upgradeContext) uploadTools1dot17(builtTools *sync.BuiltTools,
279
series ...string) (*coretools.Tools, error) {
281
logger.Warningf("running upload tools in 1.17 compatibility mode")
282
env, err := environs.New(context.config)
286
return sync.SyncBuiltTools(env.Storage(), builtTools, series...)
296
289
// validate chooses an upgrade version, if one has not already been chosen,
297
290
// and ensures the tools list contains no entries that do not have that version.
298
291
// If validate returns no error, the environment agent-version can be set to
299
292
// the value of the chosen field.
300
func (v *upgradeVersions) validate() (err error) {
301
if v.chosen == version.Zero {
302
// No explicitly specified version, so find the next available
303
// stable release to upgrade to, starting from the current agent
304
// version and doing major.minor+1 or +2 as needed.
305
nextStable := v.agent
307
nextStable.Minor += 1
293
func (context *upgradeContext) validate() (err error) {
294
if context.chosen == version.Zero {
295
// No explicitly specified version, so find the version to which we
296
// need to upgrade. If the CLI and agent major versions match, we find
297
// next available stable release to upgrade to by incrementing the
298
// minor version, starting from the current agent version and doing
299
// major.minor+1 or +2 as needed. If the CLI has a greater major version,
300
// we just use the CLI version as is.
301
nextVersion := context.agent
302
if nextVersion.Major == context.client.Major {
303
if context.agent.IsDev() {
304
nextVersion.Minor += 1
306
nextVersion.Minor += 2
309
nextStable.Minor += 2
309
nextVersion = context.client
312
newestNextStable, found := v.tools.NewestCompatible(nextStable)
312
newestNextStable, found := context.tools.NewestCompatible(nextVersion)
314
log.Debugf("found a more recent stable version %s", newestNextStable)
315
v.chosen = newestNextStable
314
logger.Debugf("found a more recent stable version %s", newestNextStable)
315
context.chosen = newestNextStable
317
newestCurrent, found := v.tools.NewestCompatible(v.agent)
317
newestCurrent, found := context.tools.NewestCompatible(context.agent)
319
log.Debugf("found more recent current version %s", newestCurrent)
320
v.chosen = newestCurrent
319
logger.Debugf("found more recent current version %s", newestCurrent)
320
context.chosen = newestCurrent
322
return fmt.Errorf("no more recent supported versions available")
322
if context.agent.Major != context.client.Major {
323
return fmt.Errorf("no compatible tools available")
325
return fmt.Errorf("no more recent supported versions available")
326
330
// If not completely specified already, pick a single tools version.
327
filter := coretools.Filter{Number: v.chosen, Released: !v.chosen.IsDev()}
328
if v.tools, err = v.tools.Match(filter); err != nil {
331
filter := coretools.Filter{Number: context.chosen, Released: !context.chosen.IsDev()}
332
if context.tools, err = context.tools.Match(filter); err != nil {
331
v.chosen, v.tools = v.tools.Newest()
335
context.chosen, context.tools = context.tools.Newest()
333
if v.chosen == v.agent {
337
if context.chosen == context.agent {
334
338
return errUpToDate
337
// Major version upgrade
338
if v.chosen.Major < v.agent.Major {
341
// Disallow major.minor version downgrades.
342
if context.chosen.Major < context.agent.Major ||
343
context.chosen.Major == context.agent.Major && context.chosen.Minor < context.agent.Minor {
339
344
// TODO(fwereade): I'm a bit concerned about old agent/CLI tools even
340
345
// *connecting* to environments with higher agent-versions; but ofc they
341
346
// have to connect in order to discover they shouldn't. However, once
342
347
// any of our tools detect an incompatible version, they should act to
343
348
// minimize damage: the CLI should abort politely, and the agents should
344
349
// run an Upgrader but no other tasks.
345
return fmt.Errorf("cannot change major version from %d to %d", v.agent.Major, v.chosen.Major)
346
} else if v.chosen.Major > v.agent.Major {
347
return fmt.Errorf("major version upgrades are not supported yet")
350
return fmt.Errorf("cannot change version from %s to %s", context.agent, context.chosen)
371
// run1dot16 implements the command without access to the API. This is
372
// needed for compatibility, so 1.16 can be upgraded to newer
373
// releases. It should be removed in 1.18.
374
func (c *UpgradeJujuCommand) run1dot16() error {
375
logger.Warningf("running in 1.16 compatibility mode")
376
conn, err := juju.NewConnFromName(c.EnvName)
382
if err == errUpToDate {
383
logger.Infof(err.Error())
388
// Determine the version to upgrade to, uploading tools if necessary.
390
cfg, err := conn.State.EnvironConfig()
394
context, err := c.initVersions1dot16(cfg, env)
399
series := bootstrap.SeriesToUpload(cfg, c.Series)
400
if err := context.uploadTools1dot16(env.Storage(), series); err != nil {
404
if err := context.validate(); err != nil {
407
logger.Infof("upgrade version chosen: %s", context.chosen)
408
logger.Infof("available tools: %s", context.tools)
410
if err := conn.State.SetEnvironAgentVersion(context.chosen); err != nil {
413
logger.Infof("started upgrade to %s", context.chosen)
417
func (c *UpgradeJujuCommand) initVersions1dot16(cfg *config.Config, env environs.Environ) (*upgradeContext, error) {
418
agent, ok := cfg.AgentVersion()
420
// Can't happen. In theory.
421
return nil, fmt.Errorf("incomplete environment configuration")
423
if c.Version == agent {
424
return nil, errUpToDate
426
client := version.Current.Number
427
available, err := envtools.FindTools(env, client.Major, -1, coretools.Filter{}, envtools.DoNotAllowRetry)
429
if !errors.IsNotFoundError(err) {
433
// No tools found and we shouldn't upload any, so if we are not asking for a
434
// major upgrade, pretend there is no more recent version available.
435
if c.Version == version.Zero && agent.Major == client.Major {
436
return nil, errUpToDate
441
return &upgradeContext{
449
func (context *upgradeContext) uploadTools1dot16(storage storage.Storage, series []string) error {
450
if context.chosen == version.Zero {
451
context.chosen = context.client
453
context.chosen = uploadVersion(context.chosen, context.tools)
454
uploaded, err := sync.Upload(storage, &context.chosen, series...)
458
context.tools = coretools.List{uploaded}