github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/azure/storage.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure 5 6 import ( 7 "fmt" 8 "net/http" 9 "strings" 10 11 "github.com/Azure/azure-sdk-for-go/arm/compute" 12 armstorage "github.com/Azure/azure-sdk-for-go/arm/storage" 13 azurestorage "github.com/Azure/azure-sdk-for-go/storage" 14 "github.com/Azure/go-autorest/autorest" 15 "github.com/Azure/go-autorest/autorest/to" 16 "github.com/juju/errors" 17 "github.com/juju/schema" 18 "gopkg.in/juju/names.v2" 19 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/provider/azure/internal/armtemplates" 22 internalazurestorage "github.com/juju/juju/provider/azure/internal/azurestorage" 23 "github.com/juju/juju/storage" 24 ) 25 26 const ( 27 azureStorageProviderType = "azure" 28 29 // volumeSizeMaxGiB is the maximum disk size (in gibibytes) for Azure disks. 30 // 31 // See: https://azure.microsoft.com/en-gb/documentation/articles/virtual-machines-disks-vhds/ 32 volumeSizeMaxGiB = 1023 33 34 // osDiskVHDContainer is the name of the blob container for VHDs 35 // backing OS disks. 36 osDiskVHDContainer = "osvhds" 37 38 // dataDiskVHDContainer is the name of the blob container for VHDs 39 // backing data disks. 40 dataDiskVHDContainer = "datavhds" 41 42 // vhdExtension is the filename extension we give to VHDs we create. 43 vhdExtension = ".vhd" 44 ) 45 46 // StorageProviderTypes implements storage.ProviderRegistry. 47 func (env *azureEnviron) StorageProviderTypes() ([]storage.ProviderType, error) { 48 return []storage.ProviderType{azureStorageProviderType}, nil 49 } 50 51 // StorageProvider implements storage.ProviderRegistry. 52 func (env *azureEnviron) StorageProvider(t storage.ProviderType) (storage.Provider, error) { 53 if t == azureStorageProviderType { 54 return &azureStorageProvider{env}, nil 55 } 56 return nil, errors.NotFoundf("storage provider %q", t) 57 } 58 59 // azureStorageProvider is a storage provider for Azure disks. 60 type azureStorageProvider struct { 61 env *azureEnviron 62 } 63 64 var _ storage.Provider = (*azureStorageProvider)(nil) 65 66 var azureStorageConfigFields = schema.Fields{} 67 68 var azureStorageConfigChecker = schema.FieldMap( 69 azureStorageConfigFields, 70 schema.Defaults{}, 71 ) 72 73 type azureStorageConfig struct { 74 } 75 76 func newAzureStorageConfig(attrs map[string]interface{}) (*azureStorageConfig, error) { 77 _, err := azureStorageConfigChecker.Coerce(attrs, nil) 78 if err != nil { 79 return nil, errors.Annotate(err, "validating Azure storage config") 80 } 81 azureStorageConfig := &azureStorageConfig{} 82 return azureStorageConfig, nil 83 } 84 85 // ValidateConfig is part of the Provider interface. 86 func (e *azureStorageProvider) ValidateConfig(cfg *storage.Config) error { 87 _, err := newAzureStorageConfig(cfg.Attrs()) 88 return errors.Trace(err) 89 } 90 91 // Supports is part of the Provider interface. 92 func (e *azureStorageProvider) Supports(k storage.StorageKind) bool { 93 return k == storage.StorageKindBlock 94 } 95 96 // Scope is part of the Provider interface. 97 func (e *azureStorageProvider) Scope() storage.Scope { 98 return storage.ScopeEnviron 99 } 100 101 // Dynamic is part of the Provider interface. 102 func (e *azureStorageProvider) Dynamic() bool { 103 return true 104 } 105 106 // DefaultPools is part of the Provider interface. 107 func (e *azureStorageProvider) DefaultPools() []*storage.Config { 108 return nil 109 } 110 111 // VolumeSource is part of the Provider interface. 112 func (e *azureStorageProvider) VolumeSource(cfg *storage.Config) (storage.VolumeSource, error) { 113 if err := e.ValidateConfig(cfg); err != nil { 114 return nil, errors.Trace(err) 115 } 116 return &azureVolumeSource{e.env}, nil 117 } 118 119 // FilesystemSource is part of the Provider interface. 120 func (e *azureStorageProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) { 121 return nil, errors.NotSupportedf("filesystems") 122 } 123 124 type azureVolumeSource struct { 125 env *azureEnviron 126 } 127 128 // CreateVolumes is specified on the storage.VolumeSource interface. 129 func (v *azureVolumeSource) CreateVolumes(params []storage.VolumeParams) (_ []storage.CreateVolumesResult, err error) { 130 131 // First, validate the params before we use them. 132 results := make([]storage.CreateVolumesResult, len(params)) 133 var instanceIds []instance.Id 134 for i, p := range params { 135 if err := v.ValidateVolumeParams(p); err != nil { 136 results[i].Error = err 137 continue 138 } 139 instanceIds = append(instanceIds, p.Attachment.InstanceId) 140 } 141 if len(instanceIds) == 0 { 142 return results, nil 143 } 144 virtualMachines, err := v.virtualMachines(instanceIds) 145 if err != nil { 146 return nil, errors.Annotate(err, "getting virtual machines") 147 } 148 storageAccount, err := v.env.getStorageAccount(false) 149 if err != nil { 150 return nil, errors.Trace(err) 151 } 152 153 // Update VirtualMachine objects in-memory, 154 // and then perform the updates all at once. 155 for i, p := range params { 156 if results[i].Error != nil { 157 continue 158 } 159 vm, ok := virtualMachines[p.Attachment.InstanceId] 160 if !ok { 161 continue 162 } 163 if vm.err != nil { 164 results[i].Error = vm.err 165 continue 166 } 167 volume, volumeAttachment, err := v.createVolume( 168 vm.vm, p, storageAccount, 169 ) 170 if err != nil { 171 results[i].Error = err 172 vm.err = err 173 continue 174 } 175 results[i].Volume = volume 176 results[i].VolumeAttachment = volumeAttachment 177 } 178 179 updateResults, err := v.updateVirtualMachines(virtualMachines, instanceIds) 180 if err != nil { 181 return nil, errors.Annotate(err, "updating virtual machines") 182 } 183 for i, err := range updateResults { 184 if results[i].Error != nil || err == nil { 185 continue 186 } 187 results[i].Error = err 188 results[i].Volume = nil 189 results[i].VolumeAttachment = nil 190 } 191 return results, nil 192 } 193 194 // createVolume updates the provided VirtualMachine's StorageProfile with the 195 // parameters for creating a new data disk. We don't actually interact with 196 // the Azure API until after all changes to the VirtualMachine are made. 197 func (v *azureVolumeSource) createVolume( 198 vm *compute.VirtualMachine, 199 p storage.VolumeParams, 200 storageAccount *armstorage.Account, 201 ) (*storage.Volume, *storage.VolumeAttachment, error) { 202 203 lun, err := nextAvailableLUN(vm) 204 if err != nil { 205 return nil, nil, errors.Annotate(err, "choosing LUN") 206 } 207 208 dataDisksRoot := dataDiskVhdRoot(storageAccount) 209 dataDiskName := p.Tag.String() 210 vhdURI := dataDisksRoot + dataDiskName + vhdExtension 211 212 sizeInGib := mibToGib(p.Size) 213 dataDisk := compute.DataDisk{ 214 Lun: to.Int32Ptr(lun), 215 DiskSizeGB: to.Int32Ptr(int32(sizeInGib)), 216 Name: to.StringPtr(dataDiskName), 217 Vhd: &compute.VirtualHardDisk{to.StringPtr(vhdURI)}, 218 Caching: compute.ReadWrite, 219 CreateOption: compute.Empty, 220 } 221 222 var dataDisks []compute.DataDisk 223 if vm.Properties.StorageProfile.DataDisks != nil { 224 dataDisks = *vm.Properties.StorageProfile.DataDisks 225 } 226 dataDisks = append(dataDisks, dataDisk) 227 vm.Properties.StorageProfile.DataDisks = &dataDisks 228 229 // Data disks associate VHDs to machines. In Juju's storage model, 230 // the VHD is the volume and the disk is the volume attachment. 231 volume := storage.Volume{ 232 p.Tag, 233 storage.VolumeInfo{ 234 VolumeId: dataDiskName, 235 Size: gibToMib(sizeInGib), 236 // We don't currently support persistent volumes in 237 // Azure, as it requires removal of "comp=media" when 238 // deleting VMs, complicating cleanup. 239 Persistent: true, 240 }, 241 } 242 volumeAttachment := storage.VolumeAttachment{ 243 p.Tag, 244 p.Attachment.Machine, 245 storage.VolumeAttachmentInfo{ 246 BusAddress: diskBusAddress(lun), 247 }, 248 } 249 return &volume, &volumeAttachment, nil 250 } 251 252 // ListVolumes is specified on the storage.VolumeSource interface. 253 func (v *azureVolumeSource) ListVolumes() ([]string, error) { 254 blobs, err := v.listBlobs() 255 if err != nil { 256 return nil, errors.Annotate(err, "listing volumes") 257 } 258 volumeIds := make([]string, 0, len(blobs)) 259 for _, blob := range blobs { 260 volumeId, ok := blobVolumeId(blob) 261 if !ok { 262 continue 263 } 264 volumeIds = append(volumeIds, volumeId) 265 } 266 return volumeIds, nil 267 } 268 269 // listBlobs returns a list of blobs in the data-disk container. 270 func (v *azureVolumeSource) listBlobs() ([]azurestorage.Blob, error) { 271 client, err := v.env.getStorageClient() 272 if err != nil { 273 return nil, errors.Trace(err) 274 } 275 blobsClient := client.GetBlobService() 276 // TODO(axw) handle pagination 277 // TODO(axw) consider taking a set of IDs and computing the 278 // longest common prefix to pass in the parameters 279 response, err := blobsClient.ListBlobs( 280 dataDiskVHDContainer, azurestorage.ListBlobsParameters{}, 281 ) 282 if err != nil { 283 if err, ok := err.(azurestorage.AzureStorageServiceError); ok { 284 switch err.Code { 285 case "ContainerNotFound": 286 return nil, nil 287 } 288 } 289 return nil, errors.Annotate(err, "listing blobs") 290 } 291 return response.Blobs, nil 292 } 293 294 // DescribeVolumes is specified on the storage.VolumeSource interface. 295 func (v *azureVolumeSource) DescribeVolumes(volumeIds []string) ([]storage.DescribeVolumesResult, error) { 296 blobs, err := v.listBlobs() 297 if err != nil { 298 return nil, errors.Annotate(err, "listing volumes") 299 } 300 301 byVolumeId := make(map[string]azurestorage.Blob) 302 for _, blob := range blobs { 303 volumeId, ok := blobVolumeId(blob) 304 if !ok { 305 continue 306 } 307 byVolumeId[volumeId] = blob 308 } 309 310 results := make([]storage.DescribeVolumesResult, len(volumeIds)) 311 for i, volumeId := range volumeIds { 312 blob, ok := byVolumeId[volumeId] 313 if !ok { 314 results[i].Error = errors.NotFoundf("%s", volumeId) 315 continue 316 } 317 sizeInMib := blob.Properties.ContentLength / (1024 * 1024) 318 results[i].VolumeInfo = &storage.VolumeInfo{ 319 VolumeId: volumeId, 320 Size: uint64(sizeInMib), 321 Persistent: true, 322 } 323 } 324 325 return results, nil 326 } 327 328 // DestroyVolumes is specified on the storage.VolumeSource interface. 329 func (v *azureVolumeSource) DestroyVolumes(volumeIds []string) ([]error, error) { 330 client, err := v.env.getStorageClient() 331 if err != nil { 332 return nil, errors.Trace(err) 333 } 334 blobsClient := client.GetBlobService() 335 results := make([]error, len(volumeIds)) 336 for i, volumeId := range volumeIds { 337 _, err := blobsClient.DeleteBlobIfExists( 338 dataDiskVHDContainer, volumeId+vhdExtension, nil, 339 ) 340 results[i] = err 341 } 342 return results, nil 343 } 344 345 // ValidateVolumeParams is specified on the storage.VolumeSource interface. 346 func (v *azureVolumeSource) ValidateVolumeParams(params storage.VolumeParams) error { 347 if mibToGib(params.Size) > volumeSizeMaxGiB { 348 return errors.Errorf( 349 "%d GiB exceeds the maximum of %d GiB", 350 mibToGib(params.Size), 351 volumeSizeMaxGiB, 352 ) 353 } 354 return nil 355 } 356 357 // AttachVolumes is specified on the storage.VolumeSource interface. 358 func (v *azureVolumeSource) AttachVolumes(attachParams []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) { 359 results := make([]storage.AttachVolumesResult, len(attachParams)) 360 instanceIds := make([]instance.Id, len(attachParams)) 361 for i, p := range attachParams { 362 instanceIds[i] = p.InstanceId 363 } 364 if len(instanceIds) == 0 { 365 return results, nil 366 } 367 virtualMachines, err := v.virtualMachines(instanceIds) 368 if err != nil { 369 return nil, errors.Annotate(err, "getting virtual machines") 370 } 371 storageAccount, err := v.env.getStorageAccount(false) 372 if err != nil { 373 return nil, errors.Trace(err) 374 } 375 376 // Update VirtualMachine objects in-memory, 377 // and then perform the updates all at once. 378 // 379 // An attachment does not require an update 380 // if it is pre-existing, so we keep a record 381 // of which VMs need updating. 382 changed := make(map[instance.Id]bool, len(virtualMachines)) 383 for i, p := range attachParams { 384 vm, ok := virtualMachines[p.InstanceId] 385 if !ok { 386 continue 387 } 388 if vm.err != nil { 389 results[i].Error = vm.err 390 continue 391 } 392 volumeAttachment, updated, err := v.attachVolume( 393 vm.vm, p, storageAccount, 394 ) 395 if err != nil { 396 results[i].Error = err 397 vm.err = err 398 continue 399 } 400 results[i].VolumeAttachment = volumeAttachment 401 if updated { 402 changed[p.InstanceId] = true 403 } 404 } 405 for _, instanceId := range instanceIds { 406 if !changed[instanceId] { 407 delete(virtualMachines, instanceId) 408 } 409 } 410 411 updateResults, err := v.updateVirtualMachines(virtualMachines, instanceIds) 412 if err != nil { 413 return nil, errors.Annotate(err, "updating virtual machines") 414 } 415 for i, err := range updateResults { 416 if results[i].Error != nil || err == nil { 417 continue 418 } 419 results[i].Error = err 420 results[i].VolumeAttachment = nil 421 } 422 return results, nil 423 } 424 425 func (v *azureVolumeSource) attachVolume( 426 vm *compute.VirtualMachine, 427 p storage.VolumeAttachmentParams, 428 storageAccount *armstorage.Account, 429 ) (_ *storage.VolumeAttachment, updated bool, _ error) { 430 431 storageAccount, err := v.env.getStorageAccount(false) 432 if err != nil { 433 return nil, false, errors.Trace(err) 434 } 435 436 dataDisksRoot := dataDiskVhdRoot(storageAccount) 437 dataDiskName := p.VolumeId 438 vhdURI := dataDisksRoot + dataDiskName + vhdExtension 439 440 var dataDisks []compute.DataDisk 441 if vm.Properties.StorageProfile.DataDisks != nil { 442 dataDisks = *vm.Properties.StorageProfile.DataDisks 443 } 444 for _, disk := range dataDisks { 445 if to.String(disk.Name) != p.VolumeId { 446 continue 447 } 448 if to.String(disk.Vhd.URI) != vhdURI { 449 continue 450 } 451 // Disk is already attached. 452 volumeAttachment := &storage.VolumeAttachment{ 453 p.Volume, 454 p.Machine, 455 storage.VolumeAttachmentInfo{ 456 BusAddress: diskBusAddress(to.Int32(disk.Lun)), 457 }, 458 } 459 return volumeAttachment, false, nil 460 } 461 462 lun, err := nextAvailableLUN(vm) 463 if err != nil { 464 return nil, false, errors.Annotate(err, "choosing LUN") 465 } 466 467 dataDisk := compute.DataDisk{ 468 Lun: to.Int32Ptr(lun), 469 Name: to.StringPtr(dataDiskName), 470 Vhd: &compute.VirtualHardDisk{to.StringPtr(vhdURI)}, 471 Caching: compute.ReadWrite, 472 CreateOption: compute.Attach, 473 } 474 dataDisks = append(dataDisks, dataDisk) 475 vm.Properties.StorageProfile.DataDisks = &dataDisks 476 477 volumeAttachment := storage.VolumeAttachment{ 478 p.Volume, 479 p.Machine, 480 storage.VolumeAttachmentInfo{ 481 BusAddress: diskBusAddress(lun), 482 }, 483 } 484 return &volumeAttachment, true, nil 485 } 486 487 // DetachVolumes is specified on the storage.VolumeSource interface. 488 func (v *azureVolumeSource) DetachVolumes(attachParams []storage.VolumeAttachmentParams) ([]error, error) { 489 results := make([]error, len(attachParams)) 490 instanceIds := make([]instance.Id, len(attachParams)) 491 for i, p := range attachParams { 492 instanceIds[i] = p.InstanceId 493 } 494 if len(instanceIds) == 0 { 495 return results, nil 496 } 497 virtualMachines, err := v.virtualMachines(instanceIds) 498 if err != nil { 499 return nil, errors.Annotate(err, "getting virtual machines") 500 } 501 storageAccount, err := v.env.getStorageAccount(false) 502 if err != nil { 503 return nil, errors.Annotate(err, "getting storage account") 504 } 505 506 // Update VirtualMachine objects in-memory, 507 // and then perform the updates all at once. 508 // 509 // An detachment does not require an update 510 // if the disk isn't attached, so we keep a 511 // record of which VMs need updating. 512 changed := make(map[instance.Id]bool, len(virtualMachines)) 513 for i, p := range attachParams { 514 vm, ok := virtualMachines[p.InstanceId] 515 if !ok { 516 continue 517 } 518 if vm.err != nil { 519 results[i] = vm.err 520 continue 521 } 522 if v.detachVolume(vm.vm, p, storageAccount) { 523 changed[p.InstanceId] = true 524 } 525 } 526 for _, instanceId := range instanceIds { 527 if !changed[instanceId] { 528 delete(virtualMachines, instanceId) 529 } 530 } 531 532 updateResults, err := v.updateVirtualMachines(virtualMachines, instanceIds) 533 if err != nil { 534 return nil, errors.Annotate(err, "updating virtual machines") 535 } 536 for i, err := range updateResults { 537 if results[i] != nil || err == nil { 538 continue 539 } 540 results[i] = err 541 } 542 return results, nil 543 } 544 545 func (v *azureVolumeSource) detachVolume( 546 vm *compute.VirtualMachine, 547 p storage.VolumeAttachmentParams, 548 storageAccount *armstorage.Account, 549 ) (updated bool) { 550 551 dataDisksRoot := dataDiskVhdRoot(storageAccount) 552 dataDiskName := p.VolumeId 553 vhdURI := dataDisksRoot + dataDiskName + vhdExtension 554 555 var dataDisks []compute.DataDisk 556 if vm.Properties.StorageProfile.DataDisks != nil { 557 dataDisks = *vm.Properties.StorageProfile.DataDisks 558 } 559 for i, disk := range dataDisks { 560 if to.String(disk.Name) != p.VolumeId { 561 continue 562 } 563 if to.String(disk.Vhd.URI) != vhdURI { 564 continue 565 } 566 dataDisks = append(dataDisks[:i], dataDisks[i+1:]...) 567 if len(dataDisks) == 0 { 568 vm.Properties.StorageProfile.DataDisks = nil 569 } else { 570 *vm.Properties.StorageProfile.DataDisks = dataDisks 571 } 572 return true 573 } 574 return false 575 } 576 577 type maybeVirtualMachine struct { 578 vm *compute.VirtualMachine 579 err error 580 } 581 582 // virtualMachines returns a mapping of instance IDs to VirtualMachines and 583 // errors, for each of the specified instance IDs. 584 func (v *azureVolumeSource) virtualMachines(instanceIds []instance.Id) (map[instance.Id]*maybeVirtualMachine, error) { 585 vmsClient := compute.VirtualMachinesClient{v.env.compute} 586 var result compute.VirtualMachineListResult 587 if err := v.env.callAPI(func() (autorest.Response, error) { 588 var err error 589 result, err = vmsClient.List(v.env.resourceGroup) 590 return result.Response, err 591 }); err != nil { 592 return nil, errors.Annotate(err, "listing virtual machines") 593 } 594 595 all := make(map[instance.Id]*compute.VirtualMachine) 596 if result.Value != nil { 597 for _, vm := range *result.Value { 598 vmCopy := vm 599 all[instance.Id(to.String(vm.Name))] = &vmCopy 600 } 601 } 602 results := make(map[instance.Id]*maybeVirtualMachine) 603 for _, id := range instanceIds { 604 result := &maybeVirtualMachine{vm: all[id]} 605 if result.vm == nil { 606 result.err = errors.NotFoundf("instance %v", id) 607 } 608 results[id] = result 609 } 610 return results, nil 611 } 612 613 // updateVirtualMachines updates virtual machines in the given map by iterating 614 // through the list of instance IDs in order, and updating each corresponding 615 // virtual machine at most once. 616 func (v *azureVolumeSource) updateVirtualMachines( 617 virtualMachines map[instance.Id]*maybeVirtualMachine, instanceIds []instance.Id, 618 ) ([]error, error) { 619 results := make([]error, len(instanceIds)) 620 vmsClient := compute.VirtualMachinesClient{v.env.compute} 621 for i, instanceId := range instanceIds { 622 vm, ok := virtualMachines[instanceId] 623 if !ok { 624 continue 625 } 626 if vm.err != nil { 627 results[i] = vm.err 628 continue 629 } 630 if err := v.env.callAPI(func() (autorest.Response, error) { 631 return vmsClient.CreateOrUpdate( 632 v.env.resourceGroup, to.String(vm.vm.Name), *vm.vm, 633 nil, // abort channel 634 ) 635 }); err != nil { 636 results[i] = err 637 vm.err = err 638 continue 639 } 640 // successfully updated, don't update again 641 delete(virtualMachines, instanceId) 642 } 643 return results, nil 644 } 645 646 func nextAvailableLUN(vm *compute.VirtualMachine) (int32, error) { 647 // Pick the smallest LUN not in use. We have to choose them in order, 648 // or the disks don't show up. 649 var inUse [32]bool 650 if vm.Properties.StorageProfile.DataDisks != nil { 651 for _, disk := range *vm.Properties.StorageProfile.DataDisks { 652 lun := to.Int32(disk.Lun) 653 if lun < 0 || lun > 31 { 654 logger.Debugf("ignore disk with invalid LUN: %+v", disk) 655 continue 656 } 657 inUse[lun] = true 658 } 659 } 660 for i, inUse := range inUse { 661 if !inUse { 662 return int32(i), nil 663 } 664 } 665 return -1, errors.New("all LUNs are in use") 666 } 667 668 // diskBusAddress returns the value to use in the BusAddress field of 669 // VolumeAttachmentInfo for a disk with the specified LUN. 670 func diskBusAddress(lun int32) string { 671 return fmt.Sprintf("scsi@5:0.0.%d", lun) 672 } 673 674 // mibToGib converts mebibytes to gibibytes. 675 // AWS expects GiB, we work in MiB; round up 676 // to nearest GiB. 677 func mibToGib(m uint64) uint64 { 678 return (m + 1023) / 1024 679 } 680 681 // gibToMib converts gibibytes to mebibytes. 682 func gibToMib(g uint64) uint64 { 683 return g * 1024 684 } 685 686 // osDiskVhdRoot returns the URL to the blob container in which we store the 687 // VHDs for OS disks for the environment. 688 func osDiskVhdRoot(storageAccount *armstorage.Account) string { 689 return blobContainerURL(storageAccount, osDiskVHDContainer) 690 } 691 692 // dataDiskVhdRoot returns the URL to the blob container in which we store the 693 // VHDs for data disks for the environment. 694 func dataDiskVhdRoot(storageAccount *armstorage.Account) string { 695 return blobContainerURL(storageAccount, dataDiskVHDContainer) 696 } 697 698 // blobContainer returns the URL to the named blob container. 699 func blobContainerURL(storageAccount *armstorage.Account, container string) string { 700 return fmt.Sprintf( 701 "%s%s/", 702 to.String(storageAccount.Properties.PrimaryEndpoints.Blob), 703 container, 704 ) 705 } 706 707 // blobVolumeId returns the volume ID for a blob, and a boolean reporting 708 // whether or not the blob's name matches the scheme we use. 709 func blobVolumeId(blob azurestorage.Blob) (string, bool) { 710 if !strings.HasSuffix(blob.Name, vhdExtension) { 711 return "", false 712 } 713 volumeId := blob.Name[:len(blob.Name)-len(vhdExtension)] 714 if _, err := names.ParseVolumeTag(volumeId); err != nil { 715 return "", false 716 } 717 return volumeId, true 718 } 719 720 // getStorageClient returns a new storage client, given an environ config 721 // and a constructor. 722 func getStorageClient( 723 newClient internalazurestorage.NewClientFunc, 724 storageEndpoint string, 725 storageAccount *armstorage.Account, 726 storageAccountKey *armstorage.AccountKey, 727 ) (internalazurestorage.Client, error) { 728 storageAccountName := to.String(storageAccount.Name) 729 const useHTTPS = true 730 return newClient( 731 storageAccountName, 732 to.String(storageAccountKey.Value), 733 storageEndpoint, 734 azurestorage.DefaultAPIVersion, 735 useHTTPS, 736 ) 737 } 738 739 // getStorageAccountKey returns the key for the storage account. 740 func getStorageAccountKey( 741 callAPI callAPIFunc, 742 client armstorage.AccountsClient, 743 resourceGroup, accountName string, 744 ) (*armstorage.AccountKey, error) { 745 logger.Debugf("getting keys for storage account %q", accountName) 746 var listKeysResult armstorage.AccountListKeysResult 747 if err := callAPI(func() (autorest.Response, error) { 748 var err error 749 listKeysResult, err = client.ListKeys(resourceGroup, accountName) 750 return listKeysResult.Response, err 751 }); err != nil { 752 if listKeysResult.Response.Response != nil && listKeysResult.StatusCode == http.StatusNotFound { 753 return nil, errors.NewNotFound(err, "storage account keys not found") 754 } 755 return nil, errors.Annotate(err, "listing storage account keys") 756 } 757 if listKeysResult.Keys == nil { 758 return nil, errors.NotFoundf("storage account keys") 759 } 760 761 // We need a storage key with full permissions. 762 var fullKey *armstorage.AccountKey 763 for _, key := range *listKeysResult.Keys { 764 logger.Debugf("storage account key: %#v", key) 765 // At least some of the time, Azure returns the permissions 766 // in title-case, which does not match the constant. 767 if strings.ToUpper(string(key.Permissions)) != string(armstorage.FULL) { 768 continue 769 } 770 fullKey = &key 771 break 772 } 773 if fullKey == nil { 774 return nil, errors.NotFoundf( 775 "storage account key with %q permission", 776 armstorage.FULL, 777 ) 778 } 779 return fullKey, nil 780 } 781 782 // storageAccountTemplateResource returns a template resource definition 783 // for creating a storage account. 784 func storageAccountTemplateResource( 785 location string, 786 envTags map[string]string, 787 accountName, accountType string, 788 ) armtemplates.Resource { 789 return armtemplates.Resource{ 790 APIVersion: armstorage.APIVersion, 791 Type: "Microsoft.Storage/storageAccounts", 792 Name: accountName, 793 Location: location, 794 Tags: envTags, 795 StorageSku: &armstorage.Sku{ 796 Name: armstorage.SkuName(accountType), 797 }, 798 } 799 }