github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/azure/internal/imageutils/images.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package imageutils
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/to"
    13  	"github.com/Azure/azure-sdk-for-go/arm/compute"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/loggo"
    16  	"github.com/juju/utils/arch"
    17  	"github.com/juju/utils/os"
    18  	jujuseries "github.com/juju/utils/series"
    19  
    20  	"github.com/juju/juju/environs/imagemetadata"
    21  	"github.com/juju/juju/environs/instances"
    22  )
    23  
    24  var logger = loggo.GetLogger("juju.provider.azure")
    25  
    26  const (
    27  	centOSPublisher = "OpenLogic"
    28  	centOSOffering  = "CentOS"
    29  
    30  	ubuntuPublisher = "Canonical"
    31  	ubuntuOffering  = "UbuntuServer"
    32  
    33  	windowsServerPublisher = "MicrosoftWindowsServer"
    34  	windowsServerOffering  = "WindowsServer"
    35  
    36  	windowsPublisher = "MicrosoftVisualStudio"
    37  	windowsOffering  = "Windows"
    38  
    39  	dailyStream = "daily"
    40  )
    41  
    42  // SeriesImage gets an instances.Image for the specified series, image stream
    43  // and location. The resulting Image's ID is in the URN format expected by
    44  // Azure Resource Manager.
    45  //
    46  // For Ubuntu, we query the SKUs to determine the most recent point release
    47  // for a series.
    48  func SeriesImage(
    49  	series, stream, location string,
    50  	client compute.VirtualMachineImagesClient,
    51  ) (*instances.Image, error) {
    52  	seriesOS, err := jujuseries.GetOSFromSeries(series)
    53  	if err != nil {
    54  		return nil, errors.Trace(err)
    55  	}
    56  
    57  	var publisher, offering, sku string
    58  	switch seriesOS {
    59  	case os.Ubuntu:
    60  		publisher = ubuntuPublisher
    61  		offering = ubuntuOffering
    62  		sku, err = ubuntuSKU(series, stream, location, client)
    63  		if err != nil {
    64  			return nil, errors.Annotatef(err, "selecting SKU for %s", series)
    65  		}
    66  
    67  	case os.Windows:
    68  		switch series {
    69  		case "win81":
    70  			publisher = windowsPublisher
    71  			offering = windowsOffering
    72  			sku = "8.1-Enterprise-N"
    73  		case "win10":
    74  			publisher = windowsPublisher
    75  			offering = windowsOffering
    76  			sku = "10-Enterprise"
    77  		case "win2012":
    78  			publisher = windowsServerPublisher
    79  			offering = windowsServerOffering
    80  			sku = "2012-Datacenter"
    81  		case "win2012r2":
    82  			publisher = windowsServerPublisher
    83  			offering = windowsServerOffering
    84  			sku = "2012-R2-Datacenter"
    85  		default:
    86  			return nil, errors.NotSupportedf("deploying %s", series)
    87  		}
    88  
    89  	case os.CentOS:
    90  		publisher = centOSPublisher
    91  		offering = centOSOffering
    92  		switch series {
    93  		case "centos7":
    94  			sku = "7.1"
    95  		default:
    96  			return nil, errors.NotSupportedf("deploying %s", series)
    97  		}
    98  
    99  	default:
   100  		// TODO(axw) CentOS
   101  		return nil, errors.NotSupportedf("deploying %s", seriesOS)
   102  	}
   103  
   104  	return &instances.Image{
   105  		Id:       fmt.Sprintf("%s:%s:%s:latest", publisher, offering, sku),
   106  		Arch:     arch.AMD64,
   107  		VirtType: "Hyper-V",
   108  	}, nil
   109  }
   110  
   111  // ubuntuSKU returns the best SKU for the Canonical:UbuntuServer offering,
   112  // matching the given series.
   113  func ubuntuSKU(series, stream, location string, client compute.VirtualMachineImagesClient) (string, error) {
   114  	seriesVersion, err := jujuseries.SeriesVersion(series)
   115  	if err != nil {
   116  		return "", errors.Trace(err)
   117  	}
   118  	logger.Debugf("listing SKUs: Location=%s, Publisher=%s, Offer=%s", location, ubuntuPublisher, ubuntuOffering)
   119  	result, err := client.ListSkus(location, ubuntuPublisher, ubuntuOffering)
   120  	if err != nil {
   121  		return "", errors.Annotate(err, "listing Ubuntu SKUs")
   122  	}
   123  	if result.Value == nil || len(*result.Value) == 0 {
   124  		return "", errors.NotFoundf("Ubuntu SKUs")
   125  	}
   126  	skuNamesByVersion := make(map[ubuntuVersion]string)
   127  	var versions ubuntuVersions
   128  	for _, result := range *result.Value {
   129  		skuName := to.String(result.Name)
   130  		if !strings.HasPrefix(skuName, seriesVersion) {
   131  			logger.Debugf("ignoring SKU %q (does not match series %q)", skuName, series)
   132  			continue
   133  		}
   134  		version, tag, err := parseUbuntuSKU(skuName)
   135  		if err != nil {
   136  			logger.Errorf("ignoring SKU %q (failed to parse: %s)", skuName, err)
   137  			continue
   138  		}
   139  		var skuStream string
   140  		switch tag {
   141  		case "", "LTS":
   142  			skuStream = imagemetadata.ReleasedStream
   143  		case "DAILY", "DAILY-LTS":
   144  			skuStream = dailyStream
   145  		}
   146  		if skuStream == "" || skuStream != stream {
   147  			logger.Debugf("ignoring SKU %q (not in %q stream)", skuName, stream)
   148  			continue
   149  		}
   150  		skuNamesByVersion[version] = skuName
   151  		versions = append(versions, version)
   152  	}
   153  	if len(versions) == 0 {
   154  		return "", errors.NotFoundf("Ubuntu SKUs for %s stream", stream)
   155  	}
   156  	sort.Sort(versions)
   157  	bestVersion := versions[len(versions)-1]
   158  	return skuNamesByVersion[bestVersion], nil
   159  }
   160  
   161  type ubuntuVersion struct {
   162  	Year  int
   163  	Month int
   164  	Point int
   165  }
   166  
   167  // parseUbuntuSKU splits an UbuntuServer SKU into its
   168  // version ("14.04.3") and tag ("LTS") parts.
   169  func parseUbuntuSKU(sku string) (ubuntuVersion, string, error) {
   170  	var version ubuntuVersion
   171  	var tag string
   172  	var err error
   173  	parts := strings.Split(sku, "-")
   174  	if len(parts) > 1 {
   175  		tag = parts[1]
   176  	}
   177  	parts = strings.SplitN(parts[0], ".", 3)
   178  	version.Year, err = strconv.Atoi(parts[0])
   179  	if err != nil {
   180  		return ubuntuVersion{}, "", errors.Trace(err)
   181  	}
   182  	version.Month, err = strconv.Atoi(parts[1])
   183  	if err != nil {
   184  		return ubuntuVersion{}, "", errors.Trace(err)
   185  	}
   186  	if len(parts) > 2 {
   187  		version.Point, err = strconv.Atoi(parts[2])
   188  		if err != nil {
   189  			return ubuntuVersion{}, "", errors.Trace(err)
   190  		}
   191  	}
   192  	return version, tag, nil
   193  }
   194  
   195  type ubuntuVersions []ubuntuVersion
   196  
   197  func (v ubuntuVersions) Len() int {
   198  	return len(v)
   199  }
   200  
   201  func (v ubuntuVersions) Swap(i, j int) {
   202  	v[i], v[j] = v[j], v[i]
   203  }
   204  
   205  func (v ubuntuVersions) Less(i, j int) bool {
   206  	vi, vj := v[i], v[j]
   207  	if vi.Year < vj.Year {
   208  		return true
   209  	} else if vi.Year > vj.Year {
   210  		return false
   211  	}
   212  	if vi.Month < vj.Month {
   213  		return true
   214  	} else if vi.Month > vj.Month {
   215  		return false
   216  	}
   217  	return vi.Point < vj.Point
   218  }