github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/commands/upgrademodel.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"bufio"
     8  	stderrors "errors"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"path"
    13  	"strings"
    14  
    15  	"github.com/juju/cmd"
    16  	"github.com/juju/errors"
    17  	"github.com/juju/gnuflag"
    18  	"github.com/juju/os/series"
    19  	"github.com/juju/version"
    20  
    21  	"github.com/juju/juju/api/controller"
    22  	"github.com/juju/juju/api/modelconfig"
    23  	"github.com/juju/juju/apiserver/params"
    24  	jujucmd "github.com/juju/juju/cmd"
    25  	"github.com/juju/juju/cmd/juju/block"
    26  	"github.com/juju/juju/cmd/modelcmd"
    27  	"github.com/juju/juju/environs/config"
    28  	"github.com/juju/juju/environs/sync"
    29  	"github.com/juju/juju/environs/tools"
    30  	"github.com/juju/juju/jujuclient"
    31  	coretools "github.com/juju/juju/tools"
    32  	jujuversion "github.com/juju/juju/version"
    33  )
    34  
    35  var usageUpgradeJujuSummary = `
    36  Upgrades Juju on all machines in a model.`[1:]
    37  
    38  var usageUpgradeJujuDetails = `
    39  Juju provides agent software to every machine it creates. This command
    40  upgrades that software across an entire model, which is, by default, the
    41  current model.
    42  A model's agent version can be shown with `[1:] + "`juju model-config agent-\nversion`" + `.
    43  A version is denoted by: major.minor.patch
    44  The upgrade candidate will be auto-selected if '--agent-version' is not
    45  specified:
    46   - If the server major version matches the client major version, the
    47   version selected is minor+1. If such a minor version is not available then
    48   the next patch version is chosen.
    49   - If the server major version does not match the client major version,
    50   the version selected is that of the client version.
    51  If the controller is without internet access, the client must first supply
    52  the software to the controller's cache via the ` + "`juju sync-agent-binaries`" + ` command.
    53  The command will abort if an upgrade is in progress. It will also abort if
    54  a previous upgrade was not fully completed (e.g.: if one of the
    55  controllers in a high availability model failed to upgrade).
    56  When looking for an agent to upgrade to Juju will check the currently
    57  configured agent stream for that model. It's possible to overwrite this for
    58  the lifetime of this upgrade using --agent-stream
    59  If a failed upgrade has been resolved, '--reset-previous-upgrade' can be
    60  used to allow the upgrade to proceed.
    61  Backups are recommended prior to upgrading.
    62  
    63  Examples:
    64      juju upgrade-model --dry-run
    65      juju upgrade-model --agent-version 2.0.1
    66      juju upgrade-model --agent-stream proposed
    67      
    68  See also: 
    69      sync-agent-binaries`
    70  
    71  func newUpgradeJujuCommand(store jujuclient.ClientStore, minUpgradeVers map[int]version.Number, options ...modelcmd.WrapOption) cmd.Command {
    72  	if minUpgradeVers == nil {
    73  		minUpgradeVers = minMajorUpgradeVersion
    74  	}
    75  	cmd := &upgradeJujuCommand{minMajorUpgradeVersion: minUpgradeVers}
    76  	cmd.SetClientStore(store)
    77  	return modelcmd.Wrap(cmd, options...)
    78  }
    79  
    80  // upgradeJujuCommand upgrades the agents in a juju installation.
    81  type upgradeJujuCommand struct {
    82  	modelcmd.ModelCommandBase
    83  	vers          string
    84  	Version       version.Number
    85  	BuildAgent    bool
    86  	DryRun        bool
    87  	ResetPrevious bool
    88  	AssumeYes     bool
    89  	AgentStream   string
    90  
    91  	// IgnoreAgentVersions is used to allow an admin to request an agent version without waiting for all agents to be at the right
    92  	// version.
    93  	IgnoreAgentVersions bool
    94  
    95  	// minMajorUpgradeVersion maps known major numbers to
    96  	// the minimum version that can be upgraded to that
    97  	// major version.  For example, users must be running
    98  	// 1.25.4 or later in order to upgrade to 2.0.
    99  	minMajorUpgradeVersion map[int]version.Number
   100  }
   101  
   102  func (c *upgradeJujuCommand) Info() *cmd.Info {
   103  	return jujucmd.Info(&cmd.Info{
   104  		Name:    "upgrade-model",
   105  		Purpose: usageUpgradeJujuSummary,
   106  		Doc:     usageUpgradeJujuDetails,
   107  		Aliases: []string{"upgrade-juju"},
   108  	})
   109  }
   110  
   111  func (c *upgradeJujuCommand) SetFlags(f *gnuflag.FlagSet) {
   112  	c.ModelCommandBase.SetFlags(f)
   113  	f.StringVar(&c.vers, "agent-version", "", "Upgrade to specific version")
   114  	f.StringVar(&c.AgentStream, "agent-stream", "", "Check this agent stream for upgrades")
   115  	f.BoolVar(&c.BuildAgent, "build-agent", false, "Build a local version of the agent binary; for development use only")
   116  	f.BoolVar(&c.DryRun, "dry-run", false, "Don't change anything, just report what would be changed")
   117  	f.BoolVar(&c.ResetPrevious, "reset-previous-upgrade", false, "Clear the previous (incomplete) upgrade status (use with care)")
   118  	f.BoolVar(&c.AssumeYes, "y", false, "Answer 'yes' to confirmation prompts")
   119  	f.BoolVar(&c.AssumeYes, "yes", false, "")
   120  	f.BoolVar(&c.IgnoreAgentVersions, "ignore-agent-versions", false,
   121  		"Don't check if all agents have already reached the current version")
   122  }
   123  
   124  func (c *upgradeJujuCommand) Init(args []string) error {
   125  	if c.vers != "" {
   126  		vers, err := version.Parse(c.vers)
   127  		if err != nil {
   128  			return err
   129  		}
   130  		if c.BuildAgent && vers.Build != 0 {
   131  			// TODO(fwereade): when we start taking versions from actual built
   132  			// code, we should disable --agent-version when used with --build-agent.
   133  			// For now, it's the only way to experiment with version upgrade
   134  			// behaviour live, so the only restriction is that Build cannot
   135  			// be used (because its value needs to be chosen internally so as
   136  			// not to collide with existing tools).
   137  			return errors.New("cannot specify build number when building an agent")
   138  		}
   139  		c.Version = vers
   140  	}
   141  	return cmd.CheckEmpty(args)
   142  }
   143  
   144  var (
   145  	errUpToDate            = stderrors.New("no upgrades available")
   146  	downgradeErrMsg        = "cannot change version from %s to lower version %s"
   147  	minMajorUpgradeVersion = map[int]version.Number{
   148  		2: version.MustParse("1.25.4"),
   149  	}
   150  )
   151  
   152  // canUpgradeRunningVersion determines if the version of the running
   153  // environment can be upgraded using this version of the
   154  // upgrade-model command.  Only versions with a minor version
   155  // of 0 are expected to be able to upgrade environments running
   156  // the previous major version.
   157  //
   158  // This check is needed because we do not guarantee API
   159  // compatibility across major versions.  For example, a 3.3.0
   160  // version of the upgrade-model command may not know how to upgrade
   161  // an environment running juju 4.0.0.
   162  //
   163  // The exception is that a N.0.* client must be able to upgrade
   164  // an environment one major version prior (N-1.*.*) so that
   165  // it can be used to upgrade the environment to N.0.*.  For
   166  // example, the 2.0.1 upgrade-model command must be able to upgrade
   167  // environments running 1.* since it must be able to upgrade
   168  // environments from 1.25.4 -> 2.0.*.
   169  func canUpgradeRunningVersion(runningAgentVer version.Number) bool {
   170  	if runningAgentVer.Major == jujuversion.Current.Major {
   171  		return true
   172  	}
   173  	if jujuversion.Current.Minor == 0 && runningAgentVer.Major == (jujuversion.Current.Major-1) {
   174  		return true
   175  	}
   176  	return false
   177  }
   178  
   179  func formatTools(tools coretools.List) string {
   180  	formatted := make([]string, len(tools))
   181  	for i, tools := range tools {
   182  		formatted[i] = fmt.Sprintf("    %s", tools.Version.String())
   183  	}
   184  	return strings.Join(formatted, "\n")
   185  }
   186  
   187  type upgradeJujuAPI interface {
   188  	FindTools(majorVersion, minorVersion int, series, arch, agentStream string) (result params.FindToolsResult, err error)
   189  	UploadTools(r io.ReadSeeker, vers version.Binary, additionalSeries ...string) (coretools.List, error)
   190  	AbortCurrentUpgrade() error
   191  	SetModelAgentVersion(version version.Number, ignoreAgentVersion bool) error
   192  	Close() error
   193  }
   194  
   195  type modelConfigAPI interface {
   196  	ModelGet() (map[string]interface{}, error)
   197  	Close() error
   198  }
   199  
   200  type controllerAPI interface {
   201  	ModelConfig() (map[string]interface{}, error)
   202  	Close() error
   203  }
   204  
   205  var getUpgradeJujuAPI = func(c *upgradeJujuCommand) (upgradeJujuAPI, error) {
   206  	return c.NewAPIClient()
   207  }
   208  
   209  var getModelConfigAPI = func(c *upgradeJujuCommand) (modelConfigAPI, error) {
   210  	api, err := c.NewAPIRoot()
   211  	if err != nil {
   212  		return nil, errors.Trace(err)
   213  	}
   214  	return modelconfig.NewClient(api), nil
   215  }
   216  
   217  var getControllerAPI = func(c *upgradeJujuCommand) (controllerAPI, error) {
   218  	api, err := c.NewControllerAPIRoot()
   219  	if err != nil {
   220  		return nil, errors.Trace(err)
   221  	}
   222  	return controller.NewClient(api), nil
   223  }
   224  
   225  // Run changes the version proposed for the juju envtools.
   226  func (c *upgradeJujuCommand) Run(ctx *cmd.Context) (err error) {
   227  
   228  	client, err := getUpgradeJujuAPI(c)
   229  	if err != nil {
   230  		return err
   231  	}
   232  	defer client.Close()
   233  	modelConfigClient, err := getModelConfigAPI(c)
   234  	if err != nil {
   235  		return err
   236  	}
   237  	defer modelConfigClient.Close()
   238  	controllerClient, err := getControllerAPI(c)
   239  	if err != nil {
   240  		return err
   241  	}
   242  	defer controllerClient.Close()
   243  	defer func() {
   244  		if err == errUpToDate {
   245  			ctx.Infof(err.Error())
   246  			err = nil
   247  		}
   248  	}()
   249  
   250  	// Determine the version to upgrade to, uploading tools if necessary.
   251  	attrs, err := modelConfigClient.ModelGet()
   252  	if err != nil {
   253  		return err
   254  	}
   255  	cfg, err := config.New(config.NoDefaults, attrs)
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	controllerModelConfig, err := controllerClient.ModelConfig()
   261  	if err != nil {
   262  		return err
   263  	}
   264  	isControllerModel := cfg.UUID() == controllerModelConfig[config.UUIDKey]
   265  	if c.BuildAgent && !isControllerModel {
   266  		// For UploadTools, model must be the "controller" model,
   267  		// that is, modelUUID == controllerUUID
   268  		return errors.Errorf("--build-agent can only be used with the controller model")
   269  	}
   270  
   271  	agentVersion, ok := cfg.AgentVersion()
   272  	if !ok {
   273  		// Can't happen. In theory.
   274  		return errors.New("incomplete model configuration")
   275  	}
   276  
   277  	if c.BuildAgent && c.Version == version.Zero {
   278  		// Currently, uploading tools assumes the version to be
   279  		// the same as jujuversion.Current if not specified with
   280  		// --agent-version.
   281  		c.Version = jujuversion.Current
   282  	}
   283  	warnCompat := false
   284  
   285  	// TODO (agprado:01/30/2018):
   286  	// This logic seems to be overly complicated and it checks the same condition multiple times.
   287  	switch {
   288  	case !canUpgradeRunningVersion(agentVersion):
   289  		// This version of upgrade-model cannot upgrade the running
   290  		// environment version (can't guarantee API compatibility).
   291  		return errors.Errorf("cannot upgrade a %s model with a %s client",
   292  			agentVersion, jujuversion.Current)
   293  	case c.Version != version.Zero && compareNoBuild(agentVersion, c.Version) == 1:
   294  		// The specified version would downgrade the environment.
   295  		// Don't upgrade and return an error.
   296  		return errors.Errorf(downgradeErrMsg, agentVersion, c.Version)
   297  	case agentVersion.Major != jujuversion.Current.Major:
   298  		// Running environment is the previous major version (a higher major
   299  		// version wouldn't have passed the check in canUpgradeRunningVersion).
   300  		if c.Version == version.Zero || c.Version.Major == agentVersion.Major {
   301  			// Not requesting an upgrade across major release boundary.
   302  			// Warn of incompatible CLI and filter on the prior major version
   303  			// when searching for available tools.
   304  			// TODO(cherylj) Add in a suggestion to upgrade to 2.0 if
   305  			// no matching tools are found (bug 1532670)
   306  			warnCompat = true
   307  			break
   308  		}
   309  		// User requested an upgrade to the next major version.
   310  		// Fallthrough to the next case to verify that the upgrade
   311  		// conditions are met.
   312  		fallthrough
   313  	case c.Version.Major > agentVersion.Major:
   314  		// User is requesting an upgrade to a new major number
   315  		// Only upgrade to a different major number if:
   316  		// 1 - Explicitly requested with --agent-version or using --build-agent, and
   317  		// 2 - The environment is running a valid version to upgrade from, and
   318  		// 3 - The upgrade is to a minor version of 0.
   319  		minVer, ok := c.minMajorUpgradeVersion[c.Version.Major]
   320  		if !ok {
   321  			return errors.Errorf("unknown version %q", c.Version)
   322  		}
   323  		retErr := false
   324  		if c.Version.Minor != 0 {
   325  			ctx.Infof("upgrades to %s must first go through juju %d.0",
   326  				c.Version, c.Version.Major)
   327  			retErr = true
   328  		}
   329  		if comp := agentVersion.Compare(minVer); comp < 0 {
   330  			ctx.Infof("upgrades to a new major version must first go through %s",
   331  				minVer)
   332  			retErr = true
   333  		}
   334  		if retErr {
   335  			return errors.New("unable to upgrade to requested version")
   336  		}
   337  	}
   338  
   339  	context, tryImplicit, err := c.initVersions(client, cfg, agentVersion, warnCompat)
   340  	if err != nil {
   341  		return err
   342  	}
   343  
   344  	// Look for any packaged binaries but only if we haven't been asked to build an agent.
   345  	var packagedAgentErr error
   346  	if !c.BuildAgent {
   347  		if packagedAgentErr = context.maybeChoosePackagedAgent(); packagedAgentErr != nil {
   348  			ctx.Verbosef("%v", packagedAgentErr)
   349  		}
   350  	}
   351  
   352  	// If there's no packaged binaries, or we're running a custom build
   353  	// or the user has asked for a new agent to be built, upload a local
   354  	// jujud binary if possible.
   355  	uploadLocalBinary := isControllerModel && packagedAgentErr != nil && tryImplicit
   356  	if !warnCompat && (uploadLocalBinary || c.BuildAgent) {
   357  		if err := context.uploadTools(c.BuildAgent, agentVersion, c.DryRun); err != nil {
   358  			return block.ProcessBlockedError(err, block.BlockChange)
   359  		}
   360  		builtMsg := ""
   361  		if c.BuildAgent {
   362  			builtMsg = " (built from source)"
   363  		}
   364  		fmt.Fprintf(ctx.Stdout, "no prepackaged agent binaries available, using local agent binary %v%s\n", context.chosen, builtMsg)
   365  		packagedAgentErr = nil
   366  	}
   367  	if packagedAgentErr != nil {
   368  		return packagedAgentErr
   369  	}
   370  
   371  	if err := context.validate(); err != nil {
   372  		return err
   373  	}
   374  	ctx.Verbosef("available agent binaries:\n%s", formatTools(context.tools))
   375  	fmt.Fprintf(ctx.Stderr, "best version:\n    %v\n", context.chosen)
   376  	if warnCompat {
   377  		fmt.Fprintf(ctx.Stderr, "version %s incompatible with this client (%s)\n", context.chosen, jujuversion.Current)
   378  	}
   379  	if c.DryRun {
   380  		if c.BuildAgent {
   381  			fmt.Fprint(ctx.Stderr, "upgrade to this version by running\n    juju upgrade-model --build-agent\n")
   382  		} else {
   383  			fmt.Fprintf(ctx.Stderr, "upgrade to this version by running\n    juju upgrade-model\n")
   384  		}
   385  	} else {
   386  		if c.ResetPrevious {
   387  			if ok, err := c.confirmResetPreviousUpgrade(ctx); !ok || err != nil {
   388  				const message = "previous upgrade not reset and no new upgrade triggered"
   389  				if err != nil {
   390  					return errors.Annotate(err, message)
   391  				}
   392  				return errors.New(message)
   393  			}
   394  			if err := client.AbortCurrentUpgrade(); err != nil {
   395  				return block.ProcessBlockedError(err, block.BlockChange)
   396  			}
   397  		}
   398  		if err := client.SetModelAgentVersion(context.chosen, c.IgnoreAgentVersions); err != nil {
   399  			if params.IsCodeUpgradeInProgress(err) {
   400  				return errors.Errorf("%s\n\n"+
   401  					"Please wait for the upgrade to complete or if there was a problem with\n"+
   402  					"the last upgrade that has been resolved, consider running the\n"+
   403  					"upgrade-model command with the --reset-previous-upgrade option.", err,
   404  				)
   405  			} else {
   406  				return block.ProcessBlockedError(err, block.BlockChange)
   407  			}
   408  		}
   409  		fmt.Fprintf(ctx.Stdout, "started upgrade to %s\n", context.chosen)
   410  	}
   411  	return nil
   412  }
   413  
   414  func tryImplicitUpload(agentVersion version.Number) (bool, error) {
   415  	newerAgent := jujuversion.Current.Compare(agentVersion) > 0
   416  	if newerAgent || agentVersion.Build > 0 || jujuversion.Current.Build > 0 {
   417  		return true, nil
   418  	}
   419  	jujudPath, err := tools.ExistingJujudLocation()
   420  	if err != nil {
   421  		return false, errors.Trace(err)
   422  	}
   423  	_, official, err := tools.JujudVersion(jujudPath)
   424  	// If there's an error getting jujud version, play it safe
   425  	// and don't implicitly do an implicit upload.
   426  	if err != nil {
   427  		return false, nil
   428  	}
   429  	return !official, nil
   430  }
   431  
   432  const resetPreviousUpgradeMessage = `
   433  WARNING! using --reset-previous-upgrade when an upgrade is in progress
   434  will cause the upgrade to fail. Only use this option to clear an
   435  incomplete upgrade where the root cause has been resolved.
   436  
   437  Continue [y/N]? `
   438  
   439  func (c *upgradeJujuCommand) confirmResetPreviousUpgrade(ctx *cmd.Context) (bool, error) {
   440  	if c.AssumeYes {
   441  		return true, nil
   442  	}
   443  	fmt.Fprint(ctx.Stdout, resetPreviousUpgradeMessage)
   444  	scanner := bufio.NewScanner(ctx.Stdin)
   445  	scanner.Scan()
   446  	err := scanner.Err()
   447  	if err != nil && err != io.EOF {
   448  		return false, err
   449  	}
   450  	answer := strings.ToLower(scanner.Text())
   451  	return answer == "y" || answer == "yes", nil
   452  }
   453  
   454  // initVersions collects state relevant to an upgrade decision. The returned
   455  // agent and client versions, and the list of currently available tools, will
   456  // always be accurate; the chosen version, and the flag indicating development
   457  // mode, may remain blank until uploadTools or validate is called.
   458  func (c *upgradeJujuCommand) initVersions(
   459  	client upgradeJujuAPI, cfg *config.Config, agentVersion version.Number, filterOnPrior bool,
   460  ) (*upgradeContext, bool, error) {
   461  	if c.Version == agentVersion {
   462  		return nil, false, errUpToDate
   463  	}
   464  	filterVersion := jujuversion.Current
   465  	if c.Version != version.Zero {
   466  		filterVersion = c.Version
   467  	} else if filterOnPrior {
   468  		// Trying to find the latest of the prior major version.
   469  		// TODO (cherylj) if no tools found, suggest upgrade to
   470  		// the current client version.
   471  		filterVersion.Major--
   472  	}
   473  	tryImplicitUpload, err := tryImplicitUpload(agentVersion)
   474  	if err != nil {
   475  		return nil, false, err
   476  	}
   477  	logger.Debugf("searching for agent binaries with major: %d", filterVersion.Major)
   478  	findResult, err := client.FindTools(filterVersion.Major, -1, "", "", c.AgentStream)
   479  	if err != nil {
   480  		return nil, false, err
   481  	}
   482  	err = findResult.Error
   483  	if findResult.Error != nil {
   484  		if !params.IsCodeNotFound(err) {
   485  			return nil, false, err
   486  		}
   487  		if !tryImplicitUpload && !c.BuildAgent {
   488  			// No tools found and we shouldn't upload any, so if we are not asking for a
   489  			// major upgrade, pretend there is no more recent version available.
   490  			if c.Version == version.Zero && agentVersion.Major == filterVersion.Major {
   491  				return nil, false, errUpToDate
   492  			}
   493  			return nil, tryImplicitUpload, err
   494  		}
   495  	}
   496  	return &upgradeContext{
   497  		agent:     agentVersion,
   498  		client:    jujuversion.Current,
   499  		chosen:    c.Version,
   500  		tools:     findResult.List,
   501  		apiClient: client,
   502  		config:    cfg,
   503  	}, tryImplicitUpload, nil
   504  }
   505  
   506  // upgradeContext holds the version information for making upgrade decisions.
   507  type upgradeContext struct {
   508  	agent     version.Number
   509  	client    version.Number
   510  	chosen    version.Number
   511  	tools     coretools.List
   512  	config    *config.Config
   513  	apiClient upgradeJujuAPI
   514  }
   515  
   516  // uploadTools compiles jujud from $GOPATH and uploads it into the supplied
   517  // storage. If no version has been explicitly chosen, the version number
   518  // reported by the built tools will be based on the client version number.
   519  // In any case, the version number reported will have a build component higher
   520  // than that of any otherwise-matching available envtools.
   521  // uploadTools resets the chosen version and replaces the available tools
   522  // with the ones just uploaded.
   523  func (context *upgradeContext) uploadTools(buildAgent bool, agentVersion version.Number, dryRun bool) (err error) {
   524  	// TODO(fwereade): this is kinda crack: we should not assume that
   525  	// jujuversion.Current matches whatever source happens to be built. The
   526  	// ideal would be:
   527  	//  1) compile jujud from $GOPATH into some build dir
   528  	//  2) get actual version with `jujud version`
   529  	//  3) check actual version for compatibility with CLI tools
   530  	//  4) generate unique build version with reference to available tools
   531  	//  5) force-version that unique version into the dir directly
   532  	//  6) archive and upload the build dir
   533  	// ...but there's no way we have time for that now. In the meantime,
   534  	// considering the use cases, this should work well enough; but it
   535  	// won't detect an incompatible major-version change, which is a shame.
   536  	//
   537  	// TODO(cherylj) If the determination of version changes, we will
   538  	// need to also change the upgrade version checks in Run() that check
   539  	// if a major upgrade is allowed.
   540  	uploadBaseVersion := context.chosen
   541  	if uploadBaseVersion == version.Zero {
   542  		uploadBaseVersion = context.client
   543  	}
   544  	// If the Juju client matches the current running agent (excluding build number),
   545  	// make sure the build number gets incremented.
   546  	agentVersionCopy := agentVersion
   547  	agentVersionCopy.Build = 0
   548  	uploadBaseVersionCopy := uploadBaseVersion
   549  	uploadBaseVersion.Build = 0
   550  	if agentVersionCopy.Compare(uploadBaseVersionCopy) == 0 {
   551  		uploadBaseVersion = agentVersion
   552  	}
   553  	context.chosen = makeUploadVersion(uploadBaseVersion, context.tools)
   554  
   555  	if dryRun {
   556  		return nil
   557  	}
   558  
   559  	builtTools, err := sync.BuildAgentTarball(buildAgent, &context.chosen, "upgrade")
   560  	if err != nil {
   561  		return errors.Trace(err)
   562  	}
   563  	defer os.RemoveAll(builtTools.Dir)
   564  
   565  	uploadToolsVersion := builtTools.Version
   566  	if builtTools.Official {
   567  		context.chosen = builtTools.Version.Number
   568  	} else {
   569  		uploadToolsVersion.Number = context.chosen
   570  	}
   571  	toolsPath := path.Join(builtTools.Dir, builtTools.StorageName)
   572  	logger.Infof("uploading agent binary %v (%dkB) to Juju controller", uploadToolsVersion, (builtTools.Size+512)/1024)
   573  	f, err := os.Open(toolsPath)
   574  	if err != nil {
   575  		return errors.Trace(err)
   576  	}
   577  	defer f.Close()
   578  	os, err := series.GetOSFromSeries(builtTools.Version.Series)
   579  	if err != nil {
   580  		return errors.Trace(err)
   581  	}
   582  	additionalSeries := series.OSSupportedSeries(os)
   583  	uploaded, err := context.apiClient.UploadTools(f, uploadToolsVersion, additionalSeries...)
   584  	if err != nil {
   585  		return errors.Trace(err)
   586  	}
   587  	context.tools = uploaded
   588  	return nil
   589  }
   590  
   591  func (context *upgradeContext) maybeChoosePackagedAgent() (err error) {
   592  	if context.chosen == version.Zero {
   593  		// No explicitly specified version, so find the version to which we
   594  		// need to upgrade. We find next available stable release to upgrade
   595  		// to by incrementing the minor version, starting from the current
   596  		// agent version and doing major.minor+1.patch=0.
   597  
   598  		// Upgrading across a major release boundary requires that the version
   599  		// be specified with --agent-version.
   600  		nextVersion := context.agent
   601  		nextVersion.Minor += 1
   602  		nextVersion.Patch = 0
   603  		// Set Tag to space so it will be considered lexicographically earlier
   604  		// than any tagged version.
   605  		nextVersion.Tag = " "
   606  
   607  		newestNextStable, found := context.tools.NewestCompatible(nextVersion)
   608  		if found {
   609  			logger.Debugf("found a more recent stable version %s", newestNextStable)
   610  			context.chosen = newestNextStable
   611  		} else {
   612  			newestCurrent, found := context.tools.NewestCompatible(context.agent)
   613  			if found {
   614  				logger.Debugf("found more recent current version %s", newestCurrent)
   615  				context.chosen = newestCurrent
   616  			} else {
   617  				if context.agent.Major != context.client.Major {
   618  					return errors.New("no compatible agent binaries available")
   619  				} else {
   620  					return errors.New("no more recent supported versions available")
   621  				}
   622  			}
   623  		}
   624  	} else {
   625  		// If not completely specified already, pick a single tools version.
   626  		filter := coretools.Filter{Number: context.chosen}
   627  		if context.tools, err = context.tools.Match(filter); err != nil {
   628  			return err
   629  		}
   630  		context.chosen, context.tools = context.tools.Newest()
   631  	}
   632  	return nil
   633  }
   634  
   635  // validate ensures an upgrade can be done using the chosen agent version.
   636  // If validate returns no error, the environment agent-version can be set to
   637  // the value of the chosen agent field.
   638  func (context *upgradeContext) validate() (err error) {
   639  	if context.chosen == context.agent {
   640  		return errUpToDate
   641  	}
   642  
   643  	// Disallow major.minor version downgrades.
   644  	if context.chosen.Major < context.agent.Major ||
   645  		context.chosen.Major == context.agent.Major && context.chosen.Minor < context.agent.Minor {
   646  		// TODO(fwereade): I'm a bit concerned about old agent/CLI tools even
   647  		// *connecting* to environments with higher agent-versions; but ofc they
   648  		// have to connect in order to discover they shouldn't. However, once
   649  		// any of our tools detect an incompatible version, they should act to
   650  		// minimize damage: the CLI should abort politely, and the agents should
   651  		// run an Upgrader but no other tasks.
   652  		return errors.Errorf(downgradeErrMsg, context.agent, context.chosen)
   653  	}
   654  
   655  	return nil
   656  }
   657  
   658  // makeUploadVersion returns a copy of the supplied version with a build number
   659  // higher than any of the supplied tools that share its major, minor and patch.
   660  func makeUploadVersion(vers version.Number, existing coretools.List) version.Number {
   661  	vers.Build++
   662  	for _, t := range existing {
   663  		if t.Version.Major != vers.Major || t.Version.Minor != vers.Minor || t.Version.Patch != vers.Patch {
   664  			continue
   665  		}
   666  		if t.Version.Build >= vers.Build {
   667  			vers.Build = t.Version.Build + 1
   668  		}
   669  	}
   670  	return vers
   671  }
   672  
   673  func compareNoBuild(a, b version.Number) int {
   674  	a.Build = 0
   675  	b.Build = 0
   676  	return a.Compare(b)
   677  }