From c6ff4aae5948d152ede0c0f8da311b94946f9fc1 Mon Sep 17 00:00:00 2001 From: Christopher Boumenot Date: Sun, 28 May 2017 21:06:09 -0700 Subject: [PATCH] Support for building from custom managed images --- builder/azure/arm/authenticate.go | 2 +- builder/azure/arm/azure_client.go | 9 +- builder/azure/arm/builder.go | 55 +++++- builder/azure/arm/config.go | 62 +++++- builder/azure/arm/resource_resolver.go | 31 +++ builder/azure/arm/step_capture_image.go | 80 +++++--- builder/azure/arm/step_capture_image_test.go | 21 ++- .../azure/arm/step_create_resource_group.go | 4 +- builder/azure/arm/step_delete_os_disk.go | 7 + builder/azure/arm/step_delete_os_disk_test.go | 1 + builder/azure/arm/step_get_os_disk.go | 11 +- builder/azure/arm/template_factory.go | 4 + ...stVirtualMachineDeployment03.approved.json | 12 +- ...stVirtualMachineDeployment04.approved.json | 12 +- ...stVirtualMachineDeployment05.approved.json | 8 +- ...stVirtualMachineDeployment06.approved.json | 12 +- ...stVirtualMachineDeployment07.approved.json | 12 +- ...stVirtualMachineDeployment08.approved.json | 175 +++++++++++++++++ ...stVirtualMachineDeployment09.approved.json | 176 ++++++++++++++++++ builder/azure/arm/template_factory_test.go | 68 +++++++ builder/azure/common/constants/stateBag.go | 6 + builder/azure/common/devicelogin.go | 2 +- .../azure/common/template/template_builder.go | 89 ++++++++- .../azure-sdk-for-go/arm/compute/models.go | 171 ++++++++--------- 24 files changed, 880 insertions(+), 150 deletions(-) create mode 100644 builder/azure/arm/template_factory_test.TestVirtualMachineDeployment08.approved.json create mode 100644 builder/azure/arm/template_factory_test.TestVirtualMachineDeployment09.approved.json diff --git a/builder/azure/arm/authenticate.go b/builder/azure/arm/authenticate.go index 9bdb802a2..d89a12540 100644 --- a/builder/azure/arm/authenticate.go +++ b/builder/azure/arm/authenticate.go @@ -4,8 +4,8 @@ package arm import ( - "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/adal" + "github.com/Azure/go-autorest/autorest/azure" ) type Authenticate struct { diff --git a/builder/azure/arm/azure_client.go b/builder/azure/arm/azure_client.go index 362926b7d..10e3d42c0 100644 --- a/builder/azure/arm/azure_client.go +++ b/builder/azure/arm/azure_client.go @@ -18,10 +18,10 @@ import ( armStorage "github.com/Azure/azure-sdk-for-go/arm/storage" "github.com/Azure/azure-sdk-for-go/storage" "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/azure" "github.com/hashicorp/packer/builder/azure/common" "github.com/hashicorp/packer/version" - "github.com/Azure/go-autorest/autorest/adal" ) const ( @@ -40,6 +40,7 @@ type AzureClient struct { network.InterfacesClient network.SubnetsClient network.VirtualNetworksClient + compute.ImagesClient compute.VirtualMachinesClient common.VaultClient armStorage.AccountsClient @@ -126,6 +127,12 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string azureClient.GroupsClient.ResponseInspector = byInspecting(maxlen) azureClient.GroupsClient.UserAgent += packerUserAgent + azureClient.ImagesClient = compute.NewImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) + azureClient.ImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + azureClient.ImagesClient.RequestInspector = withInspection(maxlen) + azureClient.ImagesClient.ResponseInspector = byInspecting(maxlen) + azureClient.ImagesClient.UserAgent += packerUserAgent + azureClient.InterfacesClient = network.NewInterfacesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.InterfacesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.InterfacesClient.RequestInspector = withInspection(maxlen) diff --git a/builder/azure/arm/builder.go b/builder/azure/arm/builder.go index 13e794f9b..d399524a1 100644 --- a/builder/azure/arm/builder.go +++ b/builder/azure/arm/builder.go @@ -15,11 +15,12 @@ import ( "github.com/hashicorp/packer/builder/azure/common/constants" "github.com/hashicorp/packer/builder/azure/common/lin" + "github.com/Azure/azure-sdk-for-go/arm/storage" + "github.com/Azure/go-autorest/autorest/adal" packerCommon "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/packer" "github.com/mitchellh/multistep" - "github.com/Azure/go-autorest/autorest/adal" ) type Builder struct { @@ -47,6 +48,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.stateBag = new(multistep.BasicStateBag) b.configureStateBag(b.stateBag) b.setTemplateParameters(b.stateBag) + b.setImageParameters(b.stateBag) return warnings, errs } @@ -87,17 +89,39 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, err } - b.config.storageAccountBlobEndpoint, err = b.getBlobEndpoint(azureClient, b.config.ResourceGroupName, b.config.StorageAccount) + if b.config.isManagedImage() { + group, err := azureClient.GroupsClient.Get(b.config.TargetManagedImageResourceGroupName) + if err != nil { + return nil, fmt.Errorf("Cannot locate the managed image resource group %s.", b.config.TargetManagedImageResourceGroupName) + } + + b.config.targetManageImageLocation = *group.Location + + // If a managed image already exists it cannot be overwritten. + _, err = azureClient.ImagesClient.Get(b.config.TargetManagedImageResourceGroupName, b.config.TargetManagedImageName, "") + if err == nil { + return nil, fmt.Errorf("A managed image named %s already exists in the resource group %s.", b.config.TargetManagedImageName, b.config.TargetManagedImageResourceGroupName) + } + } + + account, err := b.getBlobAccount(azureClient, b.config.ResourceGroupName, b.config.StorageAccount) if err != nil { return nil, err } + b.config.storageAccountBlobEndpoint = *account.AccountProperties.PrimaryEndpoints.Blob + + if !b.config.isManagedImage() && equalLocation(*account.Location, b.config.Location) == false { + return nil, fmt.Errorf("The storage account is located in %s, but the build will take place in %s. The locations must be identical", *account.Location, b.config.Location) + } endpointConnectType := PublicEndpoint if b.isPrivateNetworkCommunication() { endpointConnectType = PrivateEndpoint } + b.setRuntimeParameters(b.stateBag) b.setTemplateParameters(b.stateBag) + b.setImageParameters(b.stateBag) var steps []multistep.Step if b.config.OSType == constants.Target_Linux { @@ -198,13 +222,21 @@ func (b *Builder) Cancel() { } } -func (b *Builder) getBlobEndpoint(client *AzureClient, resourceGroupName string, storageAccountName string) (string, error) { +func equalLocation(location1, location2 string) bool { + return strings.EqualFold(canonicalizeLocation(location1), canonicalizeLocation(location2)) +} + +func canonicalizeLocation(location string) string { + return strings.Replace(location, " ", "", -1) +} + +func (b *Builder) getBlobAccount(client *AzureClient, resourceGroupName string, storageAccountName string) (*storage.Account, error) { account, err := client.AccountsClient.GetProperties(resourceGroupName, storageAccountName) if err != nil { - return "", err + return nil, err } - return *account.AccountProperties.PrimaryEndpoints.Blob, nil + return &account, err } func (b *Builder) configureStateBag(stateBag multistep.StateBag) { @@ -220,12 +252,25 @@ func (b *Builder) configureStateBag(stateBag multistep.StateBag) { stateBag.Put(constants.ArmPublicIPAddressName, DefaultPublicIPAddressName) stateBag.Put(constants.ArmResourceGroupName, b.config.tmpResourceGroupName) stateBag.Put(constants.ArmStorageAccountName, b.config.StorageAccount) + + stateBag.Put(constants.ArmIsManagedImage, b.config.isManagedImage()) + stateBag.Put(constants.ArmTargetManagedImageResourceGroupName, b.config.TargetManagedImageResourceGroupName) + stateBag.Put(constants.ArmTargetManagedImageName, b.config.TargetManagedImageName) +} + +// Parameters that are only known at runtime after querying Azure. +func (b *Builder) setRuntimeParameters(stateBag multistep.StateBag) { + stateBag.Put(constants.ArmTargetManagedImageLocation, b.config.targetManageImageLocation) } func (b *Builder) setTemplateParameters(stateBag multistep.StateBag) { stateBag.Put(constants.ArmVirtualMachineCaptureParameters, b.config.toVirtualMachineCaptureParameters()) } +func (b *Builder) setImageParameters(stateBag multistep.StateBag) { + stateBag.Put(constants.ArmImageParameters, b.config.toImageParameters()) +} + func (b *Builder) getServicePrincipalTokens(say func(string)) (*adal.ServicePrincipalToken, *adal.ServicePrincipalToken, error) { var servicePrincipalToken *adal.ServicePrincipalToken var servicePrincipalTokenVault *adal.ServicePrincipalToken diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index 1a2485c9e..6366da0d1 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -65,8 +65,19 @@ type Config struct { ImageSku string `mapstructure:"image_sku"` ImageVersion string `mapstructure:"image_version"` ImageUrl string `mapstructure:"image_url"` - Location string `mapstructure:"location"` - VMSize string `mapstructure:"vm_size"` + + ManagedImageResourceGroupName string `mapstructure:"managed_image_resource_group_name"` + ManagedImageName string `mapstructure:"managed_image_name"` + managedImageLocation string + managedImageBlobUri string + managedImageOSState compute.OperatingSystemStateTypes + + Location string `mapstructure:"location"` + VMSize string `mapstructure:"vm_size"` + + TargetManagedImageResourceGroupName string `mapstructure:"target_managed_image_resource_group_name"` + TargetManagedImageName string `mapstructure:"target_managed_image_name"` + targetManageImageLocation string // Deployment AzureTags map[string]*string `mapstructure:"azure_tags"` @@ -118,6 +129,14 @@ type keyVaultCertificate struct { Password string `json:"password,omitempty"` } +func (c *Config) toVMID() string { + return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s", c.SubscriptionID, c.tmpResourceGroupName, c.tmpComputeName) +} + +func (c *Config) isManagedImage() bool { + return c.TargetManagedImageName != "" +} + func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCaptureParameters { return &compute.VirtualMachineCaptureParameters{ DestinationContainerName: &c.CaptureContainerName, @@ -126,6 +145,18 @@ func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCapt } } +func (c *Config) toImageParameters() *compute.Image { + return &compute.Image{ + ImageProperties: &compute.ImageProperties{ + SourceVirtualMachine: &compute.SubResource{ + ID: to.StringPtr(c.toVMID()), + }, + }, + Location: to.StringPtr(c.Location), + Tags: &c.AzureTags, + } +} + func (c *Config) createCertificate() (string, error) { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { @@ -277,7 +308,9 @@ func setSshValues(c *Config) error { func setWinRMCertificate(c *Config) error { c.Comm.WinRMTransportDecorator = - func() winrm.Transporter { return &winrm.ClientNTLM{} } + func() winrm.Transporter { + return &winrm.ClientNTLM{} + } cert, err := c.createCertificate() c.winrmCertificate = cert @@ -372,7 +405,7 @@ func provideDefaultValues(c *Config) { c.VMSize = DefaultVMSize } - if c.ImageUrl == "" && c.ImageVersion == "" { + if c.ImagePublisher != "" && c.ImageVersion == "" { c.ImageVersion = DefaultImageVersion } @@ -467,7 +500,13 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) { ///////////////////////////////////////////// // Compute - if c.ImageUrl == "" { + if c.ImageUrl != "" && + (c.ManagedImageName != "" || c.ManagedImageResourceGroupName != "") && + (c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != "") { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("Specify either a VHD (image_url), Image Reference (image_publisher, image_offer, image_sku) or a Managed Disk (managed_disk_image_name, managed_disk_resource_group_name")) + } + + if c.ImageUrl == "" && c.ManagedImageName == "" { if c.ImagePublisher == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_publisher must be specified")) } @@ -479,6 +518,19 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) { if c.ImageSku == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_sku must be specified")) } + } else if c.ImageUrl == "" && c.ImagePublisher == "" { + if c.ManagedImageName == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("A managed_image_name must be specified")) + } + if c.ManagedImageResourceGroupName == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("An managed_image_resource_group_name must be specified")) + } + if c.TargetManagedImageName == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("An target_managed_image_name must be specified")) + } + if c.TargetManagedImageResourceGroupName == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("An target_managed_image_resource_group_name must be specified")) + } } else { if c.ImagePublisher != "" || c.ImageOffer != "" || c.ImageSku != "" || c.ImageVersion != "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_url must not be specified if image_publisher, image_offer, image_sku, or image_version is specified")) diff --git a/builder/azure/arm/resource_resolver.go b/builder/azure/arm/resource_resolver.go index d2198fb97..632573730 100644 --- a/builder/azure/arm/resource_resolver.go +++ b/builder/azure/arm/resource_resolver.go @@ -10,6 +10,7 @@ package arm import ( "fmt" + "github.com/Azure/azure-sdk-for-go/arm/compute" "strings" ) @@ -43,6 +44,17 @@ func (s *resourceResolver) Resolve(c *Config) error { c.VirtualNetworkSubnetName = subnetName } + if s.shouldResolveManagedImageName(c) { + image, err := findManagedImageByName(s.client, c.ManagedImageName, c.ManagedImageResourceGroupName) + if err != nil { + return err + } + + c.managedImageBlobUri = *image.ImageProperties.StorageProfile.OsDisk.BlobURI + c.managedImageLocation = *image.Location + c.managedImageOSState = image.ImageProperties.StorageProfile.OsDisk.OsState + } + return nil } @@ -50,12 +62,31 @@ func (s *resourceResolver) shouldResolveResourceGroup(c *Config) bool { return c.VirtualNetworkName != "" && c.VirtualNetworkResourceGroupName == "" } +func (s *resourceResolver) shouldResolveManagedImageName(c *Config) bool { + return c.ManagedImageName != "" +} + func getResourceGroupNameFromId(id string) string { // "/subscriptions/3f499422-dd76-4114-8859-86d526c9deb6/resourceGroups/packer-Resource-Group-yylnwsl30j/providers/... xs := strings.Split(id, "/") return xs[4] } +func findManagedImageByName(client *AzureClient, name, resourceGroupName string) (*compute.Image, error) { + images, err := client.ImagesClient.ListByResourceGroup(resourceGroupName) + if err != nil { + return nil, err + } + + for _, image := range *images.Value { + if strings.EqualFold(name, *image.Name) { + return &image, nil + } + } + + return nil, fmt.Errorf("Cannot find an image named '%s' in the resource group '%s'", name, resourceGroupName) +} + func findVirtualNetworkResourceGroup(client *AzureClient, name string) (string, error) { virtualNetworks, err := client.VirtualNetworksClient.ListAll() if err != nil { diff --git a/builder/azure/arm/step_capture_image.go b/builder/azure/arm/step_capture_image.go index a8b5e34fe..4b0498a62 100644 --- a/builder/azure/arm/step_capture_image.go +++ b/builder/azure/arm/step_capture_image.go @@ -14,39 +14,49 @@ import ( ) type StepCaptureImage struct { - client *AzureClient - capture func(resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters, cancelCh <-chan struct{}) error - get func(client *AzureClient) *CaptureTemplate - say func(message string) - error func(e error) + client *AzureClient + generalizeVM func(resourceGroupName, computeName string) error + captureVhd func(resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters, cancelCh <-chan struct{}) error + captureManagedImage func(resourceGroupName string, computeName string, parameters *compute.Image, cancelCh <-chan struct{}) error + get func(client *AzureClient) *CaptureTemplate + say func(message string) + error func(e error) } func NewStepCaptureImage(client *AzureClient, ui packer.Ui) *StepCaptureImage { var step = &StepCaptureImage{ client: client, - get: func(client *AzureClient) *CaptureTemplate { return client.Template }, - say: func(message string) { ui.Say(message) }, - error: func(e error) { ui.Error(e.Error()) }, + get: func(client *AzureClient) *CaptureTemplate { + return client.Template + }, + say: func(message string) { + ui.Say(message) + }, + error: func(e error) { + ui.Error(e.Error()) + }, } - step.capture = step.captureImage + step.generalizeVM = step.generalize + step.captureVhd = step.captureImage + step.captureManagedImage = step.captureImageFromVM + return step } -func (s *StepCaptureImage) captureImage(resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters, cancelCh <-chan struct{}) error { +func (s *StepCaptureImage) generalize(resourceGroupName string, computeName string) error { _, err := s.client.Generalize(resourceGroupName, computeName) - if err != nil { - return err - } - - _, errChan := s.client.Capture(resourceGroupName, computeName, *parameters, cancelCh) + return err +} - err = <-errChan - if err != nil { - return err - } +func (s *StepCaptureImage) captureImageFromVM(resourceGroupName string, imageName string, image *compute.Image, cancelCh <-chan struct{}) error { + _, errChan := s.client.ImagesClient.CreateOrUpdate(resourceGroupName, imageName, *image, cancelCh) + return <-errChan +} - return nil +func (s *StepCaptureImage) captureImage(resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters, cancelCh <-chan struct{}) error { + _, errChan := s.client.Capture(resourceGroupName, computeName, *parameters, cancelCh) + return <-errChan } func (s *StepCaptureImage) Run(state multistep.StateBag) multistep.StepAction { @@ -54,15 +64,35 @@ func (s *StepCaptureImage) Run(state multistep.StateBag) multistep.StepAction { var computeName = state.Get(constants.ArmComputeName).(string) var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string) - var parameters = state.Get(constants.ArmVirtualMachineCaptureParameters).(*compute.VirtualMachineCaptureParameters) + var vmCaptureParameters = state.Get(constants.ArmVirtualMachineCaptureParameters).(*compute.VirtualMachineCaptureParameters) + var imageParameters = state.Get(constants.ArmImageParameters).(*compute.Image) + + var isManagedImage = state.Get(constants.ArmIsManagedImage).(bool) + var targetManagedImageResourceGroupName = state.Get(constants.ArmTargetManagedImageResourceGroupName).(string) + var targetManagedImageName = state.Get(constants.ArmTargetManagedImageName).(string) + var targetManagedImageLocation = state.Get(constants.ArmTargetManagedImageLocation).(string) - s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName)) - s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName)) + s.say(fmt.Sprintf(" -> ResourceGroupName : '%s'", resourceGroupName)) + s.say(fmt.Sprintf(" -> ComputeName : '%s'", computeName)) result := common.StartInterruptibleTask( - func() bool { return common.IsStateCancelled(state) }, + func() bool { + return common.IsStateCancelled(state) + }, func(cancelCh <-chan struct{}) error { - return s.capture(resourceGroupName, computeName, parameters, cancelCh) + err := s.generalizeVM(resourceGroupName, computeName) + if err != nil { + return err + } + + if isManagedImage { + s.say(fmt.Sprintf(" -> ImageResourceGroupName : '%s'", targetManagedImageResourceGroupName)) + s.say(fmt.Sprintf(" -> ImageName : '%s'", targetManagedImageName)) + s.say(fmt.Sprintf(" -> Image Location : '%s'", targetManagedImageLocation)) + return s.captureManagedImage(targetManagedImageResourceGroupName, targetManagedImageName, imageParameters, cancelCh) + } else { + return s.captureVhd(resourceGroupName, computeName, vmCaptureParameters, cancelCh) + } }) // HACK(chrboum): I do not like this. The capture method should be returning this value diff --git a/builder/azure/arm/step_capture_image_test.go b/builder/azure/arm/step_capture_image_test.go index c25345c6e..cb8adb78f 100644 --- a/builder/azure/arm/step_capture_image_test.go +++ b/builder/azure/arm/step_capture_image_test.go @@ -14,9 +14,12 @@ import ( func TestStepCaptureImageShouldFailIfCaptureFails(t *testing.T) { var testSubject = &StepCaptureImage{ - capture: func(string, string, *compute.VirtualMachineCaptureParameters, <-chan struct{}) error { + captureVhd: func(string, string, *compute.VirtualMachineCaptureParameters, <-chan struct{}) error { return fmt.Errorf("!! Unit Test FAIL !!") }, + generalizeVM: func(string, string) error { + return nil + }, get: func(client *AzureClient) *CaptureTemplate { return nil }, @@ -38,7 +41,10 @@ func TestStepCaptureImageShouldFailIfCaptureFails(t *testing.T) { func TestStepCaptureImageShouldPassIfCapturePasses(t *testing.T) { var testSubject = &StepCaptureImage{ - capture: func(string, string, *compute.VirtualMachineCaptureParameters, <-chan struct{}) error { return nil }, + captureVhd: func(string, string, *compute.VirtualMachineCaptureParameters, <-chan struct{}) error { return nil }, + generalizeVM: func(string, string) error { + return nil + }, get: func(client *AzureClient) *CaptureTemplate { return nil }, @@ -70,13 +76,16 @@ func TestStepCaptureImageShouldTakeStepArgumentsFromStateBag(t *testing.T) { } var testSubject = &StepCaptureImage{ - capture: func(resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters, cancelCh <-chan struct{}) error { + captureVhd: func(resourceGroupName string, computeName string, parameters *compute.VirtualMachineCaptureParameters, cancelCh <-chan struct{}) error { actualResourceGroupName = resourceGroupName actualComputeName = computeName actualVirtualMachineCaptureParameters = parameters return nil }, + generalizeVM: func(string, string) error { + return nil + }, get: func(client *AzureClient) *CaptureTemplate { return actualCaptureTemplate }, @@ -120,5 +129,11 @@ func createTestStateBagStepCaptureImage() multistep.StateBag { stateBag.Put(constants.ArmResourceGroupName, "Unit Test: ResourceGroupName") stateBag.Put(constants.ArmVirtualMachineCaptureParameters, &compute.VirtualMachineCaptureParameters{}) + stateBag.Put(constants.ArmIsManagedImage, false) + stateBag.Put(constants.ArmTargetManagedImageResourceGroupName, "") + stateBag.Put(constants.ArmTargetManagedImageName, "") + stateBag.Put(constants.ArmTargetManagedImageLocation, "") + stateBag.Put(constants.ArmImageParameters, &compute.Image{}) + return stateBag } diff --git a/builder/azure/arm/step_create_resource_group.go b/builder/azure/arm/step_create_resource_group.go index 651f8e6b6..13a09e0e7 100644 --- a/builder/azure/arm/step_create_resource_group.go +++ b/builder/azure/arm/step_create_resource_group.go @@ -71,7 +71,9 @@ func (s *StepCreateResourceGroup) Cleanup(state multistep.StateBag) { ui.Say("\nCleanup requested, deleting resource group ...") var resourceGroupName = state.Get(constants.ArmResourceGroupName).(string) - _, err := s.client.GroupsClient.Delete(resourceGroupName, nil) + _, errChan := s.client.GroupsClient.Delete(resourceGroupName, nil) + err := <-errChan + if err != nil { ui.Error(fmt.Sprintf("Error deleting resource group. Please delete it manually.\n\n"+ "Name: %s\n"+ diff --git a/builder/azure/arm/step_delete_os_disk.go b/builder/azure/arm/step_delete_os_disk.go index 8da854af0..4cedd513d 100644 --- a/builder/azure/arm/step_delete_os_disk.go +++ b/builder/azure/arm/step_delete_os_disk.go @@ -41,6 +41,13 @@ func (s *StepDeleteOSDisk) Run(state multistep.StateBag) multistep.StepAction { s.say("Deleting the temporary OS disk ...") var osDisk = state.Get(constants.ArmOSDiskVhd).(string) + var isManagedDisk = state.Get(constants.ArmIsManagedImage).(bool) + + if isManagedDisk { + s.say(fmt.Sprintf(" -> OS Disk : skipping, managed disk was used...")) + return multistep.ActionContinue + } + s.say(fmt.Sprintf(" -> OS Disk : '%s'", osDisk)) u, err := url.Parse(osDisk) diff --git a/builder/azure/arm/step_delete_os_disk_test.go b/builder/azure/arm/step_delete_os_disk_test.go index 5e3adfb6b..6e2e2d9a9 100644 --- a/builder/azure/arm/step_delete_os_disk_test.go +++ b/builder/azure/arm/step_delete_os_disk_test.go @@ -108,6 +108,7 @@ func TestStepDeleteOSDiskShouldHandleComplexStorageContainerNames(t *testing.T) func DeleteTestStateBagStepDeleteOSDisk(osDiskVhd string) multistep.StateBag { stateBag := new(multistep.BasicStateBag) stateBag.Put(constants.ArmOSDiskVhd, osDiskVhd) + stateBag.Put(constants.ArmIsManagedImage, false) return stateBag } diff --git a/builder/azure/arm/step_get_os_disk.go b/builder/azure/arm/step_get_os_disk.go index fb21c0320..1bd702749 100644 --- a/builder/azure/arm/step_get_os_disk.go +++ b/builder/azure/arm/step_get_os_disk.go @@ -53,9 +53,16 @@ func (s *StepGetOSDisk) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - s.say(fmt.Sprintf(" -> OS Disk : '%s'", *vm.StorageProfile.OsDisk.Vhd.URI)) - state.Put(constants.ArmOSDiskVhd, *vm.StorageProfile.OsDisk.Vhd.URI) + var vhdUri string + if vm.StorageProfile.OsDisk.Vhd != nil { + s.say(fmt.Sprintf(" -> OS Disk : '%s'", vhdUri)) + vhdUri = *vm.StorageProfile.OsDisk.Vhd.URI + } else { + s.say(fmt.Sprintf(" -> Managed OS Disk : '%s'", vhdUri)) + vhdUri = *vm.StorageProfile.OsDisk.ManagedDisk.ID + } + state.Put(constants.ArmOSDiskVhd, vhdUri) return multistep.ActionContinue } diff --git a/builder/azure/arm/template_factory.go b/builder/azure/arm/template_factory.go index f4157e5d0..cdda26379 100644 --- a/builder/azure/arm/template_factory.go +++ b/builder/azure/arm/template_factory.go @@ -51,6 +51,10 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error) if config.ImageUrl != "" { builder.SetImageUrl(config.ImageUrl, osType) + } else if config.ManagedImageName != "" { + builder.SetManagedDiskUrl(config.ManagedImageName, config.managedImageLocation, config.managedImageBlobUri, config.managedImageOSState) + } else if config.TargetManagedImageName != "" && config.ImagePublisher != "" { + builder.SetManagedMarketplaceImage(config.Location, config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion) } else { builder.SetMarketPlaceImage(config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion) } diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment03.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment03.approved.json index e0d7b10bb..a220d9822 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment03.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment03.approved.json @@ -26,7 +26,7 @@ }, "resources": [ { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('publicIPAddressApiVersion')]", "location": "[variables('location')]", "name": "[variables('publicIPAddressName')]", "properties": { @@ -38,7 +38,7 @@ "type": "Microsoft.Network/publicIPAddresses" }, { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('virtualNetworksApiVersion')]", "location": "[variables('location')]", "name": "[variables('virtualNetworkName')]", "properties": { @@ -59,7 +59,7 @@ "type": "Microsoft.Network/virtualNetworks" }, { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('networkInterfacesApiVersion')]", "dependsOn": [ "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" @@ -144,9 +144,12 @@ ], "variables": { "addressPrefix": "10.0.0.0/16", - "apiVersion": "2015-06-15", + "apiVersion": "2017-03-30", "location": "[resourceGroup().location]", + "managedDiskApiVersion": "2017-03-30", + "networkInterfacesApiVersion": "2017-04-01", "nicName": "packerNic", + "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressName": "packerPublicIP", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", @@ -155,6 +158,7 @@ "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", "virtualNetworkName": "packerNetwork", "virtualNetworkResourceGroup": "[resourceGroup().name]", + "virtualNetworksApiVersion": "2017-04-01", "vmStorageAccountContainerName": "images", "vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]" } diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment04.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment04.approved.json index faa51be82..0be4bbef4 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment04.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment04.approved.json @@ -26,7 +26,7 @@ }, "resources": [ { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('publicIPAddressApiVersion')]", "location": "[variables('location')]", "name": "[variables('publicIPAddressName')]", "properties": { @@ -38,7 +38,7 @@ "type": "Microsoft.Network/publicIPAddresses" }, { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('virtualNetworksApiVersion')]", "location": "[variables('location')]", "name": "[variables('virtualNetworkName')]", "properties": { @@ -59,7 +59,7 @@ "type": "Microsoft.Network/virtualNetworks" }, { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('networkInterfacesApiVersion')]", "dependsOn": [ "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" @@ -142,9 +142,12 @@ ], "variables": { "addressPrefix": "10.0.0.0/16", - "apiVersion": "2015-06-15", + "apiVersion": "2017-03-30", "location": "[resourceGroup().location]", + "managedDiskApiVersion": "2017-03-30", + "networkInterfacesApiVersion": "2017-04-01", "nicName": "packerNic", + "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressName": "packerPublicIP", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", @@ -153,6 +156,7 @@ "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", "virtualNetworkName": "packerNetwork", "virtualNetworkResourceGroup": "[resourceGroup().name]", + "virtualNetworksApiVersion": "2017-04-01", "vmStorageAccountContainerName": "images", "vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]" } diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment05.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment05.approved.json index ae13afab1..29c4750c5 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment05.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment05.approved.json @@ -26,7 +26,7 @@ }, "resources": [ { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('networkInterfacesApiVersion')]", "dependsOn": [], "location": "[variables('location')]", "name": "[variables('nicName')]", @@ -103,9 +103,12 @@ ], "variables": { "addressPrefix": "10.0.0.0/16", - "apiVersion": "2015-06-15", + "apiVersion": "2017-03-30", "location": "[resourceGroup().location]", + "managedDiskApiVersion": "2017-03-30", + "networkInterfacesApiVersion": "2017-04-01", "nicName": "packerNic", + "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressName": "packerPublicIP", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", @@ -114,6 +117,7 @@ "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", "virtualNetworkName": "virtualNetworkName", "virtualNetworkResourceGroup": "virtualNetworkResourceGroupName", + "virtualNetworksApiVersion": "2017-04-01", "vmStorageAccountContainerName": "images", "vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]" } diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment06.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment06.approved.json index a15c63f36..fb5e79b9f 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment06.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment06.approved.json @@ -26,7 +26,7 @@ }, "resources": [ { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('publicIPAddressApiVersion')]", "location": "[variables('location')]", "name": "[variables('publicIPAddressName')]", "properties": { @@ -43,7 +43,7 @@ "type": "Microsoft.Network/publicIPAddresses" }, { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('virtualNetworksApiVersion')]", "location": "[variables('location')]", "name": "[variables('virtualNetworkName')]", "properties": { @@ -69,7 +69,7 @@ "type": "Microsoft.Network/virtualNetworks" }, { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('networkInterfacesApiVersion')]", "dependsOn": [ "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" @@ -162,9 +162,12 @@ ], "variables": { "addressPrefix": "10.0.0.0/16", - "apiVersion": "2015-06-15", + "apiVersion": "2017-03-30", "location": "[resourceGroup().location]", + "managedDiskApiVersion": "2017-03-30", + "networkInterfacesApiVersion": "2017-04-01", "nicName": "packerNic", + "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressName": "packerPublicIP", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", @@ -173,6 +176,7 @@ "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", "virtualNetworkName": "packerNetwork", "virtualNetworkResourceGroup": "[resourceGroup().name]", + "virtualNetworksApiVersion": "2017-04-01", "vmStorageAccountContainerName": "images", "vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]" } diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment07.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment07.approved.json index 02bac74f3..6f722c765 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment07.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment07.approved.json @@ -26,7 +26,7 @@ }, "resources": [ { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('publicIPAddressApiVersion')]", "location": "[variables('location')]", "name": "[variables('publicIPAddressName')]", "properties": { @@ -38,7 +38,7 @@ "type": "Microsoft.Network/publicIPAddresses" }, { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('virtualNetworksApiVersion')]", "location": "[variables('location')]", "name": "[variables('virtualNetworkName')]", "properties": { @@ -59,7 +59,7 @@ "type": "Microsoft.Network/virtualNetworks" }, { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('networkInterfacesApiVersion')]", "dependsOn": [ "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" @@ -143,9 +143,12 @@ ], "variables": { "addressPrefix": "10.0.0.0/16", - "apiVersion": "2015-06-15", + "apiVersion": "2017-03-30", "location": "[resourceGroup().location]", + "managedDiskApiVersion": "2017-03-30", + "networkInterfacesApiVersion": "2017-04-01", "nicName": "packerNic", + "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressName": "packerPublicIP", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", @@ -154,6 +157,7 @@ "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", "virtualNetworkName": "packerNetwork", "virtualNetworkResourceGroup": "[resourceGroup().name]", + "virtualNetworksApiVersion": "2017-04-01", "vmStorageAccountContainerName": "images", "vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]" } diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment08.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment08.approved.json new file mode 100644 index 000000000..9fc61b614 --- /dev/null +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment08.approved.json @@ -0,0 +1,175 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json", + "contentVersion": "1.0.0.0", + "parameters": { + "adminPassword": { + "type": "string" + }, + "adminUsername": { + "type": "string" + }, + "dnsNameForPublicIP": { + "type": "string" + }, + "osDiskName": { + "type": "string" + }, + "storageAccountBlobEndpoint": { + "type": "string" + }, + "vmName": { + "type": "string" + }, + "vmSize": { + "type": "string" + } + }, + "resources": [ + { + "apiVersion": "[variables('publicIPAddressApiVersion')]", + "location": "[variables('location')]", + "name": "[variables('publicIPAddressName')]", + "properties": { + "dnsSettings": { + "domainNameLabel": "[parameters('dnsNameForPublicIP')]" + }, + "publicIPAllocationMethod": "[variables('publicIPAddressType')]" + }, + "type": "Microsoft.Network/publicIPAddresses" + }, + { + "apiVersion": "[variables('virtualNetworksApiVersion')]", + "location": "[variables('location')]", + "name": "[variables('virtualNetworkName')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('addressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[variables('subnetName')]", + "properties": { + "addressPrefix": "[variables('subnetAddressPrefix')]" + } + } + ] + }, + "type": "Microsoft.Network/virtualNetworks" + }, + { + "apiVersion": "[variables('networkInterfacesApiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" + ], + "location": "[variables('location')]", + "name": "[variables('nicName')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]" + }, + "subnet": { + "id": "[variables('subnetRef')]" + } + } + } + ] + }, + "type": "Microsoft.Network/networkInterfaces" + }, + { + "apiVersion": "[variables('apiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]", + "[concat('Microsoft.Compute/images/', 'ManagedImageName')]" + ], + "location": "[variables('location')]", + "name": "[parameters('vmName')]", + "properties": { + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": false + } + }, + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]" + } + ] + }, + "osProfile": { + "adminPassword": "[parameters('adminPassword')]", + "adminUsername": "[parameters('adminUsername')]", + "computerName": "[parameters('vmName')]", + "linuxConfiguration": { + "ssh": { + "publicKeys": [ + { + "keyData": "", + "path": "[variables('sshKeyPath')]" + } + ] + } + } + }, + "storageProfile": { + "imageReference": { + "id": "[resourceId(resourceGroup().name, 'Microsoft.Compute/images', 'ManagedImageName')]" + }, + "osDisk": { + "caching": "ReadWrite", + "createOption": "FromImage", + "name": "osdisk" + } + } + }, + "type": "Microsoft.Compute/virtualMachines" + }, + { + "apiVersion": "[variables('managedDiskApiVersion')]", + "location": "", + "name": "ManagedImageName", + "properties": { + "storageProfile": { + "osDisk": { + "blobUri": "", + "osState": "", + "osType": "Linux" + } + } + }, + "type": "Microsoft.Compute/images" + } + ], + "variables": { + "addressPrefix": "10.0.0.0/16", + "apiVersion": "2017-03-30", + "location": "[resourceGroup().location]", + "managedDiskApiVersion": "2017-03-30", + "networkInterfacesApiVersion": "2017-04-01", + "nicName": "packerNic", + "publicIPAddressApiVersion": "2017-04-01", + "publicIPAddressName": "packerPublicIP", + "publicIPAddressType": "Dynamic", + "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", + "subnetAddressPrefix": "10.0.0.0/24", + "subnetName": "packerSubnet", + "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", + "virtualNetworkName": "packerNetwork", + "virtualNetworkResourceGroup": "[resourceGroup().name]", + "virtualNetworksApiVersion": "2017-04-01", + "vmStorageAccountContainerName": "images", + "vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]" + } +} \ No newline at end of file diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment09.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment09.approved.json new file mode 100644 index 000000000..f994956e2 --- /dev/null +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment09.approved.json @@ -0,0 +1,176 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json", + "contentVersion": "1.0.0.0", + "parameters": { + "adminPassword": { + "type": "string" + }, + "adminUsername": { + "type": "string" + }, + "dnsNameForPublicIP": { + "type": "string" + }, + "osDiskName": { + "type": "string" + }, + "storageAccountBlobEndpoint": { + "type": "string" + }, + "vmName": { + "type": "string" + }, + "vmSize": { + "type": "string" + } + }, + "resources": [ + { + "apiVersion": "[variables('publicIPAddressApiVersion')]", + "location": "[variables('location')]", + "name": "[variables('publicIPAddressName')]", + "properties": { + "dnsSettings": { + "domainNameLabel": "[parameters('dnsNameForPublicIP')]" + }, + "publicIPAllocationMethod": "[variables('publicIPAddressType')]" + }, + "type": "Microsoft.Network/publicIPAddresses" + }, + { + "apiVersion": "[variables('virtualNetworksApiVersion')]", + "location": "[variables('location')]", + "name": "[variables('virtualNetworkName')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('addressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[variables('subnetName')]", + "properties": { + "addressPrefix": "[variables('subnetAddressPrefix')]" + } + } + ] + }, + "type": "Microsoft.Network/virtualNetworks" + }, + { + "apiVersion": "[variables('networkInterfacesApiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" + ], + "location": "[variables('location')]", + "name": "[variables('nicName')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]" + }, + "subnet": { + "id": "[variables('subnetRef')]" + } + } + } + ] + }, + "type": "Microsoft.Network/networkInterfaces" + }, + { + "apiVersion": "[variables('apiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]", + "[concat('Microsoft.Compute/images/', 'packerManagedDisk')]" + ], + "location": "[variables('location')]", + "name": "[parameters('vmName')]", + "properties": { + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": false + } + }, + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]" + } + ] + }, + "osProfile": { + "adminPassword": "[parameters('adminPassword')]", + "adminUsername": "[parameters('adminUsername')]", + "computerName": "[parameters('vmName')]", + "linuxConfiguration": { + "ssh": { + "publicKeys": [ + { + "keyData": "", + "path": "[variables('sshKeyPath')]" + } + ] + } + } + }, + "storageProfile": { + "imageReference": { + "id": "[resourceId(resourceGroup().name, 'Microsoft.Compute/images', 'packerManagedDisk')]" + }, + "osDisk": { + "caching": "ReadWrite", + "createOption": "FromImage", + "name": "osdisk" + } + } + }, + "type": "Microsoft.Compute/virtualMachines" + }, + { + "apiVersion": "[variables('managedDiskApiVersion')]", + "location": "ignore", + "name": "packerManagedDisk", + "properties": { + "storageProfile": { + "imageReference": { + "offer": "UbuntuServer", + "publisher": "Canonical", + "sku": "16.04-LTS", + "version": "--version--" + } + } + }, + "type": "Microsoft.Compute/images" + } + ], + "variables": { + "addressPrefix": "10.0.0.0/16", + "apiVersion": "2017-03-30", + "location": "[resourceGroup().location]", + "managedDiskApiVersion": "2017-03-30", + "networkInterfacesApiVersion": "2017-04-01", + "nicName": "packerNic", + "publicIPAddressApiVersion": "2017-04-01", + "publicIPAddressName": "packerPublicIP", + "publicIPAddressType": "Dynamic", + "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", + "subnetAddressPrefix": "10.0.0.0/24", + "subnetName": "packerSubnet", + "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", + "virtualNetworkName": "packerNetwork", + "virtualNetworkResourceGroup": "[resourceGroup().name]", + "virtualNetworksApiVersion": "2017-04-01", + "vmStorageAccountContainerName": "images", + "vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]" + } +} \ No newline at end of file diff --git a/builder/azure/arm/template_factory_test.go b/builder/azure/arm/template_factory_test.go index 8ad32d6d0..315123ad5 100644 --- a/builder/azure/arm/template_factory_test.go +++ b/builder/azure/arm/template_factory_test.go @@ -258,6 +258,74 @@ growpart: } } +// Ensure the VM template is correct when building from a custom managed image. +func TestVirtualMachineDeployment08(t *testing.T) { + config := map[string]interface{}{ + "capture_name_prefix": "ignore", + "capture_container_name": "ignore", + "location": "ignore", + "resource_group_name": "ignore", + "storage_account": "ignore", + "subscription_id": "ignore", + "os_type": constants.Target_Linux, + "communicator": "none", + "managed_image_name": "ManagedImageName", + "managed_image_resource_group_name": "ManagedImageResourceGroupName", + "target_managed_image_name": "TargetManagedImageName", + "target_managed_image_resource_group_name": "TargetManagedImageResourceGroupName", + } + + c, _, err := newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + + deployment, err := GetVirtualMachineDeployment(c) + if err != nil { + t.Fatal(err) + } + + err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template) + if err != nil { + t.Fatal(err) + } +} + +// Ensure the VM template is correct when building from a platform managed image. +func TestVirtualMachineDeployment09(t *testing.T) { + config := map[string]interface{}{ + "capture_name_prefix": "ignore", + "capture_container_name": "ignore", + "location": "ignore", + "resource_group_name": "ignore", + "storage_account": "ignore", + "subscription_id": "ignore", + "os_type": constants.Target_Linux, + "communicator": "none", + "image_publisher": "Canonical", + "image_offer": "UbuntuServer", + "image_sku": "16.04-LTS", + "image_version": "--version--", + "target_managed_image_name": "TargetManagedImageName", + "target_managed_image_resource_group_name": "TargetManagedImageResourceGroupName", + } + + c, _, err := newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + + deployment, err := GetVirtualMachineDeployment(c) + if err != nil { + t.Fatal(err) + } + + err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template) + if err != nil { + t.Fatal(err) + } +} + // Ensure the link values are not set, and the concrete values are set. func TestKeyVaultDeployment00(t *testing.T) { c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) diff --git a/builder/azure/common/constants/stateBag.go b/builder/azure/common/constants/stateBag.go index c4dba0acc..9fa8fbfab 100644 --- a/builder/azure/common/constants/stateBag.go +++ b/builder/azure/common/constants/stateBag.go @@ -16,6 +16,7 @@ const ( const ( ArmCaptureTemplate string = "arm.CaptureTemplate" ArmComputeName string = "arm.ComputeName" + ArmImageParameters string = "arm.ImageParameters" ArmCertificateUrl string = "arm.CertificateUrl" ArmDeploymentName string = "arm.DeploymentName" ArmNicName string = "arm.NicName" @@ -28,4 +29,9 @@ const ( ArmStorageAccountName string = "arm.StorageAccountName" ArmTags string = "arm.Tags" ArmVirtualMachineCaptureParameters string = "arm.VirtualMachineCaptureParameters" + + ArmIsManagedImage string = "arm.IsManagedImage" + ArmTargetManagedImageResourceGroupName string = "arm.TargetManagedImageResourceGroupName" + ArmTargetManagedImageLocation string = "arm.TargetManagedImageLocation" + ArmTargetManagedImageName string = "arm.TargetManagedImageName" ) diff --git a/builder/azure/common/devicelogin.go b/builder/azure/common/devicelogin.go index 7aef2df15..904bd157f 100644 --- a/builder/azure/common/devicelogin.go +++ b/builder/azure/common/devicelogin.go @@ -9,11 +9,11 @@ import ( "github.com/Azure/azure-sdk-for-go/arm/resources/subscriptions" "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/to" "github.com/hashicorp/packer/version" "github.com/mitchellh/go-homedir" - "github.com/Azure/go-autorest/autorest/adal" ) var ( diff --git a/builder/azure/common/template/template_builder.go b/builder/azure/common/template/template_builder.go index 9c5bf0e37..f5a4c40bd 100644 --- a/builder/azure/common/template/template_builder.go +++ b/builder/azure/common/template/template_builder.go @@ -14,6 +14,7 @@ const ( jsonIndent = " " resourceKeyVaults = "Microsoft.KeyVault/vaults" + resourceManagedDisk = "Microsoft.Compute/images" resourceNetworkInterfaces = "Microsoft.Network/networkInterfaces" resourcePublicIPAddresses = "Microsoft.Network/publicIPAddresses" resourceVirtualMachine = "Microsoft.Compute/virtualMachines" @@ -24,6 +25,7 @@ const ( type TemplateBuilder struct { template *Template + osType compute.OperatingSystemTypes } func NewTemplateBuilder(template string) (*TemplateBuilder, error) { @@ -57,6 +59,7 @@ func (s *TemplateBuilder) BuildLinux(sshAuthorizedKey string) error { }, } + s.osType = compute.Linux return nil } @@ -93,6 +96,80 @@ func (s *TemplateBuilder) BuildWindows(keyVaultName, winRMCertificateUrl string) }, }, } + + s.osType = compute.Windows + return nil +} + +func (s *TemplateBuilder) SetManagedDiskUrl(managedDiskImageName, location, blobUri string, osState compute.OperatingSystemStateTypes) error { + resource, err := s.getResourceByType(resourceVirtualMachine) + if err != nil { + return err + } + + resourceId := s.toResourceID(resourceManagedDisk, managedDiskImageName) + profile := resource.Properties.StorageProfile + profile.ImageReference = &compute.ImageReference{ + ID: to.StringPtr(resourceId), + } + profile.OsDisk.Vhd = nil + + *resource.DependsOn = append(*resource.DependsOn, fmt.Sprintf("[concat('%s/', '%s')]", resourceManagedDisk, managedDiskImageName)) + + managedDiskResource := &Resource{ + Type: to.StringPtr(resourceManagedDisk), + ApiVersion: to.StringPtr(s.toVariable("managedDiskApiVersion")), + Name: to.StringPtr(managedDiskImageName), + Location: to.StringPtr(location), + Properties: &Properties{ + StorageProfile: &compute.StorageProfile{ + OsDisk: &compute.OSDisk{ + OsType: s.osType, + OsState: to.StringPtr(fmt.Sprintf("%s", osState)), + BlobUri: to.StringPtr(blobUri), + }, + }, + }, + } + + *s.template.Resources = append(*s.template.Resources, *managedDiskResource) + return nil +} + +func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer, sku, version string) error { + resource, err := s.getResourceByType(resourceVirtualMachine) + if err != nil { + return err + } + + managedDiskImageName := "packerManagedDisk" + + resourceId := s.toResourceID(resourceManagedDisk, managedDiskImageName) + profile := resource.Properties.StorageProfile + profile.ImageReference = &compute.ImageReference{ + ID: to.StringPtr(resourceId), + } + profile.OsDisk.Vhd = nil + *resource.DependsOn = append(*resource.DependsOn, fmt.Sprintf("[concat('%s/', '%s')]", resourceManagedDisk, managedDiskImageName)) + + managedDiskResource := &Resource{ + Type: to.StringPtr(resourceManagedDisk), + Name: &managedDiskImageName, + ApiVersion: to.StringPtr(s.toVariable("managedDiskApiVersion")), + Location: to.StringPtr(location), + Properties: &Properties{ + StorageProfile: &compute.StorageProfile{ + ImageReference: &compute.ImageReference{ + Publisher: &publisher, + Offer: &offer, + Sku: &sku, + Version: &version, + }, + }, + }, + } + + *s.template.Resources = append(*s.template.Resources, *managedDiskResource) return nil } @@ -347,7 +424,11 @@ const BasicTemplate = `{ }, "variables": { "addressPrefix": "10.0.0.0/16", - "apiVersion": "2015-06-15", + "apiVersion": "2017-03-30", + "managedDiskApiVersion": "2017-03-30", + "networkInterfacesApiVersion": "2017-04-01", + "publicIPAddressApiVersion": "2017-04-01", + "virtualNetworksApiVersion": "2017-04-01", "location": "[resourceGroup().location]", "nicName": "packerNic", "publicIPAddressName": "packerPublicIP", @@ -363,7 +444,7 @@ const BasicTemplate = `{ }, "resources": [ { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('publicIPAddressApiVersion')]", "type": "Microsoft.Network/publicIPAddresses", "name": "[variables('publicIPAddressName')]", "location": "[variables('location')]", @@ -375,7 +456,7 @@ const BasicTemplate = `{ } }, { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('virtualNetworksApiVersion')]", "type": "Microsoft.Network/virtualNetworks", "name": "[variables('virtualNetworkName')]", "location": "[variables('location')]", @@ -396,7 +477,7 @@ const BasicTemplate = `{ } }, { - "apiVersion": "[variables('apiVersion')]", + "apiVersion": "[variables('networkInterfacesApiVersion')]", "type": "Microsoft.Network/networkInterfaces", "name": "[variables('nicName')]", "location": "[variables('location')]", diff --git a/vendor/github.com/Azure/azure-sdk-for-go/arm/compute/models.go b/vendor/github.com/Azure/azure-sdk-for-go/arm/compute/models.go index a9524daef..2838ac5f0 100644 --- a/vendor/github.com/Azure/azure-sdk-for-go/arm/compute/models.go +++ b/vendor/github.com/Azure/azure-sdk-for-go/arm/compute/models.go @@ -400,19 +400,19 @@ type APIErrorBase struct { // AvailabilitySet is create or update availability set parameters. type AvailabilitySet struct { autorest.Response `json:"-"` - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Type *string `json:"type,omitempty"` - Location *string `json:"location,omitempty"` - Tags *map[string]*string `json:"tags,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` *AvailabilitySetProperties `json:"properties,omitempty"` - Sku *Sku `json:"sku,omitempty"` + Sku *Sku `json:"sku,omitempty"` } // AvailabilitySetListResult is the List Availability Set operation response. type AvailabilitySetListResult struct { autorest.Response `json:"-"` - Value *[]AvailabilitySet `json:"value,omitempty"` + Value *[]AvailabilitySet `json:"value,omitempty"` } // AvailabilitySetProperties is the instance view of a resource. @@ -480,11 +480,11 @@ type HardwareProfile struct { // Image is describes an Image. type Image struct { autorest.Response `json:"-"` - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Type *string `json:"type,omitempty"` - Location *string `json:"location,omitempty"` - Tags *map[string]*string `json:"tags,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` *ImageProperties `json:"properties,omitempty"` } @@ -501,8 +501,8 @@ type ImageDataDisk struct { // ImageListResult is the List Image operation response. type ImageListResult struct { autorest.Response `json:"-"` - Value *[]Image `json:"value,omitempty"` - NextLink *string `json:"nextLink,omitempty"` + Value *[]Image `json:"value,omitempty"` + NextLink *string `json:"nextLink,omitempty"` } // ImageListResultPreparer prepares a request to retrieve the next set of results. It returns @@ -586,8 +586,8 @@ type LinuxConfiguration struct { // ListUsagesResult is the List Usages operation response. type ListUsagesResult struct { autorest.Response `json:"-"` - Value *[]Usage `json:"value,omitempty"` - NextLink *string `json:"nextLink,omitempty"` + Value *[]Usage `json:"value,omitempty"` + NextLink *string `json:"nextLink,omitempty"` } // ListUsagesResultPreparer prepares a request to retrieve the next set of results. It returns @@ -605,13 +605,13 @@ func (client ListUsagesResult) ListUsagesResultPreparer() (*http.Request, error) // ListVirtualMachineExtensionImage is type ListVirtualMachineExtensionImage struct { autorest.Response `json:"-"` - Value *[]VirtualMachineExtensionImage `json:"value,omitempty"` + Value *[]VirtualMachineExtensionImage `json:"value,omitempty"` } // ListVirtualMachineImageResource is type ListVirtualMachineImageResource struct { autorest.Response `json:"-"` - Value *[]VirtualMachineImageResource `json:"value,omitempty"` + Value *[]VirtualMachineImageResource `json:"value,omitempty"` } // LongRunningOperationProperties is compute-specific operation properties, @@ -628,7 +628,7 @@ type ManagedDiskParameters struct { // NetworkInterfaceReference is describes a network interface reference. type NetworkInterfaceReference struct { - ID *string `json:"id,omitempty"` + ID *string `json:"id,omitempty"` *NetworkInterfaceReferenceProperties `json:"properties,omitempty"` } @@ -646,16 +646,19 @@ type NetworkProfile struct { // OperationStatusResponse is operation status response type OperationStatusResponse struct { autorest.Response `json:"-"` - Name *string `json:"name,omitempty"` - Status *string `json:"status,omitempty"` - StartTime *date.Time `json:"startTime,omitempty"` - EndTime *date.Time `json:"endTime,omitempty"` - Error *APIError `json:"error,omitempty"` + Name *string `json:"name,omitempty"` + Status *string `json:"status,omitempty"` + StartTime *date.Time `json:"startTime,omitempty"` + EndTime *date.Time `json:"endTime,omitempty"` + Error *APIError `json:"error,omitempty"` } // OSDisk is describes an Operating System disk. type OSDisk struct { OsType OperatingSystemTypes `json:"osType,omitempty"` + OsState *string `json:"osState,omitempty"` + StorageAccountType *string `json:"storageAccountType,omitempty"` + BlobUri *string `json:"blobUri,omitempty"` EncryptionSettings *DiskEncryptionSettings `json:"encryptionSettings,omitempty"` Name *string `json:"name,omitempty"` Vhd *VirtualHardDisk `json:"vhd,omitempty"` @@ -784,15 +787,15 @@ type VirtualHardDisk struct { // VirtualMachine is describes a Virtual Machine. type VirtualMachine struct { autorest.Response `json:"-"` - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Type *string `json:"type,omitempty"` - Location *string `json:"location,omitempty"` - Tags *map[string]*string `json:"tags,omitempty"` - Plan *Plan `json:"plan,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + Plan *Plan `json:"plan,omitempty"` *VirtualMachineProperties `json:"properties,omitempty"` - Resources *[]VirtualMachineExtension `json:"resources,omitempty"` - Identity *VirtualMachineIdentity `json:"identity,omitempty"` + Resources *[]VirtualMachineExtension `json:"resources,omitempty"` + Identity *VirtualMachineIdentity `json:"identity,omitempty"` } // VirtualMachineAgentInstanceView is the instance view of the VM Agent running @@ -813,7 +816,7 @@ type VirtualMachineCaptureParameters struct { // VirtualMachineCaptureResult is resource Id. type VirtualMachineCaptureResult struct { autorest.Response `json:"-"` - ID *string `json:"id,omitempty"` + ID *string `json:"id,omitempty"` *VirtualMachineCaptureResultProperties `json:"properties,omitempty"` } @@ -826,11 +829,11 @@ type VirtualMachineCaptureResultProperties struct { // VirtualMachineExtension is describes a Virtual Machine Extension. type VirtualMachineExtension struct { autorest.Response `json:"-"` - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Type *string `json:"type,omitempty"` - Location *string `json:"location,omitempty"` - Tags *map[string]*string `json:"tags,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` *VirtualMachineExtensionProperties `json:"properties,omitempty"` } @@ -845,11 +848,11 @@ type VirtualMachineExtensionHandlerInstanceView struct { // VirtualMachineExtensionImage is describes a Virtual Machine Extension Image. type VirtualMachineExtensionImage struct { autorest.Response `json:"-"` - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Type *string `json:"type,omitempty"` - Location *string `json:"location,omitempty"` - Tags *map[string]*string `json:"tags,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` *VirtualMachineExtensionImageProperties `json:"properties,omitempty"` } @@ -897,10 +900,10 @@ type VirtualMachineIdentity struct { // VirtualMachineImage is describes a Virtual Machine Image. type VirtualMachineImage struct { autorest.Response `json:"-"` - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Location *string `json:"location,omitempty"` - Tags *map[string]*string `json:"tags,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` *VirtualMachineImageProperties `json:"properties,omitempty"` } @@ -935,8 +938,8 @@ type VirtualMachineInstanceView struct { // VirtualMachineListResult is the List Virtual Machine operation response. type VirtualMachineListResult struct { autorest.Response `json:"-"` - Value *[]VirtualMachine `json:"value,omitempty"` - NextLink *string `json:"nextLink,omitempty"` + Value *[]VirtualMachine `json:"value,omitempty"` + NextLink *string `json:"nextLink,omitempty"` } // VirtualMachineListResultPreparer prepares a request to retrieve the next set of results. It returns @@ -968,15 +971,15 @@ type VirtualMachineProperties struct { // VirtualMachineScaleSet is describes a Virtual Machine Scale Set. type VirtualMachineScaleSet struct { autorest.Response `json:"-"` - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Type *string `json:"type,omitempty"` - Location *string `json:"location,omitempty"` - Tags *map[string]*string `json:"tags,omitempty"` - Sku *Sku `json:"sku,omitempty"` - Plan *Plan `json:"plan,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + Sku *Sku `json:"sku,omitempty"` + Plan *Plan `json:"plan,omitempty"` *VirtualMachineScaleSetProperties `json:"properties,omitempty"` - Identity *VirtualMachineScaleSetIdentity `json:"identity,omitempty"` + Identity *VirtualMachineScaleSetIdentity `json:"identity,omitempty"` } // VirtualMachineScaleSetDataDisk is describes a virtual machine scale set data @@ -993,8 +996,8 @@ type VirtualMachineScaleSetDataDisk struct { // VirtualMachineScaleSetExtension is describes a Virtual Machine Scale Set // Extension. type VirtualMachineScaleSetExtension struct { - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` *VirtualMachineScaleSetExtensionProperties `json:"properties,omitempty"` } @@ -1028,9 +1031,9 @@ type VirtualMachineScaleSetIdentity struct { // scale set. type VirtualMachineScaleSetInstanceView struct { autorest.Response `json:"-"` - VirtualMachine *VirtualMachineScaleSetInstanceViewStatusesSummary `json:"virtualMachine,omitempty"` - Extensions *[]VirtualMachineScaleSetVMExtensionsSummary `json:"extensions,omitempty"` - Statuses *[]InstanceViewStatus `json:"statuses,omitempty"` + VirtualMachine *VirtualMachineScaleSetInstanceViewStatusesSummary `json:"virtualMachine,omitempty"` + Extensions *[]VirtualMachineScaleSetVMExtensionsSummary `json:"extensions,omitempty"` + Statuses *[]InstanceViewStatus `json:"statuses,omitempty"` } // VirtualMachineScaleSetInstanceViewStatusesSummary is instance view statuses @@ -1042,8 +1045,8 @@ type VirtualMachineScaleSetInstanceViewStatusesSummary struct { // VirtualMachineScaleSetIPConfiguration is describes a virtual machine scale // set network profile's IP configuration. type VirtualMachineScaleSetIPConfiguration struct { - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` *VirtualMachineScaleSetIPConfigurationProperties `json:"properties,omitempty"` } @@ -1060,8 +1063,8 @@ type VirtualMachineScaleSetIPConfigurationProperties struct { // response. type VirtualMachineScaleSetListResult struct { autorest.Response `json:"-"` - Value *[]VirtualMachineScaleSet `json:"value,omitempty"` - NextLink *string `json:"nextLink,omitempty"` + Value *[]VirtualMachineScaleSet `json:"value,omitempty"` + NextLink *string `json:"nextLink,omitempty"` } // VirtualMachineScaleSetListResultPreparer prepares a request to retrieve the next set of results. It returns @@ -1080,8 +1083,8 @@ func (client VirtualMachineScaleSetListResult) VirtualMachineScaleSetListResultP // Skus operation response. type VirtualMachineScaleSetListSkusResult struct { autorest.Response `json:"-"` - Value *[]VirtualMachineScaleSetSku `json:"value,omitempty"` - NextLink *string `json:"nextLink,omitempty"` + Value *[]VirtualMachineScaleSetSku `json:"value,omitempty"` + NextLink *string `json:"nextLink,omitempty"` } // VirtualMachineScaleSetListSkusResultPreparer prepares a request to retrieve the next set of results. It returns @@ -1100,8 +1103,8 @@ func (client VirtualMachineScaleSetListSkusResult) VirtualMachineScaleSetListSku // operation response. type VirtualMachineScaleSetListWithLinkResult struct { autorest.Response `json:"-"` - Value *[]VirtualMachineScaleSet `json:"value,omitempty"` - NextLink *string `json:"nextLink,omitempty"` + Value *[]VirtualMachineScaleSet `json:"value,omitempty"` + NextLink *string `json:"nextLink,omitempty"` } // VirtualMachineScaleSetListWithLinkResultPreparer prepares a request to retrieve the next set of results. It returns @@ -1125,8 +1128,8 @@ type VirtualMachineScaleSetManagedDiskParameters struct { // VirtualMachineScaleSetNetworkConfiguration is describes a virtual machine // scale set network profile's network configurations. type VirtualMachineScaleSetNetworkConfiguration struct { - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` *VirtualMachineScaleSetNetworkConfigurationProperties `json:"properties,omitempty"` } @@ -1205,16 +1208,16 @@ type VirtualMachineScaleSetStorageProfile struct { // machine. type VirtualMachineScaleSetVM struct { autorest.Response `json:"-"` - ID *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Type *string `json:"type,omitempty"` - Location *string `json:"location,omitempty"` - Tags *map[string]*string `json:"tags,omitempty"` - InstanceID *string `json:"instanceId,omitempty"` - Sku *Sku `json:"sku,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + InstanceID *string `json:"instanceId,omitempty"` + Sku *Sku `json:"sku,omitempty"` *VirtualMachineScaleSetVMProperties `json:"properties,omitempty"` - Plan *Plan `json:"plan,omitempty"` - Resources *[]VirtualMachineExtension `json:"resources,omitempty"` + Plan *Plan `json:"plan,omitempty"` + Resources *[]VirtualMachineExtension `json:"resources,omitempty"` } // VirtualMachineScaleSetVMExtensionsSummary is extensions summary for virtual @@ -1255,8 +1258,8 @@ type VirtualMachineScaleSetVMInstanceView struct { // operation response. type VirtualMachineScaleSetVMListResult struct { autorest.Response `json:"-"` - Value *[]VirtualMachineScaleSetVM `json:"value,omitempty"` - NextLink *string `json:"nextLink,omitempty"` + Value *[]VirtualMachineScaleSetVM `json:"value,omitempty"` + NextLink *string `json:"nextLink,omitempty"` } // VirtualMachineScaleSetVMListResultPreparer prepares a request to retrieve the next set of results. It returns @@ -1309,7 +1312,7 @@ type VirtualMachineSize struct { // VirtualMachineSizeListResult is the List Virtual Machine operation response. type VirtualMachineSizeListResult struct { autorest.Response `json:"-"` - Value *[]VirtualMachineSize `json:"value,omitempty"` + Value *[]VirtualMachineSize `json:"value,omitempty"` } // VirtualMachineStatusCodeCount is the status code and count of the virtual