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