github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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  	"github.com/juju/juju/version/ubuntu"
    18  )
    19  
    20  var logger = loggo.GetLogger("juju.environs.tools")
    21  
    22  func makeToolsConstraint(cloudSpec simplestreams.CloudSpec, majorVersion, minorVersion int,
    23  	filter coretools.Filter) (*ToolsConstraint, error) {
    24  
    25  	var toolsConstraint *ToolsConstraint
    26  	if filter.Number != version.Zero {
    27  		// A specific tools version is required, however, a general match based on major/minor
    28  		// version may also have been requested. This is used to ensure any agent version currently
    29  		// recorded in the environment matches the Juju cli version.
    30  		// We can short circuit any lookup here by checking the major/minor numbers against
    31  		// the filter version and exiting early if there is a mismatch.
    32  		majorMismatch := majorVersion > 0 && majorVersion != filter.Number.Major
    33  		minorMismacth := minorVersion != -1 && minorVersion != filter.Number.Minor
    34  		if majorMismatch || minorMismacth {
    35  			return nil, coretools.ErrNoMatches
    36  		}
    37  		toolsConstraint = NewVersionedToolsConstraint(filter.Number,
    38  			simplestreams.LookupParams{CloudSpec: cloudSpec})
    39  	} else {
    40  		toolsConstraint = NewGeneralToolsConstraint(majorVersion, minorVersion, filter.Released,
    41  			simplestreams.LookupParams{CloudSpec: cloudSpec})
    42  	}
    43  	if filter.Arch != "" {
    44  		toolsConstraint.Arches = []string{filter.Arch}
    45  	} else {
    46  		logger.Debugf("no architecture specified when finding tools, looking for any")
    47  		toolsConstraint.Arches = arch.AllSupportedArches
    48  	}
    49  	// The old tools search allowed finding tools without needing to specify a series.
    50  	// The simplestreams metadata is keyed off series, so series must be specified in
    51  	// the search constraint. If no series is specified, we gather all the series from
    52  	// lucid onwards and add those to the constraint.
    53  	var seriesToSearch []string
    54  	if filter.Series != "" {
    55  		seriesToSearch = []string{filter.Series}
    56  	} else {
    57  		logger.Debugf("no series specified when finding tools, looking for any")
    58  		seriesToSearch = ubuntu.SupportedSeries()
    59  	}
    60  	toolsConstraint.Series = seriesToSearch
    61  	return toolsConstraint, nil
    62  }
    63  
    64  // Define some boolean parameter values.
    65  const DoNotAllowRetry = false
    66  
    67  // FindTools returns a List containing all tools with a given
    68  // major.minor version number available in the cloud instance, filtered by filter.
    69  // If minorVersion = -1, then only majorVersion is considered.
    70  // If no *available* tools have the supplied major.minor version number, or match the
    71  // supplied filter, the function returns a *NotFoundError.
    72  func FindTools(cloudInst environs.ConfigGetter, majorVersion, minorVersion int,
    73  	filter coretools.Filter, allowRetry bool) (list coretools.List, err error) {
    74  
    75  	var cloudSpec simplestreams.CloudSpec
    76  	if inst, ok := cloudInst.(simplestreams.HasRegion); ok {
    77  		if cloudSpec, err = inst.Region(); err != nil {
    78  			return nil, err
    79  		}
    80  	}
    81  	// If only one of region or endpoint is provided, that is a problem.
    82  	if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") {
    83  		return nil, fmt.Errorf("cannot find tools without a complete cloud configuration")
    84  	}
    85  
    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 := GetMetadataSourcesWithRetries(cloudInst, allowRetry)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	return FindToolsForCloud(sources, cloudSpec, majorVersion, minorVersion, filter)
   108  }
   109  
   110  // FindToolsForCloud returns a List containing all tools 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,
   116  	majorVersion, minorVersion int, filter coretools.Filter) (list coretools.List, err error) {
   117  
   118  	toolsConstraint, err := makeToolsConstraint(cloudSpec, majorVersion, minorVersion, filter)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	toolsMetadata, _, err := Fetch(sources, simplestreams.DefaultIndexPath, 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  		list[i] = &coretools.Tools{
   135  			Version: metadata.binary(),
   136  			URL:     metadata.FullPath,
   137  			Size:    metadata.Size,
   138  			SHA256:  metadata.SHA256,
   139  		}
   140  	}
   141  	if filter.Series != "" {
   142  		if err := checkToolsSeries(list, filter.Series); err != nil {
   143  			return nil, err
   144  		}
   145  	}
   146  	return list, err
   147  }
   148  
   149  // The following allows FindTools, as called by FindBootstrapTools, to be patched for testing.
   150  var bootstrapFindTools = FindTools
   151  
   152  type findtoolsfunc func(environs.ConfigGetter, int, int, coretools.Filter, bool) (coretools.List, error)
   153  
   154  func TestingPatchBootstrapFindTools(stub findtoolsfunc) func() {
   155  	origFunc := bootstrapFindTools
   156  	bootstrapFindTools = stub
   157  	return func() {
   158  		bootstrapFindTools = origFunc
   159  	}
   160  }
   161  
   162  // BootstrapToolsParams contains parameters for FindBootstrapTools
   163  type BootstrapToolsParams struct {
   164  	Version    *version.Number
   165  	Arch       *string
   166  	Series     string
   167  	AllowRetry bool
   168  }
   169  
   170  // FindBootstrapTools returns a ToolsList containing only those tools with
   171  // which it would be reasonable to launch an environment's first machine, given the supplied constraints.
   172  // If a specific agent version is not requested, all tools matching the current major.minor version are chosen.
   173  func FindBootstrapTools(cloudInst environs.ConfigGetter, params BootstrapToolsParams) (list coretools.List, err error) {
   174  	// Construct a tools filter.
   175  	cfg := cloudInst.Config()
   176  	cliVersion := version.Current.Number
   177  	filter := coretools.Filter{
   178  		Series: params.Series,
   179  		Arch:   stringOrEmpty(params.Arch),
   180  	}
   181  	if params.Version != nil {
   182  		// If we already have an explicit agent version set, we're done.
   183  		filter.Number = *params.Version
   184  		return bootstrapFindTools(cloudInst, cliVersion.Major, cliVersion.Minor, filter, params.AllowRetry)
   185  	}
   186  	if dev := cliVersion.IsDev() || cfg.Development(); !dev {
   187  		logger.Infof("filtering tools by released version")
   188  		filter.Released = true
   189  	}
   190  	return bootstrapFindTools(cloudInst, cliVersion.Major, cliVersion.Minor, filter, params.AllowRetry)
   191  }
   192  
   193  func stringOrEmpty(pstr *string) string {
   194  	if pstr == nil {
   195  		return ""
   196  	}
   197  	return *pstr
   198  }
   199  
   200  // FindInstanceTools returns a ToolsList containing only those tools with which
   201  // it would be reasonable to start a new instance, given the supplied series and arch.
   202  func FindInstanceTools(cloudInst environs.ConfigGetter,
   203  	vers version.Number, series string, arch *string) (list coretools.List, err error) {
   204  
   205  	// Construct a tools filter.
   206  	// Discard all that are known to be irrelevant.
   207  	filter := coretools.Filter{
   208  		Number: vers,
   209  		Series: series,
   210  		Arch:   stringOrEmpty(arch),
   211  	}
   212  	return FindTools(cloudInst, vers.Major, vers.Minor, filter, DoNotAllowRetry)
   213  }
   214  
   215  // FindExactTools returns only the tools that match the supplied version.
   216  func FindExactTools(cloudInst environs.ConfigGetter,
   217  	vers version.Number, series string, arch string) (t *coretools.Tools, err error) {
   218  
   219  	logger.Infof("finding exact version %s", vers)
   220  	// Construct a tools filter.
   221  	// Discard all that are known to be irrelevant.
   222  	filter := coretools.Filter{
   223  		Number: vers,
   224  		Series: series,
   225  		Arch:   arch,
   226  	}
   227  	availableTools, err := FindTools(cloudInst, vers.Major, vers.Minor, filter, DoNotAllowRetry)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  	if len(availableTools) != 1 {
   232  		return nil, fmt.Errorf("expected one tools, got %d tools", len(availableTools))
   233  	}
   234  	return availableTools[0], nil
   235  }
   236  
   237  // checkToolsSeries verifies that all the given possible tools are for the
   238  // given OS series.
   239  func checkToolsSeries(toolsList coretools.List, series string) error {
   240  	toolsSeries := toolsList.AllSeries()
   241  	if len(toolsSeries) != 1 {
   242  		return fmt.Errorf("expected single series, got %v", toolsSeries)
   243  	}
   244  	if toolsSeries[0] != series {
   245  		return fmt.Errorf("tools mismatch: expected series %v, got %v", series, toolsSeries[0])
   246  	}
   247  	return nil
   248  }
   249  
   250  func isToolsError(err error) bool {
   251  	switch err {
   252  	case ErrNoTools, coretools.ErrNoMatches:
   253  		return true
   254  	}
   255  	return false
   256  }
   257  
   258  func convertToolsError(err *error) {
   259  	if isToolsError(*err) {
   260  		*err = errors.NewNotFound(*err, "")
   261  	}
   262  }