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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package application
     5  
     6  import (
     7  	"gopkg.in/juju/charm.v6"
     8  
     9  	"github.com/juju/juju/juju/version"
    10  )
    11  
    12  const (
    13  	msgUserRequestedSeries = "with the user specified series %q"
    14  	msgBundleSeries        = "with the series %q defined by the bundle"
    15  	msgDefaultCharmSeries  = "with the default charm metadata series %q"
    16  	msgDefaultModelSeries  = "with the configured model default series %q"
    17  	msgLatestLTSSeries     = "with the latest LTS series %q"
    18  )
    19  
    20  type modelConfig interface {
    21  	DefaultSeries() (string, bool)
    22  }
    23  
    24  // seriesSelector is a helper type that determines what series the charm should
    25  // be deployed to.
    26  //
    27  // TODO: This type should really have a Validate method, as the force flag is
    28  // really only valid if the seriesFlag is specified. There is code and tests
    29  // that allow the force flag when series isn't specified, but they should
    30  // really be cleaned up. The `deploy` CLI command has tests to ensure that
    31  // --force is only valid with --series.
    32  type seriesSelector struct {
    33  	// seriesFlag is the series passed to the --series flag on the command line.
    34  	seriesFlag string
    35  	// charmURLSeries is the series specified as part of the charm URL, i.e.
    36  	// cs:trusty/ubuntu.
    37  	charmURLSeries string
    38  	// conf is the configuration for the model we're deploying to.
    39  	conf modelConfig
    40  	// supportedSeries is the list of series the charm supports.
    41  	supportedSeries []string
    42  	// force indicates the user explicitly wants to deploy to a requested
    43  	// series, regardless of whether the charm says it supports that series.
    44  	force bool
    45  	// from bundle specifies the deploy request comes from a bundle spec.
    46  	fromBundle bool
    47  }
    48  
    49  // charmSeries determines what series to use with a charm.
    50  // Order of preference is:
    51  // - user requested with --series or defined by bundle when deploying
    52  // - user requested in charm's url (e.g. juju deploy precise/ubuntu)
    53  // - model default (if it matches supported series)
    54  // - default from charm metadata supported series / series in url
    55  // - default LTS
    56  func (s seriesSelector) charmSeries() (selectedSeries string, err error) {
    57  	// User has requested a series with --series.
    58  	if s.seriesFlag != "" {
    59  		return s.userRequested(s.seriesFlag)
    60  	}
    61  
    62  	// User specified a series in the charm URL, e.g.
    63  	// juju deploy precise/ubuntu.
    64  	if s.charmURLSeries != "" {
    65  		return s.userRequested(s.charmURLSeries)
    66  	}
    67  
    68  	// No series explicitly requested by the user.
    69  	// Use model default series, if explicitly set and supported by the charm.
    70  	if defaultSeries, explicit := s.conf.DefaultSeries(); explicit {
    71  		if _, err := charm.SeriesForCharm(defaultSeries, s.supportedSeries); err == nil {
    72  			logger.Infof(msgDefaultModelSeries, defaultSeries)
    73  			return defaultSeries, nil
    74  		}
    75  	}
    76  
    77  	// Use the charm's perferred series, if it has one.  In a multi-series
    78  	// charm, the first series in the list is the preferred one.
    79  	defaultSeries, err := charm.SeriesForCharm("", s.supportedSeries)
    80  	if err == nil {
    81  		return defaultSeries, nil
    82  	}
    83  
    84  	// Charm hasn't specified a default (likely due to being a local charm
    85  	// deployed by path).  Last chance, best we can do is default to LTS.
    86  
    87  	// At this point, because we have no idea what series the charm supports,
    88  	// *everything* requires --force.
    89  	if !s.force {
    90  		// We know err is not nil due to above, so return the error
    91  		// returned to us from the charm call.
    92  		return "", err
    93  	}
    94  
    95  	latestLTS := version.SupportedLTS()
    96  	logger.Infof(msgLatestLTSSeries, latestLTS)
    97  	return latestLTS, nil
    98  }
    99  
   100  // userRequested checks the series the user has requested, and returns it if it
   101  // is supported, or if they used --force.
   102  func (s seriesSelector) userRequested(requestedSeries string) (string, error) {
   103  	series, err := charm.SeriesForCharm(requestedSeries, s.supportedSeries)
   104  	if s.force {
   105  		series = requestedSeries
   106  	} else if err != nil {
   107  		return "", err
   108  	}
   109  
   110  	// either it's a supported series or the user used --force, so just
   111  	// give them what they asked for.
   112  	if s.fromBundle {
   113  		logger.Infof(msgBundleSeries, series)
   114  		return series, nil
   115  	}
   116  	logger.Infof(msgUserRequestedSeries, series)
   117  	return series, nil
   118  }