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