launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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/loggo/loggo" 10 11 "launchpad.net/juju-core/environs" 12 "launchpad.net/juju-core/environs/simplestreams" 13 "launchpad.net/juju-core/errors" 14 coretools "launchpad.net/juju-core/tools" 15 "launchpad.net/juju-core/version" 16 ) 17 18 var logger = loggo.GetLogger("juju.environs.tools") 19 20 func makeToolsConstraint(cloudSpec simplestreams.CloudSpec, majorVersion, minorVersion int, 21 filter coretools.Filter) (*ToolsConstraint, error) { 22 23 var toolsConstraint *ToolsConstraint 24 if filter.Number != version.Zero { 25 // A specific tools version is required, however, a general match based on major/minor 26 // version may also have been requested. This is used to ensure any agent version currently 27 // recorded in the environment matches the Juju cli version. 28 // We can short circuit any lookup here by checking the major/minor numbers against 29 // the filter version and exiting early if there is a mismatch. 30 majorMismatch := majorVersion > 0 && majorVersion != filter.Number.Major 31 minorMismacth := minorVersion != -1 && minorVersion != filter.Number.Minor 32 if majorMismatch || minorMismacth { 33 return nil, coretools.ErrNoMatches 34 } 35 toolsConstraint = NewVersionedToolsConstraint(filter.Number.String(), 36 simplestreams.LookupParams{CloudSpec: cloudSpec}) 37 } else { 38 toolsConstraint = NewGeneralToolsConstraint(majorVersion, minorVersion, filter.Released, 39 simplestreams.LookupParams{CloudSpec: cloudSpec}) 40 } 41 if filter.Arch != "" { 42 toolsConstraint.Arches = []string{filter.Arch} 43 } else { 44 logger.Debugf("no architecture specified when finding tools, looking for any") 45 toolsConstraint.Arches = []string{"amd64", "i386", "arm"} 46 } 47 // The old tools search allowed finding tools without needing to specify a series. 48 // The simplestreams metadata is keyed off series, so series must be specified in 49 // the search constraint. If no series is specified, we gather all the series from 50 // lucid onwards and add those to the constraint. 51 var seriesToSearch []string 52 if filter.Series != "" { 53 seriesToSearch = []string{filter.Series} 54 } else { 55 logger.Debugf("no series specified when finding tools, looking for any") 56 seriesToSearch = simplestreams.SupportedSeries() 57 } 58 toolsConstraint.Series = seriesToSearch 59 return toolsConstraint, nil 60 } 61 62 // Define some boolean parameter values. 63 const DoNotAllowRetry = false 64 65 // FindTools returns a List containing all tools with a given 66 // major.minor version number available in the cloud instance, filtered by filter. 67 // If minorVersion = -1, then only majorVersion is considered. 68 // If no *available* tools have the supplied major.minor version number, or match the 69 // supplied filter, the function returns a *NotFoundError. 70 func FindTools(cloudInst environs.ConfigGetter, majorVersion, minorVersion int, 71 filter coretools.Filter, allowRetry bool) (list coretools.List, err error) { 72 73 var cloudSpec simplestreams.CloudSpec 74 if inst, ok := cloudInst.(simplestreams.HasRegion); ok { 75 if cloudSpec, err = inst.Region(); err != nil { 76 return nil, err 77 } 78 } 79 // If only one of region or endpoint is provided, that is a problem. 80 if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") { 81 return nil, fmt.Errorf("cannot find tools without a complete cloud configuration") 82 } 83 84 if minorVersion >= 0 { 85 logger.Infof("reading tools with major.minor version %d.%d", majorVersion, minorVersion) 86 } else { 87 logger.Infof("reading tools with major version %d", majorVersion) 88 } 89 defer convertToolsError(&err) 90 // Construct a tools filter. 91 // Discard all that are known to be irrelevant. 92 if filter.Number != version.Zero { 93 logger.Infof("filtering tools by version: %s", filter.Number) 94 } 95 if filter.Series != "" { 96 logger.Infof("filtering tools by series: %s", filter.Series) 97 } 98 if filter.Arch != "" { 99 logger.Infof("filtering tools by architecture: %s", filter.Arch) 100 } 101 sources, err := GetMetadataSourcesWithRetries(cloudInst, allowRetry) 102 if err != nil { 103 return nil, err 104 } 105 return FindToolsForCloud(sources, cloudSpec, majorVersion, minorVersion, filter) 106 } 107 108 // FindToolsForCloud returns a List containing all tools with a given 109 // major.minor version number and cloudSpec, filtered by filter. 110 // If minorVersion = -1, then only majorVersion is considered. 111 // If no *available* tools have the supplied major.minor version number, or match the 112 // supplied filter, the function returns a *NotFoundError. 113 func FindToolsForCloud(sources []simplestreams.DataSource, cloudSpec simplestreams.CloudSpec, 114 majorVersion, minorVersion int, filter coretools.Filter) (list coretools.List, err error) { 115 116 toolsConstraint, err := makeToolsConstraint(cloudSpec, majorVersion, minorVersion, filter) 117 if err != nil { 118 return nil, err 119 } 120 toolsMetadata, err := Fetch(sources, simplestreams.DefaultIndexPath, toolsConstraint, false) 121 if err != nil { 122 if errors.IsNotFoundError(err) { 123 err = ErrNoTools 124 } 125 return nil, err 126 } 127 if len(toolsMetadata) == 0 { 128 return nil, coretools.ErrNoMatches 129 } 130 list = make(coretools.List, len(toolsMetadata)) 131 for i, metadata := range toolsMetadata { 132 list[i] = &coretools.Tools{ 133 Version: metadata.binary(), 134 URL: metadata.FullPath, 135 Size: metadata.Size, 136 SHA256: metadata.SHA256, 137 } 138 } 139 if filter.Series != "" { 140 if err := checkToolsSeries(list, filter.Series); err != nil { 141 return nil, err 142 } 143 } 144 return list, err 145 } 146 147 // The following allows FindTools, as called by FindBootstrapTools, to be patched for testing. 148 var bootstrapFindTools = FindTools 149 150 type findtoolsfunc func(environs.ConfigGetter, int, int, coretools.Filter, bool) (coretools.List, error) 151 152 func TestingPatchBootstrapFindTools(stub findtoolsfunc) func() { 153 origFunc := bootstrapFindTools 154 bootstrapFindTools = stub 155 return func() { 156 bootstrapFindTools = origFunc 157 } 158 } 159 160 // BootstrapToolsParams contains parameters for FindBootstrapTools 161 type BootstrapToolsParams struct { 162 Version *version.Number 163 Arch *string 164 Series string 165 AllowRetry bool 166 } 167 168 // FindBootstrapTools returns a ToolsList containing only those tools with 169 // which it would be reasonable to launch an environment's first machine, given the supplied constraints. 170 // If a specific agent version is not requested, all tools matching the current major.minor version are chosen. 171 func FindBootstrapTools(cloudInst environs.ConfigGetter, params BootstrapToolsParams) (list coretools.List, err error) { 172 // Construct a tools filter. 173 cfg := cloudInst.Config() 174 cliVersion := version.Current.Number 175 filter := coretools.Filter{ 176 Series: params.Series, 177 Arch: stringOrEmpty(params.Arch), 178 } 179 if params.Version != nil { 180 // If we already have an explicit agent version set, we're done. 181 filter.Number = *params.Version 182 return bootstrapFindTools(cloudInst, cliVersion.Major, cliVersion.Minor, filter, params.AllowRetry) 183 } 184 if dev := cliVersion.IsDev() || cfg.Development(); !dev { 185 logger.Infof("filtering tools by released version") 186 filter.Released = true 187 } 188 return bootstrapFindTools(cloudInst, cliVersion.Major, cliVersion.Minor, filter, params.AllowRetry) 189 } 190 191 func stringOrEmpty(pstr *string) string { 192 if pstr == nil { 193 return "" 194 } 195 return *pstr 196 } 197 198 // FindInstanceTools returns a ToolsList containing only those tools with which 199 // it would be reasonable to start a new instance, given the supplied series and arch. 200 func FindInstanceTools(cloudInst environs.ConfigGetter, 201 vers version.Number, series string, arch *string) (list coretools.List, err error) { 202 203 // Construct a tools filter. 204 // Discard all that are known to be irrelevant. 205 filter := coretools.Filter{ 206 Number: vers, 207 Series: series, 208 Arch: stringOrEmpty(arch), 209 } 210 return FindTools(cloudInst, vers.Major, vers.Minor, filter, DoNotAllowRetry) 211 } 212 213 // FindExactTools returns only the tools that match the supplied version. 214 func FindExactTools(cloudInst environs.ConfigGetter, 215 vers version.Number, series string, arch string) (t *coretools.Tools, err error) { 216 217 logger.Infof("finding exact version %s", vers) 218 // Construct a tools filter. 219 // Discard all that are known to be irrelevant. 220 filter := coretools.Filter{ 221 Number: vers, 222 Series: series, 223 Arch: arch, 224 } 225 availableTools, err := FindTools(cloudInst, vers.Major, vers.Minor, filter, DoNotAllowRetry) 226 if err != nil { 227 return nil, err 228 } 229 if len(availableTools) != 1 { 230 return nil, fmt.Errorf("expected one tools, got %d tools", len(availableTools)) 231 } 232 return availableTools[0], nil 233 } 234 235 // CheckToolsSeries verifies that all the given possible tools are for the 236 // given OS series. 237 func checkToolsSeries(toolsList coretools.List, series string) error { 238 toolsSeries := toolsList.AllSeries() 239 if len(toolsSeries) != 1 { 240 return fmt.Errorf("expected single series, got %v", toolsSeries) 241 } 242 if toolsSeries[0] != series { 243 return fmt.Errorf("tools mismatch: expected series %v, got %v", series, toolsSeries[0]) 244 } 245 return nil 246 } 247 248 func isToolsError(err error) bool { 249 switch err { 250 case ErrNoTools, coretools.ErrNoMatches: 251 return true 252 } 253 return false 254 } 255 256 func convertToolsError(err *error) { 257 if isToolsError(*err) { 258 *err = errors.NewNotFoundError(*err, "") 259 } 260 }