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

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package bootstrap
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/utils/arch"
    11  	jujuos "github.com/juju/utils/os"
    12  	"github.com/juju/utils/series"
    13  	"github.com/juju/utils/set"
    14  	"github.com/juju/version"
    15  
    16  	"github.com/juju/juju/environs"
    17  	envtools "github.com/juju/juju/environs/tools"
    18  	coretools "github.com/juju/juju/tools"
    19  	jujuversion "github.com/juju/juju/version"
    20  )
    21  
    22  var (
    23  	findTools = envtools.FindTools
    24  )
    25  
    26  // validateUploadAllowed returns an error if an attempt to upload tools should
    27  // not be allowed.
    28  func validateUploadAllowed(env environs.Environ, toolsArch, toolsSeries *string) error {
    29  	// Now check that the architecture and series for which we are setting up an
    30  	// environment matches that from which we are bootstrapping.
    31  	hostArch := arch.HostArch()
    32  	// We can't build tools for a different architecture if one is specified.
    33  	if toolsArch != nil && *toolsArch != hostArch {
    34  		return fmt.Errorf("cannot build tools for %q using a machine running on %q", *toolsArch, hostArch)
    35  	}
    36  	hostOS := jujuos.HostOS()
    37  	if toolsSeries != nil {
    38  		toolsSeriesOS, err := series.GetOSFromSeries(*toolsSeries)
    39  		if err != nil {
    40  			return errors.Trace(err)
    41  		}
    42  		if toolsSeriesOS != hostOS {
    43  			return errors.Errorf("cannot build tools for %q using a machine running %q", *toolsSeries, hostOS)
    44  		}
    45  	}
    46  	// If no architecture is specified, ensure the target provider supports instances matching our architecture.
    47  	supportedArchitectures, err := env.SupportedArchitectures()
    48  	if err != nil {
    49  		return fmt.Errorf(
    50  			"no packaged tools available and cannot determine model's supported architectures: %v", err)
    51  	}
    52  	archSupported := false
    53  	for _, arch := range supportedArchitectures {
    54  		if hostArch == arch {
    55  			archSupported = true
    56  			break
    57  		}
    58  	}
    59  	if !archSupported {
    60  		envType := env.Config().Type()
    61  		return errors.Errorf("model %q of type %s does not support instances running on %q", env.Config().Name(), envType, hostArch)
    62  	}
    63  	return nil
    64  }
    65  
    66  // findAvailableTools returns a list of available tools,
    67  // including tools that may be locally built and then
    68  // uploaded. Tools that need to be built will have an
    69  // empty URL.
    70  func findAvailableTools(
    71  	env environs.Environ,
    72  	vers *version.Number,
    73  	arch, series *string,
    74  	upload, canBuild bool,
    75  ) (coretools.List, error) {
    76  	if upload {
    77  		// We're forcing an upload: ensure we can do so.
    78  		if !canBuild {
    79  			return nil, errors.New("cannot build tools to upload")
    80  		}
    81  		if err := validateUploadAllowed(env, arch, series); err != nil {
    82  			return nil, err
    83  		}
    84  		return locallyBuildableTools(series), nil
    85  	}
    86  
    87  	// We're not forcing an upload, so look for tools
    88  	// in the environment's simplestreams search paths
    89  	// for existing tools.
    90  
    91  	// If the user hasn't asked for a specified tools version, see if
    92  	// one is configured in the environment.
    93  	if vers == nil {
    94  		if agentVersion, ok := env.Config().AgentVersion(); ok {
    95  			vers = &agentVersion
    96  		}
    97  	}
    98  	logger.Infof("looking for bootstrap tools: version=%v", vers)
    99  	toolsList, findToolsErr := findBootstrapTools(env, vers, arch, series)
   100  	if findToolsErr != nil && !errors.IsNotFound(findToolsErr) {
   101  		return nil, findToolsErr
   102  	}
   103  
   104  	preferredStream := envtools.PreferredStream(vers, env.Config().Development(), env.Config().AgentStream())
   105  	if preferredStream == envtools.ReleasedStream || vers != nil || !canBuild {
   106  		// We are not running a development build, or agent-version
   107  		// was specified, or we cannot build any tools; the only tools
   108  		// available are the ones we've just found.
   109  		return toolsList, findToolsErr
   110  	}
   111  	// The tools located may not include the ones that the
   112  	// provider requires. We are running a development build,
   113  	// so augment the list of tools with those that we can build
   114  	// locally.
   115  
   116  	// Collate the set of arch+series that are externally available
   117  	// so we can see if we need to build any locally. If we need
   118  	// to, only then do we validate that we can upload (which
   119  	// involves a potentially expensive SupportedArchitectures call).
   120  	archSeries := make(set.Strings)
   121  	for _, tools := range toolsList {
   122  		archSeries.Add(tools.Version.Arch + tools.Version.Series)
   123  	}
   124  	var localToolsList coretools.List
   125  	for _, tools := range locallyBuildableTools(series) {
   126  		if !archSeries.Contains(tools.Version.Arch + tools.Version.Series) {
   127  			localToolsList = append(localToolsList, tools)
   128  		}
   129  	}
   130  	if len(localToolsList) == 0 || validateUploadAllowed(env, arch, series) != nil {
   131  		return toolsList, findToolsErr
   132  	}
   133  	return append(toolsList, localToolsList...), nil
   134  }
   135  
   136  // locallyBuildableTools returns the list of tools that
   137  // can be built locally, for series of the same OS.
   138  func locallyBuildableTools(toolsSeries *string) (buildable coretools.List) {
   139  	for _, ser := range series.SupportedSeries() {
   140  		if os, err := series.GetOSFromSeries(ser); err != nil || os != jujuos.HostOS() {
   141  			continue
   142  		}
   143  		if toolsSeries != nil && ser != *toolsSeries {
   144  			continue
   145  		}
   146  		binary := version.Binary{
   147  			Number: jujuversion.Current,
   148  			Series: ser,
   149  			Arch:   arch.HostArch(),
   150  		}
   151  		// Increment the build number so we know it's a development build.
   152  		binary.Build++
   153  		buildable = append(buildable, &coretools.Tools{Version: binary})
   154  	}
   155  	return buildable
   156  }
   157  
   158  // findBootstrapTools returns a tools.List containing only those tools with
   159  // which it would be reasonable to launch an environment's first machine,
   160  // given the supplied constraints. If a specific agent version is not requested,
   161  // all tools matching the current major.minor version are chosen.
   162  func findBootstrapTools(env environs.Environ, vers *version.Number, arch, series *string) (list coretools.List, err error) {
   163  	// Construct a tools filter.
   164  	cliVersion := jujuversion.Current
   165  	var filter coretools.Filter
   166  	if arch != nil {
   167  		filter.Arch = *arch
   168  	}
   169  	if series != nil {
   170  		filter.Series = *series
   171  	}
   172  	if vers != nil {
   173  		filter.Number = *vers
   174  	}
   175  	stream := envtools.PreferredStream(vers, env.Config().Development(), env.Config().AgentStream())
   176  	return findTools(env, cliVersion.Major, cliVersion.Minor, stream, filter)
   177  }