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