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