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