github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	"fmt"
     8  	"os"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/names"
    12  	"gopkg.in/juju/charm.v4"
    13  	"launchpad.net/gnuflag"
    14  
    15  	"github.com/juju/juju/cmd/envcmd"
    16  	"github.com/juju/juju/cmd/juju/block"
    17  	"github.com/juju/juju/environs/config"
    18  )
    19  
    20  // UpgradeCharm is responsible for upgrading a service's charm.
    21  type UpgradeCharmCommand struct {
    22  	envcmd.EnvCommandBase
    23  	ServiceName string
    24  	Force       bool
    25  	RepoPath    string // defaults to JUJU_REPOSITORY
    26  	SwitchURL   string
    27  	Revision    int // defaults to -1 (latest)
    28  }
    29  
    30  const upgradeCharmDoc = `
    31  When no flags are set, the service's charm will be upgraded to the latest
    32  revision available in the repository from which it was originally deployed. An
    33  explicit revision can be chosen with the --revision flag.
    34  
    35  If the charm came from a local repository, its path will be assumed to be
    36  $JUJU_REPOSITORY unless overridden by --repository.
    37  
    38  The local repository behaviour is tuned specifically to the workflow of a charm
    39  author working on a single client machine; use of local repositories from
    40  multiple clients is not supported and may lead to confusing behaviour. Each
    41  local charm gets uploaded with the revision specified in the charm, if possible,
    42  otherwise it gets a unique revision (highest in state + 1).
    43  
    44  The --switch flag allows you to replace the charm with an entirely different
    45  one. The new charm's URL and revision are inferred as they would be when running
    46  a deploy command.
    47  
    48  Please note that --switch is dangerous, because juju only has limited
    49  information with which to determine compatibility; the operation will succeed,
    50  regardless of potential havoc, so long as the following conditions hold:
    51  
    52  - The new charm must declare all relations that the service is currently
    53  participating in.
    54  - All config settings shared by the old and new charms must
    55  have the same types.
    56  
    57  The new charm may add new relations and configuration settings.
    58  
    59  --switch and --revision are mutually exclusive. To specify a given revision
    60  number with --switch, give it in the charm URL, for instance "cs:wordpress-5"
    61  would specify revision number 5 of the wordpress charm.
    62  
    63  Use of the --force flag is not generally recommended; units upgraded while in an
    64  error state will not have upgrade-charm hooks executed, and may cause unexpected
    65  behavior.
    66  `
    67  
    68  func (c *UpgradeCharmCommand) Info() *cmd.Info {
    69  	return &cmd.Info{
    70  		Name:    "upgrade-charm",
    71  		Args:    "<service>",
    72  		Purpose: "upgrade a service's charm",
    73  		Doc:     upgradeCharmDoc,
    74  	}
    75  }
    76  
    77  func (c *UpgradeCharmCommand) SetFlags(f *gnuflag.FlagSet) {
    78  	f.BoolVar(&c.Force, "force", false, "upgrade all units immediately, even if in error state")
    79  	f.StringVar(&c.RepoPath, "repository", os.Getenv("JUJU_REPOSITORY"), "local charm repository path")
    80  	f.StringVar(&c.SwitchURL, "switch", "", "crossgrade to a different charm")
    81  	f.IntVar(&c.Revision, "revision", -1, "explicit revision of current charm")
    82  }
    83  
    84  func (c *UpgradeCharmCommand) Init(args []string) error {
    85  	switch len(args) {
    86  	case 1:
    87  		if !names.IsValidService(args[0]) {
    88  			return fmt.Errorf("invalid service name %q", args[0])
    89  		}
    90  		c.ServiceName = args[0]
    91  	case 0:
    92  		return fmt.Errorf("no service specified")
    93  	default:
    94  		return cmd.CheckEmpty(args[1:])
    95  	}
    96  	if c.SwitchURL != "" && c.Revision != -1 {
    97  		return fmt.Errorf("--switch and --revision are mutually exclusive")
    98  	}
    99  	return nil
   100  }
   101  
   102  // Run connects to the specified environment and starts the charm
   103  // upgrade process.
   104  func (c *UpgradeCharmCommand) Run(ctx *cmd.Context) error {
   105  	client, err := c.NewAPIClient()
   106  	if err != nil {
   107  		return err
   108  	}
   109  	defer client.Close()
   110  	oldURL, err := client.ServiceGetCharmURL(c.ServiceName)
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	attrs, err := client.EnvironmentGet()
   116  	if err != nil {
   117  		return err
   118  	}
   119  	conf, err := config.New(config.NoDefaults, attrs)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	var newURL *charm.URL
   125  	if c.SwitchURL != "" {
   126  		newURL, err = resolveCharmURL(c.SwitchURL, client, conf)
   127  		if err != nil {
   128  			return err
   129  		}
   130  	} else {
   131  		// No new URL specified, but revision might have been.
   132  		newURL = oldURL.WithRevision(c.Revision)
   133  	}
   134  
   135  	repo, err := charm.InferRepository(newURL.Reference(), ctx.AbsPath(c.RepoPath))
   136  	if err != nil {
   137  		return err
   138  	}
   139  	config.SpecializeCharmRepo(repo, conf)
   140  
   141  	// If no explicit revision was set with either SwitchURL
   142  	// or Revision flags, discover the latest.
   143  	explicitRevision := true
   144  	if newURL.Revision == -1 {
   145  		explicitRevision = false
   146  		latest, err := charm.Latest(repo, newURL)
   147  		if err != nil {
   148  			return err
   149  		}
   150  		newURL = newURL.WithRevision(latest)
   151  	}
   152  	if *newURL == *oldURL {
   153  		if explicitRevision {
   154  			return fmt.Errorf("already running specified charm %q", newURL)
   155  		} else if newURL.Schema == "cs" {
   156  			// No point in trying to upgrade a charm store charm when
   157  			// we just determined that's the latest revision
   158  			// available.
   159  			return fmt.Errorf("already running latest charm %q", newURL)
   160  		}
   161  	}
   162  
   163  	addedURL, err := addCharmViaAPI(client, ctx, newURL, repo)
   164  	if err != nil {
   165  		return block.ProcessBlockedError(err, block.BlockChange)
   166  	}
   167  
   168  	return block.ProcessBlockedError(client.ServiceSetCharm(c.ServiceName, addedURL.String(), c.Force), block.BlockChange)
   169  }