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 }