github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/commands/upgradecharm.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names"
    13  	"gopkg.in/juju/charm.v5"
    14  	"launchpad.net/gnuflag"
    15  
    16  	"github.com/juju/juju/cmd/envcmd"
    17  	"github.com/juju/juju/cmd/juju/block"
    18  	"github.com/juju/juju/cmd/juju/service"
    19  )
    20  
    21  // UpgradeCharm is responsible for upgrading a service's charm.
    22  type UpgradeCharmCommand struct {
    23  	envcmd.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  	f.BoolVar(&c.Force, "force", false, "upgrade all units immediately, even if in error state")
    80  	f.StringVar(&c.RepoPath, "repository", os.Getenv("JUJU_REPOSITORY"), "local charm repository path")
    81  	f.StringVar(&c.SwitchURL, "switch", "", "crossgrade to a different charm")
    82  	f.IntVar(&c.Revision, "revision", -1, "explicit revision of current charm")
    83  }
    84  
    85  func (c *UpgradeCharmCommand) Init(args []string) error {
    86  	switch len(args) {
    87  	case 1:
    88  		if !names.IsValidService(args[0]) {
    89  			return fmt.Errorf("invalid service name %q", args[0])
    90  		}
    91  		c.ServiceName = args[0]
    92  	case 0:
    93  		return fmt.Errorf("no service specified")
    94  	default:
    95  		return cmd.CheckEmpty(args[1:])
    96  	}
    97  	if c.SwitchURL != "" && c.Revision != -1 {
    98  		return fmt.Errorf("--switch and --revision are mutually exclusive")
    99  	}
   100  	return nil
   101  }
   102  
   103  // Run connects to the specified environment and starts the charm
   104  // upgrade process.
   105  func (c *UpgradeCharmCommand) Run(ctx *cmd.Context) error {
   106  	client, err := c.NewAPIClient()
   107  	if err != nil {
   108  		return err
   109  	}
   110  	defer client.Close()
   111  	oldURL, err := client.ServiceGetCharmURL(c.ServiceName)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	conf, err := service.GetClientConfig(client)
   117  	if err != nil {
   118  		return errors.Trace(err)
   119  	}
   120  
   121  	var newRef *charm.Reference
   122  	if c.SwitchURL != "" {
   123  		newRef, err = charm.ParseReference(c.SwitchURL)
   124  		if err != nil {
   125  			return err
   126  		}
   127  	} else {
   128  		// No new URL specified, but revision might have been.
   129  		newRef = oldURL.WithRevision(c.Revision).Reference()
   130  	}
   131  
   132  	csClient, err := newCharmStoreClient()
   133  	if err != nil {
   134  		return errors.Trace(err)
   135  	}
   136  	defer csClient.jar.Save()
   137  	newURL, repo, err := resolveCharmURL(newRef.String(), csClient.params, ctx.AbsPath(c.RepoPath), conf)
   138  	if err != nil {
   139  		return errors.Trace(err)
   140  	}
   141  
   142  	// If no explicit revision was set with either SwitchURL
   143  	// or Revision flags, discover the latest.
   144  	if *newURL == *oldURL {
   145  		if newRef.Revision != -1 {
   146  			return fmt.Errorf("already running specified charm %q", newURL)
   147  		}
   148  		if newURL.Schema == "cs" {
   149  			// No point in trying to upgrade a charm store charm when
   150  			// we just determined that's the latest revision
   151  			// available.
   152  			return fmt.Errorf("already running latest charm %q", newURL)
   153  		}
   154  	}
   155  
   156  	addedURL, err := addCharmViaAPI(client, ctx, newURL, repo, csClient)
   157  	if err != nil {
   158  		return block.ProcessBlockedError(err, block.BlockChange)
   159  	}
   160  
   161  	return block.ProcessBlockedError(client.ServiceSetCharm(c.ServiceName, addedURL.String(), c.Force), block.BlockChange)
   162  }