github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/version/supportedseries.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package version 5 6 import ( 7 "bufio" 8 "fmt" 9 "io" 10 "os" 11 "strings" 12 "sync" 13 14 "github.com/juju/errors" 15 ) 16 17 type OSType int 18 19 const ( 20 Unknown OSType = iota 21 Ubuntu 22 Windows 23 OSX 24 CentOS 25 Arch 26 ) 27 28 func (t OSType) String() string { 29 switch t { 30 case Ubuntu: 31 return "Ubuntu" 32 case Windows: 33 return "Windows" 34 case OSX: 35 return "OSX" 36 case CentOS: 37 return "CentOS" 38 case Arch: 39 return "Arch" 40 } 41 return "Unknown" 42 } 43 44 type unknownOSForSeriesError string 45 46 func (e unknownOSForSeriesError) Error() string { 47 return `unknown OS for series: "` + string(e) + `"` 48 } 49 50 // IsUnknownOSForSeriesError returns true if err is of type unknownOSForSeriesError. 51 func IsUnknownOSForSeriesError(err error) bool { 52 _, ok := errors.Cause(err).(unknownOSForSeriesError) 53 return ok 54 } 55 56 type unknownSeriesVersionError string 57 58 func (e unknownSeriesVersionError) Error() string { 59 return `unknown version for series: "` + string(e) + `"` 60 } 61 62 // IsUnknownSeriesVersionError returns true if err is of type unknownSeriesVersionError. 63 func IsUnknownSeriesVersionError(err error) bool { 64 _, ok := errors.Cause(err).(unknownSeriesVersionError) 65 return ok 66 } 67 68 var defaultVersionIDs = map[string]string{ 69 "arch": "rolling", 70 } 71 72 // seriesVersions provides a mapping between series names and versions. 73 // The values here are current as of the time of writing. On Ubuntu systems, we update 74 // these values from /usr/share/distro-info/ubuntu.csv to ensure we have the latest values. 75 // On non-Ubuntu systems, these values provide a nice fallback option. 76 // Exported so tests can change the values to ensure the distro-info lookup works. 77 var seriesVersions = map[string]string{ 78 "precise": "12.04", 79 "quantal": "12.10", 80 "raring": "13.04", 81 "saucy": "13.10", 82 "trusty": "14.04", 83 "utopic": "14.10", 84 "vivid": "15.04", 85 "win2012hvr2": "win2012hvr2", 86 "win2012hv": "win2012hv", 87 "win2012r2": "win2012r2", 88 "win2012": "win2012", 89 "win7": "win7", 90 "win8": "win8", 91 "win81": "win81", 92 "win10": "win10", 93 "centos7": "centos7", 94 "arch": "rolling", 95 } 96 97 var centosSeries = map[string]string{ 98 "centos7": "centos7", 99 } 100 101 var archSeries = map[string]string{ 102 "arch": "rolling", 103 } 104 105 var ubuntuSeries = map[string]string{ 106 "precise": "12.04", 107 "quantal": "12.10", 108 "raring": "13.04", 109 "saucy": "13.10", 110 "trusty": "14.04", 111 "utopic": "14.10", 112 "vivid": "15.04", 113 } 114 115 // Windows versions come in various flavors: 116 // Standard, Datacenter, etc. We use string prefix match them to one 117 // of the following. Specify the longest name in a particular series first 118 // For example, if we have "Win 2012" and "Win 2012 R2", we specify "Win 2012 R2" first. 119 // We need to make sure we manually update this list with each new windows release. 120 var windowsVersionMatchOrder = []string{ 121 "Hyper-V Server 2012 R2", 122 "Hyper-V Server 2012", 123 "Windows Server 2012 R2", 124 "Windows Server 2012", 125 "Windows Storage Server 2012 R2", 126 "Windows Storage Server 2012", 127 "Windows 7", 128 "Windows 8.1", 129 "Windows 8", 130 "Windows 10", 131 } 132 133 // windowsVersions is a mapping consisting of the output from 134 // the following WMI query: (gwmi Win32_OperatingSystem).Name 135 var windowsVersions = map[string]string{ 136 "Hyper-V Server 2012 R2": "win2012hvr2", 137 "Hyper-V Server 2012": "win2012hv", 138 "Windows Server 2012 R2": "win2012r2", 139 "Windows Server 2012": "win2012", 140 "Windows Storage Server 2012 R2": "win2012r2", 141 "Windows Storage Server 2012": "win2012", 142 "Windows 7": "win7", 143 "Windows 8.1": "win81", 144 "Windows 8": "win8", 145 "Windows 10": "win10", 146 } 147 148 var distroInfo = "/usr/share/distro-info/ubuntu.csv" 149 150 // GetOSFromSeries will return the operating system based 151 // on the series that is passed to it 152 func GetOSFromSeries(series string) (OSType, error) { 153 if series == "" { 154 return Unknown, errors.NotValidf("series %q", series) 155 } 156 if _, ok := ubuntuSeries[series]; ok { 157 return Ubuntu, nil 158 } 159 if _, ok := centosSeries[series]; ok { 160 return CentOS, nil 161 } 162 if _, ok := archSeries[series]; ok { 163 return Arch, nil 164 } 165 for _, val := range windowsVersions { 166 if val == series { 167 return Windows, nil 168 } 169 } 170 for _, val := range macOSXSeries { 171 if val == series { 172 return OSX, nil 173 } 174 } 175 return Unknown, errors.Trace(unknownOSForSeriesError(series)) 176 } 177 178 var ( 179 seriesVersionsMutex sync.Mutex 180 updatedseriesVersions bool 181 ) 182 183 // SeriesVersion returns the version for the specified series. 184 func SeriesVersion(series string) (string, error) { 185 if series == "" { 186 panic("cannot pass empty series to SeriesVersion()") 187 } 188 seriesVersionsMutex.Lock() 189 defer seriesVersionsMutex.Unlock() 190 if vers, ok := seriesVersions[series]; ok { 191 return vers, nil 192 } 193 updateSeriesVersions() 194 if vers, ok := seriesVersions[series]; ok { 195 return vers, nil 196 } 197 198 return "", errors.Trace(unknownSeriesVersionError(series)) 199 } 200 201 // SupportedSeries returns the series on which we can run Juju workloads. 202 func SupportedSeries() []string { 203 seriesVersionsMutex.Lock() 204 defer seriesVersionsMutex.Unlock() 205 updateSeriesVersions() 206 var series []string 207 for s := range seriesVersions { 208 series = append(series, s) 209 } 210 return series 211 } 212 213 // OSSupportedSeries returns the series of the specified OS on which we 214 // can run Juju workloads. 215 func OSSupportedSeries(os OSType) []string { 216 var osSeries []string 217 for _, series := range SupportedSeries() { 218 seriesOS, err := GetOSFromSeries(series) 219 if err != nil || seriesOS != os { 220 continue 221 } 222 osSeries = append(osSeries, series) 223 } 224 return osSeries 225 } 226 227 func updateSeriesVersions() { 228 if !updatedseriesVersions { 229 err := updateDistroInfo() 230 if err != nil { 231 logger.Warningf("failed to update distro info: %v", err) 232 } 233 updatedseriesVersions = true 234 } 235 } 236 237 // updateDistroInfo updates seriesVersions from /usr/share/distro-info/ubuntu.csv if possible.. 238 func updateDistroInfo() error { 239 // We need to find the series version eg 12.04 from the series eg precise. Use the information found in 240 // /usr/share/distro-info/ubuntu.csv provided by distro-info-data package. 241 f, err := os.Open(distroInfo) 242 if err != nil { 243 // On non-Ubuntu systems this file won't exist but that's expected. 244 return nil 245 } 246 defer f.Close() 247 bufRdr := bufio.NewReader(f) 248 // Only find info for precise or later. 249 // TODO: only add in series that are supported (i.e. before end of life) 250 preciseOrLaterFound := false 251 for { 252 line, err := bufRdr.ReadString('\n') 253 if err == io.EOF { 254 break 255 } 256 if err != nil { 257 return fmt.Errorf("reading distro info file file: %v", err) 258 } 259 // lines are of the form: "12.04 LTS,Precise Pangolin,precise,2011-10-13,2012-04-26,2017-04-26" 260 parts := strings.Split(line, ",") 261 // Ignore any malformed lines. 262 if len(parts) < 3 { 263 continue 264 } 265 series := parts[2] 266 if series == "precise" { 267 preciseOrLaterFound = true 268 } 269 if series != "precise" && !preciseOrLaterFound { 270 continue 271 } 272 // the numeric version may contain a LTS moniker so strip that out. 273 seriesInfo := strings.Split(parts[0], " ") 274 seriesVersions[series] = seriesInfo[0] 275 ubuntuSeries[series] = seriesInfo[0] 276 } 277 return nil 278 }