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 }