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