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  }