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 }