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