Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
414 lines (365 sloc) 11.3 KB
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package series
import (
"sort"
"sync"
"github.com/juju/errors"
"github.com/juju/loggo"
"github.com/juju/utils/os"
)
var (
// TODO(katco): Remove globals (lp:1633571)
logger = loggo.GetLogger("juju.juju.series")
)
type unknownOSForSeriesError string
func (e unknownOSForSeriesError) Error() string {
return `unknown OS for series: "` + string(e) + `"`
}
// IsUnknownOSForSeriesError returns true if err is of type unknownOSForSeriesError.
func IsUnknownOSForSeriesError(err error) bool {
_, ok := errors.Cause(err).(unknownOSForSeriesError)
return ok
}
type unknownSeriesVersionError string
func (e unknownSeriesVersionError) Error() string {
return `unknown version for series: "` + string(e) + `"`
}
// IsUnknownSeriesVersionError returns true if err is of type unknownSeriesVersionError.
func IsUnknownSeriesVersionError(err error) bool {
_, ok := errors.Cause(err).(unknownSeriesVersionError)
return ok
}
type unknownVersionSeriesError string
func (e unknownVersionSeriesError) Error() string {
return `unknown series for version: "` + string(e) + `"`
}
// IsUnknownVersionSeriesError returns true if err is of type unknownVersionSeriesError.
func IsUnknownVersionSeriesError(err error) bool {
_, ok := errors.Cause(err).(unknownVersionSeriesError)
return ok
}
// seriesVersions provides a mapping between series names and versions.
// The values here are current as of the time of writing. On Ubuntu systems, we update
// these values from /usr/share/distro-info/ubuntu.csv to ensure we have the latest values.
// On non-Ubuntu systems, these values provide a nice fallback option.
// Exported so tests can change the values to ensure the distro-info lookup works.
var seriesVersions = map[string]string{
"precise": "12.04",
"quantal": "12.10",
"raring": "13.04",
"saucy": "13.10",
"trusty": "14.04",
"utopic": "14.10",
"vivid": "15.04",
"wily": "15.10",
"xenial": "16.04",
"yakkety": "16.10",
"zesty": "17.04",
"artful": "17.10",
"bionic": "18.04",
"win2008r2": "win2008r2",
"win2012hvr2": "win2012hvr2",
"win2012hv": "win2012hv",
"win2012r2": "win2012r2",
"win2012": "win2012",
"win2016": "win2016",
"win2016hv": "win2016hv",
"win2016nano": "win2016nano",
"win7": "win7",
"win8": "win8",
"win81": "win81",
"win10": "win10",
"centos7": "centos7",
"opensuseleap": "opensuse42",
genericLinuxSeries: genericLinuxVersion,
}
// versionSeries provides a mapping between versions and series names.
var versionSeries = reverseSeriesVersion()
var centosSeries = map[string]string{
"centos7": "centos7",
}
var opensuseSeries = map[string]string{
"opensuseleap": "opensuse42",
}
var ubuntuSeries = map[string]string{
"precise": "12.04",
"quantal": "12.10",
"raring": "13.04",
"saucy": "13.10",
"trusty": "14.04",
"utopic": "14.10",
"vivid": "15.04",
"wily": "15.10",
"xenial": "16.04",
"yakkety": "16.10",
"zesty": "17.04",
"artful": "17.10",
"bionic": "18.04",
}
// ubuntuLTS provides a lookup for current LTS series. Like seriesVersions,
// the values here are current at the time of writing. On Ubuntu systems this
// map is updated by updateDistroInfo, using data from
// /usr/share/distro-info/ubuntu.csv to ensure we have the latest values. On
// non-Ubuntu systems, these values provide a nice fallback option.
var ubuntuLTS = map[string]bool{
"precise": true,
"trusty": true,
"xenial": true,
// TODO(rogpeppe) add bionic when it's released.
}
// Windows versions come in various flavors:
// Standard, Datacenter, etc. We use string prefix match them to one
// of the following. Specify the longest name in a particular series first
// For example, if we have "Win 2012" and "Win 2012 R2", we specify "Win 2012 R2" first.
// We need to make sure we manually update this list with each new windows release.
var windowsVersionMatchOrder = []string{
"Hyper-V Server 2012 R2",
"Hyper-V Server 2012",
"Windows Server 2008 R2",
"Windows Server 2012 R2",
"Windows Server 2012",
"Hyper-V Server 2016",
"Windows Server 2016",
"Windows Storage Server 2012 R2",
"Windows Storage Server 2012",
"Windows Storage Server 2016",
"Windows 7",
"Windows 8.1",
"Windows 8",
"Windows 10",
}
// windowsVersions is a mapping consisting of the output from
// the following WMI query: (gwmi Win32_OperatingSystem).Name
var windowsVersions = map[string]string{
"Hyper-V Server 2012 R2": "win2012hvr2",
"Hyper-V Server 2012": "win2012hv",
"Windows Server 2008 R2": "win2008r2",
"Windows Server 2012 R2": "win2012r2",
"Windows Server 2012": "win2012",
"Hyper-V Server 2016": "win2016hv",
"Windows Server 2016": "win2016",
"Windows Storage Server 2012 R2": "win2012r2",
"Windows Storage Server 2012": "win2012",
"Windows Storage Server 2016": "win2016",
"Windows 7": "win7",
"Windows 8.1": "win81",
"Windows 8": "win8",
"Windows 10": "win10",
}
// windowsNanoVersions is a mapping from the product name
// stored in registry to a juju defined nano-series
// On the nano version so far the product name actually
// is identical to the correspondent main windows version
// and the information about it being nano is stored in
// a different place.
var windowsNanoVersions = map[string]string{
"Windows Server 2016": "win2016nano",
}
// WindowsVersions returns all windows versions as a map
func WindowsVersions() map[string]string {
save := make(map[string]string)
for i, val := range windowsVersions {
save[i] = val
}
for i, val := range windowsNanoVersions {
save[i] = val
}
return save
}
// IsWindowsNano tells us whether the provided series is a
// nano series. It may seem futile at this point, but more
// nano series will come up with time.
// This is here and not in a windows specific package
// because we might want to take decisions dependant on
// whether we have a nano series or not in more general code.
func IsWindowsNano(series string) bool {
for _, val := range windowsNanoVersions {
if val == series {
return true
}
}
return false
}
// GetOSFromSeries will return the operating system based
// on the series that is passed to it
func GetOSFromSeries(series string) (os.OSType, error) {
if series == "" {
return os.Unknown, errors.NotValidf("series %q", series)
}
osType, err := getOSFromSeries(series)
if err == nil {
return osType, nil
}
updateSeriesVersionsOnce()
return getOSFromSeries(series)
}
func getOSFromSeries(series string) (os.OSType, error) {
if _, ok := ubuntuSeries[series]; ok {
return os.Ubuntu, nil
}
if _, ok := centosSeries[series]; ok {
return os.CentOS, nil
}
if _, ok := opensuseSeries[series]; ok {
return os.OpenSUSE, nil
}
if series == genericLinuxSeries {
return os.GenericLinux, nil
}
for _, val := range windowsVersions {
if val == series {
return os.Windows, nil
}
}
for _, val := range windowsNanoVersions {
if val == series {
return os.Windows, nil
}
}
for _, val := range macOSXSeries {
if val == series {
return os.OSX, nil
}
}
return os.Unknown, errors.Trace(unknownOSForSeriesError(series))
}
var (
seriesVersionsMutex sync.Mutex
)
// SeriesVersion returns the version for the specified series.
func SeriesVersion(series string) (string, error) {
if series == "" {
return "", errors.Trace(unknownSeriesVersionError(""))
}
seriesVersionsMutex.Lock()
defer seriesVersionsMutex.Unlock()
if vers, ok := seriesVersions[series]; ok {
return vers, nil
}
updateSeriesVersionsOnce()
if vers, ok := seriesVersions[series]; ok {
return vers, nil
}
return "", errors.Trace(unknownSeriesVersionError(series))
}
// VersionSeries returns the series (e.g.trusty) for the specified version (e.g. 14.04).
func VersionSeries(version string) (string, error) {
if version == "" {
return "", errors.Trace(unknownVersionSeriesError(""))
}
seriesVersionsMutex.Lock()
defer seriesVersionsMutex.Unlock()
if series, ok := versionSeries[version]; ok {
return series, nil
}
updateSeriesVersionsOnce()
if series, ok := versionSeries[version]; ok {
return series, nil
}
return "", errors.Trace(unknownVersionSeriesError(version))
}
// SupportedLts are the current supported LTS series in ascending order.
func SupportedLts() []string {
seriesVersionsMutex.Lock()
defer seriesVersionsMutex.Unlock()
updateSeriesVersionsOnce()
versions := []string{}
for k := range ubuntuLTS {
versions = append(versions, ubuntuSeries[k])
}
sort.Strings(versions)
sorted := []string{}
for _, v := range versions {
sorted = append(sorted, versionSeries[v])
}
return sorted
}
// latestLtsSeries is used to ensure we only do
// the work to determine the latest lts series once.
var latestLtsSeries string
// LatestLts returns the Latest LTS Series found in distro-info
func LatestLts() string {
if latestLtsSeries != "" {
return latestLtsSeries
}
seriesVersionsMutex.Lock()
defer seriesVersionsMutex.Unlock()
updateSeriesVersionsOnce()
var latest string
for k := range ubuntuLTS {
if ubuntuSeries[k] > ubuntuSeries[latest] {
latest = k
}
}
latestLtsSeries = latest
return latest
}
// SetLatestLtsForTesting is provided to allow tests to override the lts series
// used and decouple the tests from the host by avoiding calling out to
// distro-info. It returns the previous setting so that it may be set back to
// the original value by the caller.
func SetLatestLtsForTesting(series string) string {
old := latestLtsSeries
latestLtsSeries = series
return old
}
func updateVersionSeries() {
versionSeries = reverseSeriesVersion()
}
// reverseSeriesVersion returns reverse of seriesVersion map,
// keyed on versions with series as values.
func reverseSeriesVersion() map[string]string {
reverse := make(map[string]string, len(seriesVersions))
for k, v := range seriesVersions {
reverse[v] = k
}
return reverse
}
// SupportedSeries returns the series on which we can run Juju workloads.
func SupportedSeries() []string {
seriesVersionsMutex.Lock()
defer seriesVersionsMutex.Unlock()
updateSeriesVersionsOnce()
var series []string
for s := range seriesVersions {
series = append(series, s)
}
return series
}
// OSSupportedSeries returns the series of the specified OS on which we
// can run Juju workloads.
func OSSupportedSeries(os os.OSType) []string {
var osSeries []string
for _, series := range SupportedSeries() {
seriesOS, err := GetOSFromSeries(series)
if err != nil || seriesOS != os {
continue
}
osSeries = append(osSeries, series)
}
return osSeries
}
// UpdateSeriesVersions forces an update of the series versions by querying
// distro-info if possible.
func UpdateSeriesVersions() error {
seriesVersionsMutex.Lock()
defer seriesVersionsMutex.Unlock()
err := updateLocalSeriesVersions()
if err != nil {
return err
}
updateVersionSeries()
latestLtsSeries = ""
return nil
}
var updatedseriesVersions bool
func updateSeriesVersionsOnce() {
if !updatedseriesVersions {
if err := updateLocalSeriesVersions(); err != nil {
logger.Warningf("failed to update distro info: %v", err)
}
updateVersionSeries()
updatedseriesVersions = true
}
}