github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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 agent binaries, 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 agent binaries, looking for %v", seriesToSearch) 60 } 61 toolsConstraint.Series = seriesToSearch 62 return toolsConstraint, nil 63 } 64 65 // HasAgentMirror is an optional interface that an Environ may 66 // implement to support agent/tools mirror lookup. 67 // 68 // TODO(axw) 2016-04-11 #1568715 69 // This exists only because we currently lack 70 // image simplestreams usable by the new Azure 71 // Resource Manager provider. When we have that, 72 // we can use "HasRegion" everywhere. 73 type HasAgentMirror interface { 74 // AgentMirror returns the CloudSpec to use for looking up agent 75 // binaries. 76 AgentMirror() (simplestreams.CloudSpec, error) 77 } 78 79 // FindTools returns a List containing all tools in the given stream, with a given 80 // major.minor version number available in the cloud instance, filtered by filter. 81 // If minorVersion = -1, then only majorVersion is considered. 82 // If no *available* tools have the supplied major.minor version number, or match the 83 // supplied filter, the function returns a *NotFoundError. 84 func FindTools(env environs.Environ, majorVersion, minorVersion int, stream string, filter coretools.Filter) (_ coretools.List, err error) { 85 var cloudSpec simplestreams.CloudSpec 86 switch env := env.(type) { 87 case simplestreams.HasRegion: 88 if cloudSpec, err = env.Region(); err != nil { 89 return nil, err 90 } 91 case HasAgentMirror: 92 if cloudSpec, err = env.AgentMirror(); err != nil { 93 return nil, err 94 } 95 } 96 // If only one of region or endpoint is provided, that is a problem. 97 if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") { 98 return nil, errors.New("cannot find agent binaries without a complete cloud configuration") 99 } 100 101 logger.Infof("finding agent binaries in stream %q", stream) 102 if minorVersion >= 0 { 103 logger.Infof("reading agent binaries with major.minor version %d.%d", majorVersion, minorVersion) 104 } else { 105 logger.Infof("reading agent binaries with major version %d", majorVersion) 106 } 107 defer convertToolsError(&err) 108 // Construct a tools filter. 109 // Discard all that are known to be irrelevant. 110 if filter.Number != version.Zero { 111 logger.Infof("filtering agent binaries by version: %s", filter.Number) 112 } 113 if filter.Series != "" { 114 logger.Infof("filtering agent binaries by series: %s", filter.Series) 115 } 116 if filter.Arch != "" { 117 logger.Infof("filtering agent binaries by architecture: %s", filter.Arch) 118 } 119 sources, err := GetMetadataSources(env) 120 if err != nil { 121 return nil, err 122 } 123 return FindToolsForCloud(sources, cloudSpec, stream, majorVersion, minorVersion, filter) 124 } 125 126 // FindToolsForCloud returns a List containing all tools in the given stream, with a given 127 // major.minor version number and cloudSpec, filtered by filter. 128 // If minorVersion = -1, then only majorVersion is considered. 129 // If no *available* tools have the supplied major.minor version number, or match the 130 // supplied filter, the function returns a *NotFoundError. 131 func FindToolsForCloud(sources []simplestreams.DataSource, cloudSpec simplestreams.CloudSpec, stream string, 132 majorVersion, minorVersion int, filter coretools.Filter) (list coretools.List, err error) { 133 134 toolsConstraint, err := makeToolsConstraint(cloudSpec, stream, majorVersion, minorVersion, filter) 135 if err != nil { 136 return nil, err 137 } 138 toolsMetadata, _, err := Fetch(sources, toolsConstraint) 139 if err != nil { 140 if errors.IsNotFound(err) { 141 err = ErrNoTools 142 } 143 return nil, err 144 } 145 if len(toolsMetadata) == 0 { 146 return nil, coretools.ErrNoMatches 147 } 148 list = make(coretools.List, len(toolsMetadata)) 149 for i, metadata := range toolsMetadata { 150 binary, err := metadata.binary() 151 if err != nil { 152 return nil, errors.Trace(err) 153 } 154 list[i] = &coretools.Tools{ 155 Version: binary, 156 URL: metadata.FullPath, 157 Size: metadata.Size, 158 SHA256: metadata.SHA256, 159 } 160 } 161 if filter.Series != "" { 162 if err := checkToolsSeries(list, filter.Series); err != nil { 163 return nil, err 164 } 165 } 166 return list, err 167 } 168 169 // FindExactTools returns only the tools that match the supplied version. 170 func FindExactTools(env environs.Environ, vers version.Number, series string, arch string) (_ *coretools.Tools, err error) { 171 logger.Infof("finding exact version %s", vers) 172 // Construct a tools filter. 173 // Discard all that are known to be irrelevant. 174 filter := coretools.Filter{ 175 Number: vers, 176 Series: series, 177 Arch: arch, 178 } 179 stream := PreferredStream(&vers, env.Config().Development(), env.Config().AgentStream()) 180 logger.Infof("looking for tools in stream %q", stream) 181 availableTools, err := FindTools(env, vers.Major, vers.Minor, stream, filter) 182 if err != nil { 183 return nil, err 184 } 185 if len(availableTools) != 1 { 186 return nil, fmt.Errorf("expected one tools, got %d tools", len(availableTools)) 187 } 188 return availableTools[0], nil 189 } 190 191 // checkToolsSeries verifies that all the given possible tools are for the 192 // given OS series. 193 func checkToolsSeries(toolsList coretools.List, series string) error { 194 toolsSeries := toolsList.AllSeries() 195 if len(toolsSeries) != 1 { 196 return fmt.Errorf("expected single series, got %v", toolsSeries) 197 } 198 if toolsSeries[0] != series { 199 return fmt.Errorf("tools mismatch: expected series %v, got %v", series, toolsSeries[0]) 200 } 201 return nil 202 } 203 204 func isToolsError(err error) bool { 205 switch err { 206 case ErrNoTools, coretools.ErrNoMatches: 207 return true 208 } 209 return false 210 } 211 212 func convertToolsError(err *error) { 213 if isToolsError(*err) { 214 *err = errors.NewNotFound(*err, "") 215 } 216 } 217 218 // PreferredStream returns the tools stream used to search for tools, based 219 // on the required version, whether devel mode is required, and any user specified stream. 220 func PreferredStream(vers *version.Number, forceDevel bool, stream string) string { 221 // If the use has already nominated a specific stream, we'll use that. 222 if stream != "" && stream != ReleasedStream { 223 return stream 224 } 225 // If we're not upgrading from a known version, we use the 226 // currently running version. 227 if vers == nil { 228 vers = &jujuversion.Current 229 } 230 // Devel versions are alpha or beta etc as defined by the version tag. 231 // The user can also force the use of devel streams via config. 232 if forceDevel || jujuversion.IsDev(*vers) { 233 return DevelStream 234 } 235 return ReleasedStream 236 }