1
// Copyright 2012, 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
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"
16
"github.com/juju/juju/constraints"
17
"github.com/juju/juju/environs"
18
envtools "github.com/juju/juju/environs/tools"
19
coretools "github.com/juju/juju/tools"
20
jujuversion "github.com/juju/juju/version"
24
findTools = envtools.FindTools
27
// validateUploadAllowed returns an error if an attempt to upload tools should
29
func validateUploadAllowed(env environs.Environ, toolsArch, toolsSeries *string) error {
30
// Now check that the architecture and series for which we are setting up an
31
// environment matches that from which we are bootstrapping.
32
hostArch := arch.HostArch()
33
// We can't build tools for a different architecture if one is specified.
34
if toolsArch != nil && *toolsArch != hostArch {
35
return fmt.Errorf("cannot build tools for %q using a machine running on %q", *toolsArch, hostArch)
37
hostOS := jujuos.HostOS()
38
if toolsSeries != nil {
39
toolsSeriesOS, err := series.GetOSFromSeries(*toolsSeries)
41
return errors.Trace(err)
43
if toolsSeriesOS != hostOS {
44
return errors.Errorf("cannot build tools for %q using a machine running %q", *toolsSeries, hostOS)
47
// If no architecture is specified, ensure the target provider supports instances matching our architecture.
48
validator, err := env.ConstraintsValidator()
50
return errors.Annotate(err,
51
"no packaged tools available and cannot determine model's supported architectures",
54
if _, err := validator.Validate(constraints.Value{Arch: &hostArch}); err != nil {
56
"model %q of type %s does not support instances running on %q",
57
env.Config().Name(), env.Config().Type(), hostArch,
63
// findAvailableTools returns a list of available tools,
64
// including tools that may be locally built and then
65
// uploaded. Tools that need to be built will have an
67
func findAvailableTools(
71
upload, canBuild bool,
72
) (coretools.List, error) {
74
// We're forcing an upload: ensure we can do so.
76
return nil, errors.New("cannot build tools to upload")
78
if err := validateUploadAllowed(env, arch, series); err != nil {
81
return locallyBuildableTools(series), nil
84
// We're not forcing an upload, so look for tools
85
// in the environment's simplestreams search paths
86
// for existing tools.
88
// If the user hasn't asked for a specified tools version, see if
89
// one is configured in the environment.
91
if agentVersion, ok := env.Config().AgentVersion(); ok {
95
logger.Infof("looking for bootstrap tools: version=%v", vers)
96
toolsList, findToolsErr := findBootstrapTools(env, vers, arch, series)
97
if findToolsErr != nil && !errors.IsNotFound(findToolsErr) {
98
return nil, findToolsErr
101
preferredStream := envtools.PreferredStream(vers, env.Config().Development(), env.Config().AgentStream())
102
if preferredStream == envtools.ReleasedStream || vers != nil || !canBuild {
103
// We are not running a development build, or agent-version
104
// was specified, or we cannot build any tools; the only tools
105
// available are the ones we've just found.
106
return toolsList, findToolsErr
108
// The tools located may not include the ones that the
109
// provider requires. We are running a development build,
110
// so augment the list of tools with those that we can build
113
// Collate the set of arch+series that are externally available
114
// so we can see if we need to build any locally. If we need
115
// to, only then do we validate that we can upload (which
116
// involves a potentially expensive ConstraintsValidator call).
117
archSeries := make(set.Strings)
118
for _, tools := range toolsList {
119
archSeries.Add(tools.Version.Arch + tools.Version.Series)
121
var localToolsList coretools.List
122
for _, tools := range locallyBuildableTools(series) {
123
if !archSeries.Contains(tools.Version.Arch + tools.Version.Series) {
124
localToolsList = append(localToolsList, tools)
127
if len(localToolsList) == 0 || validateUploadAllowed(env, arch, series) != nil {
128
return toolsList, findToolsErr
130
return append(toolsList, localToolsList...), nil
133
// locallyBuildableTools returns the list of tools that
134
// can be built locally, for series of the same OS.
135
func locallyBuildableTools(toolsSeries *string) (buildable coretools.List) {
136
for _, ser := range series.SupportedSeries() {
137
if os, err := series.GetOSFromSeries(ser); err != nil || os != jujuos.HostOS() {
140
if toolsSeries != nil && ser != *toolsSeries {
143
binary := version.Binary{
144
Number: jujuversion.Current,
146
Arch: arch.HostArch(),
148
// Increment the build number so we know it's a development build.
150
buildable = append(buildable, &coretools.Tools{Version: binary})
155
// findBootstrapTools returns a tools.List containing only those tools with
156
// which it would be reasonable to launch an environment's first machine,
157
// given the supplied constraints. If a specific agent version is not requested,
158
// all tools matching the current major.minor version are chosen.
159
func findBootstrapTools(env environs.Environ, vers *version.Number, arch, series *string) (list coretools.List, err error) {
160
// Construct a tools filter.
161
cliVersion := jujuversion.Current
162
var filter coretools.Filter
167
filter.Series = *series
170
filter.Number = *vers
172
stream := envtools.PreferredStream(vers, env.Config().Development(), env.Config().AgentStream())
173
return findTools(env, cliVersion.Major, cliVersion.Minor, stream, filter)