github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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 agent binaries, 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 agent binaries, looking for %v", seriesToSearch)
    60  	}
    61  	toolsConstraint.Series = seriesToSearch
    62  	return toolsConstraint, nil
    63  }
    64  
    65  // HasAgentMirror is an optional interface that an Environ may
    66  // implement to support agent/tools mirror lookup.
    67  //
    68  // TODO(axw) 2016-04-11 #1568715
    69  // This exists only because we currently lack
    70  // image simplestreams usable by the new Azure
    71  // Resource Manager provider. When we have that,
    72  // we can use "HasRegion" everywhere.
    73  type HasAgentMirror interface {
    74  	// AgentMirror returns the CloudSpec to use for looking up agent
    75  	// binaries.
    76  	AgentMirror() (simplestreams.CloudSpec, error)
    77  }
    78  
    79  // FindTools returns a List containing all tools in the given stream, with a given
    80  // major.minor version number available in the cloud instance, filtered by filter.
    81  // If minorVersion = -1, then only majorVersion is considered.
    82  // If no *available* tools have the supplied major.minor version number, or match the
    83  // supplied filter, the function returns a *NotFoundError.
    84  func FindTools(env environs.Environ, majorVersion, minorVersion int, stream string, filter coretools.Filter) (_ coretools.List, err error) {
    85  	var cloudSpec simplestreams.CloudSpec
    86  	switch env := env.(type) {
    87  	case simplestreams.HasRegion:
    88  		if cloudSpec, err = env.Region(); err != nil {
    89  			return nil, err
    90  		}
    91  	case HasAgentMirror:
    92  		if cloudSpec, err = env.AgentMirror(); err != nil {
    93  			return nil, err
    94  		}
    95  	}
    96  	// If only one of region or endpoint is provided, that is a problem.
    97  	if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") {
    98  		return nil, errors.New("cannot find agent binaries without a complete cloud configuration")
    99  	}
   100  
   101  	logger.Infof("finding agent binaries in stream %q", stream)
   102  	if minorVersion >= 0 {
   103  		logger.Infof("reading agent binaries with major.minor version %d.%d", majorVersion, minorVersion)
   104  	} else {
   105  		logger.Infof("reading agent binaries with major version %d", majorVersion)
   106  	}
   107  	defer convertToolsError(&err)
   108  	// Construct a tools filter.
   109  	// Discard all that are known to be irrelevant.
   110  	if filter.Number != version.Zero {
   111  		logger.Infof("filtering agent binaries by version: %s", filter.Number)
   112  	}
   113  	if filter.Series != "" {
   114  		logger.Infof("filtering agent binaries by series: %s", filter.Series)
   115  	}
   116  	if filter.Arch != "" {
   117  		logger.Infof("filtering agent binaries by architecture: %s", filter.Arch)
   118  	}
   119  	sources, err := GetMetadataSources(env)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	return FindToolsForCloud(sources, cloudSpec, stream, majorVersion, minorVersion, filter)
   124  }
   125  
   126  // FindToolsForCloud returns a List containing all tools in the given stream, with a given
   127  // major.minor version number and cloudSpec, filtered by filter.
   128  // If minorVersion = -1, then only majorVersion is considered.
   129  // If no *available* tools have the supplied major.minor version number, or match the
   130  // supplied filter, the function returns a *NotFoundError.
   131  func FindToolsForCloud(sources []simplestreams.DataSource, cloudSpec simplestreams.CloudSpec, stream string,
   132  	majorVersion, minorVersion int, filter coretools.Filter) (list coretools.List, err error) {
   133  
   134  	toolsConstraint, err := makeToolsConstraint(cloudSpec, stream, majorVersion, minorVersion, filter)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	toolsMetadata, _, err := Fetch(sources, toolsConstraint)
   139  	if err != nil {
   140  		if errors.IsNotFound(err) {
   141  			err = ErrNoTools
   142  		}
   143  		return nil, err
   144  	}
   145  	if len(toolsMetadata) == 0 {
   146  		return nil, coretools.ErrNoMatches
   147  	}
   148  	list = make(coretools.List, len(toolsMetadata))
   149  	for i, metadata := range toolsMetadata {
   150  		binary, err := metadata.binary()
   151  		if err != nil {
   152  			return nil, errors.Trace(err)
   153  		}
   154  		list[i] = &coretools.Tools{
   155  			Version: binary,
   156  			URL:     metadata.FullPath,
   157  			Size:    metadata.Size,
   158  			SHA256:  metadata.SHA256,
   159  		}
   160  	}
   161  	if filter.Series != "" {
   162  		if err := checkToolsSeries(list, filter.Series); err != nil {
   163  			return nil, err
   164  		}
   165  	}
   166  	return list, err
   167  }
   168  
   169  // FindExactTools returns only the tools that match the supplied version.
   170  func FindExactTools(env environs.Environ, vers version.Number, series string, arch string) (_ *coretools.Tools, err error) {
   171  	logger.Infof("finding exact version %s", vers)
   172  	// Construct a tools filter.
   173  	// Discard all that are known to be irrelevant.
   174  	filter := coretools.Filter{
   175  		Number: vers,
   176  		Series: series,
   177  		Arch:   arch,
   178  	}
   179  	stream := PreferredStream(&vers, env.Config().Development(), env.Config().AgentStream())
   180  	logger.Infof("looking for tools in stream %q", stream)
   181  	availableTools, err := FindTools(env, vers.Major, vers.Minor, stream, filter)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	if len(availableTools) != 1 {
   186  		return nil, fmt.Errorf("expected one tools, got %d tools", len(availableTools))
   187  	}
   188  	return availableTools[0], nil
   189  }
   190  
   191  // checkToolsSeries verifies that all the given possible tools are for the
   192  // given OS series.
   193  func checkToolsSeries(toolsList coretools.List, series string) error {
   194  	toolsSeries := toolsList.AllSeries()
   195  	if len(toolsSeries) != 1 {
   196  		return fmt.Errorf("expected single series, got %v", toolsSeries)
   197  	}
   198  	if toolsSeries[0] != series {
   199  		return fmt.Errorf("tools mismatch: expected series %v, got %v", series, toolsSeries[0])
   200  	}
   201  	return nil
   202  }
   203  
   204  func isToolsError(err error) bool {
   205  	switch err {
   206  	case ErrNoTools, coretools.ErrNoMatches:
   207  		return true
   208  	}
   209  	return false
   210  }
   211  
   212  func convertToolsError(err *error) {
   213  	if isToolsError(*err) {
   214  		*err = errors.NewNotFound(*err, "")
   215  	}
   216  }
   217  
   218  // PreferredStream returns the tools stream used to search for tools, based
   219  // on the required version, whether devel mode is required, and any user specified stream.
   220  func PreferredStream(vers *version.Number, forceDevel bool, stream string) string {
   221  	// If the use has already nominated a specific stream, we'll use that.
   222  	if stream != "" && stream != ReleasedStream {
   223  		return stream
   224  	}
   225  	// If we're not upgrading from a known version, we use the
   226  	// currently running version.
   227  	if vers == nil {
   228  		vers = &jujuversion.Current
   229  	}
   230  	// Devel versions are alpha or beta etc as defined by the version tag.
   231  	// The user can also force the use of devel streams via config.
   232  	if forceDevel || jujuversion.IsDev(*vers) {
   233  		return DevelStream
   234  	}
   235  	return ReleasedStream
   236  }