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 }