github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/environs/tools/tools.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package tools
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/utils/arch"
    12  	"github.com/juju/utils/series"
    13  	"github.com/juju/version"
    14  
    15  	"github.com/juju/juju/environs"
    16  	"github.com/juju/juju/environs/simplestreams"
    17  	coretools "github.com/juju/juju/tools"
    18  	jujuversion "github.com/juju/juju/version"
    19  )
    20  
    21  var logger = loggo.GetLogger("juju.environs.tools")
    22  
    23  func makeToolsConstraint(cloudSpec simplestreams.CloudSpec, stream string, majorVersion, minorVersion int,
    24  	filter coretools.Filter) (*ToolsConstraint, error) {
    25  
    26  	var toolsConstraint *ToolsConstraint
    27  	if filter.Number != version.Zero {
    28  		// A specific tools version is required, however, a general match based on major/minor
    29  		// version may also have been requested. This is used to ensure any agent version currently
    30  		// recorded in the environment matches the Juju cli version.
    31  		// We can short circuit any lookup here by checking the major/minor numbers against
    32  		// the filter version and exiting early if there is a mismatch.
    33  		majorMismatch := majorVersion > 0 && majorVersion != filter.Number.Major
    34  		minorMismacth := minorVersion != -1 && minorVersion != filter.Number.Minor
    35  		if majorMismatch || minorMismacth {
    36  			return nil, coretools.ErrNoMatches
    37  		}
    38  		toolsConstraint = NewVersionedToolsConstraint(filter.Number,
    39  			simplestreams.LookupParams{CloudSpec: cloudSpec, Stream: stream})
    40  	} else {
    41  		toolsConstraint = NewGeneralToolsConstraint(majorVersion, minorVersion,
    42  			simplestreams.LookupParams{CloudSpec: cloudSpec, Stream: stream})
    43  	}
    44  	if filter.Arch != "" {
    45  		toolsConstraint.Arches = []string{filter.Arch}
    46  	} else {
    47  		logger.Tracef("no architecture specified when finding tools, looking for any")
    48  		toolsConstraint.Arches = arch.AllSupportedArches
    49  	}
    50  	// The old tools search allowed finding tools without needing to specify a series.
    51  	// The simplestreams metadata is keyed off series, so series must be specified in
    52  	// the search constraint. If no series is specified, we gather all the series from
    53  	// lucid onwards and add those to the constraint.
    54  	var seriesToSearch []string
    55  	if filter.Series != "" {
    56  		seriesToSearch = []string{filter.Series}
    57  	} else {
    58  		seriesToSearch = series.SupportedSeries()
    59  		logger.Tracef("no series specified when finding tools, looking for %v", seriesToSearch)
    60  	}
    61  	toolsConstraint.Series = seriesToSearch
    62  	return toolsConstraint, nil
    63  }
    64  
    65  // Define some boolean parameter values.
    66  const DoNotAllowRetry = false
    67  
    68  // HasAgentMirror is an optional interface that an Environ may
    69  // implement to support agent/tools mirror lookup.
    70  //
    71  // TODO(axw) 2016-04-11 #1568715
    72  // This exists only because we currently lack
    73  // image simplestreams usable by the new Azure
    74  // Resource Manager provider. When we have that,
    75  // we can use "HasRegion" everywhere.
    76  type HasAgentMirror interface {
    77  	// AgentMirror returns the CloudSpec to use for looking up agent
    78  	// binaries.
    79  	AgentMirror() (simplestreams.CloudSpec, error)
    80  }
    81  
    82  // FindTools returns a List containing all tools in the given stream, with a given
    83  // major.minor version number available in the cloud instance, filtered by filter.
    84  // If minorVersion = -1, then only majorVersion is considered.
    85  // If no *available* tools have the supplied major.minor version number, or match the
    86  // supplied filter, the function returns a *NotFoundError.
    87  func FindTools(env environs.Environ, majorVersion, minorVersion int, stream string,
    88  	filter coretools.Filter) (list coretools.List, err error) {
    89  
    90  	var cloudSpec simplestreams.CloudSpec
    91  	switch env := env.(type) {
    92  	case simplestreams.HasRegion:
    93  		if cloudSpec, err = env.Region(); err != nil {
    94  			return nil, err
    95  		}
    96  	case HasAgentMirror:
    97  		if cloudSpec, err = env.AgentMirror(); err != nil {
    98  			return nil, err
    99  		}
   100  	}
   101  	// If only one of region or endpoint is provided, that is a problem.
   102  	if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") {
   103  		return nil, fmt.Errorf("cannot find tools without a complete cloud configuration")
   104  	}
   105  
   106  	logger.Infof("finding tools in stream %q", stream)
   107  	if minorVersion >= 0 {
   108  		logger.Infof("reading tools with major.minor version %d.%d", majorVersion, minorVersion)
   109  	} else {
   110  		logger.Infof("reading tools with major version %d", majorVersion)
   111  	}
   112  	defer convertToolsError(&err)
   113  	// Construct a tools filter.
   114  	// Discard all that are known to be irrelevant.
   115  	if filter.Number != version.Zero {
   116  		logger.Infof("filtering tools by version: %s", filter.Number)
   117  	}
   118  	if filter.Series != "" {
   119  		logger.Infof("filtering tools by series: %s", filter.Series)
   120  	}
   121  	if filter.Arch != "" {
   122  		logger.Infof("filtering tools by architecture: %s", filter.Arch)
   123  	}
   124  	sources, err := GetMetadataSources(env)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	return FindToolsForCloud(sources, cloudSpec, stream, majorVersion, minorVersion, filter)
   129  }
   130  
   131  // FindToolsForCloud returns a List containing all tools in the given stream, with a given
   132  // major.minor version number and cloudSpec, filtered by filter.
   133  // If minorVersion = -1, then only majorVersion is considered.
   134  // If no *available* tools have the supplied major.minor version number, or match the
   135  // supplied filter, the function returns a *NotFoundError.
   136  func FindToolsForCloud(sources []simplestreams.DataSource, cloudSpec simplestreams.CloudSpec, stream string,
   137  	majorVersion, minorVersion int, filter coretools.Filter) (list coretools.List, err error) {
   138  
   139  	toolsConstraint, err := makeToolsConstraint(cloudSpec, stream, majorVersion, minorVersion, filter)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	toolsMetadata, _, err := Fetch(sources, toolsConstraint)
   144  	if err != nil {
   145  		if errors.IsNotFound(err) {
   146  			err = ErrNoTools
   147  		}
   148  		return nil, err
   149  	}
   150  	if len(toolsMetadata) == 0 {
   151  		return nil, coretools.ErrNoMatches
   152  	}
   153  	list = make(coretools.List, len(toolsMetadata))
   154  	for i, metadata := range toolsMetadata {
   155  		binary, err := metadata.binary()
   156  		if err != nil {
   157  			return nil, errors.Trace(err)
   158  		}
   159  		list[i] = &coretools.Tools{
   160  			Version: binary,
   161  			URL:     metadata.FullPath,
   162  			Size:    metadata.Size,
   163  			SHA256:  metadata.SHA256,
   164  		}
   165  	}
   166  	if filter.Series != "" {
   167  		if err := checkToolsSeries(list, filter.Series); err != nil {
   168  			return nil, err
   169  		}
   170  	}
   171  	return list, err
   172  }
   173  
   174  // FindExactTools returns only the tools that match the supplied version.
   175  func FindExactTools(env environs.Environ, vers version.Number, series string, arch string) (t *coretools.Tools, err error) {
   176  	logger.Infof("finding exact version %s", vers)
   177  	// Construct a tools filter.
   178  	// Discard all that are known to be irrelevant.
   179  	filter := coretools.Filter{
   180  		Number: vers,
   181  		Series: series,
   182  		Arch:   arch,
   183  	}
   184  	stream := PreferredStream(&vers, env.Config().Development(), env.Config().AgentStream())
   185  	logger.Infof("looking for tools in stream %q", stream)
   186  	availableTools, err := FindTools(env, vers.Major, vers.Minor, stream, filter)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	if len(availableTools) != 1 {
   191  		return nil, fmt.Errorf("expected one tools, got %d tools", len(availableTools))
   192  	}
   193  	return availableTools[0], nil
   194  }
   195  
   196  // checkToolsSeries verifies that all the given possible tools are for the
   197  // given OS series.
   198  func checkToolsSeries(toolsList coretools.List, series string) error {
   199  	toolsSeries := toolsList.AllSeries()
   200  	if len(toolsSeries) != 1 {
   201  		return fmt.Errorf("expected single series, got %v", toolsSeries)
   202  	}
   203  	if toolsSeries[0] != series {
   204  		return fmt.Errorf("tools mismatch: expected series %v, got %v", series, toolsSeries[0])
   205  	}
   206  	return nil
   207  }
   208  
   209  func isToolsError(err error) bool {
   210  	switch err {
   211  	case ErrNoTools, coretools.ErrNoMatches:
   212  		return true
   213  	}
   214  	return false
   215  }
   216  
   217  func convertToolsError(err *error) {
   218  	if isToolsError(*err) {
   219  		*err = errors.NewNotFound(*err, "")
   220  	}
   221  }
   222  
   223  // PreferredStream returns the tools stream used to search for tools, based
   224  // on the required version, whether devel mode is required, and any user specified stream.
   225  func PreferredStream(vers *version.Number, forceDevel bool, stream string) string {
   226  	// If the use has already nominated a specific stream, we'll use that.
   227  	if stream != "" && stream != ReleasedStream {
   228  		return stream
   229  	}
   230  	// If we're not upgrading from a known version, we use the
   231  	// currently running version.
   232  	if vers == nil {
   233  		vers = &jujuversion.Current
   234  	}
   235  	// Devel versions are alpha or beta etc as defined by the version tag.
   236  	// The user can also force the use of devel streams via config.
   237  	if forceDevel || jujuversion.IsDev(*vers) {
   238  		return DevelStream
   239  	}
   240  	return ReleasedStream
   241  }